asterisk-13.1.0/0000755000000000000000000000000012443600120012107 5ustar rootrootasterisk-13.1.0/Makefile.moddir_rules0000644000000000000000000001343512040007604016245 0ustar rootroot# # Asterisk -- An open source telephony toolkit. # # Makefile rules for subdirectories containing modules # # Copyright (C) 2006, Digium, Inc. # # Kevin P. Fleming # # This program is free software, distributed under the terms of # the GNU General Public License # # Makefile rules for building modules. # In most cases, we set target-specific variables for certain targets # (remember that they apply recursively to prerequisites). # Also note that we can only set one variable per rule, so we have to # repeat the left hand side to set multiple variables. ifeq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),) _ASTCFLAGS+=${GC_CFLAGS} endif ifneq ($(findstring STATIC_BUILD,$(MENUSELECT_CFLAGS)),) STATIC_BUILD=-static endif include $(ASTTOPDIR)/Makefile.rules # If MODULE_PREFIX is defined, use it to run the standard functions to set # C_MODS, CC_MODS, LOADABLE_MODS and EMBEDDED_MODS. # Each word of MODULE_PREFIX is a prefix for filenames that we consider # valid C or CC modules (eg. app, func ...). Note that the underscore # is added here, and does not need to be in MODULE_PREFIX # # Use MODULE_EXCLUDE to specify additional modules to exclude. ifneq ($(MODULE_PREFIX),) ALL_C_MODS:= ALL_CC_MODS:= ALL_C_MODS+=$(foreach p,$(MODULE_PREFIX),$(patsubst %.c,%,$(wildcard $(p)_*.c))) ALL_CC_MODS+=$(foreach p,$(MODULE_PREFIX),$(patsubst %.cc,%,$(wildcard $(p)_*.cc))) endif C_MODS:=$(filter-out $(MENUSELECT_$(MENUSELECT_CATEGORY)),$(ALL_C_MODS)) CC_MODS:=$(filter-out $(MENUSELECT_$(MENUSELECT_CATEGORY)),$(ALL_CC_MODS)) ifneq ($(findstring EMBED_$(MENUSELECT_CATEGORY),$(MENUSELECT_EMBED)),) EMBEDDED_MODS:=$(C_MODS) $(CC_MODS) else LOADABLE_MODS:=$(C_MODS) $(CC_MODS) endif # Both C++ and C++ sources need their module name in AST_MODULE # We also pass whatever _INCLUDE list is generated by menuselect # (they are stored in file 'makeopts'). This is also necessary # for components used to build modules, which can't be determined # by the rules in this file, so the MOD_ASTCFLAGS definition # is used to collect the required flags for a module... which can # then be used any place they are required. MOD_ASTCFLAGS=-DAST_MODULE=\"$(1)\" $(MENUSELECT_OPTS_$(1):%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_$(1)),$(value $(dep)_INCLUDE)) $(addsuffix .oo,$(CC_MODS)) $(addsuffix .o,$(C_MODS)): \ _ASTCFLAGS+=$(call MOD_ASTCFLAGS,$*) ifeq ($(findstring $(OSARCH), mingw32 cygwin ),) # don't define -fPIC on mingw32 and cygwin, it is the default $(LOADABLE_MODS:%=%.so): _ASTCFLAGS+=-fPIC endif # For loadable modules, pass _LIB and _LDFLAGS from menuselect. $(LOADABLE_MODS:%=%.so): LIBS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LIB)) $(LOADABLE_MODS:%=%.so): _ASTLDFLAGS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LDFLAGS)) $(EMBEDDED_MODS:%=%.o): _ASTCFLAGS+=-DEMBEDDED_MODULE=$* $(addsuffix .so,$(filter $(LOADABLE_MODS),$(C_MODS))): %.so: %.o $(addsuffix .so,$(filter $(LOADABLE_MODS),$(CC_MODS))): %.so: %.oo modules.link: $(addsuffix .eo,$(filter $(EMBEDDED_MODS),$(C_MODS))) .PHONY: clean uninstall _all moduleinfo makeopts ifneq ($(LOADABLE_MODS),) _all: $(LOADABLE_MODS:%=%.so) ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) # linker options and extra libraries for cygwin SOLINK=-Wl,--out-implib=lib$@.a -shared LIBS+=-L$(ASTTOPDIR)/main -lasterisk -L$(ASTTOPDIR)/res $($@_LIBS) # additional libraries in res/ endif endif ifneq ($(EMBEDDED_MODS),) _all: modules.link __embed_ldscript: @echo "../$(SUBDIR)/modules.link" __embed_ldflags: @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(C_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LDFLAGS))" @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(CC_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LDFLAGS))" __embed_libs: @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(C_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LIB))" @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(CC_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LIB))" else __embed_ldscript: __embed_ldflags: __embed_libs: endif modules.link: @rm -f $@ @for file in $(patsubst %,$(SUBDIR)/%,$(filter %.eo,$^)); do echo "INPUT (../$${file})" >> $@; done @for file in $(patsubst %,$(SUBDIR)/%,$(filter-out %.eo,$^)); do echo "INPUT (../$${file})" >> $@; done clean:: rm -f *.so *.o *.oo *.eo *.i *.ii rm -f .*.d rm -f *.s *.i rm -f modules.link install:: all @echo "Installing modules from `basename $(CURDIR)`..." @for x in $(LOADABLE_MODS:%=%.so); do $(INSTALL) -m 755 $$x "$(DESTDIR)$(ASTMODDIR)" ; done uninstall:: dist-clean:: rm -f .*.moduleinfo .moduleinfo rm -f .*.makeopts .makeopts rm -f *.exports .%.moduleinfo: %.c @echo "" > $@ $(AWK) -f $(ASTTOPDIR)/build_tools/get_moduleinfo $< >> $@ echo "" >> $@ .%.moduleinfo: %.cc @echo "" > $@ $(AWK) -f $(ASTTOPDIR)/build_tools/get_moduleinfo $< >> $@ echo "" >> $@ .moduleinfo:: $(addsuffix .moduleinfo,$(addprefix .,$(sort $(ALL_C_MODS) $(ALL_CC_MODS)))) @echo "" > $@ @cat $^ >> $@ @echo "" >> $@ moduleinfo: .moduleinfo @cat $< .%.makeopts: %.c @$(AWK) -f $(ASTTOPDIR)/build_tools/get_makeopts $< > $@ .%.makeopts: %.cc @$(AWK) -f $(ASTTOPDIR)/build_tools/get_makeopts $< > $@ .makeopts:: $(addsuffix .makeopts,$(addprefix .,$(ALL_C_MODS) $(ALL_CC_MODS))) @cat $^ > $@ makeopts: .makeopts @cat $< ifneq ($(wildcard .*.d),) include .*.d endif asterisk-13.1.0/missing0000755000000000000000000001452010427665642013534 0ustar rootroot#! /bin/sh # Common stub for a few missing GNU programs while installing. # Copyright (C) 1996, 1997, 2001, 2002 Free Software Foundation, Inc. # Franc,ois Pinard , 1996. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. if test $# -eq 0; then echo 1>&2 "Try \`$0 --help' for more information" exit 1 fi # In the cases where this matters, `missing' is being run in the # srcdir already. if test -f configure.in; then configure_ac=configure.ac else configure_ac=configure.in fi case "$1" in -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an error status if there is no known handling for PROGRAM. Options: -h, --help display this help and exit -v, --version output version information and exit Supported PROGRAM values: aclocal touch file \`aclocal.m4' autoconf touch file \`configure' autoheader touch file \`config.h.in' automake touch all \`Makefile.in' files bison create \`y.tab.[ch]', if possible, from existing .[ch] flex create \`lex.yy.c', if possible, from existing .c lex create \`lex.yy.c', if possible, from existing .c makeinfo touch the output file yacc create \`y.tab.[ch]', if possible, from existing .[ch]" ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing - GNU libit 0.0" ;; -*) echo 1>&2 "$0: Unknown \`$1' option" echo 1>&2 "Try \`$0 --help' for more information" exit 1 ;; aclocal*) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acinclude.m4' or \`$configure_ac'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." touch aclocal.m4 ;; autoconf) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`$configure_ac'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." touch configure ;; autoheader) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acconfig.h' or \`$configure_ac'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' $configure_ac` test -z "$files" && files="config.h" touch_files= for f in $files; do case "$f" in *:*) touch_files="$touch_files "`echo "$f" | sed -e 's/^[^:]*://' -e 's/:.*//'`;; *) touch_files="$touch_files $f.in";; esac done touch $touch_files ;; automake*) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`Makefile.am', \`acinclude.m4' or \`$configure_ac'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." find . -type f -name Makefile.am -print | sed 's/\.am$/.in/' | while read f; do touch "$f"; done ;; bison|yacc) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.y' file. You may need the \`Bison' package in order for those modifications to take effect. You can get \`Bison' from any GNU archive site." rm -f y.tab.c y.tab.h if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.y) SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.c fi SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.h fi ;; esac fi if [ ! -f y.tab.h ]; then echo >y.tab.h fi if [ ! -f y.tab.c ]; then echo 'main() { return 0; }' >y.tab.c fi ;; lex|flex) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.l' file. You may need the \`Flex' package in order for those modifications to take effect. You can get \`Flex' from any GNU archive site." rm -f lex.yy.c if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.l) SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" lex.yy.c fi ;; esac fi if [ ! -f lex.yy.c ]; then echo 'main() { return 0; }' >lex.yy.c fi ;; makeinfo) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.texi' or \`.texinfo' file, or any other file indirectly affecting the aspect of the manual. The spurious call might also be the consequence of using a buggy \`make' (AIX, DU, IRIX). You might want to install the \`Texinfo' package or the \`GNU make' package. Grab either from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` fi touch $file ;; *) echo 1>&2 "\ WARNING: \`$1' is needed, and you do not seem to have it handy on your system. You might have modified some files without having the proper tools for further handling them. Check the \`README' file, it often tells you about the needed prerequirements for installing this package. You may also peek at any GNU archive site, in case some other package would contain this missing \`$1' program." exit 1 ;; esac exit 0 asterisk-13.1.0/CREDITS0000644000000000000000000003107112255133535013145 0ustar rootroot === DEVELOPMENT SUPPORT === We'd like to thank the following companies for helping fund development of Asterisk. * Pilosoft, Inc. - for supporting ADSI development in Asterisk * Asterlink, Inc. - for supporting broad Asterisk development * GFS - for supporting ALSA development * Telesthetic - for supporting SIP development * Christos Ricudis - for substantial code contributions * nic.at - ENUM support in Asterisk * Paul Bagyenda, Digital Solutions - for initial Voicetronix driver development. * John Todd, TalkPlus, Inc. and JR Richardson, Ntegrated Solutions. for funding the development of SIP Session Timers support. * Omnitor AB, Gunnar Hellstr�m, for funding work with videocaps, T.140 RED, originate with video/text and many more contributions. * ClearIT AB for work with meetme, res_mutestream, RTCP, manager and tonezones. * NetNation Communications (www.netnation.com) Kevin Lindsay Persistent Dynamic Queue Members * inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr) Priorities in queues * Voop AS, Nuvio Inc, Inotel S.A and Foniris Telecom A/S - funding for rewrite of SIP transfers === WISHLIST CONTRIBUTERS === We'd like to thank the following for contributing to our wishlist * Jeremy McNamara - SpeeX support * Nick Seraphin - RDNIS support * Gary - Phonejack ADSI (in progress) * Wasim - Hangup detect === HARDWARE DONORS === We'd like to thank the following for granting access to hardware for testing. * Thanks to QuickNet Technologies for their donation of an Internet PhoneJack and Linejack card to the project. (http://www.quicknet.net) * Thanks to VoipSupply for their donation of Sipura ATAs to the project for T.38 testing. (http://www.voipsupply.com) * Thanks to Grandstream for their donation of ATAs to the project for T.38 testing. (http://www.grandstream.com) === MISCELLANEOUS PATCHES === We'd like to thank the following for their patches * Jim Dixon - Zapata Telephony and app_rpt http://www.zapatatelephony.org/app_rpt.html * Russell Bryant - Asterisk release manager and countless enhancements and bug fixes. russell(AT)digium.com * Anthony Minessale II - Countless big and small fixes, and relentless forward push. ChanSpy, ForkCDR, ControlPlayback, While/EndWhile, DumpChan, Dictate, MacroIf, ExecIf, ExecIfTime, RetryDial, MixMonitor applications; many realtime concepts and implementation pieces, including res_config_odbc; format_slin; cdr_custom; several features in Dial including L(), G() and enhancements to M() and D(); several CDR enhancements including CDR variables; attended transfer; one touch record; native MOH; manager eventmask; command line '-t' flag to allow recording/voicemail on nfs shares; #exec command and multiline comments in config files; setvar in iax and sip configs. anthmct(AT)yahoo.com http://www.asterlink.com * James Golovich - Innumerable contributions, including SIP TCP and TLS support. You can find him and asterisk-perl at http://asterisk.gnuinter.net * Andre Bierwirth - Extension hints and status * Jean-Denis Girard - Various contributions from the South Pacific Islands jd-girard(AT)sysnux.pf http://www.sysnux.pf * William Jordan / Vonage - MySQL enhancements to Voicemail wjordan(AT)vonage.com * Jac Kersing - Various fixes * Steven Critchfield - Seek and Trunc functions for playback and recording critch(AT)basesys.com * Jefferson Noxon - app_lookupcidname, app_db, and various other contributions * Klaus-Peter Junghanns - in-band DTMF on SIP and MGCP * Ross Finlayson - Dynamic RTP payload support * Mahmut Fettahlioglu - Audio recording, music-on-hold changes, alaw file format, and various fixes. Can be contacted at mahmut(AT)oa.com.au * James Dennis - Cisco SIP compatibility patches to work with SIP service providers. Can be contacted at asterisk(AT)jdennis.net * Tilghman Lesher - ast_localtime(); ast_say_date_with_format(); GotoIfTime, SayUnixTime, HasNewVoicemail applications; CUT, SORT, EVAL, CURL, FIELDQTY, STRFTIME, some QUEUE* functions; func_odbc, cdr_adaptive_odbc, and other innumerable bug fixes. tilghman(AT)digium.com http://asterisk.drunkcoder.com * Jayson Vantuyl - Manager protocol changes, various other bugs. jvantuyl(AT)computingedge.net * Thorsten Lockert - OpenBSD, FreeBSD ports, making MacOS X port run on 10.3, dialplan include verification, route lookup on OpenBSD, SNMP agent support (res_snmp), various other bugs. tholo(AT)sigmasoft.com * Josh Roberson - chan_zap reload support, Advanced Voicemail Features, & other misc. patches. josh(AT)asteriasgi.com http://www.asteriasgi.com * William Waites - syslog support, SIP NAT traversal for SIP-UA. ww(AT)styx.org * Rich Murphey - Porting to FreeBSD, NetBSD, OpenBSD, and Darwin. rich(AT)whiteoaklabs.com http://whiteoaklabs.com * Simon Lockhart - Porting to Solaris (based on work of Logan ???) simon(AT)slimey.org * Olle E. Johansson - SIP RFC compliance, documentation and testing, testing, SIP outbound proxy support, Manager 1.1 update, SIP transfer support, SIP presence support, SIP call state updates (dialog-info), QUEUE_EXISTS function, device state provider architecture, multiparking (together with mvanbaak), meetme and parking device states, MiniVM - the small voicemail system, many documentation updates/corrections, and many bug fixes. oej(AT)edvina.net, http://edvina.net * Steve Kann - new jitter buffer for IAX2 stevek(AT)stevek.com * Constantine Filin - major contributions to the Asterisk Realtime Architecture * Steve Murphy - privacy support, $[ ] parser upgrade, AEL2 parser upgrade. murf(AT)digium.com * Claude Patry - bug fixes, feature enhancements, and bug marshalling cpatry(AT)gmail.com * Miroslav Nachev, miro(AT)space-comm.com COSMOS Software Enterprises, Ltd. Variable for No Answer Timeout for Attended Transfer * Slav Klenov & Vanheuverzwijn Joachim - development of the generic jitterbuffer Securax Ltd. info(AT)securax.be * Roy Sigurd Karlsbakk - providing funding for generic jitterbuffer development roy(AT)karlsbakk.net, Briiz Telecom AS * Voop AS, Nuvio Inc, Inotel S.A and Foniris Telecom A/S - rewrite of SIP transfers * Philippe Sultan - RADIUS CDR module, many fixes to res_jabber and gtalk/jingle channel drivers. INRIA, http://www.inria.fr/ * John Martin, Aupix - Improved video support in the SIP channel T.140 text support in RTP/SIP * Steve Underwood - Provided T.38 pass through support. * George Konstantoulakis - Support for Greek in voicemail added by InAccess Networks (work funded by HOL, www.hol.gr) gkon(AT)inaccessnetworks.com * Daniel Nylander - Support for Swedish and Norwegian languages in voicemail. http://www.danielnylander.se/ * Stojan Sljivic - An option for maximum number of messsages per mailbox in voicemail. Also an issue with voicemail synchronization has been fixed. GDS Partners www.gdspartners.com stojan.sljivic(AT)gdspartners.com * Bartosz Supczinski - Support for Polish added by DIR (www.dir.pl) Bartosz.Supczinski(AT)dir.pl * James Rothenberger - Support for IMAP storage integration added by OneBizTone LLC Work funded by University of Pennsylvania jar(AT)onebiztone.com * Paul Cadach - Bringing chan_h323 up to date, bug fixes, and more! * Voop AS - Financial support for a lot of work with the SIP driver and the IAX trunk MTU patch * Cedric Hans - Development of chan_unistim cedric.hans(AT)mlkj.net * Takao Takahashi & Mina Naguib - chan_unistim improvements for smaller devices * Sergio Fadda - console_video: video support for chan_oss and chan_alsa * Marta Carbone - console_video and the astobj2 framework * Luigi Rizzo - astobj2, console_video, windows build, chan_oss cleanup, and a bunch of infrastructure work (loader, new_cli, ...) * Brett Bryant - digit option for musiconhold selection, ENUMQUERY and ENUMRESULT functions, feature group configuration for features.conf, per-file CLI debug and verbose settings, TCP and TLS support for SIP, and various bug fixes. brettbryant(AT)gmail.com * Sergey Tamkovich - Realtime support for MusicOnHold, store and destroy realtime methods and implementations for odbc, sqlite, and pgsql realtime drivers, attended transfer updates, multiple speeds for ControlPlayback, and multiple bug fixes See http://voip-info.org/users/view/sergee serg(AT)voipsolutions.ru * Klaus Darillon - the SIPremoveHeader function in chan_sip and SIP Path Support. * Moises Silva (moy) - for writing LibOpenR2, and providing support for it in chan_dahdi moises.silva(AT)gmail.com * Eliel C. Sardanons - XML documentation implementation, and various other contributions eliels(AT)gmail.com * Sean Bright - Snom call pickup, newt interface for menuselect, cdr_tds rewrite, countless other improvements, fixes, and good ideas. sean(AT)malleable.com * Jan Kal�b - Calendaring support for Exchange Server 2007+ via Exchange Web Services. * University of Oslo (uio.no), Norway - SIP Max-Forwards setting support (developed by oej) * FCCN, Lissabon, Portugal - SIP show channels CLI command (developed by oej) * Viagenie, Canada - IPv6 support in socket layers and SIP implementation Developers: Marc Blanchet, Simon Perreault and Jean-Philippe Dionne * ClearIT AB, Sweden - res_mutestream, queue_exists and various other patches (developed by oej) * Despegar.com, Argentina - AstData API implementation, also sponsored by Google as part of the gsoc/2009 program (developed by Eliel) * Philippe Lindheimer - DEV_STATE additions to CCSS * Andrew "lathama" Latham Doxygen, HTTP-Static, Phoneprov, make update * George Joseph - PJSIP CLI commands, PJSIP_HEADER dialplan function === OTHER CONTRIBUTIONS === We'd like to thank the following for their listed contributions. * John Todd - Monkey sounds and associated teletorture prompt * Michael Jerris - bug marshaling * Leif Madsen, Jared Smith and Jim van Meggelen - the Asterisk book available under a Creative Commons License at http://www.asteriskdocs.org * Brian M. Clapper - poll.c emulation This product includes software developed by Brian M. Clapper === HOLD MUSIC === We'd like to thank the following for hold music * Music provided by www.opsound.org === OTHER SOURCE CODE IN ASTERISK === We'd like to thank the following for their code use * Asterisk uses libedit, the lightweight readline replacement from NetBSD. * The cdr_radius module uses libradiusclient-ng, which is also from NetBSD. * They are BSD-licensed and require the following statement: This product includes software developed by the NetBSD Foundation, Inc. and its contributors. * Digium did not implement the codecs in Asterisk. Here is the copyright on the GSM source: Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann, Technische Universitaet Berlin Any use of this software is permitted provided that this notice is not removed and that neither the authors nor the Technische Universitaet Berlin are deemed to have made any representations as to the suitability of this software for any purpose nor are held responsible for any defects of this software. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. As a matter of courtesy, the authors request to be informed about uses this software has found, about bugs in this software, and about any improvements that may be of general interest. Berlin, 28.11.1994 Jutta Degener Carsten Bormann And the copyright on the ADPCM source: Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands. All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. asterisk-13.1.0/makeopts.in0000644000000000000000000001563512423503330014277 0ustar rootroot# NOTE: Names of _INCLUDE and _LIB entries in this file must be # the exact uppercase equivalents of the names used for # dependencies in menuselect for the same package. CC=@PTHREAD_CC@ HOST_CC=cc BUILD_CC=cc CXX=@CXX@ INSTALL=@INSTALL@ AWK=@AWK@ BISON=@BISON@ FLEX=@FLEX@ GREP=@GREP@ PYTHON=@PYTHON@ MAKE=@GNU_MAKE@ AR=@AR@ RANLIB=@RANLIB@ FIND=@FIND@ COMPRESS=@COMPRESS@ BASENAME=@BASENAME@ SHELL=@SHELL@ LN=@LN@ DOXYGEN=@DOXYGEN@ DOT=@DOT@ STRIP=@STRIP@ WGET=@WGET@ FETCH=@FETCH@ DOWNLOAD=@DOWNLOAD@ SOUNDS_CACHE_DIR=@SOUNDS_CACHE_DIR@ RUBBER=@RUBBER@ CATDVI=@CATDVI@ KPATHSEA=@KPATHSEA@ XMLLINT=@XMLLINT@ XMLSTARLET=@XMLSTARLET@ MD5=@MD5@ SHA1SUM=@SHA1SUM@ OPENSSL=@OPENSSL@ LDCONFIG=@LDCONFIG@ GIT=@GIT@ BUILD_PLATFORM=@BUILD_PLATFORM@ BUILD_CPU=@BUILD_CPU@ BUILD_VENDOR=@BUILD_VENDOR@ BUILD_OS=@BUILD_OS@ HOST_PLATFORM=@HOST_PLATFORM@ HOST_CPU=@HOST_CPU@ HOST_VENDOR=@HOST_VENDOR@ HOST_OS=@HOST_OS@ OSARCH=@OSARCH@ OSREV=@PBX_OSREV@ GC_CFLAGS=@GC_CFLAGS@ GC_LDFLAGS=@GC_LDFLAGS@ PTHREAD_CFLAGS=@PTHREAD_CFLAGS@ PTHREAD_LIBS=@PTHREAD_LIBS@ CONFIG_CFLAGS=@CONFIG_CFLAGS@ CONFIG_LDFLAGS=@CONFIG_LDFLAGS@ CONFIG_SIGNED_CHAR=@CONFIG_SIGNED_CHAR@ GNU_LD=@GNU_LD@ WEAKREF=@PBX_WEAKREF@ HAVE_DYNAMIC_LIST=@PBX_DYNAMIC_LIST@ prefix = @prefix@ exec_prefix = @exec_prefix@ datarootdir = @datarootdir@ datadir = @datadir@ includedir = @includedir@ infodir = @infodir@ libdir = @libdir@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ mandir = @mandir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sysconfdir = @sysconfdir@ ASTSBINDIR = @astsbindir@ ASTETCDIR = @astetcdir@ ASTHEADERDIR = @astheaderdir@ ASTLIBDIR = @astlibdir@ ASTMODDIR = @astmoddir@ ASTMANDIR = @astmandir@ astvarlibdir = @astvarlibdir@ ASTVARLIBDIR = @astvarlibdir@ ASTDATADIR = @astdatadir@ ASTDBDIR = @astdbdir@ ASTKEYDIR = @astkeydir@ ASTSPOOLDIR = @astspooldir@ ASTLOGDIR = @astlogdir@ ASTVARRUNDIR = @astvarrundir@ AST_DEVMODE=@AST_DEVMODE@ AST_DEVMODE_STRICT=@AST_DEVMODE_STRICT@ NOISY_BUILD=@NOISY_BUILD@ AST_CODE_COVERAGE=@AST_CODE_COVERAGE@ AST_ASTERISKSSL=@AST_ASTERISKSSL@ AST_DECLARATION_AFTER_STATEMENT=@AST_DECLARATION_AFTER_STATEMENT@ AST_TRAMPOLINES=@AST_TRAMPOLINES@ AST_NO_STRICT_OVERFLOW=@AST_NO_STRICT_OVERFLOW@ AST_SHADOW_WARNINGS=@AST_SHADOW_WARNINGS@ AST_NESTED_FUNCTIONS=@AST_NESTED_FUNCTIONS@ AST_RPATH=@AST_RPATH@ AST_FORTIFY_SOURCE=@AST_FORTIFY_SOURCE@ AST_MARCH_NATIVE=@AST_MARCH_NATIVE@ ALSA_INCLUDE=@ALSA_INCLUDE@ ALSA_LIB=@ALSA_LIB@ BFD_INCLUDE=@BFD_INCLUDE@ BFD_LIB=@BFD_LIB@ BLUETOOTH_INCLUDE=@BLUETOOTH_INCLUDE@ BLUETOOTH_LIB=@BLUETOOTH_LIB@ CURL_INCLUDE=@CURL_INCLUDE@ CURL_LIB=@CURL_LIB@ CURSES_INCLUDE=@CURSES_INCLUDE@ CURSES_LIB=@CURSES_LIB@ CURSES_DIR=@CURSES_DIR@ EDITLINE_LIB=@EDITLINE_LIB@ FREETDS_INCLUDE=@FREETDS_INCLUDE@ FREETDS_LIB=@FREETDS_LIB@ GENERIC_ODBC_INCLUDE=@GENERIC_ODBC_INCLUDE@ GENERIC_ODBC_LIB=@GENERIC_ODBC_LIB@ GMIME_INCLUDE=@GMIME_INCLUDE@ GMIME_LIB=@GMIME_LIB@ HOARD_LIB=@HOARD_LIB@ GSM_INTERNAL=@GSM_INTERNAL@ GSM_INCLUDE=@GSM_INCLUDE@ GSM_LIB=@GSM_LIB@ ILBC_INTERNAL=@ILBC_INTERNAL@ ILBC_INCLUDE=@ILBC_INCLUDE@ ILBC_LIB=@ILBC_LIB@ GTK2_INCLUDE=@GTK2_INCLUDE@ GTK2_LIB=@GTK2_LIB@ ICAL_INCLUDE=@ICAL_INCLUDE@ ICAL_LIB=@ICAL_LIB@ ICONV_INCLUDE=@ICONV_INCLUDE@ ICONV_LIB=@ICONV_LIB@ IKSEMEL_INCLUDE=@IKSEMEL_INCLUDE@ IKSEMEL_LIB=@IKSEMEL_LIB@ IMAP_TK_INCLUDE=@IMAP_TK_INCLUDE@ IMAP_TK_LIB=@IMAP_TK_LIB@ IODBC_INCLUDE=@IODBC_INCLUDE@ IODBC_LIB=@IODBC_LIB@ JACK_INCLUDE=@JACK_INCLUDE@ JACK_LIB=@JACK_LIB@ JANSSON_INCLUDE=@JANSSON_INCLUDE@ JANSSON_LIB=@JANSSON_LIB@ URIPARSER_INCLUDE=@URIPARSER_INCLUDE@ URIPARSER_LIB=@URIPARSER_LIB@ LDAP_INCLUDE=@LDAP_INCLUDE@ LDAP_LIB=@LDAP_LIB@ LIBEDIT_INTERNAL=@LIBEDIT_INTERNAL@ LIBEDIT_INCLUDE=@LIBEDIT_INCLUDE@ LIBEDIT_LIB=@LIBEDIT_LIB@ LUA_INCLUDE=@LUA_INCLUDE@ LUA_LIB=@LUA_LIB@ MYSQLCLIENT_INCLUDE=@MYSQLCLIENT_INCLUDE@ MYSQLCLIENT_LIB=@MYSQLCLIENT_LIB@ NBS_INCLUDE=@NBS_INCLUDE@ NBS_LIB=@NBS_LIB@ NCURSES_INCLUDE=@NCURSES_INCLUDE@ NCURSES_LIB=@NCURSES_LIB@ NCURSES_DIR=@NCURSES_DIR@ NEON_INCLUDE=@NEON_INCLUDE@ NEON_LIB=@NEON_LIB@ NEON29_INCLUDE=@NEON_INCLUDE@ NEON29_LIB=@NEON_LIB@ NETSNMP_INCLUDE=@NETSNMP_INCLUDE@ NETSNMP_LIB=@NETSNMP_LIB@ NEWT_INCLUDE=@NEWT_INCLUDE@ NEWT_LIB=@NEWT_LIB@ OGG_INCLUDE=@OGG_INCLUDE@ OGG_LIB=@OGG_LIB@ OPUS_INCLUDE=@OPUS_INCLUDE@ OPUS_LIB=@OPUS_LIB@ OSPTK_INCLUDE=@OSPTK_INCLUDE@ OSPTK_LIB=@OSPTK_LIB@ # ossaudio can optionally use ffmpeg, x11, sdl and sdl_image. # Because sdl_image in turn depends on sdl, we don't duplicate the include OSS_INCLUDE=@OSS_INCLUDE@ @FFMPEG_INCLUDE@ @SDL_INCLUDE@ @X11_INCLUDE@ OSS_LIB=@OSS_LIB@ @FFMPEG_LIB@ @SDL_LIB@ @SDL_IMAGE_LIB@ @X11_LIB@ PGSQL_INCLUDE=@PGSQL_INCLUDE@ PGSQL_LIB=@PGSQL_LIB@ PJPROJECT_INCLUDE=@PJPROJECT_INCLUDE@ PJPROJECT_LIB=@PJPROJECT_LIB@ POPT_INCLUDE=@POPT_INCLUDE@ POPT_LIB=@POPT_LIB@ PORTAUDIO_INCLUDE=@PORTAUDIO_INCLUDE@ PORTAUDIO_LIB=@PORTAUDIO_LIB@ PRI_INCLUDE=@PRI_INCLUDE@ PRI_LIB=@PRI_LIB@ RESAMPLE_INCLUDE=@RESAMPLE_INCLUDE@ RESAMPLE_LIB=@RESAMPLE_LIB@ SS7_INCLUDE=@SS7_INCLUDE@ SS7_LIB=@SS7_LIB@ OPENR2_INCLUDE=@OPENR2_INCLUDE@ OPENR2_LIB=@OPENR2_LIB@ PWLIB_INCLUDE=@PWLIB_INCLUDE@ PWLIB_LIB=@PWLIB_LIB@ COROSYNC_INCLUDE=@COROSYNC_INCLUDE@ COROSYNC_LIB=@COROSYNC_LIB@ RADIUS_INCLUDE=@RADIUS_INCLUDE@ RADIUS_LIB=@RADIUS_LIB@ FFMPEG_INCLUDE=@FFMPEG_INCLUDE@ FFMPEG_LIB=@FFMPEG_LIB@ X11_INCLUDE=@X11_INCLUDE@ X11_LIB=@X11_LIB@ SDL_INCLUDE=@SDL_INCLUDE@ SDL_LIB=@SDL_LIB@ SDL_IMAGE_INCLUDE=@SDL_IMAGE_INCLUDE@ SDL_IMAGE_LIB=@SDL_IMAGE_LIB@ SPANDSP_INCLUDE=@SPANDSP_INCLUDE@ SPANDSP_LIB=@SPANDSP_LIB@ SPEEX_INCLUDE=@SPEEX_INCLUDE@ SPEEX_LIB=@SPEEX_LIB@ SPEEXDSP_INCLUDE=@SPEEXDSP_INCLUDE@ SPEEXDSP_LIB=@SPEEXDSP_LIB@ SQLITE_INCLUDE=@SQLITE_INCLUDE@ SQLITE_LIB=@SQLITE_LIB@ SQLITE3_INCLUDE=@SQLITE3_INCLUDE@ SQLITE3_LIB=@SQLITE3_LIB@ SRTP_LIB=@SRTP_LIB@ SRTP_INCLUDE=@SRTP_INCLUDE@ OPENSSL_INCLUDE=@OPENSSL_INCLUDE@ OPENSSL_LIB=@OPENSSL_LIB@ CRYPT_INCLUDE=@CRYPT_INCLUDE@ CRYPT_LIB=@CRYPT_LIB@ CRYPTO_INCLUDE=@CRYPTO_INCLUDE@ CRYPTO_LIB=@CRYPTO_LIB@ TONEZONE_INCLUDE=@TONEZONE_INCLUDE@ TONEZONE_LIB=@TONEZONE_LIB@ UNIXODBC_INCLUDE=@UNIXODBC_INCLUDE@ UNIXODBC_LIB=@UNIXODBC_LIB@ UUID_INCLUDE=@UUID_INCLUDE@ UUID_LIB=@UUID_LIB@ VORBIS_INCLUDE=@VORBIS_INCLUDE@ VORBIS_LIB=@VORBIS_LIB@ VPB_INCLUDE=@VPB_INCLUDE@ VPB_LIB=@VPB_LIB@ HAVE_DAHDI=@PBX_DAHDI@ DAHDI_INCLUDE=@DAHDI_INCLUDE@ ZLIB_INCLUDE=@ZLIB_INCLUDE@ ZLIB_LIB=@ZLIB_LIB@ ISDNNET_INCLUDE=@ISDNNET_INCLUDE@ ISDNNET_LIB=@ISDNNET_LIB@ MISDN_INCLUDE=@MISDN_INCLUDE@ MISDN_LIB=@MISDN_LIB@ SUPPSERV_INCLUDE=@SUPPSERV_INCLUDE@ SUPPSERV_LIB=@SUPPSERV_LIB@ CAP_LIB=@CAP_LIB@ CAP_INCLUDE=@CAP_INCLUDE@ BKTR_INCLUDE=@BKTR_INCLUDE@ BKTR_LIB=@BKTR_LIB@ TERMCAP_INCLUDE=@TERMCAP_INCLUDE@ TERMCAP_LIB=@TERMCAP_LIB@ TERMCAP_DIR=@TERMCAP_DIR@ LIBXML2_INCLUDE=@LIBXML2_INCLUDE@ LIBXML2_LIB=@LIBXML2_LIB@ LIBXSLT_INCLUDE=@LIBXSLT_INCLUDE@ LIBXSLT_LIB=@LIBXSLT_LIB@ TINFO_INCLUDE=@TINFO_INCLUDE@ TINFO_LIB=@TINFO_LIB@ TINFO_DIR=@TINFO_DIR@ # if poll is not present, let the makefile know. POLL_AVAILABLE=@HAS_POLL@ TIMERFD_INCLUDE=@TIMERFD_INCLUDE@ asterisk-13.1.0/config.guess0000755000000000000000000012355012362063071014445 0ustar rootroot#! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2014 Free Software Foundation, Inc. timestamp='2014-03-23' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner. # # You can get the latest version of this script from: # http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD # # Please send patches with a ChangeLog entry to config-patches@gnu.org. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2014 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ; set_cc_for_build= ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case "${UNAME_SYSTEM}" in Linux|GNU|GNU/*) # If the system lacks a compiler, then just pick glibc. # We could probably try harder. LIBC=gnu eval $set_cc_for_build cat <<-EOF > $dummy.c #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #else LIBC=gnu #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` ;; esac # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} exit ;; *:ekkoBSD:*:*) echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} exit ;; *:SolidBSD:*:*) echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} exit ;; macppc:MirBSD:*:*) echo powerpc-unknown-mirbsd${UNAME_RELEASE} exit ;; *:MirBSD:*:*) echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} exit ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` # Reset EXIT trap before exiting to avoid spurious non-zero exit code. exitcode=$? trap '' 0 exit $exitcode ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit ;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit ;; *:OS/390:*:*) echo i370-ibm-openedition exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit ;; arm*:riscos:*:*|arm*:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7; exit ;; esac ;; s390x:SunOS:*:*) echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) echo i386-pc-auroraux${UNAME_RELEASE} exit ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) eval $set_cc_for_build SUN_ARCH="i386" # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH="x86_64" fi fi echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`$dummy $dummyarg` && { echo "$SYSTEM_NAME"; exit; } echo mips-mips-riscos${UNAME_RELEASE} exit ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` then echo "$SYSTEM_NAME" else echo rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit ;; *:AIX:*:*) echo rs6000-ibm-aix exit ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then eval $set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && { echo "$SYSTEM_NAME"; exit; } echo unknown-hitachi-hiuxwe2 exit ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`/usr/bin/uname -p` case ${UNAME_PROCESSOR} in amd64) echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; *) echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; esac exit ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit ;; *:MINGW64*:*) echo ${UNAME_MACHINE}-pc-mingw64 exit ;; *:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit ;; *:MSYS*:*) echo ${UNAME_MACHINE}-pc-msys exit ;; i*:windows32*:*) # uname -m includes "-pc" on this system. echo ${UNAME_MACHINE}-mingw32 exit ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit ;; *:Interix*:*) case ${UNAME_MACHINE} in x86) echo i586-pc-interix${UNAME_RELEASE} exit ;; authenticamd | genuineintel | EM64T) echo x86_64-unknown-interix${UNAME_RELEASE} exit ;; IA64) echo ia64-unknown-interix${UNAME_RELEASE} exit ;; esac ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit ;; 8664:Windows_NT:*) echo x86_64-pc-mks exit ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) echo x86_64-unknown-cygwin exit ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; *:GNU:*:*) # the GNU system echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} exit ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit ;; aarch64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC="gnulibc1" ; fi echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; arc:Linux:*:* | arceb:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; arm*:Linux:*:*) eval $set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then echo ${UNAME_MACHINE}-unknown-linux-${LIBC} else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi else echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf fi fi exit ;; avr32*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; cris:Linux:*:*) echo ${UNAME_MACHINE}-axis-linux-${LIBC} exit ;; crisv32:Linux:*:*) echo ${UNAME_MACHINE}-axis-linux-${LIBC} exit ;; frv:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; hexagon:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; i*86:Linux:*:*) echo ${UNAME_MACHINE}-pc-linux-${LIBC} exit ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; m32r*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; mips:Linux:*:* | mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef ${UNAME_MACHINE} #undef ${UNAME_MACHINE}el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=${UNAME_MACHINE}el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=${UNAME_MACHINE} #else CPU= #endif #endif EOF eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } ;; openrisc*:Linux:*:*) echo or1k-unknown-linux-${LIBC} exit ;; or32:Linux:*:* | or1k*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; padre:Linux:*:*) echo sparc-unknown-linux-${LIBC} exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-${LIBC} exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; *) echo hppa-unknown-linux-${LIBC} ;; esac exit ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-${LIBC} exit ;; ppc:Linux:*:*) echo powerpc-unknown-linux-${LIBC} exit ;; ppc64le:Linux:*:*) echo powerpc64le-unknown-linux-${LIBC} exit ;; ppcle:Linux:*:*) echo powerpcle-unknown-linux-${LIBC} exit ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux-${LIBC} exit ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; tile*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; vax:Linux:*:*) echo ${UNAME_MACHINE}-dec-linux-${LIBC} exit ;; x86_64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; xtensa*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-${LIBC} exit ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit ;; i*86:syllable:*:*) echo ${UNAME_MACHINE}-pc-syllable exit ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configury will decide that # this is a cross-build. echo i586-pc-msdosdjgpp exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 exit ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3${OS_REL}; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3${OS_REL}; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. echo ${UNAME_MACHINE}-stratus-vos exit ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. echo i586-pc-haiku exit ;; x86_64:Haiku:*:*) echo x86_64-unknown-haiku exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit ;; SX-7:SUPER-UX:*:*) echo sx7-nec-superux${UNAME_RELEASE} exit ;; SX-8:SUPER-UX:*:*) echo sx8-nec-superux${UNAME_RELEASE} exit ;; SX-8R:SUPER-UX:*:*) echo sx8r-nec-superux${UNAME_RELEASE} exit ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown eval $set_cc_for_build if test "$UNAME_PROCESSOR" = unknown ; then UNAME_PROCESSOR=powerpc fi if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi fi elif test "$UNAME_PROCESSOR" = i386 ; then # Avoid executing cc on OS X 10.9, as it ships with a stub # that puts up a graphical alert prompting to install # developer tools. Any system running Mac OS X 10.7 or # later (Darwin 11 and later) is required to have a 64-bit # processor. This is not true of the ARM version of Darwin # that Apple uses in portable devices. UNAME_PROCESSOR=x86_64 fi echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit ;; *:QNX:*:4*) echo i386-pc-qnx exit ;; NEO-?:NONSTOP_KERNEL:*:*) echo neo-tandem-nsk${UNAME_RELEASE} exit ;; NSE-*:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk${UNAME_RELEASE} exit ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "${UNAME_MACHINE}" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; *:XENIX:*:SysV) echo i386-pc-xenix exit ;; i*86:skyos:*:*) echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' exit ;; i*86:rdos:*:*) echo ${UNAME_MACHINE}-pc-rdos exit ;; i*86:AROS:*:*) echo ${UNAME_MACHINE}-pc-aros exit ;; x86_64:VMkernel:*:*) echo ${UNAME_MACHINE}-unknown-esx exit ;; esac cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: asterisk-13.1.0/UPGRADE-10.txt0000644000000000000000000000743712001017342014166 0ustar rootroot=========================================================== === === Information for upgrading between Asterisk versions === === These files document all the changes that MUST be taken === into account when upgrading between the Asterisk === versions listed below. These changes may require that === you modify your configuration files, dialplan or (in === some cases) source code if you have your own Asterisk === modules or patches. These files also include advance === notice of any functionality that has been marked as === 'deprecated' and may be removed in a future release, === along with the suggested replacement functionality. === === UPGRADE-1.2.txt -- Upgrade info for 1.0 to 1.2 === UPGRADE-1.4.txt -- Upgrade info for 1.2 to 1.4 === UPGRADE-1.6.txt -- Upgrade info for 1.4 to 1.6 === UPGRADE-1.8.txt -- Upgrade info for 1.6 to 1.8 === =========================================================== From 10.4 to 10.5: * The complex processor detection and optimization has been removed from the makefile in favor of using native optimization suppport when available. BUILD_NATIVE can be disabled via menuselect under "Compiler Flags". From 10.2 to 10.3: * If no transport is specified in sip.conf, transport will default to UDP. Also, if multiple transport= lines are used, only the last will be used. From 1.8 to 10: cel_pgsql: - This module now expects an 'extra' column in the database for data added using the CELGenUserEvent() application. ConfBridge - ConfBridge's dialplan arguments have changed and are not backwards compatible. File Interpreters - The format interpreter formats/format_sln16.c for the file extension '.sln16' has been removed. The '.sln16' file interpreter now exists in the formats/format_sln.c module along with new support for sln12, sln24, sln32, sln44, sln48, sln96, and sln192 file extensions. HTTP: - A bindaddr must be specified in order for the HTTP server to run. Previous versions would default to 0.0.0.0 if no bindaddr was specified. Gtalk: - The default value for 'context' and 'parkinglots' in gtalk.conf has been changed to 'default', previously they were empty. chan_dahdi: - The mohinterpret=passthrough setting is deprecated in favor of moh_signaling=notify. pbx_lua: - Execution no longer continues after applications that do dialplan jumps (such as app.goto). Now when an application such as app.goto() is called, control is returned back to the pbx engine and the current extension function stops executing. - the autoservice now defaults to being on by default - autoservice_start() and autoservice_start() no longer return a value. Queue: - Mark QUEUE_MEMBER_PENALTY Deprecated it never worked for realtime members - QUEUE_MEMBER is now R/W supporting setting paused, ignorebusy and penalty. Asterisk Database: - The internal Asterisk database has been switched from Berkeley DB 1.86 to SQLite 3. An existing Berkeley astdb file can be converted with the astdb2sqlite3 utility in the UTILS section of menuselect. If an existing astdb is found and no astdb.sqlite3 exists, astdb2sqlite3 will be compiled automatically. Asterisk will convert an existing astdb to the SQLite3 version automatically at runtime. Module Support Level - All modules in the addons, apps, bridge, cdr, cel, channels, codecs, formats, funcs, pbx, and res have been updated to include MODULEINFO data that includes tags with a value of core, extended, or deprecated. More information is available on the Asterisk wiki at https://wiki.asterisk.org/wiki/display/AST/Asterisk+Module+Support+States Deprecated modules are now marked to not build by default and must be explicitly enabled in menuselect. =========================================================== =========================================================== asterisk-13.1.0/channels/0000755000000000000000000000000012443600112013703 5ustar rootrootasterisk-13.1.0/channels/chan_pjsip.c0000644000000000000000000021252212433377553016214 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2013, Digium, Inc. * * Joshua Colp * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \author Joshua Colp * * \brief PSJIP SIP Channel Driver * * \ingroup channel_drivers */ /*** MODULEINFO pjproject res_pjsip res_pjsip_session core ***/ #include "asterisk.h" #include #include #include ASTERISK_FILE_VERSION(__FILE__, "$Revision: 428302 $") #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/rtp_engine.h" #include "asterisk/acl.h" #include "asterisk/callerid.h" #include "asterisk/file.h" #include "asterisk/cli.h" #include "asterisk/app.h" #include "asterisk/musiconhold.h" #include "asterisk/causes.h" #include "asterisk/taskprocessor.h" #include "asterisk/dsp.h" #include "asterisk/stasis_endpoints.h" #include "asterisk/stasis_channels.h" #include "asterisk/indications.h" #include "asterisk/format_cache.h" #include "asterisk/translate.h" #include "asterisk/threadstorage.h" #include "asterisk/features_config.h" #include "asterisk/pickup.h" #include "asterisk/test.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" #include "pjsip/include/chan_pjsip.h" #include "pjsip/include/dialplan_functions.h" AST_THREADSTORAGE(uniqueid_threadbuf); #define UNIQUEID_BUFSIZE 256 static const char desc[] = "PJSIP Channel"; static const char channel_type[] = "PJSIP"; static unsigned int chan_idx; static void chan_pjsip_pvt_dtor(void *obj) { struct chan_pjsip_pvt *pvt = obj; int i; for (i = 0; i < SIP_MEDIA_SIZE; ++i) { ao2_cleanup(pvt->media[i]); pvt->media[i] = NULL; } } /* \brief Asterisk core interaction functions */ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text); static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit); static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout); static int chan_pjsip_hangup(struct ast_channel *ast); static int chan_pjsip_answer(struct ast_channel *ast); static struct ast_frame *chan_pjsip_read(struct ast_channel *ast); static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f); static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); static int chan_pjsip_transfer(struct ast_channel *ast, const char *target); static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int chan_pjsip_devicestate(const char *data); static int chan_pjsip_queryoption(struct ast_channel *ast, int option, void *data, int *datalen); static const char *chan_pjsip_get_uniqueid(struct ast_channel *ast); /*! \brief PBX interface structure for channel registration */ struct ast_channel_tech chan_pjsip_tech = { .type = channel_type, .description = "PJSIP Channel Driver", .requester = chan_pjsip_request, .send_text = chan_pjsip_sendtext, .send_digit_begin = chan_pjsip_digit_begin, .send_digit_end = chan_pjsip_digit_end, .call = chan_pjsip_call, .hangup = chan_pjsip_hangup, .answer = chan_pjsip_answer, .read = chan_pjsip_read, .write = chan_pjsip_write, .write_video = chan_pjsip_write, .exception = chan_pjsip_read, .indicate = chan_pjsip_indicate, .transfer = chan_pjsip_transfer, .fixup = chan_pjsip_fixup, .devicestate = chan_pjsip_devicestate, .queryoption = chan_pjsip_queryoption, .func_channel_read = pjsip_acf_channel_read, .get_pvt_uniqueid = chan_pjsip_get_uniqueid, .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER }; /*! \brief SIP session interaction functions */ static void chan_pjsip_session_begin(struct ast_sip_session *session); static void chan_pjsip_session_end(struct ast_sip_session *session); static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata); static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata); /*! \brief SIP session supplement structure */ static struct ast_sip_session_supplement chan_pjsip_supplement = { .method = "INVITE", .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL, .session_begin = chan_pjsip_session_begin, .session_end = chan_pjsip_session_end, .incoming_request = chan_pjsip_incoming_request, .incoming_response = chan_pjsip_incoming_response, /* It is important that this supplement runs after media has been negotiated */ .response_priority = AST_SIP_SESSION_AFTER_MEDIA, }; static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata); static struct ast_sip_session_supplement chan_pjsip_ack_supplement = { .method = "ACK", .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL, .incoming_request = chan_pjsip_incoming_ack, }; /*! \brief Function called by RTP engine to get local audio RTP peer */ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_endpoint *endpoint; if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_AUDIO]->rtp) { return AST_RTP_GLUE_RESULT_FORBID; } endpoint = channel->session->endpoint; *instance = pvt->media[SIP_MEDIA_AUDIO]->rtp; ao2_ref(*instance, +1); ast_assert(endpoint != NULL); if (endpoint->media.rtp.encryption != AST_SIP_MEDIA_ENCRYPT_NONE) { return AST_RTP_GLUE_RESULT_FORBID; } if (endpoint->media.direct_media.enabled) { return AST_RTP_GLUE_RESULT_REMOTE; } return AST_RTP_GLUE_RESULT_LOCAL; } /*! \brief Function called by RTP engine to get local video RTP peer */ static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_endpoint *endpoint; if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO]->rtp) { return AST_RTP_GLUE_RESULT_FORBID; } endpoint = channel->session->endpoint; *instance = pvt->media[SIP_MEDIA_VIDEO]->rtp; ao2_ref(*instance, +1); ast_assert(endpoint != NULL); if (endpoint->media.rtp.encryption != AST_SIP_MEDIA_ENCRYPT_NONE) { return AST_RTP_GLUE_RESULT_FORBID; } return AST_RTP_GLUE_RESULT_LOCAL; } /*! \brief Function called by RTP engine to get peer capabilities */ static void chan_pjsip_get_codec(struct ast_channel *chan, struct ast_format_cap *result) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); ast_format_cap_append_from_cap(result, channel->session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN); } static int send_direct_media_request(void *data) { RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup); return ast_sip_session_refresh(session, NULL, NULL, NULL, session->endpoint->media.direct_media.method, 1); } /*! \brief Destructor function for \ref transport_info_data */ static void transport_info_destroy(void *obj) { struct transport_info_data *data = obj; ast_free(data); } /*! \brief Datastore used to store local/remote addresses for the * INVITE request that created the PJSIP channel */ static struct ast_datastore_info transport_info = { .type = "chan_pjsip_transport_info", .destroy = transport_info_destroy, }; static struct ast_datastore_info direct_media_mitigation_info = { }; static int direct_media_mitigate_glare(struct ast_sip_session *session) { RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); if (session->endpoint->media.direct_media.glare_mitigation == AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_NONE) { return 0; } datastore = ast_sip_session_get_datastore(session, "direct_media_glare_mitigation"); if (!datastore) { return 0; } /* Removing the datastore ensures we won't try to mitigate glare on subsequent reinvites */ ast_sip_session_remove_datastore(session, "direct_media_glare_mitigation"); if ((session->endpoint->media.direct_media.glare_mitigation == AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_OUTGOING && session->inv_session->role == PJSIP_ROLE_UAC) || (session->endpoint->media.direct_media.glare_mitigation == AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_INCOMING && session->inv_session->role == PJSIP_ROLE_UAS)) { return 1; } return 0; } static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_sip_session_media *media, int rtcp_fd) { int changed = 0; if (rtp) { changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr); if (media->rtp) { ast_channel_set_fd(chan, rtcp_fd, -1); ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0); } } else if (!ast_sockaddr_isnull(&media->direct_media_addr)){ ast_sockaddr_setnull(&media->direct_media_addr); changed = 1; if (media->rtp) { ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1); ast_channel_set_fd(chan, rtcp_fd, ast_rtp_instance_fd(media->rtp, 1)); } } return changed; } /*! \brief Function called by RTP engine to change where the remote party should send media */ static int chan_pjsip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp, struct ast_rtp_instance *tpeer, const struct ast_format_cap *cap, int nat_active) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session *session = channel->session; int changed = 0; /* Don't try to do any direct media shenanigans on early bridges */ if ((rtp || vrtp || tpeer) && !ast_channel_is_bridged(chan)) { ast_debug(4, "Disregarding setting RTP on %s: channel is not bridged\n", ast_channel_name(chan)); return 0; } if (nat_active && session->endpoint->media.direct_media.disable_on_nat) { ast_debug(4, "Disregarding setting RTP on %s: NAT is active\n", ast_channel_name(chan)); return 0; } if (pvt->media[SIP_MEDIA_AUDIO]) { changed |= check_for_rtp_changes(chan, rtp, pvt->media[SIP_MEDIA_AUDIO], 1); } if (pvt->media[SIP_MEDIA_VIDEO]) { changed |= check_for_rtp_changes(chan, vrtp, pvt->media[SIP_MEDIA_VIDEO], 3); } if (direct_media_mitigate_glare(session)) { ast_debug(4, "Disregarding setting RTP on %s: mitigating re-INVITE glare\n", ast_channel_name(chan)); return 0; } if (cap && ast_format_cap_count(cap) && !ast_format_cap_identical(session->direct_media_cap, cap)) { ast_format_cap_remove_by_type(session->direct_media_cap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(session->direct_media_cap, cap, AST_MEDIA_TYPE_UNKNOWN); changed = 1; } if (changed) { ao2_ref(session, +1); ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(chan)); if (ast_sip_push_task(session->serializer, send_direct_media_request, session)) { ao2_cleanup(session); } } return 0; } /*! \brief Local glue for interacting with the RTP engine core */ static struct ast_rtp_glue chan_pjsip_rtp_glue = { .type = "PJSIP", .get_rtp_info = chan_pjsip_get_rtp_peer, .get_vrtp_info = chan_pjsip_get_vrtp_peer, .get_codec = chan_pjsip_get_codec, .update_peer = chan_pjsip_set_rtp_peer, }; static void set_channel_on_rtp_instance(struct chan_pjsip_pvt *pvt, const char *channel_id) { if (pvt->media[SIP_MEDIA_AUDIO] && pvt->media[SIP_MEDIA_AUDIO]->rtp) { ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_AUDIO]->rtp, channel_id); } if (pvt->media[SIP_MEDIA_VIDEO] && pvt->media[SIP_MEDIA_VIDEO]->rtp) { ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, channel_id); } } /*! \brief Function called to create a new PJSIP Asterisk channel */ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int state, const char *exten, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *cid_name) { struct ast_channel *chan; struct ast_format_cap *caps; RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup); struct ast_sip_channel_pvt *channel; struct ast_variable *var; if (!(pvt = ao2_alloc(sizeof(*pvt), chan_pjsip_pvt_dtor))) { return NULL; } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { return NULL; } chan = ast_channel_alloc_with_endpoint(1, state, S_COR(session->id.number.valid, session->id.number.str, ""), S_COR(session->id.name.valid, session->id.name.str, ""), session->endpoint->accountcode, "", "", assignedids, requestor, 0, session->endpoint->persistent, "PJSIP/%s-%08x", ast_sorcery_object_get_id(session->endpoint), (unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1)); if (!chan) { ao2_ref(caps, -1); return NULL; } ast_channel_tech_set(chan, &chan_pjsip_tech); if (!(channel = ast_sip_channel_pvt_alloc(pvt, session))) { ao2_ref(caps, -1); ast_channel_unlock(chan); ast_hangup(chan); return NULL; } ast_channel_stage_snapshot(chan); ast_channel_tech_pvt_set(chan, channel); if (!ast_format_cap_count(session->req_caps) || !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) { ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN); } else { ast_format_cap_append_from_cap(caps, session->req_caps, AST_MEDIA_TYPE_UNKNOWN); } ast_channel_nativeformats_set(chan, caps); if (!ast_format_cap_empty(caps)) { /* * XXX Probably should pick the first audio codec instead * of simply the first codec. The first codec may be video. */ struct ast_format *fmt = ast_format_cap_get_format(caps, 0); ast_channel_set_writeformat(chan, fmt); ast_channel_set_rawwriteformat(chan, fmt); ast_channel_set_readformat(chan, fmt); ast_channel_set_rawreadformat(chan, fmt); ao2_ref(fmt, -1); } ao2_ref(caps, -1); if (state == AST_STATE_RING) { ast_channel_rings_set(chan, 1); } ast_channel_adsicpe_set(chan, AST_ADSI_UNAVAILABLE); ast_party_id_copy(&ast_channel_caller(chan)->id, &session->id); ast_party_id_copy(&ast_channel_caller(chan)->ani, &session->id); ast_channel_context_set(chan, session->endpoint->context); ast_channel_exten_set(chan, S_OR(exten, "s")); ast_channel_priority_set(chan, 1); ast_channel_callgroup_set(chan, session->endpoint->pickup.callgroup); ast_channel_pickupgroup_set(chan, session->endpoint->pickup.pickupgroup); ast_channel_named_callgroups_set(chan, session->endpoint->pickup.named_callgroups); ast_channel_named_pickupgroups_set(chan, session->endpoint->pickup.named_pickupgroups); if (!ast_strlen_zero(session->endpoint->language)) { ast_channel_language_set(chan, session->endpoint->language); } if (!ast_strlen_zero(session->endpoint->zone)) { struct ast_tone_zone *zone = ast_get_indication_zone(session->endpoint->zone); if (!zone) { ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone. Check indications.conf for available country codes.\n", session->endpoint->zone); } ast_channel_zone_set(chan, zone); } for (var = session->endpoint->channel_vars; var; var = var->next) { char buf[512]; pbx_builtin_setvar_helper(chan, var->name, ast_get_encoded_str( var->value, buf, sizeof(buf))); } ast_channel_stage_snapshot_done(chan); ast_channel_unlock(chan); /* If res_pjsip_session is ever updated to create/destroy ast_sip_session_media * during a call such as if multiple same-type stream support is introduced, * these will need to be recaptured as well */ pvt->media[SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY); pvt->media[SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY); set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan)); return chan; } static int answer(void *data) { pj_status_t status = PJ_SUCCESS; pjsip_tx_data *packet = NULL; struct ast_sip_session *session = data; if (session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { ao2_ref(session, -1); return 0; } pjsip_dlg_inc_lock(session->inv_session->dlg); if (session->inv_session->invite_tsx) { status = pjsip_inv_answer(session->inv_session, 200, NULL, NULL, &packet); } else { ast_log(LOG_ERROR,"Cannot answer '%s' because there is no associated SIP transaction\n", ast_channel_name(session->channel)); } pjsip_dlg_dec_lock(session->inv_session->dlg); if (status == PJ_SUCCESS && packet) { ast_sip_session_send_response(session, packet); } ao2_ref(session, -1); return (status == PJ_SUCCESS) ? 0 : -1; } /*! \brief Function called by core when we should answer a PJSIP session */ static int chan_pjsip_answer(struct ast_channel *ast) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); if (ast_channel_state(ast) == AST_STATE_UP) { return 0; } ast_setstate(ast, AST_STATE_UP); ao2_ref(channel->session, +1); if (ast_sip_push_task(channel->session->serializer, answer, channel->session)) { ast_log(LOG_WARNING, "Unable to push answer task to the threadpool. Cannot answer call\n"); ao2_cleanup(channel->session); return -1; } return 0; } /*! \brief Internal helper function called when CNG tone is detected */ static struct ast_frame *chan_pjsip_cng_tone_detected(struct ast_sip_session *session, struct ast_frame *f) { const char *target_context; int exists; /* If we only needed this DSP for fax detection purposes we can just drop it now */ if (session->endpoint->dtmf == AST_SIP_DTMF_INBAND) { ast_dsp_set_features(session->dsp, DSP_FEATURE_DIGIT_DETECT); } else { ast_dsp_free(session->dsp); session->dsp = NULL; } /* If already executing in the fax extension don't do anything */ if (!strcmp(ast_channel_exten(session->channel), "fax")) { return f; } target_context = S_OR(ast_channel_macrocontext(session->channel), ast_channel_context(session->channel)); /* We need to unlock the channel 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_channel_unlock(session->channel); exists = ast_exists_extension(session->channel, target_context, "fax", 1, S_COR(ast_channel_caller(session->channel)->id.number.valid, ast_channel_caller(session->channel)->id.number.str, NULL)); ast_channel_lock(session->channel); if (exists) { ast_verb(2, "Redirecting '%s' to fax extension due to CNG detection\n", ast_channel_name(session->channel)); pbx_builtin_setvar_helper(session->channel, "FAXEXTEN", ast_channel_exten(session->channel)); if (ast_async_goto(session->channel, target_context, "fax", 1)) { ast_log(LOG_ERROR, "Failed to async goto '%s' into fax extension in '%s'\n", ast_channel_name(session->channel), target_context); } ast_frfree(f); f = &ast_null_frame; } else { ast_log(LOG_NOTICE, "FAX CNG detected on '%s' but no fax extension in '%s'\n", ast_channel_name(session->channel), target_context); } return f; } /*! \brief Function called by core to read any waiting frames */ static struct ast_frame *chan_pjsip_read(struct ast_channel *ast) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_frame *f; struct ast_sip_session_media *media = NULL; int rtcp = 0; int fdno = ast_channel_fdno(ast); switch (fdno) { case 0: media = pvt->media[SIP_MEDIA_AUDIO]; break; case 1: media = pvt->media[SIP_MEDIA_AUDIO]; rtcp = 1; break; case 2: media = pvt->media[SIP_MEDIA_VIDEO]; break; case 3: media = pvt->media[SIP_MEDIA_VIDEO]; rtcp = 1; break; } if (!media || !media->rtp) { return &ast_null_frame; } if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) { return f; } if (f->frametype != AST_FRAME_VOICE) { return f; } if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_format_cap *caps; ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(f->subclass.format)); caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append(caps, f->subclass.format, 0); ast_channel_nativeformats_set(ast, caps); ao2_ref(caps, -1); } ast_set_read_format(ast, ast_channel_readformat(ast)); ast_set_write_format(ast, ast_channel_writeformat(ast)); } if (channel->session->dsp) { f = ast_dsp_process(ast, channel->session->dsp, f); if (f && (f->frametype == AST_FRAME_DTMF)) { if (f->subclass.integer == 'f') { ast_debug(3, "Fax CNG detected on %s\n", ast_channel_name(ast)); f = chan_pjsip_cng_tone_detected(channel->session, f); } else { ast_debug(3, "* Detected inband DTMF '%c' on '%s'\n", f->subclass.integer, ast_channel_name(ast)); } } } return f; } /*! \brief Function called by core to write frames */ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session_media *media; int res = 0; switch (frame->frametype) { case AST_FRAME_VOICE: media = pvt->media[SIP_MEDIA_AUDIO]; if (!media) { return 0; } if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *cap_buf = ast_str_alloca(128); struct ast_str *write_transpath = ast_str_alloca(256); struct ast_str *read_transpath = ast_str_alloca(256); ast_log(LOG_WARNING, "Channel %s asked to send %s frame when native formats are %s (rd:%s->%s;%s wr:%s->%s;%s)\n", ast_channel_name(ast), ast_format_get_name(frame->subclass.format), ast_format_cap_get_names(ast_channel_nativeformats(ast), &cap_buf), ast_format_get_name(ast_channel_rawreadformat(ast)), ast_format_get_name(ast_channel_readformat(ast)), ast_translate_path_to_str(ast_channel_readtrans(ast), &read_transpath), ast_format_get_name(ast_channel_writeformat(ast)), ast_format_get_name(ast_channel_rawwriteformat(ast)), ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath)); return 0; } if (media->rtp) { res = ast_rtp_instance_write(media->rtp, frame); } break; case AST_FRAME_VIDEO: if ((media = pvt->media[SIP_MEDIA_VIDEO]) && media->rtp) { res = ast_rtp_instance_write(media->rtp, frame); } break; case AST_FRAME_MODEM: break; default: ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype); break; } return res; } /*! \brief Function called by core to change the underlying owner channel */ static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan); struct chan_pjsip_pvt *pvt = channel->pvt; if (channel->session->channel != oldchan) { return -1; } /* * The masquerade has suspended the channel's session * serializer so we can safely change it outside of * the serializer thread. */ channel->session->channel = newchan; set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(newchan)); return 0; } /*! AO2 hash function for on hold UIDs */ static int uid_hold_hash_fn(const void *obj, const int flags) { const char *key = obj; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_KEY: break; case OBJ_SEARCH_OBJECT: break; default: /* Hash can only work on something with a full key. */ ast_assert(0); return 0; } return ast_str_hash(key); } /*! AO2 sort function for on hold UIDs */ static int uid_hold_sort_fn(const void *obj_left, const void *obj_right, const int flags) { const char *left = obj_left; const char *right = obj_right; int cmp; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_OBJECT: case OBJ_SEARCH_KEY: cmp = strcmp(left, right); break; case OBJ_SEARCH_PARTIAL_KEY: cmp = strncmp(left, right, strlen(right)); break; default: /* Sort can only work on something with a full or partial key. */ ast_assert(0); cmp = 0; break; } return cmp; } static struct ao2_container *pjsip_uids_onhold; /*! * \brief Add a channel ID to the list of PJSIP channels on hold * * \param chan_uid - Unique ID of the channel being put into the hold list * * \retval 0 Channel has been added to or was already in the hold list * \retval -1 Failed to add channel to the hold list */ static int chan_pjsip_add_hold(const char *chan_uid) { RAII_VAR(char *, hold_uid, NULL, ao2_cleanup); hold_uid = ao2_find(pjsip_uids_onhold, chan_uid, OBJ_SEARCH_KEY); if (hold_uid) { /* Device is already on hold. Nothing to do. */ return 0; } /* Device wasn't in hold list already. Create a new one. */ hold_uid = ao2_alloc_options(strlen(chan_uid) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!hold_uid) { return -1; } ast_copy_string(hold_uid, chan_uid, strlen(chan_uid) + 1); if (ao2_link(pjsip_uids_onhold, hold_uid) == 0) { return -1; } return 0; } /*! * \brief Remove a channel ID from the list of PJSIP channels on hold * * \param chan_uid - Unique ID of the channel being taken out of the hold list */ static void chan_pjsip_remove_hold(const char *chan_uid) { ao2_find(pjsip_uids_onhold, chan_uid, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA); } /*! * \brief Determine whether a channel ID is in the list of PJSIP channels on hold * * \param chan_uid - Channel being checked * * \retval 0 The channel is not in the hold list * \retval 1 The channel is in the hold list */ static int chan_pjsip_get_hold(const char *chan_uid) { RAII_VAR(char *, hold_uid, NULL, ao2_cleanup); hold_uid = ao2_find(pjsip_uids_onhold, chan_uid, OBJ_SEARCH_KEY); if (!hold_uid) { return 0; } return 1; } /*! \brief Function called to get the device state of an endpoint */ static int chan_pjsip_devicestate(const char *data) { RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", data), ao2_cleanup); enum ast_device_state state = AST_DEVICE_UNKNOWN; RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot, NULL, ao2_cleanup); RAII_VAR(struct stasis_cache *, cache, NULL, ao2_cleanup); struct ast_devstate_aggregate aggregate; int num, inuse = 0; if (!endpoint) { return AST_DEVICE_INVALID; } endpoint_snapshot = ast_endpoint_latest_snapshot(ast_endpoint_get_tech(endpoint->persistent), ast_endpoint_get_resource(endpoint->persistent)); if (!endpoint_snapshot) { return AST_DEVICE_INVALID; } if (endpoint_snapshot->state == AST_ENDPOINT_OFFLINE) { state = AST_DEVICE_UNAVAILABLE; } else if (endpoint_snapshot->state == AST_ENDPOINT_ONLINE) { state = AST_DEVICE_NOT_INUSE; } if (!endpoint_snapshot->num_channels || !(cache = ast_channel_cache())) { return state; } ast_devstate_aggregate_init(&aggregate); ao2_ref(cache, +1); for (num = 0; num < endpoint_snapshot->num_channels; num++) { RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); struct ast_channel_snapshot *snapshot; msg = stasis_cache_get(cache, ast_channel_snapshot_type(), endpoint_snapshot->channel_ids[num]); if (!msg) { continue; } snapshot = stasis_message_data(msg); if (chan_pjsip_get_hold(snapshot->uniqueid)) { ast_devstate_aggregate_add(&aggregate, AST_DEVICE_ONHOLD); } else { ast_devstate_aggregate_add(&aggregate, ast_state_chan2dev(snapshot->state)); } if ((snapshot->state == AST_STATE_UP) || (snapshot->state == AST_STATE_RING) || (snapshot->state == AST_STATE_BUSY)) { inuse++; } } if (endpoint->devicestate_busy_at && (inuse == endpoint->devicestate_busy_at)) { state = AST_DEVICE_BUSY; } else if (ast_devstate_aggregate_result(&aggregate) != AST_DEVICE_INVALID) { state = ast_devstate_aggregate_result(&aggregate); } return state; } /*! \brief Function called to query options on a channel */ static int chan_pjsip_queryoption(struct ast_channel *ast, int option, void *data, int *datalen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct ast_sip_session *session = channel->session; int res = -1; enum ast_sip_session_t38state state = T38_STATE_UNAVAILABLE; switch (option) { case AST_OPTION_T38_STATE: if (session->endpoint->media.t38.enabled) { switch (session->t38state) { case T38_LOCAL_REINVITE: case T38_PEER_REINVITE: state = T38_STATE_NEGOTIATING; break; case T38_ENABLED: state = T38_STATE_NEGOTIATED; break; case T38_REJECTED: state = T38_STATE_REJECTED; break; default: state = T38_STATE_UNKNOWN; break; } } *((enum ast_t38_state *) data) = state; res = 0; break; default: break; } return res; } static const char *chan_pjsip_get_uniqueid(struct ast_channel *ast) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); char *uniqueid = ast_threadstorage_get(&uniqueid_threadbuf, UNIQUEID_BUFSIZE); if (!uniqueid) { return ""; } ast_copy_pj_str(uniqueid, &channel->session->inv_session->dlg->call_id->id, UNIQUEID_BUFSIZE); return uniqueid; } struct indicate_data { struct ast_sip_session *session; int condition; int response_code; void *frame_data; size_t datalen; }; static void indicate_data_destroy(void *obj) { struct indicate_data *ind_data = obj; ast_free(ind_data->frame_data); ao2_ref(ind_data->session, -1); } static struct indicate_data *indicate_data_alloc(struct ast_sip_session *session, int condition, int response_code, const void *frame_data, size_t datalen) { struct indicate_data *ind_data = ao2_alloc(sizeof(*ind_data), indicate_data_destroy); if (!ind_data) { return NULL; } ind_data->frame_data = ast_malloc(datalen); if (!ind_data->frame_data) { ao2_ref(ind_data, -1); return NULL; } memcpy(ind_data->frame_data, frame_data, datalen); ind_data->datalen = datalen; ind_data->condition = condition; ind_data->response_code = response_code; ao2_ref(session, +1); ind_data->session = session; return ind_data; } static int indicate(void *data) { pjsip_tx_data *packet = NULL; struct indicate_data *ind_data = data; struct ast_sip_session *session = ind_data->session; int response_code = ind_data->response_code; if ((session->inv_session->state != PJSIP_INV_STATE_DISCONNECTED) && (pjsip_inv_answer(session->inv_session, response_code, NULL, NULL, &packet) == PJ_SUCCESS)) { ast_sip_session_send_response(session, packet); } ao2_ref(ind_data, -1); return 0; } /*! \brief Send SIP INFO with video update request */ static int transmit_info_with_vidupdate(void *data) { const char * xml = "\r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n"; const struct ast_sip_body body = { .type = "application", .subtype = "media_control+xml", .body_text = xml }; RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup); struct pjsip_tx_data *tdata; if (ast_sip_create_request("INFO", session->inv_session->dlg, session->endpoint, NULL, NULL, &tdata)) { ast_log(LOG_ERROR, "Could not create text video update INFO request\n"); return -1; } if (ast_sip_add_body(tdata, &body)) { ast_log(LOG_ERROR, "Could not add body to text video update INFO request\n"); return -1; } ast_sip_session_send_request(session, tdata); return 0; } /*! \brief Update connected line information */ static int update_connected_line_information(void *data) { RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup); if ((ast_channel_state(session->channel) != AST_STATE_UP) && (session->inv_session->role == PJSIP_UAS_ROLE)) { int response_code = 0; if (session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { return 0; } if (ast_channel_state(session->channel) == AST_STATE_RING) { response_code = !session->endpoint->inband_progress ? 180 : 183; } else if (ast_channel_state(session->channel) == AST_STATE_RINGING) { response_code = 183; } if (response_code) { struct pjsip_tx_data *packet = NULL; if (pjsip_inv_answer(session->inv_session, response_code, NULL, NULL, &packet) == PJ_SUCCESS) { ast_sip_session_send_response(session, packet); } } } else { enum ast_sip_session_refresh_method method = session->endpoint->id.refresh_method; int generate_new_sdp; struct ast_party_id connected_id; if (session->inv_session->invite_tsx && (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE)) { method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE; } /* Only the INVITE method actually needs SDP, UPDATE can do without */ generate_new_sdp = (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE); /* * We can get away with a shallow copy here because we are * not looking at strings. */ ast_channel_lock(session->channel); connected_id = ast_channel_connected_effective_id(session->channel); ast_channel_unlock(session->channel); if ((session->endpoint->id.send_pai || session->endpoint->id.send_rpid) && (session->endpoint->id.trust_outbound || ((connected_id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED && (connected_id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED))) { ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp); } } return 0; } /*! \brief Function called by core to ask the channel to indicate some sort of condition */ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session_media *media; int response_code = 0; int res = 0; char *device_buf; size_t device_buf_size; switch (condition) { case AST_CONTROL_RINGING: if (ast_channel_state(ast) == AST_STATE_RING) { if (channel->session->endpoint->inband_progress) { response_code = 183; res = -1; } else { response_code = 180; } } else { res = -1; } ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint)); break; case AST_CONTROL_BUSY: if (ast_channel_state(ast) != AST_STATE_UP) { response_code = 486; } else { res = -1; } break; case AST_CONTROL_CONGESTION: if (ast_channel_state(ast) != AST_STATE_UP) { response_code = 503; } else { res = -1; } break; case AST_CONTROL_INCOMPLETE: if (ast_channel_state(ast) != AST_STATE_UP) { response_code = 484; } else { res = -1; } break; case AST_CONTROL_PROCEEDING: if (ast_channel_state(ast) != AST_STATE_UP) { response_code = 100; } else { res = -1; } break; case AST_CONTROL_PROGRESS: if (ast_channel_state(ast) != AST_STATE_UP) { response_code = 183; } else { res = -1; } break; case AST_CONTROL_VIDUPDATE: media = pvt->media[SIP_MEDIA_VIDEO]; if (media && media->rtp) { /* 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(ast), 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 fr; fr.frametype = AST_FRAME_CONTROL; fr.subclass.integer = AST_CONTROL_VIDUPDATE; res = ast_rtp_instance_write(media->rtp, &fr); } else { ao2_ref(channel->session, +1); if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) { ao2_cleanup(channel->session); } } ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success"); } else { ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure"); res = -1; } break; case AST_CONTROL_CONNECTED_LINE: ao2_ref(channel->session, +1); if (ast_sip_push_task(channel->session->serializer, update_connected_line_information, channel->session)) { ao2_cleanup(channel->session); } break; case AST_CONTROL_UPDATE_RTP_PEER: break; case AST_CONTROL_PVT_CAUSE_CODE: res = -1; break; case AST_CONTROL_MASQUERADE_NOTIFY: ast_assert(datalen == sizeof(int)); if (*(int *) data) { /* * Masquerade is beginning: * Wait for session serializer to get suspended. */ ast_channel_unlock(ast); ast_sip_session_suspend(channel->session); ast_channel_lock(ast); } else { /* * Masquerade is complete: * Unsuspend the session serializer. */ ast_sip_session_unsuspend(channel->session); } break; case AST_CONTROL_HOLD: chan_pjsip_add_hold(ast_channel_uniqueid(ast)); device_buf_size = strlen(ast_channel_name(ast)) + 1; device_buf = alloca(device_buf_size); ast_channel_get_device_name(ast, device_buf, device_buf_size); ast_devstate_changed_literal(AST_DEVICE_ONHOLD, 1, device_buf); ast_moh_start(ast, data, NULL); break; case AST_CONTROL_UNHOLD: chan_pjsip_remove_hold(ast_channel_uniqueid(ast)); device_buf_size = strlen(ast_channel_name(ast)) + 1; device_buf = alloca(device_buf_size); ast_channel_get_device_name(ast, device_buf, device_buf_size); ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, 1, device_buf); ast_moh_stop(ast); break; case AST_CONTROL_SRCUPDATE: break; case AST_CONTROL_SRCCHANGE: break; case AST_CONTROL_REDIRECTING: if (ast_channel_state(ast) != AST_STATE_UP) { response_code = 181; } else { res = -1; } break; case AST_CONTROL_T38_PARAMETERS: res = 0; if (channel->session->t38state == T38_PEER_REINVITE) { const struct ast_control_t38_parameters *parameters = data; if (parameters->request_response == AST_T38_REQUEST_PARMS) { res = AST_T38_REQUEST_PARMS; } } break; case -1: res = -1; break; default: ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", condition); res = -1; break; } if (response_code) { struct indicate_data *ind_data = indicate_data_alloc(channel->session, condition, response_code, data, datalen); if (!ind_data || ast_sip_push_task(channel->session->serializer, indicate, ind_data)) { ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n", response_code, ast_sorcery_object_get_id(channel->session->endpoint)); ao2_cleanup(ind_data); res = -1; } } return res; } struct transfer_data { struct ast_sip_session *session; char *target; }; static void transfer_data_destroy(void *obj) { struct transfer_data *trnf_data = obj; ast_free(trnf_data->target); ao2_cleanup(trnf_data->session); } static struct transfer_data *transfer_data_alloc(struct ast_sip_session *session, const char *target) { struct transfer_data *trnf_data = ao2_alloc(sizeof(*trnf_data), transfer_data_destroy); if (!trnf_data) { return NULL; } if (!(trnf_data->target = ast_strdup(target))) { ao2_ref(trnf_data, -1); return NULL; } ao2_ref(session, +1); trnf_data->session = session; return trnf_data; } static void transfer_redirect(struct ast_sip_session *session, const char *target) { pjsip_tx_data *packet; enum ast_control_transfer message = AST_TRANSFER_SUCCESS; pjsip_contact_hdr *contact; pj_str_t tmp; if (pjsip_inv_end_session(session->inv_session, 302, NULL, &packet) != PJ_SUCCESS) { message = AST_TRANSFER_FAILED; ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message)); return; } if (!(contact = pjsip_msg_find_hdr(packet->msg, PJSIP_H_CONTACT, NULL))) { contact = pjsip_contact_hdr_create(packet->pool); } pj_strdup2_with_null(packet->pool, &tmp, target); if (!(contact->uri = pjsip_parse_uri(packet->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR))) { message = AST_TRANSFER_FAILED; ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message)); pjsip_tx_data_dec_ref(packet); return; } pjsip_msg_add_hdr(packet->msg, (pjsip_hdr *) contact); ast_sip_session_send_response(session, packet); ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message)); } static void transfer_refer(struct ast_sip_session *session, const char *target) { pjsip_evsub *sub; enum ast_control_transfer message = AST_TRANSFER_SUCCESS; pj_str_t tmp; pjsip_tx_data *packet; if (pjsip_xfer_create_uac(session->inv_session->dlg, NULL, &sub) != PJ_SUCCESS) { message = AST_TRANSFER_FAILED; ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message)); return; } if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, target), &packet) != PJ_SUCCESS) { message = AST_TRANSFER_FAILED; ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message)); pjsip_evsub_terminate(sub, PJ_FALSE); return; } pjsip_xfer_send_request(sub, packet); ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message)); } static int transfer(void *data) { struct transfer_data *trnf_data = data; if (ast_channel_state(trnf_data->session->channel) == AST_STATE_RING) { transfer_redirect(trnf_data->session, trnf_data->target); } else { transfer_refer(trnf_data->session, trnf_data->target); } ao2_ref(trnf_data, -1); return 0; } /*! \brief Function called by core for Asterisk initiated transfer */ static int chan_pjsip_transfer(struct ast_channel *chan, const char *target) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct transfer_data *trnf_data = transfer_data_alloc(channel->session, target); if (!trnf_data) { return -1; } if (ast_sip_push_task(channel->session->serializer, transfer, trnf_data)) { ast_log(LOG_WARNING, "Error requesting transfer\n"); ao2_cleanup(trnf_data); return -1; } return 0; } /*! \brief Function called by core to start a DTMF digit */ static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO]; int res = 0; switch (channel->session->endpoint->dtmf) { case AST_SIP_DTMF_RFC_4733: if (!media || !media->rtp) { return -1; } ast_rtp_instance_dtmf_begin(media->rtp, digit); case AST_SIP_DTMF_NONE: break; case AST_SIP_DTMF_INBAND: res = -1; break; default: break; } return res; } struct info_dtmf_data { struct ast_sip_session *session; char digit; unsigned int duration; }; static void info_dtmf_data_destroy(void *obj) { struct info_dtmf_data *dtmf_data = obj; ao2_ref(dtmf_data->session, -1); } static struct info_dtmf_data *info_dtmf_data_alloc(struct ast_sip_session *session, char digit, unsigned int duration) { struct info_dtmf_data *dtmf_data = ao2_alloc(sizeof(*dtmf_data), info_dtmf_data_destroy); if (!dtmf_data) { return NULL; } ao2_ref(session, +1); dtmf_data->session = session; dtmf_data->digit = digit; dtmf_data->duration = duration; return dtmf_data; } static int transmit_info_dtmf(void *data) { RAII_VAR(struct info_dtmf_data *, dtmf_data, data, ao2_cleanup); struct ast_sip_session *session = dtmf_data->session; struct pjsip_tx_data *tdata; RAII_VAR(struct ast_str *, body_text, NULL, ast_free_ptr); struct ast_sip_body body = { .type = "application", .subtype = "dtmf-relay", }; if (!(body_text = ast_str_create(32))) { ast_log(LOG_ERROR, "Could not allocate buffer for INFO DTMF.\n"); return -1; } ast_str_set(&body_text, 0, "Signal=%c\r\nDuration=%u\r\n", dtmf_data->digit, dtmf_data->duration); body.body_text = ast_str_buffer(body_text); if (ast_sip_create_request("INFO", session->inv_session->dlg, session->endpoint, NULL, NULL, &tdata)) { ast_log(LOG_ERROR, "Could not create DTMF INFO request\n"); return -1; } if (ast_sip_add_body(tdata, &body)) { ast_log(LOG_ERROR, "Could not add body to DTMF INFO request\n"); pjsip_tx_data_dec_ref(tdata); return -1; } ast_sip_session_send_request(session, tdata); return 0; } /*! \brief Function called by core to stop a DTMF digit */ static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO]; int res = 0; switch (channel->session->endpoint->dtmf) { case AST_SIP_DTMF_INFO: { struct info_dtmf_data *dtmf_data = info_dtmf_data_alloc(channel->session, digit, duration); if (!dtmf_data) { return -1; } if (ast_sip_push_task(channel->session->serializer, transmit_info_dtmf, dtmf_data)) { ast_log(LOG_WARNING, "Error sending DTMF via INFO.\n"); ao2_cleanup(dtmf_data); return -1; } break; } case AST_SIP_DTMF_RFC_4733: if (!media || !media->rtp) { return -1; } ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration); case AST_SIP_DTMF_NONE: break; case AST_SIP_DTMF_INBAND: res = -1; break; } return res; } static void update_initial_connected_line(struct ast_sip_session *session) { struct ast_party_connected_line connected; /* * Use the channel CALLERID() as the initial connected line data. * The core or a predial handler may have supplied missing values * from the session->endpoint->id.self about who we are calling. */ ast_channel_lock(session->channel); ast_party_id_copy(&session->id, &ast_channel_caller(session->channel)->id); ast_channel_unlock(session->channel); /* Supply initial connected line information if available. */ if (!session->id.number.valid && !session->id.name.valid) { return; } ast_party_connected_line_init(&connected); connected.id = session->id; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; ast_channel_queue_connected_line_update(session->channel, &connected, NULL); } static int call(void *data) { struct ast_sip_channel_pvt *channel = data; struct ast_sip_session *session = channel->session; struct chan_pjsip_pvt *pvt = channel->pvt; pjsip_tx_data *tdata; int res = ast_sip_session_create_invite(session, &tdata); if (res) { ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0); ast_queue_hangup(session->channel); } else { set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(session->channel)); update_initial_connected_line(session); ast_sip_session_send_request(session, tdata); } ao2_ref(channel, -1); return res; } /*! \brief Function called by core to actually start calling a remote party */ static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); ao2_ref(channel, +1); if (ast_sip_push_task(channel->session->serializer, call, channel)) { ast_log(LOG_WARNING, "Error attempting to place outbound call to '%s'\n", dest); ao2_cleanup(channel); return -1; } return 0; } /*! \brief Internal function which translates from Asterisk cause codes to SIP response codes */ static int hangup_cause2sip(int cause) { switch (cause) { case AST_CAUSE_UNALLOCATED: /* 1 */ case AST_CAUSE_NO_ROUTE_DESTINATION: /* 3 IAX2: Can't find extension in context */ case AST_CAUSE_NO_ROUTE_TRANSIT_NET: /* 2 */ return 404; case AST_CAUSE_CONGESTION: /* 34 */ case AST_CAUSE_SWITCH_CONGESTION: /* 42 */ return 503; case AST_CAUSE_NO_USER_RESPONSE: /* 18 */ return 408; case AST_CAUSE_NO_ANSWER: /* 19 */ case AST_CAUSE_UNREGISTERED: /* 20 */ return 480; case AST_CAUSE_CALL_REJECTED: /* 21 */ return 403; case AST_CAUSE_NUMBER_CHANGED: /* 22 */ return 410; case AST_CAUSE_NORMAL_UNSPECIFIED: /* 31 */ return 480; case AST_CAUSE_INVALID_NUMBER_FORMAT: return 484; case AST_CAUSE_USER_BUSY: return 486; case AST_CAUSE_FAILURE: return 500; case AST_CAUSE_FACILITY_REJECTED: /* 29 */ return 501; case AST_CAUSE_CHAN_NOT_IMPLEMENTED: return 503; case AST_CAUSE_DESTINATION_OUT_OF_ORDER: return 502; case AST_CAUSE_BEARERCAPABILITY_NOTAVAIL: /* Can't find codec to connect to host */ return 488; case AST_CAUSE_INTERWORKING: /* Unspecified Interworking issues */ return 500; case AST_CAUSE_NOTDEFINED: default: ast_debug(1, "AST hangup cause %d (no match found in PJSIP)\n", cause); return 0; } /* Never reached */ return 0; } struct hangup_data { int cause; struct ast_channel *chan; }; static void hangup_data_destroy(void *obj) { struct hangup_data *h_data = obj; h_data->chan = ast_channel_unref(h_data->chan); } static struct hangup_data *hangup_data_alloc(int cause, struct ast_channel *chan) { struct hangup_data *h_data = ao2_alloc(sizeof(*h_data), hangup_data_destroy); if (!h_data) { return NULL; } h_data->cause = cause; h_data->chan = ast_channel_ref(chan); return h_data; } /*! \brief Clear a channel from a session along with its PVT */ static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast, struct chan_pjsip_pvt *pvt) { session->channel = NULL; set_channel_on_rtp_instance(pvt, ""); ast_channel_tech_pvt_set(ast, NULL); } static int hangup(void *data) { struct hangup_data *h_data = data; struct ast_channel *ast = h_data->chan; struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct chan_pjsip_pvt *pvt = channel->pvt; struct ast_sip_session *session = channel->session; int cause = h_data->cause; if (!session->defer_terminate) { pj_status_t status; pjsip_tx_data *packet = NULL; if (session->inv_session->state == PJSIP_INV_STATE_NULL) { pjsip_inv_terminate(session->inv_session, cause ? cause : 603, PJ_TRUE); } else if (((status = pjsip_inv_end_session(session->inv_session, cause ? cause : 603, NULL, &packet)) == PJ_SUCCESS) && packet) { if (packet->msg->type == PJSIP_RESPONSE_MSG) { ast_sip_session_send_response(session, packet); } else { ast_sip_session_send_request(session, packet); } } } clear_session_and_channel(session, ast, pvt); ao2_cleanup(channel); ao2_cleanup(h_data); return 0; } /*! \brief Function called by core to hang up a PJSIP session */ static int chan_pjsip_hangup(struct ast_channel *ast) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct chan_pjsip_pvt *pvt = channel->pvt; int cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel)); struct hangup_data *h_data = hangup_data_alloc(cause, ast); if (!h_data) { goto failure; } if (ast_sip_push_task(channel->session->serializer, hangup, h_data)) { ast_log(LOG_WARNING, "Unable to push hangup task to the threadpool. Expect bad things\n"); goto failure; } return 0; failure: /* Go ahead and do our cleanup of the session and channel even if we're not going * to be able to send our SIP request/response */ clear_session_and_channel(channel->session, ast, pvt); ao2_cleanup(channel); ao2_cleanup(h_data); return -1; } struct request_data { struct ast_sip_session *session; struct ast_format_cap *caps; const char *dest; int cause; }; static int request(void *obj) { struct request_data *req_data = obj; struct ast_sip_session *session = NULL; char *tmp = ast_strdupa(req_data->dest), *endpoint_name = NULL, *request_user = NULL; RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); AST_DECLARE_APP_ARGS(args, AST_APP_ARG(endpoint); AST_APP_ARG(aor); ); if (ast_strlen_zero(tmp)) { ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty destination\n"); req_data->cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; return -1; } AST_NONSTANDARD_APP_ARGS(args, tmp, '/'); /* If a request user has been specified extract it from the endpoint name portion */ if ((endpoint_name = strchr(args.endpoint, '@'))) { request_user = args.endpoint; *endpoint_name++ = '\0'; } else { endpoint_name = args.endpoint; } if (ast_strlen_zero(endpoint_name)) { ast_log(LOG_ERROR, "Unable to create PJSIP channel with empty endpoint name\n"); req_data->cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; } else if (!(endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name))) { ast_log(LOG_ERROR, "Unable to create PJSIP channel - endpoint '%s' was not found\n", endpoint_name); req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION; return -1; } if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->caps))) { ast_log(LOG_ERROR, "Failed to create outgoing session to endpoint '%s'\n", endpoint_name); req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION; return -1; } req_data->session = session; return 0; } /*! \brief Function called by core to create a new outgoing PJSIP session */ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct request_data req_data; RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup); req_data.caps = cap; req_data.dest = data; if (ast_sip_push_task_synchronous(NULL, request, &req_data)) { *cause = req_data.cause; return NULL; } session = req_data.session; if (!(session->channel = chan_pjsip_new(session, AST_STATE_DOWN, NULL, NULL, assignedids, requestor, NULL))) { /* Session needs to be terminated prematurely */ return NULL; } return session->channel; } struct sendtext_data { struct ast_sip_session *session; char text[0]; }; static void sendtext_data_destroy(void *obj) { struct sendtext_data *data = obj; ao2_ref(data->session, -1); } static struct sendtext_data* sendtext_data_create(struct ast_sip_session *session, const char *text) { int size = strlen(text) + 1; struct sendtext_data *data = ao2_alloc(sizeof(*data)+size, sendtext_data_destroy); if (!data) { return NULL; } data->session = session; ao2_ref(data->session, +1); ast_copy_string(data->text, text, size); return data; } static int sendtext(void *obj) { RAII_VAR(struct sendtext_data *, data, obj, ao2_cleanup); pjsip_tx_data *tdata; const struct ast_sip_body body = { .type = "text", .subtype = "plain", .body_text = data->text }; /* NOT ast_strlen_zero, because a zero-length message is specifically * allowed by RFC 3428 (See section 10, Examples) */ if (!data->text) { return 0; } ast_debug(3, "Sending in dialog SIP message\n"); ast_sip_create_request("MESSAGE", data->session->inv_session->dlg, data->session->endpoint, NULL, NULL, &tdata); ast_sip_add_body(tdata, &body); ast_sip_send_request(tdata, data->session->inv_session->dlg, data->session->endpoint, NULL, NULL); return 0; } /*! \brief Function called by core to send text on PJSIP session */ static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast); struct sendtext_data *data = sendtext_data_create(channel->session, text); if (!data || ast_sip_push_task(channel->session->serializer, sendtext, data)) { ao2_ref(data, -1); return -1; } return 0; } /*! \brief Convert SIP hangup causes to Asterisk hangup causes */ static int hangup_sip2cause(int cause) { /* Possible values taken from causes.h */ 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; } static void chan_pjsip_session_begin(struct ast_sip_session *session) { RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); if (session->endpoint->media.direct_media.glare_mitigation == AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_NONE) { return; } datastore = ast_sip_session_alloc_datastore(&direct_media_mitigation_info, "direct_media_glare_mitigation"); if (!datastore) { return; } ast_sip_session_add_datastore(session, datastore); } /*! \brief Function called when the session ends */ static void chan_pjsip_session_end(struct ast_sip_session *session) { if (!session->channel) { return; } chan_pjsip_remove_hold(ast_channel_uniqueid(session->channel)); ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0); if (!ast_channel_hangupcause(session->channel) && session->inv_session) { int cause = hangup_sip2cause(session->inv_session->cause); ast_queue_hangup_with_cause(session->channel, cause); } else { ast_queue_hangup(session->channel); } } /*! \brief Function called when a request is received on the session */ static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) { RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); struct transport_info_data *transport_data; pjsip_tx_data *packet = NULL; if (session->channel) { return 0; } datastore = ast_sip_session_alloc_datastore(&transport_info, "transport_info"); if (!datastore) { return -1; } transport_data = ast_calloc(1, sizeof(*transport_data)); if (!transport_data) { return -1; } pj_sockaddr_cp(&transport_data->local_addr, &rdata->tp_info.transport->local_addr); pj_sockaddr_cp(&transport_data->remote_addr, &rdata->pkt_info.src_addr); datastore->data = transport_data; ast_sip_session_add_datastore(session, datastore); if (!(session->channel = chan_pjsip_new(session, AST_STATE_RING, session->exten, NULL, NULL, NULL, NULL))) { if (pjsip_inv_end_session(session->inv_session, 503, NULL, &packet) == PJ_SUCCESS) { ast_sip_session_send_response(session, packet); } ast_log(LOG_ERROR, "Failed to allocate new PJSIP channel on incoming SIP INVITE\n"); return -1; } /* channel gets created on incoming request, but we wait to call start so other supplements have a chance to run */ return 0; } static int call_pickup_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) { struct ast_features_pickup_config *pickup_cfg = ast_get_chan_features_pickup_config(session->channel); struct ast_channel *chan; /* We don't care about reinvites */ if (session->inv_session->state >= PJSIP_INV_STATE_CONFIRMED) { return 0; } if (!pickup_cfg) { ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension.\n"); return 0; } if (strcmp(session->exten, pickup_cfg->pickupexten)) { ao2_ref(pickup_cfg, -1); return 0; } ao2_ref(pickup_cfg, -1); /* We can't directly use session->channel because the pickup operation will cause a masquerade to occur, * changing the channel pointer in session to a different channel. To ensure we work on the right channel * we store a pointer locally before we begin and keep a reference so it remains valid no matter what. */ chan = ast_channel_ref(session->channel); if (ast_pickup_call(chan)) { ast_channel_hangupcause_set(chan, AST_CAUSE_CALL_REJECTED); } else { ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL_CLEARING); } /* A hangup always occurs because the pickup operation will have either failed resulting in the call * needing to be hung up OR the pickup operation was a success and the channel we now have is actually * the channel that was replaced, which should be hung up since it is literally in limbo not connected * to anything at all. */ ast_hangup(chan); ast_channel_unref(chan); return 1; } static struct ast_sip_session_supplement call_pickup_supplement = { .method = "INVITE", .priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST - 1, .incoming_request = call_pickup_incoming_request, }; static int pbx_start_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) { int res; /* We don't care about reinvites */ if (session->inv_session->state >= PJSIP_INV_STATE_CONFIRMED) { return 0; } res = ast_pbx_start(session->channel); switch (res) { case AST_PBX_FAILED: ast_log(LOG_WARNING, "Failed to start PBX ;(\n"); ast_channel_hangupcause_set(session->channel, AST_CAUSE_SWITCH_CONGESTION); ast_hangup(session->channel); break; case AST_PBX_CALL_LIMIT: ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n"); ast_channel_hangupcause_set(session->channel, AST_CAUSE_SWITCH_CONGESTION); ast_hangup(session->channel); break; case AST_PBX_SUCCESS: default: break; } ast_debug(3, "Started PBX on new PJSIP channel %s\n", ast_channel_name(session->channel)); return (res == AST_PBX_SUCCESS) ? 0 : -1; } static struct ast_sip_session_supplement pbx_start_supplement = { .method = "INVITE", .priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST, .incoming_request = pbx_start_incoming_request, }; /*! \brief Function called when a response is received on the session */ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata) { struct pjsip_status_line status = rdata->msg_info.msg->line.status; struct ast_control_pvt_cause_code *cause_code; int data_size = sizeof(*cause_code); if (!session->channel) { return; } switch (status.code) { case 180: ast_queue_control(session->channel, AST_CONTROL_RINGING); ast_channel_lock(session->channel); if (ast_channel_state(session->channel) != AST_STATE_UP) { ast_setstate(session->channel, AST_STATE_RINGING); } ast_channel_unlock(session->channel); break; case 183: ast_queue_control(session->channel, AST_CONTROL_PROGRESS); break; case 200: ast_queue_control(session->channel, AST_CONTROL_ANSWER); break; default: break; } /* Build and send the tech-specific cause information */ /* size of the string making up the cause code is "SIP " number + " " + reason length */ data_size += 4 + 4 + pj_strlen(&status.reason); cause_code = ast_alloca(data_size); memset(cause_code, 0, data_size); ast_copy_string(cause_code->chan_name, ast_channel_name(session->channel), AST_CHANNEL_NAME); snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "SIP %d %.*s", status.code, (int) pj_strlen(&status.reason), pj_strbuf(&status.reason)); cause_code->ast_cause = hangup_sip2cause(status.code); ast_queue_control_data(session->channel, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size); ast_channel_hangupcause_hash_set(session->channel, cause_code, data_size); } static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata) { if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) { if (session->endpoint->media.direct_media.enabled && session->channel) { ast_queue_control(session->channel, AST_CONTROL_SRCCHANGE); } } return 0; } static int update_devstate(void *obj, void *arg, int flags) { ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(obj)); return 0; } static struct ast_custom_function chan_pjsip_dial_contacts_function = { .name = "PJSIP_DIAL_CONTACTS", .read = pjsip_acf_dial_contacts_read, }; static struct ast_custom_function media_offer_function = { .name = "PJSIP_MEDIA_OFFER", .read = pjsip_acf_media_offer_read, .write = pjsip_acf_media_offer_write }; /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { struct ao2_container *endpoints; CHECK_PJSIP_SESSION_MODULE_LOADED(); if (!(chan_pjsip_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append_by_type(chan_pjsip_tech.capabilities, AST_MEDIA_TYPE_AUDIO); ast_rtp_glue_register(&chan_pjsip_rtp_glue); if (ast_channel_register(&chan_pjsip_tech)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", channel_type); goto end; } if (ast_custom_function_register(&chan_pjsip_dial_contacts_function)) { ast_log(LOG_ERROR, "Unable to register PJSIP_DIAL_CONTACTS dialplan function\n"); goto end; } if (ast_custom_function_register(&media_offer_function)) { ast_log(LOG_WARNING, "Unable to register PJSIP_MEDIA_OFFER dialplan function\n"); goto end; } if (ast_sip_session_register_supplement(&chan_pjsip_supplement)) { ast_log(LOG_ERROR, "Unable to register PJSIP supplement\n"); goto end; } if (!(pjsip_uids_onhold = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, 37, uid_hold_hash_fn, uid_hold_sort_fn, NULL))) { ast_log(LOG_ERROR, "Unable to create held channels container\n"); goto end; } if (ast_sip_session_register_supplement(&call_pickup_supplement)) { ast_log(LOG_ERROR, "Unable to register PJSIP call pickup supplement\n"); ast_sip_session_unregister_supplement(&chan_pjsip_supplement); goto end; } if (ast_sip_session_register_supplement(&pbx_start_supplement)) { ast_log(LOG_ERROR, "Unable to register PJSIP pbx start supplement\n"); ast_sip_session_unregister_supplement(&chan_pjsip_supplement); ast_sip_session_unregister_supplement(&call_pickup_supplement); goto end; } if (ast_sip_session_register_supplement(&chan_pjsip_ack_supplement)) { ast_log(LOG_ERROR, "Unable to register PJSIP ACK supplement\n"); ast_sip_session_unregister_supplement(&pbx_start_supplement); ast_sip_session_unregister_supplement(&chan_pjsip_supplement); ast_sip_session_unregister_supplement(&call_pickup_supplement); goto end; } /* since endpoints are loaded before the channel driver their device states get set to 'invalid', so they need to be updated */ if ((endpoints = ast_sip_get_endpoints())) { ao2_callback(endpoints, OBJ_NODATA, update_devstate, NULL); ao2_ref(endpoints, -1); } return 0; end: ao2_cleanup(pjsip_uids_onhold); pjsip_uids_onhold = NULL; ast_custom_function_unregister(&media_offer_function); ast_custom_function_unregister(&chan_pjsip_dial_contacts_function); ast_channel_unregister(&chan_pjsip_tech); ast_rtp_glue_unregister(&chan_pjsip_rtp_glue); return AST_MODULE_LOAD_FAILURE; } /*! \brief Unload the PJSIP channel from Asterisk */ static int unload_module(void) { ao2_cleanup(pjsip_uids_onhold); pjsip_uids_onhold = NULL; ast_sip_session_unregister_supplement(&chan_pjsip_supplement); ast_sip_session_unregister_supplement(&pbx_start_supplement); ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement); ast_sip_session_unregister_supplement(&call_pickup_supplement); ast_custom_function_unregister(&media_offer_function); ast_custom_function_unregister(&chan_pjsip_dial_contacts_function); ast_channel_unregister(&chan_pjsip_tech); ao2_ref(chan_pjsip_tech.capabilities, -1); ast_rtp_glue_unregister(&chan_pjsip_rtp_glue); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Channel Driver", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_CHANNEL_DRIVER, ); asterisk-13.1.0/channels/chan_mgcp.c0000644000000000000000000045051712437125770016021 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2006, 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. */ /*! \file * * \brief Implementation of Media Gateway Control Protocol * * \author Mark Spencer * * \ingroup channel_drivers */ /*! \li \ref chan_mgcp.c uses the configuration file \ref mgcp.conf * \addtogroup configuration_file */ /*! \page mgcp.conf mgcp.conf * \verbinclude mgcp.conf.sample */ /*** MODULEINFO res_pktccops extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 428687 $") #include #include #include #include #include #include #include #include #include #include #include #include #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pickup.h" #include "asterisk/pbx.h" #include "asterisk/sched.h" #include "asterisk/io.h" #include "asterisk/rtp_engine.h" #include "asterisk/acl.h" #include "asterisk/callerid.h" #include "asterisk/cli.h" #include "asterisk/say.h" #include "asterisk/astdb.h" #include "asterisk/features.h" #include "asterisk/app.h" #include "asterisk/musiconhold.h" #include "asterisk/utils.h" #include "asterisk/netsock2.h" #include "asterisk/causes.h" #include "asterisk/dsp.h" #include "asterisk/devicestate.h" #include "asterisk/stringfields.h" #include "asterisk/abstract_jb.h" #include "asterisk/chanvars.h" #include "asterisk/pktccops.h" #include "asterisk/stasis.h" #include "asterisk/bridge.h" #include "asterisk/features_config.h" #include "asterisk/parking.h" #include "asterisk/stasis_channels.h" #include "asterisk/format_cache.h" /* * Define to work around buggy dlink MGCP phone firmware which * appears not to know that "rt" is part of the "G" package. */ /* #define DLINK_BUGGY_FIRMWARE */ #define MGCPDUMPER #define DEFAULT_EXPIRY 120 #define MAX_EXPIRY 3600 #define DIRECTMEDIA 1 #ifndef INADDR_NONE #define INADDR_NONE (in_addr_t)(-1) #endif /*! Global jitterbuffer configuration - by default, jb is disabled * \note Values shown here match the defaults shown in mgcp.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; static const char tdesc[] = "Media Gateway Control Protocol (MGCP)"; static const char config[] = "mgcp.conf"; #define MGCP_DTMF_RFC2833 (1 << 0) #define MGCP_DTMF_INBAND (1 << 1) #define MGCP_DTMF_HYBRID (1 << 2) #define DEFAULT_MGCP_GW_PORT 2427 /*!< From RFC 2705 */ #define DEFAULT_MGCP_CA_PORT 2727 /*!< From RFC 2705 */ #define MGCP_MAX_PACKET 1500 /*!< Also from RFC 2543, should sub headers tho */ #define DEFAULT_RETRANS 1000 /*!< How frequently to retransmit */ #define MAX_RETRANS 5 /*!< Try only 5 times for retransmissions */ /*! MGCP rtp stream modes { */ #define MGCP_CX_SENDONLY 0 #define MGCP_CX_RECVONLY 1 #define MGCP_CX_SENDRECV 2 #define MGCP_CX_CONF 3 #define MGCP_CX_CONFERENCE 3 #define MGCP_CX_MUTE 4 #define MGCP_CX_INACTIVE 4 /*! } */ static const char * const mgcp_cxmodes[] = { "sendonly", "recvonly", "sendrecv", "confrnce", "inactive" }; enum { MGCP_CMD_EPCF, MGCP_CMD_CRCX, MGCP_CMD_MDCX, MGCP_CMD_DLCX, MGCP_CMD_RQNT, MGCP_CMD_NTFY, MGCP_CMD_AUEP, MGCP_CMD_AUCX, MGCP_CMD_RSIP }; static char context[AST_MAX_EXTENSION] = "default"; static char language[MAX_LANGUAGE] = ""; static char musicclass[MAX_MUSICCLASS] = ""; static char parkinglot[AST_MAX_CONTEXT]; static char cid_num[AST_MAX_EXTENSION] = ""; static char cid_name[AST_MAX_EXTENSION] = ""; static int dtmfmode = 0; static int nat = 0; static int ncs = 0; static int pktcgatealloc = 0; static int hangupongateremove = 0; static ast_group_t cur_callergroup = 0; static ast_group_t cur_pickupgroup = 0; static struct { unsigned int tos; unsigned int tos_audio; unsigned int cos; unsigned int cos_audio; } qos = { 0, 0, 0, 0 }; static int immediate = 0; static int callwaiting = 0; static int callreturn = 0; static int slowsequence = 0; static int threewaycalling = 0; /*! This is for flashhook transfers */ static int transfer = 0; static int cancallforward = 0; static int singlepath = 0; static int directmedia = DIRECTMEDIA; static char accountcode[AST_MAX_ACCOUNT_CODE] = ""; static char mailbox[AST_MAX_MAILBOX_UNIQUEID]; static int amaflags = 0; static int adsi = 0; static unsigned int oseq_global = 0; AST_MUTEX_DEFINE_STATIC(oseq_lock); /*! Wait up to 16 seconds for first digit (FXO logic) */ static int firstdigittimeout = 16000; /*! How long to wait for following digits (FXO logic) */ static int gendigittimeout = 8000; /*! How long to wait for an extra digit, if there is an ambiguous match */ static int matchdigittimeout = 3000; /*! 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(netlock); AST_MUTEX_DEFINE_STATIC(monlock); /*! This is the thread for the monitor which checks for input on the channels * which are not currently in use. */ static pthread_t monitor_thread = AST_PTHREADT_NULL; static int restart_monitor(void); static struct ast_format_cap *global_capability; static int nonCodecCapability = AST_RTP_DTMF; static char ourhost[MAXHOSTNAMELEN]; static struct in_addr __ourip; static int ourport; static int mgcpdebug = 0; static struct ast_sched_context *sched; static struct io_context *io; /*! The private structures of the mgcp channels are linked for * selecting outgoing channels */ #define MGCP_MAX_HEADERS 64 #define MGCP_MAX_LINES 64 struct mgcp_request { int len; char *verb; char *identifier; char *endpoint; char *version; int headers; /*!< MGCP Headers */ char *header[MGCP_MAX_HEADERS]; int lines; /*!< SDP Content */ char *line[MGCP_MAX_LINES]; char data[MGCP_MAX_PACKET]; int cmd; /*!< int version of verb = command */ unsigned int trid; /*!< int version of identifier = transaction id */ struct mgcp_request *next; /*!< next in the queue */ }; /*! \brief mgcp_message: MGCP message for queuing up */ struct mgcp_message { struct mgcp_endpoint *owner_ep; struct mgcp_subchannel *owner_sub; int retrans; unsigned long expire; unsigned int seqno; int len; struct mgcp_message *next; char buf[0]; }; #define RESPONSE_TIMEOUT 30 /*!< in seconds */ struct mgcp_response { time_t whensent; int len; int seqno; struct mgcp_response *next; char buf[0]; }; #define MAX_SUBS 2 #define SUB_REAL 0 #define SUB_ALT 1 struct mgcp_subchannel { /*! subchannel magic string. Needed to prove that any subchannel pointer passed by asterisk really points to a valid subchannel memory area. Ugly.. But serves the purpose for the time being. */ #define MGCP_SUBCHANNEL_MAGIC "!978!" char magic[6]; ast_mutex_t lock; int id; struct ast_channel *owner; struct mgcp_endpoint *parent; struct ast_rtp_instance *rtp; struct sockaddr_in tmpdest; char txident[80]; /*! \todo FIXME txident is replaced by rqnt_ident in endpoint. This should be obsoleted */ char cxident[80]; char callid[80]; int cxmode; struct mgcp_request *cx_queue; /*!< pending CX commands */ ast_mutex_t cx_queue_lock; /*!< CX queue lock */ int nat; int iseq; /*!< Not used? RTP? */ int outgoing; int alreadygone; int sdpsent; struct cops_gate *gate; struct mgcp_subchannel *next; /*!< for out circular linked list */ }; #define MGCP_ONHOOK 1 #define MGCP_OFFHOOK 2 #define TYPE_TRUNK 1 #define TYPE_LINE 2 struct mgcp_endpoint { ast_mutex_t lock; char name[80]; struct mgcp_subchannel *sub; /*!< Pointer to our current connection, channel and stuff */ char accountcode[AST_MAX_ACCOUNT_CODE]; char exten[AST_MAX_EXTENSION]; /*!< Extention where to start */ char context[AST_MAX_EXTENSION]; char language[MAX_LANGUAGE]; char cid_num[AST_MAX_EXTENSION]; /*!< Caller*ID number */ char cid_name[AST_MAX_EXTENSION]; /*!< Caller*ID name */ char lastcallerid[AST_MAX_EXTENSION]; /*!< Last Caller*ID */ char dtmf_buf[AST_MAX_EXTENSION]; /*!< place to collect digits be */ char call_forward[AST_MAX_EXTENSION]; /*!< Last Caller*ID */ char musicclass[MAX_MUSICCLASS]; char curtone[80]; /*!< Current tone */ char mailbox[AST_MAX_EXTENSION]; char parkinglot[AST_MAX_CONTEXT]; /*!< Parkinglot */ struct stasis_subscription *mwi_event_sub; ast_group_t callgroup; ast_group_t pickupgroup; int callwaiting; int hascallwaiting; int transfer; int threewaycalling; int singlepath; int cancallforward; int directmedia; int callreturn; int dnd; /* How does this affect callwait? Do we just deny a mgcp_request if we're dnd? */ int hascallerid; int hidecallerid; int dtmfmode; int amaflags; int ncs; int pktcgatealloc; int hangupongateremove; int type; int slowsequence; /*!< MS: Sequence the endpoint as a whole */ int group; int iseq; /*!< Not used? */ int lastout; /*!< tracking this on the subchannels. Is it needed here? */ int needdestroy; /*!< Not used? */ struct ast_format_cap *cap; int nonCodecCapability; int onhooktime; int msgstate; /*!< voicemail message state */ int immediate; int hookstate; int adsi; char rqnt_ident[80]; /*!< request identifier */ struct mgcp_request *rqnt_queue; /*!< pending RQNT commands */ ast_mutex_t rqnt_queue_lock; struct mgcp_request *cmd_queue; /*!< pending commands other than RQNT */ ast_mutex_t cmd_queue_lock; int delme; /*!< needed for reload */ int needaudit; /*!< needed for reload */ struct ast_dsp *dsp; /*!< XXX Should there be a dsp/subchannel? XXX */ /* owner is tracked on the subchannels, and the *sub indicates whos in charge */ /* struct ast_channel *owner; */ /* struct ast_rtp *rtp; */ /* struct sockaddr_in tmpdest; */ /* message go the the endpoint and not the channel so they stay here */ struct ast_variable *chanvars; /*!< Variables to set for channel created by user */ struct mgcp_endpoint *next; struct mgcp_gateway *parent; }; static struct mgcp_gateway { /* A gateway containing one or more endpoints */ char name[80]; int isnamedottedip; /*!< is the name FQDN or dotted ip */ struct sockaddr_in addr; struct sockaddr_in defaddr; struct in_addr ourip; int dynamic; int expire; /*!< XXX Should we ever expire dynamic registrations? XXX */ struct mgcp_endpoint *endpoints; struct ast_ha *ha; /* obsolete time_t lastouttime; int lastout; int messagepending; */ /* Wildcard endpoint name */ char wcardep[30]; struct mgcp_message *msgs; /*!< gw msg queue */ ast_mutex_t msgs_lock; /*!< queue lock */ int retransid; /*!< retrans timer id */ int delme; /*!< needed for reload */ int realtime; struct mgcp_response *responses; struct mgcp_gateway *next; } *gateways = NULL; AST_MUTEX_DEFINE_STATIC(mgcp_reload_lock); static int mgcp_reloading = 0; /*! \brief gatelock: mutex for gateway/endpoint lists */ AST_MUTEX_DEFINE_STATIC(gatelock); static int mgcpsock = -1; static struct sockaddr_in bindaddr; static void mgcp_set_owner(struct mgcp_subchannel *sub, struct ast_channel *chan); static struct ast_frame *mgcp_read(struct ast_channel *ast); static int transmit_response(struct mgcp_subchannel *sub, char *msg, struct mgcp_request *req, char *msgrest); static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone); static int transmit_modify_request(struct mgcp_subchannel *sub); static int transmit_connect(struct mgcp_subchannel *sub); static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, char *tone, char *callernum, char *callername); static int transmit_modify_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp_instance *rtp, const struct ast_format_cap *codecs); static int transmit_connection_del(struct mgcp_subchannel *sub); static int transmit_audit_endpoint(struct mgcp_endpoint *p); static void start_rtp(struct mgcp_subchannel *sub); static void handle_response(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, int result, unsigned int ident, struct mgcp_request *resp); static void dump_cmd_queues(struct mgcp_endpoint *p, struct mgcp_subchannel *sub); static char *mgcp_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static int reload_config(int reload); static struct ast_channel *mgcp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); static int mgcp_call(struct ast_channel *ast, const char *dest, int timeout); static int mgcp_hangup(struct ast_channel *ast); static int mgcp_answer(struct ast_channel *ast); static struct ast_frame *mgcp_read(struct ast_channel *ast); static int mgcp_write(struct ast_channel *ast, struct ast_frame *frame); static int mgcp_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen); static int mgcp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int mgcp_senddigit_begin(struct ast_channel *ast, char digit); static int mgcp_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); static int mgcp_devicestate(const char *data); static void add_header_offhook(struct mgcp_subchannel *sub, struct mgcp_request *resp, char *tone); static int transmit_connect_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp_instance *rtp); static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v); static int mgcp_alloc_pktcgate(struct mgcp_subchannel *sub); static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen); static struct ast_variable *add_var(const char *buf, struct ast_variable *list); static struct ast_variable *copy_vars(struct ast_variable *src); static struct ast_channel_tech mgcp_tech = { .type = "MGCP", .description = tdesc, .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, .requester = mgcp_request, .devicestate = mgcp_devicestate, .call = mgcp_call, .hangup = mgcp_hangup, .answer = mgcp_answer, .read = mgcp_read, .write = mgcp_write, .indicate = mgcp_indicate, .fixup = mgcp_fixup, .send_digit_begin = mgcp_senddigit_begin, .send_digit_end = mgcp_senddigit_end, .func_channel_read = acf_channel_read, }; static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { /* This module does not handle MWI in an event-based manner. However, it * subscribes to MWI for each mailbox that is configured so that the core * knows that we care about it. Then, chan_mgcp will get the MWI from the * event cache instead of checking the mailbox directly. */ } static int has_voicemail(struct mgcp_endpoint *p) { int new_msgs; RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), p->mailbox); if (msg) { struct ast_mwi_state *mwi_state = stasis_message_data(msg); new_msgs = mwi_state->new_msgs; } else { new_msgs = ast_app_has_voicemail(p->mailbox, NULL); } return new_msgs; } static int unalloc_sub(struct mgcp_subchannel *sub) { struct mgcp_endpoint *p = sub->parent; if (p->sub == sub) { ast_log(LOG_WARNING, "Trying to unalloc the real channel %s@%s?!?\n", p->name, p->parent->name); return -1; } ast_debug(1, "Released sub %d of channel %s@%s\n", sub->id, p->name, p->parent->name); mgcp_set_owner(sub, NULL); if (!ast_strlen_zero(sub->cxident)) { transmit_connection_del(sub); } sub->cxident[0] = '\0'; sub->callid[0] = '\0'; sub->cxmode = MGCP_CX_INACTIVE; sub->outgoing = 0; sub->alreadygone = 0; memset(&sub->tmpdest, 0, sizeof(sub->tmpdest)); if (sub->rtp) { ast_rtp_instance_destroy(sub->rtp); sub->rtp = NULL; } dump_cmd_queues(NULL, sub); return 0; } /* modified for new transport mechanism */ static int __mgcp_xmit(struct mgcp_gateway *gw, char *data, int len) { int res; if (gw->addr.sin_addr.s_addr) res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&gw->addr, sizeof(struct sockaddr_in)); else res=sendto(mgcpsock, data, len, 0, (struct sockaddr *)&gw->defaddr, sizeof(struct sockaddr_in)); if (res != len) { ast_log(LOG_WARNING, "mgcp_xmit returned %d: %s\n", res, strerror(errno)); } return res; } static int resend_response(struct mgcp_subchannel *sub, struct mgcp_response *resp) { struct mgcp_endpoint *p = sub->parent; int res; ast_debug(1, "Retransmitting:\n%s\n to %s:%d\n", resp->buf, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); res = __mgcp_xmit(p->parent, resp->buf, resp->len); if (res > 0) res = 0; return res; } static int send_response(struct mgcp_subchannel *sub, struct mgcp_request *req) { struct mgcp_endpoint *p = sub->parent; int res; ast_debug(1, "Transmitting:\n%s\n to %s:%d\n", req->data, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); res = __mgcp_xmit(p->parent, req->data, req->len); if (res > 0) res = 0; return res; } /* modified for new transport framework */ static void dump_queue(struct mgcp_gateway *gw, struct mgcp_endpoint *p) { struct mgcp_message *cur, *q = NULL, *w, *prev; ast_mutex_lock(&gw->msgs_lock); for (prev = NULL, cur = gw->msgs; cur; prev = cur, cur = cur->next) { if (!p || cur->owner_ep == p) { if (prev) { prev->next = cur->next; } else { gw->msgs = cur->next; } ast_log(LOG_NOTICE, "Removing message from %s transaction %u\n", gw->name, cur->seqno); w = cur; if (q) { w->next = q; } else { w->next = NULL; } q = w; } } ast_mutex_unlock(&gw->msgs_lock); while (q) { cur = q; q = q->next; ast_free(cur); } } static void mgcp_queue_frame(struct mgcp_subchannel *sub, struct ast_frame *f) { for (;;) { if (sub->owner) { if (!ast_channel_trylock(sub->owner)) { ast_queue_frame(sub->owner, f); ast_channel_unlock(sub->owner); break; } else { DEADLOCK_AVOIDANCE(&sub->lock); } } else { break; } } } static void mgcp_queue_hangup(struct mgcp_subchannel *sub) { for (;;) { if (sub->owner) { if (!ast_channel_trylock(sub->owner)) { ast_queue_hangup(sub->owner); ast_channel_unlock(sub->owner); break; } else { DEADLOCK_AVOIDANCE(&sub->lock); } } else { break; } } } static void mgcp_queue_control(struct mgcp_subchannel *sub, int control) { struct ast_frame f = { AST_FRAME_CONTROL, { control } }; return mgcp_queue_frame(sub, &f); } static int retrans_pkt(const void *data) { struct mgcp_gateway *gw = (struct mgcp_gateway *)data; struct mgcp_message *cur, *exq = NULL, *w, *prev; int res = 0; /* find out expired msgs */ ast_mutex_lock(&gw->msgs_lock); for (prev = NULL, cur = gw->msgs; cur; prev = cur, cur = cur->next) { if (cur->retrans < MAX_RETRANS) { cur->retrans++; ast_debug(1, "Retransmitting #%d transaction %u on [%s]\n", cur->retrans, cur->seqno, gw->name); __mgcp_xmit(gw, cur->buf, cur->len); } else { if (prev) prev->next = cur->next; else gw->msgs = cur->next; ast_log(LOG_WARNING, "Maximum retries exceeded for transaction %u on [%s]\n", cur->seqno, gw->name); w = cur; if (exq) { w->next = exq; } else { w->next = NULL; } exq = w; } } if (!gw->msgs) { gw->retransid = -1; res = 0; } else { res = 1; } ast_mutex_unlock(&gw->msgs_lock); while (exq) { cur = exq; /* time-out transaction */ handle_response(cur->owner_ep, cur->owner_sub, 406, cur->seqno, NULL); exq = exq->next; ast_free(cur); } return res; } /* modified for the new transaction mechanism */ static int mgcp_postrequest(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, char *data, int len, unsigned int seqno) { struct mgcp_message *msg; struct mgcp_message *cur; struct mgcp_gateway *gw; struct timeval now; if (!(msg = ast_malloc(sizeof(*msg) + len))) { return -1; } if (!(gw = ((p && p->parent) ? p->parent : NULL))) { ast_free(msg); return -1; } msg->owner_sub = sub; msg->owner_ep = p; msg->seqno = seqno; msg->next = NULL; msg->len = len; msg->retrans = 0; memcpy(msg->buf, data, msg->len); ast_mutex_lock(&gw->msgs_lock); for (cur = gw->msgs; cur && cur->next; cur = cur->next); if (cur) { cur->next = msg; } else { gw->msgs = msg; } now = ast_tvnow(); msg->expire = now.tv_sec * 1000 + now.tv_usec / 1000 + DEFAULT_RETRANS; if (gw->retransid == -1) gw->retransid = ast_sched_add(sched, DEFAULT_RETRANS, retrans_pkt, (void *)gw); ast_mutex_unlock(&gw->msgs_lock); __mgcp_xmit(gw, msg->buf, msg->len); /* XXX Should schedule retransmission XXX */ return 0; } /* modified for new transport */ static int send_request(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, struct mgcp_request *req, unsigned int seqno) { int res = 0; struct mgcp_request **queue, *q, *r, *t; ast_mutex_t *l; ast_debug(1, "Slow sequence is %d\n", p->slowsequence); if (p->slowsequence) { queue = &p->cmd_queue; l = &p->cmd_queue_lock; ast_mutex_lock(l); } else { switch (req->cmd) { case MGCP_CMD_DLCX: queue = &sub->cx_queue; l = &sub->cx_queue_lock; ast_mutex_lock(l); q = sub->cx_queue; /* delete pending cx cmds */ /* buggy sb5120 */ if (!sub->parent->ncs) { while (q) { r = q->next; ast_free(q); q = r; } *queue = NULL; } break; case MGCP_CMD_CRCX: case MGCP_CMD_MDCX: queue = &sub->cx_queue; l = &sub->cx_queue_lock; ast_mutex_lock(l); break; case MGCP_CMD_RQNT: queue = &p->rqnt_queue; l = &p->rqnt_queue_lock; ast_mutex_lock(l); break; default: queue = &p->cmd_queue; l = &p->cmd_queue_lock; ast_mutex_lock(l); break; } } if (!(r = ast_malloc(sizeof(*r)))) { ast_log(LOG_WARNING, "Cannot post MGCP request: insufficient memory\n"); ast_mutex_unlock(l); return -1; } memcpy(r, req, sizeof(*r)); if (!(*queue)) { ast_debug(1, "Posting Request:\n%s to %s:%d\n", req->data, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); res = mgcp_postrequest(p, sub, req->data, req->len, seqno); } else { ast_debug(1, "Queueing Request:\n%s to %s:%d\n", req->data, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); } /* XXX find tail. We could also keep tail in the data struct for faster access */ for (t = *queue; t && t->next; t = t->next); r->next = NULL; if (t) t->next = r; else *queue = r; ast_mutex_unlock(l); return res; } static int mgcp_call(struct ast_channel *ast, const char *dest, int timeout) { int res; struct mgcp_endpoint *p; struct mgcp_subchannel *sub; char tone[50] = ""; const char *distinctive_ring = pbx_builtin_getvar_helper(ast, "ALERT_INFO"); ast_debug(3, "MGCP mgcp_call(%s)\n", ast_channel_name(ast)); sub = ast_channel_tech_pvt(ast); p = sub->parent; ast_mutex_lock(&sub->lock); switch (p->hookstate) { case MGCP_OFFHOOK: if (!ast_strlen_zero(distinctive_ring)) { snprintf(tone, sizeof(tone), "L/wt%s", distinctive_ring); ast_debug(3, "MGCP distinctive callwait %s\n", tone); } else { ast_copy_string(tone, (p->ncs ? "L/wt1" : "L/wt"), sizeof(tone)); ast_debug(3, "MGCP normal callwait %s\n", tone); } break; case MGCP_ONHOOK: default: if (!ast_strlen_zero(distinctive_ring)) { snprintf(tone, sizeof(tone), "L/r%s", distinctive_ring); ast_debug(3, "MGCP distinctive ring %s\n", tone); } else { ast_copy_string(tone, "L/rg", sizeof(tone)); ast_debug(3, "MGCP default ring\n"); } break; } if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "mgcp_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); ast_mutex_unlock(&sub->lock); return -1; } res = 0; sub->outgoing = 1; sub->cxmode = MGCP_CX_RECVONLY; ast_setstate(ast, AST_STATE_RINGING); if (p->type == TYPE_LINE) { if (!sub->rtp) { start_rtp(sub); } else { transmit_modify_request(sub); } if (sub->next->owner && !ast_strlen_zero(sub->next->cxident) && !ast_strlen_zero(sub->next->callid)) { /* try to prevent a callwait from disturbing the other connection */ sub->next->cxmode = MGCP_CX_RECVONLY; transmit_modify_request(sub->next); } transmit_notify_request_with_callerid(sub, tone, S_COR(ast_channel_connected(ast)->id.number.valid, ast_channel_connected(ast)->id.number.str, ""), S_COR(ast_channel_connected(ast)->id.name.valid, ast_channel_connected(ast)->id.name.str, "")); ast_setstate(ast, AST_STATE_RINGING); if (sub->next->owner && !ast_strlen_zero(sub->next->cxident) && !ast_strlen_zero(sub->next->callid)) { /* Put the connection back in sendrecv */ sub->next->cxmode = MGCP_CX_SENDRECV; transmit_modify_request(sub->next); } } else { ast_log(LOG_NOTICE, "Don't know how to dial on trunks yet\n"); res = -1; } ast_mutex_unlock(&sub->lock); return res; } static int mgcp_hangup(struct ast_channel *ast) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); struct mgcp_endpoint *p = sub->parent; ast_debug(1, "mgcp_hangup(%s)\n", ast_channel_name(ast)); if (!ast_channel_tech_pvt(ast)) { ast_debug(1, "Asked to hangup channel not connected\n"); return 0; } if (strcmp(sub->magic, MGCP_SUBCHANNEL_MAGIC)) { ast_debug(1, "Invalid magic. MGCP subchannel freed up already.\n"); return 0; } ast_mutex_lock(&sub->lock); ast_debug(3, "MGCP mgcp_hangup(%s) on %s@%s\n", ast_channel_name(ast), p->name, p->parent->name); if ((p->dtmfmode & MGCP_DTMF_INBAND) && p->dsp) { /* check whether other channel is active. */ if (!sub->next->owner) { if (p->dtmfmode & MGCP_DTMF_HYBRID) { p->dtmfmode &= ~MGCP_DTMF_INBAND; } ast_debug(2, "MGCP free dsp on %s@%s\n", p->name, p->parent->name); ast_dsp_free(p->dsp); p->dsp = NULL; } } mgcp_set_owner(sub, NULL); /* for deleting gate */ if (p->pktcgatealloc && sub->gate) { sub->gate->gate_open = NULL; sub->gate->gate_remove = NULL; sub->gate->got_dq_gi = NULL; sub->gate->tech_pvt = NULL; if (sub->gate->state == GATE_ALLOC_PROGRESS || sub->gate->state == GATE_ALLOCATED) { ast_pktccops_gate_alloc(GATE_DEL, sub->gate, 0, 0, 0, 0, 0, 0, NULL, NULL); } else { sub->gate->deltimer = time(NULL) + 5; } sub->gate = NULL; } if (!ast_strlen_zero(sub->cxident)) { transmit_connection_del(sub); } sub->cxident[0] = '\0'; if ((sub == p->sub) && sub->next->owner) { RAII_VAR(struct ast_channel *, bridged, ast_channel_bridge_peer(sub->next->owner), ast_channel_cleanup); if (p->hookstate == MGCP_OFFHOOK) { if (sub->next->owner && bridged) { /* ncs fix! */ transmit_notify_request_with_callerid(p->sub, (p->ncs ? "L/wt1" : "L/wt"), S_COR(ast_channel_caller(bridged)->id.number.valid, ast_channel_caller(bridged)->id.number.str, ""), S_COR(ast_channel_caller(bridged)->id.name.valid, ast_channel_caller(bridged)->id.name.str, "")); } } else { /* set our other connection as the primary and swith over to it */ p->sub = sub->next; p->sub->cxmode = MGCP_CX_RECVONLY; transmit_modify_request(p->sub); if (sub->next->owner && bridged) { transmit_notify_request_with_callerid(p->sub, "L/rg", S_COR(ast_channel_caller(bridged)->id.number.valid, ast_channel_caller(bridged)->id.number.str, ""), S_COR(ast_channel_caller(bridged)->id.name.valid, ast_channel_caller(bridged)->id.name.str, "")); } } } else if ((sub == p->sub->next) && p->hookstate == MGCP_OFFHOOK) { transmit_notify_request(sub, p->ncs ? "" : "L/v"); } else if (p->hookstate == MGCP_OFFHOOK) { transmit_notify_request(sub, "L/ro"); } else { transmit_notify_request(sub, ""); } ast_channel_tech_pvt_set(ast, NULL); sub->alreadygone = 0; sub->outgoing = 0; sub->cxmode = MGCP_CX_INACTIVE; sub->callid[0] = '\0'; if (p) { memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); } /* Reset temporary destination */ memset(&sub->tmpdest, 0, sizeof(sub->tmpdest)); if (sub->rtp) { ast_rtp_instance_destroy(sub->rtp); sub->rtp = NULL; } ast_module_unref(ast_module_info->self); if ((p->hookstate == MGCP_ONHOOK) && (!sub->next->rtp)) { p->hidecallerid = 0; if (p->hascallwaiting && !p->callwaiting) { ast_verb(3, "Enabling call waiting on %s\n", ast_channel_name(ast)); p->callwaiting = -1; } if (has_voicemail(p)) { ast_debug(3, "MGCP mgcp_hangup(%s) on %s@%s set vmwi(+)\n", ast_channel_name(ast), p->name, p->parent->name); transmit_notify_request(sub, "L/vmwi(+)"); } else { ast_debug(3, "MGCP mgcp_hangup(%s) on %s@%s set vmwi(-)\n", ast_channel_name(ast), p->name, p->parent->name); transmit_notify_request(sub, "L/vmwi(-)"); } } ast_mutex_unlock(&sub->lock); return 0; } static char *handle_mgcp_show_endpoints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct mgcp_gateway *mg; struct mgcp_endpoint *me; int hasendpoints = 0; struct ast_variable * v = NULL; switch (cmd) { case CLI_INIT: e->command = "mgcp show endpoints"; e->usage = "Usage: mgcp show endpoints\n" " Lists all endpoints known to the MGCP (Media Gateway Control Protocol) subsystem.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) { return CLI_SHOWUSAGE; } ast_mutex_lock(&gatelock); for (mg = gateways; mg; mg = mg->next) { ast_cli(a->fd, "Gateway '%s' at %s (%s%s)\n", mg->name, mg->addr.sin_addr.s_addr ? ast_inet_ntoa(mg->addr.sin_addr) : ast_inet_ntoa(mg->defaddr.sin_addr), mg->realtime ? "Realtime, " : "", mg->dynamic ? "Dynamic" : "Static"); for (me = mg->endpoints; me; me = me->next) { ast_cli(a->fd, " -- '%s@%s in '%s' is %s\n", me->name, mg->name, me->context, me->sub->owner ? "active" : "idle"); if (me->chanvars) { ast_cli(a->fd, " Variables:\n"); for (v = me->chanvars ; v ; v = v->next) { ast_cli(a->fd, " %s = '%s'\n", v->name, v->value); } } hasendpoints = 1; } if (!hasendpoints) { ast_cli(a->fd, " << No Endpoints Defined >> "); } } ast_mutex_unlock(&gatelock); return CLI_SUCCESS; } static char *handle_mgcp_audit_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct mgcp_gateway *mg; struct mgcp_endpoint *me; int found = 0; char *ename,*gname, *c; switch (cmd) { case CLI_INIT: e->command = "mgcp audit endpoint"; e->usage = "Usage: mgcp audit endpoint \n" " Lists the capabilities of an endpoint in the MGCP (Media Gateway Control Protocol) subsystem.\n" " mgcp debug MUST be on to see the results of this command.\n"; return NULL; case CLI_GENERATE: return NULL; } if (!mgcpdebug) { return CLI_SHOWUSAGE; } if (a->argc != 4) return CLI_SHOWUSAGE; /* split the name into parts by null */ ename = ast_strdupa(a->argv[3]); for (gname = ename; *gname; gname++) { if (*gname == '@') { *gname = 0; gname++; break; } } if (gname[0] == '[') { gname++; } if ((c = strrchr(gname, ']'))) { *c = '\0'; } ast_mutex_lock(&gatelock); for (mg = gateways; mg; mg = mg->next) { if (!strcasecmp(mg->name, gname)) { for (me = mg->endpoints; me; me = me->next) { if (!strcasecmp(me->name, ename)) { found = 1; transmit_audit_endpoint(me); break; } } if (found) { break; } } } if (!found) { ast_cli(a->fd, " << Could not find endpoint >> "); } ast_mutex_unlock(&gatelock); return CLI_SUCCESS; } static char *handle_mgcp_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "mgcp set debug {on|off}"; e->usage = "Usage: mgcp set debug {on|off}\n" " Enables/Disables dumping of MGCP packets for debugging purposes\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != e->args) return CLI_SHOWUSAGE; if (!strncasecmp(a->argv[e->args - 1], "on", 2)) { mgcpdebug = 1; ast_cli(a->fd, "MGCP Debugging Enabled\n"); } else if (!strncasecmp(a->argv[3], "off", 3)) { mgcpdebug = 0; ast_cli(a->fd, "MGCP Debugging Disabled\n"); } else { return CLI_SHOWUSAGE; } return CLI_SUCCESS; } static struct ast_cli_entry cli_mgcp[] = { AST_CLI_DEFINE(handle_mgcp_audit_endpoint, "Audit specified MGCP endpoint"), AST_CLI_DEFINE(handle_mgcp_show_endpoints, "List defined MGCP endpoints"), AST_CLI_DEFINE(handle_mgcp_set_debug, "Enable/Disable MGCP debugging"), AST_CLI_DEFINE(mgcp_reload, "Reload MGCP configuration"), }; static int mgcp_answer(struct ast_channel *ast) { int res = 0; struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); struct mgcp_endpoint *p = sub->parent; ast_mutex_lock(&sub->lock); sub->cxmode = MGCP_CX_SENDRECV; if (!sub->rtp) { start_rtp(sub); } else { transmit_modify_request(sub); } ast_verb(3, "MGCP mgcp_answer(%s) on %s@%s-%d\n", ast_channel_name(ast), p->name, p->parent->name, sub->id); if (ast_channel_state(ast) != AST_STATE_UP) { ast_setstate(ast, AST_STATE_UP); ast_debug(1, "mgcp_answer(%s)\n", ast_channel_name(ast)); transmit_notify_request(sub, ""); transmit_modify_request(sub); } ast_mutex_unlock(&sub->lock); return res; } static struct ast_frame *mgcp_rtp_read(struct mgcp_subchannel *sub) { /* Retrieve audio/etc from channel. Assumes sub->lock is already held. */ struct ast_frame *f; f = ast_rtp_instance_read(sub->rtp, 0); /* Don't send RFC2833 if we're not supposed to */ if (f && (f->frametype == AST_FRAME_DTMF) && !(sub->parent->dtmfmode & MGCP_DTMF_RFC2833)) return &ast_null_frame; if (sub->owner) { /* We already hold the channel lock */ if (f->frametype == AST_FRAME_VOICE) { if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(sub->owner), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_format_cap *caps; ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(f->subclass.format)); caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append(caps, f->subclass.format, 0); ast_channel_nativeformats_set(sub->owner, caps); ao2_ref(caps, -1); } else { return &ast_null_frame; } ast_set_read_format(sub->owner, ast_channel_readformat(sub->owner)); ast_set_write_format(sub->owner, ast_channel_writeformat(sub->owner)); } /* Courtesy fearnor aka alex@pilosoft.com */ if ((sub->parent->dtmfmode & MGCP_DTMF_INBAND) && (sub->parent->dsp)) { #if 0 ast_log(LOG_NOTICE, "MGCP ast_dsp_process\n"); #endif f = ast_dsp_process(sub->owner, sub->parent->dsp, f); } } } return f; } static void mgcp_set_owner(struct mgcp_subchannel *sub, struct ast_channel *chan) { sub->owner = chan; if (sub->rtp) { ast_rtp_instance_set_channel_id(sub->rtp, sub->owner ? ast_channel_uniqueid(chan) : ""); } } static struct ast_frame *mgcp_read(struct ast_channel *ast) { struct ast_frame *f; struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); ast_mutex_lock(&sub->lock); f = mgcp_rtp_read(sub); ast_mutex_unlock(&sub->lock); return f; } static int mgcp_write(struct ast_channel *ast, struct ast_frame *frame) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); int res = 0; if (frame->frametype != AST_FRAME_VOICE) { if (frame->frametype == AST_FRAME_IMAGE) return 0; else { ast_log(LOG_WARNING, "Can't send %u type frames with MGCP write\n", frame->frametype); return 0; } } else { if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *cap_buf = ast_str_alloca(64); 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(ast), &cap_buf), ast_format_get_name(ast_channel_readformat(ast)), ast_format_get_name(ast_channel_writeformat(ast))); /* return -1; */ } } if (sub) { ast_mutex_lock(&sub->lock); if (!sub->sdpsent && sub->gate) { if (sub->gate->state == GATE_ALLOCATED) { ast_debug(1, "GATE ALLOCATED, sending sdp\n"); transmit_modify_with_sdp(sub, NULL, 0); } } if ((sub->parent->sub == sub) || !sub->parent->singlepath) { if (sub->rtp) { res = ast_rtp_instance_write(sub->rtp, frame); } } ast_mutex_unlock(&sub->lock); } return res; } static int mgcp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(newchan); ast_mutex_lock(&sub->lock); ast_log(LOG_NOTICE, "mgcp_fixup(%s, %s)\n", ast_channel_name(oldchan), ast_channel_name(newchan)); if (sub->owner != oldchan) { ast_mutex_unlock(&sub->lock); ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, sub->owner); return -1; } mgcp_set_owner(sub, newchan); ast_mutex_unlock(&sub->lock); return 0; } static int mgcp_senddigit_begin(struct ast_channel *ast, char digit) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); struct mgcp_endpoint *p = sub->parent; int res = 0; ast_mutex_lock(&sub->lock); if (p->dtmfmode & MGCP_DTMF_INBAND || p->dtmfmode & MGCP_DTMF_HYBRID) { ast_debug(1, "Sending DTMF using inband/hybrid\n"); res = -1; /* Let asterisk play inband indications */ } else if (p->dtmfmode & MGCP_DTMF_RFC2833) { ast_debug(1, "Sending DTMF using RFC2833\n"); ast_rtp_instance_dtmf_begin(sub->rtp, digit); } else { ast_log(LOG_ERROR, "Don't know about DTMF_MODE %d\n", p->dtmfmode); } ast_mutex_unlock(&sub->lock); return res; } static int mgcp_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); struct mgcp_endpoint *p = sub->parent; int res = 0; char tmp[4]; ast_mutex_lock(&sub->lock); if (p->dtmfmode & MGCP_DTMF_INBAND || p->dtmfmode & MGCP_DTMF_HYBRID) { ast_debug(1, "Stopping DTMF using inband/hybrid\n"); res = -1; /* Tell Asterisk to stop inband indications */ } else if (p->dtmfmode & MGCP_DTMF_RFC2833) { ast_debug(1, "Stopping DTMF using RFC2833\n"); if (sub->parent->ncs) { tmp[0] = digit; tmp[1] = '\0'; } else { tmp[0] = 'D'; tmp[1] = '/'; tmp[2] = digit; tmp[3] = '\0'; } transmit_notify_request(sub, tmp); ast_rtp_instance_dtmf_end(sub->rtp, digit); } else { ast_log(LOG_ERROR, "Don't know about DTMF_MODE %d\n", p->dtmfmode); } ast_mutex_unlock(&sub->lock); return res; } /*! * \brief mgcp_devicestate: channel callback for device status monitoring * \param data tech/resource name of MGCP device to query * * Callback for device state management in channel subsystem * to obtain device status (up/down) of a specific MGCP endpoint * * \return device status result (from devicestate.h) AST_DEVICE_INVALID (not available) or AST_DEVICE_UNKNOWN (available but unknown state) */ static int mgcp_devicestate(const char *data) { struct mgcp_gateway *g; struct mgcp_endpoint *e = NULL; char *tmp, *endpt, *gw; int ret = AST_DEVICE_INVALID; endpt = ast_strdupa(data); if ((tmp = strchr(endpt, '@'))) { *tmp++ = '\0'; gw = tmp; } else goto error; ast_mutex_lock(&gatelock); for (g = gateways; g; g = g->next) { if (strcasecmp(g->name, gw) == 0) { e = g->endpoints; break; } } if (!e) goto error; for (; e; e = e->next) { if (strcasecmp(e->name, endpt) == 0) { break; } } if (!e) goto error; /* * As long as the gateway/endpoint is valid, we'll * assume that the device is available and its state * can be tracked. */ ret = AST_DEVICE_UNKNOWN; error: ast_mutex_unlock(&gatelock); return ret; } static char *control2str(int ind) { switch (ind) { case AST_CONTROL_HANGUP: return "Other end has hungup"; case AST_CONTROL_RING: return "Local ring"; case AST_CONTROL_RINGING: return "Remote end is ringing"; case AST_CONTROL_ANSWER: return "Remote end has answered"; case AST_CONTROL_BUSY: return "Remote end is busy"; case AST_CONTROL_TAKEOFFHOOK: return "Make it go off hook"; case AST_CONTROL_OFFHOOK: return "Line is off hook"; case AST_CONTROL_CONGESTION: return "Congestion (circuits busy)"; case AST_CONTROL_FLASH: return "Flash hook"; case AST_CONTROL_WINK: return "Wink"; case AST_CONTROL_OPTION: return "Set a low-level option"; case AST_CONTROL_RADIO_KEY: return "Key Radio"; case AST_CONTROL_RADIO_UNKEY: return "Un-Key Radio"; } return "UNKNOWN"; } static int mgcp_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(ast); int res = 0; ast_debug(3, "MGCP asked to indicate %d '%s' condition on channel %s\n", ind, control2str(ind), ast_channel_name(ast)); ast_mutex_lock(&sub->lock); switch(ind) { case AST_CONTROL_RINGING: #ifdef DLINK_BUGGY_FIRMWARE transmit_notify_request(sub, "rt"); #else if (!sub->sdpsent) { /* will hide the inband progress!!! */ transmit_notify_request(sub, sub->parent->ncs ? "L/rt" : "G/rt"); } #endif break; case AST_CONTROL_BUSY: transmit_notify_request(sub, "L/bz"); break; case AST_CONTROL_INCOMPLETE: /* We do not currently support resetting of the Interdigit Timer, so treat * Incomplete control frames as a congestion response */ case AST_CONTROL_CONGESTION: transmit_notify_request(sub, sub->parent->ncs ? "L/cg" : "G/cg"); break; case AST_CONTROL_HOLD: ast_moh_start(ast, data, NULL); break; case AST_CONTROL_UNHOLD: ast_moh_stop(ast); break; case AST_CONTROL_SRCUPDATE: ast_rtp_instance_update_source(sub->rtp); break; case AST_CONTROL_SRCCHANGE: ast_rtp_instance_change_source(sub->rtp); break; case AST_CONTROL_PROGRESS: case AST_CONTROL_PROCEEDING: transmit_modify_request(sub); case -1: transmit_notify_request(sub, ""); break; default: ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind); /* fallthrough */ case AST_CONTROL_PVT_CAUSE_CODE: res = -1; } ast_mutex_unlock(&sub->lock); return res; } static struct ast_channel *mgcp_new(struct mgcp_subchannel *sub, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_format_cap *caps = NULL; struct ast_channel *tmp; struct ast_variable *v = NULL; struct mgcp_endpoint *i = sub->parent; struct ast_format *tmpfmt; caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { ast_log(LOG_ERROR, "Format capabilities could not be created\n"); return NULL; } tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "MGCP/%s@%s-%d", i->name, i->parent->name, sub->id); if (!tmp) { ast_log(LOG_WARNING, "Channel could not be created\n"); ao2_ref(caps, -1); return NULL; } ast_channel_stage_snapshot(tmp); ast_channel_tech_set(tmp, &mgcp_tech); if (ast_format_cap_count(i->cap)) { ast_format_cap_append_from_cap(caps, i->cap, AST_MEDIA_TYPE_UNKNOWN); } else { ast_format_cap_append_from_cap(caps, global_capability, AST_MEDIA_TYPE_UNKNOWN); } ast_channel_nativeformats_set(tmp, caps); ao2_ref(caps, -1); if (sub->rtp) { ast_channel_set_fd(tmp, 0, ast_rtp_instance_fd(sub->rtp, 0)); } if (i->dtmfmode & (MGCP_DTMF_INBAND | MGCP_DTMF_HYBRID)) { i->dsp = ast_dsp_new(); ast_dsp_set_features(i->dsp, DSP_FEATURE_DIGIT_DETECT); /* this is to prevent clipping of dtmf tones during dsp processing */ ast_dsp_set_digitmode(i->dsp, DSP_DIGITMODE_NOQUELCH); } else { i->dsp = NULL; } if (state == AST_STATE_RING) { ast_channel_rings_set(tmp, 1); } tmpfmt = ast_format_cap_get_format(ast_channel_nativeformats(tmp), 0); ast_channel_set_writeformat(tmp, tmpfmt); ast_channel_set_rawwriteformat(tmp, tmpfmt); ast_channel_set_readformat(tmp, tmpfmt); ast_channel_set_rawreadformat(tmp, tmpfmt); ao2_ref(tmpfmt, -1); ast_channel_tech_pvt_set(tmp, sub); if (!ast_strlen_zero(i->language)) ast_channel_language_set(tmp, i->language); if (!ast_strlen_zero(i->accountcode)) ast_channel_accountcode_set(tmp, i->accountcode); if (i->amaflags) ast_channel_amaflags_set(tmp, i->amaflags); mgcp_set_owner(sub, tmp); ast_module_ref(ast_module_info->self); ast_channel_callgroup_set(tmp, i->callgroup); ast_channel_pickupgroup_set(tmp, i->pickupgroup); ast_channel_call_forward_set(tmp, i->call_forward); ast_channel_context_set(tmp, i->context); ast_channel_exten_set(tmp, i->exten); /* Don't use ast_set_callerid() here because it will * generate a needless NewCallerID event */ if (!ast_strlen_zero(i->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num); } if (!i->adsi) { ast_channel_adsicpe_set(tmp, AST_ADSI_UNAVAILABLE); } ast_channel_priority_set(tmp, 1); /* Set channel variables for this call from configuration */ for (v = i->chanvars ; v ; v = v->next) { char valuebuf[1024]; pbx_builtin_setvar_helper(tmp, v->name, ast_get_encoded_str(v->value, valuebuf, sizeof(valuebuf))); } if (sub->rtp) { ast_jb_configure(tmp, &global_jbconf); } ast_channel_stage_snapshot_done(tmp); ast_channel_unlock(tmp); if (state != AST_STATE_DOWN) { if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); tmp = NULL; } } ast_verb(3, "MGCP mgcp_new(%s) created in state: %s\n", ast_channel_name(tmp), ast_state2str(state)); return tmp; } static char *get_sdp_by_line(char* line, char *name, int nameLen) { if (strncasecmp(line, name, nameLen) == 0 && line[nameLen] == '=') { char *r = line + nameLen + 1; while (*r && (*r < 33)) ++r; return r; } return ""; } static char *get_sdp(struct mgcp_request *req, char *name) { int x; int len = strlen(name); char *r; for (x = 0; x < req->lines; x++) { r = get_sdp_by_line(req->line[x], name, len); if (r[0] != '\0') return r; } return ""; } static void sdpLineNum_iterator_init(int *iterator) { *iterator = 0; } static char *get_sdp_iterate(int* iterator, struct mgcp_request *req, char *name) { int len = strlen(name); char *r; while (*iterator < req->lines) { r = get_sdp_by_line(req->line[(*iterator)++], name, len); if (r[0] != '\0') return r; } return ""; } static char *__get_header(struct mgcp_request *req, char *name, int *start, char *def) { int x; int len = strlen(name); char *r; for (x = *start; x < req->headers; x++) { if (!strncasecmp(req->header[x], name, len) && (req->header[x][len] == ':')) { r = req->header[x] + len + 1; while (*r && (*r < 33)) { r++; } *start = x + 1; return r; } } /* Don't return NULL, so get_header is always a valid pointer */ return def; } static char *get_header(struct mgcp_request *req, char *name) { int start = 0; return __get_header(req, name, &start, ""); } /*! \brief get_csv: (SC:) get comma separated value */ static char *get_csv(char *c, int *len, char **next) { char *s; *next = NULL, *len = 0; if (!c) return NULL; while (*c && (*c < 33 || *c == ',')) { c++; } s = c; while (*c && (*c >= 33 && *c != ',')) { c++, (*len)++; } *next = c; if (*len == 0) { s = NULL, *next = NULL; } return s; } static struct mgcp_gateway *find_realtime_gw(char *name, char *at, struct sockaddr_in *sin) { struct mgcp_gateway *g = NULL; struct ast_variable *mgcpgwconfig = NULL; struct ast_variable *gwv, *epname = NULL; struct mgcp_endpoint *e; char lines[256]; int i, j; ast_debug(1, "*** find Realtime MGCPGW\n"); if (!(i = ast_check_realtime("mgcpgw")) || !(j = ast_check_realtime("mgcpep"))) { return NULL; } if (ast_strlen_zero(at)) { ast_debug(1, "null gw name\n"); return NULL; } if (!(mgcpgwconfig = ast_load_realtime("mgcpgw", "name", at, NULL))) { return NULL; } /*! * \note This is a fairly odd way of instantiating lines. Instead of each * line created by virtue of being in the database (and loaded via * ast_load_realtime_multientry), this code forces a specific order with a * "lines" entry in the "mgcpgw" record. This has benefits, because as with * chan_dahdi, values are inherited across definitions. The downside is * that it's not as clear what the values will be simply by looking at a * single row in the database, and it's probable that the sanest configuration * should have the first column in the "mgcpep" table be "clearvars", with a * static value of "all", if any variables are set at all. It may be worth * making this assumption explicit in the code in the future, and then just * using ast_load_realtime_multientry for the "mgcpep" records. */ lines[0] = '\0'; for (gwv = mgcpgwconfig; gwv; gwv = gwv->next) { if (!strcasecmp(gwv->name, "lines")) { ast_copy_string(lines, gwv->value, sizeof(lines)); break; } } /* Position gwv at the end of the list */ for (gwv = gwv && gwv->next ? gwv : mgcpgwconfig; gwv->next; gwv = gwv->next); if (!ast_strlen_zero(lines)) { AST_DECLARE_APP_ARGS(args, AST_APP_ARG(line)[100]; ); AST_STANDARD_APP_ARGS(args, lines); for (i = 0; i < args.argc; i++) { gwv->next = ast_load_realtime("mgcpep", "name", at, "line", args.line[i], NULL); /* Remove "line" AND position gwv at the end of the list. */ for (epname = NULL; gwv->next; gwv = gwv->next) { if (!strcasecmp(gwv->next->name, "line")) { /* Remove it from the list */ epname = gwv->next; gwv->next = gwv->next->next; } } /* Since "line" instantiates the configuration, we have to move it to the end. */ if (epname) { gwv->next = epname; epname->next = NULL; gwv = gwv->next; } } } for (gwv = mgcpgwconfig; gwv; gwv = gwv->next) { ast_debug(1, "MGCP Realtime var: %s => %s\n", gwv->name, gwv->value); } if (mgcpgwconfig) { g = build_gateway(at, mgcpgwconfig); ast_variables_destroy(mgcpgwconfig); } if (g) { g->next = gateways; g->realtime = 1; gateways = g; for (e = g->endpoints; e; e = e->next) { transmit_audit_endpoint(e); e->needaudit = 0; } } return g; } static struct mgcp_subchannel *find_subchannel_and_lock(char *name, int msgid, struct sockaddr_in *sin) { struct mgcp_endpoint *p = NULL; struct mgcp_subchannel *sub = NULL; struct mgcp_gateway *g; char tmp[256] = ""; char *at = NULL, *c; int found = 0; if (name) { ast_copy_string(tmp, name, sizeof(tmp)); at = strchr(tmp, '@'); if (!at) { ast_log(LOG_NOTICE, "Endpoint '%s' has no at sign!\n", name); return NULL; } *at++ = '\0'; } ast_mutex_lock(&gatelock); if (at && (at[0] == '[')) { at++; c = strrchr(at, ']'); if (c) { *c = '\0'; } } for (g = gateways ? gateways : find_realtime_gw(name, at, sin); g; g = g->next ? g->next : find_realtime_gw(name, at, sin)) { if ((!name || !strcasecmp(g->name, at)) && (sin || g->addr.sin_addr.s_addr || g->defaddr.sin_addr.s_addr)) { /* Found the gateway. If it's dynamic, save it's address -- now for the endpoint */ if (sin && g->dynamic && name) { if ((g->addr.sin_addr.s_addr != sin->sin_addr.s_addr) || (g->addr.sin_port != sin->sin_port)) { memcpy(&g->addr, sin, sizeof(g->addr)); { struct ast_sockaddr tmp1, tmp2; struct sockaddr_in tmp3 = {0,}; tmp3.sin_addr = g->ourip; ast_sockaddr_from_sin(&tmp1, &g->addr); ast_sockaddr_from_sin(&tmp2, &tmp3); if (ast_ouraddrfor(&tmp1, &tmp2)) { memcpy(&g->ourip, &__ourip, sizeof(g->ourip)); } ast_sockaddr_to_sin(&tmp2, &tmp3); g->ourip = tmp3.sin_addr; } ast_verb(3, "Registered MGCP gateway '%s' at %s port %d\n", g->name, ast_inet_ntoa(g->addr.sin_addr), ntohs(g->addr.sin_port)); } /* not dynamic, check if the name matches */ } else if (name) { if (strcasecmp(g->name, at)) { continue; } /* not dynamic, no name, check if the addr matches */ } else if (!name && sin) { if ((g->addr.sin_addr.s_addr != sin->sin_addr.s_addr) || (g->addr.sin_port != sin->sin_port)) { continue; } } else { continue; } for (p = g->endpoints; p; p = p->next) { ast_debug(1, "Searching on %s@%s for subchannel\n", p->name, g->name); if (msgid) { sub = p->sub; found = 1; break; } else if (name && !strcasecmp(p->name, tmp)) { ast_debug(1, "Coundn't determine subchannel, assuming current master %s@%s-%d\n", p->name, g->name, p->sub->id); sub = p->sub; found = 1; break; } } if (sub && found) { ast_mutex_lock(&sub->lock); break; } } } ast_mutex_unlock(&gatelock); if (!sub) { if (name) { if (g) { ast_log(LOG_NOTICE, "Endpoint '%s' not found on gateway '%s'\n", tmp, at); } else { ast_log(LOG_NOTICE, "Gateway '%s' (and thus its endpoint '%s') does not exist\n", at, tmp); } } } return sub; } static void parse(struct mgcp_request *req) { /* Divide fields by NULL's */ char *c; int f = 0; c = req->data; /* First header starts immediately */ req->header[f] = c; for (; *c; c++) { if (*c == '\n') { /* We've got a new header */ *c = 0; ast_debug(3, "Header: %s (%d)\n", req->header[f], (int) strlen(req->header[f])); if (ast_strlen_zero(req->header[f])) { /* Line by itself means we're now in content */ c++; break; } if (f >= MGCP_MAX_HEADERS - 1) { ast_log(LOG_WARNING, "Too many MGCP headers...\n"); } else { f++; } req->header[f] = c + 1; } else if (*c == '\r') { /* Ignore but eliminate \r's */ *c = 0; } } /* Check for last header */ if (!ast_strlen_zero(req->header[f])) { f++; } req->headers = f; /* Now we process any mime content */ f = 0; req->line[f] = c; for (; *c; c++) { if (*c == '\n') { /* We've got a new line */ *c = 0; ast_debug(3, "Line: %s (%d)\n", req->line[f], (int) strlen(req->line[f])); if (f >= MGCP_MAX_LINES - 1) { ast_log(LOG_WARNING, "Too many SDP lines...\n"); } else { f++; } req->line[f] = c + 1; } else if (*c == '\r') { /* Ignore and eliminate \r's */ *c = 0; } } /* Check for last line */ if (!ast_strlen_zero(req->line[f])) { f++; } req->lines = f; /* Parse up the initial header */ c = req->header[0]; while (*c && *c < 33) c++; /* First the verb */ req->verb = c; while (*c && (*c > 32)) c++; if (*c) { *c = '\0'; c++; while (*c && (*c < 33)) c++; req->identifier = c; while (*c && (*c > 32)) c++; if (*c) { *c = '\0'; c++; while (*c && (*c < 33)) c++; req->endpoint = c; while (*c && (*c > 32)) c++; if (*c) { *c = '\0'; c++; while (*c && (*c < 33)) c++; req->version = c; while (*c && (*c > 32)) c++; while (*c && (*c < 33)) c++; while (*c && (*c > 32)) c++; *c = '\0'; } } } ast_debug(1, "Verb: '%s', Identifier: '%s', Endpoint: '%s', Version: '%s'\n", req->verb, req->identifier, req->endpoint, req->version); ast_debug(1, "%d headers, %d lines\n", req->headers, req->lines); if (*c) { ast_log(LOG_WARNING, "Odd content, extra stuff left over ('%s')\n", c); } } static int process_sdp(struct mgcp_subchannel *sub, struct mgcp_request *req) { char *m; char *c; char *a; char host[258]; int len = 0; int portno; struct ast_format_cap *peercap; int peerNonCodecCapability; struct sockaddr_in sin; struct ast_sockaddr sin_tmp; char *codecs; struct ast_hostent ahp; struct hostent *hp; int codec, codec_count=0; int iterator; struct mgcp_endpoint *p = sub->parent; struct ast_str *global_buf = ast_str_alloca(64); struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *pvt_buf = ast_str_alloca(64); /* Get codec and RTP info from SDP */ m = get_sdp(req, "m"); c = get_sdp(req, "c"); if (ast_strlen_zero(m) || ast_strlen_zero(c)) { ast_log(LOG_WARNING, "Insufficient information for SDP (m = '%s', c = '%s')\n", m, c); return -1; } if (sscanf(c, "IN IP4 %256s", host) != 1) { ast_log(LOG_WARNING, "Invalid host in c= line, '%s'\n", c); return -1; } /* XXX This could block for a long time, and block the main thread! XXX */ hp = ast_gethostbyname(host, &ahp); if (!hp) { ast_log(LOG_WARNING, "Unable to lookup host in c= line, '%s'\n", c); return -1; } if (sscanf(m, "audio %30d RTP/AVP %n", &portno, &len) != 1 || !len) { ast_log(LOG_WARNING, "Malformed media stream descriptor: %s\n", m); return -1; } sin.sin_family = AF_INET; memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr)); sin.sin_port = htons(portno); ast_sockaddr_from_sin(&sin_tmp, &sin); ast_rtp_instance_set_remote_address(sub->rtp, &sin_tmp); ast_debug(3, "Peer RTP is at port %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); /* Scan through the RTP payload types specified in a "m=" line: */ ast_rtp_codecs_payloads_clear(ast_rtp_instance_get_codecs(sub->rtp), sub->rtp); codecs = ast_strdupa(m + len); while (!ast_strlen_zero(codecs)) { if (sscanf(codecs, "%30d%n", &codec, &len) != 1) { if (codec_count) { break; } ast_log(LOG_WARNING, "Error in codec string '%s' at '%s'\n", m, codecs); return -1; } ast_rtp_codecs_payloads_set_m_type(ast_rtp_instance_get_codecs(sub->rtp), sub->rtp, codec); codec_count++; codecs += len; } /* Next, scan through each "a=rtpmap:" line, noting each */ /* specified RTP payload type (with corresponding MIME subtype): */ sdpLineNum_iterator_init(&iterator); while ((a = get_sdp_iterate(&iterator, req, "a"))[0] != '\0') { char* mimeSubtype = ast_strdupa(a); /* ensures we have enough space */ if (sscanf(a, "rtpmap: %30d %127[^/]/", &codec, mimeSubtype) != 2) continue; /* Note: should really look at the 'freq' and '#chans' params too */ ast_rtp_codecs_payloads_set_rtpmap_type(ast_rtp_instance_get_codecs(sub->rtp), sub->rtp, codec, "audio", mimeSubtype, 0); } /* Now gather all of the codecs that were asked for: */ if (!(peercap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return -1; } ast_rtp_codecs_payload_formats(ast_rtp_instance_get_codecs(sub->rtp), peercap, &peerNonCodecCapability); ast_format_cap_get_compatible(global_capability, peercap, p->cap); ast_debug(1, "Capabilities: us - %s, them - %s, combined - %s\n", ast_format_cap_get_names(global_capability, &global_buf), ast_format_cap_get_names(peercap, &peer_buf), ast_format_cap_get_names(p->cap, &pvt_buf)); ao2_ref(peercap, -1); ast_debug(1, "Non-codec capabilities: us - %d, them - %d, combined - %d\n", nonCodecCapability, peerNonCodecCapability, p->nonCodecCapability); if (!ast_format_cap_count(p->cap)) { ast_log(LOG_WARNING, "No compatible codecs!\n"); return -1; } return 0; } static int add_header(struct mgcp_request *req, const char *var, const char *value) { if (req->len >= sizeof(req->data) - 4) { ast_log(LOG_WARNING, "Out of space, can't add anymore\n"); return -1; } if (req->lines) { ast_log(LOG_WARNING, "Can't add more headers when lines have been added\n"); return -1; } req->header[req->headers] = req->data + req->len; snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s: %s\r\n", var, value); req->len += strlen(req->header[req->headers]); if (req->headers < MGCP_MAX_HEADERS) { req->headers++; } else { ast_log(LOG_WARNING, "Out of header space\n"); return -1; } return 0; } static int add_line(struct mgcp_request *req, char *line) { if (req->len >= sizeof(req->data) - 4) { ast_log(LOG_WARNING, "Out of space, can't add anymore\n"); return -1; } if (!req->lines) { /* Add extra empty return */ ast_copy_string(req->data + req->len, "\r\n", sizeof(req->data) - req->len); req->len += strlen(req->data + req->len); } req->line[req->lines] = req->data + req->len; snprintf(req->line[req->lines], sizeof(req->data) - req->len, "%s", line); req->len += strlen(req->line[req->lines]); if (req->lines < MGCP_MAX_LINES) { req->lines++; } else { ast_log(LOG_WARNING, "Out of line space\n"); return -1; } return 0; } static int init_resp(struct mgcp_request *req, char *resp, struct mgcp_request *orig, char *resprest) { /* Initialize a response */ if (req->headers || req->len) { ast_log(LOG_WARNING, "Request already initialized?!?\n"); return -1; } req->header[req->headers] = req->data + req->len; snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %s %s\r\n", resp, orig->identifier, resprest); req->len += strlen(req->header[req->headers]); if (req->headers < MGCP_MAX_HEADERS) { req->headers++; } else { ast_log(LOG_WARNING, "Out of header space\n"); } return 0; } static int init_req(struct mgcp_endpoint *p, struct mgcp_request *req, char *verb, unsigned int oseq) { /* Initialize a response */ if (req->headers || req->len) { ast_log(LOG_WARNING, "Request already initialized?!?\n"); return -1; } req->header[req->headers] = req->data + req->len; /* check if we need brackets around the gw name */ if (p->parent->isnamedottedip) { snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %u %s@[%s] MGCP 1.0%s\r\n", verb, oseq, p->name, p->parent->name, p->ncs ? " NCS 1.0" : ""); } else { + snprintf(req->header[req->headers], sizeof(req->data) - req->len, "%s %u %s@%s MGCP 1.0%s\r\n", verb, oseq, p->name, p->parent->name, p->ncs ? " NCS 1.0" : ""); } req->len += strlen(req->header[req->headers]); if (req->headers < MGCP_MAX_HEADERS) { req->headers++; } else { ast_log(LOG_WARNING, "Out of header space\n"); } return 0; } static int respprep(struct mgcp_request *resp, struct mgcp_endpoint *p, char *msg, struct mgcp_request *req, char *msgrest) { memset(resp, 0, sizeof(*resp)); init_resp(resp, msg, req, msgrest); return 0; } static int reqprep(struct mgcp_request *req, struct mgcp_endpoint *p, char *verb) { unsigned int oseq; memset(req, 0, sizeof(struct mgcp_request)); ast_mutex_lock(&oseq_lock); oseq_global++; if (oseq_global > 999999999) { oseq_global = 1; } oseq = oseq_global; ast_mutex_unlock(&oseq_lock); init_req(p, req, verb, oseq); return oseq; } static int transmit_response(struct mgcp_subchannel *sub, char *msg, struct mgcp_request *req, char *msgrest) { struct mgcp_request resp; struct mgcp_endpoint *p = sub->parent; struct mgcp_response *mgr; if (!sub) { return -1; } respprep(&resp, p, msg, req, msgrest); if (!(mgr = ast_calloc(1, sizeof(*mgr) + resp.len + 1))) { return send_response(sub, &resp); } /* Store MGCP response in case we have to retransmit */ sscanf(req->identifier, "%30d", &mgr->seqno); time(&mgr->whensent); mgr->len = resp.len; memcpy(mgr->buf, resp.data, resp.len); mgr->buf[resp.len] = '\0'; mgr->next = p->parent->responses; p->parent->responses = mgr; return send_response(sub, &resp); } static int add_sdp(struct mgcp_request *resp, struct mgcp_subchannel *sub, struct ast_rtp_instance *rtp) { int len; int codec; char costr[80]; struct sockaddr_in sin; struct ast_sockaddr sin_tmp; char v[256]; char s[256]; char o[256]; char c[256]; char t[256]; char m[256] = ""; char a[1024] = ""; int x; struct sockaddr_in dest = { 0, }; struct ast_sockaddr dest_tmp; struct mgcp_endpoint *p = sub->parent; /* XXX We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us XXX */ len = 0; if (!sub->rtp) { ast_log(LOG_WARNING, "No way to add SDP without an RTP structure\n"); return -1; } ast_rtp_instance_get_local_address(sub->rtp, &sin_tmp); ast_sockaddr_to_sin(&sin_tmp, &sin); if (rtp) { ast_rtp_instance_get_remote_address(sub->rtp, &dest_tmp); ast_sockaddr_to_sin(&dest_tmp, &dest); } else { if (sub->tmpdest.sin_addr.s_addr) { dest.sin_addr = sub->tmpdest.sin_addr; dest.sin_port = sub->tmpdest.sin_port; /* Reset temporary destination */ memset(&sub->tmpdest, 0, sizeof(sub->tmpdest)); } else { dest.sin_addr = p->parent->ourip; dest.sin_port = sin.sin_port; } } ast_debug(1, "We're at %s port %d\n", ast_inet_ntoa(p->parent->ourip), ntohs(sin.sin_port)); ast_copy_string(v, "v=0\r\n", sizeof(v)); snprintf(o, sizeof(o), "o=root %d %d IN IP4 %s\r\n", (int)getpid(), (int)getpid(), ast_inet_ntoa(dest.sin_addr)); ast_copy_string(s, "s=session\r\n", sizeof(s)); snprintf(c, sizeof(c), "c=IN IP4 %s\r\n", ast_inet_ntoa(dest.sin_addr)); ast_copy_string(t, "t=0 0\r\n", sizeof(t)); snprintf(m, sizeof(m), "m=audio %d RTP/AVP", ntohs(dest.sin_port)); for (x = 0; x < ast_format_cap_count(p->cap); x++) { struct ast_format *format = ast_format_cap_get_format(p->cap, x); if (ast_format_get_type(format) != AST_MEDIA_TYPE_AUDIO) { ao2_ref(format, -1); continue; } ast_debug(1, "Answering with capability %s\n", ast_format_get_name(format)); codec = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(sub->rtp), 1, format, 0); if (codec > -1) { snprintf(costr, sizeof(costr), " %d", codec); strncat(m, costr, sizeof(m) - strlen(m) - 1); snprintf(costr, sizeof(costr), "a=rtpmap:%d %s/8000\r\n", codec, ast_rtp_lookup_mime_subtype2(1, format, 0, 0)); strncat(a, costr, sizeof(a) - strlen(a) - 1); } ao2_ref(format, -1); } for (x = 1LL; x <= AST_RTP_MAX; x <<= 1) { if (p->nonCodecCapability & x) { ast_debug(1, "Answering with non-codec capability %d\n", (int) x); codec = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(sub->rtp), 0, NULL, x); if (codec > -1) { snprintf(costr, sizeof(costr), " %d", codec); strncat(m, costr, sizeof(m) - strlen(m) - 1); snprintf(costr, sizeof(costr), "a=rtpmap:%d %s/8000\r\n", codec, ast_rtp_lookup_mime_subtype2(0, NULL, x, 0)); strncat(a, costr, sizeof(a) - strlen(a) - 1); if (x == AST_RTP_DTMF) { /* Indicate we support DTMF... Not sure about 16, but MSN supports it so dang it, we will too... */ snprintf(costr, sizeof costr, "a=fmtp:%d 0-16\r\n", codec); strncat(a, costr, sizeof(a) - strlen(a) - 1); } } } } strncat(m, "\r\n", sizeof(m) - strlen(m) - 1); len = strlen(v) + strlen(s) + strlen(o) + strlen(c) + strlen(t) + strlen(m) + strlen(a); snprintf(costr, sizeof(costr), "%d", len); add_line(resp, v); add_line(resp, o); add_line(resp, s); add_line(resp, c); add_line(resp, t); add_line(resp, m); add_line(resp, a); return 0; } static int transmit_modify_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp_instance *rtp, const struct ast_format_cap *codecs) { struct mgcp_request resp; char local[256]; char tmp[80]; struct mgcp_endpoint *p = sub->parent; int i; struct ast_sockaddr sub_tmpdest_tmp; unsigned int oseq; if (ast_strlen_zero(sub->cxident) && rtp) { /* We don't have a CXident yet, store the destination and wait a bit */ ast_rtp_instance_get_remote_address(rtp, &sub_tmpdest_tmp); ast_sockaddr_to_sin(&sub_tmpdest_tmp, &sub->tmpdest); return 0; } ast_copy_string(local, "e:on, s:off, p:20", sizeof(local)); for (i = 0; i < ast_format_cap_count(p->cap); i++) { struct ast_format *format = ast_format_cap_get_format(p->cap, i); if (ast_format_get_type(format) != AST_MEDIA_TYPE_AUDIO) { ao2_ref(format, -1); continue; } snprintf(tmp, sizeof(tmp), ", a:%s", ast_rtp_lookup_mime_subtype2(1, format, 0, 0)); strncat(local, tmp, sizeof(local) - strlen(local) - 1); ao2_ref(format, -1); } if (sub->gate) { if (sub->gate->state == GATE_ALLOCATED || sub->gate->state == GATE_OPEN) { snprintf(tmp, sizeof(tmp), ", dq-gi:%x", sub->gate->gateid); strncat(local, tmp, sizeof(local) - strlen(local) - 1); sub->sdpsent = 1; } else { /* oops wait */ ast_debug(1, "Waiting for opened gate...\n"); sub->sdpsent = 0; return 0; } } oseq = reqprep(&resp, p, "MDCX"); add_header(&resp, "C", sub->callid); add_header(&resp, "L", local); add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]); /* X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); add_header(&resp, "I", sub->cxident); /*add_header(&resp, "S", "");*/ add_sdp(&resp, sub, rtp); /* fill in new fields */ resp.cmd = MGCP_CMD_MDCX; resp.trid = oseq; return send_request(p, sub, &resp, oseq); } static int transmit_connect_with_sdp(struct mgcp_subchannel *sub, struct ast_rtp_instance *rtp) { struct mgcp_request resp; char local[256]; char tmp[80]; int i; struct mgcp_endpoint *p = sub->parent; unsigned int oseq; ast_debug(3, "Creating connection for %s@%s-%d in cxmode: %s callid: %s\n", p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid); ast_copy_string(local, "e:on, s:off, p:20", sizeof(local)); for (i = 0; i < ast_format_cap_count(p->cap); i++) { struct ast_format *format = ast_format_cap_get_format(p->cap, i); if (ast_format_get_type(format) != AST_MEDIA_TYPE_AUDIO) { ao2_ref(format, -1); continue; } snprintf(tmp, sizeof(tmp), ", a:%s", ast_rtp_lookup_mime_subtype2(1, format, 0, 0)); strncat(local, tmp, sizeof(local) - strlen(local) - 1); ao2_ref(format, -1); } if (sub->gate) { if(sub->gate->state == GATE_ALLOCATED) { snprintf(tmp, sizeof(tmp), ", dq-gi:%x", sub->gate->gateid); strncat(local, tmp, sizeof(local) - strlen(local) - 1); } } sub->sdpsent = 1; oseq = reqprep(&resp, p, "CRCX"); add_header(&resp, "C", sub->callid); add_header(&resp, "L", local); add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]); /* X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); /*add_header(&resp, "S", "");*/ add_sdp(&resp, sub, rtp); /* fill in new fields */ resp.cmd = MGCP_CMD_CRCX; resp.trid = oseq; return send_request(p, sub, &resp, oseq); } static int mgcp_pktcgate_remove(struct cops_gate *gate) { struct mgcp_subchannel *sub = gate->tech_pvt; if (!sub) { return 1; } ast_mutex_lock(&sub->lock); ast_debug(1, "Pktc: gate 0x%x deleted\n", gate->gateid); if (sub->gate->state != GATE_CLOSED && sub->parent->hangupongateremove) { sub->gate = NULL; if (sub->owner) { ast_softhangup(sub->owner, AST_CAUSE_REQUESTED_CHAN_UNAVAIL); ast_channel_unlock(sub->owner); } } else { sub->gate = NULL; } ast_mutex_unlock(&sub->lock); return 1; } static int mgcp_pktcgate_open(struct cops_gate *gate) { struct mgcp_subchannel *sub = gate->tech_pvt; if (!sub) { return 1; } ast_mutex_lock(&sub->lock); ast_debug(1, "Pktc: gate 0x%x open\n", gate->gateid); if (!sub->sdpsent) transmit_modify_with_sdp(sub, NULL, 0); ast_mutex_unlock(&sub->lock); return 1; } static int mgcp_alloc_pktcgate(struct mgcp_subchannel *sub) { struct mgcp_endpoint *p = sub->parent; sub->gate = ast_pktccops_gate_alloc(GATE_SET, NULL, ntohl(p->parent->addr.sin_addr.s_addr), 8, 128000, 232, 0, 0, NULL, &mgcp_pktcgate_remove); if (!sub->gate) { return 0; } sub->gate->tech_pvt = sub; sub->gate->gate_open = &mgcp_pktcgate_open; return 1; } static int transmit_connect(struct mgcp_subchannel *sub) { struct mgcp_request resp; int x; char local[256]; char tmp[80]; struct ast_format *tmpfmt; struct mgcp_endpoint *p = sub->parent; unsigned int oseq; ast_copy_string(local, "p:20, s:off, e:on", sizeof(local)); for (x = 0; x < ast_format_cap_count(p->cap); x++) { tmpfmt = ast_format_cap_get_format(p->cap, x); snprintf(tmp, sizeof(tmp), ", a:%s", ast_rtp_lookup_mime_subtype2(1, tmpfmt, 0, 0)); strncat(local, tmp, sizeof(local) - strlen(local) - 1); ao2_ref(tmpfmt, -1); } ast_debug(3, "Creating connection for %s@%s-%d in cxmode: %s callid: %s\n", p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid); sub->sdpsent = 0; oseq = reqprep(&resp, p, "CRCX"); add_header(&resp, "C", sub->callid); add_header(&resp, "L", local); add_header(&resp, "M", "inactive"); /* X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); /*add_header(&resp, "S", "");*/ /* fill in new fields */ resp.cmd = MGCP_CMD_CRCX; resp.trid = oseq; return send_request(p, sub, &resp, oseq); } static int transmit_notify_request(struct mgcp_subchannel *sub, char *tone) { struct mgcp_request resp; struct mgcp_endpoint *p = sub->parent; unsigned int oseq; ast_debug(3, "MGCP Asked to indicate tone: %s on %s@%s-%d in cxmode: %s\n", tone, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode]); ast_copy_string(p->curtone, tone, sizeof(p->curtone)); oseq = reqprep(&resp, p, "RQNT"); add_header(&resp, "X", p->rqnt_ident); switch (p->hookstate) { case MGCP_ONHOOK: add_header(&resp, "R", "L/hd(N)"); break; case MGCP_OFFHOOK: add_header_offhook(sub, &resp, tone); break; } if (!ast_strlen_zero(tone)) { add_header(&resp, "S", tone); } /* fill in new fields */ resp.cmd = MGCP_CMD_RQNT; resp.trid = oseq; return send_request(p, NULL, &resp, oseq); } static int transmit_notify_request_with_callerid(struct mgcp_subchannel *sub, char *tone, char *callernum, char *callername) { struct mgcp_request resp; char tone2[256]; char *l, *n; struct timeval t = ast_tvnow(); struct ast_tm tm; struct mgcp_endpoint *p = sub->parent; unsigned int oseq; ast_localtime(&t, &tm, NULL); n = callername; l = callernum; if (!n) n = ""; if (!l) l = ""; /* Keep track of last callerid for blacklist and callreturn */ ast_copy_string(p->lastcallerid, l, sizeof(p->lastcallerid)); snprintf(tone2, sizeof(tone2), "%s,L/ci(%02d/%02d/%02d/%02d,%s,%s)", tone, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, l, n); ast_copy_string(p->curtone, tone, sizeof(p->curtone)); oseq = reqprep(&resp, p, "RQNT"); add_header(&resp, "X", p->rqnt_ident); switch (p->hookstate) { case MGCP_ONHOOK: add_header(&resp, "R", "L/hd(N)"); break; case MGCP_OFFHOOK: add_header_offhook(sub, &resp, tone); break; } if (!ast_strlen_zero(tone2)) { add_header(&resp, "S", tone2); } ast_debug(3, "MGCP Asked to indicate tone: %s on %s@%s-%d in cxmode: %s\n", tone2, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode]); /* fill in new fields */ resp.cmd = MGCP_CMD_RQNT; resp.trid = oseq; return send_request(p, NULL, &resp, oseq); } static int transmit_modify_request(struct mgcp_subchannel *sub) { struct mgcp_request resp; struct mgcp_endpoint *p = sub->parent; int i; int fc = 1; char local[256]; char tmp[80]; unsigned int oseq; if (ast_strlen_zero(sub->cxident)) { /* We don't have a CXident yet, store the destination and wait a bit */ return 0; } ast_debug(3, "Modified %s@%s-%d with new mode: %s on callid: %s\n", p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid); ast_copy_string(local, "", sizeof(local)); for (i = 0; i < ast_format_cap_count(p->cap); i++) { struct ast_format *format = ast_format_cap_get_format(p->cap, i); if (p->ncs && !fc) { ast_format_cap_remove_by_type(p->cap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(p->cap, format, 0); /* sb5120e bug */ ao2_ref(format, -1); break; } else { fc = 0; snprintf(tmp, sizeof(tmp), ", a:%s", ast_rtp_lookup_mime_subtype2(1, format, 0, 0)); } strncat(local, tmp, sizeof(local) - strlen(local) - 1); ao2_ref(format, -1); } if (!sub->sdpsent) { if (sub->gate) { if (sub->gate->state == GATE_ALLOCATED || sub->gate->state == GATE_OPEN) { snprintf(tmp, sizeof(tmp), ", dq-gi:%x", sub->gate->gateid); strncat(local, tmp, sizeof(local) - strlen(local) - 1); } else { /* we still don't have gateid wait */ return 0; } } } oseq = reqprep(&resp, p, "MDCX"); add_header(&resp, "C", sub->callid); if (!sub->sdpsent) { add_header(&resp, "L", local); } add_header(&resp, "M", mgcp_cxmodes[sub->cxmode]); /* X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); add_header(&resp, "I", sub->cxident); switch (sub->parent->hookstate) { case MGCP_ONHOOK: add_header(&resp, "R", "L/hd(N)"); break; case MGCP_OFFHOOK: add_header_offhook(sub, &resp, ""); break; } if (!sub->sdpsent) { add_sdp(&resp, sub, NULL); sub->sdpsent = 1; } /* fill in new fields */ resp.cmd = MGCP_CMD_MDCX; resp.trid = oseq; return send_request(p, sub, &resp, oseq); } static void add_header_offhook(struct mgcp_subchannel *sub, struct mgcp_request *resp, char *tone) { struct mgcp_endpoint *p = sub->parent; char tone_indicate_end = 0; /* We also should check the tone to indicate, because it have no sense to request notify D/[0-9#*] (dtmf keys) if we are sending congestion tone for example G/cg */ if (p && (!strcasecmp(tone, (p->ncs ? "L/ro" : "G/cg")))) { tone_indicate_end = 1; } if (p && p->sub && p->sub->owner && ast_channel_state(p->sub->owner) >= AST_STATE_RINGING && (p->dtmfmode & (MGCP_DTMF_INBAND | MGCP_DTMF_HYBRID))) { add_header(resp, "R", "L/hu(N),L/hf(N)"); } else if (!tone_indicate_end){ add_header(resp, "R", (p->ncs ? "L/hu(N),L/hf(N),L/[0-9#*](N)" : "L/hu(N),L/hf(N),D/[0-9#*](N)")); } else { ast_debug(1, "We don't want more digits if we will end the call\n"); add_header(resp, "R", "L/hu(N),L/hf(N)"); } } static int transmit_audit_endpoint(struct mgcp_endpoint *p) { struct mgcp_request resp; unsigned int oseq; oseq = reqprep(&resp, p, "AUEP"); /* removed unknown param VS */ /*add_header(&resp, "F", "A,R,D,S,X,N,I,T,O,ES,E,MD,M");*/ add_header(&resp, "F", "A"); /* fill in new fields */ resp.cmd = MGCP_CMD_AUEP; resp.trid = oseq; return send_request(p, NULL, &resp, oseq); } static int transmit_connection_del(struct mgcp_subchannel *sub) { struct mgcp_endpoint *p = sub->parent; struct mgcp_request resp; unsigned int oseq; ast_debug(3, "Delete connection %s %s@%s-%d with new mode: %s on callid: %s\n", sub->cxident, p->name, p->parent->name, sub->id, mgcp_cxmodes[sub->cxmode], sub->callid); oseq = reqprep(&resp, p, "DLCX"); /* check if call id is avail */ if (sub->callid[0]) add_header(&resp, "C", sub->callid); /* X header should not be sent. kept for compatibility */ add_header(&resp, "X", sub->txident); /* check if cxident is avail */ if (sub->cxident[0]) add_header(&resp, "I", sub->cxident); /* fill in new fields */ resp.cmd = MGCP_CMD_DLCX; resp.trid = oseq; return send_request(p, sub, &resp, oseq); } static int transmit_connection_del_w_params(struct mgcp_endpoint *p, char *callid, char *cxident) { struct mgcp_request resp; unsigned int oseq; ast_debug(3, "Delete connection %s %s@%s on callid: %s\n", cxident ? cxident : "", p->name, p->parent->name, callid ? callid : ""); oseq = reqprep(&resp, p, "DLCX"); /* check if call id is avail */ if (callid && *callid) add_header(&resp, "C", callid); /* check if cxident is avail */ if (cxident && *cxident) add_header(&resp, "I", cxident); /* fill in new fields */ resp.cmd = MGCP_CMD_DLCX; resp.trid = oseq; return send_request(p, p->sub, &resp, oseq); } /*! \brief dump_cmd_queues: (SC:) cleanup pending commands */ static void dump_cmd_queues(struct mgcp_endpoint *p, struct mgcp_subchannel *sub) { struct mgcp_request *t, *q; if (p) { ast_mutex_lock(&p->rqnt_queue_lock); for (q = p->rqnt_queue; q; t = q->next, ast_free(q), q=t); p->rqnt_queue = NULL; ast_mutex_unlock(&p->rqnt_queue_lock); ast_mutex_lock(&p->cmd_queue_lock); for (q = p->cmd_queue; q; t = q->next, ast_free(q), q=t); p->cmd_queue = NULL; ast_mutex_unlock(&p->cmd_queue_lock); ast_mutex_lock(&p->sub->cx_queue_lock); for (q = p->sub->cx_queue; q; t = q->next, ast_free(q), q=t); p->sub->cx_queue = NULL; ast_mutex_unlock(&p->sub->cx_queue_lock); ast_mutex_lock(&p->sub->next->cx_queue_lock); for (q = p->sub->next->cx_queue; q; t = q->next, ast_free(q), q=t); p->sub->next->cx_queue = NULL; ast_mutex_unlock(&p->sub->next->cx_queue_lock); } else if (sub) { ast_mutex_lock(&sub->cx_queue_lock); for (q = sub->cx_queue; q; t = q->next, ast_free(q), q=t); sub->cx_queue = NULL; ast_mutex_unlock(&sub->cx_queue_lock); } } /*! \brief find_command: (SC:) remove command transaction from queue */ static struct mgcp_request *find_command(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, struct mgcp_request **queue, ast_mutex_t *l, int ident) { struct mgcp_request *prev, *req; ast_mutex_lock(l); for (prev = NULL, req = *queue; req; prev = req, req = req->next) { if (req->trid == ident) { /* remove from queue */ if (!prev) *queue = req->next; else prev->next = req->next; /* send next pending command */ if (*queue) { ast_debug(1, "Posting Queued Request:\n%s to %s:%d\n", (*queue)->data, ast_inet_ntoa(p->parent->addr.sin_addr), ntohs(p->parent->addr.sin_port)); mgcp_postrequest(p, sub, (*queue)->data, (*queue)->len, (*queue)->trid); } break; } } ast_mutex_unlock(l); return req; } /* modified for new transport mechanism */ static void handle_response(struct mgcp_endpoint *p, struct mgcp_subchannel *sub, int result, unsigned int ident, struct mgcp_request *resp) { char *c; struct mgcp_request *req; struct mgcp_gateway *gw = p->parent; if (result < 200) { /* provisional response */ return; } if (p->slowsequence) req = find_command(p, sub, &p->cmd_queue, &p->cmd_queue_lock, ident); else if (sub) req = find_command(p, sub, &sub->cx_queue, &sub->cx_queue_lock, ident); else if (!(req = find_command(p, sub, &p->rqnt_queue, &p->rqnt_queue_lock, ident))) req = find_command(p, sub, &p->cmd_queue, &p->cmd_queue_lock, ident); if (!req) { ast_verb(3, "No command found on [%s] for transaction %u. Ignoring...\n", gw->name, ident); return; } if (p && (result >= 400) && (result <= 599)) { switch (result) { case 401: p->hookstate = MGCP_OFFHOOK; break; case 402: p->hookstate = MGCP_ONHOOK; break; case 406: ast_log(LOG_NOTICE, "Transaction %u timed out\n", ident); break; case 407: ast_log(LOG_NOTICE, "Transaction %u aborted\n", ident); break; } if (sub) { if (!sub->cxident[0] && (req->cmd == MGCP_CMD_CRCX)) { ast_log(LOG_NOTICE, "DLCX for all connections on %s due to error %d\n", gw->name, result); transmit_connection_del(sub); } if (sub->owner) { ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", result, p->name, p->parent->name, sub ? sub->id:-1); mgcp_queue_hangup(sub); } } else { if (p->sub->next->owner) { ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", result, p->name, p->parent->name, sub ? sub->id:-1); mgcp_queue_hangup(p->sub); } if (p->sub->owner) { ast_log(LOG_NOTICE, "Terminating on result %d from %s@%s-%d\n", result, p->name, p->parent->name, sub ? sub->id:-1); mgcp_queue_hangup(p->sub); } dump_cmd_queues(p, NULL); } } if (resp) { /* responseAck: */ if (result == 200 && (req->cmd == MGCP_CMD_CRCX || req->cmd == MGCP_CMD_MDCX)) { if (sub) { transmit_response(sub, "000", resp, "OK"); if (sub->owner && ast_channel_state(sub->owner) == AST_STATE_RINGING) { ast_queue_control(sub->owner, AST_CONTROL_RINGING); } } } if (req->cmd == MGCP_CMD_CRCX) { if ((c = get_header(resp, "I"))) { if (!ast_strlen_zero(c) && sub) { /* if we are hanging up do not process this conn. */ if (sub->owner) { if (!ast_strlen_zero(sub->cxident)) { if (strcasecmp(c, sub->cxident)) { ast_log(LOG_WARNING, "Subchannel already has a cxident. sub->cxident: %s requested %s\n", sub->cxident, c); } } ast_copy_string(sub->cxident, c, sizeof(sub->cxident)); if (sub->tmpdest.sin_addr.s_addr) { transmit_modify_with_sdp(sub, NULL, 0); } } else { /* XXX delete this one callid and conn id may already be lost. so the following del conn may have a side effect of cleaning up the next subchannel */ transmit_connection_del(sub); } } } } if (req->cmd == MGCP_CMD_AUEP) { /* check stale connection ids */ if ((c = get_header(resp, "I"))) { char *v, *n; int len; while ((v = get_csv(c, &len, &n))) { if (len) { if (strncasecmp(v, p->sub->cxident, len) && strncasecmp(v, p->sub->next->cxident, len)) { /* connection id not found. delete it */ char cxident[80] = ""; if (len > (sizeof(cxident) - 1)) len = sizeof(cxident) - 1; ast_copy_string(cxident, v, len); ast_verb(3, "Non existing connection id %s on %s@%s \n", cxident, p->name, gw->name); transmit_connection_del_w_params(p, NULL, cxident); } } c = n; } } /* Try to determine the hookstate returned from an audit endpoint command */ if ((c = get_header(resp, "ES"))) { if (!ast_strlen_zero(c)) { if (strstr(c, "hu")) { if (p->hookstate != MGCP_ONHOOK) { /* XXX cleanup if we think we are offhook XXX */ if ((p->sub->owner || p->sub->next->owner ) && p->hookstate == MGCP_OFFHOOK) mgcp_queue_hangup(sub); p->hookstate = MGCP_ONHOOK; /* update the requested events according to the new hookstate */ transmit_notify_request(p->sub, ""); ast_verb(3, "Setting hookstate of %s@%s to ONHOOK\n", p->name, gw->name); } } else if (strstr(c, "hd")) { if (p->hookstate != MGCP_OFFHOOK) { p->hookstate = MGCP_OFFHOOK; /* update the requested events according to the new hookstate */ transmit_notify_request(p->sub, ""); ast_verb(3, "Setting hookstate of %s@%s to OFFHOOK\n", p->name, gw->name); } } } } } if (resp && resp->lines) { /* do not process sdp if we are hanging up. this may be a late response */ if (sub && sub->owner) { if (!sub->rtp) start_rtp(sub); if (sub->rtp) process_sdp(sub, resp); } } } ast_free(req); } static void start_rtp(struct mgcp_subchannel *sub) { struct ast_sockaddr bindaddr_tmp; ast_mutex_lock(&sub->lock); /* check again to be on the safe side */ if (sub->rtp) { ast_rtp_instance_destroy(sub->rtp); sub->rtp = NULL; } /* Allocate the RTP now */ ast_sockaddr_from_sin(&bindaddr_tmp, &bindaddr); sub->rtp = ast_rtp_instance_new("asterisk", sched, &bindaddr_tmp, NULL); if (sub->rtp && sub->owner) ast_channel_set_fd(sub->owner, 0, ast_rtp_instance_fd(sub->rtp, 0)); if (sub->rtp) { ast_rtp_instance_set_qos(sub->rtp, qos.tos_audio, qos.cos_audio, "MGCP RTP"); ast_rtp_instance_set_prop(sub->rtp, AST_RTP_PROPERTY_NAT, sub->nat); } /* Make a call*ID */ snprintf(sub->callid, sizeof(sub->callid), "%08lx%s", (unsigned long)ast_random(), sub->txident); /* Transmit the connection create */ if(!sub->parent->pktcgatealloc) { transmit_connect_with_sdp(sub, NULL); } else { transmit_connect(sub); sub->gate = NULL; if(!mgcp_alloc_pktcgate(sub)) mgcp_queue_hangup(sub); } ast_mutex_unlock(&sub->lock); } static void *mgcp_ss(void *data) { struct ast_channel *chan = data; struct mgcp_subchannel *sub = ast_channel_tech_pvt(chan); struct mgcp_endpoint *p = sub->parent; /* char exten[AST_MAX_EXTENSION] = ""; */ int len = 0; int timeout = firstdigittimeout; int res= 0; int getforward = 0; int loop_pause = 100; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); const char *pickupexten; len = strlen(p->dtmf_buf); ast_channel_lock(chan); pickup_cfg = ast_get_chan_features_pickup_config(chan); 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); } ast_channel_unlock(chan); while (len < AST_MAX_EXTENSION - 1) { ast_debug(1, "Dtmf buffer '%s' for '%s@%s'\n", p->dtmf_buf, p->name, p->parent->name); res = 1; /* Assume that we will get a digit */ while (strlen(p->dtmf_buf) == len) { ast_safe_sleep(chan, loop_pause); timeout -= loop_pause; if (timeout <= 0){ res = 0; break; } res = 1; } timeout = 0; len = strlen(p->dtmf_buf); if (!ast_ignore_pattern(ast_channel_context(chan), p->dtmf_buf)) { /*res = tone_zone_play_tone(p->subs[index].zfd, -1);*/ ast_indicate(chan, -1); } else { /* XXX Redundant? We should already be playing dialtone */ /*tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALTONE);*/ transmit_notify_request(sub, "L/dl"); } if (ast_exists_extension(chan, ast_channel_context(chan), p->dtmf_buf, 1, p->cid_num)) { if (!res || !ast_matchmore_extension(chan, ast_channel_context(chan), p->dtmf_buf, 1, p->cid_num)) { if (getforward) { /* Record this as the forwarding extension */ ast_copy_string(p->call_forward, p->dtmf_buf, sizeof(p->call_forward)); ast_verb(3, "Setting call forward to '%s' on channel %s\n", p->call_forward, ast_channel_name(chan)); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); if (res) break; usleep(500000); /*res = tone_zone_play_tone(p->subs[index].zfd, -1);*/ ast_indicate(chan, -1); sleep(1); memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALTONE);*/ transmit_notify_request(sub, "L/dl"); len = 0; getforward = 0; } else { /*res = tone_zone_play_tone(p->subs[index].zfd, -1);*/ ast_indicate(chan, -1); ast_channel_lock(chan); ast_channel_exten_set(chan, p->dtmf_buf); ast_channel_dialed(chan)->number.str = ast_strdup(p->dtmf_buf); memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); ast_set_callerid(chan, p->hidecallerid ? "" : p->cid_num, p->hidecallerid ? "" : p->cid_name, ast_channel_caller(chan)->ani.number.valid ? NULL : p->cid_num); ast_setstate(chan, AST_STATE_RING); ast_channel_unlock(chan); if (p->dtmfmode & MGCP_DTMF_HYBRID) { p->dtmfmode |= MGCP_DTMF_INBAND; ast_indicate(chan, -1); } res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_CONGESTION);*/ /*transmit_notify_request(p, "nbz", 1);*/ transmit_notify_request(sub, p->ncs ? "L/cg" : "G/cg"); } return NULL; } } else { /* It's a match, but they just typed a digit, and there is an ambiguous match, so just set the timeout to matchdigittimeout and wait some more */ timeout = matchdigittimeout; } } else if (res == 0) { ast_debug(1, "not enough digits (and no ambiguous match)...\n"); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_CONGESTION);*/ transmit_notify_request(sub, p->ncs ? "L/cg" : "G/cg"); /*dahdi_wait_event(p->subs[index].zfd);*/ ast_hangup(chan); memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); return NULL; } else if (p->hascallwaiting && p->callwaiting && !strcmp(p->dtmf_buf, "*70")) { ast_verb(3, "Disabling call waiting on %s\n", ast_channel_name(chan)); /* Disable call waiting if enabled */ p->callwaiting = 0; /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); len = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); timeout = firstdigittimeout; } else if (!strcmp(p->dtmf_buf, pickupexten)) { /* Scan all channels and see if any there * ringing channqels with that have call groups * that equal this channels pickup group */ if (ast_pickup_call(chan)) { ast_log(LOG_WARNING, "No call pickup possible...\n"); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_CONGESTION);*/ transmit_notify_request(sub, p->ncs ? "L/cg" : "G/cg"); } memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); ast_hangup(chan); return NULL; } else if (!p->hidecallerid && !strcmp(p->dtmf_buf, "*67")) { ast_verb(3, "Disabling Caller*ID on %s\n", ast_channel_name(chan)); /* Disable Caller*ID if enabled */ p->hidecallerid = 1; ast_set_callerid(chan, "", "", NULL); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); len = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); timeout = firstdigittimeout; } else if (p->callreturn && !strcmp(p->dtmf_buf, "*69")) { res = 0; if (!ast_strlen_zero(p->lastcallerid)) { res = ast_say_digit_str(chan, p->lastcallerid, "", ast_channel_language(chan)); } if (!res) /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); break; } else if (!strcmp(p->dtmf_buf, "*78")) { /* Do not disturb */ ast_verb(3, "Enabled DND on channel %s\n", ast_channel_name(chan)); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); p->dnd = 1; getforward = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); len = 0; } else if (!strcmp(p->dtmf_buf, "*79")) { /* Do not disturb */ ast_verb(3, "Disabled DND on channel %s\n", ast_channel_name(chan)); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); p->dnd = 0; getforward = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); len = 0; } else if (p->cancallforward && !strcmp(p->dtmf_buf, "*72")) { /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); getforward = 1; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); len = 0; } else if (p->cancallforward && !strcmp(p->dtmf_buf, "*73")) { ast_verb(3, "Cancelling call forwarding on channel %s\n", ast_channel_name(chan)); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); memset(p->call_forward, 0, sizeof(p->call_forward)); getforward = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); len = 0; } else if (ast_parking_provider_registered() && ast_parking_is_exten_park(ast_channel_context(chan), p->dtmf_buf) && sub->next->owner) { RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); /* This is a three way call, the main call being a real channel, and we're parking the first call. */ ast_channel_lock(chan); bridge_channel = ast_channel_get_bridge_channel(chan); ast_channel_unlock(chan); if (bridge_channel && !ast_parking_blind_transfer_park(bridge_channel, ast_channel_context(chan), p->dtmf_buf, NULL, NULL)) { ast_verb(3, "Parking call to '%s'\n", ast_channel_name(chan)); } break; } else if (!ast_strlen_zero(p->lastcallerid) && !strcmp(p->dtmf_buf, "*60")) { ast_verb(3, "Blacklisting number %s\n", p->lastcallerid); res = ast_db_put("blacklist", p->lastcallerid, "1"); if (!res) { /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); len = 0; } } else if (p->hidecallerid && !strcmp(p->dtmf_buf, "*82")) { ast_verb(3, "Enabling Caller*ID on %s\n", ast_channel_name(chan)); /* Enable Caller*ID if enabled */ p->hidecallerid = 0; ast_set_callerid(chan, p->cid_num, p->cid_name, NULL); /*res = tone_zone_play_tone(p->subs[index].zfd, DAHDI_TONE_DIALRECALL);*/ transmit_notify_request(sub, "L/sl"); len = 0; memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); timeout = firstdigittimeout; } else if (!ast_canmatch_extension(chan, ast_channel_context(chan), p->dtmf_buf, 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) && ((p->dtmf_buf[0] != '*') || (strlen(p->dtmf_buf) > 2))) { ast_debug(1, "Can't match %s from '%s' in context %s\n", p->dtmf_buf, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""), ast_channel_context(chan)); break; } if (!timeout) timeout = gendigittimeout; if (len && !ast_ignore_pattern(ast_channel_context(chan), p->dtmf_buf)) /*tone_zone_play_tone(p->subs[index].zfd, -1);*/ ast_indicate(chan, -1); } #if 0 for (;;) { res = ast_waitfordigit(chan, to); if (!res) { ast_debug(1, "Timeout...\n"); break; } if (res < 0) { ast_debug(1, "Got hangup...\n"); ast_hangup(chan); break; } exten[pos++] = res; if (!ast_ignore_pattern(chan->context, exten)) ast_indicate(chan, -1); if (ast_matchmore_extension(chan, chan->context, exten, 1, chan->callerid)) { if (ast_exists_extension(chan, chan->context, exten, 1, chan->callerid)) to = 3000; else to = 8000; } else break; } if (ast_exists_extension(chan, chan->context, exten, 1, chan->callerid)) { ast_copy_string(chan->exten, exten, sizeof(chan->exten)1); if (!p->rtp) { start_rtp(p); } ast_setstate(chan, AST_STATE_RING); chan->rings = 1; if (ast_pbx_run(chan)) { ast_log(LOG_WARNING, "Unable to launch PBX on %s\n", chan->name); } else { memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); return NULL; } } #endif ast_hangup(chan); memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); return NULL; } /*! \brief Complete an attended transfer * * \param p The endpoint performing the attended transfer * \param sub The sub-channel completing the attended transfer * * \note p->sub is the currently active sub-channel (the channel the phone is using) * \note p->sub->next is the sub-channel not in use, potentially on hold * * \retval 0 when channel should be hung up * \retval 1 when channel should not be hung up */ static int attempt_transfer(struct mgcp_endpoint *p, struct mgcp_subchannel *sub) { enum ast_transfer_result res; /* Ensure that the other channel goes off hold and that it is indicating properly */ ast_queue_unhold(sub->next->owner); if (ast_channel_state(sub->owner) == AST_STATE_RINGING) { ast_queue_control(sub->next->owner, AST_CONTROL_RINGING); } ast_mutex_unlock(&p->sub->next->lock); ast_mutex_unlock(&p->sub->lock); res = ast_bridge_transfer_attended(sub->owner, sub->next->owner); /* Subs are only freed when the endpoint itself is destroyed, so they will continue to exist * after ast_bridge_transfer_attended returns making this safe without reference counting */ ast_mutex_lock(&p->sub->lock); ast_mutex_lock(&p->sub->next->lock); if (res != AST_BRIDGE_TRANSFER_SUCCESS) { /* If transferring fails hang up the other channel if present and us */ if (sub->next->owner) { ast_channel_softhangup_internal_flag_add(sub->next->owner, AST_SOFTHANGUP_DEV); mgcp_queue_hangup(sub->next); } sub->next->alreadygone = 1; return 0; } unalloc_sub(sub->next); /* If the active sub is NOT the one completing the transfer change it to be, and hang up the other sub */ if (p->sub != sub) { p->sub = sub; return 1; } return 0; } static void handle_hd_hf(struct mgcp_subchannel *sub, char *ev) { struct mgcp_endpoint *p = sub->parent; struct ast_channel *c; pthread_t t; /* Off hook / answer */ if (sub->outgoing) { /* Answered */ if (sub->owner) { ast_queue_unhold(sub->owner); sub->cxmode = MGCP_CX_SENDRECV; if (!sub->rtp) { start_rtp(sub); } else { transmit_modify_request(sub); } /*transmit_notify_request(sub, "aw");*/ transmit_notify_request(sub, ""); mgcp_queue_control(sub, AST_CONTROL_ANSWER); } } else { /* Start switch */ /*sub->cxmode = MGCP_CX_SENDRECV;*/ if (!sub->owner) { if (!sub->rtp) { start_rtp(sub); } else { transmit_modify_request(sub); } if (p->immediate) { /* The channel is immediately up. Start right away */ #ifdef DLINK_BUGGY_FIRMWARE transmit_notify_request(sub, "rt"); #else transmit_notify_request(sub, p->ncs ? "L/rt" : "G/rt"); #endif c = mgcp_new(sub, AST_STATE_RING, NULL, NULL); if (!c) { ast_log(LOG_WARNING, "Unable to start PBX on channel %s@%s\n", p->name, p->parent->name); transmit_notify_request(sub, p->ncs ? "L/cg" : "G/cg"); ast_hangup(c); } } else { if (has_voicemail(p)) { transmit_notify_request(sub, "L/sl"); } else { transmit_notify_request(sub, "L/dl"); } c = mgcp_new(sub, AST_STATE_DOWN, NULL, NULL); if (c) { if (ast_pthread_create_detached(&t, NULL, mgcp_ss, c)) { ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno)); ast_hangup(c); } } else { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", p->name, p->parent->name); } } } else { if (p->hookstate == MGCP_OFFHOOK) { ast_log(LOG_WARNING, "Off hook, but already have owner on %s@%s\n", p->name, p->parent->name); } else { ast_log(LOG_WARNING, "On hook, but already have owner on %s@%s\n", p->name, p->parent->name); ast_log(LOG_WARNING, "If we're onhook why are we here trying to handle a hd or hf?\n"); } ast_queue_unhold(sub->owner); sub->cxmode = MGCP_CX_SENDRECV; if (!sub->rtp) { start_rtp(sub); } else { transmit_modify_request(sub); } /*transmit_notify_request(sub, "aw");*/ transmit_notify_request(sub, ""); /*ast_queue_control(sub->owner, AST_CONTROL_ANSWER);*/ } } } static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req, struct sockaddr_in *sin) { char *ev, *s; struct ast_frame f = { 0, }; struct mgcp_endpoint *p = sub->parent; struct mgcp_gateway *g = NULL; int res; ast_debug(1, "Handling request '%s' on %s@%s\n", req->verb, p->name, p->parent->name); /* Clear out potential response */ if (!strcasecmp(req->verb, "RSIP")) { /* Test if this RSIP request is just a keepalive */ if (!strcasecmp( get_header(req, "RM"), "X-keepalive")) { ast_verb(3, "Received keepalive request from %s@%s\n", p->name, p->parent->name); transmit_response(sub, "200", req, "OK"); } else { dump_queue(p->parent, p); dump_cmd_queues(p, NULL); if ((strcmp(p->name, p->parent->wcardep) != 0)) { ast_verb(3, "Resetting interface %s@%s\n", p->name, p->parent->name); } /* For RSIP on wildcard we reset all endpoints */ if (!strcmp(p->name, p->parent->wcardep)) { /* Reset all endpoints */ struct mgcp_endpoint *tmp_ep; g = p->parent; for (tmp_ep = g->endpoints; tmp_ep; tmp_ep = tmp_ep->next) { /*if ((strcmp(tmp_ep->name, "*") != 0) && (strcmp(tmp_ep->name, "aaln/" "*") != 0)) {*/ if (strcmp(tmp_ep->name, g->wcardep) != 0) { struct mgcp_subchannel *tmp_sub, *first_sub; ast_verb(3, "Resetting interface %s@%s\n", tmp_ep->name, p->parent->name); first_sub = tmp_ep->sub; tmp_sub = tmp_ep->sub; while (tmp_sub) { mgcp_queue_hangup(tmp_sub); tmp_sub = tmp_sub->next; if (tmp_sub == first_sub) break; } } } } else if (sub->owner) { mgcp_queue_hangup(sub); } transmit_response(sub, "200", req, "OK"); /* We don't send NTFY or AUEP to wildcard ep */ if (strcmp(p->name, p->parent->wcardep) != 0) { transmit_notify_request(sub, ""); /* Audit endpoint. Idea is to prevent lost lines due to race conditions */ transmit_audit_endpoint(p); } } } else if (!strcasecmp(req->verb, "NTFY")) { /* Acknowledge and be sure we keep looking for the same things */ transmit_response(sub, "200", req, "OK"); /* Notified of an event */ ev = get_header(req, "O"); s = strchr(ev, '/'); if (s) ev = s + 1; ast_debug(1, "Endpoint '%s@%s-%d' observed '%s'\n", p->name, p->parent->name, sub->id, ev); /* Keep looking for events unless this was a hangup */ if (strcasecmp(ev, "hu") && strcasecmp(ev, "hd") && strcasecmp(ev, "ping")) { transmit_notify_request(sub, p->curtone); } if (!strcasecmp(ev, "hd")) { p->hookstate = MGCP_OFFHOOK; sub->cxmode = MGCP_CX_SENDRECV; if (p) { /* When the endpoint have a Off hook transition we allways starts without any previous dtmfs */ memset(p->dtmf_buf, 0, sizeof(p->dtmf_buf)); } handle_hd_hf(sub, ev); } else if (!strcasecmp(ev, "hf")) { /* We can assume we are offhook if we received a hookflash */ /* First let's just do call wait and ignore threeway */ /* We're currently in charge */ if (p->hookstate != MGCP_OFFHOOK) { /* Cisco c7940 sends hf even if the phone is onhook */ /* Thanks to point on IRC for pointing this out */ return -1; } /* do not let * conference two down channels */ if (sub->owner && ast_channel_state(sub->owner) == AST_STATE_DOWN && !sub->next->owner) return -1; if (p->callwaiting || p->transfer || p->threewaycalling) { ast_verb(3, "Swapping %d for %d on %s@%s\n", p->sub->id, p->sub->next->id, p->name, p->parent->name); p->sub = p->sub->next; /* transfer control to our next subchannel */ if (!sub->next->owner) { /* plave the first call on hold and start up a new call */ sub->cxmode = MGCP_CX_MUTE; ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name); transmit_modify_request(sub); if (sub->owner) { ast_queue_hold(sub->owner, NULL); } sub->next->cxmode = MGCP_CX_RECVONLY; handle_hd_hf(sub->next, ev); } else if (sub->owner && sub->next->owner) { /* We've got two active calls lets decide whether or not to conference or just flip flop */ if ((!sub->outgoing) && (!sub->next->outgoing)) { /* We made both calls lets conference */ ast_verb(3, "MGCP Conferencing %d and %d on %s@%s\n", sub->id, sub->next->id, p->name, p->parent->name); sub->cxmode = MGCP_CX_CONF; sub->next->cxmode = MGCP_CX_CONF; ast_queue_unhold(sub->next->owner); transmit_modify_request(sub); transmit_modify_request(sub->next); } else { /* Let's flipflop between calls */ /* XXX Need to check for state up ??? */ /* XXX Need a way to indicate the current call, or maybe the call that's waiting */ ast_verb(3, "We didn't make one of the calls FLIPFLOP %d and %d on %s@%s\n", sub->id, sub->next->id, p->name, p->parent->name); sub->cxmode = MGCP_CX_MUTE; ast_verb(3, "MGCP Muting %d on %s@%s\n", sub->id, p->name, p->parent->name); transmit_modify_request(sub); ast_queue_hold(sub->owner, NULL); ast_queue_hold(sub->next->owner, NULL); handle_hd_hf(sub->next, ev); } } else { /* We've most likely lost one of our calls find an active call and bring it up */ if (sub->owner) { p->sub = sub; } else if (sub->next->owner) { p->sub = sub->next; } else { /* We seem to have lost both our calls */ /* XXX - What do we do now? */ return -1; } ast_queue_unhold(p->sub->owner); p->sub->cxmode = MGCP_CX_SENDRECV; transmit_modify_request(p->sub); } } else { ast_log(LOG_WARNING, "Callwaiting, call transfer or threeway calling not enabled on endpoint %s@%s\n", p->name, p->parent->name); } } else if (!strcasecmp(ev, "hu")) { p->hookstate = MGCP_ONHOOK; sub->cxmode = MGCP_CX_RECVONLY; ast_debug(1, "MGCP %s@%s Went on hook\n", p->name, p->parent->name); /* Do we need to send MDCX before a DLCX ? if (sub->rtp) { transmit_modify_request(sub); } */ if (p->transfer && (sub->owner && sub->next->owner) && ((!sub->outgoing) || (!sub->next->outgoing))) { /* We're allowed to transfer, we have two avtive calls and */ /* we made at least one of the calls. Let's try and transfer */ ast_mutex_lock(&p->sub->next->lock); res = attempt_transfer(p, sub); if (res) { ast_log(LOG_WARNING, "Transfer attempt failed\n"); ast_mutex_unlock(&p->sub->next->lock); return -1; } ast_mutex_unlock(&p->sub->next->lock); } else { /* Hangup the current call */ /* If there is another active call, mgcp_hangup will ring the phone with the other call */ if (sub->owner) { sub->alreadygone = 1; mgcp_queue_hangup(sub); } else { ast_verb(3, "MGCP handle_request(%s@%s-%d) ast_channel already destroyed, resending DLCX.\n", p->name, p->parent->name, sub->id); /* Instruct the other side to remove the connection since it apparently * * still thinks the channel is active. * * For Cisco IAD2421 /BAK/ */ transmit_connection_del(sub); } } if ((p->hookstate == MGCP_ONHOOK) && (!sub->rtp) && (!sub->next->rtp)) { p->hidecallerid = 0; if (p->hascallwaiting && !p->callwaiting) { ast_verb(3, "Enabling call waiting on MGCP/%s@%s-%d\n", p->name, p->parent->name, sub->id); p->callwaiting = -1; } if (has_voicemail(p)) { ast_verb(3, "MGCP handle_request(%s@%s) set vmwi(+)\n", p->name, p->parent->name); transmit_notify_request(sub, "L/vmwi(+)"); } else { ast_verb(3, "MGCP handle_request(%s@%s) set vmwi(-)\n", p->name, p->parent->name); transmit_notify_request(sub, "L/vmwi(-)"); } } } else if ((strlen(ev) == 1) && (((ev[0] >= '0') && (ev[0] <= '9')) || ((ev[0] >= 'A') && (ev[0] <= 'D')) || (ev[0] == '*') || (ev[0] == '#'))) { if (sub && sub->owner && (ast_channel_state(sub->owner) >= AST_STATE_UP)) { f.frametype = AST_FRAME_DTMF; f.subclass.integer = ev[0]; f.src = "mgcp"; /* XXX MUST queue this frame to all subs in threeway call if threeway call is active */ mgcp_queue_frame(sub, &f); ast_mutex_lock(&sub->next->lock); if (sub->next->owner) mgcp_queue_frame(sub->next, &f); ast_mutex_unlock(&sub->next->lock); if (strstr(p->curtone, (p->ncs ? "wt1" : "wt")) && (ev[0] == 'A')) { memset(p->curtone, 0, sizeof(p->curtone)); } } else { p->dtmf_buf[strlen(p->dtmf_buf)] = ev[0]; p->dtmf_buf[strlen(p->dtmf_buf)] = '\0'; } } else if (!strcasecmp(ev, "T")) { /* Digit timeout -- unimportant */ } else if (!strcasecmp(ev, "ping")) { /* ping -- unimportant */ } else { ast_log(LOG_NOTICE, "Received unknown event '%s' from %s@%s\n", ev, p->name, p->parent->name); } } else { ast_log(LOG_WARNING, "Unknown verb '%s' received from %s\n", req->verb, ast_inet_ntoa(sin->sin_addr)); transmit_response(sub, "510", req, "Unknown verb"); } return 0; } static int find_and_retrans(struct mgcp_subchannel *sub, struct mgcp_request *req) { int seqno=0; time_t now; struct mgcp_response *prev = NULL, *cur, *next, *answer = NULL; time(&now); if (sscanf(req->identifier, "%30d", &seqno) != 1) { seqno = 0; } for (cur = sub->parent->parent->responses, next = cur ? cur->next : NULL; cur; cur = next, next = cur ? cur->next : NULL) { if (now - cur->whensent > RESPONSE_TIMEOUT) { /* Delete this entry */ if (prev) prev->next = next; else sub->parent->parent->responses = next; ast_free(cur); } else { if (seqno == cur->seqno) answer = cur; prev = cur; } } if (answer) { resend_response(sub, answer); return 1; } return 0; } static int mgcpsock_read(int *id, int fd, short events, void *ignore) { struct mgcp_request req; struct sockaddr_in sin; struct mgcp_subchannel *sub; int res; socklen_t len; int result; int ident; len = sizeof(sin); memset(&req, 0, sizeof(req)); res = recvfrom(mgcpsock, req.data, sizeof(req.data) - 1, 0, (struct sockaddr *)&sin, &len); if (res < 0) { if (errno != ECONNREFUSED) ast_log(LOG_WARNING, "Recv error: %s\n", strerror(errno)); return 1; } req.data[res] = '\0'; req.len = res; ast_debug(1, "MGCP read: \n%s\nfrom %s:%d\n", req.data, ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); parse(&req); if (req.headers < 1) { /* Must have at least one header */ return 1; } if (ast_strlen_zero(req.identifier)) { ast_log(LOG_NOTICE, "Message from %s missing identifier\n", ast_inet_ntoa(sin.sin_addr)); return 1; } if (sscanf(req.verb, "%30d", &result) && sscanf(req.identifier, "%30d", &ident)) { if (result < 200) { ast_debug(1, "Ignoring provisional response on transaction %d\n", ident); return 1; } /* Try to find who this message is for, if it's important */ sub = find_subchannel_and_lock(NULL, ident, &sin); if (sub) { struct mgcp_gateway *gw = sub->parent->parent; struct mgcp_message *cur, *prev; ast_mutex_unlock(&sub->lock); ast_mutex_lock(&gw->msgs_lock); for (prev = NULL, cur = gw->msgs; cur; prev = cur, cur = cur->next) { if (cur->seqno == ident) { ast_debug(1, "Got response back on transaction %d\n", ident); if (prev) prev->next = cur->next; else gw->msgs = cur->next; break; } } /* stop retrans timer if the queue is empty */ if (!gw->msgs) { AST_SCHED_DEL(sched, gw->retransid); } ast_mutex_unlock(&gw->msgs_lock); if (cur) { handle_response(cur->owner_ep, cur->owner_sub, result, ident, &req); ast_free(cur); return 1; } ast_log(LOG_NOTICE, "Got response back on [%s] for transaction %d we aren't sending?\n", gw->name, ident); } } else { if (ast_strlen_zero(req.endpoint) || ast_strlen_zero(req.version) || ast_strlen_zero(req.verb)) { ast_log(LOG_NOTICE, "Message must have a verb, an idenitifier, version, and endpoint\n"); return 1; } /* Process request, with iflock held */ sub = find_subchannel_and_lock(req.endpoint, 0, &sin); if (sub) { /* look first to find a matching response in the queue */ if (!find_and_retrans(sub, &req)) /* pass the request off to the currently mastering subchannel */ handle_request(sub, &req, &sin); ast_mutex_unlock(&sub->lock); } } return 1; } static int *mgcpsock_read_id = NULL; static int mgcp_prune_realtime_gateway(struct mgcp_gateway *g) { struct mgcp_endpoint *enext, *e; struct mgcp_subchannel *s, *sub; int i, prune = 1; if (g->ha || !g->realtime || ast_mutex_trylock(&g->msgs_lock) || g->msgs) { ast_mutex_unlock(&g->msgs_lock); return 0; } for (e = g->endpoints; e; e = e->next) { ast_mutex_lock(&e->lock); if (e->dsp || ast_mutex_trylock(&e->rqnt_queue_lock) || ast_mutex_trylock(&e->cmd_queue_lock)) { prune = 0; } else if (e->rqnt_queue || e->cmd_queue) { prune = 0; } s = e->sub; for (i = 0; (i < MAX_SUBS) && s; i++) { ast_mutex_lock(&s->lock); if (!ast_strlen_zero(s->cxident) || s->rtp || ast_mutex_trylock(&s->cx_queue_lock) || s->gate) { prune = 0; } else if (s->cx_queue) { prune = 0; } s = s->next; } } for (e = g->endpoints, sub = e->sub, enext = e->next; e; e = enext, enext = e->next) { for (i = 0; (i < MAX_SUBS) && sub; i++) { s = sub; sub = sub->next; ast_mutex_unlock(&s->lock); ast_mutex_unlock(&s->cx_queue_lock); if (prune) { ast_mutex_destroy(&s->lock); ast_mutex_destroy(&s->cx_queue_lock); free(s); } } ast_mutex_unlock(&e->lock); ast_mutex_unlock(&e->rqnt_queue_lock); ast_mutex_unlock(&e->cmd_queue_lock); if (prune) { ast_mutex_destroy(&e->lock); ast_mutex_destroy(&e->rqnt_queue_lock); ast_mutex_destroy(&e->cmd_queue_lock); free(e); } } if (prune) { ast_debug(1, "***** MGCP REALTIME PRUNE GW: %s\n", g->name); } return prune; } static void *do_monitor(void *data) { int res; int reloading; struct mgcp_gateway *g, *gprev; /*struct mgcp_gateway *g;*/ /*struct mgcp_endpoint *e;*/ /*time_t thispass = 0, lastpass = 0;*/ time_t lastrun = 0; /* Add an I/O event to our UDP socket */ if (mgcpsock > -1) { mgcpsock_read_id = ast_io_add(io, mgcpsock, mgcpsock_read, AST_IO_IN, NULL); } /* This thread monitors all the frame relay interfaces which are not yet in use (and thus do not have a separate thread) indefinitely */ /* From here on out, we die whenever asked */ for (;;) { /* Check for a reload request */ ast_mutex_lock(&mgcp_reload_lock); reloading = mgcp_reloading; mgcp_reloading = 0; ast_mutex_unlock(&mgcp_reload_lock); if (reloading) { ast_verb(1, "Reloading MGCP\n"); reload_config(1); /* Add an I/O event to our UDP socket */ if (mgcpsock > -1 && !mgcpsock_read_id) { mgcpsock_read_id = ast_io_add(io, mgcpsock, mgcpsock_read, AST_IO_IN, NULL); } } /* Check for interfaces needing to be killed */ /* Don't let anybody kill us right away. Nobody should lock the interface list and wait for the monitor list, but the other way around is okay. */ ast_mutex_lock(&monlock); /* Lock the network interface */ ast_mutex_lock(&netlock); #if 0 /* XXX THIS IS COMPLETELY HOSED */ /* The gateway goes into a state of panic */ /* If the vmwi indicator is sent while it is reseting interfaces */ lastpass = thispass; thispass = time(NULL); g = gateways; while(g) { if (thispass != lastpass) { e = g->endpoints; while(e) { if (e->type == TYPE_LINE) { res = has_voicemail(e); if ((e->msgstate != res) && (e->hookstate == MGCP_ONHOOK) && (!e->rtp)){ if (res) { transmit_notify_request(e, "L/vmwi(+)"); } else { transmit_notify_request(e, "L/vmwi(-)"); } e->msgstate = res; e->onhooktime = thispass; } } e = e->next; } } g = g->next; } #endif /* pruning unused realtime gateways, running in every 60 seconds*/ if(time(NULL) > (lastrun + 60)) { ast_mutex_lock(&gatelock); g = gateways; gprev = NULL; while(g) { if(g->realtime) { if(mgcp_prune_realtime_gateway(g)) { if(gprev) { gprev->next = g->next; } else { gateways = g->next; } ast_mutex_unlock(&g->msgs_lock); ast_mutex_destroy(&g->msgs_lock); free(g); } else { ast_mutex_unlock(&g->msgs_lock); gprev = g; } } else { gprev = g; } g = g->next; } ast_mutex_unlock(&gatelock); lastrun = time(NULL); } /* Okay, now that we know what to do, release the network lock */ ast_mutex_unlock(&netlock); /* And from now on, we're okay to be killed, so release the monitor lock as well */ ast_mutex_unlock(&monlock); pthread_testcancel(); /* Wait for sched or io */ res = ast_sched_wait(sched); /* copied from chan_sip.c */ if ((res < 0) || (res > 1000)) { res = 1000; } res = ast_io_wait(io, res); ast_mutex_lock(&monlock); if (res >= 0) { ast_sched_runq(sched); } ast_mutex_unlock(&monlock); } /* Never reached */ return NULL; } static int restart_monitor(void) { /* If we're supposed to be stopped -- stay stopped */ if (monitor_thread == AST_PTHREADT_STOP) return 0; if (ast_mutex_lock(&monlock)) { ast_log(LOG_WARNING, "Unable to lock monitor\n"); return -1; } if (monitor_thread == pthread_self()) { ast_mutex_unlock(&monlock); ast_log(LOG_WARNING, "Cannot kill myself\n"); return -1; } if (monitor_thread != AST_PTHREADT_NULL) { /* Wake up the thread */ pthread_kill(monitor_thread, SIGURG); } else { /* Start a new monitor */ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) { ast_mutex_unlock(&monlock); ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); return -1; } } ast_mutex_unlock(&monlock); return 0; } static struct ast_channel *mgcp_request(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 mgcp_subchannel *sub; struct ast_channel *tmpc = NULL; char tmp[256]; if (!(ast_format_cap_iscompatible(cap, global_capability))) { struct ast_str *cap_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &cap_buf)); /*return NULL;*/ } ast_copy_string(tmp, dest, sizeof(tmp)); if (ast_strlen_zero(tmp)) { ast_log(LOG_NOTICE, "MGCP Channels require an endpoint\n"); return NULL; } if (!(sub = find_subchannel_and_lock(tmp, 0, NULL))) { ast_log(LOG_WARNING, "Unable to find MGCP endpoint '%s'\n", tmp); *cause = AST_CAUSE_UNREGISTERED; return NULL; } ast_verb(3, "MGCP mgcp_request(%s)\n", tmp); ast_verb(3, "MGCP cw: %d, dnd: %d, so: %d, sno: %d\n", sub->parent->callwaiting, sub->parent->dnd, sub->owner ? 1 : 0, sub->next->owner ? 1: 0); /* Must be busy */ if (((sub->parent->callwaiting) && ((sub->owner) && (sub->next->owner))) || ((!sub->parent->callwaiting) && (sub->owner)) || (sub->parent->dnd && (ast_strlen_zero(sub->parent->call_forward)))) { if (sub->parent->hookstate == MGCP_ONHOOK) { if (has_voicemail(sub->parent)) { transmit_notify_request(sub,"L/vmwi(+)"); } else { transmit_notify_request(sub,"L/vmwi(-)"); } } *cause = AST_CAUSE_BUSY; ast_mutex_unlock(&sub->lock); return NULL; } tmpc = mgcp_new(sub->owner ? sub->next : sub, AST_STATE_DOWN, assignedids, requestor); ast_mutex_unlock(&sub->lock); if (!tmpc) ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp); restart_monitor(); return tmpc; } /* modified for reload support */ /*! \brief build_gateway: parse mgcp.conf and create gateway/endpoint structures */ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) { struct mgcp_gateway *gw; struct mgcp_endpoint *e; struct mgcp_subchannel *sub; struct ast_variable *chanvars = NULL; /*char txident[80];*/ int i=0, y=0; int gw_reload = 0; int ep_reload = 0; directmedia = DIRECTMEDIA; /* locate existing gateway */ for (gw = gateways; gw; gw = gw->next) { if (!strcasecmp(cat, gw->name)) { /* gateway already exists */ gw->delme = 0; gw_reload = 1; break; } } if (!gw && !(gw = ast_calloc(1, sizeof(*gw)))) { return NULL; } if (!gw_reload) { gw->expire = -1; gw->realtime = 0; gw->retransid = -1; ast_mutex_init(&gw->msgs_lock); ast_copy_string(gw->name, cat, sizeof(gw->name)); /* check if the name is numeric ip */ if ((strchr(gw->name, '.')) && inet_addr(gw->name) != INADDR_NONE) gw->isnamedottedip = 1; } for (; v; v = v->next) { if (!strcasecmp(v->name, "host")) { if (!strcasecmp(v->value, "dynamic")) { /* They'll register with us */ gw->dynamic = 1; memset(&gw->addr.sin_addr, 0, 4); if (gw->addr.sin_port) { /* If we've already got a port, make it the default rather than absolute */ gw->defaddr.sin_port = gw->addr.sin_port; gw->addr.sin_port = 0; } } else { /* Non-dynamic. Make sure we become that way if we're not */ AST_SCHED_DEL(sched, gw->expire); gw->dynamic = 0; { struct ast_sockaddr tmp; ast_sockaddr_from_sin(&tmp, &gw->addr); if (ast_get_ip(&tmp, v->value)) { if (!gw_reload) { ast_mutex_destroy(&gw->msgs_lock); ast_free(gw); } return NULL; } ast_sockaddr_to_sin(&tmp, &gw->addr); } } } else if (!strcasecmp(v->name, "defaultip")) { struct ast_sockaddr tmp; ast_sockaddr_from_sin(&tmp, &gw->defaddr); if (ast_get_ip(&tmp, v->value)) { if (!gw_reload) { ast_mutex_destroy(&gw->msgs_lock); ast_free(gw); } return NULL; } ast_sockaddr_to_sin(&tmp, &gw->defaddr); } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { gw->ha = ast_append_ha(v->name, v->value, gw->ha, NULL); } else if (!strcasecmp(v->name, "port")) { gw->addr.sin_port = htons(atoi(v->value)); } else if (!strcasecmp(v->name, "context")) { ast_copy_string(context, v->value, sizeof(context)); } else if (!strcasecmp(v->name, "dtmfmode")) { if (!strcasecmp(v->value, "inband")) dtmfmode = MGCP_DTMF_INBAND; else if (!strcasecmp(v->value, "rfc2833")) dtmfmode = MGCP_DTMF_RFC2833; else if (!strcasecmp(v->value, "hybrid")) dtmfmode = MGCP_DTMF_HYBRID; else if (!strcasecmp(v->value, "none")) dtmfmode = 0; else ast_log(LOG_WARNING, "'%s' is not a valid DTMF mode at line %d\n", v->value, v->lineno); } else if (!strcasecmp(v->name, "nat")) { nat = ast_true(v->value); } else if (!strcasecmp(v->name, "ncs")) { ncs = ast_true(v->value); } else if (!strcasecmp(v->name, "hangupongateremove")) { hangupongateremove = ast_true(v->value); } else if (!strcasecmp(v->name, "pktcgatealloc")) { pktcgatealloc = ast_true(v->value); } else if (!strcasecmp(v->name, "callerid")) { if (!strcasecmp(v->value, "asreceived")) { cid_num[0] = '\0'; cid_name[0] = '\0'; } else { ast_callerid_split(v->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); } } else if (!strcasecmp(v->name, "language")) { ast_copy_string(language, v->value, sizeof(language)); } else if (!strcasecmp(v->name, "accountcode")) { ast_copy_string(accountcode, v->value, sizeof(accountcode)); } else if (!strcasecmp(v->name, "amaflags")) { y = ast_channel_string2amaflag(v->value); if (y < 0) { ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno); } else { amaflags = y; } } else if (!strcasecmp(v->name, "setvar")) { chanvars = add_var(v->value, chanvars); } else if (!strcasecmp(v->name, "clearvars")) { if (chanvars) { ast_variables_destroy(chanvars); chanvars = NULL; } } else if (!strcasecmp(v->name, "musiconhold")) { ast_copy_string(musicclass, v->value, sizeof(musicclass)); } else if (!strcasecmp(v->name, "parkinglot")) { ast_copy_string(parkinglot, v->value, sizeof(parkinglot)); } else if (!strcasecmp(v->name, "callgroup")) { cur_callergroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "pickupgroup")) { cur_pickupgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "immediate")) { immediate = ast_true(v->value); } else if (!strcasecmp(v->name, "cancallforward")) { cancallforward = ast_true(v->value); } else if (!strcasecmp(v->name, "singlepath")) { singlepath = ast_true(v->value); } else if (!strcasecmp(v->name, "directmedia") || !strcasecmp(v->name, "canreinvite")) { directmedia = ast_true(v->value); } else if (!strcasecmp(v->name, "mailbox")) { ast_copy_string(mailbox, v->value, sizeof(mailbox)); } else if (!strcasecmp(v->name, "hasvoicemail")) { if (ast_true(v->value) && ast_strlen_zero(mailbox)) { /* * hasvoicemail is a users.conf legacy voicemail enable method. * hasvoicemail is only going to work for app_voicemail mailboxes. */ if (strchr(gw->name, '@')) { ast_copy_string(mailbox, gw->name, sizeof(mailbox)); } else { snprintf(mailbox, sizeof(mailbox), "%s@default", gw->name); } } } else if (!strcasecmp(v->name, "adsi")) { adsi = ast_true(v->value); } else if (!strcasecmp(v->name, "callreturn")) { callreturn = ast_true(v->value); } else if (!strcasecmp(v->name, "callwaiting")) { callwaiting = ast_true(v->value); } else if (!strcasecmp(v->name, "slowsequence")) { slowsequence = ast_true(v->value); } else if (!strcasecmp(v->name, "transfer")) { transfer = ast_true(v->value); } else if (!strcasecmp(v->name, "threewaycalling")) { threewaycalling = ast_true(v->value); } else if (!strcasecmp(v->name, "wcardep")) { /* locate existing endpoint */ for (e = gw->endpoints; e; e = e->next) { if (!strcasecmp(v->value, e->name)) { /* endpoint already exists */ e->delme = 0; ep_reload = 1; break; } } if (!e) { /* Allocate wildcard endpoint */ e = ast_calloc(1, sizeof(*e)); ep_reload = 0; } if (e) { if (!ep_reload) { memset(e, 0, sizeof(struct mgcp_endpoint)); ast_mutex_init(&e->lock); ast_mutex_init(&e->rqnt_queue_lock); ast_mutex_init(&e->cmd_queue_lock); e->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); ast_copy_string(e->name, v->value, sizeof(e->name)); e->needaudit = 1; } ast_copy_string(gw->wcardep, v->value, sizeof(gw->wcardep)); /* XXX Should we really check for uniqueness?? XXX */ ast_copy_string(e->accountcode, accountcode, sizeof(e->accountcode)); ast_copy_string(e->context, context, sizeof(e->context)); ast_copy_string(e->cid_num, cid_num, sizeof(e->cid_num)); ast_copy_string(e->cid_name, cid_name, sizeof(e->cid_name)); ast_copy_string(e->language, language, sizeof(e->language)); ast_copy_string(e->musicclass, musicclass, sizeof(e->musicclass)); ast_copy_string(e->mailbox, mailbox, sizeof(e->mailbox)); ast_copy_string(e->parkinglot, parkinglot, sizeof(e->parkinglot)); if (!ast_strlen_zero(e->mailbox)) { struct stasis_topic *mailbox_specific_topic; mailbox_specific_topic = ast_mwi_topic(e->mailbox); if (mailbox_specific_topic) { e->mwi_event_sub = stasis_subscribe_pool(mailbox_specific_topic, mwi_event_cb, NULL); } } snprintf(e->rqnt_ident, sizeof(e->rqnt_ident), "%08lx", (unsigned long)ast_random()); e->msgstate = -1; e->amaflags = amaflags; ast_format_cap_append_from_cap(e->cap, global_capability, AST_MEDIA_TYPE_UNKNOWN); e->parent = gw; e->ncs = ncs; e->dtmfmode = dtmfmode; if (!ep_reload && e->sub && e->sub->rtp) { e->dtmfmode |= MGCP_DTMF_INBAND; } e->adsi = adsi; e->type = TYPE_LINE; e->immediate = immediate; e->callgroup=cur_callergroup; e->pickupgroup=cur_pickupgroup; e->callreturn = callreturn; e->cancallforward = cancallforward; e->singlepath = singlepath; e->directmedia = directmedia; e->callwaiting = callwaiting; e->hascallwaiting = callwaiting; e->slowsequence = slowsequence; e->transfer = transfer; e->threewaycalling = threewaycalling; e->onhooktime = time(NULL); /* ASSUME we're onhook */ e->hookstate = MGCP_ONHOOK; e->chanvars = copy_vars(chanvars); if (!ep_reload) { /*snprintf(txident, sizeof(txident), "%08lx", (unsigned long)ast_random());*/ for (i = 0; i < MAX_SUBS; i++) { sub = ast_calloc(1, sizeof(*sub)); if (sub) { ast_verb(3, "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name); ast_mutex_init(&sub->lock); ast_mutex_init(&sub->cx_queue_lock); sub->parent = e; sub->id = i; snprintf(sub->txident, sizeof(sub->txident), "%08lx", (unsigned long)ast_random()); /*stnrcpy(sub->txident, txident, sizeof(sub->txident) - 1);*/ sub->cxmode = MGCP_CX_INACTIVE; sub->nat = nat; sub->gate = NULL; sub->sdpsent = 0; sub->next = e->sub; e->sub = sub; } else { /* XXX Should find a way to clean up our memory */ ast_log(LOG_WARNING, "Out of memory allocating subchannel\n"); return NULL; } } /* Make out subs a circular linked list so we can always sping through the whole bunch */ /* find the end of the list */ for (sub = e->sub; sub && sub->next; sub = sub->next); /* set the last sub->next to the first sub */ sub->next = e->sub; e->next = gw->endpoints; gw->endpoints = e; } } } else if (!strcasecmp(v->name, "trunk") || !strcasecmp(v->name, "line")) { /* locate existing endpoint */ for (e = gw->endpoints; e; e = e->next) { if (!strcasecmp(v->value, e->name)) { /* endpoint already exists */ e->delme = 0; ep_reload = 1; break; } } if (!e) { e = ast_calloc(1, sizeof(*e)); ep_reload = 0; } if (e) { if (!ep_reload) { ast_mutex_init(&e->lock); ast_mutex_init(&e->rqnt_queue_lock); ast_mutex_init(&e->cmd_queue_lock); e->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); ast_copy_string(e->name, v->value, sizeof(e->name)); e->needaudit = 1; } /* XXX Should we really check for uniqueness?? XXX */ ast_copy_string(e->accountcode, accountcode, sizeof(e->accountcode)); ast_copy_string(e->context, context, sizeof(e->context)); ast_copy_string(e->cid_num, cid_num, sizeof(e->cid_num)); ast_copy_string(e->cid_name, cid_name, sizeof(e->cid_name)); ast_copy_string(e->language, language, sizeof(e->language)); ast_copy_string(e->musicclass, musicclass, sizeof(e->musicclass)); ast_copy_string(e->mailbox, mailbox, sizeof(e->mailbox)); ast_copy_string(e->parkinglot, parkinglot, sizeof(e->parkinglot)); if (!ast_strlen_zero(mailbox)) { ast_verb(3, "Setting mailbox '%s' on %s@%s\n", mailbox, gw->name, e->name); } if (!ep_reload) { /* XXX potential issue due to reload */ e->msgstate = -1; e->parent = gw; } e->amaflags = amaflags; ast_format_cap_append_from_cap(e->cap, global_capability, AST_MEDIA_TYPE_UNKNOWN); e->dtmfmode = dtmfmode; e->ncs = ncs; e->pktcgatealloc = pktcgatealloc; e->hangupongateremove = hangupongateremove; e->adsi = adsi; e->type = (!strcasecmp(v->name, "trunk")) ? TYPE_TRUNK : TYPE_LINE; e->immediate = immediate; e->callgroup=cur_callergroup; e->pickupgroup=cur_pickupgroup; e->callreturn = callreturn; e->cancallforward = cancallforward; e->directmedia = directmedia; e->singlepath = singlepath; e->callwaiting = callwaiting; e->hascallwaiting = callwaiting; e->slowsequence = slowsequence; e->transfer = transfer; e->threewaycalling = threewaycalling; /* If we already have a valid chanvars, it's not a new endpoint (it's a reload), so first, free previous mem */ if (e->chanvars) { ast_variables_destroy(e->chanvars); e->chanvars = NULL; } e->chanvars = copy_vars(chanvars); if (!ep_reload) { e->onhooktime = time(NULL); /* ASSUME we're onhook */ e->hookstate = MGCP_ONHOOK; snprintf(e->rqnt_ident, sizeof(e->rqnt_ident), "%08lx", (unsigned long)ast_random()); } for (i = 0, sub = NULL; i < MAX_SUBS; i++) { if (!ep_reload) { sub = ast_calloc(1, sizeof(*sub)); } else { if (!sub) { sub = e->sub; } else { sub = sub->next; } } if (sub) { if (!ep_reload) { ast_verb(3, "Allocating subchannel '%d' on %s@%s\n", i, e->name, gw->name); ast_mutex_init(&sub->lock); ast_mutex_init(&sub->cx_queue_lock); ast_copy_string(sub->magic, MGCP_SUBCHANNEL_MAGIC, sizeof(sub->magic)); sub->parent = e; sub->id = i; snprintf(sub->txident, sizeof(sub->txident), "%08lx", (unsigned long)ast_random()); sub->cxmode = MGCP_CX_INACTIVE; sub->next = e->sub; e->sub = sub; } sub->nat = nat; } else { /* XXX Should find a way to clean up our memory */ ast_log(LOG_WARNING, "Out of memory allocating subchannel\n"); return NULL; } } if (!ep_reload) { /* Make out subs a circular linked list so we can always sping through the whole bunch */ /* find the end of the list */ for (sub = e->sub; sub && sub->next; sub = sub->next); /* set the last sub->next to the first sub */ sub->next = e->sub; e->next = gw->endpoints; gw->endpoints = e; } } } else if (!strcasecmp(v->name, "name") || !strcasecmp(v->name, "lines")) { /* just eliminate realtime warnings */ } else { ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno); } } if (!ntohl(gw->addr.sin_addr.s_addr) && !gw->dynamic) { ast_log(LOG_WARNING, "Gateway '%s' lacks IP address and isn't dynamic\n", gw->name); if (!gw_reload) { ast_mutex_destroy(&gw->msgs_lock); ast_free(gw); } /* Return NULL */ gw_reload = 1; } else { gw->defaddr.sin_family = AF_INET; gw->addr.sin_family = AF_INET; if (gw->defaddr.sin_addr.s_addr && !ntohs(gw->defaddr.sin_port)) { gw->defaddr.sin_port = htons(DEFAULT_MGCP_GW_PORT); } if (gw->addr.sin_addr.s_addr && !ntohs(gw->addr.sin_port)) { gw->addr.sin_port = htons(DEFAULT_MGCP_GW_PORT); } { struct ast_sockaddr tmp1, tmp2; struct sockaddr_in tmp3 = {0,}; tmp3.sin_addr = gw->ourip; ast_sockaddr_from_sin(&tmp1, &gw->addr); ast_sockaddr_from_sin(&tmp2, &tmp3); if (gw->addr.sin_addr.s_addr && ast_ouraddrfor(&tmp1, &tmp2)) { memcpy(&gw->ourip, &__ourip, sizeof(gw->ourip)); } else { ast_sockaddr_to_sin(&tmp2, &tmp3); gw->ourip = tmp3.sin_addr; } } } if (chanvars) { ast_variables_destroy(chanvars); chanvars = NULL; } return (gw_reload ? NULL : gw); } static enum ast_rtp_glue_result mgcp_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct mgcp_subchannel *sub = NULL; if (!(sub = ast_channel_tech_pvt(chan)) || !(sub->rtp)) return AST_RTP_GLUE_RESULT_FORBID; *instance = sub->rtp ? ao2_ref(sub->rtp, +1), sub->rtp : NULL; if (sub->parent->directmedia) return AST_RTP_GLUE_RESULT_REMOTE; else return AST_RTP_GLUE_RESULT_LOCAL; } static int mgcp_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp, struct ast_rtp_instance *trtp, const struct ast_format_cap *cap, int nat_active) { /* XXX Is there such thing as video support with MGCP? XXX */ struct mgcp_subchannel *sub; sub = ast_channel_tech_pvt(chan); if (sub && !sub->alreadygone) { transmit_modify_with_sdp(sub, rtp, cap); return 0; } return -1; } static void mgcp_get_codec(struct ast_channel *chan, struct ast_format_cap *result) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(chan); struct mgcp_endpoint *p = sub->parent; ast_format_cap_append_from_cap(result, p->cap, AST_MEDIA_TYPE_UNKNOWN); } static struct ast_rtp_glue mgcp_rtp_glue = { .type = "MGCP", .get_rtp_info = mgcp_get_rtp_peer, .update_peer = mgcp_set_rtp_peer, .get_codec = mgcp_get_codec, }; static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *args, char *buf, size_t buflen) { struct mgcp_subchannel *sub = ast_channel_tech_pvt(chan); int res = 0; /* Sanity check */ if (!chan || ast_channel_tech(chan) != &mgcp_tech) { ast_log(LOG_ERROR, "This function requires a valid MGCP channel\n"); return -1; } if (!strcasecmp(args, "ncs")) { snprintf(buf, buflen, "%s", sub->parent->ncs ? "yes":"no"); } else { res = -1; } return res; } static void destroy_endpoint(struct mgcp_endpoint *e) { struct mgcp_subchannel *sub = e->sub->next, *s; int i; for (i = 0; i < MAX_SUBS; i++) { ast_mutex_lock(&sub->lock); if (!ast_strlen_zero(sub->cxident)) { transmit_connection_del(sub); } if (sub->rtp) { ast_rtp_instance_destroy(sub->rtp); sub->rtp = NULL; } memset(sub->magic, 0, sizeof(sub->magic)); mgcp_queue_hangup(sub); dump_cmd_queues(NULL, sub); if(sub->gate) { sub->gate->tech_pvt = NULL; sub->gate->got_dq_gi = NULL; sub->gate->gate_remove = NULL; sub->gate->gate_open = NULL; } ast_mutex_unlock(&sub->lock); sub = sub->next; } if (e->dsp) { ast_dsp_free(e->dsp); } dump_queue(e->parent, e); dump_cmd_queues(e, NULL); sub = e->sub; for (i = 0; (i < MAX_SUBS) && sub; i++) { s = sub; sub = sub->next; ast_mutex_destroy(&s->lock); ast_mutex_destroy(&s->cx_queue_lock); ast_free(s); } if (e->mwi_event_sub) { e->mwi_event_sub = stasis_unsubscribe(e->mwi_event_sub); } if (e->chanvars) { ast_variables_destroy(e->chanvars); e->chanvars = NULL; } ast_mutex_destroy(&e->lock); ast_mutex_destroy(&e->rqnt_queue_lock); ast_mutex_destroy(&e->cmd_queue_lock); ao2_ref(e->cap, -1); ast_free(e); } static void destroy_gateway(struct mgcp_gateway *g) { if (g->ha) ast_free_ha(g->ha); dump_queue(g, NULL); ast_free(g); } static void prune_gateways(void) { struct mgcp_gateway *g, *z, *r; struct mgcp_endpoint *e, *p, *t; ast_mutex_lock(&gatelock); /* prune gateways */ for (z = NULL, g = gateways; g;) { /* prune endpoints */ for (p = NULL, e = g->endpoints; e; ) { if (!g->realtime && (e->delme || g->delme)) { t = e; e = e->next; if (!p) g->endpoints = e; else p->next = e; destroy_endpoint(t); } else { p = e; e = e->next; } } if (g->delme) { r = g; g = g->next; if (!z) gateways = g; else z->next = g; destroy_gateway(r); } else { z = g; g = g->next; } } ast_mutex_unlock(&gatelock); } static struct ast_variable *add_var(const char *buf, struct ast_variable *list) { struct ast_variable *tmpvar = NULL; char *varname = ast_strdupa(buf), *varval = NULL; if ((varval = strchr(varname, '='))) { *varval++ = '\0'; if ((tmpvar = ast_variable_new(varname, varval, ""))) { tmpvar->next = list; list = tmpvar; } } return list; } /*! \brief * duplicate a list of channel variables, \return the copy. */ static struct ast_variable *copy_vars(struct ast_variable *src) { struct ast_variable *res = NULL, *tmp, *v = NULL; for (v = src ; v ; v = v->next) { if ((tmp = ast_variable_new(v->name, v->value, v->file))) { tmp->next = res; res = tmp; } } return res; } static int reload_config(int reload) { struct ast_config *cfg; struct ast_variable *v; struct mgcp_gateway *g; struct mgcp_endpoint *e; char *cat; struct ast_hostent ahp; struct hostent *hp; struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; if (gethostname(ourhost, sizeof(ourhost)-1)) { ast_log(LOG_WARNING, "Unable to get hostname, MGCP disabled\n"); return 0; } cfg = ast_config_load(config, config_flags); /* We *must* have a config file otherwise stop immediately */ if (!cfg) { ast_log(LOG_NOTICE, "Unable to load config %s, MGCP disabled\n", config); return 0; } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { return 0; } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Config file %s is in an invalid format. Aborting.\n", config); return 0; } memset(&bindaddr, 0, sizeof(bindaddr)); dtmfmode = 0; /* Copy the default jb config over global_jbconf */ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); for (v = ast_variable_browse(cfg, "general"); v; v = v->next) { /* handle jb conf */ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) { continue; } /* Create the interface list */ if (!strcasecmp(v->name, "bindaddr")) { if (!(hp = ast_gethostbyname(v->value, &ahp))) { ast_log(LOG_WARNING, "Invalid address: %s\n", v->value); } else { memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr)); } } else if (!strcasecmp(v->name, "allow")) { ast_format_cap_update_by_allow_disallow(global_capability, v->value, 1); } else if (!strcasecmp(v->name, "disallow")) { ast_format_cap_update_by_allow_disallow(global_capability, v->value, 0); } else if (!strcasecmp(v->name, "tos")) { if (ast_str2tos(v->value, &qos.tos)) { ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "tos_audio")) { if (ast_str2tos(v->value, &qos.tos_audio)) ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno); } else if (!strcasecmp(v->name, "cos")) { if (ast_str2cos(v->value, &qos.cos)) ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno); } else if (!strcasecmp(v->name, "cos_audio")) { if (ast_str2cos(v->value, &qos.cos_audio)) ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno); } else if (!strcasecmp(v->name, "port")) { if (sscanf(v->value, "%5d", &ourport) == 1) { bindaddr.sin_port = htons(ourport); } else { ast_log(LOG_WARNING, "Invalid port number '%s' at line %d of %s\n", v->value, v->lineno, config); } } else if (!strcasecmp(v->name, "firstdigittimeout")) { firstdigittimeout = atoi(v->value); } else if (!strcasecmp(v->name, "gendigittimeout")) { gendigittimeout = atoi(v->value); } else if (!strcasecmp(v->name, "matchdigittimeout")) { matchdigittimeout = atoi(v->value); } } /* mark existing entries for deletion */ ast_mutex_lock(&gatelock); for (g = gateways; g; g = g->next) { g->delme = 1; for (e = g->endpoints; e; e = e->next) { e->delme = 1; } } ast_mutex_unlock(&gatelock); for (cat = ast_category_browse(cfg, NULL); cat; cat = ast_category_browse(cfg, cat)) { if (strcasecmp(cat, "general")) { ast_mutex_lock(&gatelock); if ((g = build_gateway(cat, ast_variable_browse(cfg, cat)))) { ast_verb(3, "Added gateway '%s'\n", g->name); g->next = gateways; gateways = g; } ast_mutex_unlock(&gatelock); /* FS: process queue and IO */ if (monitor_thread == pthread_self()) { if (sched) ast_sched_runq(sched); if (io) ast_io_wait(io, 10); } } } /* prune deleted entries etc. */ prune_gateways(); if (ntohl(bindaddr.sin_addr.s_addr)) { memcpy(&__ourip, &bindaddr.sin_addr, sizeof(__ourip)); } else { hp = ast_gethostbyname(ourhost, &ahp); if (!hp) { ast_log(LOG_WARNING, "Unable to get our IP address, MGCP disabled\n"); ast_config_destroy(cfg); return 0; } memcpy(&__ourip, hp->h_addr, sizeof(__ourip)); } if (!ntohs(bindaddr.sin_port)) bindaddr.sin_port = htons(DEFAULT_MGCP_CA_PORT); bindaddr.sin_family = AF_INET; ast_mutex_lock(&netlock); if (mgcpsock > -1) close(mgcpsock); if (mgcpsock_read_id != NULL) ast_io_remove(io, mgcpsock_read_id); mgcpsock_read_id = NULL; mgcpsock = socket(AF_INET, SOCK_DGRAM, 0); if (mgcpsock < 0) { ast_log(LOG_WARNING, "Unable to create MGCP socket: %s\n", strerror(errno)); } else { if (bind(mgcpsock, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) { ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n", ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port), strerror(errno)); close(mgcpsock); mgcpsock = -1; } else { ast_verb(2, "MGCP Listening on %s:%d\n", ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port)); ast_set_qos(mgcpsock, qos.tos, qos.cos, "MGCP"); } } ast_mutex_unlock(&netlock); ast_config_destroy(cfg); /* send audit only to the new endpoints */ for (g = gateways; g; g = g->next) { for (e = g->endpoints; e && e->needaudit; e = e->next) { e->needaudit = 0; transmit_audit_endpoint(e); ast_verb(3, "MGCP Auditing endpoint %s@%s for hookstate\n", e->name, g->name); } } return 0; } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { if (!(global_capability = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_FAILURE; } if (!(mgcp_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { ao2_ref(global_capability, -1); return AST_MODULE_LOAD_FAILURE; } ast_format_cap_append(global_capability, ast_format_ulaw, 0); ast_format_cap_append(mgcp_tech.capabilities, ast_format_ulaw, 0); ast_format_cap_append(mgcp_tech.capabilities, ast_format_alaw, 0); if (!(sched = ast_sched_context_create())) { ast_log(LOG_WARNING, "Unable to create schedule context\n"); ao2_ref(global_capability, -1); ao2_ref(mgcp_tech.capabilities, -1); return AST_MODULE_LOAD_FAILURE; } if (!(io = io_context_create())) { ast_log(LOG_WARNING, "Unable to create I/O context\n"); ast_sched_context_destroy(sched); ao2_ref(global_capability, -1); ao2_ref(mgcp_tech.capabilities, -1); return AST_MODULE_LOAD_FAILURE; } if (reload_config(0)) { ao2_ref(global_capability, -1); ao2_ref(mgcp_tech.capabilities, -1); return AST_MODULE_LOAD_DECLINE; } /* Make sure we can register our mgcp channel type */ if (ast_channel_register(&mgcp_tech)) { ast_log(LOG_ERROR, "Unable to register channel class 'MGCP'\n"); io_context_destroy(io); ast_sched_context_destroy(sched); ao2_ref(global_capability, -1); ao2_ref(mgcp_tech.capabilities, -1); return AST_MODULE_LOAD_FAILURE; } ast_rtp_glue_register(&mgcp_rtp_glue); ast_cli_register_multiple(cli_mgcp, sizeof(cli_mgcp) / sizeof(struct ast_cli_entry)); /* And start the monitor for the first time */ restart_monitor(); return AST_MODULE_LOAD_SUCCESS; } static char *mgcp_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { static int deprecated = 0; if (e) { switch (cmd) { case CLI_INIT: e->command = "mgcp reload"; e->usage = "Usage: mgcp reload\n" " 'mgcp reload' is deprecated. Please use 'reload chan_mgcp.so' instead.\n"; return NULL; case CLI_GENERATE: return NULL; } } if (!deprecated && a && a->argc > 0) { ast_log(LOG_WARNING, "'mgcp reload' is deprecated. Please use 'reload chan_mgcp.so' instead.\n"); deprecated = 1; } ast_mutex_lock(&mgcp_reload_lock); if (mgcp_reloading) { ast_verbose("Previous mgcp reload not yet done\n"); } else { mgcp_reloading = 1; } ast_mutex_unlock(&mgcp_reload_lock); restart_monitor(); return CLI_SUCCESS; } static int reload(void) { mgcp_reload(NULL, 0, NULL); return 0; } static int unload_module(void) { struct mgcp_endpoint *e; struct mgcp_gateway *g; /* Check to see if we're reloading */ if (ast_mutex_trylock(&mgcp_reload_lock)) { ast_log(LOG_WARNING, "MGCP is currently reloading. Unable to remove module.\n"); return -1; } else { mgcp_reloading = 1; ast_mutex_unlock(&mgcp_reload_lock); } /* First, take us out of the channel loop */ ast_channel_unregister(&mgcp_tech); /* Shut down the monitoring thread */ if (!ast_mutex_lock(&monlock)) { if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP)) { pthread_cancel(monitor_thread); pthread_kill(monitor_thread, SIGURG); pthread_join(monitor_thread, NULL); } monitor_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&monlock); } else { ast_log(LOG_WARNING, "Unable to lock the monitor\n"); /* We always want to leave this in a consistent state */ ast_channel_register(&mgcp_tech); mgcp_reloading = 0; mgcp_reload(NULL, 0, NULL); return -1; } if (!ast_mutex_lock(&gatelock)) { for (g = gateways; g; g = g->next) { g->delme = 1; for (e = g->endpoints; e; e = e->next) { e->delme = 1; } } prune_gateways(); ast_mutex_unlock(&gatelock); } else { ast_log(LOG_WARNING, "Unable to lock the gateways list.\n"); /* We always want to leave this in a consistent state */ ast_channel_register(&mgcp_tech); /* Allow the monitor to restart */ monitor_thread = AST_PTHREADT_NULL; mgcp_reloading = 0; mgcp_reload(NULL, 0, NULL); return -1; } close(mgcpsock); ast_rtp_glue_unregister(&mgcp_rtp_glue); ast_cli_unregister_multiple(cli_mgcp, sizeof(cli_mgcp) / sizeof(struct ast_cli_entry)); ast_sched_context_destroy(sched); ao2_ref(global_capability, -1); global_capability = NULL; ao2_ref(mgcp_tech.capabilities, -1); mgcp_tech.capabilities = NULL; return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Media Gateway Control Protocol (MGCP)", .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, .nonoptreq = "res_pktccops", ); asterisk-13.1.0/channels/vcodecs.c0000644000000000000000000010600011766660300015506 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright 2007-2008, Sergio Fadda, Luigi Rizzo * * 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. */ /* * Video codecs support for console_video.c * $Revision: 369013 $ */ /*** MODULEINFO extended ***/ #include "asterisk.h" #include "console_video.h" #include "asterisk/frame.h" #include "asterisk/utils.h" /* ast_calloc() */ struct video_out_desc; struct video_dec_desc; struct fbuf_t; /* * Each codec is defined by a number of callbacks */ /*! \brief initialize the encoder */ typedef int (*encoder_init_f)(AVCodecContext *v); /*! \brief actually call the encoder */ typedef int (*encoder_encode_f)(struct video_out_desc *v); /*! \brief encapsulate the bistream in RTP frames */ typedef struct ast_frame *(*encoder_encap_f)(struct fbuf_t *, int mtu, struct ast_frame **tail); /*! \brief inizialize the decoder */ typedef int (*decoder_init_f)(AVCodecContext *enc_ctx); /*! \brief extract the bitstream from RTP frames and store in the fbuf. * return 0 if ok, 1 on error */ typedef int (*decoder_decap_f)(struct fbuf_t *b, uint8_t *data, int len); /*! \brief actually call the decoder */ typedef int (*decoder_decode_f)(struct video_dec_desc *v, struct fbuf_t *b); struct video_codec_desc { const char *name; /* format name */ int format; /* AST_FORMAT_* */ encoder_init_f enc_init; encoder_encap_f enc_encap; encoder_encode_f enc_run; decoder_init_f dec_init; decoder_decap_f dec_decap; decoder_decode_f dec_run; }; /* * Descriptor for the incoming stream, with multiple buffers for the bitstream * extracted from the RTP packets, RTP reassembly info, and a frame buffer * for the decoded frame (buf). * The descriptor is allocated as the first frame comes in. * * Incoming payload is stored in one of the dec_in[] buffers, which are * emptied by the video thread. These buffers are organized in a circular * queue, with dec_in_cur being the buffer in use by the incoming stream, * and dec_in_dpy is the one being displayed. When the pointers need to * be changed, we synchronize the access to them with dec_lock. * When the list is full dec_in_cur = NULL (we cannot store new data), * when the list is empty dec_in_dpy = NULL (we cannot display frames). */ struct video_dec_desc { struct video_codec_desc *d_callbacks; /* decoder callbacks */ AVCodecContext *dec_ctx; /* information about the codec in the stream */ AVCodec *codec; /* reference to the codec */ AVFrame *d_frame; /* place to store the decoded frame */ AVCodecParserContext *parser; uint16_t next_seq; /* must be 16 bit */ int discard; /* flag for discard status */ #define N_DEC_IN 3 /* number of incoming buffers */ struct fbuf_t *dec_in_cur; /* buffer being filled in */ struct fbuf_t *dec_in_dpy; /* buffer to display */ struct fbuf_t dec_in[N_DEC_IN]; /* incoming bitstream, allocated/extended in fbuf_append() */ struct fbuf_t dec_out; /* decoded frame, no buffer (data is in AVFrame) */ }; #ifdef debugging_only /* some debugging code to check the bitstream: * declare a bit buffer, initialize it, and fetch data from it. */ struct bitbuf { const uint8_t *base; int bitsize; /* total size in bits */ int ofs; /* next bit to read */ }; static struct bitbuf bitbuf_init(const uint8_t *base, int bitsize, int start_ofs) { struct bitbuf a; a.base = base; a.bitsize = bitsize; a.ofs = start_ofs; return a; } static int bitbuf_left(struct bitbuf *b) { return b->bitsize - b->ofs; } static uint32_t getbits(struct bitbuf *b, int n) { int i, ofs; const uint8_t *d; uint8_t mask; uint32_t retval = 0; if (n> 31) { ast_log(LOG_WARNING, "too many bits %d, max 32\n", n); return 0; } if (n + b->ofs > b->bitsize) { ast_log(LOG_WARNING, "bitbuf overflow %d of %d\n", n + b->ofs, b->bitsize); n = b->bitsize - b->ofs; } ofs = 7 - b->ofs % 8; /* start from msb */ mask = 1 << ofs; d = b->base + b->ofs / 8; /* current byte */ for (i=0 ; i < n; i++) { retval += retval + (*d & mask ? 1 : 0); /* shift in new byte */ b->ofs++; mask >>= 1; if (mask == 0) { d++; mask = 0x80; } } return retval; } static void check_h261(struct fbuf_t *b) { struct bitbuf a = bitbuf_init(b->data, b->used * 8, 0); uint32_t x, y; x = getbits(&a, 20); /* PSC, 0000 0000 0000 0001 0000 */ if (x != 0x10) { ast_log(LOG_WARNING, "bad PSC 0x%x\n", x); return; } x = getbits(&a, 5); /* temporal reference */ y = getbits(&a, 6); /* ptype */ if (0) ast_log(LOG_WARNING, "size %d TR %d PTY spl %d doc %d freeze %d %sCIF hi %d\n", b->used, x, (y & 0x20) ? 1 : 0, (y & 0x10) ? 1 : 0, (y & 0x8) ? 1 : 0, (y & 0x4) ? "" : "Q", (y & 0x2) ? 1:0); while ( (x = getbits(&a, 1)) == 1) ast_log(LOG_WARNING, "PSPARE 0x%x\n", getbits(&a, 8)); // ast_log(LOG_WARNING, "PSPARE 0 - start GOB LAYER\n"); while ( (x = bitbuf_left(&a)) > 0) { // ast_log(LOG_WARNING, "GBSC %d bits left\n", x); x = getbits(&a, 16); /* GBSC 0000 0000 0000 0001 */ if (x != 0x1) { ast_log(LOG_WARNING, "bad GBSC 0x%x\n", x); break; } x = getbits(&a, 4); /* group number */ y = getbits(&a, 5); /* gquant */ if (x == 0) { ast_log(LOG_WARNING, " bad GN %d\n", x); break; } while ( (x = getbits(&a, 1)) == 1) ast_log(LOG_WARNING, "GSPARE 0x%x\n", getbits(&a, 8)); while ( (x = bitbuf_left(&a)) > 0) { /* MB layer */ break; } } } void dump_buf(struct fbuf_t *b); void dump_buf(struct fbuf_t *b) { int i, x, last2lines; char buf[80]; last2lines = (b->used - 16) & ~0xf; ast_log(LOG_WARNING, "buf size %d of %d\n", b->used, b->size); for (i = 0; i < b->used; i++) { x = i & 0xf; if ( x == 0) { /* new line */ if (i != 0) ast_log(LOG_WARNING, "%s\n", buf); memset(buf, '\0', sizeof(buf)); sprintf(buf, "%04x: ", i); } sprintf(buf + 6 + x*3, "%02x ", b->data[i]); if (i > 31 && i < last2lines) i = last2lines - 1; } if (buf[0]) ast_log(LOG_WARNING, "%s\n", buf); } #endif /* debugging_only */ /*! * Build an ast_frame for a given chunk of data, and link it into * the queue, with possibly 'head' bytes at the beginning to * fill in some fields later. */ static struct ast_frame *create_video_frame(uint8_t *start, uint8_t *end, int format, int head, struct ast_frame *prev) { int len = end-start; uint8_t *data; struct ast_frame *f; data = ast_calloc(1, len+head); f = ast_calloc(1, sizeof(*f)); if (f == NULL || data == NULL) { ast_log(LOG_WARNING, "--- frame error f %p data %p len %d format %d\n", f, data, len, format); if (f) ast_free(f); if (data) ast_free(data); return NULL; } memcpy(data+head, start, len); f->data.ptr = data; f->mallocd = AST_MALLOCD_DATA | AST_MALLOCD_HDR; //f->has_timing_info = 1; //f->ts = ast_tvdiff_ms(ast_tvnow(), out->ts); f->datalen = len+head; f->frametype = AST_FRAME_VIDEO; f->subclass = format; f->samples = 0; f->offset = 0; f->src = "Console"; f->delivery.tv_sec = 0; f->delivery.tv_usec = 0; f->seqno = 0; AST_LIST_NEXT(f, frame_list) = NULL; if (prev) AST_LIST_NEXT(prev, frame_list) = f; return f; } /* * Append a chunk of data to a buffer taking care of bit alignment * Return 0 on success, != 0 on failure */ static int fbuf_append(struct fbuf_t *b, uint8_t *src, int len, int sbit, int ebit) { /* * Allocate buffer. ffmpeg wants an extra FF_INPUT_BUFFER_PADDING_SIZE, * and also wants 0 as a buffer terminator to prevent trouble. */ int need = len + FF_INPUT_BUFFER_PADDING_SIZE; int i; uint8_t *dst, mask; if (b->data == NULL) { b->size = need; b->used = 0; b->ebit = 0; b->data = ast_calloc(1, b->size); } else if (b->used + need > b->size) { b->size = b->used + need; b->data = ast_realloc(b->data, b->size); } if (b->data == NULL) { ast_log(LOG_WARNING, "alloc failure for %d, discard\n", b->size); return 1; } if (b->used == 0 && b->ebit != 0) { ast_log(LOG_WARNING, "ebit not reset at start\n"); b->ebit = 0; } dst = b->data + b->used; i = b->ebit + sbit; /* bits to ignore around */ if (i == 0) { /* easy case, just append */ /* do everything in the common block */ } else if (i == 8) { /* easy too, just handle the overlap byte */ mask = (1 << b->ebit) - 1; /* update the last byte in the buffer */ dst[-1] &= ~mask; /* clear bits to ignore */ dst[-1] |= (*src & mask); /* append new bits */ src += 1; /* skip and prepare for common block */ len --; } else { /* must shift the new block, not done yet */ ast_log(LOG_WARNING, "must handle shift %d %d at %d\n", b->ebit, sbit, b->used); return 1; } memcpy(dst, src, len); b->used += len; b->ebit = ebit; b->data[b->used] = 0; /* padding */ return 0; } /* * Here starts the glue code for the various supported video codecs. * For each of them, we need to provide routines for initialization, * calling the encoder, encapsulating the bitstream in ast_frames, * extracting payload from ast_frames, and calling the decoder. */ /*--- h263+ support --- */ /*! \brief initialization of h263p */ static int h263p_enc_init(AVCodecContext *enc_ctx) { /* modes supported are - Unrestricted Motion Vector (annex D) - Advanced Prediction (annex F) - Advanced Intra Coding (annex I) - Deblocking Filter (annex J) - Slice Structure (annex K) - Alternative Inter VLC (annex S) - Modified Quantization (annex T) */ enc_ctx->flags |=CODEC_FLAG_H263P_UMV; /* annex D */ enc_ctx->flags |=CODEC_FLAG_AC_PRED; /* annex f ? */ enc_ctx->flags |=CODEC_FLAG_H263P_SLICE_STRUCT; /* annex k */ enc_ctx->flags |= CODEC_FLAG_H263P_AIC; /* annex I */ return 0; } /* * Create RTP/H.263 fragments to avoid IP fragmentation. We fragment on a * PSC or a GBSC, but if we don't find a suitable place just break somewhere. * Everything is byte-aligned. */ static struct ast_frame *h263p_encap(struct fbuf_t *b, int mtu, struct ast_frame **tail) { struct ast_frame *cur = NULL, *first = NULL; uint8_t *d = b->data; int len = b->used; int l = len; /* size of the current fragment. If 0, must look for a psc */ for (;len > 0; len -= l, d += l) { uint8_t *data; struct ast_frame *f; int i, h; if (len >= 3 && d[0] == 0 && d[1] == 0 && d[2] >= 0x80) { /* we are starting a new block, so look for a PSC. */ for (i = 3; i < len - 3; i++) { if (d[i] == 0 && d[i+1] == 0 && d[i+2] >= 0x80) { l = i; break; } } } if (l > mtu || l > len) { /* psc not found, split */ l = MIN(len, mtu); } if (l < 1 || l > mtu) { ast_log(LOG_WARNING, "--- frame error l %d\n", l); break; } if (d[0] == 0 && d[1] == 0) { /* we start with a psc */ h = 0; } else { /* no psc, create a header */ h = 2; } f = create_video_frame(d, d+l, AST_FORMAT_H263_PLUS, h, cur); if (!f) break; data = f->data.ptr; if (h == 0) { /* we start with a psc */ data[0] |= 0x04; // set P == 1, and we are done } else { /* no psc, create a header */ data[0] = data[1] = 0; // P == 0 } if (!cur) first = f; cur = f; } if (cur) cur->subclass |= 1; // RTP Marker *tail = cur; /* end of the list */ return first; } /*! \brief extract the bitstreem from the RTP payload. * This is format dependent. * For h263+, the format is defined in RFC 2429 * and basically has a fixed 2-byte header as follows: * 5 bits RR reserved, shall be 0 * 1 bit P indicate a start/end condition, * in which case the payload should be prepended * by two zero-valued bytes. * 1 bit V there is an additional VRC header after this header * 6 bits PLEN length in bytes of extra picture header * 3 bits PEBIT how many bits to be ignored in the last byte * * XXX the code below is not complete. */ static int h263p_decap(struct fbuf_t *b, uint8_t *data, int len) { int PLEN; if (len < 2) { ast_log(LOG_WARNING, "invalid framesize %d\n", len); return 1; } PLEN = ( (data[0] & 1) << 5 ) | ( (data[1] & 0xf8) >> 3); if (PLEN > 0) { data += PLEN; len -= PLEN; } if (data[0] & 4) /* bit P */ data[0] = data[1] = 0; else { data += 2; len -= 2; } return fbuf_append(b, data, len, 0, 0); /* ignore trail bits */ } /* * generic encoder, used by the various protocols supported here. * We assume that the buffer is empty at the beginning. */ static int ffmpeg_encode(struct video_out_desc *v) { struct fbuf_t *b = &v->enc_out; int i; b->used = avcodec_encode_video(v->enc_ctx, b->data, b->size, v->enc_in_frame); i = avcodec_encode_video(v->enc_ctx, b->data + b->used, b->size - b->used, NULL); /* delayed frames ? */ if (i > 0) { ast_log(LOG_WARNING, "have %d more bytes\n", i); b->used += i; } return 0; } /* * Generic decoder, which is used by h263p, h263 and h261 as it simply * invokes ffmpeg's decoder. * av_parser_parse should merge a randomly chopped up stream into * proper frames. After that, if we have a valid frame, we decode it * until the entire frame is processed. */ static int ffmpeg_decode(struct video_dec_desc *v, struct fbuf_t *b) { uint8_t *src = b->data; int srclen = b->used; int full_frame = 0; if (srclen == 0) /* no data */ return 0; while (srclen) { uint8_t *data; int datalen, ret; int len = av_parser_parse(v->parser, v->dec_ctx, &data, &datalen, src, srclen, 0, 0); src += len; srclen -= len; /* The parser might return something it cannot decode, so it skips * the block returning no data */ if (data == NULL || datalen == 0) continue; ret = avcodec_decode_video(v->dec_ctx, v->d_frame, &full_frame, data, datalen); if (full_frame == 1) /* full frame */ break; if (ret < 0) { ast_log(LOG_NOTICE, "Error decoding\n"); break; } } if (srclen != 0) /* update b with leftover data */ memmove(b->data, src, srclen); b->used = srclen; b->ebit = 0; return full_frame; } static struct video_codec_desc h263p_codec = { .name = "h263p", .format = AST_FORMAT_H263_PLUS, .enc_init = h263p_enc_init, .enc_encap = h263p_encap, .enc_run = ffmpeg_encode, .dec_init = NULL, .dec_decap = h263p_decap, .dec_run = ffmpeg_decode }; /*--- Plain h263 support --------*/ static int h263_enc_init(AVCodecContext *enc_ctx) { /* XXX check whether these are supported */ enc_ctx->flags |= CODEC_FLAG_H263P_UMV; enc_ctx->flags |= CODEC_FLAG_H263P_AIC; enc_ctx->flags |= CODEC_FLAG_H263P_SLICE_STRUCT; enc_ctx->flags |= CODEC_FLAG_AC_PRED; return 0; } /* * h263 encapsulation is specified in RFC2190. There are three modes * defined (A, B, C), with 4, 8 and 12 bytes of header, respectively. * The header is made as follows * 0.....................|.......................|.............|....31 * F:1 P:1 SBIT:3 EBIT:3 SRC:3 I:1 U:1 S:1 A:1 R:4 DBQ:2 TRB:3 TR:8 * FP = 0- mode A, (only one word of header) * FP = 10 mode B, and also means this is an I or P frame * FP = 11 mode C, and also means this is a PB frame. * SBIT, EBIT nuber of bits to ignore at beginning (msbits) and end (lsbits) * SRC bits 6,7,8 from the h263 PTYPE field * I = 0 intra-coded, 1 = inter-coded (bit 9 from PTYPE) * U = 1 for Unrestricted Motion Vector (bit 10 from PTYPE) * S = 1 for Syntax Based Arith coding (bit 11 from PTYPE) * A = 1 for Advanced Prediction (bit 12 from PTYPE) * R = reserved, must be 0 * DBQ = differential quantization, DBQUANT from h263, 0 unless we are using * PB frames * TRB = temporal reference for bframes, also 0 unless this is a PB frame * TR = temporal reference for P frames, also 0 unless PB frame. * * Mode B and mode C description omitted. * * An RTP frame can start with a PSC 0000 0000 0000 0000 1000 0 * or with a GBSC, which also has the first 17 bits as a PSC. * Note - PSC are byte-aligned, GOB not necessarily. PSC start with * PSC:22 0000 0000 0000 0000 1000 00 picture start code * TR:8 .... .... temporal reference * PTYPE:13 or more ptype... * If we don't fragment a GOB SBIT and EBIT = 0. * reference, 8 bit) * * The assumption below is that we start with a PSC. */ static struct ast_frame *h263_encap(struct fbuf_t *b, int mtu, struct ast_frame **tail) { uint8_t *d = b->data; int start = 0, i, len = b->used; struct ast_frame *f, *cur = NULL, *first = NULL; const int pheader_len = 4; /* Use RFC-2190 Mode A */ uint8_t h263_hdr[12]; /* worst case, room for a type c header */ uint8_t *h = h263_hdr; /* shorthand */ #define H263_MIN_LEN 6 if (len < H263_MIN_LEN) /* unreasonably small */ return NULL; memset(h263_hdr, '\0', sizeof(h263_hdr)); /* Now set the header bytes. Only type A by now, * and h[0] = h[2] = h[3] = 0 by default. * PTYPE starts 30 bits in the picture, so the first useful * bit for us is bit 36 i.e. within d[4] (0 is the msbit). * SRC = d[4] & 0x1c goes into data[1] & 0xe0 * I = d[4] & 0x02 goes into data[1] & 0x10 * U = d[4] & 0x01 goes into data[1] & 0x08 * S = d[5] & 0x80 goes into data[1] & 0x04 * A = d[5] & 0x40 goes into data[1] & 0x02 * R = 0 goes into data[1] & 0x01 * Optimizing it, we have */ h[1] = ( (d[4] & 0x1f) << 3 ) | /* SRC, I, U */ ( (d[5] & 0xc0) >> 5 ); /* S, A, R */ /* now look for the next PSC or GOB header. First try to hit * a '0' byte then look around for the 0000 0000 0000 0000 1 pattern * which is both in the PSC and the GBSC. */ for (i = H263_MIN_LEN, start = 0; start < len; start = i, i += 3) { //ast_log(LOG_WARNING, "search at %d of %d/%d\n", i, start, len); for (; i < len ; i++) { uint8_t x, rpos, lpos; int rpos_i; /* index corresponding to rpos */ if (d[i] != 0) /* cannot be in a GBSC */ continue; if (i > len - 1) break; x = d[i+1]; if (x == 0) /* next is equally good */ continue; /* see if around us we can make 16 '0' bits for the GBSC. * Look for the first bit set on the right, and then * see if we have enough 0 on the left. * We are guaranteed to end before rpos == 0 */ for (rpos = 0x80, rpos_i = 8; rpos; rpos >>= 1, rpos_i--) if (x & rpos) /* found the '1' bit in GBSC */ break; x = d[i-1]; /* now look behind */ for (lpos = rpos; lpos ; lpos >>= 1) if (x & lpos) /* too early, not a GBSC */ break; if (lpos) /* as i said... */ continue; /* now we have a GBSC starting somewhere in d[i-1], * but it might be not byte-aligned */ if (rpos == 0x80) { /* lucky case */ i = i - 1; } else { /* XXX to be completed */ ast_log(LOG_WARNING, "unaligned GBSC 0x%x %d\n", rpos, rpos_i); } break; } /* This frame is up to offset i (not inclusive). * We do not split it yet even if larger than MTU. */ f = create_video_frame(d + start, d+i, AST_FORMAT_H263, pheader_len, cur); if (!f) break; memmove(f->data.ptr, h, 4); /* copy the h263 header */ /* XXX to do: if not aligned, fix sbit and ebit, * then move i back by 1 for the next frame */ if (!cur) first = f; cur = f; } if (cur) cur->subclass |= 1; // RTP Marker *tail = cur; return first; } /* XXX We only drop the header here, but maybe we need more. */ static int h263_decap(struct fbuf_t *b, uint8_t *data, int len) { if (len < 4) { ast_log(LOG_WARNING, "invalid framesize %d\n", len); return 1; /* error */ } if ( (data[0] & 0x80) == 0) { len -= 4; data += 4; } else { ast_log(LOG_WARNING, "unsupported mode 0x%x\n", data[0]); return 1; } return fbuf_append(b, data, len, 0, 0); /* XXX no bit alignment support yet */ } static struct video_codec_desc h263_codec = { .name = "h263", .format = AST_FORMAT_H263, .enc_init = h263_enc_init, .enc_encap = h263_encap, .enc_run = ffmpeg_encode, .dec_init = NULL, .dec_decap = h263_decap, .dec_run = ffmpeg_decode }; /*---- h261 support -----*/ static int h261_enc_init(AVCodecContext *enc_ctx) { /* It is important to set rtp_payload_size = 0, otherwise * ffmpeg in h261 mode will produce output that it cannot parse. * Also try to send I frames more frequently than with other codecs. */ enc_ctx->rtp_payload_size = 0; /* important - ffmpeg fails otherwise */ return 0; } /* * The encapsulation of H261 is defined in RFC4587 which obsoletes RFC2032 * The bitstream is preceded by a 32-bit header word: * SBIT:3 EBIT:3 I:1 V:1 GOBN:4 MBAP:5 QUANT:5 HMVD:5 VMVD:5 * SBIT and EBIT are the bits to be ignored at beginning and end, * I=1 if the stream has only INTRA frames - cannot change during the stream. * V=0 if motion vector is not used. Cannot change. * GOBN is the GOB number in effect at the start of packet, 0 if we * start with a GOB header * QUANT is the quantizer in effect, 0 if we start with GOB header * HMVD reference horizontal motion vector. 10000 is forbidden * VMVD reference vertical motion vector, as above. * Packetization should occur at GOB boundaries, and if not possible * with MacroBlock fragmentation. However it is likely that blocks * are not bit-aligned so we must take care of this. */ static struct ast_frame *h261_encap(struct fbuf_t *b, int mtu, struct ast_frame **tail) { uint8_t *d = b->data; int start = 0, i, len = b->used; struct ast_frame *f, *cur = NULL, *first = NULL; const int pheader_len = 4; uint8_t h261_hdr[4]; uint8_t *h = h261_hdr; /* shorthand */ int sbit = 0, ebit = 0; #define H261_MIN_LEN 10 if (len < H261_MIN_LEN) /* unreasonably small */ return NULL; memset(h261_hdr, '\0', sizeof(h261_hdr)); /* Similar to the code in h263_encap, but the marker there is longer. * Start a few bytes within the bitstream to avoid hitting the marker * twice. Note we might access the buffer at len, but this is ok because * the caller has it oversized. */ for (i = H261_MIN_LEN, start = 0; start < len - 1; start = i, i += 4) { #if 0 /* test - disable packetization */ i = len; /* wrong... */ #else int found = 0, found_ebit = 0; /* last GBSC position found */ for (; i < len ; i++) { uint8_t x, rpos, lpos; if (d[i] != 0) /* cannot be in a GBSC */ continue; x = d[i+1]; if (x == 0) /* next is equally good */ continue; /* See if around us we find 15 '0' bits for the GBSC. * Look for the first bit set on the right, and then * see if we have enough 0 on the left. * We are guaranteed to end before rpos == 0 */ for (rpos = 0x80, ebit = 7; rpos; ebit--, rpos >>= 1) if (x & rpos) /* found the '1' bit in GBSC */ break; x = d[i-1]; /* now look behind */ for (lpos = (rpos >> 1); lpos ; lpos >>= 1) if (x & lpos) /* too early, not a GBSC */ break; if (lpos) /* as i said... */ continue; /* now we have a GBSC starting somewhere in d[i-1], * but it might be not byte-aligned. Just remember it. */ if (i - start > mtu) /* too large, stop now */ break; found_ebit = ebit; found = i; i += 4; /* continue forward */ } if (i >= len) { /* trim if we went too forward */ i = len; ebit = 0; /* hopefully... should ask the bitstream ? */ } if (i - start > mtu && found) { /* use the previous GBSC, hope is within the mtu */ i = found; ebit = found_ebit; } #endif /* test */ if (i - start < 4) /* XXX too short ? */ continue; /* This frame is up to offset i (not inclusive). * We do not split it yet even if larger than MTU. */ f = create_video_frame(d + start, d+i, AST_FORMAT_H261, pheader_len, cur); if (!f) break; /* recompute header with I=0, V=1 */ h[0] = ( (sbit & 7) << 5 ) | ( (ebit & 7) << 2 ) | 1; memmove(f->data.ptr, h, 4); /* copy the h261 header */ if (ebit) /* not aligned, restart from previous byte */ i--; sbit = (8 - ebit) & 7; ebit = 0; if (!cur) first = f; cur = f; } if (cur) cur->subclass |= 1; // RTP Marker *tail = cur; return first; } /* * Pieces might be unaligned so we really need to put them together. */ static int h261_decap(struct fbuf_t *b, uint8_t *data, int len) { int ebit, sbit; if (len < 8) { ast_log(LOG_WARNING, "invalid framesize %d\n", len); return 1; } sbit = (data[0] >> 5) & 7; ebit = (data[0] >> 2) & 7; len -= 4; data += 4; return fbuf_append(b, data, len, sbit, ebit); } static struct video_codec_desc h261_codec = { .name = "h261", .format = AST_FORMAT_H261, .enc_init = h261_enc_init, .enc_encap = h261_encap, .enc_run = ffmpeg_encode, .dec_init = NULL, .dec_decap = h261_decap, .dec_run = ffmpeg_decode }; /* mpeg4 support */ static int mpeg4_enc_init(AVCodecContext *enc_ctx) { #if 0 //enc_ctx->flags |= CODEC_FLAG_LOW_DELAY; /*don't use b frames ?*/ enc_ctx->flags |= CODEC_FLAG_AC_PRED; enc_ctx->flags |= CODEC_FLAG_H263P_UMV; enc_ctx->flags |= CODEC_FLAG_QPEL; enc_ctx->flags |= CODEC_FLAG_4MV; enc_ctx->flags |= CODEC_FLAG_GMC; enc_ctx->flags |= CODEC_FLAG_LOOP_FILTER; enc_ctx->flags |= CODEC_FLAG_H263P_SLICE_STRUCT; #endif enc_ctx->rtp_payload_size = 0; /* important - ffmpeg fails otherwise */ return 0; } /* simplistic encapsulation - just split frames in mtu-size units */ static struct ast_frame *mpeg4_encap(struct fbuf_t *b, int mtu, struct ast_frame **tail) { struct ast_frame *f, *cur = NULL, *first = NULL; uint8_t *d = b->data; uint8_t *end = d + b->used; int len; for (;d < end; d += len, cur = f) { len = MIN(mtu, end - d); f = create_video_frame(d, d + len, AST_FORMAT_MP4_VIDEO, 0, cur); if (!f) break; if (!first) first = f; } if (cur) cur->subclass |= 1; *tail = cur; return first; } static int mpeg4_decap(struct fbuf_t *b, uint8_t *data, int len) { return fbuf_append(b, data, len, 0, 0); } static int mpeg4_decode(struct video_dec_desc *v, struct fbuf_t *b) { int full_frame = 0, datalen = b->used; int ret = avcodec_decode_video(v->dec_ctx, v->d_frame, &full_frame, b->data, datalen); if (ret < 0) { ast_log(LOG_NOTICE, "Error decoding\n"); ret = datalen; /* assume we used everything. */ } datalen -= ret; if (datalen > 0) /* update b with leftover bytes */ memmove(b->data, b->data + ret, datalen); b->used = datalen; b->ebit = 0; return full_frame; } static struct video_codec_desc mpeg4_codec = { .name = "mpeg4", .format = AST_FORMAT_MP4_VIDEO, .enc_init = mpeg4_enc_init, .enc_encap = mpeg4_encap, .enc_run = ffmpeg_encode, .dec_init = NULL, .dec_decap = mpeg4_decap, .dec_run = mpeg4_decode }; static int h264_enc_init(AVCodecContext *enc_ctx) { enc_ctx->flags |= CODEC_FLAG_TRUNCATED; //enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; //enc_ctx->flags2 |= CODEC_FLAG2_FASTPSKIP; /* TODO: Maybe we need to add some other flags */ enc_ctx->rtp_mode = 0; enc_ctx->rtp_payload_size = 0; enc_ctx->bit_rate_tolerance = enc_ctx->bit_rate; return 0; } static int h264_dec_init(AVCodecContext *dec_ctx) { dec_ctx->flags |= CODEC_FLAG_TRUNCATED; return 0; } /* * The structure of a generic H.264 stream is: * - 0..n 0-byte(s), unused, optional. one zero-byte is always present * in the first NAL before the start code prefix. * - start code prefix (3 bytes): 0x000001 * (the first bytestream has a * like these 0x00000001!) * - NAL header byte ( F[1] | NRI[2] | Type[5] ) where type != 0 * - byte-stream * - 0..n 0-byte(s) (padding, unused). * Segmentation in RTP only needs to be done on start code prefixes. * If fragments are too long... we don't support it yet. * - encapsulate (or fragment) the byte-stream (with NAL header included) */ static struct ast_frame *h264_encap(struct fbuf_t *b, int mtu, struct ast_frame **tail) { struct ast_frame *f = NULL, *cur = NULL, *first = NULL; uint8_t *d, *start = b->data; uint8_t *end = start + b->used; /* Search the first start code prefix - ITU-T H.264 sec. B.2, * and move start right after that, on the NAL header byte. */ #define HAVE_NAL(x) (x[-4] == 0 && x[-3] == 0 && x[-2] == 0 && x[-1] == 1) for (start += 4; start < end; start++) { int ty = start[0] & 0x1f; if (HAVE_NAL(start) && ty != 0 && ty != 31) break; } /* if not found, or too short, we just skip the next loop and are done. */ /* Here follows the main loop to create frames. Search subsequent start * codes, and then possibly fragment the unit into smaller fragments. */ for (;start < end - 4; start = d) { int size; /* size of current block */ uint8_t hdr[2]; /* add-on header when fragmenting */ int ty = 0; /* now search next nal */ for (d = start + 4; d < end; d++) { ty = d[0] & 0x1f; if (HAVE_NAL(d)) break; /* found NAL */ } /* have a block to send. d past the start code unless we overflow */ if (d >= end) { /* NAL not found */ d = end + 4; } else if (ty == 0 || ty == 31) { /* found but invalid type, skip */ ast_log(LOG_WARNING, "skip invalid nal type %d at %d of %d\n", ty, d - (uint8_t *)b->data, b->used); continue; } size = d - start - 4; /* don't count the end */ if (size < mtu) { // test - don't fragment // Single NAL Unit f = create_video_frame(start, d - 4, AST_FORMAT_H264, 0, cur); if (!f) break; if (!first) first = f; cur = f; continue; } // Fragmented Unit (Mode A: no DON, very weak) hdr[0] = (*start & 0xe0) | 28; /* mark as a fragmentation unit */ hdr[1] = (*start++ & 0x1f) | 0x80 ; /* keep type and set START bit */ size--; /* skip the NAL header */ while (size) { uint8_t *data; int frag_size = MIN(size, mtu); f = create_video_frame(start, start+frag_size, AST_FORMAT_H264, 2, cur); if (!f) break; size -= frag_size; /* skip this data block */ start += frag_size; data = f->data.ptr; data[0] = hdr[0]; data[1] = hdr[1] | (size == 0 ? 0x40 : 0); /* end bit if we are done */ hdr[1] &= ~0x80; /* clear start bit for subsequent frames */ if (!first) first = f; cur = f; } } if (cur) cur->subclass |= 1; // RTP Marker *tail = cur; return first; } static int h264_decap(struct fbuf_t *b, uint8_t *data, int len) { /* Start Code Prefix (Annex B in specification) */ uint8_t scp[] = { 0x00, 0x00, 0x00, 0x01 }; int retval = 0; int type, ofs = 0; if (len < 2) { ast_log(LOG_WARNING, "--- invalid len %d\n", len); return 1; } /* first of all, check if the packet has F == 0 */ if (data[0] & 0x80) { ast_log(LOG_WARNING, "--- forbidden packet; nal: %02x\n", data[0]); return 1; } type = data[0] & 0x1f; switch (type) { case 0: case 31: ast_log(LOG_WARNING, "--- invalid type: %d\n", type); return 1; case 24: case 25: case 26: case 27: case 29: ast_log(LOG_WARNING, "--- encapsulation not supported : %d\n", type); return 1; case 28: /* FU-A Unit */ if (data[1] & 0x80) { // S == 1, import F and NRI from next data[1] &= 0x1f; /* preserve type */ data[1] |= (data[0] & 0xe0); /* import F & NRI */ retval = fbuf_append(b, scp, sizeof(scp), 0, 0); ofs = 1; } else { ofs = 2; } break; default: /* From 1 to 23 (Single NAL Unit) */ retval = fbuf_append(b, scp, sizeof(scp), 0, 0); } if (!retval) retval = fbuf_append(b, data + ofs, len - ofs, 0, 0); if (retval) ast_log(LOG_WARNING, "result %d\n", retval); return retval; } static struct video_codec_desc h264_codec = { .name = "h264", .format = AST_FORMAT_H264, .enc_init = h264_enc_init, .enc_encap = h264_encap, .enc_run = ffmpeg_encode, .dec_init = h264_dec_init, .dec_decap = h264_decap, .dec_run = ffmpeg_decode }; /* * Table of translation between asterisk and ffmpeg formats. * We need also a field for read and write (encoding and decoding), because * e.g. H263+ uses different codec IDs in ffmpeg when encoding or decoding. */ struct _cm { /* map ffmpeg codec types to asterisk formats */ uint32_t ast_format; /* 0 is a terminator */ enum CodecID codec; enum { CM_RD = 1, CM_WR = 2, CM_RDWR = 3 } rw; /* read or write or both ? */ //struct video_codec_desc *codec_desc; }; static const struct _cm video_formats[] = { { AST_FORMAT_H263_PLUS, CODEC_ID_H263, CM_RD }, /* incoming H263P ? */ { AST_FORMAT_H263_PLUS, CODEC_ID_H263P, CM_WR }, { AST_FORMAT_H263, CODEC_ID_H263, CM_RD }, { AST_FORMAT_H263, CODEC_ID_H263, CM_WR }, { AST_FORMAT_H261, CODEC_ID_H261, CM_RDWR }, { AST_FORMAT_H264, CODEC_ID_H264, CM_RDWR }, { AST_FORMAT_MP4_VIDEO, CODEC_ID_MPEG4, CM_RDWR }, { 0, 0, 0 }, }; /*! \brief map an asterisk format into an ffmpeg one */ static enum CodecID map_video_format(uint32_t ast_format, int rw) { struct _cm *i; for (i = video_formats; i->ast_format != 0; i++) if (ast_format & i->ast_format && rw & i->rw) { return i->codec; } return CODEC_ID_NONE; } /* pointers to supported codecs. We assume the first one to be non null. */ static const struct video_codec_desc *supported_codecs[] = { &h263p_codec, &h264_codec, &h263_codec, &h261_codec, &mpeg4_codec, NULL }; /* * Map the AST_FORMAT to the library. If not recognised, fail. * This is useful in the input path where we get frames. */ static struct video_codec_desc *map_video_codec(int fmt) { int i; for (i = 0; supported_codecs[i]; i++) if (fmt == supported_codecs[i]->format) { ast_log(LOG_WARNING, "using %s for format 0x%x\n", supported_codecs[i]->name, fmt); return supported_codecs[i]; } return NULL; } /*! \brief uninitialize the descriptor for remote video stream */ static struct video_dec_desc *dec_uninit(struct video_dec_desc *v) { int i; if (v == NULL) /* not initialized yet */ return NULL; if (v->parser) { av_parser_close(v->parser); v->parser = NULL; } if (v->dec_ctx) { avcodec_close(v->dec_ctx); av_free(v->dec_ctx); v->dec_ctx = NULL; } if (v->d_frame) { av_free(v->d_frame); v->d_frame = NULL; } v->codec = NULL; /* only a reference */ v->d_callbacks = NULL; /* forget the decoder */ v->discard = 1; /* start in discard mode */ for (i = 0; i < N_DEC_IN; i++) fbuf_free(&v->dec_in[i]); fbuf_free(&v->dec_out); ast_free(v); return NULL; /* error, in case someone cares */ } /* * initialize ffmpeg resources used for decoding frames from the network. */ static struct video_dec_desc *dec_init(uint32_t the_ast_format) { enum CodecID codec; struct video_dec_desc *v = ast_calloc(1, sizeof(*v)); if (v == NULL) return NULL; v->discard = 1; v->d_callbacks = map_video_codec(the_ast_format); if (v->d_callbacks == NULL) { ast_log(LOG_WARNING, "cannot find video codec, drop input 0x%x\n", the_ast_format); return dec_uninit(v); } codec = map_video_format(v->d_callbacks->format, CM_RD); v->codec = avcodec_find_decoder(codec); if (!v->codec) { ast_log(LOG_WARNING, "Unable to find the decoder for format %d\n", codec); return dec_uninit(v); } /* * Initialize the codec context. */ v->dec_ctx = avcodec_alloc_context(); if (!v->dec_ctx) { ast_log(LOG_WARNING, "Cannot allocate the decoder context\n"); return dec_uninit(v); } /* XXX call dec_init() ? */ if (avcodec_open(v->dec_ctx, v->codec) < 0) { ast_log(LOG_WARNING, "Cannot open the decoder context\n"); av_free(v->dec_ctx); v->dec_ctx = NULL; return dec_uninit(v); } v->parser = av_parser_init(codec); if (!v->parser) { ast_log(LOG_WARNING, "Cannot initialize the decoder parser\n"); return dec_uninit(v); } v->d_frame = avcodec_alloc_frame(); if (!v->d_frame) { ast_log(LOG_WARNING, "Cannot allocate decoding video frame\n"); return dec_uninit(v); } v->dec_in_cur = &v->dec_in[0]; /* buffer for incoming frames */ v->dec_in_dpy = NULL; /* nothing to display */ return v; /* ok */ } /*------ end codec specific code -----*/ asterisk-13.1.0/channels/console_gui.c0000644000000000000000000014701611766660300016402 0ustar rootroot/* * GUI for console video. * The routines here are in charge of loading the keypad and handling events. * $Revision: 369013 $ */ /* * GUI layout, structure and management For the GUI we use SDL to create a large surface (gui->screen) with 4 areas: remote video on the left, local video on the right, keypad with all controls and text windows in the center, and source device thumbnails on the top. The top row is not displayed if no devices are specified in the config file. ________________________________________________________________ | ______ ______ ______ ______ ______ ______ ______ | | | tn.1 | | tn.2 | | tn.3 | | tn.4 | | tn.5 | | tn.6 | | tn.7 | | | |______| |______| |______| |______| |______| |______| |______| | | ______ ______ ______ ______ ______ ______ ______ | | |______| |______| |______| |______| |______| |______| |______| | | _________________ __________________ _________________ | | | | | | | | | | | | | | | | | | | | | | | | | | | remote video | | | | local video | | | | | | | | ______ | | | | | | keypad | | | PIP || | | | | | | | |______|| | | |_________________| | | |_________________| | | | | | | | | | | |__________________| | |________________________________________________________________| The central section is built using an image (jpg, png, maybe gif too) for the skin, and other GUI elements. Comments embedded in the image indicate to what function each area is mapped to. Another image (png with transparency) is used for the font. Mouse and keyboard events are detected on the whole surface, and handled differently according to their location: - center/right click on the local/remote window are used to resize the corresponding window; - clicks on the thumbnail start/stop sources and select them as primary or secondary video sources; - drag on the local video window are used to move the captured area (in the case of X11 grabber) or the picture-in-picture position; - keystrokes on the keypad are mapped to the corresponding key; keystrokes are used as keypad functions, or as text input if we are in text-input mode. - drag on some keypad areas (sliders etc.) are mapped to the corresponding functions (mute/unmute audio and video, enable/disable Picture-in-Picture, freeze the incoming video, dial numbers, pick up or hang up a call, ...) Configuration options control the appeareance of the gui: keypad = /tmp/kpad2.jpg ; the skin keypad_font = /tmp/font.png ; the font to use for output For future implementation, intresting features can be the following: - save of the whole SDL window as a picture - audio output device switching The audio switching feature should allow changing the device or switching to a recorded message for audio sent to remote party. The selection of the device should happen clicking on a marker in the layout. For this reason above the thumbnails row in the layout we would like a new row, the elements composing the row could be message boards, reporting the name of the device or the path of the message to be played. For video input freeze and entire window capture, we define 2 new key types, those should be activated pressing the buttons on the keypad, associated with new regions inside the keypad pictureas comments * */ /*** MODULEINFO extended ***/ #include "asterisk.h" #include "console_video.h" #include "asterisk/lock.h" #include "asterisk/frame.h" #include "asterisk/utils.h" /* ast_calloc and ast_realloc */ #include /* sqrt */ /* We use a maximum of 12 'windows' in the GUI */ enum { WIN_LOCAL, WIN_REMOTE, WIN_KEYPAD, WIN_SRC1, WIN_SRC2, WIN_SRC3, WIN_SRC4, WIN_SRC5, WIN_SRC6, WIN_SRC7, WIN_SRC8, WIN_SRC9, WIN_MAX }; #ifndef HAVE_SDL /* stubs if we don't have any sdl */ static void show_frame(struct video_desc *env, int out) {} static void sdl_setup(struct video_desc *env) {} static struct gui_info *cleanup_sdl(struct gui_info* g, int n) { return NULL; } static void eventhandler(struct video_desc *env, const char *caption) {} static int keypad_cfg_read(struct gui_info *gui, const char *val) { return 0; } #else /* HAVE_SDL, the real rendering code */ #include #include #ifdef HAVE_SDL_IMAGE #include /* for loading images */ #endif #ifdef HAVE_X11 /* Need to hook into X for SDL_WINDOWID handling */ #include #endif #define BORDER 5 /* border around our windows */ #define SRC_MSG_BD_H 20 /* height of the message board below those windows */ enum kp_type { KP_NONE, KP_RECT, KP_CIRCLE }; struct keypad_entry { int c; /* corresponding character */ int x0, y0, x1, y1, h; /* arguments */ enum kp_type type; }; /* our representation of a displayed window. SDL can only do one main * window so we map everything within that one */ struct display_window { SDL_Overlay *bmp; SDL_Rect rect; /* location of the window */ }; /* each thumbnail message board has a rectangle associated for the geometry, * and a board structure, we include these two elements in a singole structure */ struct thumb_bd { SDL_Rect rect; /* the rect for geometry and background */ struct board *board; /* the board */ }; struct gui_info { enum kb_output kb_output; /* where the keyboard output goes */ struct drag_info drag; /* info on the window are we dragging */ /* support for display. */ SDL_Surface *screen; /* the main window */ int outfd; /* fd for output */ SDL_Surface *keypad; /* the skin for the keypad */ SDL_Rect kp_rect; /* portion of the skin to display - default all */ SDL_Surface *font; /* font to be used */ SDL_Rect font_rects[96]; /* only printable chars */ /* each of the following board has two rectangles, * [0] is the geometry relative to the keypad, * [1] is the geometry relative to the whole screen * we do not use the thumb_bd for these boards because here we need * 2 rectangles for geometry */ SDL_Rect kp_msg[2]; /* incoming msg, relative to kpad */ struct board *bd_msg; SDL_Rect kp_edit[2]; /* edit user input */ struct board *bd_edit; SDL_Rect kp_dialed[2]; /* dialed number */ struct board *bd_dialed; /* other boards are one associated with the source windows * above the keypad in the layout, we only have the geometry * relative to the whole screen */ struct thumb_bd thumb_bd_array[MAX_VIDEO_SOURCES]; /* variable-size array mapping keypad regions to functions */ int kp_size, kp_used; struct keypad_entry *kp; struct display_window win[WIN_MAX]; }; /*! \brief free the resources in struct gui_info and the descriptor itself. * Return NULL so we can assign the value back to the descriptor in case. */ static struct gui_info *cleanup_sdl(struct gui_info *gui, int device_num) { int i; if (gui == NULL) return NULL; /* unload font file */ if (gui->font) { SDL_FreeSurface(gui->font); gui->font = NULL; } if (gui->outfd > -1) close(gui->outfd); if (gui->keypad) SDL_FreeSurface(gui->keypad); gui->keypad = NULL; if (gui->kp) ast_free(gui->kp); /* uninitialize the SDL environment */ for (i = 0; i < WIN_MAX; i++) { if (gui->win[i].bmp) SDL_FreeYUVOverlay(gui->win[i].bmp); } memset(gui, '\0', sizeof(gui)); /* deallocates the space allocated for the keypad message boards */ if (gui->bd_dialed) delete_board(gui->bd_dialed); if (gui->bd_msg) delete_board(gui->bd_msg); /* deallocates the space allocated for the thumbnail message boards */ for (i = 0; i < device_num; i++) { if (gui->thumb_bd_array[i].board) /* may be useless */ delete_board(gui->thumb_bd_array[i].board); } ast_free(gui); SDL_Quit(); return NULL; } /* messages to be displayed in the sources message boards * below the source windows */ /* costants defined to describe status of devices */ #define IS_PRIMARY 1 #define IS_SECONDARY 2 #define IS_ON 4 char* src_msgs[] = { " OFF", "1 OFF", " 2 OFF", "1+2 OFF", " ON", "1 ON", " 2 ON", "1+2 ON", }; /* * Display video frames (from local or remote stream) using the SDL library. * - Set the video mode to use the resolution specified by the codec context * - Create a YUV Overlay to copy the frame into it; * - After the frame is copied into the overlay, display it * * The size is taken from the configuration. * * 'out' is 0 for remote video, 1 for the local video */ static void show_frame(struct video_desc *env, int out) { AVPicture *p_in, p_out; struct fbuf_t *b_in, *b_out; SDL_Overlay *bmp; struct gui_info *gui = env->gui; if (!gui) return; if (out == WIN_LOCAL) { /* webcam/x11 to sdl */ b_in = &env->enc_in; b_out = &env->loc_dpy; p_in = NULL; } else if (out == WIN_REMOTE) { /* copy input format from the decoding context */ AVCodecContext *c; if (env->in == NULL) /* XXX should not happen - decoder not ready */ return; c = env->in->dec_ctx; b_in = &env->in->dec_out; b_in->pix_fmt = c->pix_fmt; b_in->w = c->width; b_in->h = c->height; b_out = &env->rem_dpy; p_in = (AVPicture *)env->in->d_frame; } else { int i = out-WIN_SRC1; b_in = env->out.devices[i].dev_buf; if (b_in == NULL) return; p_in = NULL; b_out = &env->src_dpy[i]; } bmp = gui->win[out].bmp; SDL_LockYUVOverlay(bmp); /* output picture info - this is sdl, YUV420P */ memset(&p_out, '\0', sizeof(p_out)); p_out.data[0] = bmp->pixels[0]; p_out.data[1] = bmp->pixels[1]; p_out.data[2] = bmp->pixels[2]; p_out.linesize[0] = bmp->pitches[0]; p_out.linesize[1] = bmp->pitches[1]; p_out.linesize[2] = bmp->pitches[2]; my_scale(b_in, p_in, b_out, &p_out); /* lock to protect access to Xlib by different threads. */ SDL_DisplayYUVOverlay(bmp, &gui->win[out].rect); SDL_UnlockYUVOverlay(bmp); } /* * Identifiers for regions of the main window. * Values between 0 and 127 correspond to ASCII characters. * The corresponding strings to be used in the skin comment section * are defined in gui_key_map. */ enum skin_area { /* answer/close functions */ KEY_PICK_UP = 128, KEY_HANG_UP = 129, KEY_MUTE = 130, KEY_AUTOANSWER = 131, KEY_SENDVIDEO = 132, KEY_LOCALVIDEO = 133, KEY_REMOTEVIDEO = 134, KEY_FLASH = 136, /* sensitive areas for the various text windows */ KEY_MESSAGEBOARD = 140, KEY_DIALEDBOARD = 141, KEY_EDITBOARD = 142, KEY_GUI_CLOSE = 199, /* close gui */ /* regions of the skin - displayed area, fonts, etc. * XXX NOTE these are not sensitive areas. */ KEY_KEYPAD = 200, /* the keypad - default to the whole image */ KEY_FONT = 201, /* the font. Maybe not really useful */ KEY_MESSAGE = 202, /* area for incoming messages */ KEY_DIALED = 203, /* area for dialed numbers */ KEY_EDIT = 204, /* area for editing user input */ #ifdef notyet /* XXX for future implementation */ KEY_AUDIO_SRCS = 210, /*indexes between 210 and 219 (or more) have been reserved for the "keys" associated with the audio device markers, clicking on these markers will change the source device for audio output */ #endif /* Keys related to video sources */ KEY_FREEZE = 220, /* freeze the incoming video */ KEY_CAPTURE = 221, /* capture the whole SDL window as a picture */ KEY_PIP = 230, /*indexes between 231 and 239 have been reserved for the "keys" associated with the device thumbnails, clicking on these pictures will change the source device for primary or secondary (PiP) video output*/ KEY_SRCS_WIN = 231, /* till 239 */ /* areas outside the keypad - simulated */ KEY_OUT_OF_KEYPAD = 241, KEY_REM_DPY = 242, KEY_LOC_DPY = 243, KEY_RESET = 253, /* the 'reset' keyword */ KEY_NONE = 254, /* invalid area */ KEY_DIGIT_BACKGROUND = 255, /* other areas within the keypad */ }; /* * Handlers for the various keypad functions */ /* accumulate digits, possibly call dial if in connected mode */ static void keypad_digit(struct video_desc *env, int digit) { if (env->owner) { /* we have a call, send the digit */ struct ast_frame f = { AST_FRAME_DTMF, 0 }; f.subclass = digit; ast_queue_frame(env->owner, &f); } else { /* no call, accumulate digits */ char buf[2] = { digit, '\0' }; if (env->gui->bd_msg) /* XXX not strictly necessary ... */ print_message(env->gui->bd_msg, buf); } } /* function used to toggle on/off the status of some variables */ static char *keypad_toggle(struct video_desc *env, int index) { ast_log(LOG_WARNING, "keypad_toggle(%i) called\n", index); switch (index) { case KEY_SENDVIDEO: /* send or do not send video */ env->out.sendvideo = !env->out.sendvideo; break; case KEY_PIP: /* enable or disable Picture in Picture */ env->out.picture_in_picture = !env->out.picture_in_picture; break; case KEY_MUTE: /* send or do not send audio */ ast_cli_command(env->gui->outfd, "console mute toggle"); break; case KEY_FREEZE: /* freeze/unfreeze the incoming frames */ env->frame_freeze = !env->frame_freeze; break; #ifdef notyet case KEY_AUTOANSWER: { struct chan_oss_pvt *o = find_desc(oss_active); o->autoanswer = !o->autoanswer; } break; #endif } return NULL; } char *console_do_answer(int fd); /* * Function called when the pick up button is pressed * perform actions according the channel status: * * - if no one is calling us and no digits was pressed, * the operation have no effects, * - if someone is calling us we answer to the call. * - if we have no call in progress and we pressed some * digit, send the digit to the console. */ static void keypad_pick_up(struct video_desc *env) { struct gui_info *gui = env->gui; ast_log(LOG_WARNING, "keypad_pick_up called\n"); if (env->owner) { /* someone is calling us, just answer */ ast_cli_command(gui->outfd, "console answer"); } else { /* we have someone to call */ char buf[160]; const char *who = ast_skip_blanks(read_message(gui->bd_msg)); buf[sizeof(buf) - 1] = '\0'; snprintf(buf, sizeof(buf), "console dial %s", who); ast_log(LOG_WARNING, "doing <%s>\n", buf); print_message(gui->bd_dialed, "\n"); print_message(gui->bd_dialed, who); reset_board(gui->bd_msg); ast_cli_command(gui->outfd, buf); } } #if 0 /* still unused */ /* * As an alternative to SDL_TTF, we can simply load the font from * an image and blit characters on the background of the GUI. * * To generate a font we can use the 'fly' command with the * following script (3 lines with 32 chars each) size 320,64 name font.png transparent 0,0,0 string 255,255,255, 0, 0,giant, !"#$%&'()*+,-./0123456789:;<=>? string 255,255,255, 0,20,giant,@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ string 255,255,255, 0,40,giant,`abcdefghijklmnopqrstuvwxyz{|}~ end */ /* Print given text on the gui */ static int gui_output(struct video_desc *env, const char *text) { return 1; /* error, not supported */ } #endif static int video_geom(struct fbuf_t *b, const char *s); static void sdl_setup(struct video_desc *env); static int kp_match_area(const struct keypad_entry *e, int x, int y); static void set_drag(struct drag_info *drag, int x, int y, enum drag_window win) { drag->x_start = x; drag->y_start = y; drag->drag_window = win; } static int update_device_info(struct video_desc *env, int i) { reset_board(env->gui->thumb_bd_array[i].board); print_message(env->gui->thumb_bd_array[i].board, src_msgs[env->out.devices[i].status_index]); return 0; } /*! \brief Changes the video output (local video) source, controlling if * it is already using that video device, * and switching the correct fields of env->out. * grabbers are always open and saved in the device table. * The secondary or the primary device can be changed, * according to the "button" parameter: * the primary device is changed if button = SDL_BUTTON_LEFT; * the secondary device is changed if button = not SDL_BUTTON_LEFT; * * the correct message boards of the sources are also updated * with the new status * * \param env = pointer to the video environment descriptor * \param index = index of the device the caller wants to use are primary or secondary device * \param button = button clicked on the mouse * * returns 0 on success, * returns 1 on error */ static int switch_video_out(struct video_desc *env, int index, Uint8 button) { int *p; /* pointer to the index of the device to select */ if (index >= env->out.device_num) { ast_log(LOG_WARNING, "no devices\n"); return 1; } /* select primary or secondary */ p = (button == SDL_BUTTON_LEFT) ? &env->out.device_primary : &env->out.device_secondary; /* controls if the device is already selected */ if (index == *p) { ast_log(LOG_WARNING, "device %s already selected\n", env->out.devices[index].name); return 0; } ast_log(LOG_WARNING, "switching to %s...\n", env->out.devices[index].name); /* already open */ if (env->out.devices[index].grabber) { /* we also have to update the messages in the source message boards below the source windows */ /* first we update the board of the previous source */ if (p == &env->out.device_primary) env->out.devices[*p].status_index &= ~IS_PRIMARY; else env->out.devices[*p].status_index &= ~IS_SECONDARY; update_device_info(env, *p); /* update the index used as primary or secondary */ *p = index; ast_log(LOG_WARNING, "done\n"); /* then we update the board of the new primary or secondary source */ if (p == &env->out.device_primary) env->out.devices[*p].status_index |= IS_PRIMARY; else env->out.devices[*p].status_index |= IS_SECONDARY; update_device_info(env, *p); return 0; } /* device is off, just do nothing */ ast_log(LOG_WARNING, "device is down\n"); return 1; } /*! \brief tries to switch the state of a device from on to off or off to on * we also have to update the status of the device and the correct message board * * \param index = the device that must be turned on or off * \param env = pointer to the video environment descriptor * * returns: * - 0 on falure switching from off to on * - 1 on success in switching from off to on * - 2 on success in switching from on to off */ static int turn_on_off(int index, struct video_desc *env) { struct video_device *p = &env->out.devices[index]; if (index >= env->out.device_num) { ast_log(LOG_WARNING, "no devices\n"); return 0; } if (!p->grabber) { /* device off */ void *g_data; /* result of grabber_open() */ struct grab_desc *g; int i; /* see if the device can be used by one of the existing drivers */ for (i = 0; (g = console_grabbers[i]); i++) { /* try open the device */ g_data = g->open(p->name, &env->out.loc_src_geometry, env->out.fps); if (!g_data) /* no luck, try the next driver */ continue; p->grabber = g; p->grabber_data = g_data; /* update the status of the source */ p->status_index |= IS_ON; /* print the new message in the message board */ update_device_info(env, index); return 1; /* open succeded */ } return 0; /* failure */ } else { /* the grabber must be closed */ p->grabber_data = p->grabber->close(p->grabber_data); p->grabber = NULL; /* dev_buf is already freed by grabber->close() */ p->dev_buf = NULL; /* update the status of the source */ p->status_index &= ~IS_ON; /* print the new message in the message board */ update_device_info(env, index); return 2; /* closed */ } } /* * Handle SDL_MOUSEBUTTONDOWN type, finding the palette * index value and calling the right callback. * * x, y are referred to the upper left corner of the main SDL window. */ static void handle_mousedown(struct video_desc *env, SDL_MouseButtonEvent button) { uint8_t index = KEY_OUT_OF_KEYPAD; /* the key or region of the display we clicked on */ struct gui_info *gui = env->gui; int i; /* integer variable used as iterator */ int x; /* integer variable usable as a container */ /* total width of source device thumbnails */ int src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER; /* x coordinate of the center of the keypad */ int x0 = MAX(env->rem_dpy.w+gui->keypad->w/2+2*BORDER, src_wins_tot_w/2); #if 0 ast_log(LOG_WARNING, "event %d %d have %d/%d regions at %p\n", button.x, button.y, gui->kp_used, gui->kp_size, gui->kp); #endif /* for each mousedown we end previous drag */ gui->drag.drag_window = DRAG_NONE; /* define keypad boundary */ /* XXX this should be extended for clicks on different audio device markers */ if (button.y >= (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0)) { /* if control reaches this point this means that the clicked point is below the row of the additional sources windows*/ /* adjust the y coordinate as if additional devices windows were not present */ button.y -= (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0); if (button.y < BORDER) index = KEY_OUT_OF_KEYPAD; else if (button.y >= MAX(MAX(env->rem_dpy.h, env->loc_dpy.h), gui->keypad->h)) index = KEY_OUT_OF_KEYPAD; else if (button.x < x0 - gui->keypad->w/2 - BORDER - env->rem_dpy.w) index = KEY_OUT_OF_KEYPAD; else if (button.x < x0 - gui->keypad->w/2 - BORDER) index = KEY_REM_DPY; else if (button.x < x0 - gui->keypad->w/2) index = KEY_OUT_OF_KEYPAD; else if (button.x >= x0 + gui->keypad->w/2 + BORDER + env->loc_dpy.w) index = KEY_OUT_OF_KEYPAD; else if (button.x >= x0 + gui->keypad->w/2 + BORDER) index = KEY_LOC_DPY; else if (button.x >= x0 + gui->keypad->w/2) index = KEY_OUT_OF_KEYPAD; else if (gui->kp) { /* we have to calculate the first coordinate inside the keypad before calling the kp_match_area*/ int x_keypad = button.x - (x0 - gui->keypad->w/2); /* find the key clicked (if one was clicked) */ for (i = 0; i < gui->kp_used; i++) { if (kp_match_area(&gui->kp[i],x_keypad, button.y - BORDER)) { index = gui->kp[i].c; break; } } } } else if (button.y < BORDER) { index = KEY_OUT_OF_KEYPAD; } else { /* we are in the thumbnail area */ x = x0 - src_wins_tot_w/2 + BORDER; if (button.y >= BORDER + SRC_WIN_H) index = KEY_OUT_OF_KEYPAD; else if (button.x < x) index = KEY_OUT_OF_KEYPAD; else if (button.x < x + src_wins_tot_w - BORDER) { /* note that the additional device windows are numbered from left to right starting from 0, with a maximum of 8, the index associated on a click is: KEY_SRCS_WIN + number_of_the_window */ for (i = 1; i <= env->out.device_num; i++) { if (button.x < x+i*(SRC_WIN_W+BORDER)-BORDER) { index = KEY_SRCS_WIN+i-1; break; } else if (button.x < x+i*(SRC_WIN_W+BORDER)) { index = KEY_OUT_OF_KEYPAD; break; } } } else index = KEY_OUT_OF_KEYPAD; } /* exec the function */ if (index < 128) { /* surely clicked on the keypad, don't care which key */ keypad_digit(env, index); return; } else if (index >= KEY_SRCS_WIN && index < KEY_SRCS_WIN+env->out.device_num) { index -= KEY_SRCS_WIN; /* index of the window, equal to the device index in the table */ /* if one of the additional device windows is clicked with left or right mouse button, we have to switch to that device */ if (button.button == SDL_BUTTON_RIGHT || button.button == SDL_BUTTON_LEFT) { switch_video_out(env, index, button.button); return; } /* turn on or off the devices selectively with other mouse buttons */ else { int ret = turn_on_off(index, env); /* print a message according to what happened */ if (!ret) ast_log(LOG_WARNING, "unable to turn on device %s\n", env->out.devices[index].name); else if (ret == 1) ast_log(LOG_WARNING, "device %s changed state to on\n", env->out.devices[index].name); else if (ret == 2) ast_log(LOG_WARNING, "device %s changed state to off\n", env->out.devices[index].name); return; } } /* XXX for future implementation else if (click on audio source marker) change audio source device */ switch (index) { /* answer/close function */ case KEY_PICK_UP: keypad_pick_up(env); break; case KEY_HANG_UP: ast_cli_command(gui->outfd, "console hangup"); break; /* other functions */ case KEY_MUTE: /* send or not send the audio */ case KEY_AUTOANSWER: case KEY_SENDVIDEO: /* send or not send the video */ case KEY_PIP: /* activate/deactivate picture in picture mode */ case KEY_FREEZE: /* freeze/unfreeze the incoming video */ keypad_toggle(env, index); break; case KEY_LOCALVIDEO: break; case KEY_REMOTEVIDEO: break; #ifdef notyet /* XXX for future implementations */ case KEY_CAPTURE: break; #endif case KEY_MESSAGEBOARD: if (button.button == SDL_BUTTON_LEFT) set_drag(&gui->drag, button.x, button.y, DRAG_MESSAGE); break; /* press outside the keypad. right increases size, center decreases, left drags */ case KEY_LOC_DPY: case KEY_REM_DPY: if (button.button == SDL_BUTTON_LEFT) { /* values used to find the position of the picture in picture (if present) */ int pip_loc_x = (double)env->out.pip_x/env->enc_in.w * env->loc_dpy.w; int pip_loc_y = (double)env->out.pip_y/env->enc_in.h * env->loc_dpy.h; /* check if picture in picture is active and the click was on it */ if (index == KEY_LOC_DPY && env->out.picture_in_picture && button.x >= x0+gui->keypad->w/2+BORDER+pip_loc_x && button.x < x0+gui->keypad->w/2+BORDER+pip_loc_x+env->loc_dpy.w/3 && button.y >= BORDER+pip_loc_y && button.y < BORDER+pip_loc_y+env->loc_dpy.h/3) { /* set the y cordinate to his previous value */ button.y += (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0); /* starts dragging the picture inside the picture */ set_drag(&gui->drag, button.x, button.y, DRAG_PIP); } else if (index == KEY_LOC_DPY) { /* set the y cordinate to his previous value */ button.y += (env->out.device_num ? SRC_WIN_H+2*BORDER+SRC_MSG_BD_H : 0); /* click in the local display, but not on the PiP */ set_drag(&gui->drag, button.x, button.y, DRAG_LOCAL); } break; } else { char buf[128]; struct fbuf_t *fb = index == KEY_LOC_DPY ? &env->loc_dpy : &env->rem_dpy; sprintf(buf, "%c%dx%d", button.button == SDL_BUTTON_RIGHT ? '>' : '<', fb->w, fb->h); video_geom(fb, buf); sdl_setup(env); /* writes messages in the source boards, those can be modified during the execution, because of the events this must be done here, otherwise the status of sources will not be shown after sdl_setup */ for (i = 0; i < env->out.device_num; i++) { update_device_info(env, i); } /* we also have to refresh other boards, to avoid messages to disappear after video resize */ print_message(gui->bd_msg, " \b"); print_message(gui->bd_dialed, " \b"); } break; case KEY_OUT_OF_KEYPAD: ast_log(LOG_WARNING, "nothing clicked, coordinates: %d, %d\n", button.x, button.y); break; case KEY_DIGIT_BACKGROUND: break; default: ast_log(LOG_WARNING, "function not yet defined %i\n", index); } } /* * Handle SDL_KEYDOWN type event, put the key pressed * in the dial buffer or in the text-message buffer, * depending on the text_mode variable value. * * key is the SDLKey structure corresponding to the key pressed. * Note that SDL returns modifiers (ctrl, shift, alt) as independent * information so the key itself is not enough and we need to * use a translation table, below - one line per entry, * plain, shift, ctrl, ... using the first char as key. */ static const char * const us_kbd_map[] = { "`~", "1!", "2@", "3#", "4$", "5%", "6^", "7&", "8*", "9(", "0)", "-_", "=+", "[{", "]}", "\\|", ";:", "'\"", ",<", ".>", "/?", "jJ\n", NULL }; static char map_key(SDL_keysym *ks) { const char *s, **p = us_kbd_map; int c = ks->sym; if (c == '\r') /* map cr into lf */ c = '\n'; if (c >= SDLK_NUMLOCK && c <= SDLK_COMPOSE) return 0; /* only a modifier */ if (ks->mod == 0) return c; while ((s = *p) && s[0] != c) p++; if (s) { /* see if we have a modifier and a chance to use it */ int l = strlen(s), mod = 0; if (l > 1) mod |= (ks->mod & KMOD_SHIFT) ? 1 : 0; if (l > 2 + mod) mod |= (ks->mod & KMOD_CTRL) ? 2 : 0; if (l > 4 + mod) mod |= (ks->mod & KMOD_ALT) ? 4 : 0; c = s[mod]; } if (ks->mod & (KMOD_CAPS|KMOD_SHIFT) && c >= 'a' && c <='z') c += 'A' - 'a'; return c; } static void handle_keyboard_input(struct video_desc *env, SDL_keysym *ks) { char buf[2] = { map_key(ks), '\0' }; struct gui_info *gui = env->gui; if (buf[0] == 0) /* modifier ? */ return; switch (gui->kb_output) { default: break; case KO_INPUT: /* to be completed */ break; case KO_MESSAGE: if (gui->bd_msg) { print_message(gui->bd_msg, buf); if (buf[0] == '\r' || buf[0] == '\n') { keypad_pick_up(env); } } break; case KO_DIALED: /* to be completed */ break; } return; } static void grabber_move(struct video_device *, int dx, int dy); int compute_drag(int *start, int end, int magnifier); int compute_drag(int *start, int end, int magnifier) { int delta = end - *start; #define POLARITY -1 /* add a small quadratic term */ delta += delta * delta * (delta > 0 ? 1 : -1 )/100; delta *= POLARITY * magnifier; #undef POLARITY *start = end; return delta; } /*! \brief This function moves the picture in picture, * controlling the limits of the containing buffer * to avoid problems deriving from going through the limits. * * \param env = pointer to the descriptor of the video environment * \param dx = the variation of the x position * \param dy = the variation of the y position */ static void pip_move(struct video_desc* env, int dx, int dy) { int new_pip_x = env->out.pip_x+dx; int new_pip_y = env->out.pip_y+dy; /* going beyond the left borders */ if (new_pip_x < 0) new_pip_x = 0; /* going beyond the right borders */ else if (new_pip_x > env->enc_in.w - env->enc_in.w/3) new_pip_x = env->enc_in.w - env->enc_in.w/3; /* going beyond the top borders */ if (new_pip_y < 0) new_pip_y = 0; /* going beyond the bottom borders */ else if (new_pip_y > env->enc_in.h - env->enc_in.h/3) new_pip_y = env->enc_in.h - env->enc_in.h/3; env->out.pip_x = new_pip_x; env->out.pip_y = new_pip_y; } /* * I am seeing some kind of deadlock or stall around * SDL_PumpEvents() while moving the window on a remote X server * (both xfree-4.4.0 and xorg 7.2) * and windowmaker. It is unclear what causes it. */ /*! \brief refresh the screen, and also grab a bunch of events. */ static void eventhandler(struct video_desc *env, const char *caption) { struct gui_info *gui = env->gui; struct drag_info *drag; #define N_EVENTS 32 int i, n; SDL_Event ev[N_EVENTS]; if (!gui) return; drag = &gui->drag; if (caption) SDL_WM_SetCaption(caption, NULL); #define MY_EV (SDL_MOUSEBUTTONDOWN|SDL_KEYDOWN) while ( (n = SDL_PeepEvents(ev, N_EVENTS, SDL_GETEVENT, SDL_ALLEVENTS)) > 0) { for (i = 0; i < n; i++) { #if 0 ast_log(LOG_WARNING, "------ event %d at %d %d\n", ev[i].type, ev[i].button.x, ev[i].button.y); #endif switch (ev[i].type) { default: ast_log(LOG_WARNING, "------ event %d at %d %d\n", ev[i].type, ev[i].button.x, ev[i].button.y); break; case SDL_ACTIVEEVENT: #if 0 /* do not react, we don't want to die because the window is minimized */ if (ev[i].active.gain == 0 && ev[i].active.state & SDL_APPACTIVE) { ast_log(LOG_WARNING, "/* somebody has killed us ? */\n"); ast_cli_command(gui->outfd, "stop now"); } #endif break; case SDL_KEYUP: /* ignore, for the time being */ break; case SDL_KEYDOWN: handle_keyboard_input(env, &ev[i].key.keysym); break; case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONUP: if (drag->drag_window == DRAG_LOCAL && env->out.device_num) { /* move the capture source */ int dx = compute_drag(&drag->x_start, ev[i].motion.x, 3); int dy = compute_drag(&drag->y_start, ev[i].motion.y, 3); grabber_move(&env->out.devices[env->out.device_primary], dx, dy); } else if (drag->drag_window == DRAG_PIP) { /* move the PiP image inside the frames of the enc_in buffers */ int dx = ev[i].motion.x - drag->x_start; int dy = ev[i].motion.y - drag->y_start; /* dx and dy value are directly applied to env->out.pip_x and env->out.pip_y, so they must work as if the format was cif */ dx = (double)dx*env->enc_in.w/env->loc_dpy.w; dy = (double)dy*env->enc_in.h/env->loc_dpy.h; /* sets starts to a new value */ drag->x_start = ev[i].motion.x; drag->y_start = ev[i].motion.y; /* ast_log(LOG_WARNING, "moving: %d, %d\n", dx, dy); */ pip_move(env, dx, dy); } else if (drag->drag_window == DRAG_MESSAGE) { /* scroll up/down the window */ int dy = compute_drag(&drag->y_start, ev[i].motion.y, 1); move_message_board(gui->bd_msg, dy); } if (ev[i].type == SDL_MOUSEBUTTONUP) drag->drag_window = DRAG_NONE; break; case SDL_MOUSEBUTTONDOWN: handle_mousedown(env, ev[i].button); break; } } } if (1) { struct timeval b, a = ast_tvnow(); int i; //SDL_Lock_EventThread(); SDL_PumpEvents(); b = ast_tvnow(); i = ast_tvdiff_ms(b, a); if (i > 3) fprintf(stderr, "-------- SDL_PumpEvents took %dms\n", i); //SDL_Unlock_EventThread(); } } static SDL_Surface *load_image(const char *file) { SDL_Surface *temp; #ifdef HAVE_SDL_IMAGE temp = IMG_Load(file); #else temp = SDL_LoadBMP(file); #endif if (temp == NULL) fprintf(stderr, "Unable to load image %s: %s\n", file, SDL_GetError()); return temp; } static void keypad_setup(struct gui_info *gui, const char *kp_file); /* TODO: consistency checks, check for bpp, widht and height */ /* Init the mask image used to grab the action. */ static struct gui_info *gui_init(const char *keypad_file, const char *font) { struct gui_info *gui = ast_calloc(1, sizeof(*gui)); if (gui == NULL) return NULL; /* initialize keypad status */ gui->kb_output = KO_MESSAGE; /* XXX temp */ gui->drag.drag_window = DRAG_NONE; gui->outfd = -1; keypad_setup(gui, keypad_file); if (gui->keypad == NULL) /* no keypad, we are done */ return gui; /* XXX load image */ if (!ast_strlen_zero(font)) { int i; SDL_Rect *r; gui->font = load_image(font); if (!gui->font) { ast_log(LOG_WARNING, "Unable to load font %s, no output available\n", font); goto error; } ast_log(LOG_WARNING, "Loaded font %s\n", font); /* XXX hardwired constants - 3 rows of 32 chars */ r = gui->font_rects; #define FONT_H 20 #define FONT_W 9 for (i = 0; i < 96; r++, i++) { r->x = (i % 32 ) * FONT_W; r->y = (i / 32 ) * FONT_H; r->w = FONT_W; r->h = FONT_H; } } gui->outfd = open ("/dev/null", O_WRONLY); /* discard output, temporary */ if (gui->outfd < 0) { ast_log(LOG_WARNING, "Unable output fd\n"); goto error; } return gui; error: ast_free(gui); return NULL; } /* setup an sdl overlay and associated info, return 0 on success, != 0 on error */ static int set_win(SDL_Surface *screen, struct display_window *win, int fmt, int w, int h, int x, int y) { win->bmp = SDL_CreateYUVOverlay(w, h, fmt, screen); if (win->bmp == NULL) return -1; /* error */ win->rect.x = x; win->rect.y = y; win->rect.w = w; win->rect.h = h; return 0; } static int keypad_cfg_read(struct gui_info *gui, const char *val); static void keypad_setup(struct gui_info *gui, const char *kp_file) { FILE *fd; char buf[1024]; const char region[] = "region"; int reg_len = strlen(region); int in_comment = 0; if (gui->keypad) return; gui->keypad = load_image(kp_file); if (!gui->keypad) return; /* now try to read the keymap from the file. */ fd = fopen(kp_file, "r"); if (fd == NULL) { ast_log(LOG_WARNING, "fail to open %s\n", kp_file); return; } /* * If the keypad image has a comment field, try to read * the button location from there. The block must start with * a comment (or empty) line, and continue with entries like: * region = token shape x0 y0 x1 y1 h * ... * (basically, lines have the same format as config file entries). * You can add it to a jpeg file using wrjpgcom */ while (fgets(buf, sizeof(buf), fd)) { char *s; if (!strstr(buf, region)) { /* no keyword yet */ if (!in_comment) /* still waiting for initial comment block */ continue; else break; } if (!in_comment) { /* first keyword, reset previous entries */ keypad_cfg_read(gui, "reset"); in_comment = 1; } s = ast_skip_blanks(buf); ast_trim_blanks(s); if (memcmp(s, region, reg_len)) break; /* keyword not found */ s = ast_skip_blanks(s + reg_len); /* space between token and '=' */ if (*s++ != '=') /* missing separator */ break; if (*s == '>') /* skip '>' if present */ s++; keypad_cfg_read(gui, ast_skip_blanks(s)); } fclose(fd); } struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest, SDL_Surface *font, SDL_Rect *font_rects); /*! \brief initialize the boards we have in the keypad */ static void init_board(struct gui_info *gui, struct board **dst, SDL_Rect *r, int dx, int dy) { if (r[0].w == 0 || r[0].h == 0) return; /* not available */ r[1] = r[0]; /* copy geometry */ r[1].x += dx; /* add offset of main window */ r[1].y += dy; if (*dst == NULL) { /* initial call */ *dst = board_setup(gui->screen, &r[1], gui->font, gui->font_rects); } else { /* call a refresh */ } } #ifdef HAVE_X11 /* * SDL is not very robust on error handling, so we need to trap ourselves * at least the most obvious failure conditions, e.g. a bad SDL_WINDOWID. * As of sdl-1.2.13, SDL_SetVideoMode crashes with bad parameters, so * we need to do the explicit X calls to make sure the window is correct. * And around these calls, we must trap X errors. */ static int my_x_handler(Display *d, XErrorEvent *e) { ast_log(LOG_WARNING, "%s error_code %d\n", __FUNCTION__, e->error_code); return 0; } #endif /* HAVE_X11 */ /*! \brief [re]set the main sdl window, useful in case of resize. * We can tell the first from subsequent calls from the value of * env->gui, which is NULL the first time. */ static void sdl_setup(struct video_desc *env) { int dpy_fmt = SDL_IYUV_OVERLAY; /* YV12 causes flicker in SDL */ int depth, maxw, maxh; const SDL_VideoInfo *info; int kp_w = 0, kp_h = 0; /* keypad width and height */ struct gui_info *gui = env->gui; /* Some helper variables used for filling the SDL window */ int x0; /* the x coordinate of the center of the keypad */ int x1; /* userful for calculating of the size of the parent window */ int y0; /* y coordinate of the keypad, the remote window and the local window */ int src_wins_tot_w; /* total width of the source windows */ int i; int x; /* useful for the creation of the source windows; */ #ifdef HAVE_X11 const char *e = getenv("SDL_WINDOWID"); if (!ast_strlen_zero(e)) { XWindowAttributes a; int (*old_x_handler)(Display *d, XErrorEvent *e) = XSetErrorHandler(my_x_handler); Display *d = XOpenDisplay(getenv("DISPLAY")); long w = atol(e); int success = w ? XGetWindowAttributes(d, w, &a) : 0; XSetErrorHandler(old_x_handler); if (!success) { ast_log(LOG_WARNING, "%s error in window\n", __FUNCTION__); return; } } #endif /* * initialize the SDL environment. We have one large window * with local and remote video, and a keypad. * At the moment we arrange them statically, as follows: * - top row: thumbnails for local video sources; * - next row: message boards for local video sources * - on the left, the remote video; * - on the center, the keypad * - on the right, the local video * We need to read in the skin for the keypad before creating the main * SDL window, because the size is only known here. */ if (gui == NULL && SDL_Init(SDL_INIT_VIDEO)) { ast_log(LOG_WARNING, "Could not initialize SDL - %s\n", SDL_GetError()); /* again not fatal, just we won't display anything */ return; } info = SDL_GetVideoInfo(); /* We want at least 16bpp to support YUV overlays. * E.g with SDL_VIDEODRIVER = aalib the default is 8 */ if (!info || !info->vfmt) { ast_log(LOG_WARNING, "Bad SDL_GetVideoInfo - %s\n", SDL_GetError()); return; } depth = info->vfmt->BitsPerPixel; if (depth < 16) depth = 16; if (!gui) env->gui = gui = gui_init(env->keypad_file, env->keypad_font); if (!gui) goto no_sdl; if (gui->keypad) { if (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) { kp_w = gui->kp_rect.w; kp_h = gui->kp_rect.h; } else { kp_w = gui->keypad->w; kp_h = gui->keypad->h; } } /* total width of the thumbnails */ src_wins_tot_w = env->out.device_num*(SRC_WIN_W+BORDER)+BORDER; /* x coordinate of the center of the keypad */ x0 = MAX(env->rem_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2); /* from center of the keypad to right border */ x1 = MAX(env->loc_dpy.w+kp_w/2+2*BORDER, src_wins_tot_w/2); /* total width of the SDL window to create */ maxw = x0+x1; /* total height of the mother window to create */ maxh = MAX( MAX(env->rem_dpy.h, env->loc_dpy.h), kp_h)+2*BORDER; maxh += env->out.device_num ? (2*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : 0; gui->screen = SDL_SetVideoMode(maxw, maxh, depth, 0); if (!gui->screen) { ast_log(LOG_ERROR, "SDL: could not set video mode - exiting\n"); goto no_sdl; } #ifdef HAVE_X11 /* * Annoying as it may be, if SDL_WINDOWID is set, SDL does * not grab keyboard/mouse events or expose or other stuff, * and it does not handle resize either. * So we need to implement workarounds here. */ do { /* First, handle the event mask */ XWindowAttributes attr; long want; SDL_SysWMinfo info; Display *SDL_Display; Window win; const char *e = getenv("SDL_WINDOWID"); if (ast_strlen_zero(e)) /* no external window, don't bother doing this */ break; SDL_VERSION(&info.version); /* it is important to set the version */ if (SDL_GetWMInfo(&info) != 1) { fprintf(stderr, "no wm info\n"); break; } SDL_Display = info.info.x11.display; if (SDL_Display == NULL) break; win = info.info.x11.window; /* * A list of events we want. * Leave ResizeRedirectMask to the parent. */ want = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask | KeymapStateMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | /* ResizeRedirectMask | */ SubstructureNotifyMask | SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask | ColormapChangeMask | OwnerGrabButtonMask; memset(&attr, '\0', sizeof(attr)); XGetWindowAttributes(SDL_Display, win, &attr); /* the following events can be delivered only to one client. * So check which ones are going to someone else, and drop * them from our request. */ { /* ev are the events for a single recipient */ long ev = ButtonPressMask | ResizeRedirectMask | SubstructureRedirectMask; ev &= (attr.all_event_masks & ~attr.your_event_mask); /* now ev contains 1 for single-recipient events owned by others. * We must clear those bits in 'want' * and then add the bits in 'attr.your_event_mask' to 'want' */ want &= ~ev; want |= attr.your_event_mask; } XSelectInput(SDL_Display, win, want); /* Second, handle resize. * We do part of the things that X11Resize does, * but also generate a ConfigureNotify event so * the owner of the window has a chance to do something * with it. */ XResizeWindow(SDL_Display, win, maxw, maxh); { XConfigureEvent ce = { .type = ConfigureNotify, .serial = 0, .send_event = 1, /* TRUE */ .display = SDL_Display, .event = win, .window = win, .x = 0, .y = 0, .width = maxw, .height = maxh, .border_width = 0, .above = 0, .override_redirect = 0 }; XSendEvent(SDL_Display, win, 1 /* TRUE */, StructureNotifyMask, (XEvent *)&ce); } } while (0); #endif /* HAVE_X11 */ y0 = env->out.device_num ? (3*BORDER+SRC_WIN_H+SRC_MSG_BD_H) : BORDER; SDL_WM_SetCaption("Asterisk console Video Output", NULL); /* intialize the windows for local and remote video */ if (set_win(gui->screen, &gui->win[WIN_REMOTE], dpy_fmt, env->rem_dpy.w, env->rem_dpy.h, x0-kp_w/2-BORDER-env->rem_dpy.w, y0)) goto no_sdl; /* unfreeze incoming frames if set (to avoid showing nothing) */ env->frame_freeze = 0; if (set_win(gui->screen, &gui->win[WIN_LOCAL], dpy_fmt, env->loc_dpy.w, env->loc_dpy.h, x0+kp_w/2+BORDER, y0)) goto no_sdl; /* initialize device_num source windows (thumbnails) and boards (for a maximum of 9 additional windows and boards) */ x = x0 - src_wins_tot_w/2 + BORDER; for (i = 0; i < env->out.device_num; i++){ struct thumb_bd *p = &gui->thumb_bd_array[i]; if (set_win(gui->screen, &gui->win[i+WIN_SRC1], dpy_fmt, SRC_WIN_W, SRC_WIN_H, x+i*(BORDER+SRC_WIN_W), BORDER)) goto no_sdl; /* set geometry for the rect for the message board of the device */ p->rect.w = SRC_WIN_W; p->rect.h = SRC_MSG_BD_H; p->rect.x = x+i*(BORDER+SRC_WIN_W); p->rect.y = 2*BORDER+SRC_WIN_H; /* the white color is used as background */ SDL_FillRect(gui->screen, &p->rect, SDL_MapRGB(gui->screen->format, 255, 255, 255)); /* if necessary, initialize boards for the sources */ if (!p->board) p->board = board_setup(gui->screen, &p->rect, gui->font, gui->font_rects); /* update board rect */ SDL_UpdateRect(gui->screen, p->rect.x, p->rect.y, p->rect.w, p->rect.h); } /* display the skin, but do not free it as we need it later to restore text areas and maybe sliders too */ if (gui->keypad) { struct SDL_Rect *dest = &gui->win[WIN_KEYPAD].rect; struct SDL_Rect *src = (gui->kp_rect.w > 0 && gui->kp_rect.h > 0) ? & gui->kp_rect : NULL; /* set the coordinates of the keypad relative to the main screen */ dest->x = x0-kp_w/2; dest->y = y0; dest->w = kp_w; dest->h = kp_h; SDL_BlitSurface(gui->keypad, src, gui->screen, dest); init_board(gui, &gui->bd_msg, gui->kp_msg, dest->x, dest->y); init_board(gui, &gui->bd_dialed, gui->kp_dialed, dest->x, dest->y); SDL_UpdateRects(gui->screen, 1, dest); } return; no_sdl: /* free resources in case of errors */ env->gui = cleanup_sdl(gui, env->out.device_num); } /* * Functions to determine if a point is within a region. Return 1 if success. * First rotate the point, with * x' = (x - x0) * cos A + (y - y0) * sin A * y' = -(x - x0) * sin A + (y - y0) * cos A * where cos A = (x1-x0)/l, sin A = (y1 - y0)/l, and * l = sqrt( (x1-x0)^2 + (y1-y0)^2 * Then determine inclusion by simple comparisons i.e.: * rectangle: x >= 0 && x < l && y >= 0 && y < h * ellipse: (x-xc)^2/l^2 + (y-yc)^2/h2 < 1 */ static int kp_match_area(const struct keypad_entry *e, int x, int y) { double xp, dx = (e->x1 - e->x0); double yp, dy = (e->y1 - e->y0); double l = sqrt(dx*dx + dy*dy); int ret = 0; if (l > 1) { /* large enough */ xp = ((x - e->x0)*dx + (y - e->y0)*dy)/l; yp = (-(x - e->x0)*dy + (y - e->y0)*dx)/l; if (e->type == KP_RECT) { ret = (xp >= 0 && xp < l && yp >=0 && yp < e->h); } else if (e->type == KP_CIRCLE) { dx = xp*xp/(l*l) + yp*yp/(e->h*e->h); ret = (dx < 1); } } #if 0 ast_log(LOG_WARNING, "result %d [%d] for match %d,%d in type %d p0 %d,%d p1 %d,%d h %d\n", ret, e->c, x, y, e->type, e->x0, e->y0, e->x1, e->y1, e->h); #endif return ret; } struct _s_k { const char *s; int k; }; static const struct _s_k gui_key_map[] = { {"FREEZE", KEY_FREEZE}, {"PIP", KEY_PIP}, {"PICK_UP", KEY_PICK_UP }, {"PICKUP", KEY_PICK_UP }, {"HANG_UP", KEY_HANG_UP }, {"HANGUP", KEY_HANG_UP }, {"MUTE", KEY_MUTE }, {"FLASH", KEY_FLASH }, {"AUTOANSWER", KEY_AUTOANSWER }, {"SENDVIDEO", KEY_SENDVIDEO }, {"LOCALVIDEO", KEY_LOCALVIDEO }, {"REMOTEVIDEO", KEY_REMOTEVIDEO }, {"GUI_CLOSE", KEY_GUI_CLOSE }, {"MESSAGEBOARD", KEY_MESSAGEBOARD }, {"DIALEDBOARD", KEY_DIALEDBOARD }, {"EDITBOARD", KEY_EDITBOARD }, {"KEYPAD", KEY_KEYPAD }, /* x0 y0 w h - active area of the keypad */ {"MESSAGE", KEY_MESSAGE }, /* x0 y0 w h - incoming messages */ {"DIALED", KEY_DIALED }, /* x0 y0 w h - dialed number */ {"EDIT", KEY_EDIT }, /* x0 y0 w h - edit user input */ {"FONT", KEY_FONT }, /* x0 yo w h rows cols - location and format of the font */ {NULL, 0 } }; static int gui_map_token(const char *s) { /* map the string into token to be returned */ int i = atoi(s); struct _s_k *p; if (i > 0 || s[1] == '\0') /* numbers or single characters */ return (i > 9) ? i : s[0]; for (p = gui_key_map; p->s; p++) { if (!strcasecmp(p->s, s)) return p->k; } return KEY_NONE; /* not found */ } /*! \brief read a keypad entry line in the format * reset * token circle xc yc diameter * token circle xc yc x1 y1 h # ellipse, main diameter and height * token rect x0 y0 x1 y1 h # rectangle with main side and eight * token x0 y0 w h # horizontal rectangle (short format) * # this is used e.g. for message boards * token is the token to be returned, either a character or a symbol * as KEY_* above * Return 1 on success, 0 on error. */ static int keypad_cfg_read(struct gui_info *gui, const char *val) { struct keypad_entry e; SDL_Rect *r = NULL; char s1[16], s2[16]; int i, ret = 0; /* default, error */ if (gui == NULL || val == NULL) return 0; s1[0] = s2[0] = '\0'; memset(&e, '\0', sizeof(e)); i = sscanf(val, "%14s %14s %d %d %d %d %d", s1, s2, &e.x0, &e.y0, &e.x1, &e.y1, &e.h); e.c = gui_map_token(s1); if (e.c == KEY_NONE) return 0; /* nothing found */ switch (i) { default: break; case 1: /* only "reset" is allowed */ if (e.c != KEY_RESET) break; if (gui->kp) gui->kp_used = 0; break; case 5: if (e.c == KEY_KEYPAD) /* active keypad area */ r = &gui->kp_rect; else if (e.c == KEY_MESSAGE) r = gui->kp_msg; else if (e.c == KEY_DIALED) r = gui->kp_dialed; else if (e.c == KEY_EDIT) r = gui->kp_edit; if (r) { r->x = atoi(s2); /* this becomes x0 */ r->y = e.x0; /* this becomes y0 */ r->w = e.y0; /* this becomes w */ r->h = e.x1; /* this becomes h */ break; } if (strcasecmp(s2, "circle")) /* invalid */ break; /* token circle xc yc diameter */ e.h = e.x1; e.y1 = e.y0; /* map radius in x1 y1 */ e.x1 = e.x0 + e.h; /* map radius in x1 y1 */ e.x0 = e.x0 - e.h; /* map radius in x1 y1 */ /* fallthrough */ case 7: if (e.c == KEY_FONT) { /* font - x0 y0 w h rows cols */ ast_log(LOG_WARNING, "font not supported yet\n"); break; } /* token circle|rect x0 y0 x1 y1 h */ if (e.x1 < e.x0 || e.h <= 0) { ast_log(LOG_WARNING, "error in coordinates\n"); e.type = 0; break; } if (!strcasecmp(s2, "circle")) { /* for a circle we specify the diameter but store center and radii */ e.type = KP_CIRCLE; e.x0 = (e.x1 + e.x0) / 2; e.y0 = (e.y1 + e.y0) / 2; e.h = e.h / 2; } else if (!strcasecmp(s2, "rect")) { e.type = KP_RECT; } else break; ret = 1; } // ast_log(LOG_WARNING, "reading [%s] returns %d %d\n", val, i, ret); if (ret == 0) return 0; if (gui->kp_size == 0) { gui->kp = ast_calloc(10, sizeof(e)); if (gui->kp == NULL) { ast_log(LOG_WARNING, "cannot allocate kp\n"); return 0; } gui->kp_size = 10; } if (gui->kp_size == gui->kp_used) { /* must allocate */ struct keypad_entry *a = ast_realloc(gui->kp, sizeof(e)*(gui->kp_size+10)); if (a == NULL) { ast_log(LOG_WARNING, "cannot reallocate kp\n"); return 0; } gui->kp = a; gui->kp_size += 10; } if (gui->kp_size == gui->kp_used) return 0; gui->kp[gui->kp_used++] = e; // ast_log(LOG_WARNING, "now %d regions\n", gui->kp_used); return 1; } #endif /* HAVE_SDL */ asterisk-13.1.0/channels/chan_skinny.c0000644000000000000000000104542712437125770016407 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2005, Digium, Inc. * * chan_skinny was developed by Jeremy McNamara & Florian Overkamp * chan_skinny was heavily modified/fixed by North Antara * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \brief Implementation of the Skinny protocol * * \author Jeremy McNamara & Florian Overkamp & North Antara * \ingroup channel_drivers */ /*! \li \ref chan_skinny.c uses the configuration file \ref skinny.conf * \addtogroup configuration_file */ /*! \page skinny.conf skinny.conf * \verbinclude skinny.conf.sample */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 428687 $") #include #include #include #include #include #include #include #include #include #include #include #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/sched.h" #include "asterisk/io.h" #include "asterisk/rtp_engine.h" #include "asterisk/netsock2.h" #include "asterisk/acl.h" #include "asterisk/callerid.h" #include "asterisk/cli.h" #include "asterisk/manager.h" #include "asterisk/say.h" #include "asterisk/astdb.h" #include "asterisk/causes.h" #include "asterisk/pickup.h" #include "asterisk/app.h" #include "asterisk/musiconhold.h" #include "asterisk/utils.h" #include "asterisk/dsp.h" #include "asterisk/stringfields.h" #include "asterisk/abstract_jb.h" #include "asterisk/threadstorage.h" #include "asterisk/devicestate.h" #include "asterisk/indications.h" #include "asterisk/linkedlists.h" #include "asterisk/stasis_endpoints.h" #include "asterisk/bridge.h" #include "asterisk/parking.h" #include "asterisk/stasis_channels.h" #include "asterisk/format_cache.h" /*** DOCUMENTATION List SKINNY devices (text format). Lists Skinny devices in text format with details on current status. Devicelist will follow as separate events, followed by a final event called DevicelistComplete. Show SKINNY device (text format). The device name you want to check. Show one SKINNY device with details on current status. List SKINNY lines (text format). Lists Skinny lines in text format with details on current status. Linelist will follow as separate events, followed by a final event called LinelistComplete. Show SKINNY line (text format). The line name you want to check. Show one SKINNY line with details on current status. ***/ /* Skinny debugging only available if asterisk configured with --enable-dev-mode */ #ifdef AST_DEVMODE static int skinnydebug = 0; char dbgcli_buf[256]; #define DEBUG_GENERAL (1 << 1) #define DEBUG_SUB (1 << 2) #define DEBUG_PACKET (1 << 3) #define DEBUG_AUDIO (1 << 4) #define DEBUG_LOCK (1 << 5) #define DEBUG_TEMPLATE (1 << 6) #define DEBUG_THREAD (1 << 7) #define DEBUG_HINT (1 << 8) #define DEBUG_KEEPALIVE (1 << 9) #define SKINNY_DEBUG(type, verb_level, text, ...) \ do{ \ if (skinnydebug & (type)) { \ ast_verb(verb_level, "[%d] " text, ast_get_tid(), ##__VA_ARGS__); \ } \ }while(0) #else #define SKINNY_DEBUG(type, verb_level, text, ...) #endif /************************************* * Skinny/Asterisk Protocol Settings * *************************************/ static const char tdesc[] = "Skinny Client Control Protocol (Skinny)"; static const char config[] = "skinny.conf"; static struct ast_format_cap *default_cap; enum skinny_codecs { SKINNY_CODEC_ALAW = 2, SKINNY_CODEC_ULAW = 4, SKINNY_CODEC_G722 = 6, SKINNY_CODEC_G723_1 = 9, SKINNY_CODEC_G729A = 12, SKINNY_CODEC_G726_32 = 82, /* XXX Which packing order does this translate to? */ SKINNY_CODEC_H261 = 100, SKINNY_CODEC_H263 = 101 }; #define DEFAULT_SKINNY_PORT 2000 #define DEFAULT_SKINNY_BACKLOG 2 #define SKINNY_MAX_PACKET 2000 #define DEFAULT_AUTH_TIMEOUT 30 #define DEFAULT_AUTH_LIMIT 50 static struct { unsigned int tos; unsigned int tos_audio; unsigned int tos_video; unsigned int cos; unsigned int cos_audio; unsigned int cos_video; } qos = { 0, 0, 0, 0, 0, 0 }; static int keep_alive = 120; static int auth_timeout = DEFAULT_AUTH_TIMEOUT; static int auth_limit = DEFAULT_AUTH_LIMIT; static int unauth_sessions = 0; static char immed_dialchar; static char vmexten[AST_MAX_EXTENSION]; /* Voicemail pilot number */ static char used_context[AST_MAX_EXTENSION]; /* placeholder to check if context are already used in regcontext */ static char regcontext[AST_MAX_CONTEXT]; /* Context for auto-extension */ static char date_format[6] = "D-M-Y"; static char version_id[16] = "P002F202"; #if __BYTE_ORDER == __LITTLE_ENDIAN #define letohl(x) (x) #define letohs(x) (x) #define htolel(x) (x) #define htoles(x) (x) #else #if defined(HAVE_BYTESWAP_H) #include #define letohl(x) bswap_32(x) #define letohs(x) bswap_16(x) #define htolel(x) bswap_32(x) #define htoles(x) bswap_16(x) #elif defined(HAVE_SYS_ENDIAN_SWAP16) #include #define letohl(x) __swap32(x) #define letohs(x) __swap16(x) #define htolel(x) __swap32(x) #define htoles(x) __swap16(x) #elif defined(HAVE_SYS_ENDIAN_BSWAP16) #include #define letohl(x) bswap32(x) #define letohs(x) bswap16(x) #define htolel(x) bswap32(x) #define htoles(x) bswap16(x) #else #define __bswap_16(x) \ ((((x) & 0xff00) >> 8) | \ (((x) & 0x00ff) << 8)) #define __bswap_32(x) \ ((((x) & 0xff000000) >> 24) | \ (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | \ (((x) & 0x000000ff) << 24)) #define letohl(x) __bswap_32(x) #define letohs(x) __bswap_16(x) #define htolel(x) __bswap_32(x) #define htoles(x) __bswap_16(x) #endif #endif /*! Global jitterbuffer configuration - by default, jb is disabled * \note Values shown here match the defaults shown in skinny.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; #ifdef AST_DEVMODE AST_THREADSTORAGE(message2str_threadbuf); #define MESSAGE2STR_BUFSIZE 35 #endif AST_THREADSTORAGE(device2str_threadbuf); #define DEVICE2STR_BUFSIZE 15 AST_THREADSTORAGE(control2str_threadbuf); #define CONTROL2STR_BUFSIZE 100 AST_THREADSTORAGE(substate2str_threadbuf); #define SUBSTATE2STR_BUFSIZE 15 AST_THREADSTORAGE(callstate2str_threadbuf); #define CALLSTATE2STR_BUFSIZE 15 /********************* * Protocol Messages * *********************/ /* message types */ #define KEEP_ALIVE_MESSAGE 0x0000 /* no additional struct */ #define REGISTER_MESSAGE 0x0001 struct register_message { char name[16]; uint32_t userId; uint32_t instance; uint32_t ip; uint32_t type; uint32_t maxStreams; uint32_t space; uint8_t protocolVersion; /*! \brief space2 is used for newer version of skinny */ char space2[3]; }; #define IP_PORT_MESSAGE 0x0002 #define KEYPAD_BUTTON_MESSAGE 0x0003 struct keypad_button_message { uint32_t button; uint32_t lineInstance; uint32_t callReference; }; #define ENBLOC_CALL_MESSAGE 0x0004 struct enbloc_call_message { char calledParty[24]; }; #define STIMULUS_MESSAGE 0x0005 struct stimulus_message { uint32_t stimulus; uint32_t stimulusInstance; uint32_t callreference; }; #define OFFHOOK_MESSAGE 0x0006 struct offhook_message { uint32_t instance; uint32_t reference; }; #define ONHOOK_MESSAGE 0x0007 struct onhook_message { uint32_t instance; uint32_t reference; }; #define CAPABILITIES_RES_MESSAGE 0x0010 struct station_capabilities { uint32_t codec; /* skinny codec, not ast codec */ uint32_t frames; union { char res[8]; uint32_t rate; } payloads; }; #define SKINNY_MAX_CAPABILITIES 18 struct capabilities_res_message { uint32_t count; struct station_capabilities caps[SKINNY_MAX_CAPABILITIES]; }; #define SPEED_DIAL_STAT_REQ_MESSAGE 0x000A struct speed_dial_stat_req_message { uint32_t speedDialNumber; }; #define LINE_STATE_REQ_MESSAGE 0x000B struct line_state_req_message { uint32_t lineNumber; }; #define TIME_DATE_REQ_MESSAGE 0x000D #define BUTTON_TEMPLATE_REQ_MESSAGE 0x000E #define VERSION_REQ_MESSAGE 0x000F #define SERVER_REQUEST_MESSAGE 0x0012 #define ALARM_MESSAGE 0x0020 struct alarm_message { uint32_t alarmSeverity; char displayMessage[80]; uint32_t alarmParam1; uint32_t alarmParam2; }; #define OPEN_RECEIVE_CHANNEL_ACK_MESSAGE 0x0022 struct open_receive_channel_ack_message_ip4 { uint32_t status; uint32_t ipAddr; uint32_t port; uint32_t callReference; }; struct open_receive_channel_ack_message_ip6 { uint32_t status; uint32_t space; char ipAddr[16]; uint32_t port; uint32_t callReference; }; #define SOFT_KEY_SET_REQ_MESSAGE 0x0025 #define SOFT_KEY_EVENT_MESSAGE 0x0026 struct soft_key_event_message { uint32_t softKeyEvent; uint32_t instance; uint32_t callreference; }; #define UNREGISTER_MESSAGE 0x0027 #define SOFT_KEY_TEMPLATE_REQ_MESSAGE 0x0028 #define HEADSET_STATUS_MESSAGE 0x002B #define REGISTER_AVAILABLE_LINES_MESSAGE 0x002D #define SERVICEURL_STATREQ_MESSAGE 0x0033 struct serviceurl_statreq_message { uint32_t instance; }; #define REGISTER_ACK_MESSAGE 0x0081 struct register_ack_message { uint32_t keepAlive; char dateTemplate[6]; char res[2]; uint32_t secondaryKeepAlive; char res2[4]; }; #define START_TONE_MESSAGE 0x0082 struct start_tone_message { uint32_t tone; uint32_t space; uint32_t instance; uint32_t reference; }; #define STOP_TONE_MESSAGE 0x0083 struct stop_tone_message { uint32_t instance; uint32_t reference; uint32_t space; }; #define SET_RINGER_MESSAGE 0x0085 struct set_ringer_message { uint32_t ringerMode; uint32_t unknown1; /* See notes in transmit_ringer_mode */ uint32_t unknown2; uint32_t space[2]; }; #define SET_LAMP_MESSAGE 0x0086 struct set_lamp_message { uint32_t stimulus; uint32_t stimulusInstance; uint32_t deviceStimulus; }; #define SET_SPEAKER_MESSAGE 0x0088 struct set_speaker_message { uint32_t mode; }; /* XXX When do we need to use this? */ #define SET_MICROPHONE_MESSAGE 0x0089 struct set_microphone_message { uint32_t mode; }; #define START_MEDIA_TRANSMISSION_MESSAGE 0x008A struct media_qualifier { uint32_t precedence; uint32_t vad; uint32_t packets; uint32_t bitRate; }; struct start_media_transmission_message_ip4 { uint32_t conferenceId; uint32_t passThruPartyId; uint32_t remoteIp; uint32_t remotePort; uint32_t packetSize; uint32_t payloadType; struct media_qualifier qualifier; uint32_t space[19]; }; struct start_media_transmission_message_ip6 { uint32_t conferenceId; uint32_t passThruPartyId; uint32_t space; char remoteIp[16]; uint32_t remotePort; uint32_t packetSize; uint32_t payloadType; struct media_qualifier qualifier; /*! \brief space2 is used for newer version of skinny */ uint32_t space2[19]; }; #define STOP_MEDIA_TRANSMISSION_MESSAGE 0x008B struct stop_media_transmission_message { uint32_t conferenceId; uint32_t passThruPartyId; uint32_t space[3]; }; #define CALL_INFO_MESSAGE 0x008F struct call_info_message { char callingPartyName[40]; char callingParty[24]; char calledPartyName[40]; char calledParty[24]; uint32_t instance; uint32_t reference; uint32_t type; char originalCalledPartyName[40]; char originalCalledParty[24]; char lastRedirectingPartyName[40]; char lastRedirectingParty[24]; uint32_t originalCalledPartyRedirectReason; uint32_t lastRedirectingReason; char callingPartyVoiceMailbox[24]; char calledPartyVoiceMailbox[24]; char originalCalledPartyVoiceMailbox[24]; char lastRedirectingVoiceMailbox[24]; uint32_t space[3]; }; #define FORWARD_STAT_MESSAGE 0x0090 struct forward_stat_message { uint32_t activeforward; uint32_t lineNumber; uint32_t fwdall; char fwdallnum[24]; uint32_t fwdbusy; char fwdbusynum[24]; uint32_t fwdnoanswer; char fwdnoanswernum[24]; }; #define SPEED_DIAL_STAT_RES_MESSAGE 0x0091 struct speed_dial_stat_res_message { uint32_t speedDialNumber; char speedDialDirNumber[24]; char speedDialDisplayName[40]; }; #define LINE_STAT_RES_MESSAGE 0x0092 struct line_stat_res_message { uint32_t lineNumber; char lineDirNumber[24]; char lineDisplayName[24]; uint32_t space[15]; }; #define DEFINETIMEDATE_MESSAGE 0x0094 struct definetimedate_message { uint32_t year; /* since 1900 */ uint32_t month; uint32_t dayofweek; /* monday = 1 */ uint32_t day; uint32_t hour; uint32_t minute; uint32_t seconds; uint32_t milliseconds; uint32_t timestamp; }; #define BUTTON_TEMPLATE_RES_MESSAGE 0x0097 struct button_definition { uint8_t instanceNumber; uint8_t buttonDefinition; }; struct button_definition_template { uint8_t buttonDefinition; /* for now, anything between 0xB0 and 0xCF is custom */ /*int custom;*/ }; #define STIMULUS_REDIAL 0x01 #define STIMULUS_SPEEDDIAL 0x02 #define STIMULUS_HOLD 0x03 #define STIMULUS_TRANSFER 0x04 #define STIMULUS_FORWARDALL 0x05 #define STIMULUS_FORWARDBUSY 0x06 #define STIMULUS_FORWARDNOANSWER 0x07 #define STIMULUS_DISPLAY 0x08 #define STIMULUS_LINE 0x09 #define STIMULUS_VOICEMAIL 0x0F #define STIMULUS_AUTOANSWER 0x11 #define STIMULUS_SERVICEURL 0x14 #define STIMULUS_DND 0x3F #define STIMULUS_CONFERENCE 0x7D #define STIMULUS_CALLPARK 0x7E #define STIMULUS_CALLPICKUP 0x7F #define STIMULUS_NONE 0xFF /* Button types */ #define BT_REDIAL STIMULUS_REDIAL #define BT_SPEEDDIAL STIMULUS_SPEEDDIAL #define BT_HOLD STIMULUS_HOLD #define BT_TRANSFER STIMULUS_TRANSFER #define BT_FORWARDALL STIMULUS_FORWARDALL #define BT_FORWARDBUSY STIMULUS_FORWARDBUSY #define BT_FORWARDNOANSWER STIMULUS_FORWARDNOANSWER #define BT_DISPLAY STIMULUS_DISPLAY #define BT_LINE STIMULUS_LINE #define BT_VOICEMAIL STIMULUS_VOICEMAIL #define BT_AUTOANSWER STIMULUS_AUTOANSWER #define BT_SERVICEURL STIMULUS_SERVICEURL #define BT_DND STIMULUS_DND #define BT_CONFERENCE STIMULUS_CONFERENCE #define BT_CALLPARK STIMULUS_CALLPARK #define BT_CALLPICKUP STIMULUS_CALLPICKUP #define BT_NONE 0x00 /* Custom button types - add our own between 0xB0 and 0xCF. This may need to be revised in the future, if stimuluses are ever added in this range. */ #define BT_CUST_LINESPEEDDIAL 0xB0 /* line or speeddial with/without hint */ #define BT_CUST_LINE 0xB1 /* line or speeddial with hint only */ struct button_template_res_message { uint32_t buttonOffset; uint32_t buttonCount; uint32_t totalButtonCount; struct button_definition definition[42]; }; #define VERSION_RES_MESSAGE 0x0098 struct version_res_message { char version[16]; }; #define DISPLAYTEXT_MESSAGE 0x0099 struct displaytext_message { char text[40]; }; #define CLEAR_NOTIFY_MESSAGE 0x0115 #define CLEAR_DISPLAY_MESSAGE 0x009A struct clear_display_message { uint32_t space; }; #define CAPABILITIES_REQ_MESSAGE 0x009B #define REGISTER_REJ_MESSAGE 0x009D struct register_rej_message { char errMsg[33]; }; #define SERVER_RES_MESSAGE 0x009E struct server_identifier { char serverName[48]; }; struct server_res_message { struct server_identifier server[5]; uint32_t serverListenPort[5]; uint32_t serverIpAddr[5]; }; #define RESET_MESSAGE 0x009F struct reset_message { uint32_t resetType; }; #define KEEP_ALIVE_ACK_MESSAGE 0x0100 #define OPEN_RECEIVE_CHANNEL_MESSAGE 0x0105 struct open_receive_channel_message { uint32_t conferenceId; uint32_t partyId; uint32_t packets; uint32_t capability; uint32_t echo; uint32_t bitrate; uint32_t space[36]; }; #define CLOSE_RECEIVE_CHANNEL_MESSAGE 0x0106 struct close_receive_channel_message { uint32_t conferenceId; uint32_t partyId; uint32_t space[2]; }; #define SOFT_KEY_TEMPLATE_RES_MESSAGE 0x0108 struct soft_key_template_definition { char softKeyLabel[16]; uint32_t softKeyEvent; }; #define BKSP_REQ_MESSAGE 0x0119 struct bksp_req_message { uint32_t instance; uint32_t callreference; }; #define KEYDEF_ONHOOK 0 #define KEYDEF_CONNECTED 1 #define KEYDEF_ONHOLD 2 #define KEYDEF_RINGIN 3 #define KEYDEF_OFFHOOK 4 #define KEYDEF_CONNWITHTRANS 5 #define KEYDEF_DADFD 6 /* Digits After Dialing First Digit */ #define KEYDEF_CONNWITHCONF 7 #define KEYDEF_RINGOUT 8 #define KEYDEF_OFFHOOKWITHFEAT 9 #define KEYDEF_UNKNOWN 10 #define KEYDEF_SLAHOLD 11 #define KEYDEF_SLACONNECTEDNOTACTIVE 12 #define KEYDEF_RINGOUTWITHTRANS 13 #define SOFTKEY_NONE 0x00 #define SOFTKEY_REDIAL 0x01 #define SOFTKEY_NEWCALL 0x02 #define SOFTKEY_HOLD 0x03 #define SOFTKEY_TRNSFER 0x04 #define SOFTKEY_CFWDALL 0x05 #define SOFTKEY_CFWDBUSY 0x06 #define SOFTKEY_CFWDNOANSWER 0x07 #define SOFTKEY_BKSPC 0x08 #define SOFTKEY_ENDCALL 0x09 #define SOFTKEY_RESUME 0x0A #define SOFTKEY_ANSWER 0x0B #define SOFTKEY_INFO 0x0C #define SOFTKEY_CONFRN 0x0D #define SOFTKEY_PARK 0x0E #define SOFTKEY_JOIN 0x0F #define SOFTKEY_MEETME 0x10 #define SOFTKEY_PICKUP 0x11 #define SOFTKEY_GPICKUP 0x12 #define SOFTKEY_DND 0x13 #define SOFTKEY_IDIVERT 0x14 #define SOFTKEY_FORCEDIAL 0x15 #define KEYMASK_ALL 0xFFFFFFFF #define KEYMASK_NONE (1 << 0) #define KEYMASK_REDIAL (1 << 1) #define KEYMASK_NEWCALL (1 << 2) #define KEYMASK_HOLD (1 << 3) #define KEYMASK_TRNSFER (1 << 4) #define KEYMASK_CFWDALL (1 << 5) #define KEYMASK_CFWDBUSY (1 << 6) #define KEYMASK_CFWDNOANSWER (1 << 7) #define KEYMASK_BKSPC (1 << 8) #define KEYMASK_ENDCALL (1 << 9) #define KEYMASK_RESUME (1 << 10) #define KEYMASK_ANSWER (1 << 11) #define KEYMASK_INFO (1 << 12) #define KEYMASK_CONFRN (1 << 13) #define KEYMASK_PARK (1 << 14) #define KEYMASK_JOIN (1 << 15) #define KEYMASK_MEETME (1 << 16) #define KEYMASK_PICKUP (1 << 17) #define KEYMASK_GPICKUP (1 << 18) #define KEYMASK_DND (1 << 29) #define KEYMASK_IDIVERT (1 << 20) #define KEYMASK_FORCEDIAL (1 << 21) /* Localized message "codes" (in octal) Below is en_US (taken from a 7970) */ /* "\200\000" ??? */ #define OCTAL_REDIAL "\200\001" /* Redial */ #define OCTAL_NEWCALL "\200\002" /* New Call */ #define OCTAL_HOLD "\200\003" /* Hold */ #define OCTAL_TRANSFER "\200\004" /* Transfer */ #define OCTAL_CFWDALL "\200\005" /* CFwdALL */ #define OCTAL_CFWDBUSY "\200\006" /* CFwdBusy */ #define OCTAL_CFWDNOAN "\200\007" /* CFwdNoAnswer */ #define OCTAL_BKSPC "\200\010" /* << */ #define OCTAL_ENDCALL "\200\011" /* EndCall */ #define OCTAL_RESUME "\200\012" /* Resume */ #define OCTAL_ANSWER "\200\013" /* Answer */ #define OCTAL_INFO "\200\014" /* Info */ #define OCTAL_CONFRN "\200\015" /* Confrn */ #define OCTAL_PARK "\200\016" /* Park */ #define OCTAL_JOIN "\200\017" /* Join */ #define OCTAL_MEETME "\200\020" /* MeetMe */ #define OCTAL_PICKUP "\200\021" /* PickUp */ #define OCTAL_GPICKUP "\200\022" /* GPickUp */ #define OCTAL_CUROPTS "\200\023" /* Your current options */ #define OCTAL_OFFHOOK "\200\024" /* Off Hook */ #define OCTAL_ONHOOK "\200\025" /* On Hook */ #define OCTAL_RINGOUT "\200\026" /* Ring out */ #define OCTAL_FROM "\200\027" /* From */ #define OCTAL_CONNECTED "\200\030" /* Connected */ #define OCTAL_BUSY "\200\031" /* Busy */ #define OCTAL_LINEINUSE "\200\032" /* Line In Use */ #define OCTAL_CALLWAITING "\200\033" /* Call Waiting */ #define OCTAL_CALLXFER "\200\034" /* Call Transfer */ #define OCTAL_CALLPARK "\200\035" /* Call Park */ #define OCTAL_CALLPROCEED "\200\036" /* Call Proceed */ #define OCTAL_INUSEREMOTE "\200\037" /* In Use Remote */ #define OCTAL_ENTRNUM "\200\040" /* Enter number */ #define OCTAL_PARKAT "\200\041" /* Call park At */ #define OCTAL_PRIMONLY "\200\042" /* Primary Only */ #define OCTAL_TMPFAIL "\200\043" /* Temp Fail */ #define OCTAL_HAVEVMAIL "\200\044" /* You Have VoiceMail */ #define OCTAL_FWDEDTO "\200\045" /* Forwarded to */ #define OCTAL_CANTCOMPCNF "\200\046" /* Can Not Complete Conference */ #define OCTAL_NOCONFBRDG "\200\047" /* No Conference Bridge */ #define OCTAL_NOPRIMARYCTL "\200\050" /* Can Not Hold Primary Control */ #define OCTAL_INVALCONFPART "\200\051" /* Invalid Conference Participant */ #define OCTAL_INCONFALREADY "\200\052" /* In Conference Already */ #define OCTAL_NOPARTINFO "\200\053" /* No Participant Info */ #define OCTAL_MAXPARTEXCEED "\200\054" /* Exceed Maximum Parties */ #define OCTAL_KEYNOTACTIVE "\200\055" /* Key Is Not Active */ #define OCTAL_ERRNOLIC "\200\056" /* Error No License */ #define OCTAL_ERRDBCFG "\200\057" /* Error DBConfig */ #define OCTAL_ERRDB "\200\060" /* Error Database */ #define OCTAL_ERRPASSLMT "\200\061" /* Error Pass Limit */ #define OCTAL_ERRUNK "\200\062" /* Error Unknown */ #define OCTAL_ERRMISMATCH "\200\063" /* Error Mismatch */ #define OCTAL_CONFERENCE "\200\064" /* Conference */ #define OCTAL_PARKNO "\200\065" /* Park Number */ #define OCTAL_PRIVATE "\200\066" /* Private */ #define OCTAL_INSUFBANDW "\200\067" /* Not Enough Bandwidth */ #define OCTAL_UNKNUM "\200\070" /* Unknown Number */ #define OCTAL_RMLSTC "\200\071" /* RmLstC */ #define OCTAL_VOICEMAIL "\200\072" /* Voicemail */ #define OCTAL_IMMDIV "\200\073" /* ImmDiv */ #define OCTAL_INTRCPT "\200\074" /* Intrcpt */ #define OCTAL_SETWTCH "\200\075" /* SetWtch */ #define OCTAL_TRNSFVM "\200\076" /* TrnsfVM */ #define OCTAL_DND "\200\077" /* DND */ #define OCTAL_DIVALL "\200\100" /* DivAll */ #define OCTAL_CALLBACK "\200\101" /* CallBack */ #define OCTAL_NETCNGREROUT "\200\102" /* Network congestion,rerouting */ #define OCTAL_BARGE "\200\103" /* Barge */ #define OCTAL_BARGEFAIL "\200\104" /* Failed to setup Barge */ #define OCTAL_BARGEEXIST "\200\105" /* Another Barge exists */ #define OCTAL_INCOMPATDEV "\200\106" /* Incompatible device type */ #define OCTAL_PARKNONUM "\200\107" /* No Park Number Available */ #define OCTAL_PARKREVERSION "\200\110" /* CallPark Reversion */ #define OCTAL_SRVNOTACTIVE "\200\111" /* Service is not Active */ #define OCTAL_HITRAFFIC "\200\112" /* High Traffic Try Again Later */ #define OCTAL_QRT "\200\113" /* QRT */ #define OCTAL_MCID "\200\114" /* MCID */ #define OCTAL_DIRTRFR "\200\115" /* DirTrfr */ #define OCTAL_SELECT "\200\116" /* Select */ #define OCTAL_CONFLIST "\200\117" /* ConfList */ #define OCTAL_IDIVERT "\200\120" /* iDivert */ #define OCTAL_CBARGE "\200\121" /* cBarge */ #define OCTAL_CANTCOMPLXFER "\200\122" /* Can Not Complete Transfer */ #define OCTAL_CANTJOINCALLS "\200\123" /* Can Not Join Calls */ #define OCTAL_MCIDSUCCESS "\200\124" /* Mcid Successful */ #define OCTAL_NUMNOTCFG "\200\125" /* Number Not Configured */ #define OCTAL_SECERROR "\200\126" /* Security Error */ #define OCTAL_VIDBANDWNA "\200\127" /* Video Bandwidth Unavailable */ #define OCTAL_VIDMODE "\200\130" /* VidMode */ #define OCTAL_CALLDURTIMEOUT "\200\131" /* Max Call Duration Timeout */ #define OCTAL_HOLDDURTIMEOUT "\200\132" /* Max Hold Duration Timeout */ #define OCTAL_OPICKUP "\200\133" /* OPickUp */ /* "\200\134" ??? */ /* "\200\135" ??? */ /* "\200\136" ??? */ /* "\200\137" ??? */ /* "\200\140" ??? */ #define OCTAL_EXTXFERRESTRICT "\200\141" /* External Transfer Restricted */ /* "\200\142" ??? */ /* "\200\143" ??? */ /* "\200\144" ??? */ #define OCTAL_MACADD "\200\145" /* Mac Address */ #define OCTAL_HOST "\200\146" /* Host Name */ #define OCTAL_DOMAIN "\200\147" /* Domain Name */ #define OCTAL_IPADD "\200\150" /* IP Address */ #define OCTAL_SUBMASK "\200\151" /* Subnet Mask */ #define OCTAL_TFTP1 "\200\152" /* TFTP Server 1 */ #define OCTAL_ROUTER1 "\200\153" /* Default Router 1 */ #define OCTAL_ROUTER2 "\200\154" /* Default Router 2 */ #define OCTAL_ROUTER3 "\200\155" /* Default Router 3 */ #define OCTAL_ROUTER4 "\200\156" /* Default Router 4 */ #define OCTAL_ROUTER5 "\200\157" /* Default Router 5 */ #define OCTAL_DNS1 "\200\160" /* DNS Server 1 */ #define OCTAL_DNS2 "\200\161" /* DNS Server 2 */ #define OCTAL_DNS3 "\200\162" /* DNS Server 3 */ #define OCTAL_DNS4 "\200\163" /* DNS Server 4 */ #define OCTAL_DNS5 "\200\164" /* DNS Server 5 */ #define OCTAL_VLANOPID "\200\165" /* Operational VLAN Id */ #define OCTAL_VLANADID "\200\166" /* Admin. VLAN Id */ #define OCTAL_CM1 "\200\167" /* CallManager 1 */ #define OCTAL_CM2 "\200\170" /* CallManager 2 */ #define OCTAL_CM3 "\200\171" /* CallManager 3 */ #define OCTAL_CM4 "\200\172" /* CallManager 4 */ #define OCTAL_CM5 "\200\173" /* CallManager 5 */ #define OCTAL_URLINFO "\200\174" /* Information URL */ #define OCTAL_URLDIRS "\200\175" /* Directories URL */ #define OCTAL_URLMSGS "\200\176" /* Messages URL */ #define OCTAL_URLSRVS "\200\177" /* Services URL */ static struct soft_key_template_definition soft_key_template_default[] = { { OCTAL_REDIAL, SOFTKEY_REDIAL }, { OCTAL_NEWCALL, SOFTKEY_NEWCALL }, { OCTAL_HOLD, SOFTKEY_HOLD }, { OCTAL_TRANSFER, SOFTKEY_TRNSFER }, { OCTAL_CFWDALL, SOFTKEY_CFWDALL }, { OCTAL_CFWDBUSY, SOFTKEY_CFWDBUSY }, { OCTAL_CFWDNOAN, SOFTKEY_CFWDNOANSWER }, { OCTAL_BKSPC, SOFTKEY_BKSPC }, { OCTAL_ENDCALL, SOFTKEY_ENDCALL }, { OCTAL_RESUME, SOFTKEY_RESUME }, { OCTAL_ANSWER, SOFTKEY_ANSWER }, { OCTAL_INFO, SOFTKEY_INFO }, { OCTAL_CONFRN, SOFTKEY_CONFRN }, { OCTAL_PARK, SOFTKEY_PARK }, { OCTAL_JOIN, SOFTKEY_JOIN }, { OCTAL_MEETME, SOFTKEY_MEETME }, { OCTAL_PICKUP, SOFTKEY_PICKUP }, { OCTAL_GPICKUP, SOFTKEY_GPICKUP }, { OCTAL_DND, SOFTKEY_DND }, { OCTAL_IDIVERT, SOFTKEY_IDIVERT }, { "Dial", SOFTKEY_FORCEDIAL}, }; struct soft_key_definitions { const uint8_t mode; const uint8_t *defaults; const int count; }; static const uint8_t soft_key_default_onhook[] = { SOFTKEY_REDIAL, SOFTKEY_NEWCALL, SOFTKEY_CFWDALL, SOFTKEY_CFWDBUSY, SOFTKEY_CFWDNOANSWER, SOFTKEY_DND, SOFTKEY_GPICKUP, /*SOFTKEY_CONFRN,*/ }; static const uint8_t soft_key_default_connected[] = { SOFTKEY_HOLD, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, SOFTKEY_PARK, SOFTKEY_CFWDALL, SOFTKEY_CFWDBUSY, SOFTKEY_CFWDNOANSWER, }; static const uint8_t soft_key_default_onhold[] = { SOFTKEY_RESUME, SOFTKEY_NEWCALL, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, }; static const uint8_t soft_key_default_ringin[] = { SOFTKEY_ANSWER, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, }; static const uint8_t soft_key_default_offhook[] = { SOFTKEY_REDIAL, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, SOFTKEY_CFWDALL, SOFTKEY_CFWDBUSY, SOFTKEY_CFWDNOANSWER, SOFTKEY_GPICKUP, }; static const uint8_t soft_key_default_connwithtrans[] = { SOFTKEY_HOLD, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, SOFTKEY_PARK, SOFTKEY_CFWDALL, SOFTKEY_CFWDBUSY, SOFTKEY_CFWDNOANSWER, }; static const uint8_t soft_key_default_dadfd[] = { SOFTKEY_BKSPC, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, SOFTKEY_FORCEDIAL, }; static const uint8_t soft_key_default_connwithconf[] = { SOFTKEY_NONE, }; static const uint8_t soft_key_default_ringout[] = { SOFTKEY_NONE, SOFTKEY_ENDCALL, }; static const uint8_t soft_key_default_ringoutwithtransfer[] = { SOFTKEY_NONE, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, }; static const uint8_t soft_key_default_offhookwithfeat[] = { SOFTKEY_REDIAL, SOFTKEY_ENDCALL, SOFTKEY_TRNSFER, }; static const uint8_t soft_key_default_unknown[] = { SOFTKEY_NONE, }; static const uint8_t soft_key_default_SLAhold[] = { SOFTKEY_REDIAL, SOFTKEY_NEWCALL, SOFTKEY_RESUME, }; static const uint8_t soft_key_default_SLAconnectednotactive[] = { SOFTKEY_REDIAL, SOFTKEY_NEWCALL, SOFTKEY_JOIN, }; static const struct soft_key_definitions soft_key_default_definitions[] = { {KEYDEF_ONHOOK, soft_key_default_onhook, sizeof(soft_key_default_onhook) / sizeof(uint8_t)}, {KEYDEF_CONNECTED, soft_key_default_connected, sizeof(soft_key_default_connected) / sizeof(uint8_t)}, {KEYDEF_ONHOLD, soft_key_default_onhold, sizeof(soft_key_default_onhold) / sizeof(uint8_t)}, {KEYDEF_RINGIN, soft_key_default_ringin, sizeof(soft_key_default_ringin) / sizeof(uint8_t)}, {KEYDEF_OFFHOOK, soft_key_default_offhook, sizeof(soft_key_default_offhook) / sizeof(uint8_t)}, {KEYDEF_CONNWITHTRANS, soft_key_default_connwithtrans, sizeof(soft_key_default_connwithtrans) / sizeof(uint8_t)}, {KEYDEF_DADFD, soft_key_default_dadfd, sizeof(soft_key_default_dadfd) / sizeof(uint8_t)}, {KEYDEF_CONNWITHCONF, soft_key_default_connwithconf, sizeof(soft_key_default_connwithconf) / sizeof(uint8_t)}, {KEYDEF_RINGOUT, soft_key_default_ringout, sizeof(soft_key_default_ringout) / sizeof(uint8_t)}, {KEYDEF_RINGOUTWITHTRANS, soft_key_default_ringoutwithtransfer, sizeof(soft_key_default_ringoutwithtransfer) / sizeof(uint8_t)}, {KEYDEF_OFFHOOKWITHFEAT, soft_key_default_offhookwithfeat, sizeof(soft_key_default_offhookwithfeat) / sizeof(uint8_t)}, {KEYDEF_UNKNOWN, soft_key_default_unknown, sizeof(soft_key_default_unknown) / sizeof(uint8_t)}, {KEYDEF_SLAHOLD, soft_key_default_SLAhold, sizeof(soft_key_default_SLAhold) / sizeof(uint8_t)}, {KEYDEF_SLACONNECTEDNOTACTIVE, soft_key_default_SLAconnectednotactive, sizeof(soft_key_default_SLAconnectednotactive) / sizeof(uint8_t)} }; struct soft_key_template_res_message { uint32_t softKeyOffset; uint32_t softKeyCount; uint32_t totalSoftKeyCount; struct soft_key_template_definition softKeyTemplateDefinition[32]; }; #define SOFT_KEY_SET_RES_MESSAGE 0x0109 struct soft_key_set_definition { uint8_t softKeyTemplateIndex[16]; uint16_t softKeyInfoIndex[16]; }; struct soft_key_set_res_message { uint32_t softKeySetOffset; uint32_t softKeySetCount; uint32_t totalSoftKeySetCount; struct soft_key_set_definition softKeySetDefinition[16]; uint32_t res; }; #define SELECT_SOFT_KEYS_MESSAGE 0x0110 struct select_soft_keys_message { uint32_t instance; uint32_t reference; uint32_t softKeySetIndex; uint32_t validKeyMask; }; #define CALL_STATE_MESSAGE 0x0111 struct call_state_message { uint32_t callState; uint32_t lineInstance; uint32_t callReference; uint32_t space[3]; }; #define DISPLAY_PROMPT_STATUS_MESSAGE 0x0112 struct display_prompt_status_message { uint32_t messageTimeout; char promptMessage[32]; uint32_t lineInstance; uint32_t callReference; uint32_t space[3]; }; #define CLEAR_PROMPT_MESSAGE 0x0113 struct clear_prompt_message { uint32_t lineInstance; uint32_t callReference; }; #define DISPLAY_NOTIFY_MESSAGE 0x0114 struct display_notify_message { uint32_t displayTimeout; char displayMessage[100]; }; #define ACTIVATE_CALL_PLANE_MESSAGE 0x0116 struct activate_call_plane_message { uint32_t lineInstance; }; #define DIALED_NUMBER_MESSAGE 0x011D struct dialed_number_message { char dialedNumber[24]; uint32_t lineInstance; uint32_t callReference; }; #define MAX_SERVICEURL 256 #define SERVICEURL_STAT_MESSAGE 0x012F struct serviceurl_stat_message { uint32_t instance; char url[MAX_SERVICEURL]; char displayName[40]; }; #define MAXCALLINFOSTR 256 #define MAXDISPLAYNOTIFYSTR 32 #define DISPLAY_PRINOTIFY_MESSAGE 0x0120 struct display_prinotify_message { uint32_t timeout; uint32_t priority; char text[MAXDISPLAYNOTIFYSTR]; }; #define CLEAR_PRINOTIFY_MESSAGE 0x0121 struct clear_prinotify_message { uint32_t priority; }; #define CALL_INFO_MESSAGE_VARIABLE 0x014A struct call_info_message_variable { uint32_t instance; uint32_t callreference; uint32_t calldirection; uint32_t unknown1; uint32_t unknown2; uint32_t unknown3; uint32_t unknown4; uint32_t unknown5; char calldetails[MAXCALLINFOSTR]; }; #define DISPLAY_PRINOTIFY_MESSAGE_VARIABLE 0x0144 struct display_prinotify_message_variable { uint32_t timeout; uint32_t priority; char text[MAXDISPLAYNOTIFYSTR]; }; #define DISPLAY_PROMPT_STATUS_MESSAGE_VARIABLE 0x0145 struct display_prompt_status_message_variable { uint32_t unknown; uint32_t lineInstance; uint32_t callReference; char promptMessage[MAXCALLINFOSTR]; }; union skinny_data { struct alarm_message alarm; struct speed_dial_stat_req_message speeddialreq; struct register_message reg; struct register_ack_message regack; struct register_rej_message regrej; struct capabilities_res_message caps; struct version_res_message version; struct button_template_res_message buttontemplate; struct displaytext_message displaytext; struct clear_display_message cleardisplay; struct display_prompt_status_message displaypromptstatus; struct clear_prompt_message clearpromptstatus; struct definetimedate_message definetimedate; struct start_tone_message starttone; struct stop_tone_message stoptone; struct speed_dial_stat_res_message speeddial; struct line_state_req_message line; struct line_stat_res_message linestat; struct soft_key_set_res_message softkeysets; struct soft_key_template_res_message softkeytemplate; struct server_res_message serverres; struct reset_message reset; struct set_lamp_message setlamp; struct set_ringer_message setringer; struct call_state_message callstate; struct keypad_button_message keypad; struct select_soft_keys_message selectsoftkey; struct activate_call_plane_message activatecallplane; struct stimulus_message stimulus; struct offhook_message offhook; struct onhook_message onhook; struct set_speaker_message setspeaker; struct set_microphone_message setmicrophone; struct call_info_message callinfo; struct start_media_transmission_message_ip4 startmedia_ip4; struct start_media_transmission_message_ip6 startmedia_ip6; struct stop_media_transmission_message stopmedia; struct open_receive_channel_message openreceivechannel; struct open_receive_channel_ack_message_ip4 openreceivechannelack_ip4; struct open_receive_channel_ack_message_ip6 openreceivechannelack_ip6; struct close_receive_channel_message closereceivechannel; struct display_notify_message displaynotify; struct dialed_number_message dialednumber; struct soft_key_event_message softkeyeventmessage; struct enbloc_call_message enbloccallmessage; struct forward_stat_message forwardstat; struct bksp_req_message bkspmessage; struct call_info_message_variable callinfomessagevariable; struct display_prompt_status_message_variable displaypromptstatusvar; struct serviceurl_stat_message serviceurlmessage; struct clear_prinotify_message clearprinotify; struct display_prinotify_message displayprinotify; struct display_prinotify_message_variable displayprinotifyvar; }; /* packet composition */ struct skinny_req { uint32_t len; uint32_t res; uint32_t e; union skinny_data data; }; /* XXX This is the combined size of the variables above. (len, res, e) If more are added, this MUST change. (sizeof(skinny_req) - sizeof(skinny_data)) DOES NOT WORK on all systems (amd64?). */ static int skinny_header_size = 12; /***************************** * Asterisk specific globals * *****************************/ static int skinnyreload = 0; /* a hostname, portnumber, socket and such is usefull for VoIP protocols */ static struct sockaddr_in bindaddr; static char ourhost[256]; static int ourport; static struct in_addr __ourip; static struct ast_hostent ahp; static struct hostent *hp; static int skinnysock = -1; static pthread_t accept_t; static int callnums = 1; #define SKINNY_DEVICE_UNKNOWN -1 #define SKINNY_DEVICE_NONE 0 #define SKINNY_DEVICE_30SPPLUS 1 #define SKINNY_DEVICE_12SPPLUS 2 #define SKINNY_DEVICE_12SP 3 #define SKINNY_DEVICE_12 4 #define SKINNY_DEVICE_30VIP 5 #define SKINNY_DEVICE_7910 6 #define SKINNY_DEVICE_7960 7 #define SKINNY_DEVICE_7940 8 #define SKINNY_DEVICE_7935 9 #define SKINNY_DEVICE_ATA186 12 /* Cisco ATA-186 */ #define SKINNY_DEVICE_7941 115 #define SKINNY_DEVICE_7971 119 #define SKINNY_DEVICE_7914 124 /* Expansion module */ #define SKINNY_DEVICE_7985 302 #define SKINNY_DEVICE_7911 307 #define SKINNY_DEVICE_7961GE 308 #define SKINNY_DEVICE_7941GE 309 #define SKINNY_DEVICE_7931 348 #define SKINNY_DEVICE_7921 365 #define SKINNY_DEVICE_7906 369 #define SKINNY_DEVICE_7962 404 /* Not found */ #define SKINNY_DEVICE_7937 431 #define SKINNY_DEVICE_7942 434 #define SKINNY_DEVICE_7945 435 #define SKINNY_DEVICE_7965 436 #define SKINNY_DEVICE_7975 437 #define SKINNY_DEVICE_7905 20000 #define SKINNY_DEVICE_7920 30002 #define SKINNY_DEVICE_7970 30006 #define SKINNY_DEVICE_7912 30007 #define SKINNY_DEVICE_7902 30008 #define SKINNY_DEVICE_CIPC 30016 /* Cisco IP Communicator */ #define SKINNY_DEVICE_7961 30018 #define SKINNY_DEVICE_7936 30019 #define SKINNY_DEVICE_SCCPGATEWAY_AN 30027 /* Analog gateway */ #define SKINNY_DEVICE_SCCPGATEWAY_BRI 30028 /* BRI gateway */ #define SKINNY_SPEAKERON 1 #define SKINNY_SPEAKEROFF 2 #define SKINNY_MICON 1 #define SKINNY_MICOFF 2 #define SKINNY_OFFHOOK 1 #define SKINNY_ONHOOK 2 #define SKINNY_RINGOUT 3 #define SKINNY_RINGIN 4 #define SKINNY_CONNECTED 5 #define SKINNY_BUSY 6 #define SKINNY_CONGESTION 7 #define SKINNY_HOLD 8 #define SKINNY_CALLWAIT 9 #define SKINNY_TRANSFER 10 #define SKINNY_PARK 11 #define SKINNY_PROGRESS 12 #define SKINNY_CALLREMOTEMULTILINE 13 #define SKINNY_INVALID 14 #define SKINNY_INCOMING 1 #define SKINNY_OUTGOING 2 #define SKINNY_SILENCE 0x00 /* Note sure this is part of the protocol, remove? */ #define SKINNY_DIALTONE 0x21 #define SKINNY_BUSYTONE 0x23 #define SKINNY_ALERT 0x24 #define SKINNY_REORDER 0x25 #define SKINNY_CALLWAITTONE 0x2D #define SKINNY_ZIPZIP 0x31 #define SKINNY_ZIP 0x32 #define SKINNY_BEEPBONK 0x33 #define SKINNY_BARGIN 0x43 #define SKINNY_NOTONE 0x7F #define SKINNY_LAMP_OFF 1 #define SKINNY_LAMP_ON 2 #define SKINNY_LAMP_WINK 3 #define SKINNY_LAMP_FLASH 4 #define SKINNY_LAMP_BLINK 5 #define SKINNY_RING_OFF 1 #define SKINNY_RING_INSIDE 2 #define SKINNY_RING_OUTSIDE 3 #define SKINNY_RING_FEATURE 4 #define SKINNY_CFWD_ALL (1 << 0) #define SKINNY_CFWD_BUSY (1 << 1) #define SKINNY_CFWD_NOANSWER (1 << 2) /* Skinny rtp stream modes. Do we really need this? */ #define SKINNY_CX_SENDONLY 0 #define SKINNY_CX_RECVONLY 1 #define SKINNY_CX_SENDRECV 2 #define SKINNY_CX_CONF 3 #define SKINNY_CX_CONFERENCE 3 #define SKINNY_CX_MUTE 4 #define SKINNY_CX_INACTIVE 4 #if 0 static const char * const skinny_cxmodes[] = { "sendonly", "recvonly", "sendrecv", "confrnce", "inactive" }; #endif /* driver scheduler */ static struct ast_sched_context *sched = NULL; /* Protect the network socket */ AST_MUTEX_DEFINE_STATIC(netlock); /* Wait up to 16 seconds for first digit */ static int firstdigittimeout = 16000; /* How long to wait for following digits */ static int gendigittimeout = 8000; /* How long to wait for an extra digit, if there is an ambiguous match */ static int matchdigittimeout = 3000; #define SUBSTATE_UNSET 0 #define SUBSTATE_OFFHOOK 1 #define SUBSTATE_ONHOOK 2 #define SUBSTATE_RINGOUT 3 #define SUBSTATE_RINGIN 4 #define SUBSTATE_CONNECTED 5 #define SUBSTATE_BUSY 6 #define SUBSTATE_CONGESTION 7 #define SUBSTATE_HOLD 8 #define SUBSTATE_CALLWAIT 9 #define SUBSTATE_PROGRESS 12 #define SUBSTATE_DIALING 101 #define DIALTYPE_NORMAL 1<<0 #define DIALTYPE_CFWD 1<<1 #define DIALTYPE_XFER 1<<2 struct skinny_subchannel { struct ast_channel *owner; struct ast_rtp_instance *rtp; struct ast_rtp_instance *vrtp; unsigned int callid; char exten[AST_MAX_EXTENSION]; /* time_t lastouttime; */ /* Unused */ int progress; int ringing; /* int lastout; */ /* Unused */ int cxmode; int nat; int calldirection; int blindxfer; int xferor; int substate; int aa_sched; int aa_beep; int aa_mute; int dialer_sched; int cfwd_sched; int dialType; int getforward; char *origtonum; char *origtoname; AST_LIST_ENTRY(skinny_subchannel) list; struct skinny_subchannel *related; struct skinny_line *line; struct skinny_subline *subline; }; #define SKINNY_LINE_OPTIONS \ char name[80]; \ char label[24]; \ char accountcode[AST_MAX_ACCOUNT_CODE]; \ char exten[AST_MAX_EXTENSION]; \ char context[AST_MAX_CONTEXT]; \ char language[MAX_LANGUAGE]; \ char cid_num[AST_MAX_EXTENSION]; \ char cid_name[AST_MAX_EXTENSION]; \ char lastcallerid[AST_MAX_EXTENSION]; \ int cfwdtype; \ char call_forward_all[AST_MAX_EXTENSION]; \ char call_forward_busy[AST_MAX_EXTENSION]; \ char call_forward_noanswer[AST_MAX_EXTENSION]; \ char mailbox[AST_MAX_MAILBOX_UNIQUEID]; \ char vmexten[AST_MAX_EXTENSION]; \ char regexten[AST_MAX_EXTENSION]; \ char regcontext[AST_MAX_CONTEXT]; \ char parkinglot[AST_MAX_CONTEXT]; \ char mohinterpret[MAX_MUSICCLASS]; \ char mohsuggest[MAX_MUSICCLASS]; \ char lastnumberdialed[AST_MAX_EXTENSION]; \ char dialoutexten[AST_MAX_EXTENSION]; \ char dialoutcontext[AST_MAX_CONTEXT]; \ ast_group_t callgroup; \ ast_group_t pickupgroup; \ struct ast_namedgroups *named_callgroups; \ struct ast_namedgroups *named_pickupgroups; \ int callwaiting; \ int transfer; \ int threewaycalling; \ int mwiblink; \ int cancallforward; \ int callfwdtimeout; \ int dnd; \ int hidecallerid; \ int amaflags; \ int instance; \ int group; \ int nonCodecCapability; \ int immediate; \ int nat; \ int directmedia; \ int prune; struct skinny_line { SKINNY_LINE_OPTIONS ast_mutex_t lock; struct skinny_container *container; struct stasis_subscription *mwi_event_sub; /* Event based MWI */ struct skinny_subchannel *activesub; AST_LIST_HEAD(, skinny_subchannel) sub; AST_LIST_HEAD(, skinny_subline) sublines; AST_LIST_ENTRY(skinny_line) list; AST_LIST_ENTRY(skinny_line) all; struct skinny_device *device; struct ast_format_cap *cap; struct ast_format_cap *confcap; struct ast_variable *chanvars; /*!< Channel variables to set for inbound call */ int newmsgs; }; static struct skinny_line_options{ SKINNY_LINE_OPTIONS } default_line_struct = { .callwaiting = 1, .transfer = 1, .mwiblink = 0, .dnd = 0, .hidecallerid = 0, .amaflags = 0, .instance = 0, .directmedia = 0, .nat = 0, .callfwdtimeout = 20000, .prune = 0, }; static struct skinny_line_options *default_line = &default_line_struct; static AST_LIST_HEAD_STATIC(lines, skinny_line); struct skinny_subline { struct skinny_container *container; struct skinny_line *line; struct skinny_subchannel *sub; AST_LIST_ENTRY(skinny_subline) list; char name[80]; char context[AST_MAX_CONTEXT]; char exten[AST_MAX_EXTENSION]; char stname[AST_MAX_EXTENSION]; char lnname[AST_MAX_EXTENSION]; char ourName[40]; char ourNum[24]; char theirName[40]; char theirNum[24]; int calldirection; int substate; int extenstate; unsigned int callid; }; struct skinny_speeddial { ast_mutex_t lock; struct skinny_container *container; char label[42]; char context[AST_MAX_CONTEXT]; char exten[AST_MAX_EXTENSION]; int instance; int stateid; int laststate; int isHint; AST_LIST_ENTRY(skinny_speeddial) list; struct skinny_device *parent; }; struct skinny_serviceurl { int instance; char url[MAX_SERVICEURL]; char displayName[40]; AST_LIST_ENTRY(skinny_serviceurl) list; struct skinny_device *device; }; #define SKINNY_DEVICECONTAINER 1 #define SKINNY_LINECONTAINER 2 #define SKINNY_SUBLINECONTAINER 3 #define SKINNY_SDCONTAINER 4 struct skinny_container { int type; void *data; }; struct skinny_addon { ast_mutex_t lock; char type[10]; AST_LIST_ENTRY(skinny_addon) list; struct skinny_device *parent; }; #define SKINNY_DEVICE_OPTIONS \ char name[80]; \ char id[16]; \ char version_id[16]; \ char vmexten[AST_MAX_EXTENSION]; \ int type; \ int protocolversion; \ int hookstate; \ int lastlineinstance; \ int lastcallreference; \ int earlyrtp; \ int transfer; \ int callwaiting; \ int mwiblink; \ int dnd; \ int prune; struct skinny_device { SKINNY_DEVICE_OPTIONS struct type *first; struct type *last; ast_mutex_t lock; struct sockaddr_in addr; struct in_addr ourip; struct ast_ha *ha; struct skinnysession *session; struct skinny_line *activeline; struct ast_format_cap *cap; struct ast_format_cap *confcap; struct ast_endpoint *endpoint; AST_LIST_HEAD(, skinny_line) lines; AST_LIST_HEAD(, skinny_speeddial) speeddials; AST_LIST_HEAD(, skinny_serviceurl) serviceurls; AST_LIST_HEAD(, skinny_addon) addons; AST_LIST_ENTRY(skinny_device) list; }; static struct skinny_device_options { SKINNY_DEVICE_OPTIONS } default_device_struct = { .transfer = 1, .earlyrtp = 1, .callwaiting = 1, .mwiblink = 0, .dnd = 0, .prune = 0, .hookstate = SKINNY_ONHOOK, }; static struct skinny_device_options *default_device = &default_device_struct; static AST_LIST_HEAD_STATIC(devices, skinny_device); struct skinnysession { pthread_t t; ast_mutex_t lock; struct timeval start; struct sockaddr_in sin; int fd; char outbuf[SKINNY_MAX_PACKET]; struct skinny_device *device; AST_LIST_ENTRY(skinnysession) list; int lockstate; /* Only for use in the skinny_session thread */ int auth_timeout_sched; int keepalive_timeout_sched; struct timeval last_keepalive; int keepalive_count; }; static struct ast_channel *skinny_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); static AST_LIST_HEAD_STATIC(sessions, skinnysession); static int skinny_devicestate(const char *data); static int skinny_call(struct ast_channel *ast, const char *dest, int timeout); static int skinny_hangup(struct ast_channel *ast); static int skinny_answer(struct ast_channel *ast); static struct ast_frame *skinny_read(struct ast_channel *ast); static int skinny_write(struct ast_channel *ast, struct ast_frame *frame); static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen); static int skinny_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int skinny_senddigit_begin(struct ast_channel *ast, char digit); static int skinny_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg); static int skinny_dialer_cb(const void *data); static int skinny_reload(void); static void skinny_set_owner(struct skinny_subchannel* sub, struct ast_channel* chan); static void setsubstate(struct skinny_subchannel *sub, int state); static void dumpsub(struct skinny_subchannel *sub, int forcehangup); static void activatesub(struct skinny_subchannel *sub, int state); static void dialandactivatesub(struct skinny_subchannel *sub, char exten[AST_MAX_EXTENSION]); static int skinny_nokeepalive_cb(const void *data); static void transmit_definetimedate(struct skinny_device *d); static struct ast_channel_tech skinny_tech = { .type = "Skinny", .description = tdesc, .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, .requester = skinny_request, .devicestate = skinny_devicestate, .call = skinny_call, .hangup = skinny_hangup, .answer = skinny_answer, .read = skinny_read, .write = skinny_write, .indicate = skinny_indicate, .fixup = skinny_fixup, .send_digit_begin = skinny_senddigit_begin, .send_digit_end = skinny_senddigit_end, }; static int skinny_extensionstate_cb(char *context, char *id, struct ast_state_cb_info *info, void *data); static struct skinny_line *skinny_line_alloc(void) { struct skinny_line *l; if (!(l = ast_calloc(1, sizeof(*l)))) { return NULL; } l->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); l->confcap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!l->cap || !l->confcap) { ao2_cleanup(l->cap); ao2_cleanup(l->confcap); ast_free(l); return NULL; } return l; } static struct skinny_line *skinny_line_destroy(struct skinny_line *l) { ao2_ref(l->cap, -1); ao2_ref(l->confcap, -1); l->named_callgroups = ast_unref_namedgroups(l->named_callgroups); l->named_pickupgroups = ast_unref_namedgroups(l->named_pickupgroups); ast_free(l->container); ast_free(l); return NULL; } static struct skinny_device *skinny_device_alloc(const char *dname) { struct skinny_device *d; if (!(d = ast_calloc(1, sizeof(*d)))) { return NULL; } d->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); d->confcap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); d->endpoint = ast_endpoint_create("Skinny", dname); if (!d->cap || !d->confcap || !d->endpoint) { ao2_cleanup(d->cap); ao2_cleanup(d->confcap); ast_free(d); return NULL; } ast_copy_string(d->name, dname, sizeof(d->name)); return d; } static struct skinny_device *skinny_device_destroy(struct skinny_device *d) { ao2_ref(d->cap, -1); ao2_ref(d->confcap, -1); ast_endpoint_shutdown(d->endpoint); ast_free(d); return NULL; } static void *get_button_template(struct skinnysession *s, struct button_definition_template *btn) { struct skinny_device *d = s->device; struct skinny_addon *a; int i; switch (d->type) { case SKINNY_DEVICE_30SPPLUS: case SKINNY_DEVICE_30VIP: /* 13 rows, 2 columns */ for (i = 0; i < 4; i++) (btn++)->buttonDefinition = BT_CUST_LINE; (btn++)->buttonDefinition = BT_REDIAL; (btn++)->buttonDefinition = BT_VOICEMAIL; (btn++)->buttonDefinition = BT_CALLPARK; (btn++)->buttonDefinition = BT_FORWARDALL; (btn++)->buttonDefinition = BT_CONFERENCE; for (i = 0; i < 4; i++) (btn++)->buttonDefinition = BT_NONE; for (i = 0; i < 13; i++) (btn++)->buttonDefinition = BT_SPEEDDIAL; break; case SKINNY_DEVICE_12SPPLUS: case SKINNY_DEVICE_12SP: case SKINNY_DEVICE_12: /* 6 rows, 2 columns */ for (i = 0; i < 2; i++) (btn++)->buttonDefinition = BT_CUST_LINE; for (i = 0; i < 4; i++) (btn++)->buttonDefinition = BT_SPEEDDIAL; (btn++)->buttonDefinition = BT_HOLD; (btn++)->buttonDefinition = BT_REDIAL; (btn++)->buttonDefinition = BT_TRANSFER; (btn++)->buttonDefinition = BT_FORWARDALL; (btn++)->buttonDefinition = BT_CALLPARK; (btn++)->buttonDefinition = BT_VOICEMAIL; break; case SKINNY_DEVICE_7910: (btn++)->buttonDefinition = BT_LINE; (btn++)->buttonDefinition = BT_HOLD; (btn++)->buttonDefinition = BT_TRANSFER; (btn++)->buttonDefinition = BT_DISPLAY; (btn++)->buttonDefinition = BT_VOICEMAIL; (btn++)->buttonDefinition = BT_CONFERENCE; (btn++)->buttonDefinition = BT_FORWARDALL; for (i = 0; i < 2; i++) (btn++)->buttonDefinition = BT_SPEEDDIAL; (btn++)->buttonDefinition = BT_REDIAL; break; case SKINNY_DEVICE_7960: case SKINNY_DEVICE_7961: case SKINNY_DEVICE_7961GE: case SKINNY_DEVICE_7962: case SKINNY_DEVICE_7965: for (i = 0; i < 6; i++) (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL; break; case SKINNY_DEVICE_7940: case SKINNY_DEVICE_7941: case SKINNY_DEVICE_7941GE: case SKINNY_DEVICE_7942: case SKINNY_DEVICE_7945: for (i = 0; i < 2; i++) (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL; break; case SKINNY_DEVICE_7935: case SKINNY_DEVICE_7936: for (i = 0; i < 2; i++) (btn++)->buttonDefinition = BT_LINE; break; case SKINNY_DEVICE_ATA186: (btn++)->buttonDefinition = BT_LINE; break; case SKINNY_DEVICE_7970: case SKINNY_DEVICE_7971: case SKINNY_DEVICE_7975: case SKINNY_DEVICE_CIPC: for (i = 0; i < 8; i++) (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL; break; case SKINNY_DEVICE_7985: /* XXX I have no idea what the buttons look like on these. */ ast_log(LOG_WARNING, "Unsupported device type '%d (7985)' found.\n", d->type); break; case SKINNY_DEVICE_7912: case SKINNY_DEVICE_7911: case SKINNY_DEVICE_7905: (btn++)->buttonDefinition = BT_LINE; (btn++)->buttonDefinition = BT_HOLD; break; case SKINNY_DEVICE_7920: /* XXX I don't know if this is right. */ for (i = 0; i < 4; i++) (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL; break; case SKINNY_DEVICE_7921: for (i = 0; i < 6; i++) (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL; break; case SKINNY_DEVICE_7902: ast_log(LOG_WARNING, "Unsupported device type '%d (7902)' found.\n", d->type); break; case SKINNY_DEVICE_7906: ast_log(LOG_WARNING, "Unsupported device type '%d (7906)' found.\n", d->type); break; case SKINNY_DEVICE_7931: ast_log(LOG_WARNING, "Unsupported device type '%d (7931)' found.\n", d->type); break; case SKINNY_DEVICE_7937: ast_log(LOG_WARNING, "Unsupported device type '%d (7937)' found.\n", d->type); break; case SKINNY_DEVICE_7914: ast_log(LOG_WARNING, "Unsupported device type '%d (7914)' found. Expansion module registered by itself?\n", d->type); break; case SKINNY_DEVICE_SCCPGATEWAY_AN: case SKINNY_DEVICE_SCCPGATEWAY_BRI: ast_log(LOG_WARNING, "Unsupported device type '%d (SCCP gateway)' found.\n", d->type); break; default: ast_log(LOG_WARNING, "Unknown device type '%d' found.\n", d->type); break; } AST_LIST_LOCK(&d->addons); AST_LIST_TRAVERSE(&d->addons, a, list) { if (!strcasecmp(a->type, "7914")) { for (i = 0; i < 14; i++) (btn++)->buttonDefinition = BT_CUST_LINESPEEDDIAL; } else { ast_log(LOG_WARNING, "Unknown addon type '%s' found. Skipping.\n", a->type); } } AST_LIST_UNLOCK(&d->addons); return btn; } static struct skinny_req *req_alloc(size_t size, int response_message) { struct skinny_req *req; if (!(req = ast_calloc(1, skinny_header_size + size + 4))) return NULL; req->len = htolel(size+4); req->e = htolel(response_message); return req; } static struct skinny_line *find_line_by_instance(struct skinny_device *d, int instance) { struct skinny_line *l; /*Dialing from on hook or on a 7920 uses instance 0 in requests but we need to start looking at instance 1 */ if (!instance) instance = 1; AST_LIST_TRAVERSE(&d->lines, l, list){ if (l->instance == instance) break; } if (!l) { ast_log(LOG_WARNING, "Could not find line with instance '%d' on device '%s'\n", instance, d->name); } return l; } static struct skinny_line *find_line_by_name(const char *dest) { struct skinny_line *l; struct skinny_line *tmpl = NULL; struct skinny_device *d; char line[256]; char *at; char *device; int checkdevice = 0; ast_copy_string(line, dest, sizeof(line)); at = strchr(line, '@'); if (at) *at++ = '\0'; device = at; if (!ast_strlen_zero(device)) checkdevice = 1; AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list){ if (checkdevice && tmpl) break; else if (!checkdevice) { /* This is a match, since we're checking for line on every device. */ } else if (!strcasecmp(d->name, device)) { } else continue; /* Found the device (or we don't care which device) */ AST_LIST_TRAVERSE(&d->lines, l, list){ /* Search for the right line */ if (!strcasecmp(l->name, line)) { if (tmpl) { ast_log(LOG_WARNING, "Ambiguous line name: %s\n", line); AST_LIST_UNLOCK(&devices); return NULL; } else tmpl = l; } } } AST_LIST_UNLOCK(&devices); return tmpl; } static struct skinny_subline *find_subline_by_name(const char *dest) { struct skinny_line *l; struct skinny_subline *subline; struct skinny_subline *tmpsubline = NULL; struct skinny_device *d; AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list){ AST_LIST_TRAVERSE(&d->lines, l, list){ AST_LIST_TRAVERSE(&l->sublines, subline, list){ if (!strcasecmp(subline->name, dest)) { if (tmpsubline) { ast_verb(2, "Ambiguous subline name: %s\n", dest); AST_LIST_UNLOCK(&devices); return NULL; } else tmpsubline = subline; } } } } AST_LIST_UNLOCK(&devices); return tmpsubline; } static struct skinny_subline *find_subline_by_callid(struct skinny_device *d, int callid) { struct skinny_subline *subline; struct skinny_line *l; AST_LIST_TRAVERSE(&d->lines, l, list){ AST_LIST_TRAVERSE(&l->sublines, subline, list){ if (subline->callid == callid) { return subline; } } } return NULL; } /*! * implement the setvar config line */ static struct ast_variable *add_var(const char *buf, struct ast_variable *list) { struct ast_variable *tmpvar = NULL; char *varname = ast_strdupa(buf), *varval = NULL; if ((varval = strchr(varname,'='))) { *varval++ = '\0'; if ((tmpvar = ast_variable_new(varname, varval, ""))) { tmpvar->next = list; list = tmpvar; } } return list; } static void skinny_locksub(struct skinny_subchannel *sub) { if (sub && sub->owner) { ast_channel_lock(sub->owner); } } static void skinny_unlocksub(struct skinny_subchannel *sub) { if (sub && sub->owner) { ast_channel_unlock(sub->owner); } } static int skinny_sched_del(int sched_id, struct skinny_subchannel *sub) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Deleting SCHED %d\n", sub->callid, sched_id); return ast_sched_del(sched, sched_id); } static int skinny_sched_add(int when, ast_sched_cb callback, struct skinny_subchannel *sub) { int ret; ret = ast_sched_add(sched, when, callback, sub); SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Added SCHED %d\n", sub->callid, ret); return ret; } /* It's quicker/easier to find the subchannel when we know the instance number too */ static struct skinny_subchannel *find_subchannel_by_instance_reference(struct skinny_device *d, int instance, int reference) { struct skinny_line *l = find_line_by_instance(d, instance); struct skinny_subchannel *sub; if (!l) { return NULL; } /* 7920 phones set call reference to 0, so use the first sub-channel on the list. This MIGHT need more love to be right */ if (!reference) sub = AST_LIST_FIRST(&l->sub); else { AST_LIST_TRAVERSE(&l->sub, sub, list) { if (sub->callid == reference) break; } } if (!sub) { ast_log(LOG_WARNING, "Could not find subchannel with reference '%d' on '%s'\n", reference, d->name); } return sub; } /* Find the subchannel when we only have the callid - this shouldn't happen often */ static struct skinny_subchannel *find_subchannel_by_reference(struct skinny_device *d, int reference) { struct skinny_line *l; struct skinny_subchannel *sub = NULL; AST_LIST_TRAVERSE(&d->lines, l, list){ AST_LIST_TRAVERSE(&l->sub, sub, list){ if (sub->callid == reference) break; } if (sub) break; } if (!l) { ast_log(LOG_WARNING, "Could not find any lines that contained a subchannel with reference '%d' on device '%s'\n", reference, d->name); } else { if (!sub) { ast_log(LOG_WARNING, "Could not find subchannel with reference '%d' on '%s@%s'\n", reference, l->name, d->name); } } return sub; } static struct skinny_speeddial *find_speeddial_by_instance(struct skinny_device *d, int instance, int isHint) { struct skinny_speeddial *sd; AST_LIST_TRAVERSE(&d->speeddials, sd, list) { if (sd->isHint == isHint && sd->instance == instance) break; } if (!sd) { ast_log(LOG_WARNING, "Could not find speeddial with instance '%d' on device '%s'\n", instance, d->name); } return sd; } static struct ast_format *codec_skinny2ast(enum skinny_codecs skinnycodec) { switch (skinnycodec) { case SKINNY_CODEC_ALAW: return ast_format_alaw; case SKINNY_CODEC_ULAW: return ast_format_ulaw; case SKINNY_CODEC_G722: return ast_format_g722; case SKINNY_CODEC_G723_1: return ast_format_g723; case SKINNY_CODEC_G729A: return ast_format_g729; case SKINNY_CODEC_G726_32: return ast_format_g726; /* XXX Is this right? */ case SKINNY_CODEC_H261: return ast_format_h261; case SKINNY_CODEC_H263: return ast_format_h263; default: return ast_format_none; } } static int codec_ast2skinny(const struct ast_format *astcodec) { if (ast_format_cmp(astcodec, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_ALAW; } else if (ast_format_cmp(astcodec, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_ULAW; } else if (ast_format_cmp(astcodec, ast_format_g722) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_G722; } else if (ast_format_cmp(astcodec, ast_format_g723) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_G723_1; } else if (ast_format_cmp(astcodec, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_G729A; } else if (ast_format_cmp(astcodec, ast_format_g726) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_G726_32; } else if (ast_format_cmp(astcodec, ast_format_h261) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_H261; } else if (ast_format_cmp(astcodec, ast_format_h263) == AST_FORMAT_CMP_EQUAL) { return SKINNY_CODEC_H263; } else { return 0; } } static int set_callforwards(struct skinny_line *l, const char *cfwd, int cfwdtype) { if (!l) return 0; if (!ast_strlen_zero(cfwd)) { if (cfwdtype & SKINNY_CFWD_ALL) { l->cfwdtype |= SKINNY_CFWD_ALL; ast_copy_string(l->call_forward_all, cfwd, sizeof(l->call_forward_all)); } if (cfwdtype & SKINNY_CFWD_BUSY) { l->cfwdtype |= SKINNY_CFWD_BUSY; ast_copy_string(l->call_forward_busy, cfwd, sizeof(l->call_forward_busy)); } if (cfwdtype & SKINNY_CFWD_NOANSWER) { l->cfwdtype |= SKINNY_CFWD_NOANSWER; ast_copy_string(l->call_forward_noanswer, cfwd, sizeof(l->call_forward_noanswer)); } } else { if (cfwdtype & SKINNY_CFWD_ALL) { l->cfwdtype &= ~SKINNY_CFWD_ALL; memset(l->call_forward_all, 0, sizeof(l->call_forward_all)); } if (cfwdtype & SKINNY_CFWD_BUSY) { l->cfwdtype &= ~SKINNY_CFWD_BUSY; memset(l->call_forward_busy, 0, sizeof(l->call_forward_busy)); } if (cfwdtype & SKINNY_CFWD_NOANSWER) { l->cfwdtype &= ~SKINNY_CFWD_NOANSWER; memset(l->call_forward_noanswer, 0, sizeof(l->call_forward_noanswer)); } } return l->cfwdtype; } static void cleanup_stale_contexts(char *new, char *old) { char *oldcontext, *newcontext, *stalecontext, *stringp, newlist[AST_MAX_CONTEXT]; while ((oldcontext = strsep(&old, "&"))) { stalecontext = '\0'; ast_copy_string(newlist, new, sizeof(newlist)); stringp = newlist; while ((newcontext = strsep(&stringp, "&"))) { if (strcmp(newcontext, oldcontext) == 0) { /* This is not the context you're looking for */ stalecontext = '\0'; break; } else if (strcmp(newcontext, oldcontext)) { stalecontext = oldcontext; } } if (stalecontext) ast_context_destroy(ast_context_find(stalecontext), "Skinny"); } } static void register_exten(struct skinny_line *l) { char multi[256]; char *stringp, *ext, *context; if (ast_strlen_zero(regcontext)) return; ast_copy_string(multi, S_OR(l->regexten, l->name), sizeof(multi)); stringp = multi; while ((ext = strsep(&stringp, "&"))) { if ((context = strchr(ext, '@'))) { *context++ = '\0'; /* split ext@context */ if (!ast_context_find(context)) { ast_log(LOG_WARNING, "Context %s must exist in regcontext= in skinny.conf!\n", context); continue; } } else { context = regcontext; } ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop", ast_strdup(l->name), ast_free_ptr, "Skinny"); } } static void unregister_exten(struct skinny_line *l) { char multi[256]; char *stringp, *ext, *context; if (ast_strlen_zero(regcontext)) return; ast_copy_string(multi, S_OR(l->regexten, l->name), sizeof(multi)); stringp = multi; while ((ext = strsep(&stringp, "&"))) { if ((context = strchr(ext, '@'))) { *context++ = '\0'; /* split ext@context */ if (!ast_context_find(context)) { ast_log(LOG_WARNING, "Context %s must exist in regcontext= in skinny.conf!\n", context); continue; } } else { context = regcontext; } ast_context_remove_extension(context, ext, 1, NULL); } } static int skinny_register(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d; struct skinny_line *l; struct skinny_subline *subline; struct skinny_speeddial *sd; struct sockaddr_in sin; socklen_t slen; int instance; int res = -1; if (s->auth_timeout_sched && ast_sched_del(sched, s->auth_timeout_sched)) { return 0; } s->auth_timeout_sched = 0; AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list){ struct ast_sockaddr addr; ast_sockaddr_from_sin(&addr, &s->sin); if (!strcasecmp(req->data.reg.name, d->id) && ast_apply_ha(d->ha, &addr)) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); if (d->session) { ast_log(LOG_WARNING, "Device already registered.\n"); transmit_definetimedate(d); res = 0; break; } s->device = d; d->type = letohl(req->data.reg.type); d->protocolversion = letohl(req->data.reg.protocolVersion); if (ast_strlen_zero(d->version_id)) { ast_copy_string(d->version_id, version_id, sizeof(d->version_id)); } d->session = s; slen = sizeof(sin); if (getsockname(s->fd, (struct sockaddr *)&sin, &slen)) { ast_log(LOG_WARNING, "Cannot get socket name\n"); sin.sin_addr = __ourip; } d->ourip = sin.sin_addr; AST_LIST_TRAVERSE(&d->speeddials, sd, list) { sd->stateid = ast_extension_state_add(sd->context, sd->exten, skinny_extensionstate_cb, sd->container); } instance = 0; AST_LIST_TRAVERSE(&d->lines, l, list) { instance++; } AST_LIST_TRAVERSE(&d->lines, l, list) { ast_format_cap_get_compatible(l->confcap, d->cap, l->cap); /* l->capability = d->capability; */ l->instance = instance; l->newmsgs = ast_app_has_voicemail(l->mailbox, NULL); set_callforwards(l, NULL, SKINNY_CFWD_ALL|SKINNY_CFWD_BUSY|SKINNY_CFWD_NOANSWER); register_exten(l); /* initialize MWI on line and device */ mwi_event_cb(l, NULL, NULL); AST_LIST_TRAVERSE(&l->sublines, subline, list) { ast_extension_state_add(subline->context, subline->exten, skinny_extensionstate_cb, subline->container); } ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); --instance; } ast_endpoint_set_state(d->endpoint, AST_ENDPOINT_ONLINE); blob = ast_json_pack("{s: s}", "peer_status", "Registered"); ast_endpoint_blob_publish(d->endpoint, ast_endpoint_state_type(), blob); res = 1; break; } } AST_LIST_UNLOCK(&devices); return res; } static void end_session(struct skinnysession *s) { pthread_cancel(s->t); } #ifdef AST_DEVMODE static char *callstate2str(int ind) { char *tmp; switch (ind) { case SKINNY_OFFHOOK: return "SKINNY_OFFHOOK"; case SKINNY_ONHOOK: return "SKINNY_ONHOOK"; case SKINNY_RINGOUT: return "SKINNY_RINGOUT"; case SKINNY_RINGIN: return "SKINNY_RINGIN"; case SKINNY_CONNECTED: return "SKINNY_CONNECTED"; case SKINNY_BUSY: return "SKINNY_BUSY"; case SKINNY_CONGESTION: return "SKINNY_CONGESTION"; case SKINNY_PROGRESS: return "SKINNY_PROGRESS"; case SKINNY_HOLD: return "SKINNY_HOLD"; case SKINNY_CALLWAIT: return "SKINNY_CALLWAIT"; default: if (!(tmp = ast_threadstorage_get(&callstate2str_threadbuf, CALLSTATE2STR_BUFSIZE))) return "Unknown"; snprintf(tmp, CALLSTATE2STR_BUFSIZE, "UNKNOWN-%d", ind); return tmp; } } #endif static int transmit_response_bysession(struct skinnysession *s, struct skinny_req *req) { int res = 0; if (!s) { ast_log(LOG_WARNING, "Asked to transmit to a non-existent session!\n"); return -1; } ast_mutex_lock(&s->lock); if ((letohl(req->len) > SKINNY_MAX_PACKET) || (letohl(req->len) < 0)) { ast_log(LOG_WARNING, "transmit_response: the length of the request (%u) is out of bounds (%d)\n", letohl(req->len), SKINNY_MAX_PACKET); ast_mutex_unlock(&s->lock); return -1; } memset(s->outbuf, 0, sizeof(s->outbuf)); memcpy(s->outbuf, req, skinny_header_size); memcpy(s->outbuf+skinny_header_size, &req->data, letohl(req->len)); res = write(s->fd, s->outbuf, letohl(req->len)+8); if (res != letohl(req->len)+8) { ast_log(LOG_WARNING, "Transmit: write only sent %d out of %u bytes: %s\n", res, letohl(req->len)+8, strerror(errno)); if (res == -1) { ast_log(LOG_WARNING, "Transmit: Skinny Client was lost, unregistering\n"); end_session(s); } } ast_free(req); ast_mutex_unlock(&s->lock); return 1; } static void transmit_response(struct skinny_device *d, struct skinny_req *req) { transmit_response_bysession(d->session, req); } static void transmit_registerrej(struct skinnysession *s) { struct skinny_req *req; char name[16]; if (!(req = req_alloc(sizeof(struct register_rej_message), REGISTER_REJ_MESSAGE))) return; memcpy(&name, req->data.reg.name, sizeof(name)); snprintf(req->data.regrej.errMsg, sizeof(req->data.regrej.errMsg), "No Authority: %s", name); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting REGISTER_REJ_MESSAGE to UNKNOWN_DEVICE\n"); transmit_response_bysession(s, req); } static void transmit_speaker_mode(struct skinny_device *d, int mode) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct set_speaker_message), SET_SPEAKER_MESSAGE))) return; req->data.setspeaker.mode = htolel(mode); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SET_SPEAKER_MESSAGE to %s, mode %d\n", d->name, mode); transmit_response(d, req); } static void transmit_microphone_mode(struct skinny_device *d, int mode) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct set_microphone_message), SET_MICROPHONE_MESSAGE))) return; req->data.setmicrophone.mode = htolel(mode); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SET_MICROPHONE_MESSAGE to %s, mode %d\n", d->name, mode); transmit_response(d, req); } //static void transmit_callinfo(struct skinny_subchannel *sub) static void transmit_callinfo(struct skinny_device *d, int instance, int callid, char *fromname, char *fromnum, char *toname, char *tonum, int calldirection, char *origtonum, char *origtoname) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct call_info_message), CALL_INFO_MESSAGE))) return; ast_copy_string(req->data.callinfo.callingPartyName, fromname, sizeof(req->data.callinfo.callingPartyName)); ast_copy_string(req->data.callinfo.callingParty, fromnum, sizeof(req->data.callinfo.callingParty)); ast_copy_string(req->data.callinfo.calledPartyName, toname, sizeof(req->data.callinfo.calledPartyName)); ast_copy_string(req->data.callinfo.calledParty, tonum, sizeof(req->data.callinfo.calledParty)); if (origtoname) { ast_copy_string(req->data.callinfo.originalCalledPartyName, origtoname, sizeof(req->data.callinfo.originalCalledPartyName)); } if (origtonum) { ast_copy_string(req->data.callinfo.originalCalledParty, origtonum, sizeof(req->data.callinfo.originalCalledParty)); } req->data.callinfo.instance = htolel(instance); req->data.callinfo.reference = htolel(callid); req->data.callinfo.type = htolel(calldirection); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CALL_INFO_MESSAGE to %s, to %s(%s) from %s(%s), origto %s(%s) (dir=%d) on %s(%d)\n", d->name, toname, tonum, fromname, fromnum, origtoname, origtonum, calldirection, d->name, instance); transmit_response(d, req); } static void transmit_callinfo_variable(struct skinny_device *d, int instance, int callreference, char *fromname, char *fromnum, char *toname, char *tonum, int calldirection, char *origtonum, char *origtoname) { struct skinny_req *req; char *strptr; char *thestrings[13]; int i; int callinfostrleft = MAXCALLINFOSTR; if (!(req = req_alloc(sizeof(struct call_info_message_variable), CALL_INFO_MESSAGE_VARIABLE))) return; req->data.callinfomessagevariable.instance = htolel(instance); req->data.callinfomessagevariable.callreference = htolel(callreference); req->data.callinfomessagevariable.calldirection = htolel(calldirection); req->data.callinfomessagevariable.unknown1 = htolel(0x00); req->data.callinfomessagevariable.unknown2 = htolel(0x00); req->data.callinfomessagevariable.unknown3 = htolel(0x00); req->data.callinfomessagevariable.unknown4 = htolel(0x00); req->data.callinfomessagevariable.unknown5 = htolel(0x00); thestrings[0] = fromnum; thestrings[1] = ""; /* Appears to be origfrom */ if (calldirection == SKINNY_OUTGOING) { thestrings[2] = tonum; thestrings[3] = origtonum; } else { thestrings[2] = ""; thestrings[3] = ""; } thestrings[4] = ""; thestrings[5] = ""; thestrings[6] = ""; thestrings[7] = ""; thestrings[8] = ""; thestrings[9] = fromname; thestrings[10] = toname; thestrings[11] = origtoname; thestrings[12] = ""; strptr = req->data.callinfomessagevariable.calldetails; for(i = 0; i < 13; i++) { if (thestrings[i]) { ast_copy_string(strptr, thestrings[i], callinfostrleft); strptr += strlen(thestrings[i]) + 1; callinfostrleft -= strlen(thestrings[i]) + 1; } else { ast_copy_string(strptr, "", callinfostrleft); strptr++; callinfostrleft--; } } req->len = req->len - (callinfostrleft & ~0x3); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CALL_INFO_MESSAGE_VARIABLE to %s, to %s(%s) from %s(%s), origto %s(%s) (dir=%d) on %s(%d)\n", d->name, toname, tonum, fromname, fromnum, origtoname, origtonum, calldirection, d->name, instance); transmit_response(d, req); } static void send_callinfo(struct skinny_subchannel *sub) { struct ast_channel *ast; struct skinny_device *d; struct skinny_line *l; struct ast_party_id connected_id; char *fromname; char *fromnum; char *toname; char *tonum; if (!sub || !sub->owner || !sub->line || !sub->line->device) { return; } ast = sub->owner; l = sub->line; d = l->device; connected_id = ast_channel_connected_effective_id(ast); if (sub->calldirection == SKINNY_INCOMING) { if ((ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { fromname = S_COR(connected_id.name.valid, connected_id.name.str, ""); fromnum = S_COR(connected_id.number.valid, connected_id.number.str, ""); } else { fromname = ""; fromnum = ""; } toname = S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""); tonum = S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""); } else if (sub->calldirection == SKINNY_OUTGOING) { fromname = S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""); fromnum = S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""); toname = S_COR(ast_channel_connected(ast)->id.name.valid, ast_channel_connected(ast)->id.name.str, ""); tonum = S_COR(ast_channel_connected(ast)->id.number.valid, ast_channel_connected(ast)->id.number.str, l->lastnumberdialed); } else { ast_verb(1, "Error sending Callinfo to %s(%d) - No call direction in sub\n", d->name, l->instance); return; } if (d->protocolversion < 17) { transmit_callinfo(d, l->instance, sub->callid, fromname, fromnum, toname, tonum, sub->calldirection, sub->origtonum, sub->origtoname); } else { transmit_callinfo_variable(d, l->instance, sub->callid, fromname, fromnum, toname, tonum, sub->calldirection, sub->origtonum, sub->origtoname); } } static void push_callinfo(struct skinny_subline *subline, struct skinny_subchannel *sub) { struct ast_channel *ast; struct skinny_device *d; struct skinny_line *l; struct ast_party_id connected_id; char *fromname; char *fromnum; char *toname; char *tonum; if (!sub || !sub->owner || !sub->line || !sub->line->device) { return; } ast = sub->owner; l = sub->line; d = l->device; connected_id = ast_channel_connected_effective_id(ast); if (sub->calldirection == SKINNY_INCOMING) { if((ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { fromname = S_COR(connected_id.name.valid, connected_id.name.str, ""); fromnum = S_COR(connected_id.number.valid, connected_id.number.str, ""); } else { fromname = ""; fromnum = ""; } toname = S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""); tonum = S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""); } else if (sub->calldirection == SKINNY_OUTGOING) { fromname = S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""); fromnum = S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""); toname = S_COR(ast_channel_connected(ast)->id.name.valid, ast_channel_connected(ast)->id.name.str, ""); tonum = S_COR(ast_channel_connected(ast)->id.number.valid, ast_channel_connected(ast)->id.number.str, l->lastnumberdialed); } else { ast_verb(1, "Error sending Callinfo to %s(%d) - No call direction in sub\n", d->name, l->instance); return; } if (d->protocolversion < 17) { transmit_callinfo(subline->line->device, subline->line->instance, subline->callid, fromname, fromnum, toname, tonum, sub->calldirection, sub->origtonum, sub->origtoname); } else { transmit_callinfo_variable(subline->line->device, subline->line->instance, subline->callid, fromname, fromnum, toname, tonum, sub->calldirection, sub->origtonum, sub->origtoname); } } static void transmit_connect(struct skinny_device *d, struct skinny_subchannel *sub) { struct skinny_req *req; struct skinny_line *l = sub->line; struct ast_format *tmpfmt; unsigned int framing; if (!(req = req_alloc(sizeof(struct open_receive_channel_message), OPEN_RECEIVE_CHANNEL_MESSAGE))) return; tmpfmt = ast_format_cap_get_format(l->cap, 0); framing = ast_format_cap_get_format_framing(l->cap, tmpfmt); req->data.openreceivechannel.conferenceId = htolel(sub->callid); req->data.openreceivechannel.partyId = htolel(sub->callid); req->data.openreceivechannel.packets = htolel(framing); req->data.openreceivechannel.capability = htolel(codec_ast2skinny(tmpfmt)); req->data.openreceivechannel.echo = htolel(0); req->data.openreceivechannel.bitrate = htolel(0); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting OPEN_RECEIVE_CHANNEL_MESSAGE to %s, confid %u, partyid %u, ms %u, fmt %d, echo %d, brate %d\n", d->name, sub->callid, sub->callid, framing, codec_ast2skinny(tmpfmt), 0, 0); ao2_ref(tmpfmt, -1); transmit_response(d, req); } static void transmit_start_tone(struct skinny_device *d, int tone, int instance, int reference) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct start_tone_message), START_TONE_MESSAGE))) return; req->data.starttone.tone = htolel(tone); req->data.starttone.instance = htolel(instance); req->data.starttone.reference = htolel(reference); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting START_TONE_MESSAGE to %s, tone %d, inst %d, ref %d\n", d->name, tone, instance, reference); transmit_response(d, req); } static void transmit_stop_tone(struct skinny_device *d, int instance, int reference) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct stop_tone_message), STOP_TONE_MESSAGE))) return; req->data.stoptone.instance = htolel(instance); req->data.stoptone.reference = htolel(reference); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting STOP_TONE_MESSAGE to %s, inst %d, ref %d\n", d->name, instance, reference); transmit_response(d, req); } static int keyset_translatebitmask(int keyset, int intmask) { int extmask = 0; int x, y; const struct soft_key_definitions *softkeymode = soft_key_default_definitions; for(x = 0; x < ARRAY_LEN(soft_key_default_definitions); x++) { if (softkeymode[x].mode == keyset) { const uint8_t *defaults = softkeymode[x].defaults; for (y = 0; y < softkeymode[x].count; y++) { if (intmask & (1 << (defaults[y]))) { extmask |= (1 << ((y))); } } break; } } return extmask; } static void transmit_selectsoftkeys(struct skinny_device *d, int instance, int callid, int softkey, int mask) { struct skinny_req *req; int newmask; if (!(req = req_alloc(sizeof(struct select_soft_keys_message), SELECT_SOFT_KEYS_MESSAGE))) return; newmask = keyset_translatebitmask(softkey, mask); req->data.selectsoftkey.instance = htolel(instance); req->data.selectsoftkey.reference = htolel(callid); req->data.selectsoftkey.softKeySetIndex = htolel(softkey); req->data.selectsoftkey.validKeyMask = htolel(newmask); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SELECT_SOFT_KEYS_MESSAGE to %s, inst %d, callid %d, softkey %d, mask 0x%08x\n", d->name, instance, callid, softkey, (unsigned)newmask); transmit_response(d, req); } static void transmit_lamp_indication(struct skinny_device *d, int stimulus, int instance, int indication) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct set_lamp_message), SET_LAMP_MESSAGE))) return; req->data.setlamp.stimulus = htolel(stimulus); req->data.setlamp.stimulusInstance = htolel(instance); req->data.setlamp.deviceStimulus = htolel(indication); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SET_LAMP_MESSAGE to %s, stim %d, inst %d, ind %d\n", d->name, stimulus, instance, indication); transmit_response(d, req); } static void transmit_ringer_mode(struct skinny_device *d, int mode) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct set_ringer_message), SET_RINGER_MESSAGE))) return; req->data.setringer.ringerMode = htolel(mode); /* XXX okay, I don't quite know what this is, but here's what happens (on a 7960). Note: The phone will always show as ringing on the display. 1: phone will audibly ring over and over 2: phone will audibly ring only once any other value, will NOT cause the phone to audibly ring */ req->data.setringer.unknown1 = htolel(1); /* XXX the value here doesn't seem to change anything. Must be higher than 0. Perhaps a packet capture can shed some light on this. */ req->data.setringer.unknown2 = htolel(1); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SET_RINGER_MESSAGE to %s, mode %d, unk1 1, unk2 1\n", d->name, mode); transmit_response(d, req); } static void transmit_clear_display_message(struct skinny_device *d, int instance, int reference) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct clear_display_message), CLEAR_DISPLAY_MESSAGE))) return; //what do we want hear CLEAR_DISPLAY_MESSAGE or CLEAR_PROMPT_STATUS??? //if we are clearing the display, it appears there is no instance and refernece info (size 0) //req->data.clearpromptstatus.lineInstance = instance; //req->data.clearpromptstatus.callReference = reference; SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CLEAR_DISPLAY_MESSAGE to %s\n", d->name); transmit_response(d, req); } /* This function is not currently used, but will be (wedhorn)*/ /* static void transmit_display_message(struct skinny_device *d, const char *text, int instance, int reference) { struct skinny_req *req; if (text == 0) { ast_verb(1, "Bug, Asked to display empty message\n"); return; } if (!(req = req_alloc(sizeof(struct displaytext_message), DISPLAYTEXT_MESSAGE))) return; ast_copy_string(req->data.displaytext.text, text, sizeof(req->data.displaytext.text)); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAYTEXT_MESSAGE to %s, text %s\n", d->name, text); transmit_response(d, req); } */ static void transmit_displaynotify(struct skinny_device *d, const char *text, int t) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct display_notify_message), DISPLAY_NOTIFY_MESSAGE))) return; ast_copy_string(req->data.displaynotify.displayMessage, text, sizeof(req->data.displaynotify.displayMessage)); req->data.displaynotify.displayTimeout = htolel(t); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_NOTIFY_MESSAGE to %s, text %s\n", d->name, text); transmit_response(d, req); } static void transmit_clearprinotify(struct skinny_device *d, int priority) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct clear_prinotify_message), CLEAR_PRINOTIFY_MESSAGE))) return; req->data.clearprinotify.priority = htolel(priority); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CLEAR_PRINOTIFY_MESSAGE to %s, priority %d\n", d->name, priority); transmit_response(d, req); } static void _transmit_displayprinotify(struct skinny_device *d, const char *text, const char *extratext, int timeout, int priority) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct display_prinotify_message), DISPLAY_PRINOTIFY_MESSAGE))) return; req->data.displayprinotify.timeout = htolel(timeout); req->data.displayprinotify.priority = htolel(priority); if ((char)*text == '\200') { int octalstrlen = strlen(text); ast_copy_string(req->data.displayprinotify.text, text, sizeof(req->data.displayprinotify.text)); ast_copy_string(req->data.displayprinotify.text+octalstrlen, extratext, sizeof(req->data.displayprinotify.text)-octalstrlen); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PRINOTIFY_MESSAGE to %s, '\\%03o\\%03o', '%s', timeout=%d, priority=%d\n", d->name, (unsigned)*text, (unsigned)*(text+1), extratext, timeout, priority); } else { ast_copy_string(req->data.displayprinotify.text, text, sizeof(req->data.displayprinotify.text)); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PRINOTIFY_MESSAGE to %s, '%s', timeout=%d, priority=%d\n", d->name, text, timeout, priority); } transmit_response(d, req); } static void _transmit_displayprinotifyvar(struct skinny_device *d, const char *text, const char *extratext, int timeout, int priority) { struct skinny_req *req; int packetlen; if (!(req = req_alloc(sizeof(struct display_prinotify_message_variable), DISPLAY_PRINOTIFY_MESSAGE_VARIABLE))) return; req->data.displayprinotifyvar.timeout = htolel(timeout); req->data.displayprinotifyvar.priority = htolel(priority); if ((char)*text == '\200') { int octalstrlen = strlen(text); ast_copy_string(req->data.displayprinotifyvar.text, text, sizeof(req->data.displayprinotifyvar.text)); ast_copy_string(req->data.displayprinotifyvar.text+octalstrlen, extratext, sizeof(req->data.displayprinotifyvar.text)-octalstrlen); packetlen = req->len - MAXDISPLAYNOTIFYSTR + strlen(text) + strlen(extratext); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PRINOTIFY_MESSAGE_VARIABLE to %s, '\\%03o\\%03o', '%s', timeout=%d, priority=%d\n", d->name, (unsigned)*text, (unsigned)*(text+1), extratext, timeout, priority); } else { ast_copy_string(req->data.displayprinotifyvar.text, text, sizeof(req->data.displayprinotifyvar.text)); packetlen = req->len - MAXDISPLAYNOTIFYSTR + strlen(text); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PRINOTIFY_MESSAGE_VARIABLE to %s, '%s', timeout=%d, priority=%d\n", d->name, text, timeout, priority); } req->len = (packetlen & ~0x3) + 4; transmit_response(d, req); } static void send_displayprinotify(struct skinny_device *d, const char *text, const char *extratext, int timeout, int priority) { if (d->protocolversion < 17) { _transmit_displayprinotify(d, text, extratext, timeout, priority); } else { _transmit_displayprinotifyvar(d, text, extratext, timeout, priority); } } static void transmit_displaypromptstatus(struct skinny_device *d, const char *text, const char *extratext, int t, int instance, int callid) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct display_prompt_status_message), DISPLAY_PROMPT_STATUS_MESSAGE))) return; req->data.displaypromptstatus.messageTimeout = htolel(t); req->data.displaypromptstatus.lineInstance = htolel(instance); req->data.displaypromptstatus.callReference = htolel(callid); if ((char)*text == '\200') { int octalstrlen = strlen(text); ast_copy_string(req->data.displaypromptstatus.promptMessage, text, sizeof(req->data.displaypromptstatusvar.promptMessage)); ast_copy_string(req->data.displaypromptstatus.promptMessage+octalstrlen, extratext, sizeof(req->data.displaypromptstatus.promptMessage)-octalstrlen); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PROMPT_STATUS_MESSAGE to %s, '\\%03o\\%03o', '%s'\n", d->name, (unsigned)*text, (unsigned)*(text+1), extratext); } else { ast_copy_string(req->data.displaypromptstatus.promptMessage, text, sizeof(req->data.displaypromptstatus.promptMessage)); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PROMPT_STATUS_MESSAGE to %s, '%s'\n", d->name, text); } transmit_response(d, req); } static void transmit_displaypromptstatusvar(struct skinny_device *d, const char *text, const char *extratext, int t, int instance, int callid) { struct skinny_req *req; int packetlen; if (!(req = req_alloc(sizeof(struct display_prompt_status_message_variable), DISPLAY_PROMPT_STATUS_MESSAGE_VARIABLE))) return; req->data.displaypromptstatusvar.lineInstance = htolel(instance); req->data.displaypromptstatusvar.callReference = htolel(callid); if ((char)*text == '\200') { int octalstrlen = strlen(text); ast_copy_string(req->data.displaypromptstatusvar.promptMessage, text, sizeof(req->data.displaypromptstatusvar.promptMessage)); ast_copy_string(req->data.displaypromptstatusvar.promptMessage+octalstrlen, extratext, sizeof(req->data.displaypromptstatusvar.promptMessage)-octalstrlen); packetlen = req->len - MAXCALLINFOSTR + strlen(text) + strlen(extratext); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PROMPT_STATUS_MESSAGE_VARIABLE to %s, '\\%03o\\%03o', '%s'\n", d->name, (unsigned)*text, (unsigned)*(text+1), extratext); } else { ast_copy_string(req->data.displaypromptstatusvar.promptMessage, text, sizeof(req->data.displaypromptstatus.promptMessage)); packetlen = req->len - MAXCALLINFOSTR + strlen(text); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DISPLAY_PROMPT_STATUS_MESSAGE_VARIABLE to %s, '%s'\n", d->name, text); } req->len = (packetlen & ~0x3) + 4; transmit_response(d, req); } static void send_displaypromptstatus(struct skinny_device *d, const char *text, const char *extratext, int t, int instance, int callid) { if (d->protocolversion < 17) { transmit_displaypromptstatus(d, text, extratext, t, instance, callid); } else { transmit_displaypromptstatusvar(d, text, extratext, t, instance, callid); } } static void transmit_clearpromptmessage(struct skinny_device *d, int instance, int callid) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct clear_prompt_message), CLEAR_PROMPT_MESSAGE))) return; req->data.clearpromptstatus.lineInstance = htolel(instance); req->data.clearpromptstatus.callReference = htolel(callid); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CLEAR_PROMPT_MESSAGE to %s, inst %d, callid %d\n", d->name, instance, callid); transmit_response(d, req); } static void transmit_dialednumber(struct skinny_device *d, const char *text, int instance, int callid) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct dialed_number_message), DIALED_NUMBER_MESSAGE))) return; ast_copy_string(req->data.dialednumber.dialedNumber, text, sizeof(req->data.dialednumber.dialedNumber)); req->data.dialednumber.lineInstance = htolel(instance); req->data.dialednumber.callReference = htolel(callid); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DIALED_NUMBER_MESSAGE to %s, num %s, inst %d, callid %d\n", d->name, text, instance, callid); transmit_response(d, req); } static void transmit_closereceivechannel(struct skinny_device *d, struct skinny_subchannel *sub) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE))) return; req->data.closereceivechannel.conferenceId = htolel(0); req->data.closereceivechannel.partyId = htolel(sub->callid); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CLOSE_RECEIVE_CHANNEL_MESSAGE to %s, confid %d, callid %u\n", d->name, 0, sub->callid); transmit_response(d, req); } static void transmit_stopmediatransmission(struct skinny_device *d, struct skinny_subchannel *sub) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE))) return; req->data.stopmedia.conferenceId = htolel(0); req->data.stopmedia.passThruPartyId = htolel(sub->callid); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting STOP_MEDIA_TRANSMISSION_MESSAGE to %s, confid %d, passthrupartyid %u\n", d->name, 0, sub->callid); transmit_response(d, req); } static void transmit_startmediatransmission(struct skinny_device *d, struct skinny_subchannel *sub, struct sockaddr_in dest, struct ast_format *format, unsigned int framing) { struct skinny_req *req; if (d->protocolversion < 17) { if (!(req = req_alloc(sizeof(struct start_media_transmission_message_ip4), START_MEDIA_TRANSMISSION_MESSAGE))) return; req->data.startmedia_ip4.conferenceId = htolel(sub->callid); req->data.startmedia_ip4.passThruPartyId = htolel(sub->callid); req->data.startmedia_ip4.remoteIp = dest.sin_addr.s_addr; req->data.startmedia_ip4.remotePort = htolel(ntohs(dest.sin_port)); req->data.startmedia_ip4.packetSize = htolel(framing); req->data.startmedia_ip4.payloadType = htolel(codec_ast2skinny(format)); req->data.startmedia_ip4.qualifier.precedence = htolel(127); req->data.startmedia_ip4.qualifier.vad = htolel(0); req->data.startmedia_ip4.qualifier.packets = htolel(0); req->data.startmedia_ip4.qualifier.bitRate = htolel(0); } else { if (!(req = req_alloc(sizeof(struct start_media_transmission_message_ip6), START_MEDIA_TRANSMISSION_MESSAGE))) return; req->data.startmedia_ip6.conferenceId = htolel(sub->callid); req->data.startmedia_ip6.passThruPartyId = htolel(sub->callid); memcpy(req->data.startmedia_ip6.remoteIp, &dest.sin_addr.s_addr, sizeof(dest.sin_addr.s_addr)); req->data.startmedia_ip6.remotePort = htolel(ntohs(dest.sin_port)); req->data.startmedia_ip6.packetSize = htolel(framing); req->data.startmedia_ip6.payloadType = htolel(codec_ast2skinny(format)); req->data.startmedia_ip6.qualifier.precedence = htolel(127); req->data.startmedia_ip6.qualifier.vad = htolel(0); req->data.startmedia_ip6.qualifier.packets = htolel(0); req->data.startmedia_ip6.qualifier.bitRate = htolel(0); } SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting START_MEDIA_TRANSMISSION_MESSAGE to %s, callid %u, passthrupartyid %u, ip %s:%d, ms %u, fmt %d, prec 127\n", d->name, sub->callid, sub->callid, ast_inet_ntoa(dest.sin_addr), dest.sin_port, framing, codec_ast2skinny(format)); transmit_response(d, req); } static void transmit_activatecallplane(struct skinny_device *d, struct skinny_line *l) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE))) return; req->data.activatecallplane.lineInstance = htolel(l->instance); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting ACTIVATE_CALL_PLANE_MESSAGE to %s, inst %d\n", d->name, l->instance); transmit_response(d, req); } static void transmit_callstate(struct skinny_device *d, int buttonInstance, unsigned callid, int state) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct call_state_message), CALL_STATE_MESSAGE))) return; req->data.callstate.callState = htolel(state); req->data.callstate.lineInstance = htolel(buttonInstance); req->data.callstate.callReference = htolel(callid); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CALL_STATE_MESSAGE to %s, state %s, inst %d, callid %u\n", d->name, callstate2str(state), buttonInstance, callid); transmit_response(d, req); } static void transmit_cfwdstate(struct skinny_device *d, struct skinny_line *l) { struct skinny_req *req; int anyon = 0; if (!(req = req_alloc(sizeof(struct forward_stat_message), FORWARD_STAT_MESSAGE))) return; if (l->cfwdtype & SKINNY_CFWD_ALL) { if (!ast_strlen_zero(l->call_forward_all)) { ast_copy_string(req->data.forwardstat.fwdallnum, l->call_forward_all, sizeof(req->data.forwardstat.fwdallnum)); req->data.forwardstat.fwdall = htolel(1); anyon++; } else { req->data.forwardstat.fwdall = htolel(0); } } if (l->cfwdtype & SKINNY_CFWD_BUSY) { if (!ast_strlen_zero(l->call_forward_busy)) { ast_copy_string(req->data.forwardstat.fwdbusynum, l->call_forward_busy, sizeof(req->data.forwardstat.fwdbusynum)); req->data.forwardstat.fwdbusy = htolel(1); anyon++; } else { req->data.forwardstat.fwdbusy = htolel(0); } } if (l->cfwdtype & SKINNY_CFWD_NOANSWER) { if (!ast_strlen_zero(l->call_forward_noanswer)) { ast_copy_string(req->data.forwardstat.fwdnoanswernum, l->call_forward_noanswer, sizeof(req->data.forwardstat.fwdnoanswernum)); req->data.forwardstat.fwdnoanswer = htolel(1); anyon++; } else { req->data.forwardstat.fwdnoanswer = htolel(0); } } req->data.forwardstat.lineNumber = htolel(l->instance); if (anyon) req->data.forwardstat.activeforward = htolel(7); else req->data.forwardstat.activeforward = htolel(0); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting FORWARD_STAT_MESSAGE to %s, inst %d, all %s, busy %s, noans %s, acitve %d\n", d->name, l->instance, l->call_forward_all, l->call_forward_busy, l->call_forward_noanswer, anyon ? 7 : 0); transmit_response(d, req); } static void transmit_speeddialstatres(struct skinny_device *d, struct skinny_speeddial *sd) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct speed_dial_stat_res_message), SPEED_DIAL_STAT_RES_MESSAGE))) return; req->data.speeddialreq.speedDialNumber = htolel(sd->instance); ast_copy_string(req->data.speeddial.speedDialDirNumber, sd->exten, sizeof(req->data.speeddial.speedDialDirNumber)); ast_copy_string(req->data.speeddial.speedDialDisplayName, sd->label, sizeof(req->data.speeddial.speedDialDisplayName)); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SPEED_DIAL_STAT_RES_MESSAGE to %s, inst %d, dir %s, display %s\n", d->name, sd->instance, sd->exten, sd->label); transmit_response(d, req); } //static void transmit_linestatres(struct skinny_device *d, struct skinny_line *l) static void transmit_linestatres(struct skinny_device *d, int instance) { struct skinny_req *req; struct skinny_line *l; struct skinny_speeddial *sd; if (!(req = req_alloc(sizeof(struct line_stat_res_message), LINE_STAT_RES_MESSAGE))) return; if ((l = find_line_by_instance(d, instance))) { req->data.linestat.lineNumber = letohl(l->instance); memcpy(req->data.linestat.lineDirNumber, l->name, sizeof(req->data.linestat.lineDirNumber)); memcpy(req->data.linestat.lineDisplayName, l->label, sizeof(req->data.linestat.lineDisplayName)); } else if ((sd = find_speeddial_by_instance(d, instance, 1))) { req->data.linestat.lineNumber = letohl(sd->instance); memcpy(req->data.linestat.lineDirNumber, sd->label, sizeof(req->data.linestat.lineDirNumber)); memcpy(req->data.linestat.lineDisplayName, sd->label, sizeof(req->data.linestat.lineDisplayName)); } SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting LINE_STAT_RES_MESSAGE to %s, inst %d, num %s, label %s\n", d->name, l->instance, req->data.linestat.lineDirNumber, req->data.linestat.lineDisplayName); transmit_response(d, req); } static void transmit_definetimedate(struct skinny_device *d) { struct skinny_req *req; struct timeval now = ast_tvnow(); struct ast_tm cmtime; if (!(req = req_alloc(sizeof(struct definetimedate_message), DEFINETIMEDATE_MESSAGE))) return; ast_localtime(&now, &cmtime, NULL); req->data.definetimedate.year = htolel(cmtime.tm_year+1900); req->data.definetimedate.month = htolel(cmtime.tm_mon+1); req->data.definetimedate.dayofweek = htolel(cmtime.tm_wday); req->data.definetimedate.day = htolel(cmtime.tm_mday); req->data.definetimedate.hour = htolel(cmtime.tm_hour); req->data.definetimedate.minute = htolel(cmtime.tm_min); req->data.definetimedate.seconds = htolel(cmtime.tm_sec); req->data.definetimedate.milliseconds = htolel(cmtime.tm_usec / 1000); req->data.definetimedate.timestamp = htolel(now.tv_sec); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting DEFINETIMEDATE_MESSAGE to %s, date %u %u %u dow %u time %u:%u:%u.%u\n", d->name, req->data.definetimedate.year, req->data.definetimedate.month, req->data.definetimedate.day, req->data.definetimedate.dayofweek, req->data.definetimedate.hour, req->data.definetimedate.minute, req->data.definetimedate.seconds, req->data.definetimedate.milliseconds); transmit_response(d, req); } static void transmit_versionres(struct skinny_device *d) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct version_res_message), VERSION_RES_MESSAGE))) return; ast_copy_string(req->data.version.version, d->version_id, sizeof(req->data.version.version)); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting VERSION_RES_MESSAGE to %s, version %s\n", d->name, d->version_id); transmit_response(d, req); } static void transmit_serverres(struct skinny_device *d) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct server_res_message), SERVER_RES_MESSAGE))) return; memcpy(req->data.serverres.server[0].serverName, ourhost, sizeof(req->data.serverres.server[0].serverName)); req->data.serverres.serverListenPort[0] = htolel(ourport); req->data.serverres.serverIpAddr[0] = htolel(d->ourip.s_addr); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SERVER_RES_MESSAGE to %s, srvname %s %s:%d\n", d->name, ourhost, ast_inet_ntoa(d->ourip), ourport); transmit_response(d, req); } static void transmit_softkeysetres(struct skinny_device *d) { struct skinny_req *req; int i; int x; int y; int keydefcount; const struct soft_key_definitions *softkeymode = soft_key_default_definitions; if (!(req = req_alloc(sizeof(struct soft_key_set_res_message), SOFT_KEY_SET_RES_MESSAGE))) return; SKINNY_DEBUG(DEBUG_TEMPLATE, 3, "Creating Softkey Template\n"); keydefcount = ARRAY_LEN(soft_key_default_definitions); req->data.softkeysets.softKeySetOffset = htolel(0); req->data.softkeysets.softKeySetCount = htolel(keydefcount); req->data.softkeysets.totalSoftKeySetCount = htolel(keydefcount); for (x = 0; x < keydefcount; x++) { const uint8_t *defaults = softkeymode->defaults; /* XXX I wanted to get the size of the array dynamically, but that wasn't wanting to work. This will have to do for now. */ for (y = 0; y < softkeymode->count; y++) { for (i = 0; i < (sizeof(soft_key_template_default) / sizeof(struct soft_key_template_definition)); i++) { if (defaults[y] == i+1) { req->data.softkeysets.softKeySetDefinition[softkeymode->mode].softKeyTemplateIndex[y] = (i+1); req->data.softkeysets.softKeySetDefinition[softkeymode->mode].softKeyInfoIndex[y] = htoles(i+301); SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "softKeySetDefinition : softKeyTemplateIndex: %d softKeyInfoIndex: %d\n", i+1, i+301); } } } softkeymode++; } SKINNY_DEBUG(DEBUG_PACKET | DEBUG_TEMPLATE, 3, "Transmitting SOFT_KEY_SET_RES_MESSAGE to %s, template data\n", d->name); transmit_response(d, req); } static void transmit_softkeytemplateres(struct skinny_device *d) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct soft_key_template_res_message), SOFT_KEY_TEMPLATE_RES_MESSAGE))) return; req->data.softkeytemplate.softKeyOffset = htolel(0); req->data.softkeytemplate.softKeyCount = htolel(sizeof(soft_key_template_default) / sizeof(struct soft_key_template_definition)); req->data.softkeytemplate.totalSoftKeyCount = htolel(sizeof(soft_key_template_default) / sizeof(struct soft_key_template_definition)); memcpy(req->data.softkeytemplate.softKeyTemplateDefinition, soft_key_template_default, sizeof(soft_key_template_default)); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SOFT_KEY_TEMPLATE_RES_MESSAGE to %s, offset 0, keycnt %u, totalkeycnt %u, template data\n", d->name, req->data.softkeytemplate.softKeyCount, req->data.softkeytemplate.totalSoftKeyCount); transmit_response(d, req); } static void transmit_reset(struct skinny_device *d, int fullrestart) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct reset_message), RESET_MESSAGE))) return; if (fullrestart) req->data.reset.resetType = 2; else req->data.reset.resetType = 1; SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting RESET_MESSAGE to %s, type %s\n", d->name, (fullrestart) ? "Restarting" : "Resetting"); transmit_response(d, req); } static void transmit_keepaliveack(struct skinnysession *s) { struct skinny_req *req; if (!(req = req_alloc(0, KEEP_ALIVE_ACK_MESSAGE))) return; #ifdef AST_DEVMODE { struct skinny_device *d = s->device; SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting KEEP_ALIVE_ACK_MESSAGE to %s\n", (d ? d->name : "unregistered")); } #endif transmit_response_bysession(s, req); } static void transmit_registerack(struct skinny_device *d) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct register_ack_message), REGISTER_ACK_MESSAGE))) return; req->data.regack.res[0] = '0'; req->data.regack.res[1] = '\0'; req->data.regack.keepAlive = htolel(keep_alive); memcpy(req->data.regack.dateTemplate, date_format, sizeof(req->data.regack.dateTemplate)); req->data.regack.res2[0] = '0'; req->data.regack.res2[1] = '\0'; req->data.regack.secondaryKeepAlive = htolel(keep_alive); #ifdef AST_DEVMODE { short res = req->data.regack.res[0] << 8 | req->data.regack.res[1]; unsigned int res2 = req->data.regack.res2[0] << 24 | req->data.regack.res2[1] << 16 | req->data.regack.res2[2] << 8 | req->data.regack.res2[3]; SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting REGISTER_ACK_MESSAGE to %s, keepalive %d, datetemplate %s, seckeepalive %d, res 0x%04x, res2 0x%08x\n", d->name, keep_alive, date_format, keep_alive, (unsigned)res, res2); } #endif transmit_response(d, req); } static void transmit_capabilitiesreq(struct skinny_device *d) { struct skinny_req *req; if (!(req = req_alloc(0, CAPABILITIES_REQ_MESSAGE))) return; SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting CAPABILITIES_REQ_MESSAGE to %s\n", d->name); transmit_response(d, req); } static void transmit_backspace(struct skinny_device *d, int instance, unsigned callid) { struct skinny_req *req; if (!(req = req_alloc(sizeof(struct bksp_req_message), BKSP_REQ_MESSAGE))) return; req->data.bkspmessage.instance = htolel(instance); req->data.bkspmessage.callreference = htolel(callid); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting BKSP_REQ_MESSAGE to %s, inst %d, callid %u \n", d->name, instance, callid); transmit_response(d, req); } static void transmit_serviceurlstat(struct skinny_device *d, int instance) { struct skinny_req *req; struct skinny_serviceurl *surl; if (!(req = req_alloc(sizeof(struct serviceurl_stat_message), SERVICEURL_STAT_MESSAGE))) return; AST_LIST_TRAVERSE(&d->serviceurls, surl, list) { if (surl->instance == instance) { break; } } if (surl) { memcpy(req->data.serviceurlmessage.displayName, surl->displayName, sizeof(req->data.serviceurlmessage.displayName)); memcpy(req->data.serviceurlmessage.url, surl->url, sizeof(req->data.serviceurlmessage.url)); } req->data.serviceurlmessage.instance = htolel(instance); SKINNY_DEBUG(DEBUG_PACKET, 3, "Transmitting SERVICEURL_STAT_MESSAGE to %s, inst %d\n", d->name, instance); transmit_response(d, req); } static int skinny_extensionstate_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data) { struct skinny_container *container = data; struct skinny_device *d = NULL; char hint[AST_MAX_EXTENSION]; int state = info->exten_state; /* only interested in device state here */ if (info->reason != AST_HINT_UPDATE_DEVICE) { return 0; } if (container->type == SKINNY_SDCONTAINER) { struct skinny_speeddial *sd = container->data; d = sd->parent; SKINNY_DEBUG(DEBUG_HINT, 3, "Got hint %s on speeddial %s\n", ast_extension_state2str(state), sd->label); if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, sd->context, sd->exten)) { /* If they are not registered, we will override notification and show no availability */ if (ast_device_state(hint) == AST_DEVICE_UNAVAILABLE) { transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_FLASH); transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK); return 0; } switch (state) { case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ case AST_EXTENSION_REMOVED: /* Extension is gone */ SKINNY_DEBUG(DEBUG_HINT, 3, "Extension state: Watcher for hint %s %s. Notify Device %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", d->name); sd->stateid = -1; transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_OFF); transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK); break; case AST_EXTENSION_RINGING: case AST_EXTENSION_UNAVAILABLE: transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_BLINK); transmit_callstate(d, sd->instance, 0, SKINNY_RINGIN); break; case AST_EXTENSION_BUSY: /* callstate = SKINNY_BUSY wasn't wanting to work - I'll settle for this */ case AST_EXTENSION_INUSE: transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_ON); transmit_callstate(d, sd->instance, 0, SKINNY_CALLREMOTEMULTILINE); break; case AST_EXTENSION_ONHOLD: transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_WINK); transmit_callstate(d, sd->instance, 0, SKINNY_HOLD); break; case AST_EXTENSION_NOT_INUSE: default: transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_OFF); transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK); break; } } sd->laststate = state; } else if (container->type == SKINNY_SUBLINECONTAINER) { struct skinny_subline *subline = container->data; struct skinny_line *l = subline->line; d = l->device; SKINNY_DEBUG(DEBUG_HINT, 3, "Got hint %s on subline %s (%s@%s)\n", ast_extension_state2str(state), subline->name, exten, context); subline->extenstate = state; if (subline->callid == 0) { return 0; } switch (state) { case AST_EXTENSION_RINGING: /* Handled by normal ringin */ break; case AST_EXTENSION_INUSE: if (subline->sub && (subline->sub->substate == SKINNY_CONNECTED)) { /* Device has a real call */ transmit_callstate(d, l->instance, subline->callid, SKINNY_CONNECTED); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_CONNECTED, KEYMASK_ALL); send_displaypromptstatus(d, OCTAL_CONNECTED, "", 0, l->instance, subline->callid); } else { /* Some other device has active call */ transmit_callstate(d, l->instance, subline->callid, SKINNY_CALLREMOTEMULTILINE); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLACONNECTEDNOTACTIVE, KEYMASK_ALL); send_displaypromptstatus(d, "In Use", "", 0, l->instance, subline->callid); } transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); transmit_ringer_mode(d, SKINNY_RING_OFF); transmit_activatecallplane(d, l); break; case AST_EXTENSION_ONHOLD: transmit_callstate(d, l->instance, subline->callid, SKINNY_HOLD); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLAHOLD, KEYMASK_ALL); send_displaypromptstatus(d, "Hold", "", 0, l->instance, subline->callid); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); transmit_activatecallplane(d, l); break; case AST_EXTENSION_NOT_INUSE: transmit_callstate(d, l->instance, subline->callid, SKINNY_ONHOOK); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_ONHOOK, KEYMASK_ALL); transmit_clearpromptmessage(d, l->instance, subline->callid); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); transmit_activatecallplane(d, l); subline->callid = 0; break; default: ast_log(LOG_WARNING, "AST_EXTENSION_STATE %s not configured\n", ast_extension_state2str(state)); } } else { ast_log(LOG_WARNING, "Invalid data supplied to skinny_extensionstate_cb\n"); } return 0; } static void update_connectedline(struct skinny_subchannel *sub, const void *data, size_t datalen) { struct ast_channel *c = sub->owner; struct skinny_line *l = sub->line; struct skinny_device *d = l->device; if (!d->session) { return; } if (sub->calldirection == SKINNY_OUTGOING && !sub->origtonum) { /* Do not set origtonum before here or origtoname won't be set */ sub->origtonum = ast_strdup(sub->exten); if (ast_channel_connected(c)->id.name.valid) { sub->origtoname = ast_strdup(ast_channel_connected(c)->id.name.str); } } if (!ast_channel_caller(c)->id.number.valid || ast_strlen_zero(ast_channel_caller(c)->id.number.str) || !ast_channel_connected(c)->id.number.valid || ast_strlen_zero(ast_channel_connected(c)->id.number.str)) return; SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Updating\n", sub->callid); send_callinfo(sub); } static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { struct skinny_line *l = userdata; struct skinny_device *d = l->device; struct skinny_line *l2; int dev_msgs = 0; if (!d || !d->session) { return; } if (msg && ast_mwi_state_type() == stasis_message_type(msg)) { struct ast_mwi_state *mwi_state = stasis_message_data(msg); l->newmsgs = mwi_state->new_msgs; } if (l->newmsgs) { transmit_lamp_indication(d, STIMULUS_VOICEMAIL, l->instance, l->mwiblink?SKINNY_LAMP_BLINK:SKINNY_LAMP_ON); } else { transmit_lamp_indication(d, STIMULUS_VOICEMAIL, l->instance, SKINNY_LAMP_OFF); } /* find out wether the device lamp should be on or off */ AST_LIST_TRAVERSE(&d->lines, l2, list) { if (l2->newmsgs) { dev_msgs++; } } if (dev_msgs) { transmit_lamp_indication(d, STIMULUS_VOICEMAIL, 0, d->mwiblink?SKINNY_LAMP_BLINK:SKINNY_LAMP_ON); } else { transmit_lamp_indication(d, STIMULUS_VOICEMAIL, 0, SKINNY_LAMP_OFF); } ast_verb(3, "Skinny mwi_event_cb found %d new messages\n", l->newmsgs); } /* I do not believe skinny can deal with video. Anyone know differently? */ /* Yes, it can. Currently 7985 and Cisco VT Advantage do video. */ static enum ast_rtp_glue_result skinny_get_vrtp_peer(struct ast_channel *c, struct ast_rtp_instance **instance) { struct skinny_subchannel *sub = NULL; if (!(sub = ast_channel_tech_pvt(c)) || !(sub->vrtp)) return AST_RTP_GLUE_RESULT_FORBID; ao2_ref(sub->vrtp, +1); *instance = sub->vrtp; return AST_RTP_GLUE_RESULT_REMOTE; } static enum ast_rtp_glue_result skinny_get_rtp_peer(struct ast_channel *c, struct ast_rtp_instance **instance) { struct skinny_subchannel *sub = NULL; struct skinny_line *l; enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_REMOTE; SKINNY_DEBUG(DEBUG_AUDIO, 4, "skinny_get_rtp_peer() Channel = %s\n", ast_channel_name(c)); if (!(sub = ast_channel_tech_pvt(c))) return AST_RTP_GLUE_RESULT_FORBID; skinny_locksub(sub); if (!(sub->rtp)){ skinny_unlocksub(sub); return AST_RTP_GLUE_RESULT_FORBID; } ao2_ref(sub->rtp, +1); *instance = sub->rtp; l = sub->line; if (!l->directmedia || l->nat){ res = AST_RTP_GLUE_RESULT_LOCAL; SKINNY_DEBUG(DEBUG_AUDIO, 4, "skinny_get_rtp_peer() Using AST_RTP_GLUE_RESULT_LOCAL \n"); } skinny_unlocksub(sub); return res; } static int skinny_set_rtp_peer(struct ast_channel *c, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp, struct ast_rtp_instance *trtp, const struct ast_format_cap *codecs, int nat_active) { struct skinny_subchannel *sub; struct skinny_line *l; struct skinny_device *d; struct sockaddr_in us = { 0, }; struct sockaddr_in them = { 0, }; struct ast_sockaddr them_tmp; struct ast_sockaddr us_tmp; sub = ast_channel_tech_pvt(c); if (ast_channel_state(c) != AST_STATE_UP) return 0; if (!sub) { return -1; } l = sub->line; d = l->device; if (rtp){ struct ast_format *tmpfmt; unsigned int framing; ast_rtp_instance_get_remote_address(rtp, &them_tmp); ast_sockaddr_to_sin(&them_tmp, &them); /* Shutdown any early-media or previous media on re-invite */ transmit_stopmediatransmission(d, sub); SKINNY_DEBUG(DEBUG_AUDIO, 4, "Peerip = %s:%d\n", ast_inet_ntoa(them.sin_addr), ntohs(them.sin_port)); tmpfmt = ast_format_cap_get_format(l->cap, 0); framing = ast_format_cap_get_format_framing(l->cap, tmpfmt); SKINNY_DEBUG(DEBUG_AUDIO, 4, "Setting payloadType to '%s' (%u ms)\n", ast_format_get_name(tmpfmt), framing); if (!(l->directmedia) || (l->nat)){ ast_rtp_instance_get_local_address(rtp, &us_tmp); ast_sockaddr_to_sin(&us_tmp, &us); us.sin_addr.s_addr = us.sin_addr.s_addr ? us.sin_addr.s_addr : d->ourip.s_addr; transmit_startmediatransmission(d, sub, us, tmpfmt, framing); } else { transmit_startmediatransmission(d, sub, them, tmpfmt, framing); } ao2_ref(tmpfmt, -1); return 0; } /* Need a return here to break the bridge */ return 0; } static struct ast_rtp_glue skinny_rtp_glue = { .type = "Skinny", .get_rtp_info = skinny_get_rtp_peer, .get_vrtp_info = skinny_get_vrtp_peer, .update_peer = skinny_set_rtp_peer, }; #ifdef AST_DEVMODE static char *skinny_debugs(void) { char *ptr; int posn = 0; ptr = dbgcli_buf; strncpy(ptr, "\0", 1); if (skinnydebug & DEBUG_GENERAL) { strncpy(ptr, "general ", 8); posn += 8; ptr += 8; } if (skinnydebug & DEBUG_SUB) { strncpy(ptr, "sub ", 4); posn += 4; ptr += 4; } if (skinnydebug & DEBUG_AUDIO) { strncpy(ptr, "audio ", 6); posn += 6; ptr += 6; } if (skinnydebug & DEBUG_PACKET) { strncpy(ptr, "packet ", 7); posn += 7; ptr += 7; } if (skinnydebug & DEBUG_LOCK) { strncpy(ptr, "lock ", 5); posn += 5; ptr += 5; } if (skinnydebug & DEBUG_TEMPLATE) { strncpy(ptr, "template ", 9); posn += 9; ptr += 9; } if (skinnydebug & DEBUG_THREAD) { strncpy(ptr, "thread ", 7); posn += 7; ptr += 7; } if (skinnydebug & DEBUG_HINT) { strncpy(ptr, "hint ", 5); posn += 5; ptr += 5; } if (skinnydebug & DEBUG_KEEPALIVE) { strncpy(ptr, "keepalive ", 10); posn += 10; ptr += 10; } if (posn > 0) { strncpy(--ptr, "\0", 1); } return dbgcli_buf; } static char *complete_skinny_debug(const char *line, const char *word, int pos, int state) { const char *debugOpts[]={ "all","audio","hint","keepalive","lock","off","packet","show","sub","template","thread",NULL }; char *wordptr = (char *)word; char buf[32]; char *bufptr = buf; int buflen = sizeof(buf); int wordlen; int which = 0; int i = 0; if (*word == '+' || *word == '-' || *word == '!') { *bufptr = *word; wordptr++; bufptr++; buflen--; } wordlen = strlen(wordptr); while (debugOpts[i]) { if (!strncasecmp(wordptr, debugOpts[i], wordlen) && ++which > state) { ast_copy_string(bufptr, debugOpts[i], buflen); return ast_strdup(buf); } i++; } return NULL; } static char *handle_skinny_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int i; int result = 0; const char *arg; int bitmask; int negate; switch (cmd) { case CLI_INIT: e->command = "skinny debug"; e->usage = "Usage: skinny debug {audio|hint|keepalive|lock|off|packet|show|sub|template|thread}\n" " Enables/Disables various Skinny debugging messages\n"; return NULL; case CLI_GENERATE: return complete_skinny_debug(a->line, a->word, a->pos, a->n); } if (a->argc < 3) return CLI_SHOWUSAGE; if (a->argc == 3 && !strncasecmp(a->argv[e->args-1], "show", 4)) { ast_cli(a->fd, "Skinny Debugging - %s\n", skinny_debugs()); return CLI_SUCCESS; } for(i = e->args; i < a->argc; i++) { result++; arg = a->argv[i]; if (!strncasecmp(arg, "off", 3)) { skinnydebug = 0; continue; } if (!strncasecmp(arg, "all", 3)) { skinnydebug = DEBUG_GENERAL|DEBUG_SUB|DEBUG_PACKET|DEBUG_AUDIO|DEBUG_LOCK|DEBUG_TEMPLATE|DEBUG_THREAD|DEBUG_HINT|DEBUG_KEEPALIVE; continue; } if (!strncasecmp(arg, "-", 1) || !strncasecmp(arg, "!", 1)) { negate = 1; arg++; } else if (!strncasecmp(arg, "+", 1)) { negate = 0; arg++; } else { negate = 0; } if (!strncasecmp(arg, "general", 7)) { bitmask = DEBUG_GENERAL; } else if (!strncasecmp(arg, "sub", 3)) { bitmask = DEBUG_SUB; } else if (!strncasecmp(arg, "packet", 6)) { bitmask = DEBUG_PACKET; } else if (!strncasecmp(arg, "audio", 5)) { bitmask = DEBUG_AUDIO; } else if (!strncasecmp(arg, "lock", 4)) { bitmask = DEBUG_LOCK; } else if (!strncasecmp(arg, "template", 8)) { bitmask = DEBUG_TEMPLATE; } else if (!strncasecmp(arg, "thread", 6)) { bitmask = DEBUG_THREAD; } else if (!strncasecmp(arg, "hint", 4)) { bitmask = DEBUG_HINT; } else if (!strncasecmp(arg, "keepalive", 9)) { bitmask = DEBUG_KEEPALIVE; } else { ast_cli(a->fd, "Skinny Debugging - option '%s' unknown\n", a->argv[i]); result--; continue; } if (negate) { skinnydebug &= ~bitmask; } else { skinnydebug |= bitmask; } } if (result) { ast_cli(a->fd, "Skinny Debugging - %s\n", skinnydebug ? skinny_debugs() : "off"); return CLI_SUCCESS; } else { return CLI_SHOWUSAGE; } } #endif static char *handle_skinny_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "skinny reload"; e->usage = "Usage: skinny reload\n" " Reloads the chan_skinny configuration\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != e->args) return CLI_SHOWUSAGE; skinny_reload(); return CLI_SUCCESS; } static char *complete_skinny_devices(const char *word, int state) { struct skinny_device *d; int wordlen = strlen(word), which = 0; AST_LIST_TRAVERSE(&devices, d, list) { if (!strncasecmp(word, d->name, wordlen) && ++which > state) { return ast_strdup(d->name); } } return NULL; } static char *complete_skinny_show_device(const char *line, const char *word, int pos, int state) { return (pos == 3 ? complete_skinny_devices(word, state) : NULL); } static char *complete_skinny_reset(const char *line, const char *word, int pos, int state) { return (pos == 2 ? complete_skinny_devices(word, state) : NULL); } static char *complete_skinny_show_line(const char *line, const char *word, int pos, int state) { struct skinny_device *d; struct skinny_line *l; int wordlen = strlen(word), which = 0; if (pos != 3) return NULL; AST_LIST_TRAVERSE(&devices, d, list) { AST_LIST_TRAVERSE(&d->lines, l, list) { if (!strncasecmp(word, l->name, wordlen) && ++which > state) { return ast_strdup(l->name); } } } return NULL; } static char *handle_skinny_reset(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct skinny_device *d; switch (cmd) { case CLI_INIT: e->command = "skinny reset"; e->usage = "Usage: skinny reset [restart]\n" " Causes a Skinny device to reset itself, optionally with a full restart\n"; return NULL; case CLI_GENERATE: return complete_skinny_reset(a->line, a->word, a->pos, a->n); } if (a->argc < 3 || a->argc > 4) return CLI_SHOWUSAGE; AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list) { int resetonly = 1; if (!strcasecmp(a->argv[2], d->id) || !strcasecmp(a->argv[2], d->name) || !strcasecmp(a->argv[2], "all")) { if (!(d->session)) continue; if (a->argc == 4 && !strcasecmp(a->argv[3], "restart")) resetonly = 0; transmit_reset(d, resetonly); } } AST_LIST_UNLOCK(&devices); return CLI_SUCCESS; } static char *device2str(int type) { char *tmp; switch (type) { case SKINNY_DEVICE_NONE: return "No Device"; case SKINNY_DEVICE_30SPPLUS: return "30SP Plus"; case SKINNY_DEVICE_12SPPLUS: return "12SP Plus"; case SKINNY_DEVICE_12SP: return "12SP"; case SKINNY_DEVICE_12: return "12"; case SKINNY_DEVICE_30VIP: return "30VIP"; case SKINNY_DEVICE_7910: return "7910"; case SKINNY_DEVICE_7960: return "7960"; case SKINNY_DEVICE_7940: return "7940"; case SKINNY_DEVICE_7935: return "7935"; case SKINNY_DEVICE_ATA186: return "ATA186"; case SKINNY_DEVICE_7941: return "7941"; case SKINNY_DEVICE_7971: return "7971"; case SKINNY_DEVICE_7914: return "7914"; case SKINNY_DEVICE_7985: return "7985"; case SKINNY_DEVICE_7911: return "7911"; case SKINNY_DEVICE_7961GE: return "7961GE"; case SKINNY_DEVICE_7941GE: return "7941GE"; case SKINNY_DEVICE_7931: return "7931"; case SKINNY_DEVICE_7921: return "7921"; case SKINNY_DEVICE_7906: return "7906"; case SKINNY_DEVICE_7962: return "7962"; case SKINNY_DEVICE_7937: return "7937"; case SKINNY_DEVICE_7942: return "7942"; case SKINNY_DEVICE_7945: return "7945"; case SKINNY_DEVICE_7965: return "7965"; case SKINNY_DEVICE_7975: return "7975"; case SKINNY_DEVICE_7905: return "7905"; case SKINNY_DEVICE_7920: return "7920"; case SKINNY_DEVICE_7970: return "7970"; case SKINNY_DEVICE_7912: return "7912"; case SKINNY_DEVICE_7902: return "7902"; case SKINNY_DEVICE_CIPC: return "IP Communicator"; case SKINNY_DEVICE_7961: return "7961"; case SKINNY_DEVICE_7936: return "7936"; case SKINNY_DEVICE_SCCPGATEWAY_AN: return "SCCPGATEWAY_AN"; case SKINNY_DEVICE_SCCPGATEWAY_BRI: return "SCCPGATEWAY_BRI"; case SKINNY_DEVICE_UNKNOWN: return "Unknown"; default: if (!(tmp = ast_threadstorage_get(&device2str_threadbuf, DEVICE2STR_BUFSIZE))) return "Unknown"; snprintf(tmp, DEVICE2STR_BUFSIZE, "UNKNOWN-%d", type); return tmp; } } static char *_skinny_show_devices(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char * const *argv) { struct skinny_device *d; struct skinny_line *l; const char *id; char idtext[256] = ""; int total_devices = 0; if (s) { /* Manager - get ActionID */ id = astman_get_header(m, "ActionID"); if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); } switch (argc) { case 3: break; default: return CLI_SHOWUSAGE; } if (!s) { ast_cli(fd, "Name DeviceId IP Type R NL\n"); ast_cli(fd, "-------------------- ---------------- --------------- --------------- - --\n"); } AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list) { int numlines = 0; total_devices++; AST_LIST_TRAVERSE(&d->lines, l, list) { numlines++; } if (!s) { ast_cli(fd, "%-20s %-16s %-15s %-15s %c %2d\n", d->name, d->id, d->session?ast_inet_ntoa(d->session->sin.sin_addr):"", device2str(d->type), d->session?'Y':'N', numlines); } else { astman_append(s, "Event: DeviceEntry\r\n%s" "Channeltype: SKINNY\r\n" "ObjectName: %s\r\n" "ChannelObjectType: device\r\n" "DeviceId: %s\r\n" "IPaddress: %s\r\n" "Type: %s\r\n" "Devicestatus: %s\r\n" "NumberOfLines: %d\r\n", idtext, d->name, d->id, d->session?ast_inet_ntoa(d->session->sin.sin_addr):"-none-", device2str(d->type), d->session?"registered":"unregistered", numlines); } } AST_LIST_UNLOCK(&devices); if (total) *total = total_devices; return CLI_SUCCESS; } /*! \brief Show SKINNY devices in the manager API */ /* Inspired from chan_sip */ static int manager_skinny_show_devices(struct mansession *s, const struct message *m) { const char *id = astman_get_header(m, "ActionID"); const char *a[] = {"skinny", "show", "devices"}; char idtext[256] = ""; int total = 0; if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); astman_send_listack(s, m, "Device status list will follow", "start"); /* List the devices in separate manager events */ _skinny_show_devices(-1, &total, s, m, 3, a); /* Send final confirmation */ astman_append(s, "Event: DevicelistComplete\r\n" "EventList: Complete\r\n" "ListItems: %d\r\n" "%s" "\r\n", total, idtext); return 0; } static char *handle_skinny_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "skinny show devices"; e->usage = "Usage: skinny show devices\n" " Lists all devices known to the Skinny subsystem.\n"; return NULL; case CLI_GENERATE: return NULL; } return _skinny_show_devices(a->fd, NULL, NULL, NULL, a->argc, a->argv); } static char *_skinny_show_device(int type, int fd, struct mansession *s, const struct message *m, int argc, const char * const *argv) { struct skinny_device *d; struct skinny_line *l; struct skinny_speeddial *sd; struct skinny_addon *sa; struct skinny_serviceurl *surl; struct ast_str *codec_buf = ast_str_alloca(64); if (argc < 4) { return CLI_SHOWUSAGE; } AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list) { if (!strcasecmp(argv[3], d->id) || !strcasecmp(argv[3], d->name)) { int numlines = 0, numaddons = 0, numspeeddials = 0, numserviceurls = 0; AST_LIST_TRAVERSE(&d->lines, l, list){ numlines++; } AST_LIST_TRAVERSE(&d->addons, sa, list) { numaddons++; } AST_LIST_TRAVERSE(&d->speeddials, sd, list) { numspeeddials++; } AST_LIST_TRAVERSE(&d->serviceurls, surl, list) { numserviceurls++; } if (type == 0) { /* CLI */ ast_cli(fd, "Name: %s\n", d->name); ast_cli(fd, "Id: %s\n", d->id); ast_cli(fd, "version: %s\n", S_OR(d->version_id, "Unknown")); ast_cli(fd, "Ip address: %s\n", (d->session ? ast_inet_ntoa(d->session->sin.sin_addr) : "Unknown")); ast_cli(fd, "Port: %d\n", (d->session ? ntohs(d->session->sin.sin_port) : 0)); ast_cli(fd, "Device Type: %s\n", device2str(d->type)); ast_cli(fd, "Conf Codecs: %s\n", ast_format_cap_get_names(d->confcap, &codec_buf)); ast_cli(fd, "Neg Codecs: %s\n", ast_format_cap_get_names(d->cap, &codec_buf)); ast_cli(fd, "Registered: %s\n", (d->session ? "Yes" : "No")); ast_cli(fd, "Lines: %d\n", numlines); AST_LIST_TRAVERSE(&d->lines, l, list) { ast_cli(fd, " %s (%s)\n", l->name, l->label); } ast_cli(fd, "Addons: %d\n", numaddons); AST_LIST_TRAVERSE(&d->addons, sa, list) { ast_cli(fd, " %s\n", sa->type); } ast_cli(fd, "Speeddials: %d\n", numspeeddials); AST_LIST_TRAVERSE(&d->speeddials, sd, list) { ast_cli(fd, " %s (%s) ishint: %d\n", sd->exten, sd->label, sd->isHint); } ast_cli(fd, "ServiceURLs: %d\n", numserviceurls); AST_LIST_TRAVERSE(&d->serviceurls, surl, list) { ast_cli(fd, " %s (%s)\n", surl->displayName, surl->url); } } else { /* manager */ astman_append(s, "Channeltype: SKINNY\r\n"); astman_append(s, "ObjectName: %s\r\n", d->name); astman_append(s, "ChannelObjectType: device\r\n"); astman_append(s, "Id: %s\r\n", d->id); astman_append(s, "version: %s\r\n", S_OR(d->version_id, "Unknown")); astman_append(s, "Ipaddress: %s\r\n", (d->session ? ast_inet_ntoa(d->session->sin.sin_addr) : "Unknown")); astman_append(s, "Port: %d\r\n", (d->session ? ntohs(d->session->sin.sin_port) : 0)); astman_append(s, "DeviceType: %s\r\n", device2str(d->type)); astman_append(s, "Codecs: %s\r\n", ast_format_cap_get_names(d->confcap, &codec_buf)); astman_append(s, "CodecOrder: %s\r\n", ast_format_cap_get_names(d->cap, &codec_buf)); astman_append(s, "Devicestatus: %s\r\n", (d->session?"registered":"unregistered")); astman_append(s, "NumberOfLines: %d\r\n", numlines); AST_LIST_TRAVERSE(&d->lines, l, list) { astman_append(s, "Line: %s (%s)\r\n", l->name, l->label); } astman_append(s, "NumberOfAddons: %d\r\n", numaddons); AST_LIST_TRAVERSE(&d->addons, sa, list) { astman_append(s, "Addon: %s\r\n", sa->type); } astman_append(s, "NumberOfSpeeddials: %d\r\n", numspeeddials); AST_LIST_TRAVERSE(&d->speeddials, sd, list) { astman_append(s, "Speeddial: %s (%s) ishint: %d\r\n", sd->exten, sd->label, sd->isHint); } astman_append(s, "ServiceURLs: %d\r\n", numserviceurls); AST_LIST_TRAVERSE(&d->serviceurls, surl, list) { astman_append(s, " %s (%s)\r\n", surl->displayName, surl->url); } } } } AST_LIST_UNLOCK(&devices); return CLI_SUCCESS; } static int manager_skinny_show_device(struct mansession *s, const struct message *m) { const char *a[4]; const char *device; device = astman_get_header(m, "Device"); if (ast_strlen_zero(device)) { astman_send_error(s, m, "Device: missing."); return 0; } a[0] = "skinny"; a[1] = "show"; a[2] = "device"; a[3] = device; _skinny_show_device(1, -1, s, m, 4, a); astman_append(s, "\r\n\r\n" ); return 0; } /*! \brief Show device information */ static char *handle_skinny_show_device(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "skinny show device"; e->usage = "Usage: skinny show device \n" " Lists all deviceinformation of a specific device known to the Skinny subsystem.\n"; return NULL; case CLI_GENERATE: return complete_skinny_show_device(a->line, a->word, a->pos, a->n); } return _skinny_show_device(0, a->fd, NULL, NULL, a->argc, a->argv); } static char *_skinny_show_lines(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char * const *argv) { struct skinny_line *l; struct skinny_subchannel *sub; int total_lines = 0; int verbose = 0; const char *id; char idtext[256] = ""; if (s) { /* Manager - get ActionID */ id = astman_get_header(m, "ActionID"); if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); } switch (argc) { case 4: verbose = 1; break; case 3: verbose = 0; break; default: return CLI_SHOWUSAGE; } if (!s) { ast_cli(fd, "Name Device Name Instance Label \n"); ast_cli(fd, "-------------------- -------------------- -------- --------------------\n"); } AST_LIST_LOCK(&lines); AST_LIST_TRAVERSE(&lines, l, all) { total_lines++; if (!s) { ast_cli(fd, "%-20s %-20s %8d %-20s\n", l->name, (l->device ? l->device->name : "Not connected"), l->instance, l->label); if (verbose) { AST_LIST_TRAVERSE(&l->sub, sub, list) { RAII_VAR(struct ast_channel *, bridged, ast_channel_bridge_peer(sub->owner), ao2_cleanup); ast_cli(fd, " %s> %s to %s\n", (sub == l->activesub?"Active ":"Inactive"), ast_channel_name(sub->owner), bridged ? ast_channel_name(bridged) : "" ); } } } else { astman_append(s, "Event: LineEntry\r\n%s" "Channeltype: SKINNY\r\n" "ObjectName: %s\r\n" "ChannelObjectType: line\r\n" "Device: %s\r\n" "Instance: %d\r\n" "Label: %s\r\n", idtext, l->name, (l->device ? l->device->name : "None"), l->instance, l->label); } } AST_LIST_UNLOCK(&lines); if (total) { *total = total_lines; } return CLI_SUCCESS; } /*! \brief Show Skinny lines in the manager API */ /* Inspired from chan_sip */ static int manager_skinny_show_lines(struct mansession *s, const struct message *m) { const char *id = astman_get_header(m, "ActionID"); const char *a[] = {"skinny", "show", "lines"}; char idtext[256] = ""; int total = 0; if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); astman_send_listack(s, m, "Line status list will follow", "start"); /* List the lines in separate manager events */ _skinny_show_lines(-1, &total, s, m, 3, a); /* Send final confirmation */ astman_append(s, "Event: LinelistComplete\r\n" "EventList: Complete\r\n" "ListItems: %d\r\n" "%s" "\r\n", total, idtext); return 0; } static char *handle_skinny_show_lines(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "skinny show lines [verbose]"; e->usage = "Usage: skinny show lines\n" " Lists all lines known to the Skinny subsystem.\n" " If 'verbose' is specified, the output includes\n" " information about subs for each line.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == e->args) { if (strcasecmp(a->argv[e->args-1], "verbose")) { return CLI_SHOWUSAGE; } } else if (a->argc != e->args - 1) { return CLI_SHOWUSAGE; } return _skinny_show_lines(a->fd, NULL, NULL, NULL, a->argc, a->argv); } static char *_skinny_show_line(int type, int fd, struct mansession *s, const struct message *m, int argc, const char * const *argv) { struct skinny_device *d; struct skinny_line *l; struct skinny_subline *subline; struct ast_str *codec_buf = ast_str_alloca(64); char group_buf[256]; char cbuf[256]; switch (argc) { case 4: break; case 6: break; default: return CLI_SHOWUSAGE; } AST_LIST_LOCK(&devices); /* Show all lines matching the one supplied */ AST_LIST_TRAVERSE(&devices, d, list) { if (argc == 6 && (strcasecmp(argv[5], d->id) && strcasecmp(argv[5], d->name))) { continue; } AST_LIST_TRAVERSE(&d->lines, l, list) { struct ast_str *tmp_str = ast_str_alloca(512); if (strcasecmp(argv[3], l->name)) { continue; } if (type == 0) { /* CLI */ ast_cli(fd, "Line: %s\n", l->name); ast_cli(fd, "On Device: %s\n", d->name); ast_cli(fd, "Line Label: %s\n", l->label); ast_cli(fd, "Extension: %s\n", S_OR(l->exten, "")); ast_cli(fd, "Context: %s\n", l->context); ast_cli(fd, "CallGroup: %s\n", ast_print_group(group_buf, sizeof(group_buf), l->callgroup)); ast_cli(fd, "PickupGroup: %s\n", ast_print_group(group_buf, sizeof(group_buf), l->pickupgroup)); ast_cli(fd, "NamedCallGroup: %s\n", ast_print_namedgroups(&tmp_str, l->named_callgroups)); ast_str_reset(tmp_str); ast_cli(fd, "NamedPickupGroup: %s\n", ast_print_namedgroups(&tmp_str, l->named_pickupgroups)); ast_str_reset(tmp_str); ast_cli(fd, "Language: %s\n", S_OR(l->language, "")); ast_cli(fd, "Accountcode: %s\n", S_OR(l->accountcode, "")); ast_cli(fd, "AmaFlag: %s\n", ast_channel_amaflags2string(l->amaflags)); ast_cli(fd, "CallerId Number: %s\n", S_OR(l->cid_num, "")); ast_cli(fd, "CallerId Name: %s\n", S_OR(l->cid_name, "")); ast_cli(fd, "Hide CallerId: %s\n", (l->hidecallerid ? "Yes" : "No")); ast_cli(fd, "CFwdAll: %s\n", S_COR((l->cfwdtype & SKINNY_CFWD_ALL), l->call_forward_all, "")); ast_cli(fd, "CFwdBusy: %s\n", S_COR((l->cfwdtype & SKINNY_CFWD_BUSY), l->call_forward_busy, "")); ast_cli(fd, "CFwdNoAnswer: %s\n", S_COR((l->cfwdtype & SKINNY_CFWD_NOANSWER), l->call_forward_noanswer, "")); ast_cli(fd, "CFwdTimeout: %dms\n", l->callfwdtimeout); ast_cli(fd, "VoicemailBox: %s\n", S_OR(l->mailbox, "")); ast_cli(fd, "VoicemailNumber: %s\n", S_OR(l->vmexten, "")); ast_cli(fd, "MWIblink: %d\n", l->mwiblink); ast_cli(fd, "Regextension: %s\n", S_OR(l->regexten, "")); ast_cli(fd, "Regcontext: %s\n", S_OR(l->regcontext, "")); ast_cli(fd, "MoHInterpret: %s\n", S_OR(l->mohinterpret, "")); ast_cli(fd, "MoHSuggest: %s\n", S_OR(l->mohsuggest, "")); ast_cli(fd, "Last dialed nr: %s\n", S_OR(l->lastnumberdialed, "")); ast_cli(fd, "Last CallerID: %s\n", S_OR(l->lastcallerid, "")); ast_cli(fd, "Transfer enabled: %s\n", (l->transfer ? "Yes" : "No")); ast_cli(fd, "Callwaiting: %s\n", (l->callwaiting ? "Yes" : "No")); ast_cli(fd, "3Way Calling: %s\n", (l->threewaycalling ? "Yes" : "No")); ast_cli(fd, "Can forward: %s\n", (l->cancallforward ? "Yes" : "No")); ast_cli(fd, "Do Not Disturb: %s\n", (l->dnd ? "Yes" : "No")); ast_cli(fd, "NAT: %s\n", (l->nat ? "Yes" : "No")); ast_cli(fd, "immediate: %s\n", (l->immediate ? "Yes" : "No")); ast_cli(fd, "Group: %d\n", l->group); ast_cli(fd, "Parkinglot: %s\n", S_OR(l->parkinglot, "")); ast_cli(fd, "Conf Codecs: %s\n", ast_format_cap_get_names(l->confcap, &codec_buf)); ast_cli(fd, "Neg Codecs: %s\n", ast_format_cap_get_names(l->cap, &codec_buf)); if (AST_LIST_FIRST(&l->sublines)) { ast_cli(fd, "Sublines:\n"); AST_LIST_TRAVERSE(&l->sublines, subline, list) { ast_cli(fd, " %s, %s@%s\n", subline->name, subline->exten, subline->context); } } ast_cli(fd, "\n"); } else { /* manager */ astman_append(s, "Channeltype: SKINNY\r\n"); astman_append(s, "ObjectName: %s\r\n", l->name); astman_append(s, "ChannelObjectType: line\r\n"); astman_append(s, "Device: %s\r\n", d->name); astman_append(s, "LineLabel: %s\r\n", l->label); astman_append(s, "Extension: %s\r\n", S_OR(l->exten, "")); astman_append(s, "Context: %s\r\n", l->context); astman_append(s, "CallGroup: %s\r\n", ast_print_group(group_buf, sizeof(group_buf), l->callgroup)); astman_append(s, "PickupGroup: %s\r\n", ast_print_group(group_buf, sizeof(group_buf), l->pickupgroup)); astman_append(s, "NamedCallGroup: %s\r\n", ast_print_namedgroups(&tmp_str, l->named_callgroups)); ast_str_reset(tmp_str); astman_append(s, "NamedPickupGroup: %s\r\n", ast_print_namedgroups(&tmp_str, l->named_pickupgroups)); ast_str_reset(tmp_str); astman_append(s, "Language: %s\r\n", S_OR(l->language, "")); astman_append(s, "Accountcode: %s\r\n", S_OR(l->accountcode, "")); astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(l->amaflags)); astman_append(s, "Callerid: %s\r\n", ast_callerid_merge(cbuf, sizeof(cbuf), l->cid_name, l->cid_num, "")); astman_append(s, "HideCallerId: %s\r\n", (l->hidecallerid ? "Yes" : "No")); astman_append(s, "CFwdAll: %s\r\n", S_COR((l->cfwdtype & SKINNY_CFWD_ALL), l->call_forward_all, "")); astman_append(s, "CFwdBusy: %s\r\n", S_COR((l->cfwdtype & SKINNY_CFWD_BUSY), l->call_forward_busy, "")); astman_append(s, "CFwdNoAnswer: %s\r\n", S_COR((l->cfwdtype & SKINNY_CFWD_NOANSWER), l->call_forward_noanswer, "")); astman_append(s, "VoicemailBox: %s\r\n", S_OR(l->mailbox, "")); astman_append(s, "VoicemailNumber: %s\r\n", S_OR(l->vmexten, "")); astman_append(s, "MWIblink: %d\r\n", l->mwiblink); astman_append(s, "RegExtension: %s\r\n", S_OR(l->regexten, "")); astman_append(s, "Regcontext: %s\r\n", S_OR(l->regcontext, "")); astman_append(s, "MoHInterpret: %s\r\n", S_OR(l->mohinterpret, "")); astman_append(s, "MoHSuggest: %s\r\n", S_OR(l->mohsuggest, "")); astman_append(s, "LastDialedNr: %s\r\n", S_OR(l->lastnumberdialed, "")); astman_append(s, "LastCallerID: %s\r\n", S_OR(l->lastcallerid, "")); astman_append(s, "Transfer: %s\r\n", (l->transfer ? "Yes" : "No")); astman_append(s, "Callwaiting: %s\r\n", (l->callwaiting ? "Yes" : "No")); astman_append(s, "3WayCalling: %s\r\n", (l->threewaycalling ? "Yes" : "No")); astman_append(s, "CanForward: %s\r\n", (l->cancallforward ? "Yes" : "No")); astman_append(s, "DoNotDisturb: %s\r\n", (l->dnd ? "Yes" : "No")); astman_append(s, "NAT: %s\r\n", (l->nat ? "Yes" : "No")); astman_append(s, "immediate: %s\r\n", (l->immediate ? "Yes" : "No")); astman_append(s, "Group: %d\r\n", l->group); astman_append(s, "Parkinglot: %s\r\n", S_OR(l->parkinglot, "")); astman_append(s, "Codecs: %s\r\n", ast_format_cap_get_names(l->confcap, &codec_buf)); astman_append(s, "\r\n"); } } } AST_LIST_UNLOCK(&devices); return CLI_SUCCESS; } static int manager_skinny_show_line(struct mansession *s, const struct message *m) { const char *a[4]; const char *line; line = astman_get_header(m, "Line"); if (ast_strlen_zero(line)) { astman_send_error(s, m, "Line: missing."); return 0; } a[0] = "skinny"; a[1] = "show"; a[2] = "line"; a[3] = line; _skinny_show_line(1, -1, s, m, 4, a); astman_append(s, "\r\n\r\n" ); return 0; } /*! \brief List line information. */ static char *handle_skinny_show_line(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "skinny show line"; e->usage = "Usage: skinny show line [ on ]\n" " List all lineinformation of a specific line known to the Skinny subsystem.\n"; return NULL; case CLI_GENERATE: return complete_skinny_show_line(a->line, a->word, a->pos, a->n); } return _skinny_show_line(0, a->fd, NULL, NULL, a->argc, a->argv); } /*! \brief List global settings for the Skinny subsystem. */ static char *handle_skinny_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char immed_str[2] = {immed_dialchar, '\0'}; switch (cmd) { case CLI_INIT: e->command = "skinny show settings"; e->usage = "Usage: skinny show settings\n" " Lists all global configuration settings of the Skinny subsystem.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, "\nGlobal Settings:\n"); ast_cli(a->fd, " Skinny Port: %d\n", ntohs(bindaddr.sin_port)); ast_cli(a->fd, " Bindaddress: %s\n", ast_inet_ntoa(bindaddr.sin_addr)); ast_cli(a->fd, " KeepAlive: %d\n", keep_alive); ast_cli(a->fd, " Date Format: %s\n", date_format); ast_cli(a->fd, " Voice Mail Extension: %s\n", S_OR(vmexten, "(not set)")); ast_cli(a->fd, " Reg. context: %s\n", S_OR(regcontext, "(not set)")); ast_cli(a->fd, " Immed. Dial Key: %s\n", S_OR(immed_str, "(not set)")); ast_cli(a->fd, " Jitterbuffer enabled: %s\n", AST_CLI_YESNO(ast_test_flag(&global_jbconf, AST_JB_ENABLED))); if (ast_test_flag(&global_jbconf, AST_JB_ENABLED)) { ast_cli(a->fd, " Jitterbuffer forced: %s\n", AST_CLI_YESNO(ast_test_flag(&global_jbconf, AST_JB_FORCED))); ast_cli(a->fd, " Jitterbuffer max size: %ld\n", global_jbconf.max_size); ast_cli(a->fd, " Jitterbuffer resync: %ld\n", global_jbconf.resync_threshold); ast_cli(a->fd, " Jitterbuffer impl: %s\n", global_jbconf.impl); if (!strcasecmp(global_jbconf.impl, "adaptive")) { ast_cli(a->fd, " Jitterbuffer tgt extra: %ld\n", global_jbconf.target_extra); } ast_cli(a->fd, " Jitterbuffer log: %s\n", AST_CLI_YESNO(ast_test_flag(&global_jbconf, AST_JB_LOG))); } return CLI_SUCCESS; } static char *_skinny_message_set(int type, int fd, struct mansession *s, const struct message *m, int argc, const char * const *argv) { struct skinny_device *d; char text_buf[32]; if (argc < 7) { return CLI_SHOWUSAGE; } AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list) { if (!strcasecmp(argv[3], d->name)) { int i; char *strp = text_buf; int charleft = sizeof(text_buf); int priority = atoi(argv[4]); int timeout = atoi(argv[5]); ast_copy_string(strp, argv[6], charleft); charleft -= strlen(strp); strp += strlen(strp); for(i=7; icommand = "skinny message set"; e->usage = "Usage: skinny message set \n" " Set the current priority level message on a device.\n"; return NULL; case CLI_GENERATE: return complete_skinny_show_device(a->line, a->word, a->pos, a->n); } return _skinny_message_set(0, a->fd, NULL, NULL, a->argc, a->argv); } static char *_skinny_message_clear(int type, int fd, struct mansession *s, const struct message *m, int argc, const char * const *argv) { struct skinny_device *d; if (argc != 5) { return CLI_SHOWUSAGE; } AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list) { if (!strcasecmp(argv[3], d->name)) { int priority = atoi(argv[4]); transmit_clearprinotify(d, priority); } } AST_LIST_UNLOCK(&devices); return CLI_SUCCESS; } /*! \brief Handle clearing messages to devices. */ static char *handle_skinny_message_clear(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "skinny message clear"; e->usage = "Usage: skinny message clear \n" " Clear the current priority level message on device.\n"; return NULL; case CLI_GENERATE: return complete_skinny_show_device(a->line, a->word, a->pos, a->n); } return _skinny_message_clear(0, a->fd, NULL, NULL, a->argc, a->argv); } static struct ast_cli_entry cli_skinny[] = { AST_CLI_DEFINE(handle_skinny_show_devices, "List defined Skinny devices"), AST_CLI_DEFINE(handle_skinny_show_device, "List Skinny device information"), AST_CLI_DEFINE(handle_skinny_show_lines, "List defined Skinny lines per device"), AST_CLI_DEFINE(handle_skinny_show_line, "List Skinny line information"), AST_CLI_DEFINE(handle_skinny_show_settings, "List global Skinny settings"), #ifdef AST_DEVMODE AST_CLI_DEFINE(handle_skinny_set_debug, "Enable/Disable Skinny debugging"), #endif AST_CLI_DEFINE(handle_skinny_reset, "Reset Skinny device(s)"), AST_CLI_DEFINE(handle_skinny_reload, "Reload Skinny config"), AST_CLI_DEFINE(handle_skinny_message_set, "Send message to devices"), AST_CLI_DEFINE(handle_skinny_message_clear, "Clear message to devices"), }; static void start_rtp(struct skinny_subchannel *sub) { struct skinny_line *l = sub->line; struct skinny_device *d = l->device; int hasvideo = 0; struct ast_sockaddr bindaddr_tmp; skinny_locksub(sub); SKINNY_DEBUG(DEBUG_AUDIO, 3, "Sub %u - Starting RTP\n", sub->callid); ast_sockaddr_from_sin(&bindaddr_tmp, &bindaddr); sub->rtp = ast_rtp_instance_new("asterisk", sched, &bindaddr_tmp, NULL); if (hasvideo) sub->vrtp = ast_rtp_instance_new("asterisk", sched, &bindaddr_tmp, NULL); if (sub->rtp) { ast_rtp_instance_set_prop(sub->rtp, AST_RTP_PROPERTY_RTCP, 1); } if (sub->vrtp) { ast_rtp_instance_set_prop(sub->vrtp, AST_RTP_PROPERTY_RTCP, 1); } if (sub->rtp && sub->owner) { ast_rtp_instance_set_channel_id(sub->rtp, ast_channel_uniqueid(sub->owner)); ast_channel_set_fd(sub->owner, 0, ast_rtp_instance_fd(sub->rtp, 0)); ast_channel_set_fd(sub->owner, 1, ast_rtp_instance_fd(sub->rtp, 1)); } if (hasvideo && sub->vrtp && sub->owner) { ast_rtp_instance_set_channel_id(sub->vrtp, ast_channel_uniqueid(sub->owner)); ast_channel_set_fd(sub->owner, 2, ast_rtp_instance_fd(sub->vrtp, 0)); ast_channel_set_fd(sub->owner, 3, ast_rtp_instance_fd(sub->vrtp, 1)); } if (sub->rtp) { ast_rtp_instance_set_qos(sub->rtp, qos.tos_audio, qos.cos_audio, "Skinny RTP"); ast_rtp_instance_set_prop(sub->rtp, AST_RTP_PROPERTY_NAT, l->nat); /* Set frame packetization */ ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(sub->rtp), ast_format_cap_get_framing(l->cap)); } if (sub->vrtp) { ast_rtp_instance_set_qos(sub->vrtp, qos.tos_video, qos.cos_video, "Skinny VRTP"); ast_rtp_instance_set_prop(sub->vrtp, AST_RTP_PROPERTY_NAT, l->nat); } /* Create the RTP connection */ transmit_connect(d, sub); skinny_unlocksub(sub); } static void destroy_rtp(struct skinny_subchannel *sub) { if (sub->rtp) { SKINNY_DEBUG(DEBUG_AUDIO, 3, "Sub %u - Destroying RTP\n", sub->callid); ast_rtp_instance_stop(sub->rtp); ast_rtp_instance_destroy(sub->rtp); sub->rtp = NULL; } if (sub->vrtp) { SKINNY_DEBUG(DEBUG_AUDIO, 3, "Sub %u - Destroying VRTP\n", sub->callid); ast_rtp_instance_stop(sub->vrtp); ast_rtp_instance_destroy(sub->vrtp); sub->vrtp = NULL; } } static void *skinny_newcall(void *data) { struct ast_channel *c = data; struct skinny_subchannel *sub = ast_channel_tech_pvt(c); struct skinny_line *l = sub->line; struct skinny_device *d = l->device; int res = 0; ast_channel_lock(c); ast_set_callerid(c, l->hidecallerid ? "" : l->cid_num, l->hidecallerid ? "" : l->cid_name, ast_channel_caller(c)->ani.number.valid ? NULL : l->cid_num); #if 1 /* XXX This code is probably not necessary */ ast_party_number_free(&ast_channel_connected(c)->id.number); ast_party_number_init(&ast_channel_connected(c)->id.number); ast_channel_connected(c)->id.number.valid = 1; ast_channel_connected(c)->id.number.str = ast_strdup(ast_channel_exten(c)); ast_party_name_free(&ast_channel_connected(c)->id.name); ast_party_name_init(&ast_channel_connected(c)->id.name); #endif ast_setstate(c, AST_STATE_RING); ast_channel_unlock(c); if (!sub->rtp) { start_rtp(sub); } ast_verb(3, "Sub %u - Calling %s@%s\n", sub->callid, ast_channel_exten(c), ast_channel_context(c)); res = ast_pbx_run(c); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); transmit_start_tone(d, SKINNY_REORDER, l->instance, sub->callid); } return NULL; } static void skinny_dialer(struct skinny_subchannel *sub, int timedout) { struct ast_channel *c = sub->owner; struct skinny_line *l = sub->line; struct skinny_device *d = l->device; if (timedout || !ast_matchmore_extension(c, ast_channel_context(c), sub->exten, 1, l->cid_num)) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Force dialing '%s' because of %s\n", sub->callid, sub->exten, (timedout ? "timeout" : "exactmatch")); if (ast_exists_extension(c, ast_channel_context(c), sub->exten, 1, l->cid_num)) { if (sub->substate == SUBSTATE_OFFHOOK) { dialandactivatesub(sub, sub->exten); } } else { if (d->hookstate == SKINNY_OFFHOOK) { // FIXME: redundant because below will onhook before the sound plays, but it correct to send it. transmit_start_tone(d, SKINNY_REORDER, l->instance, sub->callid); } dumpsub(sub, 0); } } else { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Wait for more digits\n", sub->callid); if (ast_exists_extension(c, ast_channel_context(c), sub->exten, 1, l->cid_num)) { transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_DADFD, KEYMASK_ALL); sub->dialer_sched = skinny_sched_add(matchdigittimeout, skinny_dialer_cb, sub); } else { sub->dialer_sched = skinny_sched_add(gendigittimeout, skinny_dialer_cb, sub); } } } static int skinny_dialer_cb(const void *data) { struct skinny_subchannel *sub = (struct skinny_subchannel *)data; SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Dialer called from SCHED %d\n", sub->callid, sub->dialer_sched); sub->dialer_sched = 0; skinny_dialer(sub, 1); return 0; } static int skinny_autoanswer_cb(const void *data) { struct skinny_subchannel *sub = (struct skinny_subchannel *)data; skinny_locksub(sub); sub->aa_sched = 0; setsubstate(sub, SKINNY_CONNECTED); skinny_unlocksub(sub); return 0; } static int skinny_cfwd_cb(const void *data) { struct skinny_subchannel *sub = (struct skinny_subchannel *)data; struct skinny_line *l = sub->line; sub->cfwd_sched = 0; SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - CFWDNOANS to %s.\n", sub->callid, l->call_forward_noanswer); ast_channel_call_forward_set(sub->owner, l->call_forward_noanswer); ast_queue_control(sub->owner, AST_CONTROL_REDIRECTING); return 0; } static int skinny_call(struct ast_channel *ast, const char *dest, int timeout) { int res = 0; struct skinny_subchannel *sub = ast_channel_tech_pvt(ast); struct skinny_line *l = sub->line; struct skinny_device *d = l->device; struct ast_var_t *current; int doautoanswer = 0; if (!d || !d->session) { ast_log(LOG_WARNING, "Device not registered, cannot call %s\n", dest); return -1; } if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "skinny_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } SKINNY_DEBUG(DEBUG_SUB, 3, "Skinny Call (%s) - Sub %u\n", ast_channel_name(ast), sub->callid); if (l->dnd) { ast_queue_control(ast, AST_CONTROL_BUSY); return -1; } if (AST_LIST_NEXT(sub,list) && !l->callwaiting) { ast_queue_control(ast, AST_CONTROL_BUSY); return -1; } skinny_locksub(sub); AST_LIST_TRAVERSE(ast_channel_varshead(ast), current, entries) { if (!(strcmp(ast_var_name(current), "SKINNY_AUTOANSWER"))) { if (d->hookstate == SKINNY_ONHOOK && !sub->aa_sched) { char buf[24]; int aatime; char *stringp = buf, *curstr; ast_copy_string(buf, ast_var_value(current), sizeof(buf)); curstr = strsep(&stringp, ":"); aatime = atoi(curstr); while ((curstr = strsep(&stringp, ":"))) { if (!(strcasecmp(curstr,"BEEP"))) { sub->aa_beep = 1; } else if (!(strcasecmp(curstr,"MUTE"))) { sub->aa_mute = 1; } } SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - setting autoanswer time=%dms %s%s\n", sub->callid, aatime, sub->aa_beep ? "BEEP " : "", sub->aa_mute ? "MUTE" : ""); if (aatime) { //sub->aa_sched = ast_sched_add(sched, aatime, skinny_autoanswer_cb, sub); sub->aa_sched = skinny_sched_add(aatime, skinny_autoanswer_cb, sub); } else { doautoanswer = 1; } } } } setsubstate(sub, SUBSTATE_RINGIN); if (doautoanswer) { setsubstate(sub, SUBSTATE_CONNECTED); } skinny_unlocksub(sub); return res; } static int skinny_hangup(struct ast_channel *ast) { struct skinny_subchannel *sub = ast_channel_tech_pvt(ast); if (!sub) { ast_debug(1, "Asked to hangup channel not connected\n"); return 0; } dumpsub(sub, 1); SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Destroying\n", sub->callid); skinny_set_owner(sub, NULL); ast_channel_tech_pvt_set(ast, NULL); destroy_rtp(sub); ast_free(sub->origtonum); ast_free(sub->origtoname); ast_free(sub); ast_module_unref(ast_module_info->self); return 0; } static int skinny_answer(struct ast_channel *ast) { int res = 0; struct skinny_subchannel *sub = ast_channel_tech_pvt(ast); sub->cxmode = SKINNY_CX_SENDRECV; SKINNY_DEBUG(DEBUG_SUB, 3, "skinny_answer(%s) on %s@%s-%u\n", ast_channel_name(ast), sub->line->name, sub->line->device->name, sub->callid); setsubstate(sub, SUBSTATE_CONNECTED); return res; } /* Retrieve audio/etc from channel. Assumes sub->lock is already held. */ static struct ast_frame *skinny_rtp_read(struct skinny_subchannel *sub) { struct ast_channel *ast = sub->owner; struct ast_frame *f; if (!sub->rtp) { /* We have no RTP allocated for this channel */ return &ast_null_frame; } switch(ast_channel_fdno(ast)) { case 0: f = ast_rtp_instance_read(sub->rtp, 0); /* RTP Audio */ break; case 1: f = ast_rtp_instance_read(sub->rtp, 1); /* RTCP Control Channel */ break; case 2: f = ast_rtp_instance_read(sub->vrtp, 0); /* RTP Video */ break; case 3: f = ast_rtp_instance_read(sub->vrtp, 1); /* RTCP Control Channel for video */ break; #if 0 case 5: /* Not yet supported */ f = ast_udptl_read(sub->udptl); /* UDPTL for T.38 */ break; #endif default: f = &ast_null_frame; } if (ast) { /* We already hold the channel lock */ if (f->frametype == AST_FRAME_VOICE) { if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_format_cap *caps; ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(f->subclass.format)); caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append(caps, f->subclass.format, 0); ast_channel_nativeformats_set(ast, caps); ao2_ref(caps, -1); } ast_set_read_format(ast, ast_channel_readformat(ast)); ast_set_write_format(ast, ast_channel_writeformat(ast)); } } } return f; } static struct ast_frame *skinny_read(struct ast_channel *ast) { struct ast_frame *fr; struct skinny_subchannel *sub = ast_channel_tech_pvt(ast); skinny_locksub(sub); fr = skinny_rtp_read(sub); skinny_unlocksub(sub); return fr; } static int skinny_write(struct ast_channel *ast, struct ast_frame *frame) { struct skinny_subchannel *sub = ast_channel_tech_pvt(ast); int res = 0; if (frame->frametype != AST_FRAME_VOICE) { if (frame->frametype == AST_FRAME_IMAGE) { return 0; } else { ast_log(LOG_WARNING, "Can't send %u type frames with skinny_write\n", frame->frametype); return 0; } } else { if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *codec_buf = ast_str_alloca(64); 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(ast), &codec_buf), ast_format_get_name(ast_channel_readformat(ast)), ast_format_get_name(ast_channel_writeformat(ast))); return -1; } } if (sub) { skinny_locksub(sub); if (sub->rtp) { res = ast_rtp_instance_write(sub->rtp, frame); } skinny_unlocksub(sub); } return res; } static int skinny_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct skinny_subchannel *sub = ast_channel_tech_pvt(newchan); ast_log(LOG_NOTICE, "skinny_fixup(%s, %s)\n", ast_channel_name(oldchan), ast_channel_name(newchan)); if (sub->owner != oldchan) { ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, sub->owner); return -1; } skinny_set_owner(sub, newchan); return 0; } static int skinny_senddigit_begin(struct ast_channel *ast, char digit) { return -1; /* Start inband indications */ } static int skinny_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration) { #if 0 struct skinny_subchannel *sub = ast->tech_pvt; struct skinny_line *l = sub->line; struct skinny_device *d = l->device; int tmp; /* not right */ sprintf(tmp, "%d", digit); //transmit_tone(d, digit, l->instance, sub->callid); #endif return -1; /* Stop inband indications */ } static int get_devicestate(struct skinny_line *l) { struct skinny_subchannel *sub; int res = AST_DEVICE_UNKNOWN; if (!l) res = AST_DEVICE_INVALID; else if (!l->device || !l->device->session) res = AST_DEVICE_UNAVAILABLE; else if (l->dnd) res = AST_DEVICE_BUSY; else { if (l->device->hookstate == SKINNY_ONHOOK) { res = AST_DEVICE_NOT_INUSE; } else { res = AST_DEVICE_INUSE; } AST_LIST_TRAVERSE(&l->sub, sub, list) { if (sub->substate == SUBSTATE_HOLD) { res = AST_DEVICE_ONHOLD; break; } } } return res; } static char *control2str(int ind) { char *tmp; switch (ind) { case AST_CONTROL_HANGUP: return "Other end has hungup"; case AST_CONTROL_RING: return "Local ring"; case AST_CONTROL_RINGING: return "Remote end is ringing"; case AST_CONTROL_ANSWER: return "Remote end has answered"; case AST_CONTROL_BUSY: return "Remote end is busy"; case AST_CONTROL_TAKEOFFHOOK: return "Make it go off hook"; case AST_CONTROL_OFFHOOK: return "Line is off hook"; case AST_CONTROL_CONGESTION: return "Congestion (circuits busy)"; case AST_CONTROL_FLASH: return "Flash hook"; case AST_CONTROL_WINK: return "Wink"; case AST_CONTROL_OPTION: return "Set a low-level option"; case AST_CONTROL_RADIO_KEY: return "Key Radio"; case AST_CONTROL_RADIO_UNKEY: return "Un-Key Radio"; case AST_CONTROL_PROGRESS: return "Remote end is making Progress"; case AST_CONTROL_PROCEEDING: return "Remote end is proceeding"; case AST_CONTROL_HOLD: return "Hold"; case AST_CONTROL_UNHOLD: return "Unhold"; case AST_CONTROL_VIDUPDATE: return "VidUpdate"; case AST_CONTROL_SRCUPDATE: return "Media Source Update"; case AST_CONTROL_TRANSFER: return "Transfer"; case AST_CONTROL_CONNECTED_LINE: return "Connected Line"; case AST_CONTROL_REDIRECTING: return "Redirecting"; case AST_CONTROL_T38_PARAMETERS: return "T38_Parameters"; case AST_CONTROL_CC: return "CC Not Possible"; case AST_CONTROL_SRCCHANGE: return "Media Source Change"; case AST_CONTROL_INCOMPLETE: return "Incomplete"; case -1: return "Stop tone"; default: if (!(tmp = ast_threadstorage_get(&control2str_threadbuf, CONTROL2STR_BUFSIZE))) return "Unknown"; snprintf(tmp, CONTROL2STR_BUFSIZE, "UNKNOWN-%d", ind); return tmp; } } static void skinny_transfer_attended(struct skinny_subchannel *sub) { struct skinny_subchannel *xferee; struct skinny_subchannel *xferor; enum ast_transfer_result res; if (sub->xferor) { xferor = sub; xferee = sub->related; } else { xferor = sub; xferee = sub->related; } ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); if (ast_channel_state(xferor->owner) == AST_STATE_RINGING) { ast_queue_control(xferee->owner, AST_CONTROL_RINGING); } res = ast_bridge_transfer_attended(xferee->owner, xferor->owner); if (res != AST_BRIDGE_TRANSFER_SUCCESS) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u failed to transfer %u to '%s'@'%s' - %u\n", xferor->callid, xferee->callid, xferor->exten, xferor->line->context, res); send_displayprinotify(xferor->line->device, "Transfer failed", NULL, 10, 5); ast_queue_control(xferee->owner, AST_CONTROL_HOLD); } } static void skinny_transfer_blind(struct skinny_subchannel *sub) { struct skinny_subchannel *xferee = sub->related; enum ast_transfer_result res; sub->related = NULL; xferee->related = NULL; ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); res = ast_bridge_transfer_blind(1, xferee->owner, sub->exten, sub->line->context, NULL, NULL); if (res != AST_BRIDGE_TRANSFER_SUCCESS) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u failed to blind transfer %u to '%s'@'%s' - %u\n", sub->callid, xferee->callid, sub->exten, sub->line->context, res); send_displayprinotify(sub->line->device, "Transfer failed", NULL, 10, 5); ast_queue_control(xferee->owner, AST_CONTROL_HOLD); } dumpsub(sub, 1); } static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen) { struct skinny_subchannel *sub = ast_channel_tech_pvt(ast); struct skinny_line *l = sub->line; struct skinny_device *d = l->device; struct skinnysession *s = d->session; if (!s) { ast_log(LOG_NOTICE, "Asked to indicate '%s' condition on channel %s, but session does not exist.\n", control2str(ind), ast_channel_name(ast)); return -1; } SKINNY_DEBUG(DEBUG_SUB, 3, "Asked to indicate '%s' condition on channel %s (Sub %u)\n", control2str(ind), ast_channel_name(ast), sub->callid); switch(ind) { case AST_CONTROL_RINGING: setsubstate(sub, SUBSTATE_RINGOUT); return (d->earlyrtp ? -1 : 0); /* Tell asterisk to provide inband signalling if rtp started */ case AST_CONTROL_BUSY: setsubstate(sub, SUBSTATE_BUSY); return (d->earlyrtp ? -1 : 0); /* Tell asterisk to provide inband signalling if rtp started */ case AST_CONTROL_INCOMPLETE: /* Support for incomplete not supported for chan_skinny; treat as congestion */ case AST_CONTROL_CONGESTION: setsubstate(sub, SUBSTATE_CONGESTION); return (d->earlyrtp ? -1 : 0); /* Tell asterisk to provide inband signalling if rtp started */ case AST_CONTROL_PROGRESS: setsubstate(sub, SUBSTATE_PROGRESS); return (d->earlyrtp ? -1 : 0); /* Tell asterisk to provide inband signalling if rtp started */ case -1: /* STOP_TONE */ transmit_stop_tone(d, l->instance, sub->callid); break; case AST_CONTROL_HOLD: ast_moh_start(ast, data, l->mohinterpret); break; case AST_CONTROL_UNHOLD: ast_moh_stop(ast); break; case AST_CONTROL_PROCEEDING: break; case AST_CONTROL_SRCUPDATE: if (sub->rtp) { ast_rtp_instance_update_source(sub->rtp); } break; case AST_CONTROL_SRCCHANGE: if (sub->rtp) { ast_rtp_instance_change_source(sub->rtp); } break; case AST_CONTROL_CONNECTED_LINE: update_connectedline(sub, data, datalen); break; default: ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind); /* fallthrough */ case AST_CONTROL_PVT_CAUSE_CODE: case AST_CONTROL_MASQUERADE_NOTIFY: return -1; /* Tell asterisk to provide inband signalling */ } return 0; } static void skinny_set_owner(struct skinny_subchannel* sub, struct ast_channel* chan) { sub->owner = chan; if (sub->rtp) { ast_rtp_instance_set_channel_id(sub->rtp, sub->owner ? ast_channel_uniqueid(sub->owner) : ""); } if (sub->vrtp) { ast_rtp_instance_set_channel_id(sub->vrtp, sub->owner ? ast_channel_uniqueid(sub->owner) : ""); } } static struct ast_channel *skinny_new(struct skinny_line *l, struct skinny_subline *subline, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int direction) { struct ast_channel *tmp; struct skinny_subchannel *sub; struct skinny_device *d = l->device; struct ast_variable *v = NULL; struct ast_format *tmpfmt; struct ast_format_cap *caps; #ifdef AST_DEVMODE struct ast_str *codec_buf = ast_str_alloca(64); #endif if (!l->device || !l->device->session) { ast_log(LOG_WARNING, "Device for line %s is not registered.\n", l->name); return NULL; } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { return NULL; } tmp = ast_channel_alloc(1, state, l->cid_num, l->cid_name, l->accountcode, l->exten, l->context, assignedids, requestor, l->amaflags, "Skinny/%s@%s-%d", l->name, d->name, callnums); if (!tmp) { ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); ao2_ref(caps, -1); return NULL; } else { sub = ast_calloc(1, sizeof(*sub)); if (!sub) { ast_log(LOG_WARNING, "Unable to allocate Skinny subchannel\n"); ast_channel_unlock(tmp); ast_channel_unref(tmp); ao2_ref(caps, -1); return NULL; } else { skinny_set_owner(sub, tmp); sub->callid = callnums++; d->lastlineinstance = l->instance; d->lastcallreference = sub->callid; sub->cxmode = SKINNY_CX_INACTIVE; sub->nat = l->nat; sub->line = l; sub->blindxfer = 0; sub->xferor = 0; sub->related = NULL; sub->calldirection = direction; sub->aa_sched = 0; sub->dialer_sched = 0; sub->cfwd_sched = 0; sub->dialType = DIALTYPE_NORMAL; sub->getforward = 0; if (subline) { sub->subline = subline; subline->sub = sub; } else { sub->subline = NULL; } AST_LIST_INSERT_HEAD(&l->sub, sub, list); //l->activesub = sub; } ast_channel_stage_snapshot(tmp); ast_channel_tech_set(tmp, &skinny_tech); ast_channel_tech_pvt_set(tmp, sub); if (!ast_format_cap_count(l->cap)) { ast_format_cap_append_from_cap(caps, l->cap, AST_MEDIA_TYPE_UNKNOWN); } else { ast_format_cap_append_from_cap(caps, default_cap, AST_MEDIA_TYPE_UNKNOWN); } ast_channel_nativeformats_set(tmp, caps); ao2_ref(caps, -1); tmpfmt = ast_format_cap_get_format(ast_channel_nativeformats(tmp), 0); SKINNY_DEBUG(DEBUG_SUB, 3, "skinny_new: tmp->nativeformats=%s fmt=%s\n", ast_format_cap_get_names(ast_channel_nativeformats(tmp), &codec_buf), ast_format_get_name(tmpfmt)); if (sub->rtp) { ast_channel_set_fd(tmp, 0, ast_rtp_instance_fd(sub->rtp, 0)); } if (state == AST_STATE_RING) { ast_channel_rings_set(tmp, 1); } ast_channel_set_writeformat(tmp, tmpfmt); ast_channel_set_rawwriteformat(tmp, tmpfmt); ast_channel_set_readformat(tmp, tmpfmt); ast_channel_set_rawreadformat(tmp, tmpfmt); ao2_ref(tmpfmt, -1); if (!ast_strlen_zero(l->language)) ast_channel_language_set(tmp, l->language); if (!ast_strlen_zero(l->accountcode)) ast_channel_accountcode_set(tmp, l->accountcode); if (!ast_strlen_zero(l->parkinglot)) ast_channel_parkinglot_set(tmp, l->parkinglot); if (l->amaflags) ast_channel_amaflags_set(tmp, l->amaflags); ast_module_ref(ast_module_info->self); ast_channel_callgroup_set(tmp, l->callgroup); ast_channel_pickupgroup_set(tmp, l->pickupgroup); ast_channel_named_callgroups_set(tmp, l->named_callgroups); ast_channel_named_pickupgroups_set(tmp, l->named_pickupgroups); if (l->cfwdtype & SKINNY_CFWD_ALL) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - CFWDALL to %s.\n", sub->callid, l->call_forward_all); ast_channel_call_forward_set(tmp, l->call_forward_all); } else if ((l->cfwdtype & SKINNY_CFWD_BUSY) && (get_devicestate(l) != AST_DEVICE_NOT_INUSE)) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - CFWDBUSY to %s.\n", sub->callid, l->call_forward_busy); ast_channel_call_forward_set(tmp, l->call_forward_busy); } else if (l->cfwdtype & SKINNY_CFWD_NOANSWER) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - CFWDNOANS Scheduling for %d seconds.\n", sub->callid, l->callfwdtimeout/1000); sub->cfwd_sched = skinny_sched_add(l->callfwdtimeout, skinny_cfwd_cb, sub); } if (subline) { ast_channel_context_set(tmp, subline->context); } else { ast_channel_context_set(tmp, l->context); } ast_channel_exten_set(tmp, l->exten); /* Don't use ast_set_callerid() here because it will * generate a needless NewCallerID event */ if (!ast_strlen_zero(l->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(l->cid_num); } ast_channel_priority_set(tmp, 1); ast_channel_adsicpe_set(tmp, AST_ADSI_UNAVAILABLE); if (sub->rtp) ast_jb_configure(tmp, &global_jbconf); /* Set channel variables for this call from configuration */ for (v = l->chanvars ; v ; v = v->next) pbx_builtin_setvar_helper(tmp, v->name, v->value); ast_channel_stage_snapshot_done(tmp); ast_channel_unlock(tmp); if (state != AST_STATE_DOWN) { if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); tmp = NULL; } } } return tmp; } static char *substate2str(int ind) { char *tmp; switch (ind) { case SUBSTATE_UNSET: return "SUBSTATE_UNSET"; case SUBSTATE_OFFHOOK: return "SUBSTATE_OFFHOOK"; case SUBSTATE_ONHOOK: return "SUBSTATE_ONHOOK"; case SUBSTATE_RINGOUT: return "SUBSTATE_RINGOUT"; case SUBSTATE_RINGIN: return "SUBSTATE_RINGIN"; case SUBSTATE_CONNECTED: return "SUBSTATE_CONNECTED"; case SUBSTATE_BUSY: return "SUBSTATE_BUSY"; case SUBSTATE_CONGESTION: return "SUBSTATE_CONGESTION"; case SUBSTATE_PROGRESS: return "SUBSTATE_PROGRESS"; case SUBSTATE_HOLD: return "SUBSTATE_HOLD"; case SUBSTATE_CALLWAIT: return "SUBSTATE_CALLWAIT"; case SUBSTATE_DIALING: return "SUBSTATE_DIALING"; default: if (!(tmp = ast_threadstorage_get(&substate2str_threadbuf, SUBSTATE2STR_BUFSIZE))) return "Unknown"; snprintf(tmp, SUBSTATE2STR_BUFSIZE, "UNKNOWN-%d", ind); return tmp; } } static void setsubstate(struct skinny_subchannel *sub, int state) { struct skinny_line *l = sub->line; struct skinny_subline *subline = sub->subline; struct skinny_device *d = l->device; struct ast_channel *c = sub->owner; struct ast_party_id connected_id; pthread_t t; int actualstate = state; char *fromnum; if (sub->substate == SUBSTATE_ONHOOK) { return; } skinny_locksub(sub); if (sub->dialer_sched) { skinny_sched_del(sub->dialer_sched, sub); sub->dialer_sched = 0; } if (state != SUBSTATE_RINGIN && sub->aa_sched) { skinny_sched_del(sub->aa_sched, sub); sub->aa_sched = 0; sub->aa_beep = 0; sub->aa_mute = 0; } if (sub->cfwd_sched) { if (state == SUBSTATE_CONNECTED) { if (skinny_sched_del(sub->cfwd_sched, sub)) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - trying to change state from %s to %s, but already forwarded because no answer.\n", sub->callid, substate2str(sub->substate), substate2str(actualstate)); skinny_unlocksub(sub); return; } sub->cfwd_sched = 0; } else if (state == SUBSTATE_ONHOOK) { skinny_sched_del(sub->cfwd_sched, sub); } } if ((state == SUBSTATE_RINGIN) && ((d->hookstate == SKINNY_OFFHOOK) || (AST_LIST_NEXT(AST_LIST_FIRST(&l->sub), list)))) { actualstate = SUBSTATE_CALLWAIT; } if (sub->substate == SUBSTATE_RINGIN && state != SUBSTATE_RINGIN) { transmit_clearprinotify(d, 5); } if ((state == SUBSTATE_CONNECTED) && (!subline) && (AST_LIST_FIRST(&l->sublines))) { const char *slastation; struct skinny_subline *tmpsubline; slastation = pbx_builtin_getvar_helper(c, "SLASTATION"); ast_verb(3, "Connecting %s to subline\n", slastation); if (slastation) { AST_LIST_TRAVERSE(&l->sublines, tmpsubline, list) { if (!strcasecmp(tmpsubline->stname, slastation)) { subline = tmpsubline; break; } } if (subline) { struct skinny_line *tmpline; subline->sub = sub; sub->subline = subline; subline->callid = sub->callid; send_callinfo(sub); AST_LIST_TRAVERSE(&lines, tmpline, all) { AST_LIST_TRAVERSE(&tmpline->sublines, tmpsubline, list) { if (!(subline == tmpsubline)) { if (!strcasecmp(subline->lnname, tmpsubline->lnname)) { struct ast_state_cb_info info = { .exten_state = tmpsubline->extenstate, }; tmpsubline->callid = callnums++; transmit_callstate(tmpsubline->line->device, tmpsubline->line->instance, tmpsubline->callid, SKINNY_OFFHOOK); push_callinfo(tmpsubline, sub); skinny_extensionstate_cb(NULL, NULL, &info, tmpsubline->container); } } } } } } } if (subline) { /* Different handling for subs under a subline, indications come through hints */ switch (actualstate) { case SUBSTATE_ONHOOK: AST_LIST_REMOVE(&l->sub, sub, list); if (sub->related) { sub->related->related = NULL; } if (sub == l->activesub) { l->activesub = NULL; transmit_closereceivechannel(d, sub); transmit_stopmediatransmission(d, sub); } if (subline->callid) { transmit_stop_tone(d, l->instance, sub->callid); transmit_callstate(d, l->instance, subline->callid, SKINNY_CALLREMOTEMULTILINE); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLACONNECTEDNOTACTIVE, KEYMASK_ALL); send_displaypromptstatus(d, "In Use", "", 0, l->instance, subline->callid); } sub->cxmode = SKINNY_CX_RECVONLY; sub->substate = SUBSTATE_ONHOOK; sub->substate = SUBSTATE_ONHOOK; if (sub->owner) { ast_queue_hangup(sub->owner); } break; case SUBSTATE_CONNECTED: transmit_activatecallplane(d, l); transmit_stop_tone(d, l->instance, sub->callid); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_CONNECTED, KEYMASK_ALL); transmit_callstate(d, l->instance, subline->callid, SKINNY_CONNECTED); if (!sub->rtp) { start_rtp(sub); } if (sub->substate == SUBSTATE_RINGIN || sub->substate == SUBSTATE_CALLWAIT) { ast_queue_control(sub->owner, AST_CONTROL_ANSWER); } if (sub->substate == SUBSTATE_DIALING || sub->substate == SUBSTATE_RINGOUT) { transmit_dialednumber(d, sub->exten, l->instance, sub->callid); } if (ast_channel_state(sub->owner) != AST_STATE_UP) { ast_setstate(sub->owner, AST_STATE_UP); } sub->substate = SUBSTATE_CONNECTED; l->activesub = sub; break; case SUBSTATE_HOLD: if (sub->substate != SUBSTATE_CONNECTED) { ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_HOLD from %s (on call-%u)\n", substate2str(sub->substate), sub->callid); return; } transmit_activatecallplane(d, l); transmit_closereceivechannel(d, sub); transmit_stopmediatransmission(d, sub); transmit_callstate(d, l->instance, subline->callid, SKINNY_CALLREMOTEMULTILINE); transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLACONNECTEDNOTACTIVE, KEYMASK_ALL); send_displaypromptstatus(d, "In Use", "", 0, l->instance, subline->callid); sub->substate = SUBSTATE_HOLD; ast_queue_hold(sub->owner, l->mohsuggest); break; default: ast_log(LOG_WARNING, "Substate handling under subline for state %d not implemented on Sub-%u\n", state, sub->callid); } skinny_unlocksub(sub); return; } if ((d->hookstate == SKINNY_ONHOOK) && ((actualstate == SUBSTATE_OFFHOOK) || (actualstate == SUBSTATE_DIALING) || (actualstate == SUBSTATE_RINGOUT) || (actualstate == SUBSTATE_CONNECTED) || (actualstate == SUBSTATE_BUSY) || (actualstate == SUBSTATE_CONGESTION) || (actualstate == SUBSTATE_PROGRESS))) { d->hookstate = SKINNY_OFFHOOK; transmit_speaker_mode(d, SKINNY_SPEAKERON); } SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - change state from %s to %s\n", sub->callid, substate2str(sub->substate), substate2str(actualstate)); if (actualstate == sub->substate) { skinny_unlocksub(sub); return; } switch (actualstate) { case SUBSTATE_OFFHOOK: ast_verb(1, "Call-id: %u\n", sub->callid); l->activesub = sub; transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK); transmit_activatecallplane(d, l); transmit_clear_display_message(d, l->instance, sub->callid); transmit_start_tone(d, SKINNY_DIALTONE, l->instance, sub->callid); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_OFFHOOK, KEYMASK_ALL); send_displaypromptstatus(d, OCTAL_ENTRNUM, "", 0, l->instance, sub->callid); sub->substate = SUBSTATE_OFFHOOK; sub->dialer_sched = skinny_sched_add(firstdigittimeout, skinny_dialer_cb, sub); break; case SUBSTATE_ONHOOK: AST_LIST_REMOVE(&l->sub, sub, list); if (sub->related) { sub->related->related = NULL; } if ((sub->substate == SUBSTATE_RINGIN || sub->substate == SUBSTATE_CALLWAIT) && ast_channel_hangupcause(sub->owner) == AST_CAUSE_ANSWERED_ELSEWHERE) { transmit_callstate(d, l->instance, sub->callid, SKINNY_CONNECTED); } if (sub == l->activesub) { l->activesub = NULL; transmit_closereceivechannel(d, sub); transmit_stopmediatransmission(d, sub); transmit_stop_tone(d, l->instance, sub->callid); transmit_callstate(d, l->instance, sub->callid, SKINNY_ONHOOK); transmit_clearpromptmessage(d, l->instance, sub->callid); transmit_ringer_mode(d, SKINNY_RING_OFF); transmit_definetimedate(d); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); } else { transmit_stop_tone(d, l->instance, sub->callid); transmit_callstate(d, l->instance, sub->callid, SKINNY_ONHOOK); transmit_clearpromptmessage(d, l->instance, sub->callid); } sub->cxmode = SKINNY_CX_RECVONLY; if (sub->owner) { if (sub->substate == SUBSTATE_OFFHOOK) { sub->substate = SUBSTATE_ONHOOK; skinny_unlocksub(sub); ast_hangup(sub->owner); return; } else { sub->substate = SUBSTATE_ONHOOK; ast_queue_hangup(sub->owner); } } else { sub->substate = SUBSTATE_ONHOOK; } break; case SUBSTATE_DIALING: if (ast_strlen_zero(sub->exten) || !ast_exists_extension(c, ast_channel_context(c), sub->exten, 1, l->cid_num)) { ast_log(LOG_WARNING, "Exten (%s)@(%s) does not exist, unable to set substate DIALING on sub %u\n", sub->exten, ast_channel_context(c), sub->callid); break; } if (d->hookstate == SKINNY_ONHOOK) { d->hookstate = SKINNY_OFFHOOK; transmit_speaker_mode(d, SKINNY_SPEAKERON); transmit_activatecallplane(d, l); } if (!sub->subline) { transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK); transmit_stop_tone(d, l->instance, sub->callid); transmit_clear_display_message(d, l->instance, sub->callid); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGOUT, KEYMASK_ALL); send_displaypromptstatus(d, "Dialing", "", 0, l->instance, sub->callid); } if (AST_LIST_FIRST(&l->sublines)) { if (subline) { ast_channel_exten_set(c, subline->exten); ast_channel_context_set(c, "sla_stations"); } else { pbx_builtin_setvar_helper(c, "_DESTEXTEN", sub->exten); pbx_builtin_setvar_helper(c, "_DESTCONTEXT", ast_channel_context(c)); ast_channel_exten_set(c, l->dialoutexten); ast_channel_context_set(c, l->dialoutcontext); ast_copy_string(l->lastnumberdialed, sub->exten, sizeof(l->lastnumberdialed)); } } else { ast_channel_exten_set(c, sub->exten); ast_copy_string(l->lastnumberdialed, sub->exten, sizeof(l->lastnumberdialed)); } sub->substate = SUBSTATE_DIALING; if (ast_pthread_create(&t, NULL, skinny_newcall, c)) { ast_log(LOG_WARNING, "Unable to create new call thread: %s\n", strerror(errno)); ast_hangup(c); } break; case SUBSTATE_RINGOUT: if (!(sub->substate == SUBSTATE_DIALING || sub->substate == SUBSTATE_PROGRESS)) { ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_RINGOUT from %s (on call-%u)\n", substate2str(sub->substate), sub->callid); break; } if (sub->substate != SUBSTATE_PROGRESS) { transmit_callstate(d, l->instance, sub->callid, SKINNY_PROGRESS); } if (!d->earlyrtp) { transmit_start_tone(d, SKINNY_ALERT, l->instance, sub->callid); } transmit_callstate(d, l->instance, sub->callid, SKINNY_RINGOUT); if (sub->related) { transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGOUTWITHTRANS, KEYMASK_ALL); } else { transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGOUT, KEYMASK_ALL); } transmit_dialednumber(d, sub->exten, l->instance, sub->callid); send_displaypromptstatus(d, OCTAL_RINGOUT, "", 0, l->instance, sub->callid); send_callinfo(sub); sub->substate = SUBSTATE_RINGOUT; break; case SUBSTATE_RINGIN: connected_id = ast_channel_connected_effective_id(c); if ((ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { fromnum = S_COR(connected_id.number.valid, connected_id.number.str, "Unknown"); } else { fromnum = "Unknown"; } transmit_callstate(d, l->instance, sub->callid, SKINNY_RINGIN); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGIN, KEYMASK_ALL); send_displaypromptstatus(d, OCTAL_FROM, fromnum, 0, l->instance, sub->callid); send_displayprinotify(d, OCTAL_FROM, fromnum, 10, 5); send_callinfo(sub); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); transmit_ringer_mode(d, SKINNY_RING_INSIDE); transmit_activatecallplane(d, l); if (d->hookstate == SKINNY_ONHOOK) { l->activesub = sub; } if (sub->substate != SUBSTATE_RINGIN || sub->substate != SUBSTATE_CALLWAIT) { ast_setstate(c, AST_STATE_RINGING); ast_queue_control(c, AST_CONTROL_RINGING); } sub->substate = SUBSTATE_RINGIN; break; case SUBSTATE_CALLWAIT: transmit_callstate(d, l->instance, sub->callid, SKINNY_RINGIN); transmit_callstate(d, l->instance, sub->callid, SKINNY_CALLWAIT); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGIN, KEYMASK_ALL); send_displaypromptstatus(d, OCTAL_CALLWAITING, "", 0, l->instance, sub->callid); send_callinfo(sub); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); transmit_start_tone(d, SKINNY_CALLWAITTONE, l->instance, sub->callid); ast_setstate(c, AST_STATE_RINGING); ast_queue_control(c, AST_CONTROL_RINGING); sub->substate = SUBSTATE_CALLWAIT; break; case SUBSTATE_CONNECTED: if (sub->substate == SUBSTATE_RINGIN) { transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK); } if (sub->substate == SUBSTATE_HOLD) { ast_queue_unhold(sub->owner); transmit_connect(d, sub); } transmit_ringer_mode(d, SKINNY_RING_OFF); transmit_activatecallplane(d, l); transmit_stop_tone(d, l->instance, sub->callid); send_callinfo(sub); transmit_callstate(d, l->instance, sub->callid, SKINNY_CONNECTED); send_displaypromptstatus(d, OCTAL_CONNECTED, "", 0, l->instance, sub->callid); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_CONNECTED, KEYMASK_ALL); if (!sub->rtp) { start_rtp(sub); } if (sub->aa_beep) { transmit_start_tone(d, SKINNY_ZIP, l->instance, sub->callid); } if (sub->aa_mute) { transmit_microphone_mode(d, SKINNY_MICOFF); } if (sub->substate == SUBSTATE_RINGIN || sub->substate == SUBSTATE_CALLWAIT) { ast_queue_control(sub->owner, AST_CONTROL_ANSWER); } if (sub->substate == SUBSTATE_DIALING || sub->substate == SUBSTATE_RINGOUT) { transmit_dialednumber(d, sub->exten, l->instance, sub->callid); } if (ast_channel_state(sub->owner) != AST_STATE_UP) { ast_setstate(sub->owner, AST_STATE_UP); } sub->substate = SUBSTATE_CONNECTED; l->activesub = sub; break; case SUBSTATE_BUSY: if (!(sub->substate == SUBSTATE_DIALING || sub->substate == SUBSTATE_PROGRESS || sub->substate == SUBSTATE_RINGOUT)) { ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_BUSY from %s (on call-%u)\n", substate2str(sub->substate), sub->callid); break; } if (!d->earlyrtp) { transmit_start_tone(d, SKINNY_BUSYTONE, l->instance, sub->callid); } send_callinfo(sub); transmit_callstate(d, l->instance, sub->callid, SKINNY_BUSY); send_displaypromptstatus(d, OCTAL_BUSY, "", 0, l->instance, sub->callid); sub->substate = SUBSTATE_BUSY; break; case SUBSTATE_CONGESTION: if (!(sub->substate == SUBSTATE_DIALING || sub->substate == SUBSTATE_PROGRESS || sub->substate == SUBSTATE_RINGOUT)) { ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_CONGESTION from %s (on call-%u)\n", substate2str(sub->substate), sub->callid); break; } if (!d->earlyrtp) { transmit_start_tone(d, SKINNY_REORDER, l->instance, sub->callid); } send_callinfo(sub); transmit_callstate(d, l->instance, sub->callid, SKINNY_CONGESTION); send_displaypromptstatus(d, "Congestion", "", 0, l->instance, sub->callid); sub->substate = SUBSTATE_CONGESTION; break; case SUBSTATE_PROGRESS: if (sub->substate != SUBSTATE_DIALING) { ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_PROGRESS from %s (on call-%u)\n", substate2str(sub->substate), sub->callid); break; } if (!d->earlyrtp) { transmit_start_tone(d, SKINNY_ALERT, l->instance, sub->callid); } send_callinfo(sub); transmit_callstate(d, l->instance, sub->callid, SKINNY_PROGRESS); send_displaypromptstatus(d, "Call Progress", "", 0, l->instance, sub->callid); sub->substate = SUBSTATE_PROGRESS; break; case SUBSTATE_HOLD: if (sub->substate != SUBSTATE_CONNECTED) { ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_HOLD from %s (on call-%u)\n", substate2str(sub->substate), sub->callid); break; } ast_queue_hold(sub->owner, l->mohsuggest); transmit_activatecallplane(d, l); transmit_closereceivechannel(d, sub); transmit_stopmediatransmission(d, sub); transmit_callstate(d, l->instance, sub->callid, SKINNY_HOLD); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_WINK); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_ONHOLD, KEYMASK_ALL); sub->substate = SUBSTATE_HOLD; break; default: ast_log(LOG_WARNING, "Was asked to change to nonexistant substate %d on Sub-%u\n", state, sub->callid); } skinny_unlocksub(sub); } static void dumpsub(struct skinny_subchannel *sub, int forcehangup) { struct skinny_line *l = sub->line; struct skinny_device *d = l->device; struct skinny_subchannel *activate_sub = NULL; struct skinny_subchannel *tsub; SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Dumping\n", sub->callid); if (!forcehangup && sub->substate == SUBSTATE_HOLD) { l->activesub = NULL; return; } if (sub == l->activesub) { d->hookstate = SKINNY_ONHOOK; transmit_speaker_mode(d, SKINNY_SPEAKEROFF); if (sub->related) { activate_sub = sub->related; setsubstate(sub, SUBSTATE_ONHOOK); l->activesub = activate_sub; if (l->activesub->substate != SUBSTATE_HOLD) { ast_log(LOG_WARNING, "Sub-%u was related but not at SUBSTATE_HOLD\n", sub->callid); return; } setsubstate(l->activesub, SUBSTATE_HOLD); } else { setsubstate(sub, SUBSTATE_ONHOOK); AST_LIST_TRAVERSE(&l->sub, tsub, list) { if (tsub->substate == SUBSTATE_CALLWAIT) { activate_sub = tsub; } } if (activate_sub) { setsubstate(activate_sub, SUBSTATE_RINGIN); return; } AST_LIST_TRAVERSE(&l->sub, tsub, list) { if (tsub->substate == SUBSTATE_HOLD) { activate_sub = tsub; } } if (activate_sub) { setsubstate(activate_sub, SUBSTATE_HOLD); return; } } } else { setsubstate(sub, SUBSTATE_ONHOOK); } } static void activatesub(struct skinny_subchannel *sub, int state) { struct skinny_line *l = sub->line; SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Activating, and deactivating sub %u\n", sub->callid, l->activesub ? l->activesub->callid : 0); if (sub == l->activesub) { setsubstate(sub, state); } else { if (l->activesub) { if (l->activesub->substate == SUBSTATE_RINGIN) { setsubstate(l->activesub, SUBSTATE_CALLWAIT); } else if (l->activesub->substate != SUBSTATE_HOLD) { setsubstate(l->activesub, SUBSTATE_ONHOOK); } } l->activesub = sub; setsubstate(sub, state); } } static void dialandactivatesub(struct skinny_subchannel *sub, char exten[AST_MAX_EXTENSION]) { struct skinny_line *l = sub->line; struct skinny_device *d = l->device; if (sub->dialType == DIALTYPE_NORMAL) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Dial %s and Activate\n", sub->callid, exten); ast_copy_string(sub->exten, exten, sizeof(sub->exten)); activatesub(sub, SUBSTATE_DIALING); } else if (sub->dialType == DIALTYPE_CFWD) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Set callforward(%d) to %s\n", sub->callid, sub->getforward, exten); set_callforwards(l, sub->exten, sub->getforward); dumpsub(sub, 1); transmit_cfwdstate(d, l); transmit_displaynotify(d, "CFwd enabled", 10); } else if (sub->dialType == DIALTYPE_XFER) { ast_copy_string(sub->exten, exten, sizeof(sub->exten)); skinny_transfer_blind(sub); } } static int handle_hold_button(struct skinny_subchannel *sub) { if (!sub) return -1; if (sub->related) { setsubstate(sub, SUBSTATE_HOLD); activatesub(sub->related, SUBSTATE_CONNECTED); } else { if (sub->substate == SUBSTATE_HOLD) { activatesub(sub, SUBSTATE_CONNECTED); } else { setsubstate(sub, SUBSTATE_HOLD); } } return 1; } static int handle_transfer_button(struct skinny_subchannel *sub) { struct skinny_line *l; struct skinny_device *d; struct skinny_subchannel *newsub; struct ast_channel *c; if (!sub) { ast_verbose("Transfer: No subchannel to transfer\n"); return -1; } l = sub->line; d = l->device; if (!d->session) { ast_log(LOG_WARNING, "Device for line %s is not registered.\n", l->name); return -1; } if (!sub->related) { /* Another sub has not been created so this must be first XFER press */ if (!(sub->substate == SUBSTATE_HOLD)) { setsubstate(sub, SUBSTATE_HOLD); } c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if (c) { newsub = ast_channel_tech_pvt(c); /* point the sub and newsub at each other so we know they are related */ newsub->related = sub; sub->related = newsub; newsub->xferor = 1; setsubstate(newsub, SUBSTATE_OFFHOOK); } else { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } } else { /* We already have a related sub so we can either complete XFER or go into BLINDXFER (or cancel BLINDXFER */ if (sub->substate == SUBSTATE_OFFHOOK) { if (sub->dialType == DIALTYPE_XFER) { sub->dialType = DIALTYPE_NORMAL; } else { sub->dialType = DIALTYPE_XFER; } } else { skinny_transfer_attended(sub); } } return 0; } static void handle_callforward_button(struct skinny_line *l, struct skinny_subchannel *sub, int cfwdtype) { struct skinny_device *d = l->device; struct ast_channel *c; if (!d->session) { ast_log(LOG_WARNING, "Device for line %s is not registered.\n", l->name); return; } if (!sub && (l->cfwdtype & cfwdtype)) { set_callforwards(l, NULL, cfwdtype); if (sub) { dumpsub(sub, 1); } transmit_cfwdstate(d, l); transmit_displaynotify(d, "CFwd disabled", 10); } else { if (!sub || !sub->owner) { if (!(c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING))) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); return; } sub = ast_channel_tech_pvt(c); l->activesub = sub; setsubstate(sub, SUBSTATE_OFFHOOK); } sub->getforward |= cfwdtype; sub->dialType = DIALTYPE_CFWD; } } static int handle_ip_port_message(struct skinny_req *req, struct skinnysession *s) { /* no response necessary */ return 1; } static void handle_keepalive_message(struct skinny_req *req, struct skinnysession *s) { if (ast_sched_del(sched, s->keepalive_timeout_sched)) { return; } #ifdef AST_DEVMODE { long keepalive_diff; keepalive_diff = (long) ast_tvdiff_ms(ast_tvnow(), ast_tvadd(s->last_keepalive, ast_tv(keep_alive, 0))); SKINNY_DEBUG(DEBUG_PACKET|DEBUG_KEEPALIVE, 3, "Keep_alive %d on %s, %.3fs %s\n", ++s->keepalive_count, (s->device ? s->device->name : "unregistered"), (float) labs(keepalive_diff) / 1000, (keepalive_diff > 0 ? "late" : "early")); } #endif s->keepalive_timeout_sched = ast_sched_add(sched, keep_alive*3000, skinny_nokeepalive_cb, s); s->last_keepalive = ast_tvnow(); transmit_keepaliveack(s); } static int handle_keypad_button_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_subchannel *sub = NULL; struct skinny_line *l; struct skinny_device *d = s->device; struct ast_frame f = { 0, }; char dgt; int digit; int lineInstance; int callReference; size_t len; digit = letohl(req->data.keypad.button); lineInstance = letohl(req->data.keypad.lineInstance); callReference = letohl(req->data.keypad.callReference); if (lineInstance && callReference) { sub = find_subchannel_by_instance_reference(d, lineInstance, callReference); } else { sub = d->activeline->activesub; } if (!sub) return 0; l = sub->line; if (digit == 14) { dgt = '*'; } else if (digit == 15) { dgt = '#'; } else if (digit >= 0 && digit <= 9) { dgt = '0' + digit; } else { /* digit=10-13 (A,B,C,D ?), or * digit is bad value * * probably should not end up here, but set * value for backward compatibility, and log * a warning. */ dgt = '0' + digit; ast_log(LOG_WARNING, "Unsupported digit %d\n", digit); } if ((sub->owner && ast_channel_state(sub->owner) < AST_STATE_UP)) { if (sub->dialer_sched && !skinny_sched_del(sub->dialer_sched, sub)) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Got a digit and not timed out, so try dialing\n", sub->callid); sub->dialer_sched = 0; len = strlen(sub->exten); if (len == 0) { transmit_stop_tone(d, l->instance, sub->callid); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_DADFD, KEYMASK_ALL&~KEYMASK_FORCEDIAL); } if (len < sizeof(sub->exten) - 1 && dgt != immed_dialchar) { sub->exten[len] = dgt; sub->exten[len + 1] = '\0'; } if (len == sizeof(sub->exten) - 1 || dgt == immed_dialchar) { skinny_dialer(sub, 1); } else { skinny_dialer(sub, 0); } } else { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u Got a digit already timedout, ignore\n", sub->callid); /* Timed out so the call is being progressed elsewhere, to late for digits */ return 0; } } else { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %u - Got a digit and sending as DTMF\n", sub->callid); f.subclass.integer = dgt; f.src = "skinny"; if (sub->owner) { if (ast_channel_state(sub->owner) == 0) { f.frametype = AST_FRAME_DTMF_BEGIN; ast_queue_frame(sub->owner, &f); } /* XXX MUST queue this frame to all lines in threeway call if threeway call is active */ f.frametype = AST_FRAME_DTMF_END; ast_queue_frame(sub->owner, &f); /* XXX This seriously needs to be fixed */ if (AST_LIST_NEXT(sub, list) && AST_LIST_NEXT(sub, list)->owner) { if (ast_channel_state(sub->owner) == 0) { f.frametype = AST_FRAME_DTMF_BEGIN; ast_queue_frame(AST_LIST_NEXT(sub, list)->owner, &f); } f.frametype = AST_FRAME_DTMF_END; ast_queue_frame(AST_LIST_NEXT(sub, list)->owner, &f); } } else { ast_log(LOG_WARNING, "Got digit on %s, but not associated with channel\n", l->name); } } return 1; } static int handle_stimulus_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; struct skinny_subchannel *sub; /*struct skinny_speeddial *sd;*/ struct ast_channel *c; int event; int instance; #ifdef AST_DEVMODE int callreference; /* This is only used in AST_DEVMODE, as an argument to SKINNY_DEBUG */ callreference = letohl(req->data.stimulus.callreference); #endif event = letohl(req->data.stimulus.stimulus); instance = letohl(req->data.stimulus.stimulusInstance); /* Note that this call should be using the passed in instance and callreference */ sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); if (!sub) { l = find_line_by_instance(d, d->lastlineinstance); if (!l) { return 0; } sub = l->activesub; } else { l = sub->line; } switch(event) { case STIMULUS_REDIAL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_REDIAL from %s, inst %d, callref %d\n", d->name, instance, callreference); if (ast_strlen_zero(l->lastnumberdialed)) { ast_log(LOG_WARNING, "Attempted redial, but no previously dialed number found. Ignoring button.\n"); break; } c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = ast_channel_tech_pvt(c); l = sub->line; dialandactivatesub(sub, l->lastnumberdialed); } break; case STIMULUS_SPEEDDIAL: { struct skinny_speeddial *sd; SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_SPEEDDIAL from %s, inst %d, callref %d\n", d->name, instance, callreference); if (!(sd = find_speeddial_by_instance(d, instance, 0))) { return 0; } if (!sub || !sub->owner) c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); else c = sub->owner; if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = ast_channel_tech_pvt(c); dialandactivatesub(sub, sd->exten); } } break; case STIMULUS_HOLD: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_HOLD from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_hold_button(sub); break; case STIMULUS_TRANSFER: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_TRANSFER from %s, inst %d, callref %d\n", d->name, instance, callreference); if (l->transfer) handle_transfer_button(sub); else transmit_displaynotify(d, "Transfer disabled", 10); break; case STIMULUS_CONFERENCE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_CONFERENCE from %s, inst %d, callref %d\n", d->name, instance, callreference); /* XXX determine the best way to pull off a conference. Meetme? */ break; case STIMULUS_VOICEMAIL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_VOICEMAIL from %s, inst %d, callref %d\n", d->name, instance, callreference); if (!sub || !sub->owner) { c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); } else { c = sub->owner; } if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); break; } sub = ast_channel_tech_pvt(c); if (sub->substate == SUBSTATE_UNSET || sub->substate == SUBSTATE_OFFHOOK){ dialandactivatesub(sub, l->vmexten); } break; case STIMULUS_CALLPARK: { char extout[AST_MAX_EXTENSION]; char message[32]; RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_CALLPARK from %s, inst %d, callref %d\n", d->name, instance, callreference); if (!ast_parking_provider_registered()) { transmit_displaynotify(d, "Call Park not available", 10); break; } if ((sub && sub->owner) && (ast_channel_state(sub->owner) == AST_STATE_UP)) { c = sub->owner; ast_channel_lock(c); bridge_channel = ast_channel_get_bridge_channel(c); ast_channel_unlock(c); if (!bridge_channel) { transmit_displaynotify(d, "Call Park failed", 10); break; } if (!ast_parking_park_call(bridge_channel, extout, sizeof(extout))) { snprintf(message, sizeof(message), "Call Parked at: %s", extout); transmit_displaynotify(d, message, 10); break; } transmit_displaynotify(d, "Call Park failed", 10); } else { transmit_displaynotify(d, "Call Park not available", 10); } break; } case STIMULUS_DND: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_DND from %s, inst %d, callref %d\n", d->name, instance, callreference); /* Do not disturb */ if (l->dnd != 0){ ast_verb(3, "Disabling DND on %s@%s\n", l->name, d->name); l->dnd = 0; transmit_lamp_indication(d, STIMULUS_DND, 1, SKINNY_LAMP_ON); transmit_displaynotify(d, "DnD disabled", 10); } else { ast_verb(3, "Enabling DND on %s@%s\n", l->name, d->name); l->dnd = 1; transmit_lamp_indication(d, STIMULUS_DND, 1, SKINNY_LAMP_OFF); transmit_displaynotify(d, "DnD enabled", 10); } break; case STIMULUS_FORWARDALL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_FORWARDALL from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_callforward_button(l, sub, SKINNY_CFWD_ALL); break; case STIMULUS_FORWARDBUSY: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_FORWARDBUSY from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_callforward_button(l, sub, SKINNY_CFWD_BUSY); break; case STIMULUS_FORWARDNOANSWER: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_FORWARDNOANSWER from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_callforward_button(l, sub, SKINNY_CFWD_NOANSWER); break; case STIMULUS_DISPLAY: /* Not sure what this is */ SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_DISPLAY from %s, inst %d, callref %d\n", d->name, instance, callreference); break; case STIMULUS_LINE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_LINE from %s, inst %d, callref %d\n", d->name, instance, callreference); l = find_line_by_instance(d, instance); if (!l) { return 0; } d->activeline = l; /* turn the speaker on */ transmit_speaker_mode(d, SKINNY_SPEAKERON); transmit_ringer_mode(d, SKINNY_RING_OFF); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); d->hookstate = SKINNY_OFFHOOK; if (sub && sub->calldirection == SKINNY_INCOMING) { setsubstate(sub, SUBSTATE_CONNECTED); } else { if (sub && sub->owner) { ast_debug(1, "Current subchannel [%s] already has owner\n", ast_channel_name(sub->owner)); } else { c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if (c) { setsubstate(ast_channel_tech_pvt(c), SUBSTATE_OFFHOOK); } else { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } } } break; default: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received UNKNOWN_STIMULUS(%d) from %s, inst %d, callref %d\n", event, d->name, instance, callreference); break; } ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); return 1; } static int handle_offhook_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l = NULL; struct skinny_subchannel *sub = NULL; struct ast_channel *c; int instance; int reference; instance = letohl(req->data.offhook.instance); reference = letohl(req->data.offhook.reference); if (d->hookstate == SKINNY_OFFHOOK) { ast_verb(3, "Got offhook message when device (%s) already offhook\n", d->name); return 0; } if (reference) { sub = find_subchannel_by_instance_reference(d, instance, reference); if (sub) { l = sub->line; } } if (!sub) { if (instance) { l = find_line_by_instance(d, instance); } else { l = d->activeline; } sub = l->activesub; } transmit_ringer_mode(d, SKINNY_RING_OFF); d->hookstate = SKINNY_OFFHOOK; ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); if (sub && sub->substate == SUBSTATE_HOLD) { return 1; } transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); if (sub && sub->calldirection == SKINNY_INCOMING) { setsubstate(sub, SUBSTATE_CONNECTED); } else { /* Not ideal, but let's send updated time at onhook and offhook, as it clears the display */ transmit_definetimedate(d); if (sub && sub->owner) { ast_debug(1, "Current sub [%s] already has owner\n", ast_channel_name(sub->owner)); } else { c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if (c) { setsubstate(ast_channel_tech_pvt(c), SUBSTATE_OFFHOOK); } else { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } } } return 1; } static int handle_onhook_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; struct skinny_subchannel *sub; int instance; int reference; instance = letohl(req->data.onhook.instance); reference = letohl(req->data.onhook.reference); if (instance && reference) { sub = find_subchannel_by_instance_reference(d, instance, reference); if (!sub) { return 0; } l = sub->line; } else { l = d->activeline; sub = l->activesub; if (!sub) { return 0; } } if (d->hookstate == SKINNY_ONHOOK) { /* Something else already put us back on hook */ /* Not ideal, but let's send updated time anyway, as it clears the display */ transmit_definetimedate(d); return 0; } if (l->transfer && sub->xferor && ast_channel_state(sub->owner) >= AST_STATE_RING) { /* We're allowed to transfer, we have two active calls and we made at least one of the calls. Let's try and transfer */ handle_transfer_button(sub); return 0; } ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); dumpsub(sub, 0); d->hookstate = SKINNY_ONHOOK; /* Not ideal, but let's send updated time at onhook and offhook, as it clears the display */ transmit_definetimedate(d); return 1; } static int handle_capabilities_res_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; uint32_t count = 0; struct ast_format_cap *codecs = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); int i; #ifdef AST_DEVMODE struct ast_str *codec_buf = ast_str_alloca(64); #endif if (!codecs) { return 0; } count = letohl(req->data.caps.count); if (count > SKINNY_MAX_CAPABILITIES) { count = SKINNY_MAX_CAPABILITIES; ast_log(LOG_WARNING, "Received more capabilities than we can handle (%d). Ignoring the rest.\n", SKINNY_MAX_CAPABILITIES); } for (i = 0; i < count; i++) { struct ast_format *acodec; int scodec = 0; scodec = letohl(req->data.caps.caps[i].codec); acodec = codec_skinny2ast(scodec); SKINNY_DEBUG(DEBUG_AUDIO, 4, "Adding codec capability %s (%d)\n", ast_format_get_name(acodec), scodec); ast_format_cap_append(codecs, acodec, 0); } ast_format_cap_get_compatible(d->confcap, codecs, d->cap); SKINNY_DEBUG(DEBUG_AUDIO, 4, "Device capability set to '%s'\n", ast_format_cap_get_names(d->cap, &codec_buf)); AST_LIST_TRAVERSE(&d->lines, l, list) { ast_mutex_lock(&l->lock); ast_format_cap_get_compatible(l->confcap, d->cap, l->cap); ast_mutex_unlock(&l->lock); } ao2_ref(codecs, -1); return 1; } static int handle_button_template_req_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; int i; struct skinny_speeddial *sd; struct skinny_serviceurl *surl; struct button_definition_template btn[42]; int lineInstance = 1; int speeddialInstance = 1; int serviceurlInstance = 1; int buttonCount = 0; if (!(req = req_alloc(sizeof(struct button_template_res_message), BUTTON_TEMPLATE_RES_MESSAGE))) return -1; SKINNY_DEBUG(DEBUG_TEMPLATE, 3, "Creating Button Template\n"); memset(&btn, 0, sizeof(btn)); get_button_template(s, btn); for (i=0; i<42; i++) { int btnSet = 0; switch (btn[i].buttonDefinition) { case BT_CUST_LINE: /* assume failure */ req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE; req->data.buttontemplate.definition[i].instanceNumber = 0; AST_LIST_TRAVERSE(&d->lines, l, list) { if (l->instance == lineInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = lineInstance; lineInstance++; buttonCount++; btnSet = 1; break; } } if (!btnSet) { AST_LIST_TRAVERSE(&d->speeddials, sd, list) { if (sd->isHint && sd->instance == lineInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = lineInstance; lineInstance++; buttonCount++; btnSet = 1; break; } } } break; case BT_CUST_LINESPEEDDIAL: /* assume failure */ req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE; req->data.buttontemplate.definition[i].instanceNumber = 0; AST_LIST_TRAVERSE(&d->lines, l, list) { if (l->instance == lineInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = lineInstance; lineInstance++; buttonCount++; btnSet = 1; break; } } if (!btnSet) { AST_LIST_TRAVERSE(&d->speeddials, sd, list) { if (sd->isHint && sd->instance == lineInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = lineInstance; lineInstance++; buttonCount++; btnSet = 1; break; } else if (!sd->isHint && sd->instance == speeddialInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_SPEEDDIAL, speeddialInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_SPEEDDIAL; req->data.buttontemplate.definition[i].instanceNumber = speeddialInstance; speeddialInstance++; buttonCount++; btnSet = 1; break; } } } if (!btnSet) { AST_LIST_TRAVERSE(&d->serviceurls, surl, list) { if (surl->instance == serviceurlInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_SERVICEURL, serviceurlInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_SERVICEURL; req->data.buttontemplate.definition[i].instanceNumber = serviceurlInstance; serviceurlInstance++; buttonCount++; btnSet = 1; break; } } } break; case BT_LINE: req->data.buttontemplate.definition[i].buttonDefinition = htolel(BT_NONE); req->data.buttontemplate.definition[i].instanceNumber = htolel(0); AST_LIST_TRAVERSE(&d->lines, l, list) { if (l->instance == lineInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_LINE, lineInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_LINE; req->data.buttontemplate.definition[i].instanceNumber = lineInstance; lineInstance++; buttonCount++; btnSet = 1; break; } } break; case BT_SPEEDDIAL: req->data.buttontemplate.definition[i].buttonDefinition = BT_NONE; req->data.buttontemplate.definition[i].instanceNumber = 0; AST_LIST_TRAVERSE(&d->speeddials, sd, list) { if (!sd->isHint && sd->instance == speeddialInstance) { SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", BT_SPEEDDIAL, speeddialInstance); req->data.buttontemplate.definition[i].buttonDefinition = BT_SPEEDDIAL; req->data.buttontemplate.definition[i].instanceNumber = speeddialInstance - 1; speeddialInstance++; buttonCount++; btnSet = 1; break; } } break; case BT_NONE: break; default: SKINNY_DEBUG(DEBUG_TEMPLATE, 4, "Adding button: %d, %d\n", btn[i].buttonDefinition, 0); req->data.buttontemplate.definition[i].buttonDefinition = htolel(btn[i].buttonDefinition); req->data.buttontemplate.definition[i].instanceNumber = 0; buttonCount++; btnSet = 1; break; } } req->data.buttontemplate.buttonOffset = 0; req->data.buttontemplate.buttonCount = htolel(buttonCount); req->data.buttontemplate.totalButtonCount = htolel(buttonCount); SKINNY_DEBUG(DEBUG_PACKET | DEBUG_TEMPLATE, 3, "Transmitting BUTTON_TEMPLATE_RES_MESSAGE to %s, type %d\n", d->name, d->type); transmit_response(d, req); return 1; } static int handle_open_receive_channel_ack_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; struct skinny_subchannel *sub; struct sockaddr_in sin = { 0, }; struct sockaddr_in us = { 0, }; struct ast_sockaddr sin_tmp; struct ast_sockaddr us_tmp; struct ast_format *tmpfmt; uint32_t addr; int port; int status; int callid; unsigned int framing; status = (d->protocolversion<17) ? letohl(req->data.openreceivechannelack_ip4.status) : letohl(req->data.openreceivechannelack_ip6.status); if (status) { SKINNY_DEBUG(DEBUG_PACKET, 3, "Received OPEN_RECEIVE_CHANNEL_ACK_MESSAGE from %s, status %d\n", d->name, status); ast_log(LOG_ERROR, "Open Receive Channel Failure\n"); return 0; } if (d->protocolversion<17) { addr = req->data.openreceivechannelack_ip4.ipAddr; port = letohl(req->data.openreceivechannelack_ip4.port); callid = letohl(req->data.openreceivechannelack_ip4.callReference); } else { memcpy(&addr, &req->data.openreceivechannelack_ip6.ipAddr, sizeof(addr)); port = letohl(req->data.openreceivechannelack_ip6.port); callid = letohl(req->data.openreceivechannelack_ip6.callReference); } sin.sin_family = AF_INET; sin.sin_addr.s_addr = addr; sin.sin_port = htons(port); SKINNY_DEBUG(DEBUG_PACKET, 3, "Received OPEN_RECEIVE_CHANNEL_ACK_MESSAGE from %s, status %d, callid %d, ip %s:%d\n", d->name, status, callid, ast_inet_ntoa(sin.sin_addr), port); sub = find_subchannel_by_reference(d, callid); if (!sub) { ast_log(LOG_ERROR, "Open Receive Channel Failure - can't find sub for %d\n", callid); return 0; } l = sub->line; if (sub->rtp) { ast_sockaddr_from_sin(&sin_tmp, &sin); ast_rtp_instance_set_remote_address(sub->rtp, &sin_tmp); ast_rtp_instance_get_local_address(sub->rtp, &us_tmp); ast_sockaddr_to_sin(&us_tmp, &us); us.sin_addr.s_addr = us.sin_addr.s_addr ? us.sin_addr.s_addr : d->ourip.s_addr; } else { ast_log(LOG_ERROR, "No RTP structure, this is very bad\n"); return 0; } SKINNY_DEBUG(DEBUG_PACKET, 4, "device ipaddr = %s:%d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); SKINNY_DEBUG(DEBUG_PACKET, 4, "asterisk ipaddr = %s:%d\n", ast_inet_ntoa(us.sin_addr), ntohs(us.sin_port)); tmpfmt = ast_format_cap_get_format(l->cap, 0); framing = ast_format_cap_get_format_framing(l->cap, tmpfmt); SKINNY_DEBUG(DEBUG_PACKET, 4, "Setting payloadType to '%s' (%u ms)\n", ast_format_get_name(tmpfmt), framing); transmit_startmediatransmission(d, sub, us, tmpfmt, framing); ao2_ref(tmpfmt, -1); return 1; } static int handle_enbloc_call_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; struct skinny_subchannel *sub = NULL; struct ast_channel *c; sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); if (!sub) { l = find_line_by_instance(d, d->lastlineinstance); if (!l) { return 0; } } else { l = sub->line; } c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if(!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { d->hookstate = SKINNY_OFFHOOK; sub = ast_channel_tech_pvt(c); dialandactivatesub(sub, req->data.enbloccallmessage.calledParty); } return 1; } static int handle_soft_key_event_message(struct skinny_req *req, struct skinnysession *s) { struct skinny_device *d = s->device; struct skinny_line *l; struct skinny_subchannel *sub = NULL; struct ast_channel *c; int event; int instance; int callreference; event = letohl(req->data.softkeyeventmessage.softKeyEvent); instance = letohl(req->data.softkeyeventmessage.instance); callreference = letohl(req->data.softkeyeventmessage.callreference); if (instance) { l = find_line_by_instance(d, instance); if (callreference) { sub = find_subchannel_by_instance_reference(d, instance, callreference); } else { sub = find_subchannel_by_instance_reference(d, instance, d->lastcallreference); } } else { l = find_line_by_instance(d, d->lastlineinstance); } if (!l) { ast_log(LOG_WARNING, "Received Softkey Event: %d(%d/%d) but can't find line\n", event, instance, callreference); return 0; } ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); switch(event) { case SOFTKEY_NONE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_NONE from %s, inst %d, callref %d\n", d->name, instance, callreference); break; case SOFTKEY_REDIAL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_REDIAL from %s, inst %d, callref %d\n", d->name, instance, callreference); if (ast_strlen_zero(l->lastnumberdialed)) { ast_log(LOG_WARNING, "Attempted redial, but no previously dialed number found. Ignoring button.\n"); break; } if (!sub || !sub->owner) { c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); } else { c = sub->owner; } if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = ast_channel_tech_pvt(c); dialandactivatesub(sub, l->lastnumberdialed); } break; case SOFTKEY_NEWCALL: /* Actually the DIAL softkey */ SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_NEWCALL from %s, inst %d, callref %d\n", d->name, instance, callreference); /* New Call ALWAYS gets a new sub-channel */ c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); sub = ast_channel_tech_pvt(c); if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { activatesub(sub, SUBSTATE_OFFHOOK); } break; case SOFTKEY_HOLD: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_HOLD from %s, inst %d, callref %d\n", d->name, instance, callreference); if (sub) { setsubstate(sub, SUBSTATE_HOLD); } else { /* No sub, maybe an SLA call */ struct skinny_subline *subline; if ((subline = find_subline_by_callid(d, callreference))) { setsubstate(subline->sub, SUBSTATE_HOLD); } } break; case SOFTKEY_TRNSFER: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_TRNSFER from %s, inst %d, callref %d\n", d->name, instance, callreference); if (l->transfer) handle_transfer_button(sub); else transmit_displaynotify(d, "Transfer disabled", 10); break; case SOFTKEY_DND: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_DND from %s, inst %d, callref %d\n", d->name, instance, callreference); /* Do not disturb */ if (l->dnd != 0){ ast_verb(3, "Disabling DND on %s@%s\n", l->name, d->name); l->dnd = 0; transmit_lamp_indication(d, STIMULUS_DND, 1, SKINNY_LAMP_ON); transmit_displaynotify(d, "DnD disabled", 10); } else { ast_verb(3, "Enabling DND on %s@%s\n", l->name, d->name); l->dnd = 1; transmit_lamp_indication(d, STIMULUS_DND, 1, SKINNY_LAMP_OFF); transmit_displaynotify(d, "DnD enabled", 10); } break; case SOFTKEY_CFWDALL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CFWDALL from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_callforward_button(l, sub, SKINNY_CFWD_ALL); break; case SOFTKEY_CFWDBUSY: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CFWDBUSY from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_callforward_button(l, sub, SKINNY_CFWD_BUSY); break; case SOFTKEY_CFWDNOANSWER: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CFWDNOANSWER from %s, inst %d, callref %d\n", d->name, instance, callreference); handle_callforward_button(l, sub, SKINNY_CFWD_NOANSWER); break; case SOFTKEY_BKSPC: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_BKSPC from %s, inst %d, callref %d\n", d->name, instance, callreference); if (sub->dialer_sched && !skinny_sched_del(sub->dialer_sched, sub)) { size_t len; sub->dialer_sched = 0; len = strlen(sub->exten); if (len > 0) { sub->exten[len-1] = '\0'; if (len == 1) { transmit_start_tone(d, SKINNY_DIALTONE, l->instance, sub->callid); transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_OFFHOOK, KEYMASK_ALL); } transmit_backspace(d, l->instance, sub->callid); } sub->dialer_sched = skinny_sched_add(gendigittimeout, skinny_dialer_cb, sub); } break; case SOFTKEY_ENDCALL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_ENDCALL from %s, inst %d, callref %d\n", d->name, instance, callreference); ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); if (sub) { dumpsub(sub, 1); } else { /* No sub, maybe an SLA call */ struct skinny_subline *subline; if ((subline = find_subline_by_callid(d, callreference))) { dumpsub(subline->sub, 1); } } d->hookstate = SKINNY_ONHOOK; /* Not ideal, but let's send updated time at onhook and offhook, as it clears the display */ transmit_definetimedate(d); break; case SOFTKEY_RESUME: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_RESUME from %s, inst %d, callref %d\n", d->name, instance, callreference); if (sub) { activatesub(sub, SUBSTATE_CONNECTED); } else { /* No sub, maybe an inactive SLA call */ struct skinny_subline *subline; subline = find_subline_by_callid(d, callreference); c = skinny_new(l, subline, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = ast_channel_tech_pvt(c); dialandactivatesub(sub, subline->exten); } } break; case SOFTKEY_ANSWER: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_ANSWER from %s, inst %d, callref %d\n", d->name, instance, callreference); transmit_ringer_mode(d, SKINNY_RING_OFF); transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); if (d->hookstate == SKINNY_ONHOOK) { transmit_speaker_mode(d, SKINNY_SPEAKERON); d->hookstate = SKINNY_OFFHOOK; } if (sub && sub->calldirection == SKINNY_INCOMING) { activatesub(sub, SUBSTATE_CONNECTED); } break; case SOFTKEY_INFO: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_INFO from %s, inst %d, callref %d\n", d->name, instance, callreference); break; case SOFTKEY_CONFRN: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CONFRN from %s, inst %d, callref %d\n", d->name, instance, callreference); /* XXX determine the best way to pull off a conference. Meetme? */ break; case SOFTKEY_PARK: { char extout[AST_MAX_EXTENSION]; char message[32]; RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup); SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_PARK from %s, inst %d, callref %d\n", d->name, instance, callreference); if (!ast_parking_provider_registered()) { transmit_displaynotify(d, "Call Park not available", 10); break; } if ((sub && sub->owner) && (ast_channel_state(sub->owner) == AST_STATE_UP)) { c = sub->owner; ast_channel_lock(c); bridge_channel = ast_channel_get_bridge_channel(c); ast_channel_unlock(c); if (!bridge_channel) { transmit_displaynotify(d, "Call Park failed", 10); break; } if (!ast_parking_park_call(bridge_channel, extout, sizeof(extout))) { snprintf(message, sizeof(message), "Call Parked at: %s", extout); transmit_displaynotify(d, message, 10); break; } transmit_displaynotify(d, "Call Park failed", 10); } else { transmit_displaynotify(d, "Call Park not available", 10); } break; } case SOFTKEY_JOIN: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_JOIN from %s, inst %d, callref %d\n", d->name, instance, callreference); /* this is SLA territory, should not get here unless there is a meetme at subline */ { struct skinny_subline *subline; subline = find_subline_by_callid(d, callreference); c = skinny_new(l, subline, AST_STATE_DOWN, NULL, NULL, SKINNY_OUTGOING); if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { sub = ast_channel_tech_pvt(c); dialandactivatesub(sub, subline->exten); } } break; case SOFTKEY_MEETME: /* XXX How is this different from CONFRN? */ SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_MEETME from %s, inst %d, callref %d\n", d->name, instance, callreference); break; case SOFTKEY_PICKUP: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_PICKUP from %s, inst %d, callref %d\n", d->name, instance, callreference); break; case SOFTKEY_GPICKUP: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_GPICKUP from %s, inst %d, callref %d\n", d->name, instance, callreference); if (!sub || !sub->owner) { c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, NULL, SKINNY_INCOMING); } else { c = sub->owner; } if (!c) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); } else { ast_channel_ref(c); sub = ast_channel_tech_pvt(c); ast_pickup_call(c); if (sub->owner == c) { ast_channel_unref(c); dumpsub(sub, 1); } else { ast_hangup(c); setsubstate(sub, SUBSTATE_CONNECTED); ast_channel_unref(c); } } break; case SOFTKEY_FORCEDIAL: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_FORCEDIAL from %s, inst %d, callref %d\n", d->name, instance, callreference); skinny_dialer(sub, 1); break; default: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_UNKNOWN(%d) from %s, inst %d, callref %d\n", event, d->name, instance, callreference); break; } return 1; } static int handle_message(struct skinny_req *req, struct skinnysession *s) { int res = 0; struct skinny_speeddial *sd; struct skinny_device *d = s->device; if (!d && !(letohl(req->e) == REGISTER_MESSAGE || letohl(req->e) == ALARM_MESSAGE || letohl(req->e) == KEEP_ALIVE_MESSAGE)) { ast_log(LOG_WARNING, "Client sent message #%u without first registering.\n", req->e); return 0; } switch(letohl(req->e)) { case KEEP_ALIVE_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received KEEP_ALIVE_MESSAGE from %s\n", (d ? d->name : "unregistered")); handle_keepalive_message(req, s); break; case REGISTER_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received REGISTER_MESSAGE from %s, name %s, type %u, protovers %d\n", d->name, req->data.reg.name, letohl(req->data.reg.type), letohl(req->data.reg.protocolVersion)); res = skinny_register(req, s); if (!res) { sleep(2); res = skinny_register(req, s); } if (res != 1) { transmit_registerrej(s); return -1; } ast_atomic_fetchadd_int(&unauth_sessions, -1); ast_verb(3, "Device '%s' successfully registered (protoVers %d)\n", s->device->name, s->device->protocolversion); transmit_registerack(s->device); transmit_capabilitiesreq(s->device); res = 0; break; case IP_PORT_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received IP_PORT_MESSAGE from %s\n", d->name); res = handle_ip_port_message(req, s); break; case KEYPAD_BUTTON_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received KEYPAD_BUTTON_MESSAGE from %s, digit %u, inst %u, callref %u\n", d->name, letohl(req->data.keypad.button), letohl(req->data.keypad.lineInstance), letohl(req->data.keypad.callReference)); res = handle_keypad_button_message(req, s); break; case ENBLOC_CALL_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received ENBLOC_CALL_MESSAGE from %s, calledParty %s\n", d->name, req->data.enbloccallmessage.calledParty); res = handle_enbloc_call_message(req, s); break; case STIMULUS_MESSAGE: /* SKINNY_PACKETDEBUG handled in handle_stimulus_message */ res = handle_stimulus_message(req, s); break; case OFFHOOK_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received OFFHOOK_MESSAGE from %s, inst %u, ref %u\n", d->name, letohl(req->data.offhook.instance), letohl(req->data.offhook.reference)); res = handle_offhook_message(req, s); break; case ONHOOK_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received ONHOOK_MESSAGE from %s, inst %u, ref %u\n", d->name, letohl(req->data.offhook.instance), letohl(req->data.offhook.reference)); res = handle_onhook_message(req, s); break; case CAPABILITIES_RES_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET | DEBUG_AUDIO, 3, "Received CAPABILITIES_RES_MESSAGE from %s, count %u, codec data\n", d->name, letohl(req->data.caps.count)); res = handle_capabilities_res_message(req, s); break; case SPEED_DIAL_STAT_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SPEED_DIAL_STAT_REQ_MESSAGE from %s, sdNum %u\n", d->name, letohl(req->data.speeddialreq.speedDialNumber)); if ( (sd = find_speeddial_by_instance(s->device, letohl(req->data.speeddialreq.speedDialNumber), 0)) ) { transmit_speeddialstatres(d, sd); } break; case LINE_STATE_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received LINE_STATE_REQ_MESSAGE from %s, lineNum %u\n", d->name, letohl(req->data.line.lineNumber)); transmit_linestatres(d, letohl(req->data.line.lineNumber)); break; case TIME_DATE_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received TIME_DATE_REQ_MESSAGE from %s\n", d->name); transmit_definetimedate(d); break; case BUTTON_TEMPLATE_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received BUTTON_TEMPLATE_REQ_MESSAGE from %s\n", d->name); res = handle_button_template_req_message(req, s); break; case VERSION_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received VERSION_REQ_MESSAGE from %s\n", d->name); transmit_versionres(d); break; case SERVER_REQUEST_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SERVER_REQUEST_MESSAGE from %s\n", d->name); transmit_serverres(d); break; case ALARM_MESSAGE: /* no response necessary */ SKINNY_DEBUG(DEBUG_PACKET, 3, "Received ALARM_MESSAGE from %s, alarm %s\n", d->name, req->data.alarm.displayMessage); break; case OPEN_RECEIVE_CHANNEL_ACK_MESSAGE: /* SKINNY_PACKETDEBUG handled in handle_open_receive_channel_ack_message */ res = handle_open_receive_channel_ack_message(req, s); break; case SOFT_KEY_SET_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFT_KEY_SET_REQ_MESSAGE from %s\n", d->name); transmit_softkeysetres(d); transmit_selectsoftkeys(d, 0, 0, KEYDEF_ONHOOK, KEYMASK_ALL); break; case SOFT_KEY_EVENT_MESSAGE: /* SKINNY_PACKETDEBUG handled in handle_soft_key_event_message */ res = handle_soft_key_event_message(req, s); break; case UNREGISTER_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received UNREGISTER_MESSAGE from %s\n", d->name); ast_log(LOG_NOTICE, "Received UNREGISTER_MESSAGE from %s\n", d->name); end_session(s); break; case SOFT_KEY_TEMPLATE_REQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFT_KEY_TEMPLATE_REQ_MESSAGE from %s\n", d->name); transmit_softkeytemplateres(d); break; case HEADSET_STATUS_MESSAGE: /* XXX umm...okay? Why do I care? */ SKINNY_DEBUG(DEBUG_PACKET, 3, "Received HEADSET_STATUS_MESSAGE from %s\n", d->name); break; case REGISTER_AVAILABLE_LINES_MESSAGE: /* XXX I have no clue what this is for, but my phone was sending it, so... */ SKINNY_DEBUG(DEBUG_PACKET, 3, "Received REGISTER_AVAILABLE_LINES_MESSAGE from %s\n", d->name); break; case SERVICEURL_STATREQ_MESSAGE: SKINNY_DEBUG(DEBUG_PACKET, 3, "SERVICEURL_STATREQ_MESSAGE from %s\n", d->name); transmit_serviceurlstat(d, letohl(req->data.serviceurlmessage.instance)); break; default: SKINNY_DEBUG(DEBUG_PACKET, 3, "Received UNKNOWN_MESSAGE(%x) from %s\n", (unsigned)letohl(req->e), d->name); break; } return res; } static void destroy_session(struct skinnysession *s) { ast_mutex_lock(&s->lock); if (s->fd > -1) { close(s->fd); } if (s->device) { s->device->session = NULL; } else { ast_atomic_fetchadd_int(&unauth_sessions, -1); } ast_mutex_unlock(&s->lock); ast_mutex_destroy(&s->lock); ast_free(s); } static int skinny_noauth_cb(const void *data) { struct skinnysession *s = (struct skinnysession *)data; ast_log(LOG_WARNING, "Skinny Client failed to authenticate in %d seconds (SCHED %d)\n", auth_timeout, s->auth_timeout_sched); s->auth_timeout_sched = 0; end_session(s); return 0; } static int skinny_nokeepalive_cb(const void *data) { struct skinnysession *s = (struct skinnysession *)data; ast_log(LOG_WARNING, "Skinny Client failed to send keepalive in last %d seconds (SCHED %d)\n", keep_alive*3, s->keepalive_timeout_sched); s->keepalive_timeout_sched = 0; end_session(s); return 0; } static void skinny_session_cleanup(void *data) { struct skinnysession *s = (struct skinnysession *)data; struct skinny_device *d = s->device; struct skinny_line *l; struct skinny_speeddial *sd; ast_log(LOG_NOTICE, "Ending Skinny session from %s at %s\n", d ? d->name : "unknown", ast_inet_ntoa(s->sin.sin_addr)); if (s->lockstate) { ast_mutex_unlock(&s->lock); } if (s->auth_timeout_sched && !ast_sched_del(sched, s->auth_timeout_sched)) { s->auth_timeout_sched = 0; } if (s->keepalive_timeout_sched && !ast_sched_del(sched, s->keepalive_timeout_sched)) { s->keepalive_timeout_sched = 0; } if (d) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); d->session = NULL; AST_LIST_TRAVERSE(&d->speeddials, sd, list) { if (sd->stateid > -1) ast_extension_state_del(sd->stateid, NULL); } AST_LIST_TRAVERSE(&d->lines, l, list) { if (l->device != d) { continue; } ast_format_cap_remove_by_type(l->cap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_update_by_allow_disallow(l->cap, "all", 0); l->instance = 0; unregister_exten(l); ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "Skinny/%s", l->name); } ast_endpoint_set_state(d->endpoint, AST_ENDPOINT_OFFLINE); blob = ast_json_pack("{s: s}", "peer_status", "Unregistered"); ast_endpoint_blob_publish(d->endpoint, ast_endpoint_state_type(), blob); } AST_LIST_LOCK(&sessions); AST_LIST_REMOVE(&sessions, s, list); AST_LIST_UNLOCK(&sessions); destroy_session(s); } static void *skinny_session(void *data) { int res; int bytesread; struct skinny_req *req = NULL; struct skinnysession *s = data; int dlen = 0; struct pollfd fds[1]; if (!s) { ast_log(LOG_WARNING, "Bad Skinny Session\n"); return 0; } ast_log(LOG_NOTICE, "Starting Skinny session from %s\n", ast_inet_ntoa(s->sin.sin_addr)); pthread_cleanup_push(skinny_session_cleanup, s); s->start = ast_tvnow(); s->last_keepalive = ast_tvnow(); s->keepalive_count = 0; s->lockstate = 0; AST_LIST_LOCK(&sessions); AST_LIST_INSERT_HEAD(&sessions, s, list); AST_LIST_UNLOCK(&sessions); s->auth_timeout_sched = ast_sched_add(sched, auth_timeout*1000, skinny_noauth_cb, s); s->keepalive_timeout_sched = ast_sched_add(sched, keep_alive*3000, skinny_nokeepalive_cb, s); for (;;) { fds[0].fd = s->fd; fds[0].events = POLLIN; fds[0].revents = 0; res = ast_poll(fds, 1, -1); /* block */ if (res < 0) { if (errno != EINTR) { ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno)); ast_verb(3, "Ending Skinny session from %s (bad input)\n", ast_inet_ntoa(s->sin.sin_addr)); break; } } if (fds[0].revents) { if (!(req = ast_calloc(1, SKINNY_MAX_PACKET))) { ast_log(LOG_WARNING, "Unable to allocated memorey for skinny_req.\n"); break; } ast_mutex_lock(&s->lock); s->lockstate = 1; if ((res = read(s->fd, req, skinny_header_size)) != skinny_header_size) { if (res < 0) { ast_log(LOG_WARNING, "Header read() returned error: %s\n", strerror(errno)); } else { ast_log(LOG_WARNING, "Unable to read header. Only found %d bytes.\n", res); } break; } if (letohl(req->e) < 0) { ast_log(LOG_ERROR, "Event Message is NULL from socket %d, This is bad\n", s->fd); break; } dlen = letohl(req->len) - 4; if (dlen < 0) { ast_log(LOG_WARNING, "Skinny Client sent invalid data.\n"); break; } if (dlen > (SKINNY_MAX_PACKET - skinny_header_size)) { ast_log(LOG_WARNING, "Skinny packet too large (%d bytes), max length(%d bytes)\n", dlen+8, SKINNY_MAX_PACKET); break; } bytesread = 0; while (1) { if ((res = read(s->fd, ((char*)&req->data)+bytesread, dlen-bytesread)) < 0) { ast_log(LOG_WARNING, "Data read() returned error: %s\n", strerror(errno)); break; } bytesread += res; if (bytesread >= dlen) { if (res < bytesread) { ast_log(LOG_WARNING, "Rest of partial data received.\n"); } if (bytesread > dlen) { ast_log(LOG_WARNING, "Client sent wrong amount of data (%d), expected (%d).\n", bytesread, dlen); res = -1; } break; } ast_log(LOG_WARNING, "Partial data received, waiting (%d bytes read of %d)\n", bytesread, dlen); if (sched_yield() < 0) { ast_log(LOG_WARNING, "Data yield() returned error: %s\n", strerror(errno)); res = -1; break; } } s->lockstate = 0; ast_mutex_unlock(&s->lock); if (res < 0) { break; } pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); res = handle_message(req, s); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); } if (req) { ast_free(req); req = NULL; } } ast_log(LOG_NOTICE, "Skinny Session returned: %s\n", strerror(errno)); if (req) { ast_free(req); } pthread_cleanup_pop(1); return 0; } static void *accept_thread(void *ignore) { int as; struct sockaddr_in sin; socklen_t sinlen; struct skinnysession *s; struct protoent *p; int arg = 1; for (;;) { sinlen = sizeof(sin); as = accept(skinnysock, (struct sockaddr *)&sin, &sinlen); if (as < 0) { ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno)); continue; } if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= auth_limit) { close(as); ast_atomic_fetchadd_int(&unauth_sessions, -1); continue; } p = getprotobyname("tcp"); if(p) { if( setsockopt(as, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) { ast_log(LOG_WARNING, "Failed to set Skinny tcp connection to TCP_NODELAY mode: %s\n", strerror(errno)); } } if (!(s = ast_calloc(1, sizeof(struct skinnysession)))) { close(as); ast_atomic_fetchadd_int(&unauth_sessions, -1); continue; } ast_mutex_init(&s->lock); memcpy(&s->sin, &sin, sizeof(sin)); s->fd = as; if (ast_pthread_create(&s->t, NULL, skinny_session, s)) { destroy_session(s); } } SKINNY_DEBUG(DEBUG_THREAD, 3, "Killing accept thread\n"); close(as); return 0; } static int skinny_devicestate(const char *data) { struct skinny_line *l; char *tmp; tmp = ast_strdupa(data); l = find_line_by_name(tmp); return get_devicestate(l); } static struct ast_channel *skinny_request(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 skinny_line *l; struct skinny_subline *subline = NULL; struct ast_channel *tmpc = NULL; char tmp[256]; if (!(ast_format_cap_has_type(cap, AST_MEDIA_TYPE_AUDIO))) { struct ast_str *codec_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &codec_buf)); return NULL; } ast_copy_string(tmp, dest, sizeof(tmp)); if (ast_strlen_zero(tmp)) { ast_log(LOG_NOTICE, "Skinny channels require a device\n"); return NULL; } l = find_line_by_name(tmp); if (!l) { subline = find_subline_by_name(tmp); if (!subline) { ast_log(LOG_NOTICE, "No available lines on: %s\n", dest); return NULL; } l = subline->line; } ast_verb(3, "skinny_request(%s)\n", tmp); tmpc = skinny_new(l, subline, AST_STATE_DOWN, assignedids, requestor, SKINNY_INCOMING); if (!tmpc) { ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp); } else if (subline) { struct skinny_subchannel *sub = ast_channel_tech_pvt(tmpc); subline->sub = sub; subline->calldirection = SKINNY_INCOMING; subline->substate = SUBSTATE_UNSET; subline->callid = sub->callid; sub->subline = subline; } return tmpc; } #define TYPE_GENERAL 1 #define TYPE_DEF_DEVICE 2 #define TYPE_DEF_LINE 4 #define TYPE_DEVICE 8 #define TYPE_LINE 16 #define CLINE_OPTS ((struct skinny_line_options *)item) #define CLINE ((struct skinny_line *)item) #define CDEV_OPTS ((struct skinny_device_options *)item) #define CDEV ((struct skinny_device *)item) static void config_parse_variables(int type, void *item, struct ast_variable *vptr) { struct ast_variable *v; int lineInstance = 1; int speeddialInstance = 1; int serviceUrlInstance = 1; while(vptr) { v = vptr; vptr = vptr->next; if (type & (TYPE_GENERAL)) { char newcontexts[AST_MAX_CONTEXT]; char oldcontexts[AST_MAX_CONTEXT]; char *stringp, *context, *oldregcontext; if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) { v = v->next; continue; } if (!strcasecmp(v->name, "bindaddr")) { if (!(hp = ast_gethostbyname(v->value, &ahp))) { ast_log(LOG_WARNING, "Invalid address: %s\n", v->value); } else { memcpy(&bindaddr.sin_addr, hp->h_addr, sizeof(bindaddr.sin_addr)); } continue; } else if (!strcasecmp(v->name, "keepalive")) { keep_alive = atoi(v->value); continue; } else if (!strcasecmp(v->name, "authtimeout")) { int timeout = atoi(v->value); if (timeout < 1) { ast_log(LOG_WARNING, "Invalid authtimeout value '%s', using default value\n", v->value); auth_timeout = DEFAULT_AUTH_TIMEOUT; } else { auth_timeout = timeout; } continue; } else if (!strcasecmp(v->name, "authlimit")) { int limit = atoi(v->value); if (limit < 1) { ast_log(LOG_WARNING, "Invalid authlimit value '%s', using default value\n", v->value); auth_limit = DEFAULT_AUTH_LIMIT; } else { auth_limit = limit; } continue; } else if (!strcasecmp(v->name, "regcontext")) { ast_copy_string(newcontexts, v->value, sizeof(newcontexts)); stringp = newcontexts; /* Initialize copy of current global_regcontext for later use in removing stale contexts */ ast_copy_string(oldcontexts, regcontext, sizeof(oldcontexts)); oldregcontext = oldcontexts; /* Let's remove any contexts that are no longer defined in regcontext */ cleanup_stale_contexts(stringp, oldregcontext); /* Create contexts if they don't exist already */ while ((context = strsep(&stringp, "&"))) { ast_copy_string(used_context, context, sizeof(used_context)); ast_context_find_or_create(NULL, NULL, context, "Skinny"); } ast_copy_string(regcontext, v->value, sizeof(regcontext)); continue; } else if (!strcasecmp(v->name, "vmexten")) { ast_copy_string(vmexten, v->value, sizeof(vmexten)); continue; } else if (!strcasecmp(v->name, "immeddialkey")) { if (!strcmp(v->value,"#")) { immed_dialchar = '#'; } else if (!strcmp(v->value,"*")) { immed_dialchar = '*'; } else { ast_log(LOG_WARNING, "Invalid immeddialkey '%s' at line %d, only # or * accepted. Immeddial key disabled\n", v->value, v->lineno); } continue; } else if (!strcasecmp(v->name, "dateformat")) { memcpy(date_format, v->value, sizeof(date_format)); continue; } else if (!strcasecmp(v->name, "tos")) { if (ast_str2tos(v->value, &qos.tos)) ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno); continue; } else if (!strcasecmp(v->name, "tos_audio")) { if (ast_str2tos(v->value, &qos.tos_audio)) ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno); continue; } else if (!strcasecmp(v->name, "tos_video")) { if (ast_str2tos(v->value, &qos.tos_video)) ast_log(LOG_WARNING, "Invalid tos_video value at line %d, refer to QoS documentation\n", v->lineno); continue; } else if (!strcasecmp(v->name, "cos")) { if (ast_str2cos(v->value, &qos.cos)) ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno); continue; } else if (!strcasecmp(v->name, "cos_audio")) { if (ast_str2cos(v->value, &qos.cos_audio)) ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno); continue; } else if (!strcasecmp(v->name, "cos_video")) { if (ast_str2cos(v->value, &qos.cos_video)) ast_log(LOG_WARNING, "Invalid cos_video value at line %d, refer to QoS documentation\n", v->lineno); continue; } else if (!strcasecmp(v->name, "bindport")) { if (sscanf(v->value, "%5d", &ourport) == 1) { bindaddr.sin_port = htons(ourport); } else { ast_log(LOG_WARNING, "Invalid bindport '%s' at line %d of %s\n", v->value, v->lineno, config); } continue; } else if (!strcasecmp(v->name, "allow")) { ast_format_cap_update_by_allow_disallow(default_cap, v->value, 1); continue; } else if (!strcasecmp(v->name, "disallow")) { ast_format_cap_update_by_allow_disallow(default_cap, v->value, 0); continue; } } if (!strcasecmp(v->name, "transfer")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { CDEV_OPTS->transfer = ast_true(v->value); continue; } else if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->transfer = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "callwaiting")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { CDEV_OPTS->callwaiting = ast_true(v->value); continue; } else if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->callwaiting = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "directmedia") || !strcasecmp(v->name, "canreinvite")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->directmedia = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "nat")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->nat = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "context")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->context, v->value, sizeof(CLINE_OPTS->context)); continue; } }else if (!strcasecmp(v->name, "vmexten")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { ast_copy_string(CDEV_OPTS->vmexten, v->value, sizeof(CDEV_OPTS->vmexten)); continue; } else if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->vmexten, v->value, sizeof(CLINE_OPTS->vmexten)); continue; } } else if (!strcasecmp(v->name, "mwiblink")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { CDEV_OPTS->mwiblink = ast_true(v->value); continue; } else if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->mwiblink = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "linelabel")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->label, v->value, sizeof(CLINE_OPTS->label)); continue; } } else if (!strcasecmp(v->name, "callerid")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { if (!strcasecmp(v->value, "asreceived")) { CLINE_OPTS->cid_num[0] = '\0'; CLINE_OPTS->cid_name[0] = '\0'; } else { ast_callerid_split(v->value, CLINE_OPTS->cid_name, sizeof(CLINE_OPTS->cid_name), CLINE_OPTS->cid_num, sizeof(CLINE_OPTS->cid_num)); } continue; } } else if (!strcasecmp(v->name, "amaflags")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { int tempamaflags = ast_channel_string2amaflag(v->value); if (tempamaflags < 0) { ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno); } else { CLINE_OPTS->amaflags = tempamaflags; } continue; } } else if (!strcasecmp(v->name, "regexten")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->regexten, v->value, sizeof(CLINE_OPTS->regexten)); continue; } } else if (!strcasecmp(v->name, "language")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->language, v->value, sizeof(CLINE_OPTS->language)); continue; } } else if (!strcasecmp(v->name, "accountcode")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->accountcode, v->value, sizeof(CLINE_OPTS->accountcode)); continue; } } else if (!strcasecmp(v->name, "mohinterpret") || !strcasecmp(v->name, "musiconhold")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->mohinterpret, v->value, sizeof(CLINE_OPTS->mohinterpret)); continue; } } else if (!strcasecmp(v->name, "mohsuggest")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->mohsuggest, v->value, sizeof(CLINE_OPTS->mohsuggest)); continue; } } else if (!strcasecmp(v->name, "callgroup")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->callgroup = ast_get_group(v->value); continue; } } else if (!strcasecmp(v->name, "pickupgroup")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->pickupgroup = ast_get_group(v->value); continue; } } else if (!strcasecmp(v->name, "namedcallgroup")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->named_callgroups = ast_get_namedgroups(v->value); continue; } } else if (!strcasecmp(v->name, "namedpickupgroup")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->named_pickupgroups = ast_get_namedgroups(v->value); continue; } } else if (!strcasecmp(v->name, "immediate")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE | TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->immediate = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "cancallforward")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->cancallforward = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "callfwdtimeout")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->callfwdtimeout = atoi(v->value); continue; } } else if (!strcasecmp(v->name, "mailbox")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->mailbox, v->value, sizeof(CLINE_OPTS->mailbox)); continue; } } else if ( !strcasecmp(v->name, "parkinglot")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_copy_string(CLINE_OPTS->parkinglot, v->value, sizeof(CLINE_OPTS->parkinglot)); continue; } } else if (!strcasecmp(v->name, "hasvoicemail")) { if (type & (TYPE_LINE)) { if (ast_true(v->value) && ast_strlen_zero(CLINE->mailbox)) { /* * hasvoicemail is a users.conf legacy voicemail enable method. * hasvoicemail is only going to work for app_voicemail mailboxes. */ if (strchr(CLINE->name, '@')) { ast_copy_string(CLINE->mailbox, CLINE->name, sizeof(CLINE->mailbox)); } else { snprintf(CLINE->mailbox, sizeof(CLINE->mailbox), "%s@default", CLINE->name); } } continue; } } else if (!strcasecmp(v->name, "threewaycalling")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { CLINE_OPTS->threewaycalling = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "setvar")) { if (type & (TYPE_LINE)) { CLINE->chanvars = add_var(v->value, CLINE->chanvars); continue; } } else if (!strcasecmp(v->name, "earlyrtp")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { CDEV_OPTS->earlyrtp = ast_true(v->value); continue; } } else if (!strcasecmp(v->name, "host")) { if (type & (TYPE_DEVICE)) { struct ast_sockaddr CDEV_addr_tmp; CDEV_addr_tmp.ss.ss_family = AF_INET; if (ast_get_ip(&CDEV_addr_tmp, v->value)) { ast_log(LOG_WARNING, "Bad IP '%s' at line %d.\n", v->value, v->lineno); } ast_sockaddr_to_sin(&CDEV_addr_tmp, &CDEV->addr); continue; } } else if (!strcasecmp(v->name, "port")) { if (type & (TYPE_DEF_DEVICE)) { CDEV->addr.sin_port = htons(atoi(v->value)); continue; } } else if (!strcasecmp(v->name, "device")) { if (type & (TYPE_DEVICE)) { ast_copy_string(CDEV_OPTS->id, v->value, sizeof(CDEV_OPTS->id)); continue; } } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { if (type & (TYPE_DEVICE)) { CDEV->ha = ast_append_ha(v->name, v->value, CDEV->ha, NULL); continue; } } else if (!strcasecmp(v->name, "allow")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { ast_format_cap_update_by_allow_disallow(CDEV->confcap, v->value, 1); continue; } if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_format_cap_update_by_allow_disallow(CLINE->confcap, v->value, 1); continue; } } else if (!strcasecmp(v->name, "disallow")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { ast_format_cap_update_by_allow_disallow(CDEV->confcap, v->value, 0); continue; } if (type & (TYPE_DEF_LINE | TYPE_LINE)) { ast_format_cap_update_by_allow_disallow(CLINE->confcap, v->value, 0); continue; } } else if (!strcasecmp(v->name, "version")) { if (type & (TYPE_DEF_DEVICE | TYPE_DEVICE)) { ast_copy_string(CDEV_OPTS->version_id, v->value, sizeof(CDEV_OPTS->version_id)); continue; } } else if (!strcasecmp(v->name, "line")) { if (type & (TYPE_DEVICE)) { struct skinny_line *l; AST_LIST_TRAVERSE(&lines, l, all) { if (!strcasecmp(v->value, l->name) && !l->prune) { /* FIXME: temp solution about line conflicts */ struct skinny_device *d; struct skinny_line *l2; int lineinuse = 0; AST_LIST_TRAVERSE(&devices, d, list) { AST_LIST_TRAVERSE(&d->lines, l2, list) { if (l2 == l && strcasecmp(d->id, CDEV->id)) { ast_log(LOG_WARNING, "Line %s already used by %s. Not connecting to %s.\n", l->name, d->name, CDEV->name); lineinuse++; } } } if (!lineinuse) { if (!AST_LIST_FIRST(&CDEV->lines)) { CDEV->activeline = l; } lineInstance++; AST_LIST_INSERT_HEAD(&CDEV->lines, l, list); l->device = CDEV; } break; } } continue; } } else if (!strcasecmp(v->name, "subline")) { if (type & (TYPE_LINE)) { struct skinny_subline *subline; struct skinny_container *container; char buf[256]; char *stringp = buf, *exten, *stname, *context; if (!(subline = ast_calloc(1, sizeof(*subline)))) { ast_log(LOG_WARNING, "Unable to allocate memory for subline %s. Ignoring subline.\n", v->value); continue; } if (!(container = ast_calloc(1, sizeof(*container)))) { ast_log(LOG_WARNING, "Unable to allocate memory for subline %s container. Ignoring subline.\n", v->value); ast_free(subline); continue; } ast_copy_string(buf, v->value, sizeof(buf)); exten = strsep(&stringp, "@"); ast_copy_string(subline->exten, ast_strip(exten), sizeof(subline->exten)); stname = strsep(&exten, "_"); ast_copy_string(subline->stname, ast_strip(stname), sizeof(subline->stname)); ast_copy_string(subline->lnname, ast_strip(exten), sizeof(subline->lnname)); context = strsep(&stringp, ","); ast_copy_string(subline->name, ast_strip(stringp), sizeof(subline->name)); ast_copy_string(subline->context, ast_strip(context), sizeof(subline->context)); subline->line = CLINE; subline->sub = NULL; container->type = SKINNY_SUBLINECONTAINER; container->data = subline; subline->container = container; AST_LIST_INSERT_HEAD(&CLINE->sublines, subline, list); continue; } } else if (!strcasecmp(v->name, "dialoutcontext")) { if (type & (TYPE_LINE)) { ast_copy_string(CLINE_OPTS->dialoutcontext, v->value, sizeof(CLINE_OPTS->dialoutcontext)); continue; } } else if (!strcasecmp(v->name, "dialoutexten")) { if (type & (TYPE_LINE)) { ast_copy_string(CLINE_OPTS->dialoutexten, v->value, sizeof(CLINE_OPTS->dialoutexten)); continue; } } else if (!strcasecmp(v->name, "speeddial")) { if (type & (TYPE_DEVICE)) { struct skinny_speeddial *sd; struct skinny_container *container; char buf[256]; char *stringp = buf, *exten, *context, *label; if (!(sd = ast_calloc(1, sizeof(*sd)))) { ast_log(LOG_WARNING, "Unable to allocate memory for speeddial %s. Ignoring speeddial.\n", v->name); continue; } if (!(container = ast_calloc(1, sizeof(*container)))) { ast_log(LOG_WARNING, "Unable to allocate memory for speeddial %s container. Ignoring speeddial.\n", v->name); ast_free(sd); continue; } ast_copy_string(buf, v->value, sizeof(buf)); exten = strsep(&stringp, ","); if ((context = strchr(exten, '@'))) { *context++ = '\0'; } label = stringp; ast_mutex_init(&sd->lock); ast_copy_string(sd->exten, exten, sizeof(sd->exten)); if (!ast_strlen_zero(context)) { sd->isHint = 1; sd->instance = lineInstance++; ast_copy_string(sd->context, context, sizeof(sd->context)); } else { sd->isHint = 0; sd->instance = speeddialInstance++; sd->context[0] = '\0'; } ast_copy_string(sd->label, S_OR(label, exten), sizeof(sd->label)); sd->parent = CDEV; container->type = SKINNY_SDCONTAINER; container->data = sd; sd->container = container; AST_LIST_INSERT_HEAD(&CDEV->speeddials, sd, list); continue; } } else if (!strcasecmp(v->name, "serviceurl")) { if (type & (TYPE_DEVICE)) { struct skinny_serviceurl *surl; char buf[256]; char *stringp = buf, *serviceUrl, *displayName; if (!(surl = ast_calloc(1, sizeof(*surl)))) { ast_log(LOG_WARNING, "Unable to allocate memory for serviceurl %s. Ignoring service URL.\n", v->name); continue; } ast_copy_string(buf, v->value, sizeof(buf)); displayName = strsep(&stringp, ","); if (stringp) { serviceUrl = stringp; ast_copy_string(surl->url, ast_strip(serviceUrl), sizeof(surl->url)); ast_copy_string(surl->displayName, displayName, sizeof(surl->displayName)); surl->instance = serviceUrlInstance++; surl->device = CDEV; AST_LIST_INSERT_HEAD(&CDEV->serviceurls, surl, list); } else { ast_free(surl); ast_log(LOG_WARNING, "Badly formed option for service URL in %s. Ignoring service URL.\n", v->name); } continue; } } else if (!strcasecmp(v->name, "addon")) { if (type & (TYPE_DEVICE)) { struct skinny_addon *a; if (!(a = ast_calloc(1, sizeof(*a)))) { ast_log(LOG_WARNING, "Unable to allocate memory for addon %s. Ignoring addon.\n", v->name); continue; } else { ast_mutex_init(&a->lock); ast_copy_string(a->type, v->value, sizeof(a->type)); AST_LIST_INSERT_HEAD(&CDEV->addons, a, list); } continue; } } else { ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno); continue; } ast_log(LOG_WARNING, "Invalid category used: %s at line %d\n", v->name, v->lineno); } } static struct skinny_line *config_line(const char *lname, struct ast_variable *v) { struct skinny_line *l, *temp; int update = 0; struct skinny_container *container; ast_log(LOG_NOTICE, "Configuring skinny line %s.\n", lname); /* We find the old line and remove it just before the new line is created */ AST_LIST_LOCK(&lines); AST_LIST_TRAVERSE(&lines, temp, all) { if (!strcasecmp(lname, temp->name) && temp->prune) { update = 1; break; } } if (!(l = skinny_line_alloc())) { ast_verb(1, "Unable to allocate memory for line %s.\n", lname); AST_LIST_UNLOCK(&lines); return NULL; } if (!(container = ast_calloc(1, sizeof(*container)))) { ast_log(LOG_WARNING, "Unable to allocate memory for line %s container.\n", lname); skinny_line_destroy(l); AST_LIST_UNLOCK(&lines); return NULL; } container->type = SKINNY_LINECONTAINER; container->data = l; l->container = container; memcpy(l, default_line, sizeof(*default_line)); ast_mutex_init(&l->lock); ast_copy_string(l->name, lname, sizeof(l->name)); ast_format_cap_append_from_cap(l->confcap, default_cap, AST_MEDIA_TYPE_UNKNOWN); AST_LIST_INSERT_TAIL(&lines, l, all); ast_mutex_lock(&l->lock); AST_LIST_UNLOCK(&lines); config_parse_variables(TYPE_LINE, l, v); if (!ast_strlen_zero(l->mailbox)) { struct stasis_topic *mailbox_specific_topic; ast_verb(3, "Setting mailbox '%s' on line %s\n", l->mailbox, l->name); mailbox_specific_topic = ast_mwi_topic(l->mailbox); if (mailbox_specific_topic) { l->mwi_event_sub = stasis_subscribe_pool(mailbox_specific_topic, mwi_event_cb, l); } } if (!ast_strlen_zero(vmexten) && ast_strlen_zero(l->vmexten)) { ast_copy_string(l->vmexten, vmexten, sizeof(l->vmexten)); } ast_mutex_unlock(&l->lock); /* We do not want to unlink or free the line yet, it needs to be available to detect a device reconfig when we load the devices. Old lines will be pruned after the reload completes */ ast_verb(3, "%s config for line '%s'\n", update ? "Updated" : (skinnyreload ? "Reloaded" : "Created"), l->name); return l; } static struct skinny_device *config_device(const char *dname, struct ast_variable *v) { struct skinny_device *d, *temp; struct skinny_line *l, *ltemp; struct skinny_subchannel *sub; int update = 0; ast_log(LOG_NOTICE, "Configuring skinny device %s.\n", dname); AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, temp, list) { if (!strcasecmp(dname, temp->name) && temp->prune) { update = 1; break; } } if (!(d = skinny_device_alloc(dname))) { ast_verb(1, "Unable to allocate memory for device %s.\n", dname); AST_LIST_UNLOCK(&devices); return NULL; } memcpy(d, default_device, sizeof(*default_device)); ast_mutex_init(&d->lock); ast_copy_string(d->name, dname, sizeof(d->name)); ast_format_cap_append_from_cap(d->confcap, default_cap, AST_MEDIA_TYPE_UNKNOWN); AST_LIST_INSERT_TAIL(&devices, d, list); ast_mutex_lock(&d->lock); AST_LIST_UNLOCK(&devices); config_parse_variables(TYPE_DEVICE, d, v); if (!AST_LIST_FIRST(&d->lines)) { ast_log(LOG_ERROR, "A Skinny device must have at least one line!\n"); ast_mutex_unlock(&d->lock); return NULL; } if (/*d->addr.sin_addr.s_addr && */!ntohs(d->addr.sin_port)) { d->addr.sin_port = htons(DEFAULT_SKINNY_PORT); } if (skinnyreload){ AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, temp, list) { if (strcasecmp(d->id, temp->id) || !temp->prune || !temp->session) { continue; } ast_mutex_lock(&d->lock); d->session = temp->session; d->session->device = d; d->hookstate = temp->hookstate; AST_LIST_LOCK(&d->lines); AST_LIST_TRAVERSE(&d->lines, l, list){ AST_LIST_LOCK(&temp->lines); AST_LIST_TRAVERSE(&temp->lines, ltemp, list) { if (strcasecmp(l->name, ltemp->name)) { continue; } ast_mutex_lock(<emp->lock); l->instance = ltemp->instance; if (l == temp->activeline) { d->activeline = l; } if (!AST_LIST_EMPTY(<emp->sub)) { ast_mutex_lock(&l->lock); l->sub = ltemp->sub; l->activesub = ltemp->activesub; AST_LIST_TRAVERSE(&l->sub, sub, list) { sub->line = l; } ast_mutex_unlock(&l->lock); } ast_mutex_unlock(<emp->lock); } AST_LIST_UNLOCK(&temp->lines); } AST_LIST_UNLOCK(&d->lines); ast_mutex_unlock(&d->lock); } AST_LIST_UNLOCK(&devices); } ast_mutex_unlock(&d->lock); ast_verb(3, "%s config for device '%s'\n", update ? "Updated" : (skinnyreload ? "Reloaded" : "Created"), d->name); return d; } static int config_load(void) { int on = 1; struct ast_config *cfg; char *cat; int oldport = ntohs(bindaddr.sin_port); struct ast_flags config_flags = { 0 }; ast_log(LOG_NOTICE, "Configuring skinny from %s\n", config); if (gethostname(ourhost, sizeof(ourhost))) { ast_log(LOG_WARNING, "Unable to get hostname, Skinny disabled.\n"); return 0; } cfg = ast_config_load(config, config_flags); /* We *must* have a config file otherwise stop immediately */ if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_NOTICE, "Unable to load config %s, Skinny disabled.\n", config); return -1; } memset(&bindaddr, 0, sizeof(bindaddr)); immed_dialchar = '\0'; memset(&vmexten, '\0', sizeof(vmexten)); /* Copy the default jb config over global_jbconf */ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); /* load the general section */ cat = ast_category_browse(cfg, "general"); config_parse_variables(TYPE_GENERAL, NULL, ast_variable_browse(cfg, "general")); if (ntohl(bindaddr.sin_addr.s_addr)) { __ourip = bindaddr.sin_addr; } else { hp = ast_gethostbyname(ourhost, &ahp); if (!hp) { ast_log(LOG_WARNING, "Unable to get our IP address, Skinny disabled\n"); ast_config_destroy(cfg); return 0; } memcpy(&__ourip, hp->h_addr, sizeof(__ourip)); } if (!ntohs(bindaddr.sin_port)) { bindaddr.sin_port = htons(DEFAULT_SKINNY_PORT); } bindaddr.sin_family = AF_INET; /* load the lines sections */ config_parse_variables(TYPE_DEF_LINE, default_line, ast_variable_browse(cfg, "lines")); cat = ast_category_browse(cfg, "lines"); while (cat && strcasecmp(cat, "general") && strcasecmp(cat, "devices")) { config_line(cat, ast_variable_browse(cfg, cat)); cat = ast_category_browse(cfg, cat); } /* load the devices sections */ config_parse_variables(TYPE_DEF_DEVICE, default_device, ast_variable_browse(cfg, "devices")); cat = ast_category_browse(cfg, "devices"); while (cat && strcasecmp(cat, "general") && strcasecmp(cat, "lines")) { config_device(cat, ast_variable_browse(cfg, cat)); cat = ast_category_browse(cfg, cat); } ast_mutex_lock(&netlock); if ((skinnysock > -1) && (ntohs(bindaddr.sin_port) != oldport)) { close(skinnysock); skinnysock = -1; } if (skinnysock < 0) { skinnysock = socket(AF_INET, SOCK_STREAM, 0); if(setsockopt(skinnysock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { ast_log(LOG_ERROR, "Set Socket Options failed: errno %d, %s\n", errno, strerror(errno)); ast_config_destroy(cfg); ast_mutex_unlock(&netlock); return 0; } if (skinnysock < 0) { ast_log(LOG_WARNING, "Unable to create Skinny socket: %s\n", strerror(errno)); } else { if (bind(skinnysock, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) { ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n", ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port), strerror(errno)); close(skinnysock); skinnysock = -1; ast_config_destroy(cfg); ast_mutex_unlock(&netlock); return 0; } if (listen(skinnysock, DEFAULT_SKINNY_BACKLOG)) { ast_log(LOG_WARNING, "Failed to start listening to %s:%d: %s\n", ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port), strerror(errno)); close(skinnysock); skinnysock = -1; ast_config_destroy(cfg); ast_mutex_unlock(&netlock); return 0; } ast_verb(2, "Skinny listening on %s:%d\n", ast_inet_ntoa(bindaddr.sin_addr), ntohs(bindaddr.sin_port)); ast_set_qos(skinnysock, qos.tos, qos.cos, "Skinny"); ast_pthread_create_background(&accept_t, NULL, accept_thread, NULL); } } ast_mutex_unlock(&netlock); ast_config_destroy(cfg); return 1; } static void delete_devices(void) { struct skinny_device *d; struct skinny_line *l; struct skinny_speeddial *sd; struct skinny_addon *a; struct skinny_serviceurl *surl; AST_LIST_LOCK(&devices); AST_LIST_LOCK(&lines); /* Delete all devices */ while ((d = AST_LIST_REMOVE_HEAD(&devices, list))) { /* Delete all lines for this device */ while ((l = AST_LIST_REMOVE_HEAD(&d->lines, list))) { AST_LIST_REMOVE(&lines, l, all); AST_LIST_REMOVE(&d->lines, l, list); l = skinny_line_destroy(l); } /* Delete all speeddials for this device */ while ((sd = AST_LIST_REMOVE_HEAD(&d->speeddials, list))) { free(sd->container); free(sd); } /* Delete all serviceurls for this device */ while ((surl = AST_LIST_REMOVE_HEAD(&d->serviceurls, list))) { free(surl); } /* Delete all addons for this device */ while ((a = AST_LIST_REMOVE_HEAD(&d->addons, list))) { free(a); } d = skinny_device_destroy(d); } AST_LIST_UNLOCK(&lines); AST_LIST_UNLOCK(&devices); } int skinny_reload(void) { struct skinny_device *d; struct skinny_line *l; struct skinny_speeddial *sd; struct skinny_addon *a; if (skinnyreload) { ast_verb(3, "Chan_skinny is already reloading.\n"); return 0; } skinnyreload = 1; /* Mark all devices and lines as candidates to be pruned */ AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE(&devices, d, list) { d->prune = 1; } AST_LIST_UNLOCK(&devices); AST_LIST_LOCK(&lines); AST_LIST_TRAVERSE(&lines, l, all) { l->prune = 1; } AST_LIST_UNLOCK(&lines); config_load(); /* Remove any devices that no longer exist in the config */ AST_LIST_LOCK(&devices); AST_LIST_TRAVERSE_SAFE_BEGIN(&devices, d, list) { if (!d->prune) { continue; } ast_verb(3, "Removing device '%s'\n", d->name); /* Delete all lines for this device. We do not want to free the line here, that will happen below. */ while ((l = AST_LIST_REMOVE_HEAD(&d->lines, list))) { if (l->mwi_event_sub) { l->mwi_event_sub = stasis_unsubscribe(l->mwi_event_sub); } } /* Delete all speeddials for this device */ while ((sd = AST_LIST_REMOVE_HEAD(&d->speeddials, list))) { free(sd); } /* Delete all addons for this device */ while ((a = AST_LIST_REMOVE_HEAD(&d->addons, list))) { free(a); } AST_LIST_REMOVE_CURRENT(list); d = skinny_device_destroy(d); } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&devices); AST_LIST_LOCK(&lines); AST_LIST_TRAVERSE_SAFE_BEGIN(&lines, l, all) { if (l->prune) { AST_LIST_REMOVE_CURRENT(all); l = skinny_line_destroy(l); } } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&lines); AST_LIST_TRAVERSE(&devices, d, list) { /* Do a soft reset to re-register the devices after cleaning up the removed devices and lines */ if (d->session) { ast_verb(3, "Restarting device '%s'\n", d->name); transmit_reset(d, 1); } } skinnyreload = 0; return 0; } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { int res = 0; if (!(default_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } if (!(skinny_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { ao2_ref(default_cap, -1); return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append_by_type(skinny_tech.capabilities, AST_MEDIA_TYPE_AUDIO); ast_format_cap_append(default_cap, ast_format_ulaw, 0); ast_format_cap_append(default_cap, ast_format_alaw, 0); for (; res < ARRAY_LEN(soft_key_template_default); res++) { soft_key_template_default[res].softKeyEvent = htolel(soft_key_template_default[res].softKeyEvent); } /* load and parse config */ res = config_load(); if (res == -1) { ao2_ref(skinny_tech.capabilities, -1); ao2_ref(default_cap, -1); return AST_MODULE_LOAD_DECLINE; } sched = ast_sched_context_create(); if (!sched) { ao2_ref(skinny_tech.capabilities, -1); ao2_ref(default_cap, -1); ast_log(LOG_WARNING, "Unable to create schedule context\n"); return AST_MODULE_LOAD_FAILURE; } /* Make sure we can register our skinny channel type */ if (ast_channel_register(&skinny_tech)) { ao2_ref(default_cap, -1); ao2_ref(skinny_tech.capabilities, -1); ast_log(LOG_ERROR, "Unable to register channel class 'Skinny'\n"); return -1; } ast_rtp_glue_register(&skinny_rtp_glue); ast_cli_register_multiple(cli_skinny, ARRAY_LEN(cli_skinny)); ast_manager_register_xml("SKINNYdevices", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_skinny_show_devices); ast_manager_register_xml("SKINNYshowdevice", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_skinny_show_device); ast_manager_register_xml("SKINNYlines", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_skinny_show_lines); ast_manager_register_xml("SKINNYshowline", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_skinny_show_line); if (ast_sched_start_thread(sched)) { ast_sched_context_destroy(sched); sched = NULL; ast_channel_unregister(&skinny_tech); ao2_ref(default_cap, -1); ao2_ref(skinny_tech.capabilities, -1); return AST_MODULE_LOAD_FAILURE; } return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) { struct skinnysession *s; struct skinny_device *d; struct skinny_line *l; struct skinny_subchannel *sub; struct ast_context *con; pthread_t tempthread; ast_rtp_glue_unregister(&skinny_rtp_glue); ast_channel_unregister(&skinny_tech); ao2_cleanup(skinny_tech.capabilities); ast_cli_unregister_multiple(cli_skinny, ARRAY_LEN(cli_skinny)); ast_manager_unregister("SKINNYdevices"); ast_manager_unregister("SKINNYshowdevice"); ast_manager_unregister("SKINNYlines"); ast_manager_unregister("SKINNYshowline"); ast_mutex_lock(&netlock); if (accept_t && (accept_t != AST_PTHREADT_STOP)) { pthread_cancel(accept_t); pthread_kill(accept_t, SIGURG); pthread_join(accept_t, NULL); } accept_t = AST_PTHREADT_STOP; ast_mutex_unlock(&netlock); AST_LIST_LOCK(&sessions); /* Destroy all the interfaces and free their memory */ while((s = AST_LIST_REMOVE_HEAD(&sessions, list))) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); AST_LIST_UNLOCK(&sessions); d = s->device; AST_LIST_TRAVERSE(&d->lines, l, list){ ast_mutex_lock(&l->lock); AST_LIST_TRAVERSE(&l->sub, sub, list) { skinny_locksub(sub); if (sub->owner) { ast_softhangup(sub->owner, AST_SOFTHANGUP_APPUNLOAD); } skinny_unlocksub(sub); } if (l->mwi_event_sub) { l->mwi_event_sub = stasis_unsubscribe(l->mwi_event_sub); } ast_mutex_unlock(&l->lock); unregister_exten(l); } ast_endpoint_set_state(d->endpoint, AST_ENDPOINT_OFFLINE); blob = ast_json_pack("{s: s}", "peer_status", "Unregistered"); ast_endpoint_blob_publish(d->endpoint, ast_endpoint_state_type(), blob); tempthread = s->t; pthread_cancel(tempthread); pthread_join(tempthread, NULL); AST_LIST_LOCK(&sessions); } AST_LIST_UNLOCK(&sessions); delete_devices(); close(skinnysock); if (sched) { ast_sched_context_destroy(sched); } con = ast_context_find(used_context); if (con) ast_context_destroy(con, "Skinny"); ao2_ref(default_cap, -1); return 0; } static int reload(void) { skinny_reload(); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Skinny Client Control Protocol (Skinny)", .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, ); asterisk-13.1.0/channels/console_video.c0000644000000000000000000012246111766660300016721 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright 2007-2008, Marta Carbone, Sergio Fadda, Luigi Rizzo * * 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. */ /* * Experimental support for video sessions. We use SDL for rendering, ffmpeg * as the codec library for encoding and decoding, and Video4Linux and X11 * to generate the local video stream. * * If one of these pieces is not available, either at compile time or at * runtime, we do our best to run without it. Of course, no codec library * means we can only deal with raw data, no SDL means we cannot do rendering, * no V4L or X11 means we cannot generate data (but in principle we could * stream from or record to a file). * * We need a recent (2007.07.12 or newer) version of ffmpeg to avoid warnings. * Older versions might give 'deprecated' messages during compilation, * thus not compiling in AST_DEVMODE, or don't have swscale, in which case * you can try to compile #defining OLD_FFMPEG here. * * $Revision: 369013 $ */ //#define DROP_PACKETS 5 /* if set, drop this % of video packets */ //#define OLD_FFMPEG 1 /* set for old ffmpeg with no swscale */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 369013 $") #include #include "asterisk/cli.h" #include "asterisk/file.h" #include "asterisk/channel.h" #include "console_video.h" /* The code is structured as follows. When a new console channel is created, we call console_video_start() to initialize SDL, the source, and the encoder/ decoder for the formats in use (XXX the latter two should be done later, once the codec negotiation is complete). Also, a thread is created to handle the video source and generate frames. While communication is on, the local source is generated by the video thread, which wakes up periodically, generates frames and enqueues them in chan->readq. Incoming rtp frames are passed to console_write_video(), decoded and passed to SDL for display. For as unfortunate and confusing as it can be, we need to deal with a number of different video representations (size, codec/pixel format, codec parameters), as follows: loc_src is the data coming from the camera/X11/etc. The format is typically constrained by the video source. enc_in is the input required by the encoder. Typically constrained in size by the encoder type. enc_out is the bitstream transmitted over RTP. Typically negotiated while the call is established. loc_dpy is the format used to display the local video source. Depending on user preferences this can have the same size as loc_src_fmt, or enc_in_fmt, or thumbnail size (e.g. PiP output) dec_in is the incoming RTP bitstream. Negotiated during call establishment, it is not necessarily the same as enc_in_fmt dec_out the output of the decoder. The format is whatever the other side sends, and the buffer is allocated by avcodec_decode_... so we only copy the data here. rem_dpy the format used to display the remote stream src_dpy is the format used to display the local video source streams The number of these fbuf_t is determined at run time, with dynamic allocation We store the format info together with the buffer storing the data. As a future optimization, a format/buffer may reference another one if the formats are equivalent. This will save some unnecessary format conversion. In order to handle video you need to add to sip.conf (and presumably iax.conf too) the following: [general](+) videosupport=yes allow=h263 ; this or other video formats allow=h263p ; this or other video formats */ /* * Codecs are absolutely necessary or we cannot do anything. * SDL is optional (used for rendering only), so that we can still * stream video withouth displaying it. */ #if !defined(HAVE_VIDEO_CONSOLE) || !defined(HAVE_FFMPEG) /* stubs if required pieces are missing */ int console_write_video(struct ast_channel *chan, struct ast_frame *f) { return 0; /* writing video not supported */ } int console_video_cli(struct video_desc *env, const char *var, int fd) { return 1; /* nothing matched */ } int console_video_config(struct video_desc **penv, const char *var, const char *val) { return 1; /* no configuration */ } void console_video_start(struct video_desc *env, struct ast_channel *owner) { ast_log(LOG_NOTICE, "voice only, console video support not present\n"); } void console_video_uninit(struct video_desc *env) { } int get_gui_startup(struct video_desc* env) { return 0; /* no gui here */ } int console_video_formats = 0; #else /* defined(HAVE_FFMPEG) && defined(HAVE_SDL) */ /*! The list of video formats we support. */ int console_video_formats = AST_FORMAT_H263_PLUS | AST_FORMAT_H263 | AST_FORMAT_MP4_VIDEO | AST_FORMAT_H264 | AST_FORMAT_H261 ; /* function to scale and encode buffers */ static void my_scale(struct fbuf_t *in, AVPicture *p_in, struct fbuf_t *out, AVPicture *p_out); /* * this structure will be an entry in the table containing * every device specified in the file oss.conf, it contains various infomation * about the device */ struct video_device { char *name; /* name of the device */ /* allocated dynamically (see fill_table function) */ struct grab_desc *grabber; /* the grabber for the device type */ void *grabber_data; /* device's private data structure */ struct fbuf_t *dev_buf; /* buffer for incoming data */ struct timeval last_frame; /* when we read the last frame ? */ int status_index; /* what is the status of the device (source) */ /* status index is set using the IS_ON, IS_PRIMARY and IS_SECONDARY costants */ /* status_index is the index of the status message in the src_msgs array in console_gui.c */ }; struct video_codec_desc; /* forward declaration */ /* * Descriptor of the local source, made of the following pieces: * + configuration info (geometry, device name, fps...). These are read * from the config file and copied here before calling video_out_init(); * + the frame buffer (buf) and source pixel format, allocated at init time; * + the encoding and RTP info, including timestamps to generate * frames at the correct rate; * + source-specific info, i.e. fd for /dev/video, dpy-image for x11, etc, * filled in by grabber_open, part of source_specific information are in * the device table (devices member), others are shared; * NOTE: loc_src.data == NULL means the rest of the struct is invalid, and * the video source is not available. */ struct video_out_desc { /* video device support. * videodevice and geometry are read from the config file. * At the right time we try to open it and allocate a buffer. * If we are successful, webcam_bufsize > 0 and we can read. */ /* all the following is config file info copied from the parent */ int fps; int bitrate; int qmin; int sendvideo; struct fbuf_t loc_src_geometry; /* local source geometry only (from config file) */ struct fbuf_t enc_out; /* encoder output buffer, allocated in video_out_init() */ struct video_codec_desc *enc; /* encoder */ void *enc_ctx; /* encoding context */ AVCodec *codec; AVFrame *enc_in_frame; /* enc_in mapped into avcodec format. */ /* The initial part of AVFrame is an AVPicture */ int mtu; /* Table of devices specified with "videodevice=" in oss.conf. * Static size as we have a limited number of entries. */ struct video_device devices[MAX_VIDEO_SOURCES]; int device_num; /*number of devices in table*/ int device_primary; /*index of the actual primary device in the table*/ int device_secondary; /*index of the actual secondary device in the table*/ int picture_in_picture; /*Is the PiP mode activated? 0 = NO | 1 = YES*/ /* these are the coordinates of the picture inside the picture (visible if PiP mode is active) these coordinates are valid considering the containing buffer with cif geometry*/ int pip_x; int pip_y; }; /* * The overall descriptor, with room for config info, video source and * received data descriptors, SDL info, etc. * This should be globally visible to all modules (grabber, vcodecs, gui) * and contain all configurtion info. */ struct video_desc { char codec_name[64]; /* the codec we use */ int stayopen; /* set if gui starts manually */ pthread_t vthread; /* video thread */ ast_mutex_t dec_lock; /* sync decoder and video thread */ int shutdown; /* set to shutdown vthread */ struct ast_channel *owner; /* owner channel */ struct fbuf_t enc_in; /* encoder input buffer, allocated in video_out_init() */ char keypad_file[256]; /* image for the keypad */ char keypad_font[256]; /* font for the keypad */ char sdl_videodriver[256]; struct fbuf_t rem_dpy; /* display remote video, no buffer (it is in win[WIN_REMOTE].bmp) */ struct fbuf_t loc_dpy; /* display local source, no buffer (managed by SDL in bmp[1]) */ /* geometry of the thumbnails for all video sources. */ struct fbuf_t src_dpy[MAX_VIDEO_SOURCES]; /* no buffer allocated here */ int frame_freeze; /* flag to freeze the incoming frame */ /* local information for grabbers, codecs, gui */ struct gui_info *gui; struct video_dec_desc *in; /* remote video descriptor */ struct video_out_desc out; /* local video descriptor */ }; static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p); void fbuf_free(struct fbuf_t *b) { struct fbuf_t x = *b; if (b->data && b->size) ast_free(b->data); memset(b, '\0', sizeof(*b)); /* restore some fields */ b->w = x.w; b->h = x.h; b->pix_fmt = x.pix_fmt; } /* return the status of env->stayopen to chan_oss, as the latter * does not have access to fields of struct video_desc */ int get_gui_startup(struct video_desc* env) { return env ? env->stayopen : 0; } #if 0 /* helper function to print the amount of memory used by the process. * Useful to track memory leaks, unfortunately this code is OS-specific * so we keep it commented out. */ static int used_mem(const char *msg) { char in[128]; pid_t pid = getpid(); sprintf(in, "ps -o vsz= -o rss= %d", pid); ast_log(LOG_WARNING, "used mem (vsize, rss) %s ", msg); system(in); return 0; } #endif #include "vcodecs.c" #include "console_gui.c" /*! \brief Try to open video sources, return 0 on success, 1 on error * opens all video sources found in the oss.conf configuration files. * Saves the grabber and the datas in the device table (in the devices field * of the descriptor referenced by v). * Initializes the device_primary and device_secondary * fields of v with the first devices that was * successfully opened. * * \param v = video out environment descriptor * * returns 0 on success, 1 on error */ static int grabber_open(struct video_out_desc *v) { struct grab_desc *g; void *g_data; int i, j; /* for each device in the device table... */ for (i = 0; i < v->device_num; i++) { /* device already open */ if (v->devices[i].grabber) continue; /* for each type of grabber supported... */ for (j = 0; (g = console_grabbers[j]); j++) { /* the grabber is opened and the informations saved in the device table */ g_data = g->open(v->devices[i].name, &v->loc_src_geometry, v->fps); if (!g_data) continue; v->devices[i].grabber = g; v->devices[i].grabber_data = g_data; v->devices[i].status_index |= IS_ON; } } /* the first working device is selected as the primary one and the secondary one */ for (i = 0; i < v->device_num; i++) { if (!v->devices[i].grabber) continue; v->device_primary = i; v->device_secondary = i; return 0; /* source found */ } return 1; /* no source found */ } /*! \brief complete a buffer from the specified local video source. * Called by get_video_frames(), in turn called by the video thread. * * \param dev = video environment descriptor * \param fps = frame per seconds, for every device * * returns: * - NULL on falure * - reference to the device buffer on success */ static struct fbuf_t *grabber_read(struct video_device *dev, int fps) { struct timeval now = ast_tvnow(); if (dev->grabber == NULL) /* not initialized */ return NULL; /* the last_frame field in this row of the device table (dev) is always initialized, it is set during the parsing of the config file, and never unset, function fill_device_table(). */ /* check if it is time to read */ if (ast_tvdiff_ms(now, dev->last_frame) < 1000/fps) return NULL; /* too early */ dev->last_frame = now; /* XXX actually, should correct for drift */ return dev->grabber->read(dev->grabber_data); } /*! \brief handler run when dragging with the left button on * the local source window - the effect is to move the offset * of the captured area. */ static void grabber_move(struct video_device *dev, int dx, int dy) { if (dev->grabber && dev->grabber->move) { dev->grabber->move(dev->grabber_data, dx, dy); } } /* * Map the codec name to the library. If not recognised, use a default. * This is useful in the output path where we decide by name, presumably. */ static struct video_codec_desc *map_config_video_format(char *name) { int i; for (i = 0; supported_codecs[i]; i++) if (!strcasecmp(name, supported_codecs[i]->name)) break; if (supported_codecs[i] == NULL) { ast_log(LOG_WARNING, "Cannot find codec for '%s'\n", name); i = 0; strcpy(name, supported_codecs[i]->name); } ast_log(LOG_WARNING, "Using codec '%s'\n", name); return supported_codecs[i]; } /*! \brief uninitialize the descriptor for local video stream */ static int video_out_uninit(struct video_desc *env) { struct video_out_desc *v = &env->out; int i; /* integer variable used as iterator */ /* XXX this should be a codec callback */ if (v->enc_ctx) { AVCodecContext *enc_ctx = (AVCodecContext *)v->enc_ctx; avcodec_close(enc_ctx); av_free(enc_ctx); v->enc_ctx = NULL; } if (v->enc_in_frame) { av_free(v->enc_in_frame); v->enc_in_frame = NULL; } v->codec = NULL; /* nothing to free, this is only a reference */ /* release the buffers */ fbuf_free(&env->enc_in); fbuf_free(&v->enc_out); /* close the grabbers */ for (i = 0; i < v->device_num; i++) { if (v->devices[i].grabber){ v->devices[i].grabber_data = v->devices[i].grabber->close(v->devices[i].grabber_data); v->devices[i].grabber = NULL; /* dev_buf is already freed by grabber->close() */ v->devices[i].dev_buf = NULL; } v->devices[i].status_index = 0; } v->picture_in_picture = 0; env->frame_freeze = 0; return -1; } /* * Initialize the encoder for the local source: * - enc_ctx, codec, enc_in_frame are used by ffmpeg for encoding; * - enc_out is used to store the encoded frame (to be sent) * - mtu is used to determine the max size of video fragment * NOTE: we enter here with the video source already open. */ static int video_out_init(struct video_desc *env) { int codec; int size; struct fbuf_t *enc_in; struct video_out_desc *v = &env->out; v->enc_ctx = NULL; v->codec = NULL; v->enc_in_frame = NULL; v->enc_out.data = NULL; codec = map_video_format(v->enc->format, CM_WR); v->codec = avcodec_find_encoder(codec); if (!v->codec) { ast_log(LOG_WARNING, "Cannot find the encoder for format %d\n", codec); return -1; /* error, but nothing to undo yet */ } v->mtu = 1400; /* set it early so the encoder can use it */ /* allocate the input buffer for encoding. * ffmpeg only supports PIX_FMT_YUV420P for the encoding. */ enc_in = &env->enc_in; enc_in->pix_fmt = PIX_FMT_YUV420P; enc_in->size = (enc_in->w * enc_in->h * 3)/2; enc_in->data = ast_calloc(1, enc_in->size); if (!enc_in->data) { ast_log(LOG_WARNING, "Cannot allocate encoder input buffer\n"); return video_out_uninit(env); } /* construct an AVFrame that points into buf_in */ v->enc_in_frame = avcodec_alloc_frame(); if (!v->enc_in_frame) { ast_log(LOG_WARNING, "Unable to allocate the encoding video frame\n"); return video_out_uninit(env); } /* parameters for PIX_FMT_YUV420P */ size = enc_in->w * enc_in->h; v->enc_in_frame->data[0] = enc_in->data; v->enc_in_frame->data[1] = v->enc_in_frame->data[0] + size; v->enc_in_frame->data[2] = v->enc_in_frame->data[1] + size/4; v->enc_in_frame->linesize[0] = enc_in->w; v->enc_in_frame->linesize[1] = enc_in->w/2; v->enc_in_frame->linesize[2] = enc_in->w/2; /* now setup the parameters for the encoder. * XXX should be codec-specific */ { AVCodecContext *enc_ctx = avcodec_alloc_context(); v->enc_ctx = enc_ctx; enc_ctx->pix_fmt = enc_in->pix_fmt; enc_ctx->width = enc_in->w; enc_ctx->height = enc_in->h; /* XXX rtp_callback ? * rtp_mode so ffmpeg inserts as many start codes as possible. */ enc_ctx->rtp_mode = 1; enc_ctx->rtp_payload_size = v->mtu / 2; // mtu/2 enc_ctx->bit_rate = v->bitrate; enc_ctx->bit_rate_tolerance = enc_ctx->bit_rate/2; enc_ctx->qmin = v->qmin; /* should be configured */ enc_ctx->time_base = (AVRational){1, v->fps}; enc_ctx->gop_size = v->fps*5; // emit I frame every 5 seconds v->enc->enc_init(v->enc_ctx); if (avcodec_open(enc_ctx, v->codec) < 0) { ast_log(LOG_WARNING, "Unable to initialize the encoder %d\n", codec); av_free(enc_ctx); v->enc_ctx = NULL; return video_out_uninit(env); } } /* * Allocate enough for the encoded bitstream. As we are compressing, * we hope that the output is never larger than the input size. */ v->enc_out.data = ast_calloc(1, enc_in->size); v->enc_out.size = enc_in->size; v->enc_out.used = 0; return 0; } /*! \brief possibly uninitialize the video console. * Called at the end of a call, should reset the 'owner' field, * then possibly terminate the video thread if the gui has * not been started manually. * In practice, signal the thread and give it a bit of time to * complete, giving up if it gets stuck. Because uninit * is called from hangup with the channel locked, and the thread * uses the chan lock, we need to unlock here. This is unsafe, * and we should really use refcounts for the channels. */ void console_video_uninit(struct video_desc *env) { int i, t = 100; /* initial wait is shorter, than make it longer */ if (env->stayopen == 0) { /* gui opened by a call, do the shutdown */ env->shutdown = 1; for (i=0; env->shutdown && i < 10; i++) { if (env->owner) ast_channel_unlock(env->owner); usleep(t); t = 1000000; if (env->owner) ast_channel_lock(env->owner); } env->vthread = NULL; } env->owner = NULL; /* this is unconditional */ } /*! fill an AVPicture from our fbuf info, as it is required by * the image conversion routines in ffmpeg. Note that the pointers * are recalculated if the fbuf has an offset (and so represents a picture in picture) * XXX This depends on the format. */ static AVPicture *fill_pict(struct fbuf_t *b, AVPicture *p) { /* provide defaults for commonly used formats */ int l4 = b->w * b->h/4; /* size of U or V frame */ int len = b->w; /* Y linesize, bytes */ int luv = b->w/2; /* U/V linesize, bytes */ int sample_size = 1; memset(p, '\0', sizeof(*p)); switch (b->pix_fmt) { case PIX_FMT_RGB555: case PIX_FMT_RGB565: sample_size = 2; luv = 0; break; case PIX_FMT_RGBA32: sample_size = 4; luv = 0; break; case PIX_FMT_YUYV422: /* Packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr */ sample_size = 2; /* all data in first plane, probably */ luv = 0; break; } len *= sample_size; p->data[0] = b->data; p->linesize[0] = len; /* these are only valid for component images */ p->data[1] = luv ? b->data + 4*l4 : b->data+len; p->data[2] = luv ? b->data + 5*l4 : b->data+len; p->linesize[1] = luv; p->linesize[2] = luv; /* add the offsets to the pointers previously calculated, it is necessary for the picture in picture mode */ p->data[0] += len*b->win_y + b->win_x*sample_size; if (luv) { p->data[1] += luv*(b->win_y/2) + (b->win_x/2) * sample_size; p->data[2] += luv*(b->win_y/2) + (b->win_x/2) * sample_size; } return p; } /*! convert/scale between an input and an output format. * Old version of ffmpeg only have img_convert, which does not rescale. * New versions use sws_scale which does both. */ static void my_scale(struct fbuf_t *in, AVPicture *p_in, struct fbuf_t *out, AVPicture *p_out) { AVPicture my_p_in, my_p_out; int eff_w=out->w, eff_h=out->h; if (p_in == NULL) p_in = fill_pict(in, &my_p_in); if (p_out == NULL) p_out = fill_pict(out, &my_p_out); /*if win_w is different from zero then we must change the size of the scaled buffer (the position is already encoded into the out parameter)*/ if (out->win_w) { /* picture in picture enabled */ eff_w=out->win_w; eff_h=out->win_h; } #ifdef OLD_FFMPEG /* XXX img_convert is deprecated, and does not do rescaling, PiP not supported */ img_convert(p_out, out->pix_fmt, p_in, in->pix_fmt, in->w, in->h); #else /* XXX replacement */ { struct SwsContext *convert_ctx; convert_ctx = sws_getContext(in->w, in->h, in->pix_fmt, eff_w, eff_h, out->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL); if (convert_ctx == NULL) { ast_log(LOG_ERROR, "FFMPEG::convert_cmodel : swscale context initialization failed\n"); return; } if (0) ast_log(LOG_WARNING, "in %d %dx%d out %d %dx%d\n", in->pix_fmt, in->w, in->h, out->pix_fmt, eff_w, eff_h); sws_scale(convert_ctx, p_in->data, p_in->linesize, in->w, in->h, /* src slice */ p_out->data, p_out->linesize); sws_freeContext(convert_ctx); } #endif /* XXX replacement */ } struct video_desc *get_video_desc(struct ast_channel *c); /* * This function is called (by asterisk) for each video packet * coming from the network (the 'in' path) that needs to be processed. * We need to reconstruct the entire video frame before we can decode it. * After a video packet is received we have to: * - extract the bitstream with pre_process_data() * - append the bitstream to a buffer * - if the fragment is the last (RTP Marker) we decode it with decode_video() * - after the decoding is completed we display the decoded frame with show_frame() */ int console_write_video(struct ast_channel *chan, struct ast_frame *f); int console_write_video(struct ast_channel *chan, struct ast_frame *f) { struct video_desc *env = get_video_desc(chan); struct video_dec_desc *v = env->in; if (!env->gui) /* no gui, no rendering */ return 0; if (v == NULL) env->in = v = dec_init(f->subclass & ~1); if (v == NULL) { /* This is not fatal, but we won't have incoming video */ ast_log(LOG_WARNING, "Cannot initialize input decoder\n"); return 0; } if (v->dec_in_cur == NULL) /* no buffer for incoming frames, drop */ return 0; #if defined(DROP_PACKETS) && DROP_PACKETS > 0 /* Simulate lost packets */ if ((random() % 10000) <= 100*DROP_PACKETS) { ast_log(LOG_NOTICE, "Packet lost [%d]\n", f->seqno); return 0; } #endif if (v->discard) { /* * In discard mode, drop packets until we find one with * the RTP marker set (which is the end of frame). * Note that the RTP marker flag is sent as the LSB of the * subclass, which is a bitmask of formats. The low bit is * normally used for audio so there is no interference. */ if (f->subclass & 0x01) { v->dec_in_cur->used = 0; v->dec_in_cur->ebit = 0; v->next_seq = f->seqno + 1; /* wrap at 16 bit */ v->discard = 0; ast_log(LOG_WARNING, "out of discard mode, frame %d\n", f->seqno); } return 0; } /* * Only in-order fragments will be accepted. Remember seqno * has 16 bit so there is wraparound. Also, ideally we could * accept a bit of reordering, but at the moment we don't. */ if (v->next_seq != f->seqno) { ast_log(LOG_WARNING, "discarding frame out of order, %d %d\n", v->next_seq, f->seqno); v->discard = 1; return 0; } v->next_seq++; if (f->data.ptr == NULL || f->datalen < 2) { ast_log(LOG_WARNING, "empty video frame, discard\n"); return 0; } if (v->d_callbacks->dec_decap(v->dec_in_cur, f->data.ptr, f->datalen)) { ast_log(LOG_WARNING, "error in dec_decap, enter discard\n"); v->discard = 1; } if (f->subclass & 0x01) { // RTP Marker /* prepare to decode: advance the buffer so the video thread knows. */ struct fbuf_t *tmp = v->dec_in_cur; /* store current pointer */ ast_mutex_lock(&env->dec_lock); if (++v->dec_in_cur == &v->dec_in[N_DEC_IN]) /* advance to next, circular */ v->dec_in_cur = &v->dec_in[0]; if (v->dec_in_dpy == NULL) { /* were not displaying anything, so set it */ v->dec_in_dpy = tmp; } else if (v->dec_in_dpy == v->dec_in_cur) { /* current slot is busy */ v->dec_in_cur = NULL; } ast_mutex_unlock(&env->dec_lock); } return 0; } /*! \brief refreshes the buffers of all the device by calling the * grabber_read on each device in the device table. * it encodes the primary source buffer, if the picture in picture mode is * enabled it encodes (in the buffer to split) the secondary source buffer too. * The encoded buffer is splitted to build the local and the remote view. * Return a list of ast_frame representing the video fragments. * The head pointer is returned by the function, the tail pointer * is returned as an argument. * * \param env = video environment descriptor * \param tail = tail ponter (pratically a return value) */ static struct ast_frame *get_video_frames(struct video_desc *env, struct ast_frame **tail) { struct video_out_desc *v = &env->out; struct ast_frame *dummy; struct fbuf_t *loc_src_primary = NULL, *p_read; int i; /* if no device was found in the config file */ if (!env->out.device_num) return NULL; /* every time this function is called we refresh the buffers of every device, updating the private device buffer in the device table */ for (i = 0; i < env->out.device_num; i++) { p_read = grabber_read(&env->out.devices[i], env->out.fps); /* it is used only if different from NULL, we mantain last good buffer otherwise */ if (p_read) env->out.devices[i].dev_buf = p_read; } /* select the primary device buffer as the one to encode */ loc_src_primary = env->out.devices[env->out.device_primary].dev_buf; /* loc_src_primary can be NULL if the device has been turned off during execution of it is read too early */ if (loc_src_primary) { /* Scale the video for the encoder, then use it for local rendering so we will see the same as the remote party */ my_scale(loc_src_primary, NULL, &env->enc_in, NULL); } if (env->out.picture_in_picture) { /* the picture in picture mode is enabled */ struct fbuf_t *loc_src_secondary; /* reads from the secondary source */ loc_src_secondary = env->out.devices[env->out.device_secondary].dev_buf; if (loc_src_secondary) { env->enc_in.win_x = env->out.pip_x; env->enc_in.win_y = env->out.pip_y; env->enc_in.win_w = env->enc_in.w/3; env->enc_in.win_h = env->enc_in.h/3; /* scales to the correct geometry and inserts in the enc_in buffer the picture in picture */ my_scale(loc_src_secondary, NULL, &env->enc_in, NULL); /* returns to normal parameters (not picture in picture) */ env->enc_in.win_x = 0; env->enc_in.win_y = 0; env->enc_in.win_w = 0; env->enc_in.win_h = 0; } else { /* loc_src_secondary can be NULL if the device has been turned off during execution of it is read too early */ env->out.picture_in_picture = 0; /* disable picture in picture */ } } show_frame(env, WIN_LOCAL); /* local rendering */ for (i = 0; i < env->out.device_num; i++) show_frame(env, i+WIN_SRC1); /* rendering of every source device in thumbnails */ if (tail == NULL) tail = &dummy; *tail = NULL; /* if no reason for encoding, do not encode */ if (!env->owner || !loc_src_primary || !v->sendvideo) return NULL; if (v->enc_out.data == NULL) { static volatile int a = 0; if (a++ < 2) ast_log(LOG_WARNING, "fail, no encoder output buffer\n"); return NULL; } v->enc->enc_run(v); return v->enc->enc_encap(&v->enc_out, v->mtu, tail); } /* * Helper thread to periodically poll the video sources and enqueue the * generated frames directed to the remote party to the channel's queue. * Using a separate thread also helps because the encoding can be * computationally expensive so we don't want to starve the main thread. */ static void *video_thread(void *arg) { struct video_desc *env = arg; int count = 0; char save_display[128] = ""; int i; /* integer variable used as iterator */ /* if sdl_videodriver is set, override the environment. Also, * if it contains 'console' override DISPLAY around the call to SDL_Init * so we use the console as opposed to the x11 version of aalib */ if (!ast_strlen_zero(env->sdl_videodriver)) { /* override */ const char *s = getenv("DISPLAY"); setenv("SDL_VIDEODRIVER", env->sdl_videodriver, 1); if (s && !strcasecmp(env->sdl_videodriver, "aalib-console")) { ast_copy_string(save_display, s, sizeof(save_display)); unsetenv("DISPLAY"); } } sdl_setup(env); if (!ast_strlen_zero(save_display)) { setenv("DISPLAY", save_display, 1); } ast_mutex_init(&env->dec_lock); /* used to sync decoder and renderer */ if (grabber_open(&env->out)) { ast_log(LOG_WARNING, "cannot open local video source\n"); } if (env->out.device_num) { env->out.devices[env->out.device_primary].status_index |= IS_PRIMARY | IS_SECONDARY; } /* even if no device is connected, we must call video_out_init, * as some of the data structures it initializes are * used in get_video_frames() */ video_out_init(env); /* Writes intial status of the sources. */ if (env->gui) { for (i = 0; i < env->out.device_num; i++) { print_message(env->gui->thumb_bd_array[i].board, src_msgs[env->out.devices[i].status_index]); } } for (;;) { struct timespec t = { 0, 50000000 }; /* XXX 20 times/sec */ struct ast_frame *p, *f; struct ast_channel *chan; int fd; char *caption = NULL, buf[160]; /* determine if video format changed */ if (count++ % 10 == 0) { if (env->out.sendvideo && env->out.devices) { snprintf(buf, sizeof(buf), "%s %s %dx%d @@ %dfps %dkbps", env->out.devices[env->out.device_primary].name, env->codec_name, env->enc_in.w, env->enc_in.h, env->out.fps, env->out.bitrate / 1000); } else { sprintf(buf, "hold"); } caption = buf; } /* manage keypad events */ /* XXX here we should always check for events, * otherwise the drag will not work */ if (env->gui) eventhandler(env, caption); /* sleep for a while */ nanosleep(&t, NULL); if (env->in) { struct video_dec_desc *v = env->in; /* * While there is something to display, call the decoder and free * the buffer, possibly enabling the receiver to store new data. */ while (v->dec_in_dpy) { struct fbuf_t *tmp = v->dec_in_dpy; /* store current pointer */ /* decode the frame, but show it only if not frozen */ if (v->d_callbacks->dec_run(v, tmp) && !env->frame_freeze) show_frame(env, WIN_REMOTE); tmp->used = 0; /* mark buffer as free */ tmp->ebit = 0; ast_mutex_lock(&env->dec_lock); if (++v->dec_in_dpy == &v->dec_in[N_DEC_IN]) /* advance to next, circular */ v->dec_in_dpy = &v->dec_in[0]; if (v->dec_in_cur == NULL) /* receiver was idle, enable it... */ v->dec_in_cur = tmp; /* using the slot just freed */ else if (v->dec_in_dpy == v->dec_in_cur) /* this was the last slot */ v->dec_in_dpy = NULL; /* nothing more to display */ ast_mutex_unlock(&env->dec_lock); } } if (env->shutdown) break; f = get_video_frames(env, &p); /* read and display */ if (!f) continue; chan = env->owner; if (chan == NULL) { /* drop the chain of frames, nobody uses them */ while (f) { struct ast_frame *g = AST_LIST_NEXT(f, frame_list); ast_frfree(f); f = g; } continue; } ast_channel_lock(chan); /* AST_LIST_INSERT_TAIL is only good for one frame, cannot use here */ if (ast_channel_readq(chan).first == NULL) { ast_channel_readq(chan).first = f; } else { ast_channel_readq(chan).last->frame_list.next = f; } ast_channel_readq(chan).last = p; /* * more or less same as ast_queue_frame, but extra * write on the alertpipe to signal frames. */ if (ast_channel_alertable(chan)) { for (p = f; p; p = AST_LIST_NEXT(p, frame_list)) { if (ast_channel_alert(chan)) { ast_log(LOG_WARNING, "Unable to write to alert pipe on %s, frametype/subclass %d/%d: %s!\n", ast_channel_name(chan), f->frametype, f->subclass, strerror(errno)); } } ast_channel_unlock(chan); } /* thread terminating, here could call the uninit */ /* uninitialize the local and remote video environments */ env->in = dec_uninit(env->in); video_out_uninit(env); if (env->gui) env->gui = cleanup_sdl(env->gui, env->out.device_num); ast_mutex_destroy(&env->dec_lock); env->shutdown = 0; return NULL; } static void copy_geometry(struct fbuf_t *src, struct fbuf_t *dst) { if (dst->w == 0) dst->w = src->w; if (dst->h == 0) dst->h = src->h; } /*! initialize the video environment. * Apart from the formats (constant) used by sdl and the codec, * we use enc_in as the basic geometry. */ static void init_env(struct video_desc *env) { struct fbuf_t *c = &(env->out.loc_src_geometry); /* local source */ struct fbuf_t *ei = &(env->enc_in); /* encoder input */ struct fbuf_t *ld = &(env->loc_dpy); /* local display */ struct fbuf_t *rd = &(env->rem_dpy); /* remote display */ int i; /* integer working as iterator */ c->pix_fmt = PIX_FMT_YUV420P; /* default - camera format */ ei->pix_fmt = PIX_FMT_YUV420P; /* encoder input */ if (ei->w == 0 || ei->h == 0) { ei->w = 352; ei->h = 288; } ld->pix_fmt = rd->pix_fmt = PIX_FMT_YUV420P; /* sdl format */ /* inherit defaults */ copy_geometry(ei, c); /* camera inherits from encoder input */ copy_geometry(ei, rd); /* remote display inherits from encoder input */ copy_geometry(rd, ld); /* local display inherits from remote display */ /* fix the size of buffers for small windows */ for (i = 0; i < env->out.device_num; i++) { env->src_dpy[i].pix_fmt = PIX_FMT_YUV420P; env->src_dpy[i].w = SRC_WIN_W; env->src_dpy[i].h = SRC_WIN_H; } /* now we set the default coordinates for the picture in picture frames inside the env_in buffers, those can be changed by dragging the picture in picture with left click */ env->out.pip_x = ei->w - ei->w/3; env->out.pip_y = ei->h - ei->h/3; } /*! * The first call to the video code, called by oss_new() or similar. * Here we initialize the various components we use, namely SDL for display, * ffmpeg for encoding/decoding, and a local video source. * We do our best to progress even if some of the components are not * available. */ void console_video_start(struct video_desc *env, struct ast_channel *owner) { ast_log(LOG_WARNING, "env %p chan %p\n", env, owner); if (env == NULL) /* video not initialized */ return; env->owner = owner; /* work even if no owner is specified */ if (env->vthread) return; /* already initialized, nothing to do */ init_env(env); env->out.enc = map_config_video_format(env->codec_name); ast_log(LOG_WARNING, "start video out %s %dx%d\n", env->codec_name, env->enc_in.w, env->enc_in.h); /* * Register all codecs supported by the ffmpeg library. * We only need to do it once, but probably doesn't * harm to do it multiple times. */ avcodec_init(); avcodec_register_all(); av_log_set_level(AV_LOG_ERROR); /* only report errors */ if (env->out.fps == 0) { env->out.fps = 15; ast_log(LOG_WARNING, "fps unset, forcing to %d\n", env->out.fps); } if (env->out.bitrate == 0) { env->out.bitrate = 65000; ast_log(LOG_WARNING, "bitrate unset, forcing to %d\n", env->out.bitrate); } /* create the thread as detached so memory is freed on termination */ ast_pthread_create_detached_background(&env->vthread, NULL, video_thread, env); } /* * Parse a geometry string, accepting also common names for the formats. * Trick: if we have a leading > or < and a numeric geometry, * return the larger or smaller one. * E.g. <352x288 gives the smaller one, 320x240 */ static int video_geom(struct fbuf_t *b, const char *s) { int w = 0, h = 0; static struct { const char *s; int w; int h; } *fp, formats[] = { {"16cif", 1408, 1152 }, {"xga", 1024, 768 }, {"4cif", 704, 576 }, {"vga", 640, 480 }, {"cif", 352, 288 }, {"qvga", 320, 240 }, {"qcif", 176, 144 }, {"sqcif", 128, 96 }, {NULL, 0, 0 }, }; if (*s == '<' || *s == '>') sscanf(s+1,"%dx%d", &w, &h); for (fp = formats; fp->s; fp++) { if (*s == '>') { /* look for a larger one */ if (fp->w <= w) { if (fp > formats) fp--; /* back one step if possible */ break; } } else if (*s == '<') { /* look for a smaller one */ if (fp->w < w) break; } else if (!strcasecmp(s, fp->s)) { /* look for a string */ break; } } if (*s == '<' && fp->s == NULL) /* smallest */ fp--; if (fp->s) { b->w = fp->w; b->h = fp->h; } else if (sscanf(s, "%dx%d", &b->w, &b->h) != 2) { ast_log(LOG_WARNING, "Invalid video_size %s, using 352x288\n", s); b->w = 352; b->h = 288; } return 0; } /*! \brief add an entry to the video_device table, * ignoring duplicate names. * The table is a static array of 9 elements. * The last_frame field of each entry of the table is initialized to * the current time (we need a value inside this field, on stop of the * GUI the last_frame value is not changed, to avoid checking if it is 0 we * set the initial value on current time) XXX * * PARAMETERS: * \param devices_p = pointer to the table of devices * \param device_num_p = pointer to the number of devices * \param s = name of the new device to insert * * returns 0 on success, 1 on error */ static int device_table_fill(struct video_device *devices, int *device_num_p, const char *s) { int i; struct video_device *p; /* with the current implementation, we support a maximum of 9 devices.*/ if (*device_num_p >= 9) return 0; /* more devices will be ignored */ /* ignore duplicate names */ for (i = 0; i < *device_num_p; i++) { if (!strcmp(devices[i].name, s)) return 0; } /* inserts the new video device */ p = &devices[*device_num_p]; /* XXX the string is allocated but NEVER deallocated, the good time to do that is when the module is unloaded, now we skip the problem */ p->name = ast_strdup(s); /* copy the name */ /* other fields initially NULL */ p->grabber = NULL; p->grabber_data = NULL; p->dev_buf = NULL; p->last_frame = ast_tvnow(); p->status_index = 0; (*device_num_p)++; /* one device added */ return 0; } /* extend ast_cli with video commands. Called by console_video_config */ int console_video_cli(struct video_desc *env, const char *var, int fd) { if (env == NULL) return 1; /* unrecognised */ if (!strcasecmp(var, "videodevice")) { ast_cli(fd, "videodevice is [%s]\n", env->out.devices[env->out.device_primary].name); } else if (!strcasecmp(var, "videocodec")) { ast_cli(fd, "videocodec is [%s]\n", env->codec_name); } else if (!strcasecmp(var, "sendvideo")) { ast_cli(fd, "sendvideo is [%s]\n", env->out.sendvideo ? "on" : "off"); } else if (!strcasecmp(var, "video_size")) { int in_w = 0, in_h = 0; if (env->in) { in_w = env->in->dec_out.w; in_h = env->in->dec_out.h; } ast_cli(fd, "sizes: video %dx%d camera %dx%d local %dx%d remote %dx%d in %dx%d\n", env->enc_in.w, env->enc_in.h, env->out.loc_src_geometry.w, env->out.loc_src_geometry.h, env->loc_dpy.w, env->loc_dpy.h, env->rem_dpy.w, env->rem_dpy.h, in_w, in_h); } else if (!strcasecmp(var, "bitrate")) { ast_cli(fd, "bitrate is [%d]\n", env->out.bitrate); } else if (!strcasecmp(var, "qmin")) { ast_cli(fd, "qmin is [%d]\n", env->out.qmin); } else if (!strcasecmp(var, "fps")) { ast_cli(fd, "fps is [%d]\n", env->out.fps); } else if (!strcasecmp(var, "startgui")) { env->stayopen = 1; console_video_start(env, NULL); } else if (!strcasecmp(var, "stopgui") && env->stayopen != 0) { env->stayopen = 0; if (env->gui && env->owner) ast_cli_command(-1, "console hangup"); else /* not in a call */ console_video_uninit(env); } else { return 1; /* unrecognised */ } return 0; /* recognised */ } /*! parse config command for video support. */ int console_video_config(struct video_desc **penv, const char *var, const char *val) { struct video_desc *env; if (penv == NULL) { ast_log(LOG_WARNING, "bad argument penv=NULL\n"); return 1; /* error */ } /* allocate the video descriptor first time we get here */ env = *penv; if (env == NULL) { env = *penv = ast_calloc(1, sizeof(struct video_desc)); if (env == NULL) { ast_log(LOG_WARNING, "fail to allocate video_desc\n"); return 1; /* error */ } /* set default values - 0's are already there */ env->out.device_primary = 0; env->out.device_secondary = 0; env->out.fps = 5; env->out.bitrate = 65000; env->out.sendvideo = 1; env->out.qmin = 3; env->out.device_num = 0; } CV_START(var, val); CV_F("videodevice", device_table_fill(env->out.devices, &env->out.device_num, val)); CV_BOOL("sendvideo", env->out.sendvideo); CV_F("video_size", video_geom(&env->enc_in, val)); CV_F("camera_size", video_geom(&env->out.loc_src_geometry, val)); CV_F("local_size", video_geom(&env->loc_dpy, val)); CV_F("remote_size", video_geom(&env->rem_dpy, val)); CV_STR("keypad", env->keypad_file); CV_F("region", keypad_cfg_read(env->gui, val)); CV_UINT("startgui", env->stayopen); /* enable gui at startup */ CV_STR("keypad_font", env->keypad_font); CV_STR("sdl_videodriver", env->sdl_videodriver); CV_UINT("fps", env->out.fps); CV_UINT("bitrate", env->out.bitrate); CV_UINT("qmin", env->out.qmin); CV_STR("videocodec", env->codec_name); return 1; /* nothing found */ CV_END; /* the 'nothing found' case */ return 0; /* found something */ } #endif /* video support */ asterisk-13.1.0/channels/sig_pri.h0000644000000000000000000007415512355353654015546 0ustar rootroot#ifndef _SIG_PRI_H #define _SIG_PRI_H /* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2009, 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. */ /*! \file * * \brief Interface header for PRI signaling module * * \author Matthew Fredrickson */ #include "asterisk/channel.h" #include "asterisk/frame.h" #include "asterisk/ccss.h" #include #include #if defined(HAVE_PRI_CCSS) /*! PRI debug message flags when normal PRI debugging is turned on at the command line. */ #define SIG_PRI_DEBUG_NORMAL \ (PRI_DEBUG_APDU | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE | PRI_DEBUG_CC) #else /*! PRI debug message flags when normal PRI debugging is turned on at the command line. */ #define SIG_PRI_DEBUG_NORMAL \ (PRI_DEBUG_APDU | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE) #endif /* !defined(HAVE_PRI_CCSS) */ #if 0 /*! PRI debug message flags set on initial startup. */ #define SIG_PRI_DEBUG_DEFAULT (SIG_PRI_DEBUG_NORMAL | PRI_DEBUG_Q931_DUMP) #else /*! PRI debug message flags set on initial startup. */ #define SIG_PRI_DEBUG_DEFAULT 0 #endif #define SIG_PRI_AOC_GRANT_S (1 << 0) #define SIG_PRI_AOC_GRANT_D (1 << 1) #define SIG_PRI_AOC_GRANT_E (1 << 2) enum sig_pri_tone { SIG_PRI_TONE_RINGTONE = 0, SIG_PRI_TONE_STUTTER, SIG_PRI_TONE_CONGESTION, SIG_PRI_TONE_DIALTONE, SIG_PRI_TONE_DIALRECALL, SIG_PRI_TONE_INFO, SIG_PRI_TONE_BUSY, }; enum sig_pri_law { SIG_PRI_DEFLAW = 0, SIG_PRI_ULAW, SIG_PRI_ALAW }; enum sig_pri_moh_signaling { /*! Generate MOH to the remote party. */ SIG_PRI_MOH_SIGNALING_MOH, /*! Send hold notification signaling to the remote party. */ SIG_PRI_MOH_SIGNALING_NOTIFY, #if defined(HAVE_PRI_CALL_HOLD) /*! Use HOLD/RETRIEVE signaling to release the B channel while on hold. */ SIG_PRI_MOH_SIGNALING_HOLD, #endif /* defined(HAVE_PRI_CALL_HOLD) */ }; enum sig_pri_moh_state { /*! Bridged peer has not put us on hold. */ SIG_PRI_MOH_STATE_IDLE, /*! Bridged peer has put us on hold and we were to notify the remote party. */ SIG_PRI_MOH_STATE_NOTIFY, /*! Bridged peer has put us on hold and we were to play MOH or HOLD/RETRIEVE fallback. */ SIG_PRI_MOH_STATE_MOH, #if defined(HAVE_PRI_CALL_HOLD) /*! Requesting to put channel on hold. */ SIG_PRI_MOH_STATE_HOLD_REQ, /*! Trying to go on hold when bridged peer requested to unhold. */ SIG_PRI_MOH_STATE_PEND_UNHOLD, /*! Channel is held. */ SIG_PRI_MOH_STATE_HOLD, /*! Requesting to take channel out of hold. */ SIG_PRI_MOH_STATE_RETRIEVE_REQ, /*! Trying to take channel out of hold when bridged peer requested to hold. */ SIG_PRI_MOH_STATE_PEND_HOLD, /*! Failed to take the channel out of hold. No B channels were available? */ SIG_PRI_MOH_STATE_RETRIEVE_FAIL, #endif /* defined(HAVE_PRI_CALL_HOLD) */ /*! Number of MOH states. Must be last in enum. */ SIG_PRI_MOH_STATE_NUM }; enum sig_pri_moh_event { /*! Reset the MOH state machine. (Because of hangup.) */ SIG_PRI_MOH_EVENT_RESET, /*! Bridged peer placed this channel on hold. */ SIG_PRI_MOH_EVENT_HOLD, /*! Bridged peer took this channel off hold. */ SIG_PRI_MOH_EVENT_UNHOLD, #if defined(HAVE_PRI_CALL_HOLD) /*! The hold request was successfully acknowledged. */ SIG_PRI_MOH_EVENT_HOLD_ACK, /*! The hold request was rejected. */ SIG_PRI_MOH_EVENT_HOLD_REJ, /*! The unhold request was successfully acknowledged. */ SIG_PRI_MOH_EVENT_RETRIEVE_ACK, /*! The unhold request was rejected. */ SIG_PRI_MOH_EVENT_RETRIEVE_REJ, /*! The remote party took this channel off hold. */ SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK, #endif /* defined(HAVE_PRI_CALL_HOLD) */ /*! Number of MOH events. Must be last in enum. */ SIG_PRI_MOH_EVENT_NUM }; /*! Call establishment life cycle level for simple comparisons. */ enum sig_pri_call_level { /*! Call does not exist. */ SIG_PRI_CALL_LEVEL_IDLE, /*! Call is present but has no response yet. (SETUP) */ SIG_PRI_CALL_LEVEL_SETUP, /*! Call is collecting digits for overlap dialing. (SETUP ACKNOWLEDGE) */ SIG_PRI_CALL_LEVEL_OVERLAP, /*! Call routing is happening. (PROCEEDING) */ SIG_PRI_CALL_LEVEL_PROCEEDING, /*! Called party is being alerted of the call. (ALERTING) */ SIG_PRI_CALL_LEVEL_ALERTING, /*! Call is dialing 'w' deferred digits. (CONNECT) */ SIG_PRI_CALL_LEVEL_DEFER_DIAL, /*! Call is connected/answered. (CONNECT) */ SIG_PRI_CALL_LEVEL_CONNECT, }; enum sig_pri_reset_state { /*! \brief The channel is not being RESTARTed. */ SIG_PRI_RESET_IDLE, /*! * \brief The channel is being RESTARTed. * \note Waiting for a RESTART ACKNOWLEDGE from the peer. */ SIG_PRI_RESET_ACTIVE, /*! * \brief Peer may not be sending the expected RESTART ACKNOWLEDGE. * * \details We have already received a SETUP on this channel. * If another SETUP comes in on this channel then the peer * considers this channel useable. Assume that the peer is * never going to give us a RESTART ACKNOWLEDGE and assume that * we have received one. This is not according to Q.931, but * some peers occasionally fail to send a RESTART ACKNOWLEDGE. */ SIG_PRI_RESET_NO_ACK, }; struct sig_pri_span; struct xfer_rsp_data; struct sig_pri_callback { /* Unlock the private in the signalling private structure. This is used for three way calling madness. */ void (* const unlock_private)(void *pvt); /* Lock the private in the signalling private structure. ... */ void (* const lock_private)(void *pvt); /* Do deadlock avoidance for the private signaling structure lock. */ void (* const deadlock_avoidance_private)(void *pvt); /* Function which is called back to handle any other DTMF events that are received. Called by analog_handle_event. Why is this * important to use, instead of just directly using events received before they are passed into the library? Because sometimes, * (CWCID) the library absorbs DTMF events received. */ //void (* const handle_dtmf)(void *pvt, struct ast_channel *ast, enum analog_sub analog_index, struct ast_frame **dest); //int (* const dial_digits)(void *pvt, enum analog_sub sub, struct analog_dialoperation *dop); int (* const play_tone)(void *pvt, enum sig_pri_tone tone); int (* const set_echocanceller)(void *pvt, int enable); int (* const train_echocanceller)(void *pvt); int (* const dsp_reset_and_flush_digits)(void *pvt); struct ast_channel * (* const new_ast_channel)(void *pvt, int state, enum sig_pri_law law, char *exten, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor); void (* const fixup_chans)(void *old_chan, void *new_chan); /* Note: Called with PRI lock held */ void (* const handle_dchan_exception)(struct sig_pri_span *pri, int index); void (* const set_alarm)(void *pvt, int in_alarm); void (* const set_dialing)(void *pvt, int is_dialing); void (* const set_digital)(void *pvt, int is_digital); void (* const set_outgoing)(void *pvt, int is_outgoing); void (* const set_callerid)(void *pvt, const struct ast_party_caller *caller); void (* const set_dnid)(void *pvt, const char *dnid); void (* const set_rdnis)(void *pvt, const char *rdnis); void (* const queue_control)(void *pvt, int subclass); int (* const new_nobch_intf)(struct sig_pri_span *pri); void (* const init_config)(void *pvt, struct sig_pri_span *pri); const char *(* const get_orig_dialstring)(void *pvt); void (* const make_cc_dialstring)(void *pvt, char *buf, size_t buf_size); void (* const update_span_devstate)(struct sig_pri_span *pri); void (* const dial_digits)(void *pvt, const char *dial_string); void (* const open_media)(void *pvt); /*! * \brief Post an AMI B channel association event. * * \param pvt Private structure of the user of this module. * \param chan Channel associated with the private pointer * * \return Nothing */ void (* const ami_channel_event)(void *pvt, struct ast_channel *chan); /*! Reference the parent module. */ void (*module_ref)(void); /*! Unreference the parent module. */ void (*module_unref)(void); /*! Mark the span for destruction. */ void (*destroy_later)(struct sig_pri_span *pri); }; /*! Global sig_pri callbacks to the upper layer. */ extern struct sig_pri_callback sig_pri_callbacks; #define SIG_PRI_NUM_DCHANS 4 /*!< No more than 4 d-channels */ #define SIG_PRI_MAX_CHANNELS 672 /*!< No more than a DS3 per trunk group */ #define SIG_PRI DAHDI_SIG_CLEAR #define SIG_BRI (0x2000000 | DAHDI_SIG_CLEAR) #define SIG_BRI_PTMP (0X4000000 | DAHDI_SIG_CLEAR) /* QSIG channel mapping option types */ #define DAHDI_CHAN_MAPPING_PHYSICAL 0 #define DAHDI_CHAN_MAPPING_LOGICAL 1 /* Overlap dialing option types */ #define DAHDI_OVERLAPDIAL_NONE 0 #define DAHDI_OVERLAPDIAL_OUTGOING 1 #define DAHDI_OVERLAPDIAL_INCOMING 2 #define DAHDI_OVERLAPDIAL_BOTH (DAHDI_OVERLAPDIAL_INCOMING|DAHDI_OVERLAPDIAL_OUTGOING) #if defined(HAVE_PRI_SERVICE_MESSAGES) /*! \brief Persistent Service State */ #define SRVST_DBKEY "service-state" /*! \brief The out-of-service SERVICE state */ #define SRVST_TYPE_OOS "O" /*! \brief SRVST_INITIALIZED is used to indicate a channel being out-of-service * The SRVST_INITIALIZED is mostly used maintain backwards compatibility but also may * mean that the channel has not yet received a RESTART message. If a channel is * out-of-service with this reason a RESTART message will result in the channel * being put into service. */ #define SRVST_INITIALIZED 0 /*! \brief SRVST_NEAREND is used to indicate that the near end was put out-of-service */ #define SRVST_NEAREND (1 << 0) /*! \brief SRVST_FAREND is used to indicate that the far end was taken out-of-service */ #define SRVST_FAREND (1 << 1) /*! \brief SRVST_BOTH is used to indicate that both sides of the channel are out-of-service */ #define SRVST_BOTH (SRVST_NEAREND | SRVST_FAREND) /*! \brief The AstDB family */ static const char dahdi_db[] = "dahdi/registry"; #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ struct sig_pri_chan { /* Options to be set by user */ unsigned int hidecallerid:1; unsigned int hidecalleridname:1; /*!< Hide just the name not the number for legacy PBX use */ unsigned int immediate:1; /*!< Answer before getting digits? */ unsigned int priexclusive:1; /*!< Whether or not to override and use exculsive mode for channel selection */ unsigned int priindication_oob:1; unsigned int use_callerid:1; /*!< Whether or not to use caller id on this channel */ unsigned int use_callingpres:1; /*!< Whether to use the callingpres the calling switch sends */ char context[AST_MAX_CONTEXT]; char mohinterpret[MAX_MUSICCLASS]; int stripmsd; int channel; /*!< Channel Number or CRV */ /* Options to be checked by user */ int cid_ani2; /*!< Automatic Number Identification number (Alternate PRI caller ID number) */ int cid_ton; /*!< Type Of Number (TON) */ int callingpres; /*!< The value of calling presentation that we're going to use when placing a PRI call */ char cid_num[AST_MAX_EXTENSION]; char cid_subaddr[AST_MAX_EXTENSION]; char cid_name[AST_MAX_EXTENSION]; char cid_ani[AST_MAX_EXTENSION]; /*! \brief User tag for party id's sent from this device driver. */ char user_tag[AST_MAX_EXTENSION]; char exten[AST_MAX_EXTENSION]; /* Internal variables -- Don't touch */ /* Probably will need DS0 number, DS1 number, and a few other things */ char dialdest[256]; /* Queued up digits for overlap dialing. They will be sent out as information messages when setup ACK is received */ #if defined(HAVE_PRI_SETUP_KEYPAD) /*! \brief Keypad digits that came in with the SETUP message. */ char keypad_digits[AST_MAX_EXTENSION]; #endif /* defined(HAVE_PRI_SETUP_KEYPAD) */ /*! 'w' deferred dialing digits. */ char deferred_digits[AST_MAX_EXTENSION]; /*! Music class suggested with AST_CONTROL_HOLD. */ char moh_suggested[MAX_MUSICCLASS]; enum sig_pri_moh_state moh_state; #if defined(HAVE_PRI_AOC_EVENTS) struct pri_subcmd_aoc_e aoc_e; int aoc_s_request_invoke_id; /*!< If an AOC-S request was present for the call, this is the invoke_id to use for the response */ unsigned int aoc_s_request_invoke_id_valid:1; /*!< This is set when the AOC-S invoke id is present */ unsigned int waiting_for_aoce:1; /*!< Delaying hangup for AOC-E msg. If this is set and AOC-E is received, continue with hangup before timeout period. */ unsigned int holding_aoce:1; /*!< received AOC-E msg from asterisk. holding for disconnect/release */ #endif /* defined(HAVE_PRI_AOC_EVENTS) */ unsigned int inalarm:1; unsigned int alreadyhungup:1; /*!< TRUE if the call has already gone/hungup */ unsigned int isidlecall:1; /*!< TRUE if this is an idle call */ unsigned int progress:1; /*!< TRUE if the call has seen inband-information progress through the network */ /*! * \brief TRUE when this channel is allocated. * * \details * Needed to hold an outgoing channel allocation before the * owner pointer is created. * * \note This is one of several items to check to see if a * channel is available for use. */ unsigned int allocated:1; unsigned int outgoing:1; unsigned int digital:1; /*! \brief TRUE if this interface has no B channel. (call hold and call waiting) */ unsigned int no_b_channel:1; #if defined(HAVE_PRI_CALL_WAITING) /*! \brief TRUE if this is a call waiting call */ unsigned int is_call_waiting:1; #endif /* defined(HAVE_PRI_CALL_WAITING) */ #if defined(HAVE_PRI_SETUP_ACK_INBAND) /*! TRUE if outgoing SETUP had no called digits */ unsigned int no_dialed_digits:1; #endif /* defined(HAVE_PRI_SETUP_ACK_INBAND) */ struct ast_channel *owner; struct sig_pri_span *pri; q931_call *call; /*!< opaque libpri call control structure */ /*! Call establishment life cycle level for simple comparisons. */ enum sig_pri_call_level call_level; /*! \brief Channel reset/restart state. */ enum sig_pri_reset_state resetting; #if defined(HAVE_PRI_TRANSFER) /*! If non-NULL, send transfer disconnect successfull response to first call disconnecting. */ struct xfer_rsp_data *xfer_data; #endif /* defined(HAVE_PRI_TRANSFER) */ int prioffset; /*!< channel number in span */ int logicalspan; /*!< logical span number within trunk group */ int mastertrunkgroup; /*!< what trunk group is our master */ #if defined(HAVE_PRI_SERVICE_MESSAGES) /*! \brief Active SRVST_DBKEY out-of-service status value. */ unsigned service_status; #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ void *chan_pvt; /*!< Private structure of the user of this module. */ #if defined(HAVE_PRI_REVERSE_CHARGE) /*! * \brief Reverse charging indication * \details * -1 - No reverse charging, * 1 - Reverse charging, * 0,2-7 - Reserved for future use */ int reverse_charging_indication; #endif }; #if defined(HAVE_PRI_MWI) /*! Maximum number of mailboxes per span. */ #define SIG_PRI_MAX_MWI_MAILBOXES 8 /*! Typical maximum length of mwi voicemail controlling number */ #define SIG_PRI_MAX_MWI_VM_NUMBER_LEN 10 /* digits in number */ /*! Typical maximum length of mwi mailbox number */ #define SIG_PRI_MAX_MWI_MBOX_NUMBER_LEN 10 /* digits in number */ /*! Typical maximum length of mwi mailbox context */ #define SIG_PRI_MAX_MWI_CONTEXT_LEN 10 /*! * \brief Maximum mwi_vm_numbers and mwi_vm_boxes string length. * \details * max_length = #mailboxes * (vm_number + ',') * The last ',' is a null terminator instead. */ #define SIG_PRI_MAX_MWI_VM_NUMBER_STR (SIG_PRI_MAX_MWI_MAILBOXES \ * (SIG_PRI_MAX_MWI_VM_NUMBER_LEN + 1)) /*! * \brief Maximum length of vm_mailbox string. * \details * max_length = vm_box + '@' + context. */ #define SIG_PRI_MAX_MWI_VM_MAILBOX (SIG_PRI_MAX_MWI_MBOX_NUMBER_LEN \ + 1 + SIG_PRI_MAX_MWI_CONTEXT_LEN) /*! * \brief Maximum mwi_mailboxs string length. * \details * max_length = #mailboxes * (vm_mailbox + ',') * The last ',' is a null terminator instead. */ #define SIG_PRI_MAX_MWI_MAILBOX_STR (SIG_PRI_MAX_MWI_MAILBOXES \ * (SIG_PRI_MAX_MWI_VM_MAILBOX + 1)) struct sig_pri_mbox { /*! * \brief MWI mailbox event subscription. * \note NULL if mailbox not configured. */ struct stasis_subscription *sub; /*! \brief Mailbox uniqueid. */ const char *uniqueid; /*! \brief Mailbox number sent to span. */ const char *vm_box; /*! \brief Voicemail access controlling number sent to span. */ const char *vm_number; }; #endif /* defined(HAVE_PRI_MWI) */ enum sig_pri_colp_signaling { /*! Block all connected line updates. */ SIG_PRI_COLP_BLOCK, /*! Only send connected line information with the CONNECT message. */ SIG_PRI_COLP_CONNECT, /*! Allow all connected line updates. */ SIG_PRI_COLP_UPDATE, }; struct sig_pri_span { /* Should be set by user */ struct ast_cc_config_params *cc_params; /*!< CC config parameters for each new call. */ int pritimers[PRI_MAX_TIMERS]; int overlapdial; /*!< In overlap dialing mode */ int qsigchannelmapping; /*!< QSIG channel mapping type */ int discardremoteholdretrieval; /*!< shall remote hold or remote retrieval notifications be discarded? */ int facilityenable; /*!< Enable facility IEs */ #if defined(HAVE_PRI_L2_PERSISTENCE) /*! Layer 2 persistence option. */ int l2_persistence; #endif /* defined(HAVE_PRI_L2_PERSISTENCE) */ int dchan_logical_span[SIG_PRI_NUM_DCHANS]; /*!< Logical offset the DCHAN sits in */ int fds[SIG_PRI_NUM_DCHANS]; /*!< FD's for d-channels */ #if defined(HAVE_PRI_AOC_EVENTS) int aoc_passthrough_flag; /*!< Represents what AOC messages (S,D,E) are allowed to pass-through */ unsigned int aoce_delayhangup:1; /*!< defines whether the aoce_delayhangup option is enabled or not */ #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_SERVICE_MESSAGES) unsigned int enable_service_message_support:1; /*!< enable SERVICE message support */ #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ #ifdef HAVE_PRI_INBANDDISCONNECT unsigned int inbanddisconnect:1; /*!< Should we support inband audio after receiving DISCONNECT? */ #endif #if defined(HAVE_PRI_CALL_HOLD) /*! \brief TRUE if held calls are transferred on disconnect. */ unsigned int hold_disconnect_transfer:1; #endif /* defined(HAVE_PRI_CALL_HOLD) */ /*! * \brief TRUE if call transfer is enabled for the span. * \note Support switch-side transfer (called 2BCT, RLT or other names) */ unsigned int transfer:1; #if defined(HAVE_PRI_CALL_WAITING) /*! \brief TRUE if we will allow incoming ISDN call waiting calls. */ unsigned int allow_call_waiting_calls:1; #endif /* defined(HAVE_PRI_CALL_WAITING) */ /*! TRUE if layer 1 alarm status is ignored */ unsigned int layer1_ignored:1; /*! * TRUE if a new call's sig_pri_chan.user_tag[] has the MSN * appended to the initial_user_tag[]. */ unsigned int append_msn_to_user_tag:1; /*! TRUE if a SETUP ACK message needs to open the audio path. */ unsigned int inband_on_setup_ack:1; /*! TRUE if a PROCEEDING message needs to unsquelch the received audio. */ unsigned int inband_on_proceeding:1; #if defined(HAVE_PRI_MCID) /*! \brief TRUE if allow sending MCID request on this span. */ unsigned int mcid_send:1; #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_DATETIME_SEND) /*! \brief Configured date/time ie send policy option. */ int datetime_send; #endif /* defined(HAVE_PRI_DATETIME_SEND) */ int dialplan; /*!< Dialing plan */ int localdialplan; /*!< Local dialing plan */ int cpndialplan; /*!< Connected party dialing plan */ char internationalprefix[10]; /*!< country access code ('00' for european dialplans) */ char nationalprefix[10]; /*!< area access code ('0' for european dialplans) */ char localprefix[20]; /*!< area access code + area code ('0'+area code for european dialplans) */ char privateprefix[20]; /*!< for private dialplans */ char unknownprefix[20]; /*!< for unknown dialplans */ enum sig_pri_moh_signaling moh_signaling; /*! Send connected line signaling to peer option. */ enum sig_pri_colp_signaling colp_send; long resetinterval; /*!< Interval (in seconds) for resetting unused channels */ #if defined(HAVE_PRI_DISPLAY_TEXT) unsigned long display_flags_send; /*!< PRI_DISPLAY_OPTION_xxx flags for display text sending */ unsigned long display_flags_receive; /*!< PRI_DISPLAY_OPTION_xxx flags for display text receiving */ #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #if defined(HAVE_PRI_MWI) /*! \brief Active MWI mailboxes */ struct sig_pri_mbox mbox[SIG_PRI_MAX_MWI_MAILBOXES]; /*! * \brief Comma separated list of mailboxes to indicate MWI. * \note Empty if disabled. * \note Format: vm_mailbox{,vm_mailbox} * \note String is split apart when span is started. */ char mwi_mailboxes[SIG_PRI_MAX_MWI_MAILBOX_STR]; /*! * \brief Comma separated list of mailbox numbers sent over ISDN span for MWI. * \note Empty if disabled. * \note Format: vm_box{,vm_box} * \note String is split apart when span is started. */ char mwi_vm_boxes[SIG_PRI_MAX_MWI_VM_NUMBER_STR]; /*! * \brief Comma separated list of voicemail access controlling numbers for MWI. * \note Format: vm_number{,vm_number} * \note String is split apart when span is started. */ char mwi_vm_numbers[SIG_PRI_MAX_MWI_VM_NUMBER_STR]; #endif /* defined(HAVE_PRI_MWI) */ /*! * \brief Initial user tag for party id's sent from this device driver. * \note String set by config file. */ char initial_user_tag[AST_MAX_EXTENSION]; char msn_list[AST_MAX_EXTENSION]; /*!< Comma separated list of MSNs to handle. Empty if disabled. */ char idleext[AST_MAX_EXTENSION]; /*!< Where to idle extra calls */ char idlecontext[AST_MAX_CONTEXT]; /*!< What context to use for idle */ char idledial[AST_MAX_EXTENSION]; /*!< What to dial before dumping */ int minunused; /*!< Min # of channels to keep empty */ int minidle; /*!< Min # of "idling" calls to keep active */ int nodetype; /*!< Node type */ int switchtype; /*!< Type of switch to emulate */ int nsf; /*!< Network-Specific Facilities */ int trunkgroup; /*!< What our trunkgroup is */ #if defined(HAVE_PRI_CCSS) int cc_ptmp_recall_mode; /*!< CC PTMP recall mode. globalRecall(0), specificRecall(1) */ int cc_qsig_signaling_link_req; /*!< CC Q.SIG signaling link retention (Party A) release(0), retain(1), do-not-care(2) */ int cc_qsig_signaling_link_rsp; /*!< CC Q.SIG signaling link retention (Party B) release(0), retain(1) */ #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CALL_WAITING) /*! * \brief Number of extra outgoing calls to allow on a span before * considering that span congested. */ int max_call_waiting_calls; struct { int stripmsd; unsigned int hidecallerid:1; unsigned int hidecalleridname:1; /*!< Hide just the name not the number for legacy PBX use */ unsigned int immediate:1; /*!< Answer before getting digits? */ unsigned int priexclusive:1; /*!< Whether or not to override and use exculsive mode for channel selection */ unsigned int priindication_oob:1; unsigned int use_callerid:1; /*!< Whether or not to use caller id on this channel */ unsigned int use_callingpres:1; /*!< Whether to use the callingpres the calling switch sends */ char context[AST_MAX_CONTEXT]; char mohinterpret[MAX_MUSICCLASS]; } ch_cfg; /*! * \brief Number of outstanding call waiting calls. * \note Must be zero to allow new calls from asterisk to * immediately allocate a B channel. */ int num_call_waiting_calls; #endif /* defined(HAVE_PRI_CALL_WAITING) */ int dchanavail[SIG_PRI_NUM_DCHANS]; /*!< Whether each channel is available */ int debug; /*!< set to true if to dump PRI event info */ int span; /*!< span number put into user output messages */ int resetting; /*!< true if span is being reset/restarted */ int resetpos; /*!< current position during a reset (-1 if not started) */ int sig; /*!< ISDN signalling type (SIG_PRI, SIG_BRI, SIG_BRI_PTMP, etc...) */ int new_chan_seq; /*!< New struct ast_channel sequence number */ /*! TRUE if we have already whined about no D channels available. */ unsigned int no_d_channels:1; /* Everything after here is internally set */ struct pri *dchans[SIG_PRI_NUM_DCHANS]; /*!< Actual d-channels */ struct pri *pri; /*!< Currently active D-channel */ /*! * List of private structures of the user of this module for no B channel * interfaces. (hold and call waiting interfaces) */ void *no_b_chan_iflist; /*! * List of private structures of the user of this module for no B channel * interfaces. (hold and call waiting interfaces) */ void *no_b_chan_end; int numchans; /*!< Num of channels we represent */ struct sig_pri_chan *pvts[SIG_PRI_MAX_CHANNELS];/*!< Member channel pvt structs */ pthread_t master; /*!< Thread of master */ ast_mutex_t lock; /*!< libpri access Mutex */ time_t lastreset; /*!< time when unused channels were last reset */ /*! * \brief Congestion device state of the span. * \details * AST_DEVICE_NOT_INUSE - Span does not have all B channels in use. * AST_DEVICE_BUSY - All B channels are in use. * AST_DEVICE_UNAVAILABLE - Span is in alarm. * \note * Device name: \verbatim DAHDI/I/congestion. \endverbatim */ int congestion_devstate; #if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) /*! \todo An ISDN span threshold device state could be useful in determining how often a span utilization goes over a configurable threshold. */ /*! * \brief User threshold device state of the span. * \details * AST_DEVICE_NOT_INUSE - There are no B channels in use. * AST_DEVICE_INUSE - The number of B channels in use is less than * the configured threshold but not zero. * AST_DEVICE_BUSY - The number of B channels in use meets or exceeds * the configured threshold. * AST_DEVICE_UNAVAILABLE - Span is in alarm. * \note * Device name: DAHDI/I/threshold */ int threshold_devstate; /*! * \brief Number of B channels in use to consider the span in a busy state. * \note Setting the threshold to zero is interpreted as all B channels. */ int user_busy_threshold; #endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ }; void sig_pri_extract_called_num_subaddr(struct sig_pri_chan *p, const char *rdest, char *called, size_t called_buff_size); int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, const char *rdest, int timeout, int layer1); int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast); int sig_pri_indicate(struct sig_pri_chan *p, struct ast_channel *chan, int condition, const void *data, size_t datalen); int sig_pri_answer(struct sig_pri_chan *p, struct ast_channel *ast); int sig_pri_is_chan_available(struct sig_pri_chan *pvt); int sig_pri_available(struct sig_pri_chan **pvt, int is_specific_channel); void sig_pri_init_pri(struct sig_pri_span *pri); /* If return 0, it means this function was able to handle it (pre setup digits). If non zero, the user of this * functions should handle it normally (generate inband DTMF) */ int sig_pri_digit_begin(struct sig_pri_chan *pvt, struct ast_channel *ast, char digit); void sig_pri_dial_complete(struct sig_pri_chan *pvt, struct ast_channel *ast); void sig_pri_stop_pri(struct sig_pri_span *pri); int sig_pri_start_pri(struct sig_pri_span *pri); void sig_pri_set_alarm(struct sig_pri_chan *p, int in_alarm); void sig_pri_chan_alarm_notify(struct sig_pri_chan *p, int noalarm); int sig_pri_is_alarm_ignored(struct sig_pri_span *pri); void pri_event_alarm(struct sig_pri_span *pri, int index, int before_start_pri); void pri_event_noalarm(struct sig_pri_span *pri, int index, int before_start_pri); struct ast_channel *sig_pri_request(struct sig_pri_chan *p, enum sig_pri_law law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int transfercapability); struct sig_pri_chan *sig_pri_chan_new(void *pvt_data, struct sig_pri_span *pri, int logicalspan, int channo, int trunkgroup); void sig_pri_chan_delete(struct sig_pri_chan *doomed); int pri_is_up(struct sig_pri_span *pri); struct mansession; int sig_pri_ami_show_spans(struct mansession *s, const char *show_cmd, struct sig_pri_span *pri, const int *dchannels, const char *action_id); void sig_pri_cli_show_channels_header(int fd); void sig_pri_cli_show_channels(int fd, struct sig_pri_span *pri); void sig_pri_cli_show_spans(int fd, int span, struct sig_pri_span *pri); void sig_pri_cli_show_span(int fd, int *dchannels, struct sig_pri_span *pri); int pri_send_keypad_facility_exec(struct sig_pri_chan *p, const char *digits); int pri_send_callrerouting_facility_exec(struct sig_pri_chan *p, enum ast_channel_state chanstate, const char *destination, const char *original, const char *reason); #if defined(HAVE_PRI_SERVICE_MESSAGES) int pri_maintenance_bservice(struct pri *pri, struct sig_pri_chan *p, int changestatus); #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_pri_chan *pchan); #if defined(HAVE_PRI_DISPLAY_TEXT) void sig_pri_sendtext(struct sig_pri_chan *pchan, const char *text); #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ int sig_pri_cc_agent_init(struct ast_cc_agent *agent, struct sig_pri_chan *pvt_chan); int sig_pri_cc_agent_start_offer_timer(struct ast_cc_agent *agent); int sig_pri_cc_agent_stop_offer_timer(struct ast_cc_agent *agent); void sig_pri_cc_agent_req_rsp(struct ast_cc_agent *agent, enum ast_cc_agent_response_reason reason); int sig_pri_cc_agent_status_req(struct ast_cc_agent *agent); int sig_pri_cc_agent_stop_ringing(struct ast_cc_agent *agent); int sig_pri_cc_agent_party_b_free(struct ast_cc_agent *agent); int sig_pri_cc_agent_start_monitoring(struct ast_cc_agent *agent); int sig_pri_cc_agent_callee_available(struct ast_cc_agent *agent); void sig_pri_cc_agent_destructor(struct ast_cc_agent *agent); int sig_pri_cc_monitor_req_cc(struct ast_cc_monitor *monitor, int *available_timer_id); int sig_pri_cc_monitor_suspend(struct ast_cc_monitor *monitor); int sig_pri_cc_monitor_unsuspend(struct ast_cc_monitor *monitor); int sig_pri_cc_monitor_status_rsp(struct ast_cc_monitor *monitor, enum ast_device_state devstate); int sig_pri_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id); void sig_pri_cc_monitor_destructor(void *monitor_pvt); int sig_pri_load(const char *cc_type_name); void sig_pri_unload(void); #endif /* _SIG_PRI_H */ asterisk-13.1.0/channels/sig_analog.h0000644000000000000000000003540212010546365016174 0ustar rootroot#ifndef _SIG_ANALOG_H #define _SIG_ANALOG_H /* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2009, 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. */ /*! \file * * \brief Interface header for analog signaling module * * \author Matthew Fredrickson */ #include "asterisk/channel.h" #include "asterisk/frame.h" #include "asterisk/smdi.h" #define ANALOG_SMDI_MD_WAIT_TIMEOUT 1500 /* 1.5 seconds */ #define ANALOG_MAX_CID 300 #define READ_SIZE 160 #define RING_PATTERNS 3 /* Signalling types supported */ enum analog_sigtype { ANALOG_SIG_NONE = -1, ANALOG_SIG_FXOLS = 1, ANALOG_SIG_FXOKS, ANALOG_SIG_FXOGS, ANALOG_SIG_FXSLS, ANALOG_SIG_FXSKS, ANALOG_SIG_FXSGS, ANALOG_SIG_EMWINK, ANALOG_SIG_EM, ANALOG_SIG_EM_E1, ANALOG_SIG_FEATD, ANALOG_SIG_FEATDMF, ANALOG_SIG_E911, ANALOG_SIG_FGC_CAMA, ANALOG_SIG_FGC_CAMAMF, ANALOG_SIG_FEATB, ANALOG_SIG_SFWINK, ANALOG_SIG_SF, ANALOG_SIG_SF_FEATD, ANALOG_SIG_SF_FEATDMF, ANALOG_SIG_FEATDMF_TA, ANALOG_SIG_SF_FEATB, }; enum analog_tone { ANALOG_TONE_RINGTONE = 0, ANALOG_TONE_STUTTER, ANALOG_TONE_CONGESTION, ANALOG_TONE_DIALTONE, ANALOG_TONE_DIALRECALL, ANALOG_TONE_INFO, }; enum analog_event { ANALOG_EVENT_NONE = 0, ANALOG_EVENT_ONHOOK, ANALOG_EVENT_RINGOFFHOOK, ANALOG_EVENT_WINKFLASH, ANALOG_EVENT_ALARM, ANALOG_EVENT_NOALARM, ANALOG_EVENT_DIALCOMPLETE, ANALOG_EVENT_RINGERON, ANALOG_EVENT_RINGEROFF, ANALOG_EVENT_HOOKCOMPLETE, ANALOG_EVENT_PULSE_START, ANALOG_EVENT_POLARITY, ANALOG_EVENT_RINGBEGIN, ANALOG_EVENT_EC_DISABLED, ANALOG_EVENT_REMOVED, ANALOG_EVENT_NEONMWI_ACTIVE, ANALOG_EVENT_NEONMWI_INACTIVE, ANALOG_EVENT_TX_CED_DETECTED, ANALOG_EVENT_RX_CED_DETECTED, ANALOG_EVENT_EC_NLP_DISABLED, ANALOG_EVENT_EC_NLP_ENABLED, ANALOG_EVENT_ERROR, /* not a DAHDI event */ ANALOG_EVENT_DTMFCID, /* not a DAHDI event */ ANALOG_EVENT_PULSEDIGIT = (1 << 16), ANALOG_EVENT_DTMFDOWN = (1 << 17), ANALOG_EVENT_DTMFUP = (1 << 18), }; enum analog_sub { ANALOG_SUB_REAL = 0, /*!< Active call */ ANALOG_SUB_CALLWAIT, /*!< Call-Waiting call on hold */ ANALOG_SUB_THREEWAY, /*!< Three-way call */ }; enum analog_dsp_digitmode { ANALOG_DIGITMODE_DTMF = 1, ANALOG_DIGITMODE_MF, }; enum analog_cid_start { ANALOG_CID_START_POLARITY = 1, ANALOG_CID_START_POLARITY_IN, ANALOG_CID_START_RING, ANALOG_CID_START_DTMF_NOALERT, }; enum dialop { ANALOG_DIAL_OP_REPLACE = 2, }; struct analog_dialoperation { enum dialop op; char dialstr[256]; }; struct analog_callback { /* Unlock the private in the signalling private structure. This is used for three way calling madness. */ void (* const unlock_private)(void *pvt); /* Lock the private in the signalling private structure. ... */ void (* const lock_private)(void *pvt); /* Do deadlock avoidance for the private signaling structure lock. */ void (* const deadlock_avoidance_private)(void *pvt); /* Function which is called back to handle any other DTMF events that are received. Called by analog_handle_event. Why is this * important to use, instead of just directly using events received before they are passed into the library? Because sometimes, * (CWCID) the library absorbs DTMF events received. */ void (* const handle_dtmf)(void *pvt, struct ast_channel *ast, enum analog_sub analog_index, struct ast_frame **dest); int (* const get_event)(void *pvt); int (* const wait_event)(void *pvt); int (* const is_off_hook)(void *pvt); int (* const is_dialing)(void *pvt, enum analog_sub sub); /* Start a trunk type signalling protocol (everything except phone ports basically */ int (* const start)(void *pvt); int (* const ring)(void *pvt); int (* const flash)(void *pvt); /*! \brief Set channel on hook */ int (* const on_hook)(void *pvt); /*! \brief Set channel off hook */ int (* const off_hook)(void *pvt); void (* const set_needringing)(void *pvt, int value); /*! \brief Set FXS line polarity to 0=IDLE NZ=REVERSED */ void (* const set_polarity)(void *pvt, int value); /*! \brief Reset FXS line polarity to IDLE, based on answeronpolarityswitch and hanguponpolarityswitch */ void (* const start_polarityswitch)(void *pvt); /*! \brief Switch FXS line polarity, based on answeronpolarityswitch=yes */ void (* const answer_polarityswitch)(void *pvt); /*! \brief Switch FXS line polarity, based on answeronpolarityswitch and hanguponpolarityswitch */ void (* const hangup_polarityswitch)(void *pvt); /* We're assuming that we're going to only wink on ANALOG_SUB_REAL - even though in the code there's an argument to the index * function */ int (* const wink)(void *pvt, enum analog_sub sub); int (* const dial_digits)(void *pvt, enum analog_sub sub, struct analog_dialoperation *dop); int (* const send_fsk)(void *pvt, struct ast_channel *ast, char *fsk); int (* const play_tone)(void *pvt, enum analog_sub sub, enum analog_tone tone); int (* const set_echocanceller)(void *pvt, int enable); int (* const train_echocanceller)(void *pvt); int (* const dsp_set_digitmode)(void *pvt, enum analog_dsp_digitmode mode); int (* const dsp_reset_and_flush_digits)(void *pvt); int (* const send_callerid)(void *pvt, int cwcid, struct ast_party_caller *caller); /* Returns 0 if CID received. Returns 1 if event received, and -1 if error. name and num are size ANALOG_MAX_CID */ int (* const get_callerid)(void *pvt, char *name, char *num, enum analog_event *ev, size_t timeout); /* Start CID detection */ int (* const start_cid_detect)(void *pvt, int cid_signalling); /* Stop CID detection */ int (* const stop_cid_detect)(void *pvt); /* Play the CAS callwait tone on the REAL sub, then repeat after 10 seconds, and then stop */ int (* const callwait)(void *pvt); /* Stop playing any CAS call waiting announcement tones that might be running on the REAL sub */ int (* const stop_callwait)(void *pvt); /* Bearer control related (non signalling) callbacks */ int (* const allocate_sub)(void *pvt, enum analog_sub sub); int (* const unallocate_sub)(void *pvt, enum analog_sub sub); /*! This function is for swapping of the owners with the underlying subs. Typically it means you need to change the fds * of the new owner to be the fds of the sub specified, for each of the two subs given */ void (* const swap_subs)(void *pvt, enum analog_sub a, struct ast_channel *new_a_owner, enum analog_sub b, struct ast_channel *new_b_owner); struct ast_channel * (* const new_ast_channel)(void *pvt, int state, int startpbx, enum analog_sub sub, const struct ast_channel *requestor); /* Add the given sub to a conference */ int (* const conf_add)(void *pvt, enum analog_sub sub); /* Delete the given sub from any conference that might be running on the channels */ int (* const conf_del)(void *pvt, enum analog_sub sub); /* If you would like to do any optimizations after the conference members have been added and removed, * you can do so here */ int (* const complete_conference_update)(void *pvt, int needconf); /* This is called when there are no more subchannels on the given private that are left up, * for any cleanup or whatever else you would like to do. Called from analog_hangup() */ void (* const all_subchannels_hungup)(void *pvt); int (* const has_voicemail)(void *pvt); int (* const check_for_conference)(void *pvt); void (* const handle_notify_message)(struct ast_channel *chan, void *pvt, int cid_flags, int neon_mwievent); /* callbacks for increasing and decreasing ss_thread_count, will handle locking and condition signal */ void (* const increase_ss_count)(void); void (* const decrease_ss_count)(void); int (* const distinctive_ring)(struct ast_channel *chan, void *pvt, int idx, int *ringdata); /* Sets the specified sub-channel in and out of signed linear mode, returns the value that was overwritten */ int (* const set_linear_mode)(void *pvt, enum analog_sub sub, int linear_mode); void (* const set_inthreeway)(void *pvt, enum analog_sub sub, int inthreeway); void (* const get_and_handle_alarms)(void *pvt); void * (* const get_sigpvt_bridged_channel)(struct ast_channel *chan); int (* const get_sub_fd)(void *pvt, enum analog_sub sub); void (* const set_cadence)(void *pvt, int *cidrings, struct ast_channel *chan); void (* const set_alarm)(void *pvt, int in_alarm); void (* const set_dialing)(void *pvt, int is_dialing); void (* const set_outgoing)(void *pvt, int is_outgoing); void (* const set_ringtimeout)(void *pvt, int ringt); void (* const set_waitingfordt)(void *pvt, struct ast_channel *ast); int (* const check_waitingfordt)(void *pvt); void (* const set_confirmanswer)(void *pvt, int flag); int (* const check_confirmanswer)(void *pvt); void (* const set_callwaiting)(void *pvt, int callwaiting_enable); void (* const cancel_cidspill)(void *pvt); int (* const confmute)(void *pvt, int mute); void (* const set_pulsedial)(void *pvt, int flag); void (* const set_new_owner)(void *pvt, struct ast_channel *new_owner); const char *(* const get_orig_dialstring)(void *pvt); int (* const have_progressdetect)(void *pvt); }; /*! Global analog callbacks to the upper layer. */ extern struct analog_callback analog_callbacks; struct analog_subchannel { struct ast_channel *owner; struct ast_frame f; /*!< One frame for each channel. How did this ever work before? */ unsigned int inthreeway:1; /* Have we allocated a subchannel yet or not */ unsigned int allocd:1; }; struct analog_pvt { /* Analog signalling type used in this private */ enum analog_sigtype sig; /* To contain the private structure passed into the channel callbacks */ void *chan_pvt; /* All members after this are giong to be transient, and most will probably change */ struct ast_channel *owner; /*!< Our current active owner (if applicable) */ struct analog_subchannel subs[3]; /*!< Sub-channels */ struct analog_dialoperation dop; int onhooktime; /*< Time the interface went on-hook. */ int fxsoffhookstate; /*< TRUE if the FXS port is off-hook */ /*! \brief -1 = unknown, 0 = no messages, 1 = new messages available */ int msgstate; /* XXX: Option Variables - Set by allocator of private structure */ unsigned int answeronpolarityswitch:1; unsigned int callreturn:1; unsigned int cancallforward:1; unsigned int canpark:1; unsigned int dahditrcallerid:1; /*!< should we use the callerid from incoming call on dahdi transfer or not */ unsigned int hanguponpolarityswitch:1; unsigned int immediate:1; unsigned int permcallwaiting:1; /*!< TRUE if call waiting is enabled. (Configured option) */ unsigned int permhidecallerid:1; /*!< Whether to hide our outgoing caller ID or not */ unsigned int pulse:1; unsigned int threewaycalling:1; unsigned int transfer:1; unsigned int transfertobusy:1; /*!< allow flash-transfers to busy channels */ unsigned int use_callerid:1; /*!< Whether or not to use caller id on this channel */ unsigned int callwaitingcallerid:1; /*!< TRUE if send caller ID for Call Waiting */ /*! * \brief TRUE if SMDI (Simplified Message Desk Interface) is enabled */ unsigned int use_smdi:1; /*! \brief The SMDI interface to get SMDI messages from. */ struct ast_smdi_interface *smdi_iface; /* Not used for anything but log messages. Could be just the TCID */ int channel; /*!< Channel Number */ enum analog_sigtype outsigmod; int echotraining; int cid_signalling; /*!< Asterisk callerid type we're using */ int polarityonanswerdelay; int stripmsd; enum analog_cid_start cid_start; char mohsuggest[MAX_MUSICCLASS]; char cid_num[AST_MAX_EXTENSION]; char cid_name[AST_MAX_EXTENSION]; /* XXX: All variables after this are internal */ unsigned int callwaiting:1; /*!< TRUE if call waiting is enabled. (Active option) */ unsigned int dialednone:1; unsigned int dialing:1; /*!< TRUE if in the process of dialing digits or sending something */ unsigned int dnd:1; /*!< TRUE if Do-Not-Disturb is enabled. */ unsigned int echobreak:1; unsigned int hidecallerid:1; unsigned int outgoing:1; unsigned int inalarm:1; /*! * \brief TRUE if Call Waiting (CW) CPE Alert Signal (CAS) is being sent. * \note * After CAS is sent, the call waiting caller id will be sent if the phone * gives a positive reply. */ unsigned int callwaitcas:1; char callwait_num[AST_MAX_EXTENSION]; char callwait_name[AST_MAX_EXTENSION]; char lastcid_num[AST_MAX_EXTENSION]; char lastcid_name[AST_MAX_EXTENSION]; struct ast_party_caller caller; int cidrings; /*!< Which ring to deliver CID on */ char echorest[20]; int polarity; struct timeval polaritydelaytv; char dialdest[256]; time_t guardtime; /*!< Must wait this much time before using for new call */ struct timeval flashtime; /*!< Last flash-hook time */ int whichwink; /*!< SIG_FEATDMF_TA Which wink are we on? */ char finaldial[64]; char *origcid_num; /*!< malloced original callerid */ char *origcid_name; /*!< malloced original callerid */ char call_forward[AST_MAX_EXTENSION]; /* Ast channel to pass to __ss_analog_thread */ struct ast_channel *ss_astchan; /* All variables after this are definitely going to be audited */ int ringt; int ringt_base; }; struct analog_pvt *analog_new(enum analog_sigtype signallingtype, void *private_data); void analog_delete(struct analog_pvt *doomed); void analog_free(struct analog_pvt *p); int analog_call(struct analog_pvt *p, struct ast_channel *ast, const char *rdest, int timeout); int analog_hangup(struct analog_pvt *p, struct ast_channel *ast); int analog_answer(struct analog_pvt *p, struct ast_channel *ast); struct ast_frame *analog_exception(struct analog_pvt *p, struct ast_channel *ast); struct ast_channel * analog_request(struct analog_pvt *p, int *callwait, const struct ast_channel *requestor); int analog_available(struct analog_pvt *p); void *analog_handle_init_event(struct analog_pvt *i, int event); int analog_config_complete(struct analog_pvt *p); void analog_handle_dtmf(struct analog_pvt *p, struct ast_channel *ast, enum analog_sub index, struct ast_frame **dest); enum analog_cid_start analog_str_to_cidstart(const char *value); const char *analog_cidstart_to_str(enum analog_cid_start cid_start); enum analog_sigtype analog_str_to_sigtype(const char *name); const char *analog_sigtype_to_str(enum analog_sigtype sigtype); unsigned int analog_str_to_cidtype(const char *name); const char *analog_cidtype_to_str(unsigned int cid_type); int analog_ss_thread_start(struct analog_pvt *p, struct ast_channel *ast); int analog_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, void *newp); int analog_dnd(struct analog_pvt *p, int flag); #endif /* _SIG_ANSLOG_H */ asterisk-13.1.0/channels/sig_ss7.c0000644000000000000000000031171312347633447015457 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2010 Digium, Inc. * * Richard Mudgett * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief SS7 signaling module. * * \author Matthew Fredrickson * \author Richard Mudgett * * See Also: * \arg \ref AstCREDITS */ /*** MODULEINFO core ***/ #include "asterisk.h" #if defined(HAVE_SS7) #include #include "asterisk/pbx.h" #include "asterisk/causes.h" #include "asterisk/musiconhold.h" #include "asterisk/cli.h" #include "asterisk/callerid.h" #include "asterisk/transcap.h" #include "asterisk/stasis_channels.h" #include "sig_ss7.h" #if !defined(LIBSS7_ABI_COMPATIBILITY) #error "Upgrade your libss7" #elif LIBSS7_ABI_COMPATIBILITY != 2 #error "Your installed libss7 is not compatible" #endif /* ------------------------------------------------------------------- */ static const char *sig_ss7_call_level2str(enum sig_ss7_call_level level) { switch (level) { case SIG_SS7_CALL_LEVEL_IDLE: return "Idle"; case SIG_SS7_CALL_LEVEL_ALLOCATED: return "Allocated"; case SIG_SS7_CALL_LEVEL_CONTINUITY: return "Continuity"; case SIG_SS7_CALL_LEVEL_SETUP: return "Setup"; case SIG_SS7_CALL_LEVEL_PROCEEDING: return "Proceeding"; case SIG_SS7_CALL_LEVEL_ALERTING: return "Alerting"; case SIG_SS7_CALL_LEVEL_CONNECT: return "Connect"; } return "Unknown"; } static void sig_ss7_unlock_private(struct sig_ss7_chan *p) { if (sig_ss7_callbacks.unlock_private) { sig_ss7_callbacks.unlock_private(p->chan_pvt); } } static void sig_ss7_lock_private(struct sig_ss7_chan *p) { if (sig_ss7_callbacks.lock_private) { sig_ss7_callbacks.lock_private(p->chan_pvt); } } static void sig_ss7_deadlock_avoidance_private(struct sig_ss7_chan *p) { if (sig_ss7_callbacks.deadlock_avoidance_private) { sig_ss7_callbacks.deadlock_avoidance_private(p->chan_pvt); } else { /* Fallback to the old way if callback not present. */ sig_ss7_unlock_private(p); sched_yield(); sig_ss7_lock_private(p); } } void sig_ss7_set_alarm(struct sig_ss7_chan *p, int in_alarm) { p->inalarm = in_alarm; if (sig_ss7_callbacks.set_alarm) { sig_ss7_callbacks.set_alarm(p->chan_pvt, in_alarm); } } static void sig_ss7_set_dialing(struct sig_ss7_chan *p, int is_dialing) { if (sig_ss7_callbacks.set_dialing) { sig_ss7_callbacks.set_dialing(p->chan_pvt, is_dialing); } } static void sig_ss7_set_digital(struct sig_ss7_chan *p, int is_digital) { if (sig_ss7_callbacks.set_digital) { sig_ss7_callbacks.set_digital(p->chan_pvt, is_digital); } } static void sig_ss7_set_outgoing(struct sig_ss7_chan *p, int is_outgoing) { p->outgoing = is_outgoing; if (sig_ss7_callbacks.set_outgoing) { sig_ss7_callbacks.set_outgoing(p->chan_pvt, is_outgoing); } } static void sig_ss7_set_inservice(struct sig_ss7_chan *p, int is_inservice) { p->inservice = is_inservice; if (sig_ss7_callbacks.set_inservice) { sig_ss7_callbacks.set_inservice(p->chan_pvt, is_inservice); } } static void sig_ss7_set_locallyblocked(struct sig_ss7_chan *p, int is_blocked, int type) { if (is_blocked) { p->locallyblocked |= type; } else { p->locallyblocked &= ~type; } if (sig_ss7_callbacks.set_locallyblocked) { sig_ss7_callbacks.set_locallyblocked(p->chan_pvt, p->locallyblocked); } } static void sig_ss7_set_remotelyblocked(struct sig_ss7_chan *p, int is_blocked, int type) { if (is_blocked) { p->remotelyblocked |= type; } else { p->remotelyblocked &= ~type; } if (sig_ss7_callbacks.set_remotelyblocked) { sig_ss7_callbacks.set_remotelyblocked(p->chan_pvt, p->remotelyblocked); } } /*! * \internal * \brief Open the SS7 channel media path. * \since 1.8.12 * * \param p Channel private control structure. * * \return Nothing */ static void sig_ss7_open_media(struct sig_ss7_chan *p) { if (sig_ss7_callbacks.open_media) { sig_ss7_callbacks.open_media(p->chan_pvt); } } /*! * \internal * \brief Set the caller id information in the parent module. * \since 1.8 * * \param p sig_ss7 channel structure. * * \return Nothing */ static void sig_ss7_set_caller_id(struct sig_ss7_chan *p) { struct ast_party_caller caller; if (sig_ss7_callbacks.set_callerid) { ast_party_caller_init(&caller); caller.id.name.str = p->cid_name; caller.id.name.presentation = p->callingpres; caller.id.name.valid = 1; caller.id.number.str = p->cid_num; caller.id.number.plan = p->cid_ton; caller.id.number.presentation = p->callingpres; caller.id.number.valid = 1; if (!ast_strlen_zero(p->cid_subaddr)) { caller.id.subaddress.valid = 1; //caller.id.subaddress.type = 0;/* nsap */ //caller.id.subaddress.odd_even_indicator = 0; caller.id.subaddress.str = p->cid_subaddr; } caller.ani.number.str = p->cid_ani; //caller.ani.number.plan = p->xxx; //caller.ani.number.presentation = p->xxx; caller.ani.number.valid = 1; caller.ani2 = p->cid_ani2; sig_ss7_callbacks.set_callerid(p->chan_pvt, &caller); } } /*! * \internal * \brief Set the Dialed Number Identifier. * \since 1.8 * * \param p sig_ss7 channel structure. * \param dnid Dialed Number Identifier string. * * \return Nothing */ static void sig_ss7_set_dnid(struct sig_ss7_chan *p, const char *dnid) { if (sig_ss7_callbacks.set_dnid) { sig_ss7_callbacks.set_dnid(p->chan_pvt, dnid); } } static int sig_ss7_play_tone(struct sig_ss7_chan *p, enum sig_ss7_tone tone) { int res; if (sig_ss7_callbacks.play_tone) { res = sig_ss7_callbacks.play_tone(p->chan_pvt, tone); } else { res = -1; } return res; } static int sig_ss7_set_echocanceller(struct sig_ss7_chan *p, int enable) { if (sig_ss7_callbacks.set_echocanceller) { return sig_ss7_callbacks.set_echocanceller(p->chan_pvt, enable); } return -1; } static void sig_ss7_loopback(struct sig_ss7_chan *p, int enable) { if (p->loopedback != enable) { p->loopedback = enable; if (sig_ss7_callbacks.set_loopback) { sig_ss7_callbacks.set_loopback(p->chan_pvt, enable); } } } static struct ast_channel *sig_ss7_new_ast_channel(struct sig_ss7_chan *p, int state, int ulaw, int transfercapability, char *exten, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_channel *ast; if (sig_ss7_callbacks.new_ast_channel) { ast = sig_ss7_callbacks.new_ast_channel(p->chan_pvt, state, ulaw, exten, assignedids, requestor); } else { return NULL; } if (!ast) { return NULL; } if (!p->owner) { p->owner = ast; } if (p->outgoing) { p->do_hangup = SS7_HANGUP_FREE_CALL; } else { p->do_hangup = SS7_HANGUP_SEND_REL; } ast_channel_transfercapability_set(ast, transfercapability); pbx_builtin_setvar_helper(ast, "TRANSFERCAPABILITY", ast_transfercapability2str(transfercapability)); if (transfercapability & AST_TRANS_CAP_DIGITAL) { sig_ss7_set_digital(p, 1); } return ast; } static void sig_ss7_handle_link_exception(struct sig_ss7_linkset *linkset, int which) { if (sig_ss7_callbacks.handle_link_exception) { sig_ss7_callbacks.handle_link_exception(linkset, which); } } static struct sig_ss7_linkset *sig_ss7_find_linkset(struct ss7 *ss7) { if (sig_ss7_callbacks.find_linkset) { return sig_ss7_callbacks.find_linkset(ss7); } return NULL; } /*! * \internal * \brief Determine if a private channel structure is available. * * \param pvt Channel to determine if available. * * \return TRUE if the channel is available. */ static int sig_ss7_is_chan_available(struct sig_ss7_chan *pvt) { if (pvt->inservice && !pvt->inalarm && !pvt->owner && !pvt->ss7call && pvt->call_level == SIG_SS7_CALL_LEVEL_IDLE && !pvt->locallyblocked && !pvt->remotelyblocked) { return 1; } return 0; } /*! * \internal * \brief Obtain the sig_ss7 owner channel lock if the owner exists. * \since 1.8 * * \param ss7 SS7 linkset control structure. * \param chanpos Channel position in the span. * * \note Assumes the ss7->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_ss7_lock_owner(struct sig_ss7_linkset *ss7, int chanpos) { for (;;) { if (!ss7->pvts[chanpos]->owner) { /* There is no owner lock to get. */ break; } if (!ast_channel_trylock(ss7->pvts[chanpos]->owner)) { /* We got the lock */ break; } /* Avoid deadlock */ sig_ss7_unlock_private(ss7->pvts[chanpos]); DEADLOCK_AVOIDANCE(&ss7->lock); sig_ss7_lock_private(ss7->pvts[chanpos]); } } /*! * \internal * \brief Queue the given frame onto the owner channel. * \since 1.8 * * \param ss7 SS7 linkset control structure. * \param chanpos Channel position in the span. * \param frame Frame to queue onto the owner channel. * * \note Assumes the ss7->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_ss7_queue_frame(struct sig_ss7_linkset *ss7, int chanpos, struct ast_frame *frame) { sig_ss7_lock_owner(ss7, chanpos); if (ss7->pvts[chanpos]->owner) { ast_queue_frame(ss7->pvts[chanpos]->owner, frame); ast_channel_unlock(ss7->pvts[chanpos]->owner); } } /*! * \internal * \brief Queue a control frame of the specified subclass onto the owner channel. * \since 1.8 * * \param ss7 SS7 linkset control structure. * \param chanpos Channel position in the span. * \param subclass Control frame subclass to queue onto the owner channel. * * \note Assumes the ss7->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_ss7_queue_control(struct sig_ss7_linkset *ss7, int chanpos, int subclass) { struct ast_frame f = {AST_FRAME_CONTROL, }; struct sig_ss7_chan *p = ss7->pvts[chanpos]; if (sig_ss7_callbacks.queue_control) { sig_ss7_callbacks.queue_control(p->chan_pvt, subclass); } f.subclass.integer = subclass; sig_ss7_queue_frame(ss7, chanpos, &f); } /*! * \internal * \brief Queue a PVT_CAUSE_CODE frame onto the owner channel. * \since 11.0 * * \param owner Owner channel of the pvt. * \param cause String describing the cause to be placed into the frame. * * \note Assumes the linkset->lock is already obtained. * \note Assumes the sig_ss7_lock_private(linkset->pvts[chanpos]) is already obtained. * \note Assumes linkset->pvts[chanpos]->owner is non-NULL and its lock is already obtained. * * \return Nothing */ static void ss7_queue_pvt_cause_data(struct ast_channel *owner, const char *cause, int ast_cause) { struct ast_control_pvt_cause_code *cause_code; int datalen = sizeof(*cause_code) + strlen(cause); cause_code = ast_alloca(datalen); memset(cause_code, 0, datalen); cause_code->ast_cause = ast_cause; ast_copy_string(cause_code->chan_name, ast_channel_name(owner), AST_CHANNEL_NAME); ast_copy_string(cause_code->code, cause, datalen + 1 - sizeof(*cause_code)); ast_queue_control_data(owner, AST_CONTROL_PVT_CAUSE_CODE, cause_code, datalen); ast_channel_hangupcause_hash_set(owner, cause_code, datalen); } /*! * \brief Find the channel position by CIC/DPC. * * \param linkset SS7 linkset control structure. * \param cic Circuit Identification Code * \param dpc Destination Point Code * * \retval chanpos on success. * \retval -1 on error. */ int sig_ss7_find_cic(struct sig_ss7_linkset *linkset, int cic, unsigned int dpc) { int i; int winner = -1; for (i = 0; i < linkset->numchans; i++) { if (linkset->pvts[i] && (linkset->pvts[i]->dpc == dpc && linkset->pvts[i]->cic == cic)) { winner = i; break; } } return winner; } /*! * \internal * \brief Find the channel position by CIC/DPC and gripe if not found. * * \param linkset SS7 linkset control structure. * \param cic Circuit Identification Code * \param dpc Destination Point Code * \param msg_name Message type name that failed. * * \retval chanpos on success. * \retval -1 on error. */ static int ss7_find_cic_gripe(struct sig_ss7_linkset *linkset, int cic, unsigned int dpc, const char *msg_name) { int chanpos; chanpos = sig_ss7_find_cic(linkset, cic, dpc); if (chanpos < 0) { ast_log(LOG_WARNING, "Linkset %d: SS7 %s requested on unconfigured CIC/DPC %d/%d.\n", linkset->span, msg_name, cic, dpc); return -1; } return chanpos; } static struct sig_ss7_chan *ss7_find_pvt(struct ss7 *ss7, int cic, unsigned int dpc) { int chanpos; struct sig_ss7_linkset *winner; winner = sig_ss7_find_linkset(ss7); if (winner && (chanpos = sig_ss7_find_cic(winner, cic, dpc)) > -1) { return winner->pvts[chanpos]; } return NULL; } int sig_ss7_cb_hangup(struct ss7 *ss7, int cic, unsigned int dpc, int cause, int do_hangup) { struct sig_ss7_chan *p; int res; if (!(p = ss7_find_pvt(ss7, cic, dpc))) { return SS7_CIC_NOT_EXISTS; } sig_ss7_lock_private(p); if (p->owner) { ast_channel_hangupcause_set(p->owner, cause); ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); p->do_hangup = do_hangup; res = SS7_CIC_USED; } else { res = SS7_CIC_IDLE; } sig_ss7_unlock_private(p); return res; } void sig_ss7_cb_call_null(struct ss7 *ss7, struct isup_call *call, int lock) { int i; struct sig_ss7_linkset *winner; winner = sig_ss7_find_linkset(ss7); if (!winner) { return; } for (i = 0; i < winner->numchans; i++) { if (winner->pvts[i] && (winner->pvts[i]->ss7call == call)) { if (lock) { sig_ss7_lock_private(winner->pvts[i]); } winner->pvts[i]->ss7call = NULL; if (winner->pvts[i]->owner) { ast_channel_hangupcause_set(winner->pvts[i]->owner, AST_CAUSE_NORMAL_TEMPORARY_FAILURE); ast_channel_softhangup_internal_flag_add(winner->pvts[i]->owner, AST_SOFTHANGUP_DEV); } if (lock) { sig_ss7_unlock_private(winner->pvts[i]); } ast_log(LOG_WARNING, "libss7 asked set ss7 call to NULL on CIC %d DPC %d\n", winner->pvts[i]->cic, winner->pvts[i]->dpc); } } } void sig_ss7_cb_notinservice(struct ss7 *ss7, int cic, unsigned int dpc) { struct sig_ss7_chan *p; if (!(p = ss7_find_pvt(ss7, cic, dpc))) { return; } sig_ss7_lock_private(p); sig_ss7_set_inservice(p, 0); sig_ss7_unlock_private(p); } /*! * \internal * \brief Check if CICs in a range belong to the linkset for a given DPC. * \since 11.0 * * \param linkset SS7 linkset control structure. * \param startcic Circuit Identification Code to start from * \param endcic Circuit Identification Code to search up-to * \param dpc Destination Point Code * \param state Array containing the status of the search * * \retval Nothing. */ static void ss7_check_range(struct sig_ss7_linkset *linkset, int startcic, int endcic, unsigned int dpc, unsigned char *state) { int cic; for (cic = startcic; cic <= endcic; cic++) { if (state[cic - startcic] && sig_ss7_find_cic(linkset, cic, dpc) == -1) { state[cic - startcic] = 0; } } } static int ss7_match_range(struct sig_ss7_chan *pvt, int startcic, int endcic, unsigned int dpc) { if (pvt && pvt->dpc == dpc && pvt->cic >= startcic && pvt->cic <= endcic) { return 1; } return 0; } /*! * \internal * \brief Check if a range is defined for the given DPC. * \since 11.0 * * \param linkset SS7 linkset control structure. * \param startcic Start CIC of the range to clear. * \param endcic End CIC of the range to clear. * \param dpc Destination Point Code. * * \note Assumes the linkset->lock is already obtained. * * \return TRUE if all CICs in the range are present */ int sig_ss7_find_cic_range(struct sig_ss7_linkset *linkset, int startcic, int endcic, unsigned int dpc) { int i, found = 0; for (i = 0; i < linkset->numchans; i++) { if (ss7_match_range(linkset->pvts[i], startcic, endcic, dpc)) { found++; } } if (found == endcic - startcic + 1) { return 1; } return 0; } static void ss7_handle_cqm(struct sig_ss7_linkset *linkset, ss7_event *e) { unsigned char status[32]; struct sig_ss7_chan *p = NULL; int i; int offset; int chanpos; memset(status, 0, sizeof(status)); for (i = 0; i < linkset->numchans; i++) { if (ss7_match_range(linkset->pvts[i], e->cqm.startcic, e->cqm.endcic, e->cqm.opc)) { p = linkset->pvts[i]; sig_ss7_lock_private(p); offset = p->cic - e->cqm.startcic; status[offset] = 0; if (p->locallyblocked) { status[offset] |= (1 << 0) | (1 << 4); } if (p->remotelyblocked) { status[offset] |= (1 << 1) | (1 << 5); } if (p->ss7call) { if (p->outgoing) { status[offset] |= (1 << 3); } else { status[offset] |= (1 << 2); } } else { status[offset] |= 0x3 << 2; } sig_ss7_unlock_private(p); } } if (p) { isup_cqr(linkset->ss7, e->cqm.startcic, e->cqm.endcic, e->cqm.opc, status); } else { ast_log(LOG_WARNING, "Could not find any equipped circuits within CQM CICs\n"); } chanpos = sig_ss7_find_cic(linkset, e->cqm.startcic, e->cqm.opc); if (chanpos < 0) { isup_free_call(linkset->ss7, e->cqm.call); return; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->cqm.call; if (!p->owner) { p->ss7call = isup_free_call_if_clear(linkset->ss7, e->cqm.call); } sig_ss7_unlock_private(p); } static inline void ss7_hangup_cics(struct sig_ss7_linkset *linkset, int startcic, int endcic, unsigned int dpc) { int i; for (i = 0; i < linkset->numchans; i++) { if (ss7_match_range(linkset->pvts[i], startcic, endcic, dpc)) { sig_ss7_lock_private(linkset->pvts[i]); sig_ss7_lock_owner(linkset, i); if (linkset->pvts[i]->owner) { ast_softhangup_nolock(linkset->pvts[i]->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(linkset->pvts[i]->owner); } sig_ss7_unlock_private(linkset->pvts[i]); } } } /*! * \param linkset SS7 linkset control structure. * \param startcic Start CIC of the range to clear. * \param endcic End CIC of the range to clear. * \param dpc Destination Point Code. * \param state Affected CICs from the operation. NULL for all CICs in the range. * \param block Operation to perform. TRUE to block. * \param remotely Direction of the blocking. TRUE to block/unblock remotely. * \param type Blocking type - hardware or maintenance. * * \note Assumes the linkset->lock is already obtained. * \note Must be called without sig_ss7_lock_private() obtained. * * \return Nothing. */ static inline void ss7_block_cics(struct sig_ss7_linkset *linkset, int startcic, int endcic, unsigned int dpc, unsigned char state[], int block, int remotely, int type) { int i; for (i = 0; i < linkset->numchans; i++) { if (ss7_match_range(linkset->pvts[i], startcic, endcic, dpc)) { sig_ss7_lock_private(linkset->pvts[i]); if (state) { if (state[linkset->pvts[i]->cic - startcic]) { if (remotely) { sig_ss7_set_remotelyblocked(linkset->pvts[i], block, type); } else { sig_ss7_set_locallyblocked(linkset->pvts[i], block, type); } sig_ss7_lock_owner(linkset, i); if (linkset->pvts[i]->owner) { if (ast_channel_state(linkset->pvts[i]->owner) == AST_STATE_DIALING && linkset->pvts[i]->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING) { ast_channel_hangupcause_set(linkset->pvts[i]->owner, SS7_CAUSE_TRY_AGAIN); } ast_channel_unlock(linkset->pvts[i]->owner); } } } else { if (remotely) { sig_ss7_set_remotelyblocked(linkset->pvts[i], block, type); } else { sig_ss7_set_locallyblocked(linkset->pvts[i], block, type); } } sig_ss7_unlock_private(linkset->pvts[i]); } } } /*! * \param linkset SS7 linkset control structure. * \param startcic Start CIC of the range to set in service. * \param endcic End CIC of the range to set in service. * \param dpc Destination Point Code. * * \note Must be called without sig_ss7_lock_private() obtained. * * \return Nothing. */ static void ss7_inservice(struct sig_ss7_linkset *linkset, int startcic, int endcic, unsigned int dpc) { int i; for (i = 0; i < linkset->numchans; i++) { if (ss7_match_range(linkset->pvts[i], startcic, endcic, dpc)) { sig_ss7_lock_private(linkset->pvts[i]); sig_ss7_set_inservice(linkset->pvts[i], 1); sig_ss7_unlock_private(linkset->pvts[i]); } } } static int ss7_find_alloc_call(struct sig_ss7_chan *p) { if (!p) { return 0; } if (!p->ss7call) { p->ss7call = isup_new_call(p->ss7->ss7, p->cic, p->dpc, 0); if (!p->ss7call) { return 0; } } return 1; } /* * XXX This routine is not tolerant of holes in the pvts[] array. * XXX This routine assumes the cic's in the pvts[] array are sorted. * * Probably the easiest way to deal with the invalid assumptions * is to have a local pvts[] array and sort it by dpc and cic. * Then the existing algorithm could work. */ static void ss7_reset_linkset(struct sig_ss7_linkset *linkset) { int i, startcic, endcic, dpc; struct sig_ss7_chan *p; if (linkset->numchans <= 0) { return; } startcic = linkset->pvts[0]->cic; p = linkset->pvts[0]; /* DB: CIC's DPC fix */ dpc = linkset->pvts[0]->dpc; for (i = 0; i < linkset->numchans; i++) { if (linkset->pvts[i+1] && linkset->pvts[i+1]->dpc == dpc && linkset->pvts[i+1]->cic - linkset->pvts[i]->cic == 1 && linkset->pvts[i]->cic - startcic < (linkset->type == SS7_ANSI ? 24 : 31)) { continue; } else { endcic = linkset->pvts[i]->cic; ast_verb(1, "Resetting CICs %d to %d\n", startcic, endcic); sig_ss7_lock_private(p); if (!ss7_find_alloc_call(p)) { ast_log(LOG_ERROR, "Unable to allocate new ss7call\n"); } else if (!(endcic - startcic)) { /* GRS range can not be 0 - use RSC instead */ isup_rsc(linkset->ss7, p->ss7call); } else { isup_grs(linkset->ss7, p->ss7call, endcic); } sig_ss7_unlock_private(p); /* DB: CIC's DPC fix */ if (linkset->pvts[i+1]) { startcic = linkset->pvts[i+1]->cic; dpc = linkset->pvts[i+1]->dpc; p = linkset->pvts[i+1]; } } } } /*! * \internal * \brief Complete the RSC procedure started earlier * \since 11.0 * * \param p Signaling private structure pointer. * * \note Assumes the ss7->lock is already obtained. * \note Assumes sig_ss7_lock_private(p) is already obtained. * * \return Nothing. */ static void ss7_do_rsc(struct sig_ss7_chan *p) { if (!p || !p->ss7call) { return; } isup_rsc(p->ss7->ss7, p->ss7call); if (p->locallyblocked & SS7_BLOCKED_MAINTENANCE) { isup_blo(p->ss7->ss7, p->ss7call); } else { sig_ss7_set_locallyblocked(p, 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); } } /*! * \internal * \brief Start RSC procedure on a specific link * \since 11.0 * * \param ss7 SS7 linkset control structure. * \param which Channel position in the span. * * \note Assumes the ss7->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return TRUE on success */ static int ss7_start_rsc(struct sig_ss7_linkset *linkset, int which) { if (!linkset->pvts[which]) { return 0; } if (!ss7_find_alloc_call(linkset->pvts[which])) { return 0; } sig_ss7_set_remotelyblocked(linkset->pvts[which], 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); sig_ss7_set_inservice(linkset->pvts[which], 0); sig_ss7_loopback(linkset->pvts[which], 0); sig_ss7_lock_owner(linkset, which); if (linkset->pvts[which]->owner) { ast_channel_hangupcause_set(linkset->pvts[which]->owner, AST_CAUSE_NORMAL_CLEARING); ast_softhangup_nolock(linkset->pvts[which]->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(linkset->pvts[which]->owner); linkset->pvts[which]->do_hangup = SS7_HANGUP_SEND_RSC; } else { ss7_do_rsc(linkset->pvts[which]); } return 1; } /*! * \internal * \brief Determine if a private channel structure is available. * \since 11.0 * * \param linkset SS7 linkset control structure. * \param startcic Start CIC of the range to clear. * \param endcic End CIC of the range to clear. * \param dpc Destination Point Code. * \param do_hangup What we have to do to clear the call. * * \note Assumes the linkset->lock is already obtained. * \note Must be called without sig_ss7_lock_private() obtained. * * \return Nothing. */ static void ss7_clear_channels(struct sig_ss7_linkset *linkset, int startcic, int endcic, int dpc, int do_hangup) { int i; for (i = 0; i < linkset->numchans; i++) { if (ss7_match_range(linkset->pvts[i], startcic, endcic, dpc)) { sig_ss7_lock_private(linkset->pvts[i]); sig_ss7_set_inservice(linkset->pvts[i], 0); sig_ss7_lock_owner(linkset, i); if (linkset->pvts[i]->owner) { ast_channel_hangupcause_set(linkset->pvts[i]->owner, AST_CAUSE_NORMAL_CLEARING); ast_softhangup_nolock(linkset->pvts[i]->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(linkset->pvts[i]->owner); linkset->pvts[i]->do_hangup = (linkset->pvts[i]->cic != startcic) ? do_hangup : SS7_HANGUP_DO_NOTHING; } else if (linkset->pvts[i] && linkset->pvts[i]->cic != startcic) { isup_free_call(linkset->pvts[i]->ss7->ss7, linkset->pvts[i]->ss7call); linkset->pvts[i]->ss7call = NULL; } sig_ss7_unlock_private(linkset->pvts[i]); } } } /*! * \internal * * \param p Signaling private structure pointer. * \param linkset SS7 linkset control structure. * * \note Assumes the linkset->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return Nothing. */ static void ss7_start_call(struct sig_ss7_chan *p, struct sig_ss7_linkset *linkset) { struct ss7 *ss7 = linkset->ss7; int law; struct ast_channel *c; char tmp[256]; char *strp; struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); if (!(linkset->flags & LINKSET_FLAG_EXPLICITACM)) { p->call_level = SIG_SS7_CALL_LEVEL_PROCEEDING; isup_acm(ss7, p->ss7call); } else { p->call_level = SIG_SS7_CALL_LEVEL_SETUP; } /* Companding law is determined by SS7 signaling type. */ if (linkset->type == SS7_ITU) { law = SIG_SS7_ALAW; } else { law = SIG_SS7_ULAW; } isup_set_echocontrol(p->ss7call, (linkset->flags & LINKSET_FLAG_DEFAULTECHOCONTROL) ? 1 : 0); /* * Release the SS7 lock while we create the channel so other * threads can send messages. We must also release the private * lock to prevent deadlock while creating the channel. */ ast_mutex_unlock(&linkset->lock); sig_ss7_unlock_private(p); c = sig_ss7_new_ast_channel(p, AST_STATE_RING, law, 0, p->exten, NULL, NULL); if (!c) { ast_log(LOG_WARNING, "Unable to start PBX on CIC %d\n", p->cic); ast_mutex_lock(&linkset->lock); sig_ss7_lock_private(p); isup_rel(linkset->ss7, p->ss7call, AST_CAUSE_SWITCH_CONGESTION); p->call_level = SIG_SS7_CALL_LEVEL_IDLE; ast_callid_threadstorage_auto_clean(callid, callid_created); return; } /* Hold the channel and private lock while we setup the channel. */ ast_channel_lock(c); sig_ss7_lock_private(p); ast_channel_stage_snapshot(c); /* * It is reasonably safe to set the following * channel variables while the channel private * structure is locked. The PBX has not been * started yet and it is unlikely that any other task * will do anything with the channel we have just * created. * * We only reference these variables in the context of the ss7_linkset function * when receiving either and IAM or a COT message. */ if (!ast_strlen_zero(p->charge_number)) { pbx_builtin_setvar_helper(c, "SS7_CHARGE_NUMBER", p->charge_number); /* Clear this after we set it */ p->charge_number[0] = 0; } if (!ast_strlen_zero(p->gen_add_number)) { pbx_builtin_setvar_helper(c, "SS7_GENERIC_ADDRESS", p->gen_add_number); /* Clear this after we set it */ p->gen_add_number[0] = 0; } if (!ast_strlen_zero(p->jip_number)) { pbx_builtin_setvar_helper(c, "SS7_JIP", p->jip_number); /* Clear this after we set it */ p->jip_number[0] = 0; } if (!ast_strlen_zero(p->gen_dig_number)) { pbx_builtin_setvar_helper(c, "SS7_GENERIC_DIGITS", p->gen_dig_number); /* Clear this after we set it */ p->gen_dig_number[0] = 0; } snprintf(tmp, sizeof(tmp), "%d", p->gen_dig_type); pbx_builtin_setvar_helper(c, "SS7_GENERIC_DIGTYPE", tmp); /* Clear this after we set it */ p->gen_dig_type = 0; snprintf(tmp, sizeof(tmp), "%d", p->gen_dig_scheme); pbx_builtin_setvar_helper(c, "SS7_GENERIC_DIGSCHEME", tmp); /* Clear this after we set it */ p->gen_dig_scheme = 0; if (!ast_strlen_zero(p->lspi_ident)) { pbx_builtin_setvar_helper(c, "SS7_LSPI_IDENT", p->lspi_ident); /* Clear this after we set it */ p->lspi_ident[0] = 0; } snprintf(tmp, sizeof(tmp), "%d", p->call_ref_ident); pbx_builtin_setvar_helper(c, "SS7_CALLREF_IDENT", tmp); /* Clear this after we set it */ p->call_ref_ident = 0; snprintf(tmp, sizeof(tmp), "%d", p->call_ref_pc); pbx_builtin_setvar_helper(c, "SS7_CALLREF_PC", tmp); /* Clear this after we set it */ p->call_ref_pc = 0; snprintf(tmp, sizeof(tmp), "%d", p->calling_party_cat); pbx_builtin_setvar_helper(c, "SS7_CALLING_PARTY_CATEGORY", tmp); /* Clear this after we set it */ p->calling_party_cat = 0; if (p->redirect_counter) { struct ast_party_redirecting redirecting; switch (p->redirect_info_ind) { case 0: strp = "NO_REDIRECTION"; break; case 1: strp = "CALL_REROUTED_PRES_ALLOWED"; break; case 2: strp = "CALL_REROUTED_INFO_RESTRICTED"; break; case 3: strp = "CALL_DIVERTED_PRES_ALLOWED"; break; case 4: strp = "CALL_DIVERTED_INFO_RESTRICTED"; break; case 5: strp = "CALL_REROUTED_PRES_RESTRICTED"; break; case 6: strp = "CALL_DIVERTED_PRES_RESTRICTED"; break; case 7: strp = "SPARE"; break; default: strp = "NO_REDIRECTION"; break; } pbx_builtin_setvar_helper(c, "SS7_REDIRECT_INFO_IND", strp); /* Clear this after we set it */ p->redirect_info_ind = 0; ast_party_redirecting_init(&redirecting); if (p->redirect_info_counter) { redirecting.count = p->redirect_info_counter; if (p->redirect_info_counter != p->redirect_counter) { if (p->redirect_info_counter < p->redirect_counter) { redirecting.count = p->redirect_counter; } ast_log(LOG_WARNING, "Redirect counters differ: %u while info says %u - using %u\n", p->redirect_counter, p->redirect_info_counter, redirecting.count); } /* Clear this after we set it */ p->redirect_info_counter = 0; p->redirect_counter = 0; } if (p->redirect_counter) { redirecting.count = p->redirect_counter; /* Clear this after we set it */ p->redirect_counter = 0; } switch (p->redirect_info_orig_reas) { case SS7_REDIRECTING_REASON_UNKNOWN: redirecting.orig_reason.code = AST_REDIRECTING_REASON_UNKNOWN; break; case SS7_REDIRECTING_REASON_USER_BUSY: redirecting.orig_reason.code = AST_REDIRECTING_REASON_USER_BUSY; break; case SS7_REDIRECTING_REASON_NO_ANSWER: redirecting.orig_reason.code = AST_REDIRECTING_REASON_NO_ANSWER; break; case SS7_REDIRECTING_REASON_UNCONDITIONAL: redirecting.orig_reason.code = AST_REDIRECTING_REASON_UNCONDITIONAL; break; default: redirecting.orig_reason.code = AST_REDIRECTING_REASON_UNKNOWN; break; } switch (p->redirect_info_reas) { case SS7_REDIRECTING_REASON_UNKNOWN: redirecting.reason.code = AST_REDIRECTING_REASON_UNKNOWN; break; case SS7_REDIRECTING_REASON_USER_BUSY: redirecting.reason.code = AST_REDIRECTING_REASON_USER_BUSY; if (!p->redirect_info_orig_reas && redirecting.count == 1) { redirecting.orig_reason.code = AST_REDIRECTING_REASON_USER_BUSY; } break; case SS7_REDIRECTING_REASON_NO_ANSWER: redirecting.reason.code = AST_REDIRECTING_REASON_NO_ANSWER; if (!p->redirect_info_orig_reas && redirecting.count == 1) { redirecting.orig_reason.code = AST_REDIRECTING_REASON_NO_ANSWER; } break; case SS7_REDIRECTING_REASON_UNCONDITIONAL: redirecting.reason.code = AST_REDIRECTING_REASON_UNCONDITIONAL; if (!p->redirect_info_orig_reas && redirecting.count == 1) { redirecting.orig_reason.code = AST_REDIRECTING_REASON_UNCONDITIONAL; } break; case SS7_REDIRECTING_REASON_DEFLECTION_DURING_ALERTING: case SS7_REDIRECTING_REASON_DEFLECTION_IMMEDIATE_RESPONSE: redirecting.reason.code = AST_REDIRECTING_REASON_DEFLECTION; break; case SS7_REDIRECTING_REASON_UNAVAILABLE: redirecting.reason.code = AST_REDIRECTING_REASON_UNAVAILABLE; break; default: redirecting.reason.code = AST_REDIRECTING_REASON_UNKNOWN; break; } /* Clear this after we set it */ p->redirect_info_orig_reas = 0; p->redirect_info_reas = 0; if (!ast_strlen_zero(p->redirecting_num)) { redirecting.from.number.str = ast_strdup(p->redirecting_num); redirecting.from.number.presentation = p->redirecting_presentation; redirecting.from.number.valid = 1; /* Clear this after we set it */ p->redirecting_num[0] = 0; } if (!ast_strlen_zero(p->generic_name)) { redirecting.from.name.str = ast_strdup(p->generic_name); redirecting.from.name.presentation = p->redirecting_presentation; redirecting.from.name.valid = 1; /* Clear this after we set it */ p->generic_name[0] = 0; } if (!ast_strlen_zero(p->orig_called_num)) { redirecting.orig.number.str = ast_strdup(p->orig_called_num); redirecting.orig.number.presentation = p->orig_called_presentation; redirecting.orig.number.valid = 1; /* Clear this after we set it */ p->orig_called_num[0] = 0; } else if (redirecting.count == 1) { ast_party_id_copy(&redirecting.orig, &redirecting.from); } ast_channel_update_redirecting(c, &redirecting, NULL); ast_party_redirecting_free(&redirecting); } if (p->cug_indicator != ISUP_CUG_NON) { sprintf(tmp, "%d", p->cug_interlock_code); pbx_builtin_setvar_helper(c, "SS7_CUG_INTERLOCK_CODE", tmp); switch (p->cug_indicator) { case ISUP_CUG_NON: strp = "NON_CUG"; break; case ISUP_CUG_OUTGOING_ALLOWED: strp = "OUTGOING_ALLOWED"; break; case ISUP_CUG_OUTGOING_NOT_ALLOWED: strp = "OUTGOING_NOT_ALLOWED"; break; default: strp = "SPARE"; break; } pbx_builtin_setvar_helper(c, "SS7_CUG_INDICATOR", strp); if (!ast_strlen_zero(p->cug_interlock_ni)) { pbx_builtin_setvar_helper(c, "SS7_CUG_INTERLOCK_NI", p->cug_interlock_ni); } p->cug_indicator = ISUP_CUG_NON; } ast_channel_stage_snapshot_done(c); sig_ss7_unlock_private(p); ast_channel_unlock(c); if (ast_pbx_start(c)) { ast_log(LOG_WARNING, "Unable to start PBX on %s (CIC %d)\n", ast_channel_name(c), p->cic); ast_hangup(c); } else { ast_verb(3, "Accepting call to '%s' on CIC %d\n", p->exten, p->cic); } /* Must return with linkset and private lock. */ ast_mutex_lock(&linkset->lock); sig_ss7_lock_private(p); ast_callid_threadstorage_auto_clean(callid, callid_created); } static void ss7_apply_plan_to_number(char *buf, size_t size, const struct sig_ss7_linkset *ss7, const char *number, const unsigned nai) { if (ast_strlen_zero(number)) { /* make sure a number exists so prefix isn't placed on an empty string */ if (size) { *buf = '\0'; } return; } switch (nai) { case SS7_NAI_INTERNATIONAL: snprintf(buf, size, "%s%s", ss7->internationalprefix, number); break; case SS7_NAI_NATIONAL: snprintf(buf, size, "%s%s", ss7->nationalprefix, number); break; case SS7_NAI_SUBSCRIBER: snprintf(buf, size, "%s%s", ss7->subscriberprefix, number); break; case SS7_NAI_UNKNOWN: snprintf(buf, size, "%s%s", ss7->unknownprefix, number); break; case SS7_NAI_NETWORKROUTED: snprintf(buf, size, "%s%s", ss7->networkroutedprefix, number); break; default: snprintf(buf, size, "%s", number); break; } } static int ss7_pres_scr2cid_pres(char presentation_ind, char screening_ind) { return ((presentation_ind & 0x3) << 5) | (screening_ind & 0x3); } /*! * \internal * \brief Set callid threadstorage for the ss7_linkset thread to that of an existing channel * * \param linkset ss7 span control structure. * \param chanpos channel position in the span * * \note Assumes the ss7->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return a reference to the callid bound to the channel which has also * been bound to threadstorage if it exists. If this returns non-NULL, * the callid must be unreffed and the threadstorage should be unbound * before the while loop wraps in ss7_linkset. */ static struct ast_callid *func_ss7_linkset_callid(struct sig_ss7_linkset *linkset, int chanpos) { struct ast_callid *callid = NULL; sig_ss7_lock_owner(linkset, chanpos); if (linkset->pvts[chanpos]->owner) { callid = ast_channel_callid(linkset->pvts[chanpos]->owner); ast_channel_unlock(linkset->pvts[chanpos]->owner); if (callid) { ast_callid_threadassoc_add(callid); } } return callid; } /*! * \internal * \brief Proceed with the call based on the extension matching status * is matching in the dialplan. * \since 11.0 * * \param linkset ss7 span control structure. * \param p Signaling private structure pointer. * \param e Event causing the match. * * \note Assumes the linkset->lock is already obtained. * \note Assumes the sig_ss7_lock_private(ss7->pvts[chanpos]) is already obtained. * * \return Nothing. */ static void ss7_match_extension(struct sig_ss7_linkset *linkset, struct sig_ss7_chan *p, ss7_event *e) { ast_verb(3, "SS7 exten: %s complete: %d\n", p->exten, p->called_complete); if (!p->called_complete && linkset->type == SS7_ITU /* ANSI does not support overlap dialing. */ && ast_matchmore_extension(NULL, p->context, p->exten, 1, p->cid_num) && !isup_start_digittimeout(linkset->ss7, p->ss7call)) { /* Wait for more digits. */ return; } if (ast_exists_extension(NULL, p->context, p->exten, 1, p->cid_num)) { /* DNID is complete */ p->called_complete = 1; sig_ss7_set_dnid(p, p->exten); /* If COT successful start call! */ if ((e->e == ISUP_EVENT_IAM) ? !(e->iam.cot_check_required || e->iam.cot_performed_on_previous_cic) : (!(e->sam.cot_check_required || e->sam.cot_performed_on_previous_cic) || e->sam.cot_check_passed)) { ss7_start_call(p, linkset); } return; } ast_debug(1, "Call on CIC for unconfigured extension %s\n", p->exten); isup_rel(linkset->ss7, (e->e == ISUP_EVENT_IAM) ? e->iam.call : e->sam.call, AST_CAUSE_UNALLOCATED); } /* This is a thread per linkset that handles all received events from libss7. */ void *ss7_linkset(void *data) { int res, i; struct timeval *next = NULL, tv; struct sig_ss7_linkset *linkset = (struct sig_ss7_linkset *) data; struct ss7 *ss7 = linkset->ss7; ss7_event *e = NULL; struct sig_ss7_chan *p; struct pollfd pollers[SIG_SS7_NUM_DCHANS]; unsigned char mb_state[255]; int nextms; #define SS7_MAX_POLL 60000 /* Maximum poll time in ms. */ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); ss7_set_debug(ss7, SIG_SS7_DEBUG_DEFAULT); ast_mutex_lock(&linkset->lock); ss7_start(ss7); ast_mutex_unlock(&linkset->lock); for (;;) { ast_mutex_lock(&linkset->lock); if ((next = ss7_schedule_next(ss7))) { tv = ast_tvnow(); tv.tv_sec = next->tv_sec - tv.tv_sec; tv.tv_usec = next->tv_usec - tv.tv_usec; if (tv.tv_usec < 0) { tv.tv_usec += 1000000; tv.tv_sec -= 1; } if (tv.tv_sec < 0) { tv.tv_sec = 0; tv.tv_usec = 0; } nextms = tv.tv_sec * 1000; nextms += tv.tv_usec / 1000; if (SS7_MAX_POLL < nextms) { nextms = SS7_MAX_POLL; } } else { nextms = SS7_MAX_POLL; } for (i = 0; i < linkset->numsigchans; i++) { pollers[i].fd = linkset->fds[i]; pollers[i].events = ss7_pollflags(ss7, linkset->fds[i]); pollers[i].revents = 0; } ast_mutex_unlock(&linkset->lock); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); res = poll(pollers, linkset->numsigchans, nextms); pthread_testcancel(); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); ast_mutex_lock(&linkset->lock); if ((res < 0) && (errno != EINTR)) { ast_log(LOG_ERROR, "poll(%s)\n", strerror(errno)); } else if (!res) { ss7_schedule_run(ss7); } for (i = 0; i < linkset->numsigchans; i++) { if (pollers[i].revents & POLLPRI) { sig_ss7_handle_link_exception(linkset, i); } if (pollers[i].revents & POLLIN) { res = ss7_read(ss7, pollers[i].fd); } if (pollers[i].revents & POLLOUT) { res = ss7_write(ss7, pollers[i].fd); if (res < 0) { ast_debug(1, "Error in write %s\n", strerror(errno)); } } } while ((e = ss7_check_event(ss7))) { struct ast_callid *callid = NULL; int chanpos = -1; char cause_str[30]; if (linkset->debug) { ast_verbose("Linkset %d: Processing event: %s\n", linkset->span, ss7_event2str(e->e)); } switch (e->e) { case SS7_EVENT_UP: if (linkset->state != LINKSET_STATE_UP) { ast_verb(1, "--- SS7 Up ---\n"); ss7_reset_linkset(linkset); } linkset->state = LINKSET_STATE_UP; break; case SS7_EVENT_DOWN: ast_verb(1, "--- SS7 Down ---\n"); linkset->state = LINKSET_STATE_DOWN; for (i = 0; i < linkset->numchans; i++) { p = linkset->pvts[i]; if (p) { sig_ss7_set_inservice(p, 0); if (linkset->flags & LINKSET_FLAG_INITIALHWBLO) { sig_ss7_set_remotelyblocked(p, 1, SS7_BLOCKED_HARDWARE); } } } break; case MTP2_LINK_UP: ast_verb(1, "MTP2 link up (SLC %d)\n", e->gen.data); break; case MTP2_LINK_DOWN: ast_log(LOG_WARNING, "MTP2 link down (SLC %d)\n", e->gen.data); break; case ISUP_EVENT_CPG: chanpos = ss7_find_cic_gripe(linkset, e->cpg.cic, e->cpg.opc, "CPG"); if (chanpos < 0) { isup_free_call(ss7, e->cpg.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); callid = func_ss7_linkset_callid(linkset, chanpos); switch (e->cpg.event) { case CPG_EVENT_ALERTING: if (p->call_level < SIG_SS7_CALL_LEVEL_ALERTING) { p->call_level = SIG_SS7_CALL_LEVEL_ALERTING; } sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { ast_setstate(p->owner, AST_STATE_RINGING); if (!ast_strlen_zero(e->cpg.connected_num)) { struct ast_party_connected_line ast_connected; char connected_num[AST_MAX_EXTENSION]; ast_party_connected_line_init(&ast_connected); ast_connected.id.number.presentation = ss7_pres_scr2cid_pres(e->cpg.connected_presentation_ind, e->cpg.connected_screening_ind); ss7_apply_plan_to_number(connected_num, sizeof(connected_num), linkset, e->cpg.connected_num, e->cpg.connected_nai); ast_connected.id.number.str = ast_strdup(connected_num); ast_connected.id.number.valid = 1; ast_channel_queue_connected_line_update(p->owner, &ast_connected, NULL); ast_party_connected_line_free(&ast_connected); } ast_channel_unlock(p->owner); } sig_ss7_queue_control(linkset, chanpos, AST_CONTROL_RINGING); break; case CPG_EVENT_PROGRESS: case CPG_EVENT_INBANDINFO: { ast_debug(1, "Queuing frame PROGRESS on CIC %d\n", p->cic); sig_ss7_queue_control(linkset, chanpos, AST_CONTROL_PROGRESS); p->progress = 1; sig_ss7_set_dialing(p, 0); sig_ss7_open_media(p); } break; default: ast_debug(1, "Do not handle CPG with event type 0x%x\n", e->cpg.event); break; } sig_ss7_unlock_private(p); break; case ISUP_EVENT_RSC: ast_verb(1, "Resetting CIC %d\n", e->rsc.cic); chanpos = ss7_find_cic_gripe(linkset, e->rsc.cic, e->rsc.opc, "RSC"); if (chanpos < 0) { isup_free_call(ss7, e->rsc.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->rsc.call; callid = func_ss7_linkset_callid(linkset, chanpos); sig_ss7_set_inservice(p, 1); sig_ss7_set_remotelyblocked(p, 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); if (p->locallyblocked & SS7_BLOCKED_MAINTENANCE) { isup_blo(ss7, e->rsc.call); } else if (p->locallyblocked & SS7_BLOCKED_HARDWARE) { sig_ss7_set_locallyblocked(p, 0, SS7_BLOCKED_HARDWARE); } isup_set_call_dpc(e->rsc.call, p->dpc); sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { p->do_hangup = SS7_HANGUP_SEND_RLC; if (!(e->rsc.got_sent_msg & ISUP_SENT_IAM)) { /* Q.784 6.2.3 */ ast_channel_hangupcause_set(p->owner, AST_CAUSE_NORMAL_CLEARING); } else { ast_channel_hangupcause_set(p->owner, SS7_CAUSE_TRY_AGAIN); } ss7_queue_pvt_cause_data(p->owner, "SS7 ISUP_EVENT_RSC", AST_CAUSE_INTERWORKING); ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(p->owner); } else { isup_rlc(ss7, e->rsc.call); p->ss7call = isup_free_call_if_clear(ss7, e->rsc.call); } /* End the loopback if we have one */ sig_ss7_loopback(p, 0); sig_ss7_unlock_private(p); break; case ISUP_EVENT_GRS: if (!sig_ss7_find_cic_range(linkset, e->grs.startcic, e->grs.endcic, e->grs.opc)) { ast_log(LOG_WARNING, "GRS on unconfigured range CIC %d - %d PC %d\n", e->grs.startcic, e->grs.endcic, e->grs.opc); chanpos = sig_ss7_find_cic(linkset, e->grs.startcic, e->grs.opc); if (chanpos < 0) { isup_free_call(ss7, e->grs.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = isup_free_call_if_clear(ss7, e->grs.call); sig_ss7_unlock_private(p); break; } /* Leave startcic last to collect all cics mb_state */ for (i = e->grs.endcic - e->grs.startcic; 0 <= i; --i) { /* * We are guaranteed to find chanpos because * sig_ss7_find_cic_range() includes it. */ chanpos = sig_ss7_find_cic(linkset, e->grs.startcic + i, e->grs.opc); p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); if (p->locallyblocked & SS7_BLOCKED_MAINTENANCE) { mb_state[i] = 1; } else { mb_state[i] = 0; sig_ss7_set_locallyblocked(p, 0, SS7_BLOCKED_HARDWARE); } sig_ss7_set_remotelyblocked(p, 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); if (!i) { p->ss7call = e->grs.call; isup_gra(ss7, p->ss7call, e->grs.endcic, mb_state); } sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); if (ast_channel_state(p->owner) == AST_STATE_DIALING && linkset->pvts[i]->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING) { ast_channel_hangupcause_set(p->owner, SS7_CAUSE_TRY_AGAIN); } else { ast_channel_hangupcause_set(p->owner, AST_CAUSE_NORMAL_CLEARING); } p->do_hangup = SS7_HANGUP_FREE_CALL; ast_channel_unlock(p->owner); } else if (!i) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } else if (p->ss7call) { /* clear any other session */ isup_free_call(ss7, p->ss7call); p->ss7call = NULL; } sig_ss7_set_inservice(p, 1); sig_ss7_unlock_private(p); } break; case ISUP_EVENT_CQM: ast_debug(1, "Got Circuit group query message from CICs %d to %d\n", e->cqm.startcic, e->cqm.endcic); ss7_handle_cqm(linkset, e); break; case ISUP_EVENT_GRA: if (!sig_ss7_find_cic_range(linkset, e->gra.startcic, e->gra.endcic, e->gra.opc)) { /* Never will be true */ ast_log(LOG_WARNING, "GRA on unconfigured range CIC %d - %d PC %d\n", e->gra.startcic, e->gra.endcic, e->gra.opc); isup_free_call(ss7, e->gra.call); break; } ast_verb(1, "Got reset acknowledgement from CIC %d to %d DPC: %d\n", e->gra.startcic, e->gra.endcic, e->gra.opc); ss7_block_cics(linkset, e->gra.startcic, e->gra.endcic, e->gra.opc, e->gra.status, 1, 1, SS7_BLOCKED_MAINTENANCE); ss7_inservice(linkset, e->gra.startcic, e->gra.endcic, e->gra.opc); chanpos = sig_ss7_find_cic(linkset, e->gra.startcic, e->gra.opc); if (chanpos < 0) { isup_free_call(ss7, e->gra.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); /* we may send a CBD with GRS! */ p->ss7call = isup_free_call_if_clear(ss7, e->gra.call); sig_ss7_unlock_private(p); break; case ISUP_EVENT_SAM: chanpos = ss7_find_cic_gripe(linkset, e->sam.cic, e->sam.opc, "SAM"); if (chanpos < 0) { isup_free_call(ss7, e->sam.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { ast_log(LOG_WARNING, "SAM on CIC %d PC %d already have call\n", e->sam.cic, e->sam.opc); ast_channel_unlock(p->owner); sig_ss7_unlock_private(p); break; } p->called_complete = 0; if (!ast_strlen_zero(e->sam.called_party_num)) { char *st; strncat(p->exten, e->sam.called_party_num, sizeof(p->exten) - strlen(p->exten) - 1); st = strchr(p->exten, '#'); if (st) { *st = '\0'; p->called_complete = 1; } ss7_match_extension(linkset, p, e); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_IAM: ast_debug(1, "Got IAM for CIC %d and called number %s, calling number %s\n", e->iam.cic, e->iam.called_party_num, e->iam.calling_party_num); chanpos = ss7_find_cic_gripe(linkset, e->iam.cic, e->iam.opc, "IAM"); if (chanpos < 0) { isup_free_call(ss7, e->iam.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); /* * The channel should be idle and not have an owner at this point since we * are in the process of creating an owner for it. */ ast_assert(!p->owner && p->call_level == SIG_SS7_CALL_LEVEL_IDLE); if (p->remotelyblocked) { ast_log(LOG_NOTICE, "Got IAM on remotely blocked CIC %d DPC %d remove blocking\n", e->iam.cic, e->iam.opc); sig_ss7_set_remotelyblocked(p, 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); sig_ss7_set_inservice(p, 1); } if (!sig_ss7_is_chan_available(p)) { /* Circuit is likely blocked or in alarm. */ isup_rel(ss7, e->iam.call, AST_CAUSE_NORMAL_CIRCUIT_CONGESTION); if (p->locallyblocked) { isup_clear_callflags(ss7, e->iam.call, ISUP_GOT_IAM); p->ss7call = isup_free_call_if_clear(ss7, e->iam.call); ast_log(LOG_WARNING, "Got IAM on locally blocked CIC %d DPC %d, ignore\n", e->iam.cic, e->iam.opc); } sig_ss7_unlock_private(p); break; } /* Mark channel as in use so no outgoing call will steal it. */ p->call_level = SIG_SS7_CALL_LEVEL_ALLOCATED; p->ss7call = e->iam.call; isup_set_call_dpc(p->ss7call, p->dpc); if ((p->use_callerid) && (!ast_strlen_zero(e->iam.calling_party_num))) { ss7_apply_plan_to_number(p->cid_num, sizeof(p->cid_num), linkset, e->iam.calling_party_num, e->iam.calling_nai); p->callingpres = ss7_pres_scr2cid_pres(e->iam.presentation_ind, e->iam.screening_ind); } else { p->cid_num[0] = 0; if (e->iam.presentation_ind) { p->callingpres = ss7_pres_scr2cid_pres(e->iam.presentation_ind, e->iam.screening_ind); } } p->called_complete = 0; if (p->immediate) { p->exten[0] = 's'; p->exten[1] = '\0'; } else if (!ast_strlen_zero(e->iam.called_party_num)) { char *st; ss7_apply_plan_to_number(p->exten, sizeof(p->exten), linkset, e->iam.called_party_num, e->iam.called_nai); st = strchr(p->exten, '#'); if (st) { *st = '\0'; p->called_complete = 1; } } else { p->exten[0] = '\0'; } p->cid_ani[0] = '\0'; if ((p->use_callerid) && (!ast_strlen_zero(e->iam.generic_name))) { ast_copy_string(p->cid_name, e->iam.generic_name, sizeof(p->cid_name)); } else { p->cid_name[0] = '\0'; } p->cid_ani2 = e->iam.oli_ani2; p->cid_ton = 0; ast_copy_string(p->charge_number, e->iam.charge_number, sizeof(p->charge_number)); ast_copy_string(p->gen_add_number, e->iam.gen_add_number, sizeof(p->gen_add_number)); p->gen_add_type = e->iam.gen_add_type; p->gen_add_nai = e->iam.gen_add_nai; p->gen_add_pres_ind = e->iam.gen_add_pres_ind; p->gen_add_num_plan = e->iam.gen_add_num_plan; ast_copy_string(p->gen_dig_number, e->iam.gen_dig_number, sizeof(p->gen_dig_number)); p->gen_dig_type = e->iam.gen_dig_type; p->gen_dig_scheme = e->iam.gen_dig_scheme; ast_copy_string(p->jip_number, e->iam.jip_number, sizeof(p->jip_number)); if (!ast_strlen_zero(e->iam.orig_called_num)) { ss7_apply_plan_to_number(p->orig_called_num, sizeof(p->orig_called_num), linkset, e->iam.orig_called_num, e->iam.orig_called_nai); p->orig_called_presentation = ss7_pres_scr2cid_pres(e->iam.orig_called_pres_ind, e->iam.orig_called_screening_ind); } if (!ast_strlen_zero(e->iam.redirecting_num)) { ss7_apply_plan_to_number(p->redirecting_num, sizeof(p->redirecting_num), linkset, e->iam.redirecting_num, e->iam.redirecting_num_nai); p->redirecting_presentation = ss7_pres_scr2cid_pres(e->iam.redirecting_num_presentation_ind, e->iam.redirecting_num_screening_ind); } ast_copy_string(p->generic_name, e->iam.generic_name, sizeof(p->generic_name)); p->calling_party_cat = e->iam.calling_party_cat; p->redirect_counter = e->iam.redirect_counter; p->redirect_info = e->iam.redirect_info; p->redirect_info_ind = e->iam.redirect_info_ind; p->redirect_info_orig_reas = e->iam.redirect_info_orig_reas; p->redirect_info_counter = e->iam.redirect_info_counter; if (p->redirect_info_counter && !p->redirect_counter) { p->redirect_counter = p->redirect_info_counter; } p->redirect_info_reas = e->iam.redirect_info_reas; p->cug_indicator = e->iam.cug_indicator; p->cug_interlock_code = e->iam.cug_interlock_code; ast_copy_string(p->cug_interlock_ni, e->iam.cug_interlock_ni, sizeof(p->cug_interlock_ni)); if (e->iam.cot_check_required) { sig_ss7_loopback(p, 1); } p->echocontrol_ind = e->iam.echocontrol_ind; sig_ss7_set_caller_id(p); ss7_match_extension(linkset, p, e); sig_ss7_unlock_private(p); if (e->iam.cot_performed_on_previous_cic) { chanpos = sig_ss7_find_cic(linkset, (e->iam.cic - 1), e->iam.opc); if (chanpos < 0) { /* some stupid switch do this */ ast_verb(1, "COT request on previous nonexistent CIC %d in IAM PC %d\n", (e->iam.cic - 1), e->iam.opc); break; } ast_verb(1, "COT request on previous CIC %d in IAM PC %d\n", (e->iam.cic - 1), e->iam.opc); p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); if (sig_ss7_is_chan_available(p)) { sig_ss7_set_inservice(p, 0); /* to prevent to use this circuit */ sig_ss7_loopback(p, 1); } /* If already have a call don't loop */ sig_ss7_unlock_private(p); } break; case ISUP_EVENT_DIGITTIMEOUT: chanpos = ss7_find_cic_gripe(linkset, e->digittimeout.cic, e->digittimeout.opc, "DIGITTIMEOUT"); if (chanpos < 0) { isup_free_call(ss7, e->digittimeout.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); ast_debug(1, "Digittimeout on CIC: %d PC: %d\n", e->digittimeout.cic, e->digittimeout.opc); if (ast_exists_extension(NULL, p->context, p->exten, 1, p->cid_num)) { /* DNID is complete */ p->called_complete = 1; sig_ss7_set_dnid(p, p->exten); /* If COT successful start call! */ if (!(e->digittimeout.cot_check_required || e->digittimeout.cot_performed_on_previous_cic) || e->digittimeout.cot_check_passed) { ss7_start_call(p, linkset); } } else { ast_debug(1, "Call on CIC for unconfigured extension %s\n", p->exten); isup_rel(linkset->ss7, e->digittimeout.call, AST_CAUSE_UNALLOCATED); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_COT: if (e->cot.cot_performed_on_previous_cic) { chanpos = sig_ss7_find_cic(linkset, (e->cot.cic - 1), e->cot.opc); /* some stupid switches do this!!! */ if (-1 < chanpos) { p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); sig_ss7_set_inservice(p, 1); sig_ss7_loopback(p, 0); sig_ss7_unlock_private(p);; ast_verb(1, "Loop turned off on CIC: %d PC: %d\n", (e->cot.cic - 1), e->cot.opc); } } chanpos = ss7_find_cic_gripe(linkset, e->cot.cic, e->cot.opc, "COT"); if (chanpos < 0) { isup_free_call(ss7, e->cot.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->cot.call; if (p->loopedback) { sig_ss7_loopback(p, 0); ast_verb(1, "Loop turned off on CIC: %d PC: %d\n", e->cot.cic, e->cot.opc); } /* Don't start call if we didn't get IAM or COT failed! */ if ((e->cot.got_sent_msg & ISUP_GOT_IAM) && e->cot.passed && p->called_complete) { ss7_start_call(p, linkset); } p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); sig_ss7_unlock_private(p); break; case ISUP_EVENT_CCR: ast_debug(1, "Got CCR request on CIC %d\n", e->ccr.cic); chanpos = ss7_find_cic_gripe(linkset, e->ccr.cic, e->ccr.opc, "CCR"); if (chanpos < 0) { isup_free_call(ss7, e->ccr.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->ccr.call; sig_ss7_loopback(p, 1); if (linkset->type == SS7_ANSI) { isup_lpa(linkset->ss7, e->ccr.cic, p->dpc); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_CVT: ast_debug(1, "Got CVT request on CIC %d\n", e->cvt.cic); chanpos = ss7_find_cic_gripe(linkset, e->cvt.cic, e->cvt.opc, "CVT"); if (chanpos < 0) { isup_free_call(ss7, e->cvt.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->cvt.call; sig_ss7_loopback(p, 1); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, e->cvt.call); } isup_cvr(linkset->ss7, e->cvt.cic, p->dpc); sig_ss7_unlock_private(p); break; case ISUP_EVENT_REL: chanpos = ss7_find_cic_gripe(linkset, e->rel.cic, e->rel.opc, "REL"); if (chanpos < 0) { isup_free_call(ss7, e->rel.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->rel.call; callid = func_ss7_linkset_callid(linkset, chanpos); sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { snprintf(cause_str, sizeof(cause_str), "SS7 ISUP_EVENT_REL (%d)", e->rel.cause); ss7_queue_pvt_cause_data(p->owner, cause_str, e->rel.cause); ast_channel_hangupcause_set(p->owner, e->rel.cause); ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); p->do_hangup = SS7_HANGUP_SEND_RLC; ast_channel_unlock(p->owner); } else { ast_verb(1, "REL on CIC %d DPC %d without owner!\n", p->cic, p->dpc); isup_rlc(ss7, p->ss7call); p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } /* End the loopback if we have one */ sig_ss7_loopback(p, 0); /* the rel is not complete here!!! */ sig_ss7_unlock_private(p); break; case ISUP_EVENT_ACM: chanpos = ss7_find_cic_gripe(linkset, e->acm.cic, e->acm.opc, "ACM"); if (chanpos < 0) { isup_free_call(ss7, e->acm.call); break; } p = linkset->pvts[chanpos]; ast_debug(1, "Queueing frame from SS7_EVENT_ACM on CIC %d\n", p->cic); if (e->acm.call_ref_ident > 0) { p->rlt = 1; /* Setting it but not using it here*/ } sig_ss7_lock_private(p); p->ss7call = e->acm.call; callid = func_ss7_linkset_callid(linkset, chanpos); sig_ss7_queue_control(linkset, chanpos, AST_CONTROL_PROCEEDING); if (p->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING) { p->call_level = SIG_SS7_CALL_LEVEL_PROCEEDING; } sig_ss7_set_dialing(p, 0); /* Send alerting if subscriber is free */ if (e->acm.called_party_status_ind == 1) { if (p->call_level < SIG_SS7_CALL_LEVEL_ALERTING) { p->call_level = SIG_SS7_CALL_LEVEL_ALERTING; } sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { ast_setstate(p->owner, AST_STATE_RINGING); ast_channel_unlock(p->owner); } sig_ss7_queue_control(linkset, chanpos, AST_CONTROL_RINGING); } p->echocontrol_ind = e->acm.echocontrol_ind; sig_ss7_unlock_private(p); break; case ISUP_EVENT_CGB: chanpos = ss7_find_cic_gripe(linkset, e->cgb.startcic, e->cgb.opc, "CGB"); if (chanpos < 0) { isup_free_call(ss7, e->cgb.call); break; } p = linkset->pvts[chanpos]; ss7_check_range(linkset, e->cgb.startcic, e->cgb.endcic, e->cgb.opc, e->cgb.status); ss7_block_cics(linkset, e->cgb.startcic, e->cgb.endcic, e->cgb.opc, e->cgb.status, 1, 1, (e->cgb.type) ? SS7_BLOCKED_HARDWARE : SS7_BLOCKED_MAINTENANCE); sig_ss7_lock_private(p); p->ss7call = e->cgb.call; isup_cgba(linkset->ss7, p->ss7call, e->cgb.endcic, e->cgb.status); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, e->cgb.call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_CGU: chanpos = ss7_find_cic_gripe(linkset, e->cgu.startcic, e->cgu.opc, "CGU"); if (chanpos < 0) { isup_free_call(ss7, e->cgu.call); break; } p = linkset->pvts[chanpos]; ss7_check_range(linkset, e->cgu.startcic, e->cgu.endcic, e->cgu.opc, e->cgu.status); ss7_block_cics(linkset, e->cgu.startcic, e->cgu.endcic, e->cgu.opc, e->cgu.status, 0, 1, e->cgu.type ? SS7_BLOCKED_HARDWARE : SS7_BLOCKED_MAINTENANCE); sig_ss7_lock_private(p); p->ss7call = e->cgu.call; isup_cgua(linkset->ss7, p->ss7call, e->cgu.endcic, e->cgu.status); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, e->cgu.call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_UCIC: chanpos = ss7_find_cic_gripe(linkset, e->ucic.cic, e->ucic.opc, "UCIC"); if (chanpos < 0) { isup_free_call(ss7, e->ucic.call); break; } p = linkset->pvts[chanpos]; ast_debug(1, "Unequiped Circuit Id Code on CIC %d\n", e->ucic.cic); sig_ss7_lock_private(p); sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(p->owner); } sig_ss7_set_remotelyblocked(p, 1, SS7_BLOCKED_MAINTENANCE); sig_ss7_set_inservice(p, 0); p->ss7call = NULL; isup_free_call(ss7, e->ucic.call); sig_ss7_unlock_private(p);/* doesn't require a SS7 acknowledgement */ break; case ISUP_EVENT_BLO: chanpos = ss7_find_cic_gripe(linkset, e->blo.cic, e->blo.opc, "BLO"); if (chanpos < 0) { isup_free_call(ss7, e->blo.call); break; } p = linkset->pvts[chanpos]; ast_debug(1, "Blocking CIC %d\n", e->blo.cic); sig_ss7_lock_private(p); p->ss7call = e->blo.call; sig_ss7_set_remotelyblocked(p, 1, SS7_BLOCKED_MAINTENANCE); isup_bla(linkset->ss7, e->blo.call); sig_ss7_lock_owner(linkset, chanpos); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, e->blo.call); } else { if (e->blo.got_sent_msg & ISUP_SENT_IAM) { /* Q.784 6.2.2 */ ast_channel_hangupcause_set(p->owner, SS7_CAUSE_TRY_AGAIN); } ast_channel_unlock(p->owner); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_BLA: chanpos = ss7_find_cic_gripe(linkset, e->bla.cic, e->bla.opc, "BLA"); if (chanpos < 0) { isup_free_call(ss7, e->bla.call); break; } ast_debug(1, "Locally blocking CIC %d\n", e->bla.cic); p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->bla.call; sig_ss7_set_locallyblocked(p, 1, SS7_BLOCKED_MAINTENANCE); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_UBL: chanpos = ss7_find_cic_gripe(linkset, e->ubl.cic, e->ubl.opc, "UBL"); if (chanpos < 0) { isup_free_call(ss7, e->ubl.call); break; } p = linkset->pvts[chanpos]; ast_debug(1, "Remotely unblocking CIC %d PC %d\n", e->ubl.cic, e->ubl.opc); sig_ss7_lock_private(p); p->ss7call = e->ubl.call; sig_ss7_set_remotelyblocked(p, 0, SS7_BLOCKED_MAINTENANCE); isup_uba(linkset->ss7, e->ubl.call); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_UBA: chanpos = ss7_find_cic_gripe(linkset, e->uba.cic, e->uba.opc, "UBA"); if (chanpos < 0) { isup_free_call(ss7, e->uba.call); break; } p = linkset->pvts[chanpos]; ast_debug(1, "Locally unblocking CIC %d PC %d\n", e->uba.cic, e->uba.opc); sig_ss7_lock_private(p); p->ss7call = e->uba.call; sig_ss7_set_locallyblocked(p, 0, SS7_BLOCKED_MAINTENANCE); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_CON: case ISUP_EVENT_ANM: if (e->e == ISUP_EVENT_CON) { chanpos = ss7_find_cic_gripe(linkset, e->con.cic, e->con.opc, "CON"); if (chanpos < 0) { isup_free_call(ss7, e->con.call); break; } } else { chanpos = ss7_find_cic_gripe(linkset, e->anm.cic, e->anm.opc, "ANM"); if (chanpos < 0) { isup_free_call(ss7, e->anm.call); break; } } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = (e->e == ISUP_EVENT_ANM) ? e->anm.call : e->con.call; callid = func_ss7_linkset_callid(linkset, chanpos); if (p->call_level < SIG_SS7_CALL_LEVEL_CONNECT) { p->call_level = SIG_SS7_CALL_LEVEL_CONNECT; } if (!ast_strlen_zero((e->e == ISUP_EVENT_ANM) ? e->anm.connected_num : e->con.connected_num)) { sig_ss7_lock_owner(linkset, chanpos); if (p->owner) { struct ast_party_connected_line ast_connected; char connected_num[AST_MAX_EXTENSION]; ast_party_connected_line_init(&ast_connected); if (e->e == ISUP_EVENT_ANM) { ast_connected.id.number.presentation = ss7_pres_scr2cid_pres( e->anm.connected_presentation_ind, e->anm.connected_screening_ind); ss7_apply_plan_to_number(connected_num, sizeof(connected_num), linkset, e->anm.connected_num, e->anm.connected_nai); ast_connected.id.number.str = ast_strdup(connected_num); } else { ast_connected.id.number.presentation = ss7_pres_scr2cid_pres( e->con.connected_presentation_ind, e->con.connected_screening_ind); ss7_apply_plan_to_number(connected_num, sizeof(connected_num), linkset, e->con.connected_num, e->con.connected_nai); ast_connected.id.number.str = ast_strdup(connected_num); } ast_connected.id.number.valid = 1; ast_connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; ast_channel_queue_connected_line_update(p->owner, &ast_connected, NULL); ast_party_connected_line_free(&ast_connected); ast_channel_unlock(p->owner); } } sig_ss7_queue_control(linkset, chanpos, AST_CONTROL_ANSWER); sig_ss7_set_dialing(p, 0); sig_ss7_open_media(p); if (((e->e == ISUP_EVENT_ANM) ? !e->anm.echocontrol_ind : !e->con.echocontrol_ind) || !(linkset->flags & LINKSET_FLAG_USEECHOCONTROL)) { sig_ss7_set_echocanceller(p, 1); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_RLC: chanpos = ss7_find_cic_gripe(linkset, e->rlc.cic, e->rlc.opc, "RLC"); if (chanpos < 0) { isup_free_call(ss7, e->rlc.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->rlc.call; callid = func_ss7_linkset_callid(linkset, chanpos); if (e->rlc.got_sent_msg & (ISUP_SENT_RSC | ISUP_SENT_REL)) { sig_ss7_loopback(p, 0); if (e->rlc.got_sent_msg & ISUP_SENT_RSC) { sig_ss7_set_inservice(p, 1); } } sig_ss7_lock_owner(linkset, chanpos); if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, e->rlc.call); p->call_level = SIG_SS7_CALL_LEVEL_IDLE; } else { p->do_hangup = SS7_HANGUP_DO_NOTHING; ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(p->owner); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_FAA: /*! * \todo The handling of the SS7 FAA message is not good and I * don't know enough to handle it correctly. */ chanpos = ss7_find_cic_gripe(linkset, e->faa.cic, e->faa.opc, "FAA"); if (chanpos < 0) { isup_free_call(ss7, e->faa.call); break; } /* XXX FAR and FAA used for something dealing with transfers? */ p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); callid = func_ss7_linkset_callid(linkset, chanpos); ast_debug(1, "FAA received on CIC %d\n", e->faa.cic); p->ss7call = isup_free_call_if_clear(ss7, e->faa.call); sig_ss7_unlock_private(p); break; case ISUP_EVENT_CGBA: chanpos = ss7_find_cic_gripe(linkset, e->cgba.startcic, e->cgba.opc, "CGBA"); if (chanpos < 0) { /* Never will be true */ isup_free_call(ss7, e->cgba.call); break; } ss7_block_cics(linkset, e->cgba.startcic, e->cgba.endcic, e->cgba.opc, e->cgba.status, 1, 0, e->cgba.type ? SS7_BLOCKED_HARDWARE : SS7_BLOCKED_MAINTENANCE); p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->cgba.call; if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_CGUA: chanpos = ss7_find_cic_gripe(linkset, e->cgua.startcic, e->cgua.opc, "CGUA"); if (chanpos < 0) { /* Never will be true */ isup_free_call(ss7, e->cgua.call); break; } ss7_block_cics(linkset, e->cgua.startcic, e->cgua.endcic, e->cgua.opc, e->cgua.status, 0, 0, e->cgba.type ? SS7_BLOCKED_HARDWARE : SS7_BLOCKED_MAINTENANCE); p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->cgua.call; if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_SUS: chanpos = ss7_find_cic_gripe(linkset, e->sus.cic, e->sus.opc, "SUS"); if (chanpos < 0) { isup_free_call(ss7, e->sus.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->sus.call; if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; case ISUP_EVENT_RES: chanpos = ss7_find_cic_gripe(linkset, e->res.cic, e->res.opc, "RES"); if (chanpos < 0) { isup_free_call(ss7, e->res.call); break; } p = linkset->pvts[chanpos]; sig_ss7_lock_private(p); p->ss7call = e->res.call; if (!p->owner) { p->ss7call = isup_free_call_if_clear(ss7, p->ss7call); } sig_ss7_unlock_private(p); break; default: ast_debug(1, "Unknown event %s\n", ss7_event2str(e->e)); break; } /* Call ID stuff needs to be cleaned up here */ if (callid) { callid = ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } ast_mutex_unlock(&linkset->lock); } return 0; } static void ss7_rel(struct sig_ss7_linkset *ss7) { /* Release the lock first */ ast_mutex_unlock(&ss7->lock); /* Then break the poll to send our messages */ if (ss7->master != AST_PTHREADT_NULL) { pthread_kill(ss7->master, SIGURG); } } static void ss7_grab(struct sig_ss7_chan *pvt, struct sig_ss7_linkset *ss7) { /* Grab the lock first */ while (ast_mutex_trylock(&ss7->lock)) { /* Avoid deadlock */ sig_ss7_deadlock_avoidance_private(pvt); } } /*! * \brief Reset a specific CIC. * \since 11.0 * * \param linkset linkset control structure. * \param cic Circuit Identification Code * \param dpc Destination Point Code * * \return TRUE on success */ int sig_ss7_reset_cic(struct sig_ss7_linkset *linkset, int cic, unsigned int dpc) { int i; ast_mutex_lock(&linkset->lock); for (i = 0; i < linkset->numchans; i++) { if (linkset->pvts[i] && linkset->pvts[i]->cic == cic && linkset->pvts[i]->dpc == dpc) { int res; sig_ss7_lock_private(linkset->pvts[i]); sig_ss7_set_locallyblocked(linkset->pvts[i], 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); res = ss7_start_rsc(linkset, i); sig_ss7_unlock_private(linkset->pvts[i]); ss7_rel(linkset); /* Also breaks the poll to send our messages */ return res; } } ss7_rel(linkset); return 0; } /*! * \brief Block or Unblock a specific CIC. * \since 11.0 * * \param linkset linkset control structure. * \param do_block Action to perform. Block if TRUE. * \param which On which CIC to perform the operation. * * \return 0 on success */ int sig_ss7_cic_blocking(struct sig_ss7_linkset *linkset, int do_block, int which) { ast_mutex_lock(&linkset->lock); sig_ss7_lock_private(linkset->pvts[which]); if (!ss7_find_alloc_call(linkset->pvts[which])) { sig_ss7_unlock_private(linkset->pvts[which]); ss7_rel(linkset); return -1; } if (do_block) { isup_blo(linkset->ss7, linkset->pvts[which]->ss7call); } else { isup_ubl(linkset->ss7, linkset->pvts[which]->ss7call); } sig_ss7_unlock_private(linkset->pvts[which]); ss7_rel(linkset); /* Also breaks the poll to send our messages */ return 0; } /*! * \brief Block or Unblock a range of CICs. * \since 11.0 * * \param linkset linkset control structure. * \param do_block Action to perform. Block if TRUE. * \param chanpos Channel position to start from. * \param endcic Circuit Identification Code of the end of the range. * \param state Array of CIC blocking status. * \param type Type of the blocking - maintenance or hardware * * \note Assumes the linkset->lock is already obtained. * * \return 0 on success */ int sig_ss7_group_blocking(struct sig_ss7_linkset *linkset, int do_block, int chanpos, int endcic, unsigned char state[], int type) { sig_ss7_lock_private(linkset->pvts[chanpos]); if (!ss7_find_alloc_call(linkset->pvts[chanpos])) { sig_ss7_unlock_private(linkset->pvts[chanpos]); return -1; } if (do_block) { isup_cgb(linkset->ss7, linkset->pvts[chanpos]->ss7call, endcic, state, type); } else { isup_cgu(linkset->ss7, linkset->pvts[chanpos]->ss7call, endcic, state, type); } sig_ss7_unlock_private(linkset->pvts[chanpos]); return 0; } /*! * \brief Reset a group of CICs. * \since 11.0 * * \param linkset linkset control structure. * \param cic Circuit Identification Code * \param dpc Destination Point Code * \param range Range of the CICs to reset * * \note Assumes the linkset->lock is already obtained. * * \return 0 on success */ int sig_ss7_reset_group(struct sig_ss7_linkset *linkset, int cic, unsigned int dpc, int range) { int i; for (i = 0; i < linkset->numchans; i++) { if (linkset->pvts[i] && linkset->pvts[i]->cic == cic && linkset->pvts[i]->dpc == dpc) { ss7_clear_channels(linkset, cic, cic + range, dpc, SS7_HANGUP_FREE_CALL); ss7_block_cics(linkset, cic, cic + range, dpc, NULL, 0, 1, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); ss7_block_cics(linkset, cic, cic + range, dpc, NULL, 0, 0, SS7_BLOCKED_MAINTENANCE | SS7_BLOCKED_HARDWARE); sig_ss7_lock_private(linkset->pvts[i]); if (!ss7_find_alloc_call(linkset->pvts[i])) { sig_ss7_unlock_private(linkset->pvts[i]); return -1; } isup_grs(linkset->ss7, linkset->pvts[i]->ss7call, linkset->pvts[i]->cic + range); sig_ss7_unlock_private(linkset->pvts[i]); break; } } return 0; } void sig_ss7_free_isup_call(struct sig_ss7_linkset *linkset, int channel) { sig_ss7_lock_private(linkset->pvts[channel]); if (linkset->pvts[channel]->ss7call) { isup_free_call(linkset->ss7, linkset->pvts[channel]->ss7call); linkset->pvts[channel]->ss7call = NULL; } sig_ss7_unlock_private(linkset->pvts[channel]); } static int ss7_parse_prefix(struct sig_ss7_chan *p, const char *number, char *nai) { int strip = 0; if (strncmp(number, p->ss7->internationalprefix, strlen(p->ss7->internationalprefix)) == 0) { strip = strlen(p->ss7->internationalprefix); *nai = SS7_NAI_INTERNATIONAL; } else if (strncmp(number, p->ss7->nationalprefix, strlen(p->ss7->nationalprefix)) == 0) { strip = strlen(p->ss7->nationalprefix); *nai = SS7_NAI_NATIONAL; } else if (strncmp(number, p->ss7->networkroutedprefix, strlen(p->ss7->networkroutedprefix)) == 0) { strip = strlen(p->ss7->networkroutedprefix); *nai = SS7_NAI_NETWORKROUTED; } else if (strncmp(number, p->ss7->unknownprefix, strlen(p->ss7->unknownprefix)) == 0) { strip = strlen(p->ss7->unknownprefix); *nai = SS7_NAI_UNKNOWN; } else if (strncmp(number, p->ss7->subscriberprefix, strlen(p->ss7->subscriberprefix)) == 0) { strip = strlen(p->ss7->subscriberprefix); *nai = SS7_NAI_SUBSCRIBER; } else { *nai = SS7_NAI_SUBSCRIBER; } return strip; } /*! * \brief Notify the SS7 layer that the link is in alarm. * \since 1.8 * * \param linkset Controlling linkset for the channel. * \param which Link index of the signaling channel. * * \return Nothing */ void sig_ss7_link_alarm(struct sig_ss7_linkset *linkset, int which) { linkset->linkstate[which] |= (LINKSTATE_DOWN | LINKSTATE_INALARM); linkset->linkstate[which] &= ~LINKSTATE_UP; ss7_link_alarm(linkset->ss7, linkset->fds[which]); } /*! * \brief Notify the SS7 layer that the link is no longer in alarm. * \since 1.8 * * \param linkset Controlling linkset for the channel. * \param which Link index of the signaling channel. * * \return Nothing */ void sig_ss7_link_noalarm(struct sig_ss7_linkset *linkset, int which) { linkset->linkstate[which] &= ~(LINKSTATE_INALARM | LINKSTATE_DOWN); linkset->linkstate[which] |= LINKSTATE_STARTING; ss7_link_noalarm(linkset->ss7, linkset->fds[which]); } /*! * \brief Setup and add a SS7 link channel. * \since 1.8 * * \param linkset Controlling linkset for the channel. * \param which Link index of the signaling channel. * \param ss7type Switch type of the linkset * \param transport Signaling transport of channel. * \param inalarm Non-zero if the channel is in alarm. * \param networkindicator User configuration parameter. * \param pointcode User configuration parameter. * \param adjpointcode User configuration parameter. * * \retval 0 on success. * \retval -1 on error. */ int sig_ss7_add_sigchan(struct sig_ss7_linkset *linkset, int which, int ss7type, int transport, int inalarm, int networkindicator, int pointcode, int adjpointcode, int cur_slc) { if (!linkset->ss7) { linkset->type = ss7type; linkset->ss7 = ss7_new(ss7type); if (!linkset->ss7) { ast_log(LOG_ERROR, "Can't create new SS7!\n"); return -1; } } ss7_set_network_ind(linkset->ss7, networkindicator); ss7_set_pc(linkset->ss7, pointcode); if (ss7_add_link(linkset->ss7, transport, linkset->fds[which], cur_slc, adjpointcode)) { ast_log(LOG_WARNING, "Could not add SS7 link!\n"); } if (inalarm) { linkset->linkstate[which] = LINKSTATE_DOWN | LINKSTATE_INALARM; ss7_link_alarm(linkset->ss7, linkset->fds[which]); } else { linkset->linkstate[which] = LINKSTATE_DOWN; ss7_link_noalarm(linkset->ss7, linkset->fds[which]); } return 0; } /*! * \brief Determine if the specified channel is available for an outgoing call. * \since 1.8 * * \param p Signaling private structure pointer. * * \retval TRUE if the channel is available. */ int sig_ss7_available(struct sig_ss7_chan *p) { int available; if (!p->ss7) { /* Something is wrong here. A SS7 channel without the ss7 pointer? */ return 0; } /* Only have to deal with the linkset lock. */ ast_mutex_lock(&p->ss7->lock); available = sig_ss7_is_chan_available(p); if (available) { p->ss7call = isup_new_call(p->ss7->ss7, p->cic, p->dpc, 1); if (!p->ss7call) { ast_log(LOG_ERROR, "Unable to allocate new SS7 call!\n"); available = 0; } else { p->call_level = SIG_SS7_CALL_LEVEL_ALLOCATED; } } ast_mutex_unlock(&p->ss7->lock); return available; } static unsigned char cid_pres2ss7pres(int cid_pres) { return (cid_pres >> 5) & 0x03; } static unsigned char cid_pres2ss7screen(int cid_pres) { return cid_pres & 0x03; } static void ss7_connected_line_update(struct sig_ss7_chan *p, struct ast_party_connected_line *connected) { int connected_strip = 0; char connected_nai; unsigned char connected_pres; unsigned char connected_screen; const char *connected_num; if (!connected->id.number.valid) { return; } connected_num = S_OR(connected->id.number.str, ""); if (p->ss7->called_nai == SS7_NAI_DYNAMIC) { connected_strip = ss7_parse_prefix(p, connected_num, &connected_nai); } else { connected_nai = p->ss7->called_nai; } connected_pres = cid_pres2ss7pres(connected->id.number.presentation); connected_screen = cid_pres2ss7screen(connected->id.number.presentation); isup_set_connected(p->ss7call, connected_num + connected_strip, connected_nai, connected_pres, connected_screen); } static unsigned char ss7_redirect_reason(struct sig_ss7_chan *p, struct ast_party_redirecting *redirecting, int orig) { int reason = (orig) ? redirecting->orig_reason.code : redirecting->reason.code; switch (reason) { case AST_REDIRECTING_REASON_USER_BUSY: return SS7_REDIRECTING_REASON_USER_BUSY; case AST_REDIRECTING_REASON_NO_ANSWER: return SS7_REDIRECTING_REASON_NO_ANSWER; case AST_REDIRECTING_REASON_UNCONDITIONAL: return SS7_REDIRECTING_REASON_UNCONDITIONAL; } if (orig || reason == AST_REDIRECTING_REASON_UNKNOWN) { return SS7_REDIRECTING_REASON_UNKNOWN; } if (reason == AST_REDIRECTING_REASON_UNAVAILABLE) { return SS7_REDIRECTING_REASON_UNAVAILABLE; } if (reason == AST_REDIRECTING_REASON_DEFLECTION) { if (p->call_level > SIG_SS7_CALL_LEVEL_PROCEEDING) { return SS7_REDIRECTING_REASON_DEFLECTION_DURING_ALERTING; } return SS7_REDIRECTING_REASON_DEFLECTION_IMMEDIATE_RESPONSE; } return SS7_REDIRECTING_REASON_UNKNOWN; } static unsigned char ss7_redirect_info_ind(struct ast_channel *ast) { const char *redirect_info_ind; struct ast_party_redirecting *redirecting = ast_channel_redirecting(ast); redirect_info_ind = pbx_builtin_getvar_helper(ast, "SS7_REDIRECT_INFO_IND"); if (!ast_strlen_zero(redirect_info_ind)) { if (!strcasecmp(redirect_info_ind, "CALL_REROUTED_PRES_ALLOWED")) { return SS7_INDICATION_REROUTED_PRES_ALLOWED; } if (!strcasecmp(redirect_info_ind, "CALL_REROUTED_INFO_RESTRICTED")) { return SS7_INDICATION_REROUTED_INFO_RESTRICTED; } if (!strcasecmp(redirect_info_ind, "CALL_DIVERTED_PRES_ALLOWED")) { return SS7_INDICATION_DIVERTED_PRES_ALLOWED; } if (!strcasecmp(redirect_info_ind, "CALL_DIVERTED_INFO_RESTRICTED")) { return SS7_INDICATION_DIVERTED_INFO_RESTRICTED; } if (!strcasecmp(redirect_info_ind, "CALL_REROUTED_PRES_RESTRICTED")) { return SS7_INDICATION_REROUTED_PRES_RESTRICTED; } if (!strcasecmp(redirect_info_ind, "CALL_DIVERTED_PRES_RESTRICTED")) { return SS7_INDICATION_DIVERTED_PRES_RESTRICTED; } if (!strcasecmp(redirect_info_ind, "SPARE")) { return SS7_INDICATION_SPARE; } return SS7_INDICATION_NO_REDIRECTION; } if (redirecting->reason.code == AST_REDIRECTING_REASON_DEFLECTION) { if ((redirecting->to.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { if ((redirecting->orig.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { return SS7_INDICATION_DIVERTED_PRES_ALLOWED; } return SS7_INDICATION_DIVERTED_PRES_RESTRICTED; } return SS7_INDICATION_DIVERTED_INFO_RESTRICTED; } if ((redirecting->to.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { if ((redirecting->orig.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { return SS7_INDICATION_REROUTED_PRES_ALLOWED; } return SS7_INDICATION_REROUTED_PRES_RESTRICTED; } return SS7_INDICATION_REROUTED_INFO_RESTRICTED; } static void ss7_redirecting_update(struct sig_ss7_chan *p, struct ast_channel *ast) { int num_nai_strip = 0; struct ast_party_redirecting *redirecting = ast_channel_redirecting(ast); if (!redirecting->count) { return; } isup_set_redirect_counter(p->ss7call, redirecting->count); if (redirecting->orig.number.valid) { char ss7_orig_called_nai = p->ss7->called_nai; const char *ss7_orig_called_num = S_OR(redirecting->orig.number.str, ""); if (ss7_orig_called_nai == SS7_NAI_DYNAMIC) { num_nai_strip = ss7_parse_prefix(p, ss7_orig_called_num, &ss7_orig_called_nai); } else { num_nai_strip = 0; } isup_set_orig_called_num(p->ss7call, ss7_orig_called_num + num_nai_strip, ss7_orig_called_nai, cid_pres2ss7pres(redirecting->orig.number.presentation), cid_pres2ss7screen(redirecting->orig.number.presentation)); } if (redirecting->from.number.valid) { char ss7_redirecting_num_nai = p->ss7->calling_nai; const char *redirecting_number = S_OR(redirecting->from.number.str, ""); if (ss7_redirecting_num_nai == SS7_NAI_DYNAMIC) { num_nai_strip = ss7_parse_prefix(p, redirecting_number, &ss7_redirecting_num_nai); } else { num_nai_strip = 0; } isup_set_redirecting_number(p->ss7call, redirecting_number + num_nai_strip, ss7_redirecting_num_nai, cid_pres2ss7pres(redirecting->from.number.presentation), cid_pres2ss7screen(redirecting->from.number.presentation)); } isup_set_redirection_info(p->ss7call, ss7_redirect_info_ind(ast), ss7_redirect_reason(p, ast_channel_redirecting(ast), 1), redirecting->count, ss7_redirect_reason(p, ast_channel_redirecting(ast), 0)); } /*! * \brief Dial out using the specified SS7 channel. * \since 1.8 * * \param p Signaling private structure pointer. * \param ast Asterisk channel structure pointer. * \param rdest Dialstring. * * \retval 0 on success. * \retval -1 on error. */ int sig_ss7_call(struct sig_ss7_chan *p, struct ast_channel *ast, const char *rdest) { char ss7_called_nai; int called_nai_strip; char ss7_calling_nai; int calling_nai_strip; const char *col_req = NULL; const char *ss7_cug_indicator_str; const char *ss7_cug_interlock_ni; const char *ss7_cug_interlock_code; const char *ss7_interworking_indicator; const char *ss7_forward_indicator_pmbits; unsigned char ss7_cug_indicator; const char *charge_str = NULL; const char *gen_address = NULL; const char *gen_digits = NULL; const char *gen_dig_type = NULL; const char *gen_dig_scheme = NULL; const char *gen_name = NULL; const char *jip_digits = NULL; const char *lspi_ident = NULL; const char *rlt_flag = NULL; const char *call_ref_id = NULL; const char *call_ref_pc = NULL; const char *send_far = NULL; const char *tmr = NULL; char *c; char *l; char dest[256]; ast_copy_string(dest, rdest, sizeof(dest)); c = strchr(dest, '/'); if (c) { c++; } else { c = ""; } if (strlen(c) < p->stripmsd) { ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd); return -1; } if (!p->hidecallerid) { l = ast_channel_connected(ast)->id.number.valid ? ast_channel_connected(ast)->id.number.str : NULL; } else { l = NULL; } ss7_grab(p, p->ss7); if (p->call_level != SIG_SS7_CALL_LEVEL_ALLOCATED) { /* Call collision before sending IAM. Abort call. */ ss7_rel(p->ss7); return -1; } called_nai_strip = 0; ss7_called_nai = p->ss7->called_nai; if (ss7_called_nai == SS7_NAI_DYNAMIC) { /* compute dynamically */ called_nai_strip = ss7_parse_prefix(p, c + p->stripmsd, &ss7_called_nai); } isup_set_called(p->ss7call, c + p->stripmsd + called_nai_strip, ss7_called_nai, p->ss7->ss7); calling_nai_strip = 0; ss7_calling_nai = p->ss7->calling_nai; if ((l != NULL) && (ss7_calling_nai == SS7_NAI_DYNAMIC)) { /* compute dynamically */ calling_nai_strip = ss7_parse_prefix(p, l, &ss7_calling_nai); } isup_set_calling(p->ss7call, l ? (l + calling_nai_strip) : NULL, ss7_calling_nai, p->use_callingpres ? cid_pres2ss7pres(ast_channel_connected(ast)->id.number.presentation) : (l ? SS7_PRESENTATION_ALLOWED : (ast_channel_connected(ast)->id.number.presentation == AST_PRES_UNAVAILABLE ? SS7_PRESENTATION_ADDR_NOT_AVAILABLE : SS7_PRESENTATION_RESTRICTED)), p->use_callingpres ? cid_pres2ss7screen(ast_channel_connected(ast)->id.number.presentation) : SS7_SCREENING_USER_PROVIDED); isup_set_oli(p->ss7call, ast_channel_connected(ast)->ani2); /* Set the charge number if it is set */ charge_str = pbx_builtin_getvar_helper(ast, "SS7_CHARGE_NUMBER"); if (charge_str) isup_set_charge(p->ss7call, charge_str, SS7_ANI_CALLING_PARTY_SUB_NUMBER, 0x10); gen_address = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_ADDRESS"); if (gen_address) isup_set_gen_address(p->ss7call, gen_address, p->gen_add_nai,p->gen_add_pres_ind, p->gen_add_num_plan,p->gen_add_type); /* need to add some types here for NAI,PRES,TYPE */ gen_digits = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_DIGITS"); gen_dig_type = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_DIGTYPE"); gen_dig_scheme = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_DIGSCHEME"); if (gen_digits) isup_set_gen_digits(p->ss7call, gen_digits, atoi(gen_dig_type), atoi(gen_dig_scheme)); gen_name = pbx_builtin_getvar_helper(ast, "SS7_GENERIC_NAME"); if (gen_name) isup_set_generic_name(p->ss7call, gen_name, GEN_NAME_TYPE_CALLING_NAME, GEN_NAME_AVAIL_AVAILABLE, GEN_NAME_PRES_ALLOWED); jip_digits = pbx_builtin_getvar_helper(ast, "SS7_JIP"); if (jip_digits) isup_set_jip_digits(p->ss7call, jip_digits); lspi_ident = pbx_builtin_getvar_helper(ast, "SS7_LSPI_IDENT"); if (lspi_ident) isup_set_lspi(p->ss7call, lspi_ident, 0x18, 0x7, 0x00); rlt_flag = pbx_builtin_getvar_helper(ast, "SS7_RLT_ON"); if ((rlt_flag) && ((strncmp("NO", rlt_flag, strlen(rlt_flag))) != 0 )) { isup_set_lspi(p->ss7call, rlt_flag, 0x18, 0x7, 0x00); /* Setting for Nortel DMS-250/500 */ } call_ref_id = pbx_builtin_getvar_helper(ast, "SS7_CALLREF_IDENT"); call_ref_pc = pbx_builtin_getvar_helper(ast, "SS7_CALLREF_PC"); if (call_ref_id && call_ref_pc) { isup_set_callref(p->ss7call, atoi(call_ref_id), call_ref_pc ? atoi(call_ref_pc) : 0); } send_far = pbx_builtin_getvar_helper(ast, "SS7_SEND_FAR"); if (send_far && strncmp("NO", send_far, strlen(send_far)) != 0) { isup_far(p->ss7->ss7, p->ss7call); } tmr = pbx_builtin_getvar_helper(ast, "SS7_TMR_NUM"); if (tmr) { isup_set_tmr(p->ss7call, atoi(tmr)); } else if ((tmr = pbx_builtin_getvar_helper(ast, "SS7_TMR")) && tmr[0] != '\0') { if (!strcasecmp(tmr, "SPEECH")) { isup_set_tmr(p->ss7call, SS7_TMR_SPEECH); } else if (!strcasecmp(tmr, "SPARE")) { isup_set_tmr(p->ss7call, SS7_TMR_SPARE); } else if (!strcasecmp(tmr, "3K1_AUDIO")) { isup_set_tmr(p->ss7call, SS7_TMR_3K1_AUDIO); } else if (!strcasecmp(tmr, "64K_UNRESTRICTED")) { isup_set_tmr(p->ss7call, SS7_TMR_64K_UNRESTRICTED); } else { isup_set_tmr(p->ss7call, SS7_TMR_N64K_OR_SPARE); } } col_req = pbx_builtin_getvar_helper(ast, "SS7_COL_REQUEST"); if (ast_true(col_req)) { isup_set_col_req(p->ss7call); } ss7_cug_indicator_str = pbx_builtin_getvar_helper(ast, "SS7_CUG_INDICATOR"); if (!ast_strlen_zero(ss7_cug_indicator_str)) { if (!strcasecmp(ss7_cug_indicator_str, "OUTGOING_ALLOWED")) { ss7_cug_indicator = ISUP_CUG_OUTGOING_ALLOWED; } else if (!strcasecmp(ss7_cug_indicator_str, "OUTGOING_NOT_ALLOWED")) { ss7_cug_indicator = ISUP_CUG_OUTGOING_NOT_ALLOWED; } else { ss7_cug_indicator = ISUP_CUG_NON; } if (ss7_cug_indicator != ISUP_CUG_NON) { ss7_cug_interlock_code = pbx_builtin_getvar_helper(ast, "SS7_CUG_INTERLOCK_CODE"); ss7_cug_interlock_ni = pbx_builtin_getvar_helper(ast, "SS7_CUG_INTERLOCK_NI"); if (ss7_cug_interlock_code && ss7_cug_interlock_ni && strlen(ss7_cug_interlock_ni) == 4) { isup_set_cug(p->ss7call, ss7_cug_indicator, ss7_cug_interlock_ni, atoi(ss7_cug_interlock_code)); } } } ss7_redirecting_update(p, ast); isup_set_echocontrol(p->ss7call, (p->ss7->flags & LINKSET_FLAG_DEFAULTECHOCONTROL) ? 1 : 0); ss7_interworking_indicator = pbx_builtin_getvar_helper(ast, "SS7_INTERWORKING_INDICATOR"); if (ss7_interworking_indicator) { isup_set_interworking_indicator(p->ss7call, ast_true(ss7_interworking_indicator)); } ss7_forward_indicator_pmbits = pbx_builtin_getvar_helper(ast, "SS7_FORWARD_INDICATOR_PMBITS"); if (ss7_forward_indicator_pmbits) { isup_set_forward_indicator_pmbits(p->ss7call, atoi(ss7_forward_indicator_pmbits)); } p->call_level = SIG_SS7_CALL_LEVEL_SETUP; p->do_hangup = SS7_HANGUP_SEND_REL; isup_iam(p->ss7->ss7, p->ss7call); sig_ss7_set_dialing(p, 1); ast_setstate(ast, AST_STATE_DIALING); ss7_rel(p->ss7); return 0; } /*! * \brief SS7 hangup channel. * \since 1.8 * * \param p Signaling private structure pointer. * \param ast Asterisk channel structure pointer. * * \retval 0 on success. * \retval -1 on error. */ int sig_ss7_hangup(struct sig_ss7_chan *p, struct ast_channel *ast) { if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } p->owner = NULL; sig_ss7_set_dialing(p, 0); sig_ss7_set_outgoing(p, 0); p->progress = 0; p->rlt = 0; p->exten[0] = '\0'; /* Perform low level hangup if no owner left */ ss7_grab(p, p->ss7); p->call_level = SIG_SS7_CALL_LEVEL_IDLE; if (p->ss7call) { switch (p->do_hangup) { case SS7_HANGUP_SEND_REL: { const char *cause = pbx_builtin_getvar_helper(ast,"SS7_CAUSE"); int icause = ast_channel_hangupcause(ast) ? ast_channel_hangupcause(ast) : -1; if (cause) { if (atoi(cause)) { icause = atoi(cause); } } if (icause > 255) { icause = 16; } isup_rel(p->ss7->ss7, p->ss7call, icause); p->do_hangup = SS7_HANGUP_DO_NOTHING; } break; case SS7_HANGUP_SEND_RSC: ss7_do_rsc(p); p->do_hangup = SS7_HANGUP_DO_NOTHING; break; case SS7_HANGUP_SEND_RLC: isup_rlc(p->ss7->ss7, p->ss7call); p->do_hangup = SS7_HANGUP_DO_NOTHING; p->ss7call = isup_free_call_if_clear(p->ss7->ss7, p->ss7call); break; case SS7_HANGUP_FREE_CALL: p->do_hangup = SS7_HANGUP_DO_NOTHING; isup_free_call(p->ss7->ss7, p->ss7call); p->ss7call = NULL; break; case SS7_HANGUP_REEVENT_IAM: isup_event_iam(p->ss7->ss7, p->ss7call, p->dpc); p->do_hangup = SS7_HANGUP_SEND_REL; break; case SS7_HANGUP_DO_NOTHING: p->ss7call = isup_free_call_if_clear(p->ss7->ss7, p->ss7call); break; } } ss7_rel(p->ss7); return 0; } /*! * \brief SS7 answer channel. * \since 1.8 * * \param p Signaling private structure pointer. * \param ast Asterisk channel structure pointer. * * \retval 0 on success. * \retval -1 on error. */ int sig_ss7_answer(struct sig_ss7_chan *p, struct ast_channel *ast) { int res; ss7_grab(p, p->ss7); if (p->call_level < SIG_SS7_CALL_LEVEL_CONNECT) { if (p->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING && (p->ss7->flags & LINKSET_FLAG_AUTOACM)) { isup_acm(p->ss7->ss7, p->ss7call); } p->call_level = SIG_SS7_CALL_LEVEL_CONNECT; } res = isup_anm(p->ss7->ss7, p->ss7call); sig_ss7_open_media(p); ss7_rel(p->ss7); return res; } /*! * \brief Fix up a channel: If a channel is consumed, this is called. Basically update any ->owner links. * \since 1.8 * * \param oldchan Old channel pointer to replace. * \param newchan New channel pointer to set. * \param pchan Signaling private structure pointer. * * \return Nothing */ void sig_ss7_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_ss7_chan *pchan) { if (pchan->owner == oldchan) { pchan->owner = newchan; } } /*! * \brief SS7 indication. * \since 1.8 * * \param p Signaling private structure pointer. * \param chan Asterisk channel structure pointer. * \param condition AST control frame subtype. * \param data AST control frame payload contents. * \param datalen Length of payload contents. * * \retval 0 on success. * \retval -1 on error or indication condition not handled. */ int sig_ss7_indicate(struct sig_ss7_chan *p, struct ast_channel *chan, int condition, const void *data, size_t datalen) { int res = -1; switch (condition) { case AST_CONTROL_BUSY: if (p->call_level < SIG_SS7_CALL_LEVEL_CONNECT) { ast_channel_hangupcause_set(chan, AST_CAUSE_USER_BUSY); ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); res = 0; break; } res = sig_ss7_play_tone(p, SIG_SS7_TONE_BUSY); break; case AST_CONTROL_RINGING: ss7_grab(p, p->ss7); if (p->call_level < SIG_SS7_CALL_LEVEL_ALERTING && !p->outgoing) { if ((isup_far(p->ss7->ss7, p->ss7call)) != -1) { p->rlt = 1; } if (p->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING && (p->ss7->flags & LINKSET_FLAG_AUTOACM)) { isup_acm(p->ss7->ss7, p->ss7call); } /* No need to send CPG if call will be RELEASE */ if (p->rlt != 1) { isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_ALERTING); } p->call_level = SIG_SS7_CALL_LEVEL_ALERTING; } ss7_rel(p->ss7); res = sig_ss7_play_tone(p, SIG_SS7_TONE_RINGTONE); if (ast_channel_state(chan) != AST_STATE_UP && ast_channel_state(chan) != AST_STATE_RING) { ast_setstate(chan, AST_STATE_RINGING); } break; case AST_CONTROL_PROCEEDING: ast_debug(1,"Received AST_CONTROL_PROCEEDING on %s\n",ast_channel_name(chan)); ss7_grab(p, p->ss7); /* This IF sends the FAR for an answered ALEG call */ if (ast_channel_state(chan) == AST_STATE_UP && (p->rlt != 1)){ if ((isup_far(p->ss7->ss7, p->ss7call)) != -1) { p->rlt = 1; } } if (p->call_level < SIG_SS7_CALL_LEVEL_PROCEEDING && !p->outgoing) { p->call_level = SIG_SS7_CALL_LEVEL_PROCEEDING; isup_acm(p->ss7->ss7, p->ss7call); } ss7_rel(p->ss7); /* don't continue in ast_indicate */ res = 0; break; case AST_CONTROL_PROGRESS: ast_debug(1,"Received AST_CONTROL_PROGRESS on %s\n",ast_channel_name(chan)); ss7_grab(p, p->ss7); if (!p->progress && p->call_level < SIG_SS7_CALL_LEVEL_ALERTING && !p->outgoing) { p->progress = 1; /* No need to send inband-information progress again. */ isup_cpg(p->ss7->ss7, p->ss7call, CPG_EVENT_INBANDINFO); /* enable echo canceler here on SS7 calls */ if (!p->echocontrol_ind || !(p->ss7->flags & LINKSET_FLAG_USEECHOCONTROL)) { sig_ss7_set_echocanceller(p, 1); } } ss7_rel(p->ss7); /* don't continue in ast_indicate */ res = 0; break; case AST_CONTROL_INCOMPLETE: if (p->call_level < SIG_SS7_CALL_LEVEL_CONNECT) { ast_channel_hangupcause_set(chan, AST_CAUSE_INVALID_NUMBER_FORMAT); ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); res = 0; break; } /* Wait for DTMF digits to complete the dialed number. */ res = 0; break; case AST_CONTROL_CONGESTION: if (p->call_level < SIG_SS7_CALL_LEVEL_CONNECT) { ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION); ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); res = 0; break; } res = sig_ss7_play_tone(p, SIG_SS7_TONE_CONGESTION); break; case AST_CONTROL_HOLD: ast_moh_start(chan, data, p->mohinterpret); break; case AST_CONTROL_UNHOLD: ast_moh_stop(chan); break; case AST_CONTROL_SRCUPDATE: res = 0; break; case AST_CONTROL_CONNECTED_LINE: ss7_connected_line_update(p, ast_channel_connected(chan)); res = 0; break; case AST_CONTROL_REDIRECTING: ss7_redirecting_update(p, chan); res = 0; break; case -1: res = sig_ss7_play_tone(p, -1); break; } return res; } /*! * \brief SS7 channel request. * \since 1.8 * * \param p Signaling private structure pointer. * \param law Companding law preferred * \param requestor Asterisk channel requesting a channel to dial (Can be NULL) * \param transfercapability * * \retval ast_channel on success. * \retval NULL on error. */ struct ast_channel *sig_ss7_request(struct sig_ss7_chan *p, enum sig_ss7_law law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int transfercapability) { struct ast_channel *ast; /* Companding law is determined by SS7 signaling type. */ if (p->ss7->type == SS7_ITU) { law = SIG_SS7_ALAW; } else { law = SIG_SS7_ULAW; } sig_ss7_set_outgoing(p, 1); ast = sig_ss7_new_ast_channel(p, AST_STATE_RESERVED, law, transfercapability, p->exten, assignedids, requestor); if (!ast) { sig_ss7_set_outgoing(p, 0); /* Release the allocated channel. Only have to deal with the linkset lock. */ ast_mutex_lock(&p->ss7->lock); p->call_level = SIG_SS7_CALL_LEVEL_IDLE; isup_free_call(p->ss7->ss7, p->ss7call); ast_mutex_unlock(&p->ss7->lock); } return ast; } /*! * \brief Delete the sig_ss7 private channel structure. * \since 1.8 * * \param doomed sig_ss7 private channel structure to delete. * * \return Nothing */ void sig_ss7_chan_delete(struct sig_ss7_chan *doomed) { ast_free(doomed); } #define SIG_SS7_SC_HEADER "%-4s %4s %-4s %-3s %-3s %-10s %-4s %s\n" #define SIG_SS7_SC_LINE "%4d %4d %-4s %-3s %-3s %-10s %-4s %s" void sig_ss7_cli_show_channels_header(int fd) { ast_cli(fd, SIG_SS7_SC_HEADER, "link", "", "Chan", "Lcl", "Rem", "Call", "SS7", "Channel"); ast_cli(fd, SIG_SS7_SC_HEADER, "set", "Chan", "Idle", "Blk", "Blk", "Level", "Call", "Name"); } void sig_ss7_cli_show_channels(int fd, struct sig_ss7_linkset *linkset) { char line[256]; int idx; struct sig_ss7_chan *pvt; ast_mutex_lock(&linkset->lock); for (idx = 0; idx < linkset->numchans; ++idx) { if (!linkset->pvts[idx]) { continue; } pvt = linkset->pvts[idx]; sig_ss7_lock_private(pvt); sig_ss7_lock_owner(linkset, idx); snprintf(line, sizeof(line), SIG_SS7_SC_LINE, linkset->span, pvt->channel, sig_ss7_is_chan_available(pvt) ? "Yes" : "No", pvt->locallyblocked ? "Yes" : "No", pvt->remotelyblocked ? "Yes" : "No", sig_ss7_call_level2str(pvt->call_level), pvt->ss7call ? "Yes" : "No", pvt->owner ? ast_channel_name(pvt->owner) : ""); if (pvt->owner) { ast_channel_unlock(pvt->owner); } sig_ss7_unlock_private(pvt); ast_mutex_unlock(&linkset->lock); ast_cli(fd, "%s\n", line); ast_mutex_lock(&linkset->lock); } ast_mutex_unlock(&linkset->lock); } /*! * \brief Create a new sig_ss7 private channel structure. * \since 1.8 * * \param pvt_data Upper layer private data structure. * \param ss7 Controlling linkset for the channel. * * \retval sig_ss7_chan on success. * \retval NULL on error. */ struct sig_ss7_chan *sig_ss7_chan_new(void *pvt_data, struct sig_ss7_linkset *ss7) { struct sig_ss7_chan *pvt; pvt = ast_calloc(1, sizeof(*pvt)); if (!pvt) { return pvt; } pvt->chan_pvt = pvt_data; pvt->ss7 = ss7; return pvt; } /*! * \brief Initialize the SS7 linkset control. * \since 1.8 * * \param ss7 SS7 linkset control structure. * * \return Nothing */ void sig_ss7_init_linkset(struct sig_ss7_linkset *ss7) { int idx; memset(ss7, 0, sizeof(*ss7)); ast_mutex_init(&ss7->lock); ss7->master = AST_PTHREADT_NULL; for (idx = 0; idx < ARRAY_LEN(ss7->fds); ++idx) { ss7->fds[idx] = -1; } } /* ------------------------------------------------------------------- */ #endif /* defined(HAVE_SS7) */ /* end sig_ss7.c */ asterisk-13.1.0/channels/chan_oss.c0000644000000000000000000013046212364505025015663 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2007, Digium, Inc. * * Mark Spencer * * FreeBSD changes and multiple device support by Luigi Rizzo, 2005.05.25 * note-this code best seen with ts=8 (8-spaces tabs) in the editor * * 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. */ // #define HAVE_VIDEO_CONSOLE // uncomment to enable video /*! \file * * \brief Channel driver for OSS sound cards * * \author Mark Spencer * \author Luigi Rizzo * * \ingroup channel_drivers */ /*! \li \ref chan_oss.c uses the configuration file \ref oss.conf * \addtogroup configuration_file */ /*! \page oss.conf oss.conf * \verbinclude oss.conf.sample */ /*** MODULEINFO oss extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") #include /* isalnum() used here */ #include #include #ifdef __linux #include #elif defined(__FreeBSD__) || defined(__CYGWIN__) || defined(__GLIBC__) || defined(__sun) #include #else #include #endif #include "asterisk/channel.h" #include "asterisk/file.h" #include "asterisk/callerid.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/cli.h" #include "asterisk/causes.h" #include "asterisk/musiconhold.h" #include "asterisk/app.h" #include "asterisk/bridge.h" #include "asterisk/format_cache.h" #include "console_video.h" /*! Global jitterbuffer configuration - by default, jb is disabled * \note Values shown here match the defaults shown in oss.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; /* * Basic mode of operation: * * we have one keyboard (which receives commands from the keyboard) * and multiple headset's connected to audio cards. * Cards/Headsets are named as the sections of oss.conf. * The section called [general] contains the default parameters. * * At any time, the keyboard is attached to one card, and you * can switch among them using the command 'console foo' * where 'foo' is the name of the card you want. * * oss.conf parameters are START_CONFIG [general] ; General config options, with default values shown. ; You should use one section per device, with [general] being used ; for the first device and also as a template for other devices. ; ; All but 'debug' can go also in the device-specific sections. ; ; debug = 0x0 ; misc debug flags, default is 0 ; Set the device to use for I/O ; device = /dev/dsp ; Optional mixer command to run upon startup (e.g. to set ; volume levels, mutes, etc. ; mixer = ; Software mic volume booster (or attenuator), useful for sound ; cards or microphones with poor sensitivity. The volume level ; is in dB, ranging from -20.0 to +20.0 ; boost = n ; mic volume boost in dB ; Set the callerid for outgoing calls ; callerid = John Doe <555-1234> ; autoanswer = no ; no autoanswer on call ; autohangup = yes ; hangup when other party closes ; extension = s ; default extension to call ; context = default ; default context for outgoing calls ; language = "" ; default language ; Default Music on Hold class to use when this channel is placed on hold in ; the case that the music class is not 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 class to use. ; ; mohinterpret=default ; If you set overridecontext to 'yes', then the whole dial string ; will be interpreted as an extension, which is extremely useful ; to dial SIP, IAX and other extensions which use the '@' character. ; The default is 'no' just for backward compatibility, but the ; suggestion is to change it. ; overridecontext = no ; if 'no', the last @ will start the context ; if 'yes' the whole string is an extension. ; low level device parameters in case you have problems with the ; device driver on your operating system. You should not touch these ; unless you know what you are doing. ; queuesize = 10 ; frames in device driver ; frags = 8 ; argument to SETFRAGMENT ;------------------------------ JITTER BUFFER CONFIGURATION -------------------------- ; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of an ; OSS 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 OSS channel can't accept jitter, ; thus an enabled jitterbuffer on the receive OSS side will always ; be used if the sending side can create jitter. ; 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, usualy sent from exotic devices ; and programs. Defaults to 1000. ; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of an OSS ; channel. Two implementations are currenlty available - "fixed" ; (with size always equals to jbmax-size) and "adaptive" (with ; variable size, actually the new jb of IAX2). Defaults to fixed. ; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no". ;----------------------------------------------------------------------------------- [card1] ; device = /dev/dsp1 ; alternate device END_CONFIG .. and so on for the other cards. */ /* * The following parameters are used in the driver: * * FRAME_SIZE the size of an audio frame, in samples. * 160 is used almost universally, so you should not change it. * * FRAGS the argument for the SETFRAGMENT ioctl. * Overridden by the 'frags' parameter in oss.conf * * Bits 0-7 are the base-2 log of the device's block size, * bits 16-31 are the number of blocks in the driver's queue. * There are a lot of differences in the way this parameter * is supported by different drivers, so you may need to * experiment a bit with the value. * A good default for linux is 30 blocks of 64 bytes, which * results in 6 frames of 320 bytes (160 samples). * FreeBSD works decently with blocks of 256 or 512 bytes, * leaving the number unspecified. * Note that this only refers to the device buffer size, * this module will then try to keep the lenght of audio * buffered within small constraints. * * QUEUE_SIZE The max number of blocks actually allowed in the device * driver's buffer, irrespective of the available number. * Overridden by the 'queuesize' parameter in oss.conf * * Should be >=2, and at most as large as the hw queue above * (otherwise it will never be full). */ #define FRAME_SIZE 160 #define QUEUE_SIZE 10 #if defined(__FreeBSD__) #define FRAGS 0x8 #else #define FRAGS ( ( (6 * 5) << 16 ) | 0x6 ) #endif /* * XXX text message sizes are probably 256 chars, but i am * not sure if there is a suitable definition anywhere. */ #define TEXT_SIZE 256 #if 0 #define TRYOPEN 1 /* try to open on startup */ #endif #define O_CLOSE 0x444 /* special 'close' mode for device */ /* Which device to use */ #if defined( __OpenBSD__ ) || defined( __NetBSD__ ) #define DEV_DSP "/dev/audio" #else #define DEV_DSP "/dev/dsp" #endif static char *config = "oss.conf"; /* default config file */ static int oss_debug; /*! * \brief descriptor for one of our channels. * * There is one used for 'default' values (from the [general] entry in * the configuration file), and then one instance for each device * (the default is cloned from [general], others are only created * if the relevant section exists). */ struct chan_oss_pvt { struct chan_oss_pvt *next; char *name; int total_blocks; /*!< total blocks in the output device */ int sounddev; enum { M_UNSET, M_FULL, M_READ, M_WRITE } duplex; int autoanswer; /*!< Boolean: whether to answer the immediately upon calling */ int autohangup; /*!< Boolean: whether to hangup the call when the remote end hangs up */ int hookstate; /*!< Boolean: 1 if offhook; 0 if onhook */ char *mixer_cmd; /*!< initial command to issue to the mixer */ unsigned int queuesize; /*!< max fragments in queue */ unsigned int frags; /*!< parameter for SETFRAGMENT */ int warned; /*!< various flags used for warnings */ #define WARN_used_blocks 1 #define WARN_speed 2 #define WARN_frag 4 int w_errors; /*!< overfull in the write path */ struct timeval lastopen; int overridecontext; int mute; /*! boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must * be representable in 16 bits to avoid overflows. */ #define BOOST_SCALE (1<<9) #define BOOST_MAX 40 /*!< slightly less than 7 bits */ int boost; /*!< input boost, scaled by BOOST_SCALE */ char device[64]; /*!< device to open */ pthread_t sthread; struct ast_channel *owner; struct video_desc *env; /*!< parameters for video support */ char ext[AST_MAX_EXTENSION]; char ctx[AST_MAX_CONTEXT]; char language[MAX_LANGUAGE]; char cid_name[256]; /*!< Initial CallerID name */ char cid_num[256]; /*!< Initial CallerID number */ char mohinterpret[MAX_MUSICCLASS]; /*! buffers used in oss_write */ char oss_write_buf[FRAME_SIZE * 2]; int oss_write_dst; /*! buffers used in oss_read - AST_FRIENDLY_OFFSET space for headers * plus enough room for a full frame */ char oss_read_buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET]; int readpos; /*!< read position above */ struct ast_frame read_f; /*!< returned by oss_read */ }; /*! forward declaration */ static struct chan_oss_pvt *find_desc(const char *dev); static char *oss_active; /*!< the active device */ /*! \brief return the pointer to the video descriptor */ struct video_desc *get_video_desc(struct ast_channel *c) { struct chan_oss_pvt *o = c ? ast_channel_tech_pvt(c) : find_desc(oss_active); return o ? o->env : NULL; } static struct chan_oss_pvt oss_default = { .sounddev = -1, .duplex = M_UNSET, /* XXX check this */ .autoanswer = 1, .autohangup = 1, .queuesize = QUEUE_SIZE, .frags = FRAGS, .ext = "s", .ctx = "default", .readpos = AST_FRIENDLY_OFFSET, /* start here on reads */ .lastopen = { 0, 0 }, .boost = BOOST_SCALE, }; static int setformat(struct chan_oss_pvt *o, int mode); static struct ast_channel *oss_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int oss_digit_begin(struct ast_channel *c, char digit); static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration); static int oss_text(struct ast_channel *c, const char *text); static int oss_hangup(struct ast_channel *c); static int oss_answer(struct ast_channel *c); static struct ast_frame *oss_read(struct ast_channel *chan); static int oss_call(struct ast_channel *c, const char *dest, int timeout); static int oss_write(struct ast_channel *chan, struct ast_frame *f); static int oss_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen); static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static char tdesc[] = "OSS Console Channel Driver"; /* cannot do const because need to update some fields at runtime */ static struct ast_channel_tech oss_tech = { .type = "Console", .description = tdesc, .requester = oss_request, .send_digit_begin = oss_digit_begin, .send_digit_end = oss_digit_end, .send_text = oss_text, .hangup = oss_hangup, .answer = oss_answer, .read = oss_read, .call = oss_call, .write = oss_write, .write_video = console_write_video, .indicate = oss_indicate, .fixup = oss_fixup, }; /*! * \brief returns a pointer to the descriptor with the given name */ static struct chan_oss_pvt *find_desc(const char *dev) { struct chan_oss_pvt *o = NULL; if (!dev) ast_log(LOG_WARNING, "null dev\n"); for (o = oss_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next); if (!o) ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--"); return o; } /* ! * \brief split a string in extension-context, returns pointers to malloc'ed * strings. * * If we do not have 'overridecontext' then the last @ is considered as * a context separator, and the context is overridden. * This is usually not very necessary as you can play with the dialplan, * and it is nice not to need it because you have '@' in SIP addresses. * * \return the buffer address. */ static char *ast_ext_ctx(const char *src, char **ext, char **ctx) { struct chan_oss_pvt *o = find_desc(oss_active); if (ext == NULL || ctx == NULL) return NULL; /* error */ *ext = *ctx = NULL; if (src && *src != '\0') *ext = ast_strdup(src); if (*ext == NULL) return NULL; if (!o->overridecontext) { /* parse from the right */ *ctx = strrchr(*ext, '@'); if (*ctx) *(*ctx)++ = '\0'; } return *ext; } /*! * \brief Returns the number of blocks used in the audio output channel */ static int used_blocks(struct chan_oss_pvt *o) { struct audio_buf_info info; if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) { if (!(o->warned & WARN_used_blocks)) { ast_log(LOG_WARNING, "Error reading output space\n"); o->warned |= WARN_used_blocks; } return 1; } if (o->total_blocks == 0) { if (0) /* debugging */ ast_log(LOG_WARNING, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments); o->total_blocks = info.fragments; } return o->total_blocks - info.fragments; } /*! Write an exactly FRAME_SIZE sized frame */ static int soundcard_writeframe(struct chan_oss_pvt *o, short *data) { int res; if (o->sounddev < 0) setformat(o, O_RDWR); if (o->sounddev < 0) return 0; /* not fatal */ /* * Nothing complex to manage the audio device queue. * If the buffer is full just drop the extra, otherwise write. * XXX in some cases it might be useful to write anyways after * a number of failures, to restart the output chain. */ res = used_blocks(o); if (res > o->queuesize) { /* no room to write a block */ if (o->w_errors++ == 0 && (oss_debug & 0x4)) ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors); return 0; } o->w_errors = 0; return write(o->sounddev, (void *)data, FRAME_SIZE * 2); } /*! * reset and close the device if opened, * then open and initialize it in the desired mode, * trigger reads and writes so we can start using it. */ static int setformat(struct chan_oss_pvt *o, int mode) { int fmt, desired, res, fd; if (o->sounddev >= 0) { ioctl(o->sounddev, SNDCTL_DSP_RESET, 0); close(o->sounddev); o->duplex = M_UNSET; o->sounddev = -1; } if (mode == O_CLOSE) /* we are done */ return 0; if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000) return -1; /* don't open too often */ o->lastopen = ast_tvnow(); fd = o->sounddev = open(o->device, mode | O_NONBLOCK); if (fd < 0) { ast_log(LOG_WARNING, "Unable to re-open DSP device %s: %s\n", o->device, strerror(errno)); return -1; } if (o->owner) ast_channel_set_fd(o->owner, 0, fd); #if __BYTE_ORDER == __LITTLE_ENDIAN fmt = AFMT_S16_LE; #else fmt = AFMT_S16_BE; #endif res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt); if (res < 0) { ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n"); return -1; } switch (mode) { case O_RDWR: res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); /* Check to see if duplex set (FreeBSD Bug) */ res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt); if (res == 0 && (fmt & DSP_CAP_DUPLEX)) { ast_verb(2, "Console is full duplex\n"); o->duplex = M_FULL; }; break; case O_WRONLY: o->duplex = M_WRITE; break; case O_RDONLY: o->duplex = M_READ; break; } fmt = 0; res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt); if (res < 0) { ast_log(LOG_WARNING, "Failed to set audio device to mono\n"); return -1; } fmt = desired = DEFAULT_SAMPLE_RATE; /* 8000 Hz desired */ res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt); if (res < 0) { ast_log(LOG_WARNING, "Failed to set sample rate to %d\n", desired); return -1; } if (fmt != desired) { if (!(o->warned & WARN_speed)) { ast_log(LOG_WARNING, "Requested %d Hz, got %d Hz -- sound may be choppy\n", desired, fmt); o->warned |= WARN_speed; } } /* * on Freebsd, SETFRAGMENT does not work very well on some cards. * Default to use 256 bytes, let the user override */ if (o->frags) { fmt = o->frags; res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt); if (res < 0) { if (!(o->warned & WARN_frag)) { ast_log(LOG_WARNING, "Unable to set fragment size -- sound may be choppy\n"); o->warned |= WARN_frag; } } } /* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */ res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT; res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res); /* it may fail if we are in half duplex, never mind */ return 0; } /* * some of the standard methods supported by channels. */ static int oss_digit_begin(struct ast_channel *c, char digit) { return 0; } static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration) { /* no better use for received digits than print them */ ast_verbose(" << Console Received digit %c of duration %u ms >> \n", digit, duration); return 0; } static int oss_text(struct ast_channel *c, const char *text) { /* print received messages */ ast_verbose(" << Console Received text %s >> \n", text); return 0; } /*! * \brief handler for incoming calls. Either autoanswer, or start ringing */ static int oss_call(struct ast_channel *c, const char *dest, int timeout) { struct chan_oss_pvt *o = ast_channel_tech_pvt(c); struct ast_frame f = { AST_FRAME_CONTROL, }; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(name); AST_APP_ARG(flags); ); char *parse = ast_strdupa(dest); AST_NONSTANDARD_APP_ARGS(args, parse, '/'); ast_verbose(" << Call to device '%s' dnid '%s' rdnis '%s' on console from '%s' <%s> >>\n", dest, S_OR(ast_channel_dialed(c)->number.str, ""), S_COR(ast_channel_redirecting(c)->from.number.valid, ast_channel_redirecting(c)->from.number.str, ""), S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, ""), S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "")); if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "answer") == 0) { f.subclass.integer = AST_CONTROL_ANSWER; ast_queue_frame(c, &f); } else if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "noanswer") == 0) { f.subclass.integer = AST_CONTROL_RINGING; ast_queue_frame(c, &f); ast_indicate(c, AST_CONTROL_RINGING); } else if (o->autoanswer) { ast_verbose(" << Auto-answered >> \n"); f.subclass.integer = AST_CONTROL_ANSWER; ast_queue_frame(c, &f); o->hookstate = 1; } else { ast_verbose("<< Type 'answer' to answer, or use 'autoanswer' for future calls >> \n"); f.subclass.integer = AST_CONTROL_RINGING; ast_queue_frame(c, &f); ast_indicate(c, AST_CONTROL_RINGING); } return 0; } /*! * \brief remote side answered the phone */ static int oss_answer(struct ast_channel *c) { struct chan_oss_pvt *o = ast_channel_tech_pvt(c); ast_verbose(" << Console call has been answered >> \n"); ast_setstate(c, AST_STATE_UP); o->hookstate = 1; return 0; } static int oss_hangup(struct ast_channel *c) { struct chan_oss_pvt *o = ast_channel_tech_pvt(c); ast_channel_tech_pvt_set(c, NULL); o->owner = NULL; ast_verbose(" << Hangup on console >> \n"); console_video_uninit(o->env); ast_module_unref(ast_module_info->self); if (o->hookstate) { if (o->autoanswer || o->autohangup) { /* Assume auto-hangup too */ o->hookstate = 0; setformat(o, O_CLOSE); } } return 0; } /*! \brief used for data coming from the network */ static int oss_write(struct ast_channel *c, struct ast_frame *f) { int src; struct chan_oss_pvt *o = ast_channel_tech_pvt(c); /* * we could receive a block which is not a multiple of our * FRAME_SIZE, so buffer it locally and write to the device * in FRAME_SIZE chunks. * Keep the residue stored for future use. */ src = 0; /* read position into f->data */ while (src < f->datalen) { /* Compute spare room in the buffer */ int l = sizeof(o->oss_write_buf) - o->oss_write_dst; if (f->datalen - src >= l) { /* enough to fill a frame */ memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l); soundcard_writeframe(o, (short *) o->oss_write_buf); src += l; o->oss_write_dst = 0; } else { /* copy residue */ l = f->datalen - src; memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l); src += l; /* but really, we are done */ o->oss_write_dst += l; } } return 0; } static struct ast_frame *oss_read(struct ast_channel *c) { int res; struct chan_oss_pvt *o = ast_channel_tech_pvt(c); struct ast_frame *f = &o->read_f; /* XXX can be simplified returning &ast_null_frame */ /* prepare a NULL frame in case we don't have enough data to return */ memset(f, '\0', sizeof(struct ast_frame)); f->frametype = AST_FRAME_NULL; f->src = oss_tech.type; res = read(o->sounddev, o->oss_read_buf + o->readpos, sizeof(o->oss_read_buf) - o->readpos); if (res < 0) /* audio data not ready, return a NULL frame */ return f; o->readpos += res; if (o->readpos < sizeof(o->oss_read_buf)) /* not enough samples */ return f; if (o->mute) return f; o->readpos = AST_FRIENDLY_OFFSET; /* reset read pointer for next frame */ if (ast_channel_state(c) != AST_STATE_UP) /* drop data if frame is not up */ return f; /* ok we can build and deliver the frame to the caller */ f->frametype = AST_FRAME_VOICE; f->subclass.format = ao2_bump(ast_format_slin); f->samples = FRAME_SIZE; f->datalen = FRAME_SIZE * 2; f->data.ptr = o->oss_read_buf + AST_FRIENDLY_OFFSET; if (o->boost != BOOST_SCALE) { /* scale and clip values */ int i, x; int16_t *p = (int16_t *) f->data.ptr; for (i = 0; i < f->samples; i++) { x = (p[i] * o->boost) / BOOST_SCALE; if (x > 32767) x = 32767; else if (x < -32768) x = -32768; p[i] = x; } } f->offset = AST_FRIENDLY_OFFSET; return f; } static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct chan_oss_pvt *o = ast_channel_tech_pvt(newchan); o->owner = newchan; return 0; } static int oss_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen) { struct chan_oss_pvt *o = ast_channel_tech_pvt(c); int res = 0; switch (cond) { case AST_CONTROL_INCOMPLETE: case AST_CONTROL_BUSY: case AST_CONTROL_CONGESTION: case AST_CONTROL_RINGING: case AST_CONTROL_PVT_CAUSE_CODE: case -1: res = -1; break; case AST_CONTROL_PROGRESS: case AST_CONTROL_PROCEEDING: case AST_CONTROL_VIDUPDATE: case AST_CONTROL_SRCUPDATE: break; case AST_CONTROL_HOLD: ast_verbose(" << Console Has Been Placed on Hold >> \n"); ast_moh_start(c, data, o->mohinterpret); break; case AST_CONTROL_UNHOLD: ast_verbose(" << Console Has Been Retrieved from Hold >> \n"); ast_moh_stop(c); break; default: ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, ast_channel_name(c)); return -1; } return res; } /*! * \brief allocate a new channel. */ static struct ast_channel *oss_new(struct chan_oss_pvt *o, char *ext, char *ctx, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_channel *c; c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, assignedids, requestor, 0, "Console/%s", o->device + 5); if (c == NULL) return NULL; ast_channel_tech_set(c, &oss_tech); if (o->sounddev < 0) setformat(o, O_RDWR); ast_channel_set_fd(c, 0, o->sounddev); /* -1 if device closed, override later */ ast_channel_set_readformat(c, ast_format_slin); ast_channel_set_writeformat(c, ast_format_slin); ast_channel_nativeformats_set(c, oss_tech.capabilities); /* if the console makes the call, add video to the offer */ /* if (state == AST_STATE_RINGING) TODO XXX CONSOLE VIDEO IS DISABLED UNTIL IT GETS A MAINTAINER c->nativeformats |= console_video_formats; */ ast_channel_tech_pvt_set(c, o); if (!ast_strlen_zero(o->language)) ast_channel_language_set(c, o->language); /* Don't use ast_set_callerid() here because it will * generate a needless NewCallerID event */ if (!ast_strlen_zero(o->cid_num)) { ast_channel_caller(c)->ani.number.valid = 1; ast_channel_caller(c)->ani.number.str = ast_strdup(o->cid_num); } if (!ast_strlen_zero(ext)) { ast_channel_dialed(c)->number.str = ast_strdup(ext); } o->owner = c; ast_module_ref(ast_module_info->self); ast_jb_configure(c, &global_jbconf); ast_channel_unlock(c); if (state != AST_STATE_DOWN) { if (ast_pbx_start(c)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(c)); ast_hangup(c); o->owner = c = NULL; } } console_video_start(get_video_desc(c), c); /* XXX cleanup */ return c; } static struct ast_channel *oss_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct ast_channel *c; struct chan_oss_pvt *o; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(name); AST_APP_ARG(flags); ); char *parse = ast_strdupa(data); AST_NONSTANDARD_APP_ARGS(args, parse, '/'); o = find_desc(args.name); ast_log(LOG_WARNING, "oss_request ty <%s> data 0x%p <%s>\n", type, data, data); if (o == NULL) { ast_log(LOG_NOTICE, "Device %s not found\n", args.name); /* XXX we could default to 'dsp' perhaps ? */ return NULL; } if (ast_format_cap_iscompatible_format(cap, ast_format_slin) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *codec_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Format %s unsupported\n", ast_format_cap_get_names(cap, &codec_buf)); return NULL; } if (o->owner) { ast_log(LOG_NOTICE, "Already have a call (chan %p) on the OSS channel\n", o->owner); *cause = AST_CAUSE_BUSY; return NULL; } c = oss_new(o, NULL, NULL, AST_STATE_DOWN, assignedids, requestor); if (c == NULL) { ast_log(LOG_WARNING, "Unable to create new OSS channel\n"); return NULL; } return c; } static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value); /*! Generic console command handler. Basically a wrapper for a subset * of config file options which are also available from the CLI */ static char *console_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); const char *var, *value; switch (cmd) { case CLI_INIT: e->command = CONSOLE_VIDEO_CMDS; e->usage = "Usage: " CONSOLE_VIDEO_CMDS "...\n" " Generic handler for console commands.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < e->args) return CLI_SHOWUSAGE; if (o == NULL) { ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n", oss_active); return CLI_FAILURE; } var = a->argv[e->args-1]; value = a->argc > e->args ? a->argv[e->args] : NULL; if (value) /* handle setting */ store_config_core(o, var, value); if (!console_video_cli(o->env, var, a->fd)) /* print video-related values */ return CLI_SUCCESS; /* handle other values */ if (!strcasecmp(var, "device")) { ast_cli(a->fd, "device is [%s]\n", o->device); } return CLI_SUCCESS; } static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); switch (cmd) { case CLI_INIT: e->command = "console {set|show} autoanswer [on|off]"; e->usage = "Usage: console {set|show} autoanswer [on|off]\n" " Enables or disables autoanswer feature. If used without\n" " argument, displays the current on/off status of autoanswer.\n" " The default value of autoanswer is in 'oss.conf'.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == e->args - 1) { ast_cli(a->fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off"); return CLI_SUCCESS; } if (a->argc != e->args) return CLI_SHOWUSAGE; if (o == NULL) { ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n", oss_active); return CLI_FAILURE; } if (!strcasecmp(a->argv[e->args-1], "on")) o->autoanswer = 1; else if (!strcasecmp(a->argv[e->args - 1], "off")) o->autoanswer = 0; else return CLI_SHOWUSAGE; return CLI_SUCCESS; } /*! \brief helper function for the answer key/cli command */ static char *console_do_answer(int fd) { struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } }; struct chan_oss_pvt *o = find_desc(oss_active); if (!o->owner) { if (fd > -1) ast_cli(fd, "No one is calling us\n"); return CLI_FAILURE; } o->hookstate = 1; ast_queue_frame(o->owner, &f); return CLI_SUCCESS; } /*! * \brief answer command from the console */ static char *console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "console answer"; e->usage = "Usage: console answer\n" " Answers an incoming call on the console (OSS) channel.\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } if (a->argc != e->args) return CLI_SHOWUSAGE; return console_do_answer(a->fd); } /*! * \brief Console send text CLI command * * \note concatenate all arguments into a single string. argv is NULL-terminated * so we can use it right away */ static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); char buf[TEXT_SIZE]; if (cmd == CLI_INIT) { e->command = "console send text"; e->usage = "Usage: console send text \n" " Sends a text message for display on the remote terminal.\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc < e->args + 1) return CLI_SHOWUSAGE; if (!o->owner) { ast_cli(a->fd, "Not in a call\n"); return CLI_FAILURE; } ast_join(buf, sizeof(buf) - 1, a->argv + e->args); if (!ast_strlen_zero(buf)) { struct ast_frame f = { 0, }; int i = strlen(buf); buf[i] = '\n'; f.frametype = AST_FRAME_TEXT; f.subclass.integer = 0; f.data.ptr = buf; f.datalen = i + 1; ast_queue_frame(o->owner, &f); } return CLI_SUCCESS; } static char *console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); if (cmd == CLI_INIT) { e->command = "console hangup"; e->usage = "Usage: console hangup\n" " Hangs up any call currently placed on the console.\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc != e->args) return CLI_SHOWUSAGE; if (!o->owner && !o->hookstate) { /* XXX maybe only one ? */ ast_cli(a->fd, "No call to hang up\n"); return CLI_FAILURE; } o->hookstate = 0; if (o->owner) ast_queue_hangup_with_cause(o->owner, AST_CAUSE_NORMAL_CLEARING); setformat(o, O_CLOSE); return CLI_SUCCESS; } static char *console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_FLASH } }; struct chan_oss_pvt *o = find_desc(oss_active); if (cmd == CLI_INIT) { e->command = "console flash"; e->usage = "Usage: console flash\n" " Flashes the call currently placed on the console.\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc != e->args) return CLI_SHOWUSAGE; if (!o->owner) { /* XXX maybe !o->hookstate too ? */ ast_cli(a->fd, "No call to flash\n"); return CLI_FAILURE; } o->hookstate = 0; if (o->owner) ast_queue_frame(o->owner, &f); return CLI_SUCCESS; } static char *console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char *s = NULL; char *mye = NULL, *myc = NULL; struct chan_oss_pvt *o = find_desc(oss_active); if (cmd == CLI_INIT) { e->command = "console dial"; e->usage = "Usage: console dial [extension[@context]]\n" " Dials a given extension (and context if specified)\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc > e->args + 1) return CLI_SHOWUSAGE; if (o->owner) { /* already in a call */ int i; struct ast_frame f = { AST_FRAME_DTMF, { 0 } }; const char *digits; if (a->argc == e->args) { /* argument is mandatory here */ ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n"); return CLI_FAILURE; } digits = a->argv[e->args]; /* send the string one char at a time */ for (i = 0; i < strlen(digits); i++) { f.subclass.integer = digits[i]; ast_queue_frame(o->owner, &f); } return CLI_SUCCESS; } /* if we have an argument split it into extension and context */ if (a->argc == e->args + 1) s = ast_ext_ctx(a->argv[e->args], &mye, &myc); /* supply default values if needed */ if (mye == NULL) mye = o->ext; if (myc == NULL) myc = o->ctx; if (ast_exists_extension(NULL, myc, mye, 1, NULL)) { o->hookstate = 1; oss_new(o, mye, myc, AST_STATE_RINGING, NULL, NULL); } else ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc); if (s) ast_free(s); return CLI_SUCCESS; } static char *console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); const char *s; int toggle = 0; if (cmd == CLI_INIT) { e->command = "console {mute|unmute} [toggle]"; e->usage = "Usage: console {mute|unmute} [toggle]\n" " Mute/unmute the microphone.\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc > e->args) return CLI_SHOWUSAGE; if (a->argc == e->args) { if (strcasecmp(a->argv[e->args-1], "toggle")) return CLI_SHOWUSAGE; toggle = 1; } s = a->argv[e->args-2]; if (!strcasecmp(s, "mute")) o->mute = toggle ? !o->mute : 1; else if (!strcasecmp(s, "unmute")) o->mute = toggle ? !o->mute : 0; else return CLI_SHOWUSAGE; ast_cli(a->fd, "Console mic is %s\n", o->mute ? "off" : "on"); return CLI_SUCCESS; } static char *console_transfer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); char *tmp, *ext, *ctx; switch (cmd) { case CLI_INIT: e->command = "console transfer"; e->usage = "Usage: console transfer [@context]\n" " Transfers the currently connected call to the given extension (and\n" " context if specified)\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; if (o == NULL) return CLI_FAILURE; if (o->owner == NULL || !ast_channel_is_bridged(o->owner)) { ast_cli(a->fd, "There is no call to transfer\n"); return CLI_SUCCESS; } tmp = ast_ext_ctx(a->argv[2], &ext, &ctx); if (ctx == NULL) { /* supply default context if needed */ ctx = ast_strdupa(ast_channel_context(o->owner)); } if (ast_bridge_transfer_blind(1, o->owner, ext, ctx, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) { ast_log(LOG_WARNING, "Unable to transfer call from channel %s\n", ast_channel_name(o->owner)); } ast_free(tmp); return CLI_SUCCESS; } static char *console_active(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "console {set|show} active []"; e->usage = "Usage: console active [device]\n" " If used without a parameter, displays which device is the current\n" " console. If a device is specified, the console sound device is changed to\n" " the device specified.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 3) ast_cli(a->fd, "active console is [%s]\n", oss_active); else if (a->argc != 4) return CLI_SHOWUSAGE; else { struct chan_oss_pvt *o; if (strcmp(a->argv[3], "show") == 0) { for (o = oss_default.next; o; o = o->next) ast_cli(a->fd, "device [%s] exists\n", o->name); return CLI_SUCCESS; } o = find_desc(a->argv[3]); if (o == NULL) ast_cli(a->fd, "No device [%s] exists\n", a->argv[3]); else oss_active = o->name; } return CLI_SUCCESS; } /*! * \brief store the boost factor */ static void store_boost(struct chan_oss_pvt *o, const char *s) { double boost = 0; if (sscanf(s, "%30lf", &boost) != 1) { ast_log(LOG_WARNING, "invalid boost <%s>\n", s); return; } if (boost < -BOOST_MAX) { ast_log(LOG_WARNING, "boost %s too small, using %d\n", s, -BOOST_MAX); boost = -BOOST_MAX; } else if (boost > BOOST_MAX) { ast_log(LOG_WARNING, "boost %s too large, using %d\n", s, BOOST_MAX); boost = BOOST_MAX; } boost = exp(log(10) * boost / 20) * BOOST_SCALE; o->boost = boost; ast_log(LOG_WARNING, "setting boost %s to %d\n", s, o->boost); } static char *console_boost(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_oss_pvt *o = find_desc(oss_active); switch (cmd) { case CLI_INIT: e->command = "console boost"; e->usage = "Usage: console boost [boost in dB]\n" " Sets or display mic boost in dB\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 2) ast_cli(a->fd, "boost currently %5.1f\n", 20 * log10(((double) o->boost / (double) BOOST_SCALE))); else if (a->argc == 3) store_boost(o, a->argv[2]); return CLI_SUCCESS; } static struct ast_cli_entry cli_oss[] = { AST_CLI_DEFINE(console_answer, "Answer an incoming console call"), AST_CLI_DEFINE(console_hangup, "Hangup a call on the console"), AST_CLI_DEFINE(console_flash, "Flash a call on the console"), AST_CLI_DEFINE(console_dial, "Dial an extension on the console"), AST_CLI_DEFINE(console_mute, "Disable/Enable mic input"), AST_CLI_DEFINE(console_transfer, "Transfer a call to a different extension"), AST_CLI_DEFINE(console_cmd, "Generic console command"), AST_CLI_DEFINE(console_sendtext, "Send text to the remote device"), AST_CLI_DEFINE(console_autoanswer, "Sets/displays autoanswer"), AST_CLI_DEFINE(console_boost, "Sets/displays mic boost in dB"), AST_CLI_DEFINE(console_active, "Sets/displays active console"), }; /*! * store the mixer argument from the config file, filtering possibly * invalid or dangerous values (the string is used as argument for * system("mixer %s") */ static void store_mixer(struct chan_oss_pvt *o, const char *s) { int i; for (i = 0; i < strlen(s); i++) { if (!isalnum(s[i]) && strchr(" \t-/", s[i]) == NULL) { ast_log(LOG_WARNING, "Suspect char %c in mixer cmd, ignoring:\n\t%s\n", s[i], s); return; } } if (o->mixer_cmd) ast_free(o->mixer_cmd); o->mixer_cmd = ast_strdup(s); ast_log(LOG_WARNING, "setting mixer %s\n", s); } /*! * store the callerid components */ static void store_callerid(struct chan_oss_pvt *o, const char *s) { ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num)); } static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value) { CV_START(var, value); /* handle jb conf */ if (!ast_jb_read_conf(&global_jbconf, var, value)) return; if (!console_video_config(&o->env, var, value)) return; /* matched there */ CV_BOOL("autoanswer", o->autoanswer); CV_BOOL("autohangup", o->autohangup); CV_BOOL("overridecontext", o->overridecontext); CV_STR("device", o->device); CV_UINT("frags", o->frags); CV_UINT("debug", oss_debug); CV_UINT("queuesize", o->queuesize); CV_STR("context", o->ctx); CV_STR("language", o->language); CV_STR("mohinterpret", o->mohinterpret); CV_STR("extension", o->ext); CV_F("mixer", store_mixer(o, value)); CV_F("callerid", store_callerid(o, value)) ; CV_F("boost", store_boost(o, value)); CV_END; } /*! * grab fields from the config file, init the descriptor and open the device. */ static struct chan_oss_pvt *store_config(struct ast_config *cfg, char *ctg) { struct ast_variable *v; struct chan_oss_pvt *o; if (ctg == NULL) { o = &oss_default; ctg = "general"; } else { if (!(o = ast_calloc(1, sizeof(*o)))) return NULL; *o = oss_default; /* "general" is also the default thing */ if (strcmp(ctg, "general") == 0) { o->name = ast_strdup("dsp"); oss_active = o->name; goto openit; } o->name = ast_strdup(ctg); } strcpy(o->mohinterpret, "default"); o->lastopen = ast_tvnow(); /* don't leave it 0 or tvdiff may wrap */ /* fill other fields from configuration */ for (v = ast_variable_browse(cfg, ctg); v; v = v->next) { store_config_core(o, v->name, v->value); } if (ast_strlen_zero(o->device)) ast_copy_string(o->device, DEV_DSP, sizeof(o->device)); if (o->mixer_cmd) { char *cmd; if (ast_asprintf(&cmd, "mixer %s", o->mixer_cmd) >= 0) { ast_log(LOG_WARNING, "running [%s]\n", cmd); if (system(cmd) < 0) { ast_log(LOG_WARNING, "system() failed: %s\n", strerror(errno)); } ast_free(cmd); } } /* if the config file requested to start the GUI, do it */ if (get_gui_startup(o->env)) console_video_start(o->env, NULL); if (o == &oss_default) /* we are done with the default */ return NULL; openit: #ifdef TRYOPEN if (setformat(o, O_RDWR) < 0) { /* open device */ ast_verb(1, "Device %s not detected\n", ctg); ast_verb(1, "Turn off OSS support by adding " "'noload=chan_oss.so' in /etc/asterisk/modules.conf\n"); goto error; } if (o->duplex != M_FULL) ast_log(LOG_WARNING, "XXX I don't work right with non " "full-duplex sound cards XXX\n"); #endif /* TRYOPEN */ /* link into list of devices */ if (o != &oss_default) { o->next = oss_default.next; oss_default.next = o; } return o; #ifdef TRYOPEN error: if (o != &oss_default) ast_free(o); return NULL; #endif } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { struct ast_config *cfg = NULL; char *ctg = NULL; struct ast_flags config_flags = { 0 }; /* Copy the default jb config over global_jbconf */ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); /* load config file */ if (!(cfg = ast_config_load(config, config_flags))) { ast_log(LOG_NOTICE, "Unable to load config %s\n", config); return AST_MODULE_LOAD_DECLINE; } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Config file %s is in an invalid format. Aborting.\n", config); return AST_MODULE_LOAD_DECLINE; } do { store_config(cfg, ctg); } while ( (ctg = ast_category_browse(cfg, ctg)) != NULL); ast_config_destroy(cfg); if (find_desc(oss_active) == NULL) { ast_log(LOG_NOTICE, "Device %s not found\n", oss_active); /* XXX we could default to 'dsp' perhaps ? */ /* XXX should cleanup allocated memory etc. */ return AST_MODULE_LOAD_FAILURE; } if (!(oss_tech.capabilities = ast_format_cap_alloc(0))) { return AST_MODULE_LOAD_FAILURE; } ast_format_cap_append(oss_tech.capabilities, ast_format_slin, 0); /* TODO XXX CONSOLE VIDEO IS DISABLE UNTIL IT HAS A MAINTAINER * add console_video_formats to oss_tech.capabilities once this occurs. */ if (ast_channel_register(&oss_tech)) { ast_log(LOG_ERROR, "Unable to register channel type 'OSS'\n"); return AST_MODULE_LOAD_DECLINE; } ast_cli_register_multiple(cli_oss, ARRAY_LEN(cli_oss)); return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) { struct chan_oss_pvt *o, *next; ast_channel_unregister(&oss_tech); ast_cli_unregister_multiple(cli_oss, ARRAY_LEN(cli_oss)); o = oss_default.next; while (o) { close(o->sounddev); if (o->owner) ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD); if (o->owner) return -1; next = o->next; ast_free(o->name); ast_free(o); o = next; } ao2_cleanup(oss_tech.capabilities); oss_tech.capabilities = NULL; return 0; } AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "OSS Console Channel Driver"); asterisk-13.1.0/channels/chan_bridge_media.c0000644000000000000000000001435312364505025017452 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2013 Digium, Inc. * * Jonathan Rose * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief Bridge Media Channels driver * * \author Jonathan Rose * \author Richard Mudgett * * \brief Bridge Media Channels * * \ingroup channel_drivers */ /*** MODULEINFO core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") #include "asterisk/channel.h" #include "asterisk/bridge.h" #include "asterisk/core_unreal.h" #include "asterisk/module.h" static int media_call(struct ast_channel *chan, const char *addr, int timeout) { /* ast_call() will fail unconditionally against channels provided by this driver */ return -1; } static int media_hangup(struct ast_channel *ast) { struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast); int res; if (!p) { return -1; } /* Give the pvt a ref to fulfill calling requirements. */ ao2_ref(p, +1); res = ast_unreal_hangup(p, ast); ao2_ref(p, -1); return res; } static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static struct ast_channel *record_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static struct ast_channel_tech announce_tech = { .type = "Announcer", .description = "Bridge Media Announcing Channel Driver", .requester = announce_request, .call = media_call, .hangup = media_hangup, .send_digit_begin = ast_unreal_digit_begin, .send_digit_end = ast_unreal_digit_end, .read = ast_unreal_read, .write = ast_unreal_write, .write_video = ast_unreal_write, .exception = ast_unreal_read, .indicate = ast_unreal_indicate, .fixup = ast_unreal_fixup, .send_html = ast_unreal_sendhtml, .send_text = ast_unreal_sendtext, .queryoption = ast_unreal_queryoption, .setoption = ast_unreal_setoption, .properties = AST_CHAN_TP_INTERNAL, }; static struct ast_channel_tech record_tech = { .type = "Recorder", .description = "Bridge Media Recording Channel Driver", .requester = record_request, .call = media_call, .hangup = media_hangup, .send_digit_begin = ast_unreal_digit_begin, .send_digit_end = ast_unreal_digit_end, .read = ast_unreal_read, .write = ast_unreal_write, .write_video = ast_unreal_write, .exception = ast_unreal_read, .indicate = ast_unreal_indicate, .fixup = ast_unreal_fixup, .send_html = ast_unreal_sendhtml, .send_text = ast_unreal_sendtext, .queryoption = ast_unreal_queryoption, .setoption = ast_unreal_setoption, .properties = AST_CHAN_TP_INTERNAL, }; static struct ast_channel *media_request_helper(struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, struct ast_channel_tech *tech, const char *role) { struct ast_channel *chan; RAII_VAR(struct ast_callid *, callid, NULL, ast_callid_cleanup); RAII_VAR(struct ast_unreal_pvt *, pvt, NULL, ao2_cleanup); if (!(pvt = ast_unreal_alloc(sizeof(*pvt), ast_unreal_destructor, cap))) { return NULL; } ast_copy_string(pvt->name, data, sizeof(pvt->name)); ast_set_flag(pvt, AST_UNREAL_NO_OPTIMIZATION); callid = ast_read_threadstorage_callid(); chan = ast_unreal_new_channels(pvt, tech, AST_STATE_UP, AST_STATE_UP, NULL, NULL, assignedids, requestor, callid); if (!chan) { return NULL; } ast_answer(pvt->owner); ast_answer(pvt->chan); if (ast_channel_add_bridge_role(pvt->chan, role)) { ast_hangup(chan); return NULL; } return chan; } static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { return media_request_helper(cap, assignedids, requestor, data, &announce_tech, "announcer"); } static struct ast_channel *record_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { return media_request_helper(cap, assignedids, requestor, data, &record_tech, "recorder"); } static void cleanup_capabilities(void) { if (announce_tech.capabilities) { ao2_ref(announce_tech.capabilities, -1); announce_tech.capabilities = NULL; } if (record_tech.capabilities) { ao2_ref(record_tech.capabilities, -1); record_tech.capabilities = NULL; } } static int unload_module(void) { ast_channel_unregister(&announce_tech); ast_channel_unregister(&record_tech); cleanup_capabilities(); return 0; } static int load_module(void) { announce_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!announce_tech.capabilities) { return AST_MODULE_LOAD_DECLINE; } record_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!record_tech.capabilities) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append_by_type(announce_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_by_type(record_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN); if (ast_channel_register(&announce_tech)) { ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n", announce_tech.type, announce_tech.description); cleanup_capabilities(); return AST_MODULE_LOAD_DECLINE; } if (ast_channel_register(&record_tech)) { ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n", record_tech.type, record_tech.description); cleanup_capabilities(); return AST_MODULE_LOAD_DECLINE; } return AST_MODULE_LOAD_SUCCESS; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bridge Media Channel Driver", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, ); asterisk-13.1.0/channels/chan_phone.h0000644000000000000000000003504411017334725016176 0ustar rootroot/* * 8-bit raw data * * Source: DialTone.ulaw * * Copyright (C) 1999, Mark Spencer * * Distributed under the terms of the GNU General Public License * */ /*! \file * \brief * 8-bit raw data */ static unsigned char DialTone[] = { 0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa, 0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c, 0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3, 0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47, 0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49, 0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf, 0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24, 0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e, 0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b, 0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c, 0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f, 0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9, 0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41, 0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b, 0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5, 0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b, 0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96, 0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14, 0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95, 0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a, 0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5, 0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43, 0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32, 0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e, 0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16, 0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91, 0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10, 0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92, 0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19, 0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4, 0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a, 0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d, 0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c, 0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15, 0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90, 0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10, 0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93, 0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a, 0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8, 0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a, 0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c, 0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d, 0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16, 0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93, 0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13, 0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97, 0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e, 0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad, 0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9, 0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e, 0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0, 0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b, 0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a, 0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b, 0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e, 0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28, 0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9, 0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0, 0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36, 0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab, 0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27, 0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6, 0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29, 0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae, 0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a, 0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd, 0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7, 0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c, 0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7, 0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a, 0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5, 0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78, 0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d, 0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6, 0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e, 0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64, 0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7, 0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39, 0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf, 0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b, 0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa, 0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c, 0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3, 0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47, 0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49, 0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf, 0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24, 0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e, 0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b, 0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c, 0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f, 0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9, 0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41, 0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b, 0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5, 0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b, 0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96, 0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14, 0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95, 0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a, 0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5, 0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43, 0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32, 0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e, 0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16, 0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91, 0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10, 0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92, 0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19, 0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4, 0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a, 0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d, 0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c, 0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15, 0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90, 0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10, 0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93, 0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a, 0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8, 0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a, 0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c, 0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d, 0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16, 0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93, 0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13, 0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97, 0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e, 0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad, 0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9, 0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e, 0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0, 0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b, 0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a, 0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b, 0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e, 0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28, 0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9, 0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0, 0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36, 0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab, 0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27, 0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6, 0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29, 0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae, 0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a, 0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd, 0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7, 0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c, 0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7, 0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a, 0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5, 0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78, 0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d, 0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6, 0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e, 0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64, 0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7, 0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39, 0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf, 0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b, 0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa, 0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c, 0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3, 0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47, 0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49, 0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf, 0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24, 0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e, 0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b, 0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c, 0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f, 0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9, 0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41, 0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b, 0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5, 0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b, 0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96, 0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14, 0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95, 0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a, 0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5, 0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43, 0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32, 0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e, 0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16, 0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91, 0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10, 0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92, 0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19, 0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4, 0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a, 0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d, 0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c, 0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15, 0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90, 0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10, 0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93, 0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a, 0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8, 0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a, 0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c, 0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d, 0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16, 0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93, 0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13, 0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97, 0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e, 0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad, 0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9, 0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e, 0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0, 0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b, 0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a, 0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b, 0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e, 0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28, 0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9, 0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0, 0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36, 0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab, 0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27, 0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6, 0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29, 0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae, 0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a, 0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd, 0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7, 0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c, 0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7, 0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a, 0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5, 0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78, 0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d, 0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6, 0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e, 0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64, 0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7, 0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39, 0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf, 0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b }; asterisk-13.1.0/channels/chan_dahdi.h0000644000000000000000000007021712347633447016151 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2013 Digium, Inc. * * Richard Mudgett * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief DAHDI internal API definitions. * * \author Richard Mudgett * * See Also: * \arg \ref AstCREDITS */ #ifndef _ASTERISK_CHAN_DAHDI_H #define _ASTERISK_CHAN_DAHDI_H #if defined(HAVE_OPENR2) #include #endif /* defined(HAVE_OPENR2) */ #include #include #include "asterisk/channel.h" #include "asterisk/dsp.h" #include "asterisk/app.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif /* ------------------------------------------------------------------- */ #if defined(HAVE_PRI) struct sig_pri_span; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) struct sig_ss7_linkset; #endif /* defined(HAVE_SS7) */ #define SUB_REAL 0 /*!< Active call */ #define SUB_CALLWAIT 1 /*!< Call-Waiting call on hold */ #define SUB_THREEWAY 2 /*!< Three-way call */ struct distRingData { int ring[3]; int range; }; struct ringContextData { char contextData[AST_MAX_CONTEXT]; }; struct dahdi_distRings { struct distRingData ringnum[3]; struct ringContextData ringContext[3]; }; extern const char * const subnames[]; struct dahdi_subchannel { int dfd; struct ast_channel *owner; int chan; short buffer[AST_FRIENDLY_OFFSET/2 + READ_SIZE]; struct ast_frame f; /*!< One frame for each channel. How did this ever work before? */ unsigned int needringing:1; unsigned int needbusy:1; unsigned int needcongestion:1; unsigned int needanswer:1; unsigned int needflash:1; unsigned int needhold:1; unsigned int needunhold:1; unsigned int linear:1; unsigned int inthreeway:1; struct dahdi_confinfo curconf; }; #define MAX_SLAVES 4 /* States for sending MWI message * First three states are required for send Ring Pulse Alert Signal */ typedef enum { MWI_SEND_NULL = 0, MWI_SEND_SA, MWI_SEND_SA_WAIT, MWI_SEND_PAUSE, MWI_SEND_SPILL, MWI_SEND_CLEANUP, MWI_SEND_DONE, } mwisend_states; struct mwisend_info { struct timeval pause; mwisend_states mwisend_current; }; /*! Specify the lists dahdi_pvt can be put in. */ enum DAHDI_IFLIST { DAHDI_IFLIST_NONE, /*!< The dahdi_pvt is not in any list. */ DAHDI_IFLIST_MAIN, /*!< The dahdi_pvt is in the main interface list */ #if defined(HAVE_PRI) DAHDI_IFLIST_NO_B_CHAN, /*!< The dahdi_pvt is in a no B channel interface list */ #endif /* defined(HAVE_PRI) */ }; struct dahdi_pvt { ast_mutex_t lock; /*!< Channel private lock. */ struct callerid_state *cs; struct ast_channel *owner; /*!< Our current active owner (if applicable) */ /*!< Up to three channels can be associated with this call */ struct dahdi_subchannel sub_unused; /*!< Just a safety precaution */ struct dahdi_subchannel subs[3]; /*!< Sub-channels */ struct dahdi_confinfo saveconf; /*!< Saved conference info */ struct dahdi_pvt *slaves[MAX_SLAVES]; /*!< Slave to us (follows our conferencing) */ struct dahdi_pvt *master; /*!< Master to us (we follow their conferencing) */ int inconference; /*!< If our real should be in the conference */ int bufsize; /*!< Size of the buffers */ int buf_no; /*!< Number of buffers */ int buf_policy; /*!< Buffer policy */ int faxbuf_no; /*!< Number of Fax buffers */ int faxbuf_policy; /*!< Fax buffer policy */ int sig; /*!< Signalling style */ /*! * \brief Nonzero if the signaling type is sent over a radio. * \note Set to a couple of nonzero values but it is only tested like a boolean. */ int radio; int outsigmod; /*!< Outbound Signalling style (modifier) */ int oprmode; /*!< "Operator Services" mode */ struct dahdi_pvt *oprpeer; /*!< "Operator Services" peer tech_pvt ptr */ /*! \brief Hardware Rx gain set by chan_dahdi.conf */ float hwrxgain; /*! \brief Hardware Tx gain set by chan_dahdi.conf */ float hwtxgain; /*! \brief Amount of gain to increase during caller id */ float cid_rxgain; /*! \brief Software Rx gain set by chan_dahdi.conf */ float rxgain; /*! \brief Software Tx gain set by chan_dahdi.conf */ float txgain; float txdrc; /*!< Dynamic Range Compression factor. a number between 1 and 6ish */ float rxdrc; int tonezone; /*!< tone zone for this chan, or -1 for default */ enum DAHDI_IFLIST which_iflist; /*!< Which interface list is this structure listed? */ struct dahdi_pvt *next; /*!< Next channel in list */ struct dahdi_pvt *prev; /*!< Prev channel in list */ /* flags */ /*! * \brief TRUE if ADSI (Analog Display Services Interface) available * \note Set from the "adsi" value read in from chan_dahdi.conf */ unsigned int adsi:1; /*! * \brief TRUE if we can use a polarity reversal to mark when an outgoing * call is answered by the remote party. * \note Set from the "answeronpolarityswitch" value read in from chan_dahdi.conf */ unsigned int answeronpolarityswitch:1; /*! * \brief TRUE if busy detection is enabled. * (Listens for the beep-beep busy pattern.) * \note Set from the "busydetect" value read in from chan_dahdi.conf */ unsigned int busydetect:1; /*! * \brief TRUE if call return is enabled. * (*69, if your dialplan doesn't catch this first) * \note Set from the "callreturn" value read in from chan_dahdi.conf */ unsigned int callreturn:1; /*! * \brief TRUE if busy extensions will hear the call-waiting tone * and can use hook-flash to switch between callers. * \note Can be disabled by dialing *70. * \note Initialized with the "callwaiting" value read in from chan_dahdi.conf */ unsigned int callwaiting:1; /*! * \brief TRUE if send caller ID for Call Waiting * \note Set from the "callwaitingcallerid" value read in from chan_dahdi.conf */ unsigned int callwaitingcallerid:1; /*! * \brief TRUE if support for call forwarding enabled. * Dial *72 to enable call forwarding. * Dial *73 to disable call forwarding. * \note Set from the "cancallforward" value read in from chan_dahdi.conf */ unsigned int cancallforward:1; /*! * \brief TRUE if support for call parking is enabled. * \note Set from the "canpark" value read in from chan_dahdi.conf */ unsigned int canpark:1; /*! \brief TRUE if to wait for a DTMF digit to confirm answer */ unsigned int confirmanswer:1; /*! * \brief TRUE if the channel is to be destroyed on hangup. * (Used by pseudo channels.) */ unsigned int destroy:1; unsigned int didtdd:1; /*!< flag to say its done it once */ /*! \brief TRUE if analog type line dialed no digits in Dial() */ unsigned int dialednone:1; /*! * \brief TRUE if in the process of dialing digits or sending something. * \note This is used as a receive squelch for ISDN until connected. */ unsigned int dialing:1; /*! \brief TRUE if the transfer capability of the call is digital. */ unsigned int digital:1; /*! \brief TRUE if Do-Not-Disturb is enabled, present only for non sig_analog */ unsigned int dnd:1; /*! \brief XXX BOOLEAN Purpose??? */ unsigned int echobreak:1; /*! * \brief TRUE if echo cancellation enabled when bridged. * \note Initialized with the "echocancelwhenbridged" value read in from chan_dahdi.conf * \note Disabled if the echo canceller is not setup. */ unsigned int echocanbridged:1; /*! \brief TRUE if echo cancellation is turned on. */ unsigned int echocanon:1; /*! \brief TRUE if a fax tone has already been handled. */ unsigned int faxhandled:1; /*! TRUE if dynamic faxbuffers are configured for use, default is OFF */ unsigned int usefaxbuffers:1; /*! TRUE while buffer configuration override is in use */ unsigned int bufferoverrideinuse:1; /*! \brief TRUE if over a radio and dahdi_read() has been called. */ unsigned int firstradio:1; /*! * \brief TRUE if the call will be considered "hung up" on a polarity reversal. * \note Set from the "hanguponpolarityswitch" value read in from chan_dahdi.conf */ unsigned int hanguponpolarityswitch:1; /*! \brief TRUE if DTMF detection needs to be done by hardware. */ unsigned int hardwaredtmf:1; /*! * \brief TRUE if the outgoing caller ID is blocked/hidden. * \note Caller ID can be disabled by dialing *67. * \note Caller ID can be enabled by dialing *82. * \note Initialized with the "hidecallerid" value read in from chan_dahdi.conf */ unsigned int hidecallerid:1; /*! * \brief TRUE if hide just the name not the number for legacy PBX use. * \note Only applies to PRI channels. * \note Set from the "hidecalleridname" value read in from chan_dahdi.conf */ unsigned int hidecalleridname:1; /*! \brief TRUE if DTMF detection is disabled. */ unsigned int ignoredtmf:1; /*! * \brief TRUE if the channel should be answered immediately * without attempting to gather any digits. * \note Set from the "immediate" value read in from chan_dahdi.conf */ unsigned int immediate:1; /*! \brief TRUE if in an alarm condition. */ unsigned int inalarm:1; /*! \brief TRUE if TDD in MATE mode */ unsigned int mate:1; /*! \brief TRUE if we originated the call leg. */ unsigned int outgoing:1; /*! * \brief TRUE if busy extensions will hear the call-waiting tone * and can use hook-flash to switch between callers. * \note Set from the "callwaiting" value read in from chan_dahdi.conf */ unsigned int permcallwaiting:1; /*! * \brief TRUE if the outgoing caller ID is blocked/restricted/hidden. * \note Set from the "hidecallerid" value read in from chan_dahdi.conf */ unsigned int permhidecallerid:1; /*! * \brief TRUE if PRI congestion/busy indications are sent out-of-band. * \note Set from the "priindication" value read in from chan_dahdi.conf */ unsigned int priindication_oob:1; /*! * \brief TRUE if PRI B channels are always exclusively selected. * \note Set from the "priexclusive" value read in from chan_dahdi.conf */ unsigned int priexclusive:1; /*! * \brief TRUE if we will pulse dial. * \note Set from the "pulsedial" value read in from chan_dahdi.conf */ unsigned int pulse:1; /*! \brief TRUE if a pulsed digit was detected. (Pulse dial phone detected) */ unsigned int pulsedial:1; unsigned int restartpending:1; /*!< flag to ensure counted only once for restart */ /*! * \brief TRUE if caller ID is restricted. * \note Set but not used. Should be deleted. Redundant with permhidecallerid. * \note Set from the "restrictcid" value read in from chan_dahdi.conf */ unsigned int restrictcid:1; /*! * \brief TRUE if three way calling is enabled * \note Set from the "threewaycalling" value read in from chan_dahdi.conf */ unsigned int threewaycalling:1; /*! * \brief TRUE if call transfer is enabled * \note For FXS ports (either direct analog or over T1/E1): * Support flash-hook call transfer * \note For digital ports using ISDN PRI protocols: * Support switch-side transfer (called 2BCT, RLT or other names) * \note Set from the "transfer" value read in from chan_dahdi.conf */ unsigned int transfer:1; /*! * \brief TRUE if caller ID is used on this channel. * \note PRI and SS7 spans will save caller ID from the networking peer. * \note FXS ports will generate the caller ID spill. * \note FXO ports will listen for the caller ID spill. * \note Set from the "usecallerid" value read in from chan_dahdi.conf */ unsigned int use_callerid:1; /*! * \brief TRUE if we will use the calling presentation setting * from the Asterisk channel for outgoing calls. * \note Only applies to PRI and SS7 channels. * \note Set from the "usecallingpres" value read in from chan_dahdi.conf */ unsigned int use_callingpres:1; /*! * \brief TRUE if distinctive rings are to be detected. * \note For FXO lines * \note Set indirectly from the "usedistinctiveringdetection" value read in from chan_dahdi.conf */ unsigned int usedistinctiveringdetection:1; /*! * \brief TRUE if we should use the callerid from incoming call on dahdi transfer. * \note Set from the "useincomingcalleridondahditransfer" value read in from chan_dahdi.conf */ unsigned int dahditrcallerid:1; /*! * \brief TRUE if allowed to flash-transfer to busy channels. * \note Set from the "transfertobusy" value read in from chan_dahdi.conf */ unsigned int transfertobusy:1; /*! * \brief TRUE if the FXO port monitors for neon type MWI indications from the other end. * \note Set if the "mwimonitor" value read in contains "neon" from chan_dahdi.conf */ unsigned int mwimonitor_neon:1; /*! * \brief TRUE if the FXO port monitors for fsk type MWI indications from the other end. * \note Set if the "mwimonitor" value read in contains "fsk" from chan_dahdi.conf */ unsigned int mwimonitor_fsk:1; /*! * \brief TRUE if the FXO port monitors for rpas precursor to fsk MWI indications from the other end. * \note RPAS - Ring Pulse Alert Signal * \note Set if the "mwimonitor" value read in contains "rpas" from chan_dahdi.conf */ unsigned int mwimonitor_rpas:1; /*! \brief TRUE if an MWI monitor thread is currently active */ unsigned int mwimonitoractive:1; /*! \brief TRUE if a MWI message sending thread is active */ unsigned int mwisendactive:1; /*! * \brief TRUE if channel is out of reset and ready * \note Used by SS7. Otherwise set but not used. */ unsigned int inservice:1; /*! * \brief Bitmask for the channel being locally blocked. * \note Applies to SS7 and MFCR2 channels. * \note For MFCR2 only the first bit is used - TRUE if blocked * \note For SS7 two bits are used * \note Bit 0 - TRUE if maintenance blocked * \note Bit 1 - TRUE if hardware blocked */ unsigned int locallyblocked:2; /*! * \brief Bitmask for the channel being remotely blocked. 1 maintenance, 2 blocked in hardware. * \note Applies to SS7 and MFCR2 channels. * \note For MFCR2 only the first bit is used - TRUE if blocked * \note For SS7 two bits are used * \note Bit 0 - TRUE if maintenance blocked * \note Bit 1 - TRUE if hardware blocked */ unsigned int remotelyblocked:2; /*! * \brief TRUE if the channel alarms will be managed also as Span ones * \note Applies to all channels */ unsigned int manages_span_alarms:1; /*! \brief TRUE if hardware Rx gain set by Asterisk */ unsigned int hwrxgain_enabled; /*! \brief TRUE if hardware Tx gain set by Asterisk */ unsigned int hwtxgain_enabled; #if defined(HAVE_PRI) struct sig_pri_span *pri; int logicalspan; #endif /* defined(HAVE_PRI) */ /*! * \brief TRUE if SMDI (Simplified Message Desk Interface) is enabled * \note Set from the "usesmdi" value read in from chan_dahdi.conf */ unsigned int use_smdi:1; struct mwisend_info mwisend_data; /*! \brief The SMDI interface to get SMDI messages from. */ struct ast_smdi_interface *smdi_iface; /*! \brief Distinctive Ring data */ struct dahdi_distRings drings; /*! * \brief The configured context for incoming calls. * \note The "context" string read in from chan_dahdi.conf */ char context[AST_MAX_CONTEXT]; /*! * \brief A description for the channel configuration * \note The "description" string read in from chan_dahdi.conf */ char description[32]; /*! * \brief Saved context string. */ char defcontext[AST_MAX_CONTEXT]; /*! \brief Extension to use in the dialplan. */ char exten[AST_MAX_EXTENSION]; /*! * \brief Language configured for calls. * \note The "language" string read in from chan_dahdi.conf */ char language[MAX_LANGUAGE]; /*! * \brief The configured music-on-hold class to use for calls. * \note The "musicclass" or "mohinterpret" or "musiconhold" string read in from chan_dahdi.conf */ char mohinterpret[MAX_MUSICCLASS]; /*! * \brief Suggested music-on-hold class for peer channel to use for calls. * \note The "mohsuggest" string read in from chan_dahdi.conf */ char mohsuggest[MAX_MUSICCLASS]; char parkinglot[AST_MAX_EXTENSION]; /*!< Parking lot for this channel */ #if defined(HAVE_PRI) || defined(HAVE_SS7) /*! \brief Automatic Number Identification number (Alternate PRI caller ID number) */ char cid_ani[AST_MAX_EXTENSION]; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ /*! \brief Automatic Number Identification code from PRI */ int cid_ani2; /*! \brief Caller ID number from an incoming call. */ char cid_num[AST_MAX_EXTENSION]; /*! * \brief Caller ID tag from incoming call * \note the "cid_tag" string read in from chan_dahdi.conf */ char cid_tag[AST_MAX_EXTENSION]; /*! \brief Caller ID Q.931 TON/NPI field values. Set by PRI. Zero otherwise. */ int cid_ton; /*! \brief Caller ID name from an incoming call. */ char cid_name[AST_MAX_EXTENSION]; /*! \brief Caller ID subaddress from an incoming call. */ char cid_subaddr[AST_MAX_EXTENSION]; char *origcid_num; /*!< malloced original callerid */ char *origcid_name; /*!< malloced original callerid */ /*! \brief Call waiting number. */ char callwait_num[AST_MAX_EXTENSION]; /*! \brief Call waiting name. */ char callwait_name[AST_MAX_EXTENSION]; /*! \brief Redirecting Directory Number Information Service (RDNIS) number */ char rdnis[AST_MAX_EXTENSION]; /*! \brief Dialed Number Identifier */ char dnid[AST_MAX_EXTENSION]; /*! * \brief Bitmapped groups this belongs to. * \note The "group" bitmapped group string read in from chan_dahdi.conf */ ast_group_t group; /*! \brief Default call PCM encoding format: DAHDI_LAW_ALAW or DAHDI_LAW_MULAW. */ int law_default; /*! \brief Active PCM encoding format: DAHDI_LAW_ALAW or DAHDI_LAW_MULAW */ int law; int confno; /*!< Our conference */ int confusers; /*!< Who is using our conference */ int propconfno; /*!< Propagated conference number */ /*! * \brief Bitmapped call groups this belongs to. * \note The "callgroup" bitmapped group string read in from chan_dahdi.conf */ ast_group_t callgroup; /*! * \brief Bitmapped pickup groups this belongs to. * \note The "pickupgroup" bitmapped group string read in from chan_dahdi.conf */ ast_group_t pickupgroup; /*! * \brief Named call groups this belongs to. * \note The "namedcallgroup" string read in from chan_dahdi.conf */ struct ast_namedgroups *named_callgroups; /*! * \brief Named pickup groups this belongs to. * \note The "namedpickupgroup" string read in from chan_dahdi.conf */ struct ast_namedgroups *named_pickupgroups; /*! * \brief Channel variable list with associated values to set when a channel is created. * \note The "setvar" strings read in from chan_dahdi.conf */ struct ast_variable *vars; int channel; /*!< Channel Number */ int span; /*!< Span number */ time_t guardtime; /*!< Must wait this much time before using for new call */ int cid_signalling; /*!< CID signalling type bell202 or v23 */ int cid_start; /*!< CID start indicator, polarity or ring or DTMF without warning event */ int dtmfcid_holdoff_state; /*!< State indicator that allows for line to settle before checking for dtmf energy */ struct timeval dtmfcid_delay; /*!< Time value used for allow line to settle */ int callingpres; /*!< The value of calling presentation that we're going to use when placing a PRI call */ int callwaitingrepeat; /*!< How many samples to wait before repeating call waiting */ int cidcwexpire; /*!< When to stop waiting for CID/CW CAS response (In samples) */ int cid_suppress_expire; /*!< How many samples to suppress after a CID spill. */ /*! \brief Analog caller ID waveform sample buffer */ unsigned char *cidspill; /*! \brief Position in the cidspill buffer to send out next. */ int cidpos; /*! \brief Length of the cidspill buffer containing samples. */ int cidlen; /*! \brief Ring timeout timer?? */ int ringt; /*! * \brief Ring timeout base. * \note Value computed indirectly from "ringtimeout" read in from chan_dahdi.conf */ int ringt_base; /*! * \brief Number of most significant digits/characters to strip from the dialed number. * \note Feature is deprecated. Use dialplan logic. * \note The characters are stripped before the PRI TON/NPI prefix * characters are processed. */ int stripmsd; /*! * \brief TRUE if Call Waiting (CW) CPE Alert Signal (CAS) is being sent. * \note * After CAS is sent, the call waiting caller id will be sent if the phone * gives a positive reply. */ int callwaitcas; /*! \brief Number of call waiting rings. */ int callwaitrings; /*! \brief Echo cancel parameters. */ struct { struct dahdi_echocanparams head; struct dahdi_echocanparam params[DAHDI_MAX_ECHOCANPARAMS]; } echocancel; /*! * \brief Echo training time. 0 = disabled * \note Set from the "echotraining" value read in from chan_dahdi.conf */ int echotraining; /*! \brief Filled with 'w'. XXX Purpose?? */ char echorest[20]; /*! * \brief Number of times to see "busy" tone before hanging up. * \note Set from the "busycount" value read in from chan_dahdi.conf */ int busycount; /*! * \brief Busy cadence pattern description. * \note Set from the "busypattern" value read from chan_dahdi.conf */ struct ast_dsp_busy_pattern busy_cadence; /*! * \brief Bitmapped call progress detection flags. CALLPROGRESS_xxx values. * \note Bits set from the "callprogress" and "faxdetect" values read in from chan_dahdi.conf */ int callprogress; /*! * \brief Number of milliseconds to wait for dialtone. * \note Set from the "waitfordialtone" value read in from chan_dahdi.conf */ int waitfordialtone; /*! * \brief Number of frames to watch for dialtone in incoming calls * \note Set from the "dialtone_detect" value read in from chan_dahdi.conf */ int dialtone_detect; int dialtone_scanning_time_elapsed; /*!< Amount of audio scanned for dialtone, in frames */ struct timeval waitingfordt; /*!< Time we started waiting for dialtone */ struct timeval flashtime; /*!< Last flash-hook time */ /*! \brief Opaque DSP configuration structure. */ struct ast_dsp *dsp; /*! \brief DAHDI dial operation command struct for ioctl() call. */ struct dahdi_dialoperation dop; int whichwink; /*!< SIG_FEATDMF_TA Which wink are we on? */ /*! \brief Second part of SIG_FEATDMF_TA wink operation. */ char finaldial[64]; char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Account code */ int amaflags; /*!< AMA Flags */ struct tdd_state *tdd; /*!< TDD flag */ /*! \brief Accumulated call forwarding number. */ char call_forward[AST_MAX_EXTENSION]; /*! * \brief Voice mailbox location. * \note Set from the "mailbox" string read in from chan_dahdi.conf */ char mailbox[AST_MAX_MAILBOX_UNIQUEID]; /*! \brief Opaque event subscription parameters for message waiting indication support. */ struct stasis_subscription *mwi_event_sub; /*! \brief Delayed dialing for E911. Overlap digits for ISDN. */ char dialdest[256]; #ifdef HAVE_DAHDI_LINEREVERSE_VMWI struct dahdi_vmwi_info mwisend_setting; /*!< Which VMWI methods to use */ unsigned int mwisend_fsk: 1; /*! Variable for enabling FSK MWI handling in chan_dahdi */ unsigned int mwisend_rpas:1; /*! Variable for enabling Ring Pulse Alert before MWI FSK Spill */ #endif int distinctivering; /*!< Which distinctivering to use */ int dtmfrelax; /*!< whether to run in relaxed DTMF mode */ /*! \brief Holding place for event injected from outside normal operation. */ int fake_event; /*! * \brief Minimal time period (ms) between the answer polarity * switch and hangup polarity switch. */ int polarityonanswerdelay; /*! \brief Start delay time if polarityonanswerdelay is nonzero. */ struct timeval polaritydelaytv; /*! * \brief Send caller ID on FXS after this many rings. Set to 1 for US. * \note Set from the "sendcalleridafter" value read in from chan_dahdi.conf */ int sendcalleridafter; /*! \brief Current line interface polarity. POLARITY_IDLE, POLARITY_REV */ int polarity; /*! \brief DSP feature flags: DSP_FEATURE_xxx */ int dsp_features; #if defined(HAVE_SS7) /*! \brief SS7 control parameters */ struct sig_ss7_linkset *ss7; #endif /* defined(HAVE_SS7) */ #if defined(HAVE_OPENR2) struct dahdi_mfcr2 *mfcr2; openr2_chan_t *r2chan; openr2_calling_party_category_t mfcr2_recvd_category; openr2_calling_party_category_t mfcr2_category; int mfcr2_dnis_index; int mfcr2_ani_index; int mfcr2call:1; int mfcr2_answer_pending:1; int mfcr2_charge_calls:1; int mfcr2_allow_collect_calls:1; int mfcr2_forced_release:1; int mfcr2_dnis_matched:1; int mfcr2_call_accepted:1; int mfcr2_accept_on_offer:1; int mfcr2_progress_sent:1; #endif /* defined(HAVE_OPENR2) */ /*! \brief DTMF digit in progress. 0 when no digit in progress. */ char begindigit; /*! \brief TRUE if confrence is muted. */ int muting; void *sig_pvt; struct ast_cc_config_params *cc_params; /* DAHDI channel names may differ greatly from the * string that was provided to an app such as Dial. We * need to save the original string passed to dahdi_request * for call completion purposes. This way, we can replicate * the original dialed string later. */ char dialstring[AST_CHANNEL_NAME]; }; /* Analog signaling */ #define SIG_EM DAHDI_SIG_EM #define SIG_EMWINK (0x0100000 | DAHDI_SIG_EM) #define SIG_FEATD (0x0200000 | DAHDI_SIG_EM) #define SIG_FEATDMF (0x0400000 | DAHDI_SIG_EM) #define SIG_FEATB (0x0800000 | DAHDI_SIG_EM) #define SIG_E911 (0x1000000 | DAHDI_SIG_EM) #define SIG_FEATDMF_TA (0x2000000 | DAHDI_SIG_EM) #define SIG_FGC_CAMA (0x4000000 | DAHDI_SIG_EM) #define SIG_FGC_CAMAMF (0x8000000 | DAHDI_SIG_EM) #define SIG_FXSLS DAHDI_SIG_FXSLS #define SIG_FXSGS DAHDI_SIG_FXSGS #define SIG_FXSKS DAHDI_SIG_FXSKS #define SIG_FXOLS DAHDI_SIG_FXOLS #define SIG_FXOGS DAHDI_SIG_FXOGS #define SIG_FXOKS DAHDI_SIG_FXOKS #define SIG_SF DAHDI_SIG_SF #define SIG_SFWINK (0x0100000 | DAHDI_SIG_SF) #define SIG_SF_FEATD (0x0200000 | DAHDI_SIG_SF) #define SIG_SF_FEATDMF (0x0400000 | DAHDI_SIG_SF) #define SIG_SF_FEATB (0x0800000 | DAHDI_SIG_SF) #define SIG_EM_E1 DAHDI_SIG_EM_E1 /* PRI signaling */ #define SIG_PRI DAHDI_SIG_CLEAR #define SIG_BRI (0x2000000 | DAHDI_SIG_CLEAR) #define SIG_BRI_PTMP (0X4000000 | DAHDI_SIG_CLEAR) /* SS7 signaling */ #define SIG_SS7 (0x1000000 | DAHDI_SIG_CLEAR) /* MFC/R2 signaling */ #define SIG_MFCR2 DAHDI_SIG_CAS #define SIG_PRI_LIB_HANDLE_CASES \ SIG_PRI: \ case SIG_BRI: \ case SIG_BRI_PTMP /*! * \internal * \brief Determine if sig_pri handles the signaling. * \since 1.8 * * \param signaling Signaling to determine if is for sig_pri. * * \return TRUE if the signaling is for sig_pri. */ static inline int dahdi_sig_pri_lib_handles(int signaling) { int handles; switch (signaling) { case SIG_PRI_LIB_HANDLE_CASES: handles = 1; break; default: handles = 0; break; } return handles; } static inline int dahdi_analog_lib_handles(int signalling, int radio, int oprmode) { switch (signalling) { case SIG_FXOLS: case SIG_FXOGS: case SIG_FXOKS: case SIG_FXSLS: case SIG_FXSGS: case SIG_FXSKS: case SIG_EMWINK: case SIG_EM: case SIG_EM_E1: case SIG_FEATD: case SIG_FEATDMF: case SIG_E911: case SIG_FGC_CAMA: case SIG_FGC_CAMAMF: case SIG_FEATB: case SIG_SFWINK: case SIG_SF: case SIG_SF_FEATD: case SIG_SF_FEATDMF: case SIG_FEATDMF_TA: case SIG_SF_FEATB: break; default: /* The rest of the function should cover the remainder of signalling types */ return 0; } if (radio) { return 0; } if (oprmode) { return 0; } return 1; } #define dahdi_get_index(ast, p, nullok) _dahdi_get_index(ast, p, nullok, __PRETTY_FUNCTION__, __LINE__) int _dahdi_get_index(struct ast_channel *ast, struct dahdi_pvt *p, int nullok, const char *fname, unsigned long line); void dahdi_dtmf_detect_disable(struct dahdi_pvt *p); void dahdi_dtmf_detect_enable(struct dahdi_pvt *p); void dahdi_ec_enable(struct dahdi_pvt *p); void dahdi_ec_disable(struct dahdi_pvt *p); void dahdi_conf_update(struct dahdi_pvt *p); void dahdi_master_slave_link(struct dahdi_pvt *slave, struct dahdi_pvt *master); void dahdi_master_slave_unlink(struct dahdi_pvt *slave, struct dahdi_pvt *master, int needlock); /* ------------------------------------------------------------------- */ #if defined(__cplusplus) || defined(c_plusplus) } #endif #endif /* _ASTERISK_CHAN_DAHDI_H */ asterisk-13.1.0/channels/pjsip/0000755000000000000000000000000012443600112015030 5ustar rootrootasterisk-13.1.0/channels/pjsip/include/0000755000000000000000000000000012443600111016452 5ustar rootrootasterisk-13.1.0/channels/pjsip/include/dialplan_functions.h0000644000000000000000000000464612252061526022522 0ustar rootroot/* * 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. */ /*! * \file * \brief PJSIP dialplan functions header file */ #ifndef _PJSIP_DIALPLAN_FUNCTIONS #define _PJSIP_DIALPLAN_FUNCTIONS /*! * \brief CHANNEL function read callback * \param chan The channel the function is called on * \param cmd The name of the function * \param data Arguments passed to the function * \param buf Out buffer that should be populated with the data * \param len Size of the buffer * * \retval 0 on success * \retval -1 on failure */ int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); /*! * \brief PJSIP_MEDIA_OFFER function write callback * \param chan The channel the function is called on * \param cmd The name of the function * \param data Arguments passed to the function * \param value Value to be set by the function * * \retval 0 on success * \retval -1 on failure */ int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value); /*! * \brief PJSIP_MEDIA_OFFER function read callback * \param chan The channel the function is called on * \param cmd The name of the function * \param data Arguments passed to the function * \param buf Out buffer that should be populated with the data * \param len Size of the buffer * * \retval 0 on success * \retval -1 on failure */ int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); /*! * \brief PJSIP_DIAL_CONTACTS function read callback * \param chan The channel the function is called on * \param cmd The name of the function * \param data Arguments passed to the function * \param buf Out buffer that should be populated with the data * \param len Size of the buffer * * \retval 0 on success * \retval -1 on failure */ int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len); #endif /* _PJSIP_DIALPLAN_FUNCTIONS */asterisk-13.1.0/channels/pjsip/include/chan_pjsip.h0000644000000000000000000000266012252061526020756 0ustar rootroot/* * 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. */ /*! * \file * \brief PJSIP Channel Driver shared data structures */ #ifndef _CHAN_PJSIP_HEADER #define _CHAN_PJSIP_HEADER struct ast_sip_session_media; /*! * \brief Transport information stored in transport_info datastore */ struct transport_info_data { /*! \brief The address that sent the request */ pj_sockaddr remote_addr; /*! \brief Our address that received the request */ pj_sockaddr local_addr; }; /*! * \brief Positions of various media */ enum sip_session_media_position { /*! \brief First is audio */ SIP_MEDIA_AUDIO = 0, /*! \brief Second is video */ SIP_MEDIA_VIDEO, /*! \brief Last is the size for media details */ SIP_MEDIA_SIZE, }; /*! * \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt * data structure */ struct chan_pjsip_pvt { /*! \brief The available media sessions */ struct ast_sip_session_media *media[SIP_MEDIA_SIZE]; }; #endif /* _CHAN_PJSIP_HEADER */ asterisk-13.1.0/channels/pjsip/dialplan_functions.c0000644000000000000000000007550512414363703021076 0ustar rootroot/* * 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. */ /*! * \file * * \author \verbatim Joshua Colp \endverbatim * \author \verbatim Matt Jordan \endverbatim * * \ingroup functions * * \brief PJSIP channel dialplan functions */ /*** MODULEINFO core ***/ /*** DOCUMENTATION Return a dial string for dialing all contacts on an AOR. Name of the endpoint Name of an AOR to use, if not specified the configured AORs on the endpoint are used Optional request user to use in the request URI Returns a properly formatted dial string for dialing all contacts on an AOR. Media and codec offerings to be set on an outbound SIP channel prior to dialing. types of media offered Returns the codecs offered based upon the media choice R/O Retrieve media related information. When rtp is specified, the type parameter must be provided. It specifies which RTP parameter to read. Retrieve the local address for RTP. Retrieve the remote address for RTP. If direct media is enabled, this address is the remote address used for RTP. Whether or not the media stream is encrypted. The media stream is not encrypted. The media stream is encrypted. Whether or not the media stream is currently restricted due to a call hold. The media stream is not held. The media stream is held. When rtp is specified, the media_type parameter may be provided. It specifies which media stream the chosen RTP parameter should be retrieved from. Retrieve information from the audio media stream. If not specified, audio is used by default. Retrieve information from the video media stream. R/O Retrieve RTCP statistics. When rtcp is specified, the statistic parameter must be provided. It specifies which RTCP statistic parameter to read. Retrieve a summary of all RTCP statistics. The following data items are returned in a semi-colon delineated list: Our Synchronization Source identifier Their Synchronization Source identifier Our lost packet count Received packet jitter Received packet count Transmitted packet jitter Transmitted packet count Remote lost packet count Round trip time Retrieve a summary of all RTCP Jitter statistics. The following data items are returned in a semi-colon delineated list: Our minimum jitter Our max jitter Our average jitter Our jitter standard deviation Their minimum jitter Their max jitter Their average jitter Their jitter standard deviation Retrieve a summary of all RTCP packet loss statistics. The following data items are returned in a semi-colon delineated list: Our minimum lost packets Our max lost packets Our average lost packets Our lost packets standard deviation Their minimum lost packets Their max lost packets Their average lost packets Their lost packets standard deviation Retrieve a summary of all RTCP round trip time information. The following data items are returned in a semi-colon delineated list: Minimum round trip time Maximum round trip time Average round trip time Standard deviation round trip time Transmitted packet count Received packet count Transmitted packet jitter Received packet jitter Their max jitter Their minimum jitter Their average jitter Their jitter standard deviation Our max jitter Our minimum jitter Our average jitter Our jitter standard deviation Transmitted packet loss Received packet loss Their max lost packets Their minimum lost packets Their average lost packets Their lost packets standard deviation Our max lost packets Our minimum lost packets Our average lost packets Our lost packets standard deviation Round trip time Maximum round trip time Minimum round trip time Average round trip time Standard deviation round trip time Our Synchronization Source identifier Their Synchronization Source identifier When rtcp is specified, the media_type parameter may be provided. It specifies which media stream the chosen RTCP parameter should be retrieved from. Retrieve information from the audio media stream. If not specified, audio is used by default. Retrieve information from the video media stream. R/O The name of the endpoint associated with this channel. Use the PJSIP_ENDPOINT function to obtain further endpoint related information. R/O Obtain information about the current PJSIP channel and its session. When pjsip is specified, the type parameter must be provided. It specifies which signalling parameter to read. Whether or not the signalling uses a secure transport. The signalling uses a non-secure transport. The signalling uses a secure transport. The request URI of the INVITE request associated with the creation of this channel. The local URI. The remote URI. The current state of any T.38 fax on this channel. T.38 faxing is disabled on this channel. Asterisk has sent a re-INVITE to the remote end to initiate a T.38 fax. The remote end has sent a re-INVITE to Asterisk to initiate a T.38 fax. A T.38 fax session has been enabled. A T.38 fax session was attempted but was rejected. On inbound calls, the full IP address and port number that the INVITE request was received on. On outbound calls, the full IP address and port number that the INVITE request was transmitted from. On inbound calls, the full IP address and port number that the INVITE request was received from. On outbound calls, the full IP address and port number that the INVITE request was transmitted to. ***/ #include "asterisk.h" #include #include #include ASTERISK_FILE_VERSION(__FILE__, "$Revision: 424622 $") #include "asterisk/astobj2.h" #include "asterisk/module.h" #include "asterisk/acl.h" #include "asterisk/app.h" #include "asterisk/channel.h" #include "asterisk/format.h" #include "asterisk/pbx.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" #include "include/chan_pjsip.h" #include "include/dialplan_functions.h" /*! * \brief String representations of the T.38 state enum */ static const char *t38state_to_string[T38_MAX_ENUM] = { [T38_DISABLED] = "DISABLED", [T38_LOCAL_REINVITE] = "LOCAL_REINVITE", [T38_PEER_REINVITE] = "REMOTE_REINVITE", [T38_ENABLED] = "ENABLED", [T38_REJECTED] = "REJECTED", }; /*! * \internal \brief Handle reading RTP information */ static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct chan_pjsip_pvt *pvt; struct ast_sip_session_media *media = NULL; struct ast_sockaddr addr; if (!channel) { ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); return -1; } pvt = channel->pvt; if (!pvt) { ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan)); return -1; } if (ast_strlen_zero(type)) { ast_log(AST_LOG_WARNING, "You must supply a type field for 'rtp' information\n"); return -1; } if (ast_strlen_zero(field) || !strcmp(field, "audio")) { media = pvt->media[SIP_MEDIA_AUDIO]; } else if (!strcmp(field, "video")) { media = pvt->media[SIP_MEDIA_VIDEO]; } else { ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field); return -1; } if (!media || !media->rtp) { ast_log(AST_LOG_WARNING, "Channel %s has no %s media/RTP session\n", ast_channel_name(chan), S_OR(field, "audio")); return -1; } if (!strcmp(type, "src")) { ast_rtp_instance_get_local_address(media->rtp, &addr); ast_copy_string(buf, ast_sockaddr_stringify(&addr), buflen); } else if (!strcmp(type, "dest")) { ast_rtp_instance_get_remote_address(media->rtp, &addr); ast_copy_string(buf, ast_sockaddr_stringify(&addr), buflen); } else if (!strcmp(type, "direct")) { ast_copy_string(buf, ast_sockaddr_stringify(&media->direct_media_addr), buflen); } else if (!strcmp(type, "secure")) { snprintf(buf, buflen, "%d", media->srtp ? 1 : 0); } else if (!strcmp(type, "hold")) { snprintf(buf, buflen, "%d", media->held ? 1 : 0); } else { ast_log(AST_LOG_WARNING, "Unknown type field '%s' specified for 'rtp' information\n", type); return -1; } return 0; } /*! * \internal \brief Handle reading RTCP information */ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); struct chan_pjsip_pvt *pvt; struct ast_sip_session_media *media = NULL; if (!channel) { ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); return -1; } pvt = channel->pvt; if (!pvt) { ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan)); return -1; } if (ast_strlen_zero(type)) { ast_log(AST_LOG_WARNING, "You must supply a type field for 'rtcp' information\n"); return -1; } if (ast_strlen_zero(field) || !strcmp(field, "audio")) { media = pvt->media[SIP_MEDIA_AUDIO]; } else if (!strcmp(field, "video")) { media = pvt->media[SIP_MEDIA_VIDEO]; } else { ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field); return -1; } if (!media || !media->rtp) { ast_log(AST_LOG_WARNING, "Channel %s has no %s media/RTP session\n", ast_channel_name(chan), S_OR(field, "audio")); return -1; } if (!strncasecmp(type, "all", 3)) { enum ast_rtp_instance_stat_field stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY; if (!strcasecmp(type, "all_jitter")) { stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER; } else if (!strcasecmp(type, "all_rtt")) { stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT; } else if (!strcasecmp(type, "all_loss")) { stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS; } if (!ast_rtp_instance_get_quality(media->rtp, stat_field, buf, buflen)) { ast_log(AST_LOG_WARNING, "Unable to retrieve 'rtcp' statistics for %s\n", ast_channel_name(chan)); return -1; } } else { struct ast_rtp_instance_stats stats; int i; struct { const char *name; enum { INT, DBL } type; union { unsigned int *i4; double *d8; }; } lookup[] = { { "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(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { ast_log(AST_LOG_WARNING, "Unable to retrieve 'rtcp' statistics for %s\n", ast_channel_name(chan)); return -1; } for (i = 0; !ast_strlen_zero(lookup[i].name); i++) { if (!strcasecmp(type, lookup[i].name)) { if (lookup[i].type == INT) { snprintf(buf, buflen, "%u", *lookup[i].i4); } else { snprintf(buf, buflen, "%f", *lookup[i].d8); } return 0; } } ast_log(AST_LOG_WARNING, "Unrecognized argument '%s' for 'rtcp' information\n", type); return -1; } return 0; } /*! * \internal \brief Handle reading signalling information */ static int channel_read_pjsip(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen) { struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan); char *buf_copy; pjsip_dialog *dlg; if (!channel) { ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); return -1; } dlg = channel->session->inv_session->dlg; if (!strcmp(type, "secure")) { snprintf(buf, buflen, "%d", dlg->secure ? 1 : 0); } else if (!strcmp(type, "target_uri")) { pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->target, buf, buflen); buf_copy = ast_strdupa(buf); ast_escape_quoted(buf_copy, buf, buflen); } else if (!strcmp(type, "local_uri")) { pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->local.info->uri, buf, buflen); buf_copy = ast_strdupa(buf); ast_escape_quoted(buf_copy, buf, buflen); } else if (!strcmp(type, "remote_uri")) { pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->remote.info->uri, buf, buflen); buf_copy = ast_strdupa(buf); ast_escape_quoted(buf_copy, buf, buflen); } else if (!strcmp(type, "t38state")) { ast_copy_string(buf, t38state_to_string[channel->session->t38state], buflen); } else if (!strcmp(type, "local_addr")) { RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); struct transport_info_data *transport_data; datastore = ast_sip_session_get_datastore(channel->session, "transport_info"); if (!datastore) { ast_log(AST_LOG_WARNING, "No transport information for channel %s\n", ast_channel_name(chan)); return -1; } transport_data = datastore->data; if (pj_sockaddr_has_addr(&transport_data->local_addr)) { pj_sockaddr_print(&transport_data->local_addr, buf, buflen, 3); } } else if (!strcmp(type, "remote_addr")) { RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); struct transport_info_data *transport_data; datastore = ast_sip_session_get_datastore(channel->session, "transport_info"); if (!datastore) { ast_log(AST_LOG_WARNING, "No transport information for channel %s\n", ast_channel_name(chan)); return -1; } transport_data = datastore->data; if (pj_sockaddr_has_addr(&transport_data->remote_addr)) { pj_sockaddr_print(&transport_data->remote_addr, buf, buflen, 3); } } else { ast_log(AST_LOG_WARNING, "Unrecognized argument '%s' for 'pjsip' information\n", type); return -1; } return 0; } /*! \brief Struct used to push function arguments to task processor */ struct pjsip_func_args { struct ast_channel *chan; const char *param; const char *type; const char *field; char *buf; size_t len; int ret; }; /*! \internal \brief Taskprocessor callback that handles the read on a PJSIP thread */ static int read_pjsip(void *data) { struct pjsip_func_args *func_args = data; if (!strcmp(func_args->param, "rtp")) { func_args->ret = channel_read_rtp(func_args->chan, func_args->type, func_args->field, func_args->buf, func_args->len); } else if (!strcmp(func_args->param, "rtcp")) { func_args->ret = channel_read_rtcp(func_args->chan, func_args->type, func_args->field, func_args->buf, func_args->len); } else if (!strcmp(func_args->param, "endpoint")) { struct ast_sip_channel_pvt *pvt = ast_channel_tech_pvt(func_args->chan); if (!pvt) { ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(func_args->chan)); return -1; } if (!pvt->session || !pvt->session->endpoint) { ast_log(AST_LOG_WARNING, "Channel %s has no endpoint!\n", ast_channel_name(func_args->chan)); return -1; } snprintf(func_args->buf, func_args->len, "%s", ast_sorcery_object_get_id(pvt->session->endpoint)); } else if (!strcmp(func_args->param, "pjsip")) { func_args->ret = channel_read_pjsip(func_args->chan, func_args->type, func_args->field, func_args->buf, func_args->len); } else { func_args->ret = -1; } return 0; } int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { struct pjsip_func_args func_args = { 0, }; struct ast_sip_channel_pvt *channel; char *parse = ast_strdupa(data); AST_DECLARE_APP_ARGS(args, AST_APP_ARG(param); AST_APP_ARG(type); AST_APP_ARG(field); ); if (!chan) { ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); return -1; } channel = ast_channel_tech_pvt(chan); /* Check for zero arguments */ if (ast_strlen_zero(parse)) { ast_log(LOG_ERROR, "Cannot call %s without arguments\n", cmd); return -1; } AST_STANDARD_APP_ARGS(args, parse); /* Sanity check */ if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) { ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel\n", cmd); return 0; } if (!channel) { ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan)); return -1; } memset(buf, 0, len); func_args.chan = chan; func_args.param = args.param; func_args.type = args.type; func_args.field = args.field; func_args.buf = buf; func_args.len = len; if (ast_sip_push_task_synchronous(channel->session->serializer, read_pjsip, &func_args)) { ast_log(LOG_WARNING, "Unable to read properties of channel %s: failed to push task\n", ast_channel_name(chan)); return -1; } return func_args.ret; } int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); RAII_VAR(struct ast_str *, dial, NULL, ast_free_ptr); const char *aor_name; char *rest; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(endpoint_name); AST_APP_ARG(aor_name); AST_APP_ARG(request_user); ); AST_STANDARD_APP_ARGS(args, data); if (ast_strlen_zero(args.endpoint_name)) { ast_log(LOG_WARNING, "An endpoint name must be specified when using the '%s' dialplan function\n", cmd); return -1; } else if (!(endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", args.endpoint_name))) { ast_log(LOG_WARNING, "Specified endpoint '%s' was not found\n", args.endpoint_name); return -1; } aor_name = S_OR(args.aor_name, endpoint->aors); if (ast_strlen_zero(aor_name)) { ast_log(LOG_WARNING, "No AOR has been provided and no AORs are configured on endpoint '%s'\n", args.endpoint_name); return -1; } else if (!(dial = ast_str_create(len))) { ast_log(LOG_WARNING, "Could not get enough buffer space for dialing contacts\n"); return -1; } else if (!(rest = ast_strdupa(aor_name))) { ast_log(LOG_WARNING, "Could not duplicate provided AORs\n"); return -1; } while ((aor_name = strsep(&rest, ","))) { RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup); RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup); struct ao2_iterator it_contacts; struct ast_sip_contact *contact; if (!aor) { /* If the AOR provided is not found skip it, there may be more */ continue; } else if (!(contacts = ast_sip_location_retrieve_aor_contacts(aor))) { /* No contacts are available, skip it as well */ continue; } else if (!ao2_container_count(contacts)) { /* We were given a container but no contacts are in it... */ continue; } it_contacts = ao2_iterator_init(contacts, 0); for (; (contact = ao2_iterator_next(&it_contacts)); ao2_ref(contact, -1)) { ast_str_append(&dial, -1, "PJSIP/"); if (!ast_strlen_zero(args.request_user)) { ast_str_append(&dial, -1, "%s@", args.request_user); } ast_str_append(&dial, -1, "%s/%s&", args.endpoint_name, contact->uri); } ao2_iterator_destroy(&it_contacts); } /* Trim the '&' at the end off */ ast_str_truncate(dial, ast_str_strlen(dial) - 1); ast_copy_string(buf, ast_str_buffer(dial), len); return 0; } static int media_offer_read_av(struct ast_sip_session *session, char *buf, size_t len, enum ast_media_type media_type) { int i, size = 0; for (i = 0; i < ast_format_cap_count(session->req_caps); i++) { struct ast_format *fmt = ast_format_cap_get_format(session->req_caps, i); if (ast_format_get_type(fmt) != media_type) { ao2_ref(fmt, -1); continue; } /* add one since we'll include a comma */ size = strlen(ast_format_get_name(fmt)) + 1; len -= size; if ((len) < 0) { ao2_ref(fmt, -1); break; } /* no reason to use strncat here since we have already ensured buf has enough space, so strcat can be safely used */ strcat(buf, ast_format_get_name(fmt)); strcat(buf, ","); ao2_ref(fmt, -1); } if (size) { /* remove the extra comma */ buf[strlen(buf) - 1] = '\0'; } return 0; } struct media_offer_data { struct ast_sip_session *session; enum ast_media_type media_type; const char *value; }; static int media_offer_write_av(void *obj) { struct media_offer_data *data = obj; ast_format_cap_remove_by_type(data->session->req_caps, data->media_type); ast_format_cap_update_by_allow_disallow(data->session->req_caps, data->value, 1); return 0; } int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { struct ast_sip_channel_pvt *channel; if (!chan) { ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); return -1; } if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) { ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel\n", cmd); return -1; } channel = ast_channel_tech_pvt(chan); if (!strcmp(data, "audio")) { return media_offer_read_av(channel->session, buf, len, AST_MEDIA_TYPE_AUDIO); } else if (!strcmp(data, "video")) { return media_offer_read_av(channel->session, buf, len, AST_MEDIA_TYPE_VIDEO); } return 0; } int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) { struct ast_sip_channel_pvt *channel; struct media_offer_data mdata = { .value = value }; if (!chan) { ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); return -1; } if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) { ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel\n", cmd); return -1; } channel = ast_channel_tech_pvt(chan); mdata.session = channel->session; if (!strcmp(data, "audio")) { mdata.media_type = AST_MEDIA_TYPE_AUDIO; } else if (!strcmp(data, "video")) { mdata.media_type = AST_MEDIA_TYPE_VIDEO; } return ast_sip_push_task_synchronous(channel->session->serializer, media_offer_write_av, &mdata); } asterisk-13.1.0/channels/chan_nbs.c0000644000000000000000000001727712364505025015651 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2006, 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. */ /*! \file * * \brief Network broadcast sound support channel driver * * \author Mark Spencer * * \ingroup channel_drivers */ /*** MODULEINFO nbs extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") #include #include #include #include #include #include #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/utils.h" #include "asterisk/format_cache.h" static const char tdesc[] = "Network Broadcast Sound Driver"; static char context[AST_MAX_EXTENSION] = "default"; static const char type[] = "NBS"; /* NBS creates private structures on demand */ struct nbs_pvt { NBS *nbs; struct ast_channel *owner; /* Channel we belong to, possibly NULL */ char app[16]; /* Our app */ char stream[80]; /* Our stream */ struct ast_module_user *u; /*! for holding a reference to this module */ }; static struct ast_channel *nbs_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int nbs_call(struct ast_channel *ast, const char *dest, int timeout); static int nbs_hangup(struct ast_channel *ast); static struct ast_frame *nbs_xread(struct ast_channel *ast); static int nbs_xwrite(struct ast_channel *ast, struct ast_frame *frame); static struct ast_channel_tech nbs_tech = { .type = type, .description = tdesc, .requester = nbs_request, .call = nbs_call, .hangup = nbs_hangup, .read = nbs_xread, .write = nbs_xwrite, }; static int nbs_call(struct ast_channel *ast, const char *dest, int timeout) { struct nbs_pvt *p; p = ast_channel_tech_pvt(ast); if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "nbs_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } /* When we call, it just works, really, there's no destination... Just ring the phone and wait for someone to answer */ ast_debug(1, "Calling %s on %s\n", dest, ast_channel_name(ast)); /* If we can't connect, return congestion */ if (nbs_connect(p->nbs)) { ast_log(LOG_WARNING, "NBS Connection failed on %s\n", ast_channel_name(ast)); ast_queue_control(ast, AST_CONTROL_CONGESTION); } else { ast_setstate(ast, AST_STATE_RINGING); ast_queue_control(ast, AST_CONTROL_ANSWER); } return 0; } static void nbs_destroy(struct nbs_pvt *p) { if (p->nbs) nbs_delstream(p->nbs); ast_module_user_remove(p->u); ast_free(p); } static struct nbs_pvt *nbs_alloc(const char *data) { struct nbs_pvt *p; int flags = 0; char stream[256]; char *opts; ast_copy_string(stream, data, sizeof(stream)); if ((opts = strchr(stream, ':'))) { *opts = '\0'; opts++; } else opts = ""; p = ast_calloc(1, sizeof(*p)); if (p) { if (!ast_strlen_zero(opts)) { if (strchr(opts, 'm')) flags |= NBS_FLAG_MUTE; if (strchr(opts, 'o')) flags |= NBS_FLAG_OVERSPEAK; if (strchr(opts, 'e')) flags |= NBS_FLAG_EMERGENCY; if (strchr(opts, 'O')) flags |= NBS_FLAG_OVERRIDE; } else flags = NBS_FLAG_OVERSPEAK; ast_copy_string(p->stream, stream, sizeof(p->stream)); p->nbs = nbs_newstream("asterisk", stream, flags); if (!p->nbs) { ast_log(LOG_WARNING, "Unable to allocate new NBS stream '%s' with flags %d\n", stream, flags); ast_free(p); p = NULL; } else { /* Set for 8000 hz mono, 640 samples */ nbs_setbitrate(p->nbs, 8000); nbs_setchannels(p->nbs, 1); nbs_setblocksize(p->nbs, 640); nbs_setblocking(p->nbs, 0); } } return p; } static int nbs_hangup(struct ast_channel *ast) { struct nbs_pvt *p; p = ast_channel_tech_pvt(ast); ast_debug(1, "nbs_hangup(%s)\n", ast_channel_name(ast)); if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } nbs_destroy(p); ast_channel_tech_pvt_set(ast, NULL); ast_setstate(ast, AST_STATE_DOWN); return 0; } static struct ast_frame *nbs_xread(struct ast_channel *ast) { ast_debug(1, "Returning null frame on %s\n", ast_channel_name(ast)); return &ast_null_frame; } static int nbs_xwrite(struct ast_channel *ast, struct ast_frame *frame) { struct nbs_pvt *p = ast_channel_tech_pvt(ast); if (ast_channel_state(ast) != AST_STATE_UP) { /* Don't try tos end audio on-hook */ return 0; } if (nbs_write(p->nbs, frame->data.ptr, frame->datalen / 2) < 0) return -1; return 0; } static struct ast_channel *nbs_new(struct nbs_pvt *i, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_channel *tmp; tmp = ast_channel_alloc(1, state, 0, 0, "", "s", context, assignedids, requestor, 0, "NBS/%s", i->stream); if (tmp) { ast_channel_tech_set(tmp, &nbs_tech); ast_channel_set_fd(tmp, 0, nbs_fd(i->nbs)); ast_channel_nativeformats_set(tmp, nbs_tech.capabilities); ast_channel_set_rawreadformat(tmp, ast_format_slin); ast_channel_set_rawwriteformat(tmp, ast_format_slin); ast_channel_set_writeformat(tmp, ast_format_slin); ast_channel_set_readformat(tmp, ast_format_slin); if (state == AST_STATE_RING) ast_channel_rings_set(tmp, 1); ast_channel_tech_pvt_set(tmp, i); ast_channel_context_set(tmp, context); ast_channel_exten_set(tmp, "s"); ast_channel_language_set(tmp, ""); i->owner = tmp; i->u = ast_module_user_add(tmp); ast_channel_unlock(tmp); if (state != AST_STATE_DOWN) { if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); } } } else ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); return tmp; } static struct ast_channel *nbs_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct nbs_pvt *p; struct ast_channel *tmp = NULL; if (ast_format_cap_iscompatible_format(cap, ast_format_slin) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *cap_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &cap_buf)); return NULL; } p = nbs_alloc(data); if (p) { tmp = nbs_new(p, AST_STATE_DOWN, assignedids, requestor); if (!tmp) nbs_destroy(p); } return tmp; } static int unload_module(void) { /* First, take us out of the channel loop */ ast_channel_unregister(&nbs_tech); ao2_ref(nbs_tech.capabilities, -1); nbs_tech.capabilities = NULL; return 0; } static int load_module(void) { if (!(nbs_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_FAILURE; } ast_format_cap_append(nbs_tech.capabilities, ast_format_slin, 0); /* Make sure we can register our channel type */ if (ast_channel_register(&nbs_tech)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); return AST_MODULE_LOAD_DECLINE; } return AST_MODULE_LOAD_SUCCESS; } AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Network Broadcast Sound Support"); asterisk-13.1.0/channels/chan_console.c0000644000000000000000000012204512427457051016524 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2006 - 2008, Digium, Inc. * * Russell Bryant * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief Cross-platform console channel driver * * \author Russell Bryant * * \note Some of the code in this file came from chan_oss and chan_alsa. * chan_oss, Mark Spencer * chan_oss, Luigi Rizzo * chan_alsa, Matthew Fredrickson * * \ingroup channel_drivers * * Portaudio http://www.portaudio.com/ * * To install portaudio v19 from svn, check it out using the following command: * - svn co https://www.portaudio.com/repos/portaudio/branches/v19-devel * * \note Since this works with any audio system that libportaudio supports, * including ALSA and OSS, this may someday deprecate chan_alsa and chan_oss. * However, before that can be done, it needs to *at least* have all of the * features that these other channel drivers have. The features implemented * in at least one of the other console channel drivers that are not yet * implemented here are: * * - Set Auto-answer from the dialplan * - transfer CLI command * - boost CLI command and .conf option * - console_video support */ /*! \li \ref chan_console.c uses the configuration file \ref console.conf * \addtogroup configuration_file */ /*! \page console.conf console.conf * \verbinclude console.conf.sample */ /*** MODULEINFO portaudio extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 427557 $") #include /* SIGURG */ #include #include "asterisk/module.h" #include "asterisk/channel.h" #include "asterisk/pbx.h" #include "asterisk/causes.h" #include "asterisk/cli.h" #include "asterisk/musiconhold.h" #include "asterisk/callerid.h" #include "asterisk/astobj2.h" #include "asterisk/stasis_channels.h" #include "asterisk/format_cache.h" /*! * \brief The sample rate to request from PortAudio * * \todo Make this optional. If this is only going to talk to 8 kHz endpoints, * then it makes sense to use 8 kHz natively. */ #define SAMPLE_RATE 16000 /*! * \brief The number of samples to configure the portaudio stream for * * 320 samples (20 ms) is the most common frame size in Asterisk. So, the code * in this module reads 320 sample frames from the portaudio stream and queues * them up on the Asterisk channel. Frames of any size can be written to a * portaudio stream, but the portaudio documentation does say that for high * performance applications, the data should be written to Pa_WriteStream in * the same size as what is used to initialize the stream. */ #define NUM_SAMPLES 320 /*! \brief Mono Input */ #define INPUT_CHANNELS 1 /*! \brief Mono Output */ #define OUTPUT_CHANNELS 1 /*! * \brief Maximum text message length * \note This should be changed if there is a common definition somewhere * that defines the maximum length of a text message. */ #define TEXT_SIZE 256 /*! \brief Dance, Kirby, Dance! @{ */ #define V_BEGIN " --- <(\"<) --- " #define V_END " --- (>\")> ---\n" /*! @} */ static const char config_file[] = "console.conf"; /*! * \brief Console pvt structure * * Currently, this is a singleton object. However, multiple instances will be * needed when this module is updated for multiple device support. */ static struct console_pvt { AST_DECLARE_STRING_FIELDS( /*! Name of the device */ AST_STRING_FIELD(name); AST_STRING_FIELD(input_device); AST_STRING_FIELD(output_device); /*! Default context for outgoing calls */ AST_STRING_FIELD(context); /*! Default extension for outgoing calls */ AST_STRING_FIELD(exten); /*! Default CallerID number */ AST_STRING_FIELD(cid_num); /*! Default CallerID name */ AST_STRING_FIELD(cid_name); /*! Default MOH class to listen to, if: * - No MOH class set on the channel * - Peer channel putting this device on hold did not suggest a class */ AST_STRING_FIELD(mohinterpret); /*! Default language */ AST_STRING_FIELD(language); /*! Default parkinglot */ AST_STRING_FIELD(parkinglot); ); /*! Current channel for this device */ struct ast_channel *owner; /*! Current PortAudio stream for this device */ PaStream *stream; /*! A frame for preparing to queue on to the channel */ struct ast_frame fr; /*! Running = 1, Not running = 0 */ unsigned int streamstate:1; /*! On-hook = 0, Off-hook = 1 */ unsigned int hookstate:1; /*! Unmuted = 0, Muted = 1 */ unsigned int muted:1; /*! Automatically answer incoming calls */ unsigned int autoanswer:1; /*! Ignore context in the console dial CLI command */ unsigned int overridecontext:1; /*! Set during a reload so that we know to destroy this if it is no longer * in the configuration file. */ unsigned int destroy:1; /*! ID for the stream monitor thread */ pthread_t thread; } globals; AST_MUTEX_DEFINE_STATIC(globals_lock); static struct ao2_container *pvts; #define NUM_PVT_BUCKETS 7 static struct console_pvt *active_pvt; AST_RWLOCK_DEFINE_STATIC(active_lock); /*! * \brief Global jitterbuffer configuration * * \note Disabled by default. * \note Values shown here match the defaults shown in console.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; /*! Channel Technology Callbacks @{ */ static struct ast_channel *console_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int console_digit_begin(struct ast_channel *c, char digit); static int console_digit_end(struct ast_channel *c, char digit, unsigned int duration); static int console_text(struct ast_channel *c, const char *text); static int console_hangup(struct ast_channel *c); static int console_answer(struct ast_channel *c); static struct ast_frame *console_read(struct ast_channel *chan); static int console_call(struct ast_channel *c, const char *dest, int timeout); static int console_write(struct ast_channel *chan, struct ast_frame *f); static int console_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen); static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); /*! @} */ static struct ast_channel_tech console_tech = { .type = "Console", .description = "Console Channel Driver", .requester = console_request, .send_digit_begin = console_digit_begin, .send_digit_end = console_digit_end, .send_text = console_text, .hangup = console_hangup, .answer = console_answer, .read = console_read, .call = console_call, .write = console_write, .indicate = console_indicate, .fixup = console_fixup, }; /*! \brief lock a console_pvt struct */ #define console_pvt_lock(pvt) ao2_lock(pvt) /*! \brief unlock a console_pvt struct */ #define console_pvt_unlock(pvt) ao2_unlock(pvt) static inline struct console_pvt *ref_pvt(struct console_pvt *pvt) { if (pvt) ao2_ref(pvt, +1); return pvt; } static inline struct console_pvt *unref_pvt(struct console_pvt *pvt) { ao2_ref(pvt, -1); return NULL; } static struct console_pvt *find_pvt(const char *name) { struct console_pvt tmp_pvt = { .name = name, }; return ao2_find(pvts, &tmp_pvt, OBJ_POINTER); } /*! * \brief Stream monitor thread * * \arg data A pointer to the console_pvt structure that contains the portaudio * stream that needs to be monitored. * * This function runs in its own thread to monitor data coming in from a * portaudio stream. When enough data is available, it is queued up to * be read from the Asterisk channel. */ static void *stream_monitor(void *data) { struct console_pvt *pvt = data; char buf[NUM_SAMPLES * sizeof(int16_t)]; PaError res; struct ast_frame f = { .frametype = AST_FRAME_VOICE, .subclass.format = ast_format_slin16, .src = "console_stream_monitor", .data.ptr = buf, .datalen = sizeof(buf), .samples = sizeof(buf) / sizeof(int16_t), }; for (;;) { pthread_testcancel(); res = Pa_ReadStream(pvt->stream, buf, sizeof(buf) / sizeof(int16_t)); pthread_testcancel(); if (!pvt->owner) { return NULL; } if (res == paNoError) ast_queue_frame(pvt->owner, &f); } return NULL; } static int open_stream(struct console_pvt *pvt) { int res = paInternalError; if (!strcasecmp(pvt->input_device, "default") && !strcasecmp(pvt->output_device, "default")) { res = Pa_OpenDefaultStream(&pvt->stream, INPUT_CHANNELS, OUTPUT_CHANNELS, paInt16, SAMPLE_RATE, NUM_SAMPLES, NULL, NULL); } else { PaStreamParameters input_params = { .channelCount = 1, .sampleFormat = paInt16, .suggestedLatency = (1.0 / 50.0), /* 20 ms */ .device = paNoDevice, }; PaStreamParameters output_params = { .channelCount = 1, .sampleFormat = paInt16, .suggestedLatency = (1.0 / 50.0), /* 20 ms */ .device = paNoDevice, }; PaDeviceIndex idx, num_devices, def_input, def_output; if (!(num_devices = Pa_GetDeviceCount())) return res; def_input = Pa_GetDefaultInputDevice(); def_output = Pa_GetDefaultOutputDevice(); for (idx = 0; idx < num_devices && (input_params.device == paNoDevice || output_params.device == paNoDevice); idx++) { const PaDeviceInfo *dev = Pa_GetDeviceInfo(idx); if (dev->maxInputChannels) { if ( (idx == def_input && !strcasecmp(pvt->input_device, "default")) || !strcasecmp(pvt->input_device, dev->name) ) input_params.device = idx; } if (dev->maxOutputChannels) { if ( (idx == def_output && !strcasecmp(pvt->output_device, "default")) || !strcasecmp(pvt->output_device, dev->name) ) output_params.device = idx; } } if (input_params.device == paNoDevice) ast_log(LOG_ERROR, "No input device found for console device '%s'\n", pvt->name); if (output_params.device == paNoDevice) ast_log(LOG_ERROR, "No output device found for console device '%s'\n", pvt->name); res = Pa_OpenStream(&pvt->stream, &input_params, &output_params, SAMPLE_RATE, NUM_SAMPLES, paNoFlag, NULL, NULL); } return res; } static int start_stream(struct console_pvt *pvt) { PaError res; int ret_val = 0; console_pvt_lock(pvt); /* It is possible for console_hangup to be called before the * stream is started, if this is the case pvt->owner will be NULL * and start_stream should be aborted. */ if (pvt->streamstate || !pvt->owner) goto return_unlock; pvt->streamstate = 1; ast_debug(1, "Starting stream\n"); res = open_stream(pvt); if (res != paNoError) { ast_log(LOG_WARNING, "Failed to open stream - (%d) %s\n", res, Pa_GetErrorText(res)); ret_val = -1; goto return_unlock; } res = Pa_StartStream(pvt->stream); if (res != paNoError) { ast_log(LOG_WARNING, "Failed to start stream - (%d) %s\n", res, Pa_GetErrorText(res)); ret_val = -1; goto return_unlock; } if (ast_pthread_create_background(&pvt->thread, NULL, stream_monitor, pvt)) { ast_log(LOG_ERROR, "Failed to start stream monitor thread\n"); ret_val = -1; } return_unlock: console_pvt_unlock(pvt); return ret_val; } static int stop_stream(struct console_pvt *pvt) { if (!pvt->streamstate || pvt->thread == AST_PTHREADT_NULL) return 0; pthread_cancel(pvt->thread); pthread_kill(pvt->thread, SIGURG); pthread_join(pvt->thread, NULL); console_pvt_lock(pvt); Pa_AbortStream(pvt->stream); Pa_CloseStream(pvt->stream); pvt->stream = NULL; pvt->streamstate = 0; console_pvt_unlock(pvt); return 0; } /*! * \note Called with the pvt struct locked */ static struct ast_channel *console_new(struct console_pvt *pvt, const char *ext, const char *ctx, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_format_cap *caps; struct ast_channel *chan; caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { return NULL; } if (!(chan = ast_channel_alloc(1, state, pvt->cid_num, pvt->cid_name, NULL, ext, ctx, assignedids, requestor, 0, "Console/%s", pvt->name))) { ao2_ref(caps, -1); return NULL; } ast_channel_stage_snapshot(chan); ast_channel_tech_set(chan, &console_tech); ast_channel_set_readformat(chan, ast_format_slin16); ast_channel_set_writeformat(chan, ast_format_slin16); ast_format_cap_append(caps, ast_format_slin16, 0); ast_channel_nativeformats_set(chan, caps); ao2_ref(caps, -1); ast_channel_tech_pvt_set(chan, ref_pvt(pvt)); pvt->owner = chan; if (!ast_strlen_zero(pvt->language)) ast_channel_language_set(chan, pvt->language); ast_jb_configure(chan, &global_jbconf); ast_channel_stage_snapshot_done(chan); ast_channel_unlock(chan); if (state != AST_STATE_DOWN) { if (ast_pbx_start(chan)) { ast_channel_hangupcause_set(chan, AST_CAUSE_SWITCH_CONGESTION); ast_hangup(chan); chan = NULL; } else start_stream(pvt); } return chan; } static struct ast_channel *console_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct ast_channel *chan = NULL; struct console_pvt *pvt; if (!(pvt = find_pvt(data))) { ast_log(LOG_ERROR, "Console device '%s' not found\n", data); return NULL; } if (!(ast_format_cap_iscompatible(cap, console_tech.capabilities))) { struct ast_str *cap_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Channel requested with unsupported format(s): '%s'\n", ast_format_cap_get_names(cap, &cap_buf)); goto return_unref; } if (pvt->owner) { ast_log(LOG_NOTICE, "Console channel already active!\n"); *cause = AST_CAUSE_BUSY; goto return_unref; } console_pvt_lock(pvt); chan = console_new(pvt, NULL, NULL, AST_STATE_DOWN, assignedids, requestor); console_pvt_unlock(pvt); if (!chan) ast_log(LOG_WARNING, "Unable to create new Console channel!\n"); return_unref: unref_pvt(pvt); return chan; } static int console_digit_begin(struct ast_channel *c, char digit) { ast_verb(1, V_BEGIN "Console Received Beginning of Digit %c" V_END, digit); return -1; /* non-zero to request inband audio */ } static int console_digit_end(struct ast_channel *c, char digit, unsigned int duration) { ast_verb(1, V_BEGIN "Console Received End of Digit %c (duration %u)" V_END, digit, duration); return -1; /* non-zero to request inband audio */ } static int console_text(struct ast_channel *c, const char *text) { ast_verb(1, V_BEGIN "Console Received Text '%s'" V_END, text); return 0; } static int console_hangup(struct ast_channel *c) { struct console_pvt *pvt = ast_channel_tech_pvt(c); ast_verb(1, V_BEGIN "Hangup on Console" V_END); pvt->hookstate = 0; pvt->owner = NULL; stop_stream(pvt); ast_channel_tech_pvt_set(c, unref_pvt(pvt)); return 0; } static int console_answer(struct ast_channel *c) { struct console_pvt *pvt = ast_channel_tech_pvt(c); ast_verb(1, V_BEGIN "Call from Console has been Answered" V_END); ast_setstate(c, AST_STATE_UP); return start_stream(pvt); } /* * \brief Implementation of the ast_channel_tech read() callback * * Calling this function is harmless. However, if it does get called, it * is an indication that something weird happened that really shouldn't * have and is worth looking into. * * Why should this function not get called? Well, let me explain. There are * a couple of ways to pass on audio that has come from this channel. The way * that this channel driver uses is that once the audio is available, it is * wrapped in an ast_frame and queued onto the channel using ast_queue_frame(). * * The other method would be signalling to the core that there is audio waiting, * and that it needs to call the channel's read() callback to get it. The way * the channel gets signalled is that one or more file descriptors are placed * in the fds array on the ast_channel which the core will poll() on. When the * fd indicates that input is available, the read() callback is called. This * is especially useful when there is a dedicated file descriptor where the * audio is read from. An example would be the socket for an RTP stream. */ static struct ast_frame *console_read(struct ast_channel *chan) { ast_debug(1, "I should not be called ...\n"); return &ast_null_frame; } static int console_call(struct ast_channel *c, const char *dest, int timeout) { struct console_pvt *pvt = ast_channel_tech_pvt(c); enum ast_control_frame_type ctrl; ast_verb(1, V_BEGIN "Call to device '%s' on console from '%s' <%s>" V_END, dest, S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, ""), S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "")); console_pvt_lock(pvt); if (pvt->autoanswer) { pvt->hookstate = 1; console_pvt_unlock(pvt); ast_verb(1, V_BEGIN "Auto-answered" V_END); ctrl = AST_CONTROL_ANSWER; } else { console_pvt_unlock(pvt); ast_verb(1, V_BEGIN "Type 'console answer' to answer, or use the 'autoanswer' option " "for future calls" V_END); ctrl = AST_CONTROL_RINGING; ast_indicate(c, AST_CONTROL_RINGING); } ast_queue_control(c, ctrl); return start_stream(pvt); } static int console_write(struct ast_channel *chan, struct ast_frame *f) { struct console_pvt *pvt = ast_channel_tech_pvt(chan); Pa_WriteStream(pvt->stream, f->data.ptr, f->samples); return 0; } static int console_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen) { struct console_pvt *pvt = ast_channel_tech_pvt(chan); int res = 0; switch (cond) { case AST_CONTROL_BUSY: case AST_CONTROL_CONGESTION: case AST_CONTROL_RINGING: case AST_CONTROL_INCOMPLETE: case AST_CONTROL_PVT_CAUSE_CODE: case -1: res = -1; /* Ask for inband indications */ break; case AST_CONTROL_PROGRESS: case AST_CONTROL_PROCEEDING: case AST_CONTROL_VIDUPDATE: case AST_CONTROL_SRCUPDATE: break; case AST_CONTROL_HOLD: ast_verb(1, V_BEGIN "Console Has Been Placed on Hold" V_END); ast_moh_start(chan, data, pvt->mohinterpret); break; case AST_CONTROL_UNHOLD: ast_verb(1, V_BEGIN "Console Has Been Retrieved from Hold" V_END); ast_moh_stop(chan); break; default: ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, ast_channel_name(chan)); /* The core will play inband indications for us if appropriate */ res = -1; } return res; } static int console_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct console_pvt *pvt = ast_channel_tech_pvt(newchan); pvt->owner = newchan; return 0; } /*! * split a string in extension-context, returns pointers to malloc'ed * strings. * If we do not have 'overridecontext' then the last @ is considered as * a context separator, and the context is overridden. * This is usually not very necessary as you can play with the dialplan, * and it is nice not to need it because you have '@' in SIP addresses. * Return value is the buffer address. * * \note came from chan_oss */ static char *ast_ext_ctx(struct console_pvt *pvt, const char *src, char **ext, char **ctx) { if (ext == NULL || ctx == NULL) return NULL; /* error */ *ext = *ctx = NULL; if (src && *src != '\0') *ext = ast_strdup(src); if (*ext == NULL) return NULL; if (!pvt->overridecontext) { /* parse from the right */ *ctx = strrchr(*ext, '@'); if (*ctx) *(*ctx)++ = '\0'; } return *ext; } static struct console_pvt *get_active_pvt(void) { struct console_pvt *pvt; ast_rwlock_rdlock(&active_lock); pvt = ref_pvt(active_pvt); ast_rwlock_unlock(&active_lock); return pvt; } static char *cli_console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct console_pvt *pvt; char *res = CLI_SUCCESS; switch (cmd) { case CLI_INIT: e->command = "console {set|show} autoanswer [on|off]"; e->usage = "Usage: console {set|show} autoanswer [on|off]\n" " Enables or disables autoanswer feature. If used without\n" " argument, displays the current on/off status of autoanswer.\n" " The default value of autoanswer is in 'oss.conf'.\n"; return NULL; case CLI_GENERATE: return NULL; } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is set as active.\n"); return CLI_FAILURE; } if (a->argc == e->args - 1) { ast_cli(a->fd, "Auto answer is %s.\n", pvt->autoanswer ? "on" : "off"); unref_pvt(pvt); return CLI_SUCCESS; } if (a->argc != e->args) { unref_pvt(pvt); return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[e->args-1], "on")) pvt->autoanswer = 1; else if (!strcasecmp(a->argv[e->args - 1], "off")) pvt->autoanswer = 0; else res = CLI_SHOWUSAGE; unref_pvt(pvt); return res; } static char *cli_console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct console_pvt *pvt; if (cmd == CLI_INIT) { e->command = "console flash"; e->usage = "Usage: console flash\n" " Flashes the call currently placed on the console.\n"; return NULL; } else if (cmd == CLI_GENERATE) { return NULL; } if (a->argc != e->args) { return CLI_SHOWUSAGE; } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is set as active\n"); return CLI_FAILURE; } if (!pvt->owner) { ast_cli(a->fd, "No call to flash\n"); unref_pvt(pvt); return CLI_FAILURE; } pvt->hookstate = 0; ast_queue_control(pvt->owner, AST_CONTROL_FLASH); unref_pvt(pvt); return CLI_SUCCESS; } static char *cli_console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char *s = NULL; const char *mye = NULL, *myc = NULL; struct console_pvt *pvt; if (cmd == CLI_INIT) { e->command = "console dial"; e->usage = "Usage: console dial [extension[@context]]\n" " Dials a given extension (and context if specified)\n"; return NULL; } else if (cmd == CLI_GENERATE) { return NULL; } if (a->argc > e->args + 1) { return CLI_SHOWUSAGE; } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is currently set as active\n"); return CLI_FAILURE; } if (pvt->owner) { /* already in a call */ int i; struct ast_frame f = { AST_FRAME_DTMF }; const char *s; if (a->argc == e->args) { /* argument is mandatory here */ ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n"); unref_pvt(pvt); return CLI_FAILURE; } s = a->argv[e->args]; /* send the string one char at a time */ for (i = 0; i < strlen(s); i++) { f.subclass.integer = s[i]; ast_queue_frame(pvt->owner, &f); } unref_pvt(pvt); return CLI_SUCCESS; } /* if we have an argument split it into extension and context */ if (a->argc == e->args + 1) { char *ext = NULL, *con = NULL; s = ast_ext_ctx(pvt, a->argv[e->args], &ext, &con); ast_debug(1, "provided '%s', exten '%s' context '%s'\n", a->argv[e->args], mye, myc); mye = ext; myc = con; } /* supply default values if needed */ if (ast_strlen_zero(mye)) mye = pvt->exten; if (ast_strlen_zero(myc)) myc = pvt->context; if (ast_exists_extension(NULL, myc, mye, 1, NULL)) { console_pvt_lock(pvt); pvt->hookstate = 1; console_new(pvt, mye, myc, AST_STATE_RINGING, NULL, NULL); console_pvt_unlock(pvt); } else ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc); free(s); unref_pvt(pvt); return CLI_SUCCESS; } static char *cli_console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct console_pvt *pvt; if (cmd == CLI_INIT) { e->command = "console hangup"; e->usage = "Usage: console hangup\n" " Hangs up any call currently placed on the console.\n"; return NULL; } else if (cmd == CLI_GENERATE) { return NULL; } if (a->argc != e->args) { return CLI_SHOWUSAGE; } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is set as active\n"); return CLI_FAILURE; } if (!pvt->owner && !pvt->hookstate) { ast_cli(a->fd, "No call to hang up\n"); unref_pvt(pvt); return CLI_FAILURE; } pvt->hookstate = 0; if (pvt->owner) ast_queue_hangup(pvt->owner); unref_pvt(pvt); return CLI_SUCCESS; } static char *cli_console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { const char *s; struct console_pvt *pvt; char *res = CLI_SUCCESS; if (cmd == CLI_INIT) { e->command = "console {mute|unmute}"; e->usage = "Usage: console {mute|unmute}\n" " Mute/unmute the microphone.\n"; return NULL; } else if (cmd == CLI_GENERATE) { return NULL; } if (a->argc != e->args) { return CLI_SHOWUSAGE; } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is set as active\n"); return CLI_FAILURE; } s = a->argv[e->args-1]; if (!strcasecmp(s, "mute")) pvt->muted = 1; else if (!strcasecmp(s, "unmute")) pvt->muted = 0; else res = CLI_SHOWUSAGE; ast_verb(1, V_BEGIN "The Console is now %s" V_END, pvt->muted ? "Muted" : "Unmuted"); unref_pvt(pvt); return res; } static char *cli_list_available(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { PaDeviceIndex idx, num, def_input, def_output; if (cmd == CLI_INIT) { e->command = "console list available"; e->usage = "Usage: console list available\n" " List all available devices.\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc != e->args) return CLI_SHOWUSAGE; ast_cli(a->fd, "\n" "=============================================================\n" "=== Available Devices =======================================\n" "=============================================================\n" "===\n"); num = Pa_GetDeviceCount(); if (!num) { ast_cli(a->fd, "(None)\n"); return CLI_SUCCESS; } def_input = Pa_GetDefaultInputDevice(); def_output = Pa_GetDefaultOutputDevice(); for (idx = 0; idx < num; idx++) { const PaDeviceInfo *dev = Pa_GetDeviceInfo(idx); if (!dev) continue; ast_cli(a->fd, "=== ---------------------------------------------------------\n" "=== Device Name: %s\n", dev->name); if (dev->maxInputChannels) ast_cli(a->fd, "=== ---> %sInput Device\n", (idx == def_input) ? "Default " : ""); if (dev->maxOutputChannels) ast_cli(a->fd, "=== ---> %sOutput Device\n", (idx == def_output) ? "Default " : ""); ast_cli(a->fd, "=== ---------------------------------------------------------\n===\n"); } ast_cli(a->fd, "=============================================================\n\n"); return CLI_SUCCESS; } static char *cli_list_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_iterator i; struct console_pvt *pvt; if (cmd == CLI_INIT) { e->command = "console list devices"; e->usage = "Usage: console list devices\n" " List all configured devices.\n"; return NULL; } else if (cmd == CLI_GENERATE) return NULL; if (a->argc != e->args) return CLI_SHOWUSAGE; ast_cli(a->fd, "\n" "=============================================================\n" "=== Configured Devices ======================================\n" "=============================================================\n" "===\n"); i = ao2_iterator_init(pvts, 0); while ((pvt = ao2_iterator_next(&i))) { console_pvt_lock(pvt); ast_cli(a->fd, "=== ---------------------------------------------------------\n" "=== Device Name: %s\n" "=== ---> Active: %s\n" "=== ---> Input Device: %s\n" "=== ---> Output Device: %s\n" "=== ---> Context: %s\n" "=== ---> Extension: %s\n" "=== ---> CallerID Num: %s\n" "=== ---> CallerID Name: %s\n" "=== ---> MOH Interpret: %s\n" "=== ---> Language: %s\n" "=== ---> Parkinglot: %s\n" "=== ---> Muted: %s\n" "=== ---> Auto-Answer: %s\n" "=== ---> Override Context: %s\n" "=== ---------------------------------------------------------\n===\n", pvt->name, (pvt == active_pvt) ? "Yes" : "No", pvt->input_device, pvt->output_device, pvt->context, pvt->exten, pvt->cid_num, pvt->cid_name, pvt->mohinterpret, pvt->language, pvt->parkinglot, pvt->muted ? "Yes" : "No", pvt->autoanswer ? "Yes" : "No", pvt->overridecontext ? "Yes" : "No"); console_pvt_unlock(pvt); unref_pvt(pvt); } ao2_iterator_destroy(&i); ast_cli(a->fd, "=============================================================\n\n"); return CLI_SUCCESS; } /*! * \brief answer command from the console */ static char *cli_console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct console_pvt *pvt; switch (cmd) { case CLI_INIT: e->command = "console answer"; e->usage = "Usage: console answer\n" " Answers an incoming call on the console channel.\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is set as active\n"); return CLI_FAILURE; } if (a->argc != e->args) { unref_pvt(pvt); return CLI_SHOWUSAGE; } if (!pvt->owner) { ast_cli(a->fd, "No one is calling us\n"); unref_pvt(pvt); return CLI_FAILURE; } pvt->hookstate = 1; ast_indicate(pvt->owner, -1); ast_queue_control(pvt->owner, AST_CONTROL_ANSWER); unref_pvt(pvt); return CLI_SUCCESS; } /*! * \brief Console send text CLI command * * \note concatenate all arguments into a single string. argv is NULL-terminated * so we can use it right away */ static char *cli_console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char buf[TEXT_SIZE]; struct console_pvt *pvt; struct ast_frame f = { .frametype = AST_FRAME_TEXT, .data.ptr = buf, .src = "console_send_text", }; int len; if (cmd == CLI_INIT) { e->command = "console send text"; e->usage = "Usage: console send text \n" " Sends a text message for display on the remote terminal.\n"; return NULL; } else if (cmd == CLI_GENERATE) { return NULL; } pvt = get_active_pvt(); if (!pvt) { ast_cli(a->fd, "No console device is set as active\n"); return CLI_FAILURE; } if (a->argc < e->args + 1) { unref_pvt(pvt); return CLI_SHOWUSAGE; } if (!pvt->owner) { ast_cli(a->fd, "Not in a call\n"); unref_pvt(pvt); return CLI_FAILURE; } ast_join(buf, sizeof(buf) - 1, a->argv + e->args); if (ast_strlen_zero(buf)) { unref_pvt(pvt); return CLI_SHOWUSAGE; } len = strlen(buf); buf[len] = '\n'; f.datalen = len + 1; ast_queue_frame(pvt->owner, &f); unref_pvt(pvt); return CLI_SUCCESS; } static void set_active(struct console_pvt *pvt, const char *value) { if (pvt == &globals) { ast_log(LOG_ERROR, "active is only valid as a per-device setting\n"); return; } if (!ast_true(value)) return; ast_rwlock_wrlock(&active_lock); if (active_pvt) unref_pvt(active_pvt); active_pvt = ref_pvt(pvt); ast_rwlock_unlock(&active_lock); } static char *cli_console_active(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct console_pvt *pvt; switch (cmd) { case CLI_INIT: e->command = "console {set|show} active"; e->usage = "Usage: console {set|show} active []\n" " Set or show the active console device for the Asterisk CLI.\n"; return NULL; case CLI_GENERATE: if (a->pos == e->args) { struct ao2_iterator i; int x = 0; char *res = NULL; i = ao2_iterator_init(pvts, 0); while ((pvt = ao2_iterator_next(&i))) { if (++x > a->n && !strncasecmp(pvt->name, a->word, strlen(a->word))) res = ast_strdup(pvt->name); unref_pvt(pvt); if (res) { ao2_iterator_destroy(&i); return res; } } ao2_iterator_destroy(&i); } return NULL; } if (a->argc < e->args) return CLI_SHOWUSAGE; if (a->argc == 3) { pvt = get_active_pvt(); if (!pvt) ast_cli(a->fd, "No device is currently set as the active console device.\n"); else { console_pvt_lock(pvt); ast_cli(a->fd, "The active console device is '%s'.\n", pvt->name); console_pvt_unlock(pvt); pvt = unref_pvt(pvt); } return CLI_SUCCESS; } if (!(pvt = find_pvt(a->argv[e->args - 1]))) { ast_cli(a->fd, "Could not find a device called '%s'.\n", a->argv[e->args]); return CLI_FAILURE; } set_active(pvt, "yes"); console_pvt_lock(pvt); ast_cli(a->fd, "The active console device has been set to '%s'\n", pvt->name); console_pvt_unlock(pvt); unref_pvt(pvt); return CLI_SUCCESS; } static struct ast_cli_entry cli_console[] = { AST_CLI_DEFINE(cli_console_dial, "Dial an extension from the console"), AST_CLI_DEFINE(cli_console_hangup, "Hangup a call on the console"), AST_CLI_DEFINE(cli_console_mute, "Disable/Enable mic input"), AST_CLI_DEFINE(cli_console_answer, "Answer an incoming console call"), AST_CLI_DEFINE(cli_console_sendtext, "Send text to a connected party"), AST_CLI_DEFINE(cli_console_flash, "Send a flash to the connected party"), AST_CLI_DEFINE(cli_console_autoanswer, "Turn autoanswer on or off"), AST_CLI_DEFINE(cli_list_available, "List available devices"), AST_CLI_DEFINE(cli_list_devices, "List configured devices"), AST_CLI_DEFINE(cli_console_active, "View or Set the active console device"), }; /*! * \brief Set default values for a pvt struct * * \note This function expects the pvt lock to be held. */ static void set_pvt_defaults(struct console_pvt *pvt) { if (pvt == &globals) { ast_string_field_set(pvt, mohinterpret, "default"); ast_string_field_set(pvt, context, "default"); ast_string_field_set(pvt, exten, "s"); ast_string_field_set(pvt, language, ""); ast_string_field_set(pvt, cid_num, ""); ast_string_field_set(pvt, cid_name, ""); ast_string_field_set(pvt, parkinglot, ""); pvt->overridecontext = 0; pvt->autoanswer = 0; } else { ast_mutex_lock(&globals_lock); ast_string_field_set(pvt, mohinterpret, globals.mohinterpret); ast_string_field_set(pvt, context, globals.context); ast_string_field_set(pvt, exten, globals.exten); ast_string_field_set(pvt, language, globals.language); ast_string_field_set(pvt, cid_num, globals.cid_num); ast_string_field_set(pvt, cid_name, globals.cid_name); ast_string_field_set(pvt, parkinglot, globals.parkinglot); pvt->overridecontext = globals.overridecontext; pvt->autoanswer = globals.autoanswer; ast_mutex_unlock(&globals_lock); } } static void store_callerid(struct console_pvt *pvt, const char *value) { char cid_name[256]; char cid_num[256]; ast_callerid_split(value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); ast_string_field_set(pvt, cid_name, cid_name); ast_string_field_set(pvt, cid_num, cid_num); } /*! * \brief Store a configuration parameter in a pvt struct * * \note This function expects the pvt lock to be held. */ static void store_config_core(struct console_pvt *pvt, const char *var, const char *value) { if (pvt == &globals && !ast_jb_read_conf(&global_jbconf, var, value)) return; CV_START(var, value); CV_STRFIELD("context", pvt, context); CV_STRFIELD("extension", pvt, exten); CV_STRFIELD("mohinterpret", pvt, mohinterpret); CV_STRFIELD("language", pvt, language); CV_F("callerid", store_callerid(pvt, value)); CV_BOOL("overridecontext", pvt->overridecontext); CV_BOOL("autoanswer", pvt->autoanswer); CV_STRFIELD("parkinglot", pvt, parkinglot); if (pvt != &globals) { CV_F("active", set_active(pvt, value)) CV_STRFIELD("input_device", pvt, input_device); CV_STRFIELD("output_device", pvt, output_device); } ast_log(LOG_WARNING, "Unknown option '%s'\n", var); CV_END; } static void pvt_destructor(void *obj) { struct console_pvt *pvt = obj; ast_string_field_free_memory(pvt); } static int init_pvt(struct console_pvt *pvt, const char *name) { pvt->thread = AST_PTHREADT_NULL; if (ast_string_field_init(pvt, 32)) return -1; ast_string_field_set(pvt, name, S_OR(name, "")); return 0; } static void build_device(struct ast_config *cfg, const char *name) { struct ast_variable *v; struct console_pvt *pvt; int new = 0; if ((pvt = find_pvt(name))) { console_pvt_lock(pvt); set_pvt_defaults(pvt); pvt->destroy = 0; } else { if (!(pvt = ao2_alloc(sizeof(*pvt), pvt_destructor))) return; init_pvt(pvt, name); set_pvt_defaults(pvt); new = 1; } for (v = ast_variable_browse(cfg, name); v; v = v->next) store_config_core(pvt, v->name, v->value); if (new) ao2_link(pvts, pvt); else console_pvt_unlock(pvt); unref_pvt(pvt); } static int pvt_mark_destroy_cb(void *obj, void *arg, int flags) { struct console_pvt *pvt = obj; pvt->destroy = 1; return 0; } static void destroy_pvts(void) { struct ao2_iterator i; struct console_pvt *pvt; i = ao2_iterator_init(pvts, 0); while ((pvt = ao2_iterator_next(&i))) { if (pvt->destroy) { ao2_unlink(pvts, pvt); ast_rwlock_wrlock(&active_lock); if (active_pvt == pvt) active_pvt = unref_pvt(pvt); ast_rwlock_unlock(&active_lock); } unref_pvt(pvt); } ao2_iterator_destroy(&i); } /*! * \brief Load the configuration * \param reload if this was called due to a reload * \retval 0 success * \retval -1 failure */ static int load_config(int reload) { struct ast_config *cfg; struct ast_variable *v; struct ast_flags config_flags = { 0 }; char *context = NULL; /* default values */ memcpy(&global_jbconf, &default_jbconf, sizeof(global_jbconf)); ast_mutex_lock(&globals_lock); set_pvt_defaults(&globals); ast_mutex_unlock(&globals_lock); if (!(cfg = ast_config_load(config_file, config_flags))) { ast_log(LOG_NOTICE, "Unable to open configuration file %s!\n", config_file); return -1; } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_NOTICE, "Config file %s has an invalid format\n", config_file); return -1; } ao2_callback(pvts, OBJ_NODATA, pvt_mark_destroy_cb, NULL); ast_mutex_lock(&globals_lock); for (v = ast_variable_browse(cfg, "general"); v; v = v->next) store_config_core(&globals, v->name, v->value); ast_mutex_unlock(&globals_lock); while ((context = ast_category_browse(cfg, context))) { if (strcasecmp(context, "general")) build_device(cfg, context); } ast_config_destroy(cfg); destroy_pvts(); return 0; } static int pvt_hash_cb(const void *obj, const int flags) { const struct console_pvt *pvt = obj; return ast_str_case_hash(pvt->name); } static int pvt_cmp_cb(void *obj, void *arg, int flags) { struct console_pvt *pvt = obj, *pvt2 = arg; return !strcasecmp(pvt->name, pvt2->name) ? CMP_MATCH | CMP_STOP : 0; } static void stop_streams(void) { struct console_pvt *pvt; struct ao2_iterator i; i = ao2_iterator_init(pvts, 0); while ((pvt = ao2_iterator_next(&i))) { if (pvt->hookstate) stop_stream(pvt); unref_pvt(pvt); } ao2_iterator_destroy(&i); } static int unload_module(void) { ao2_ref(console_tech.capabilities, -1); console_tech.capabilities = NULL; ast_channel_unregister(&console_tech); ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console)); stop_streams(); Pa_Terminate(); /* Will unref all the pvts so they will get destroyed, too */ ao2_ref(pvts, -1); pvt_destructor(&globals); return 0; } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { PaError res; if (!(console_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append(console_tech.capabilities, ast_format_slin16, 0); init_pvt(&globals, NULL); if (!(pvts = ao2_container_alloc(NUM_PVT_BUCKETS, pvt_hash_cb, pvt_cmp_cb))) goto return_error; if (load_config(0)) goto return_error; res = Pa_Initialize(); if (res != paNoError) { ast_log(LOG_WARNING, "Failed to initialize audio system - (%d) %s\n", res, Pa_GetErrorText(res)); goto return_error_pa_init; } if (ast_channel_register(&console_tech)) { ast_log(LOG_ERROR, "Unable to register channel type 'Console'\n"); goto return_error_chan_reg; } if (ast_cli_register_multiple(cli_console, ARRAY_LEN(cli_console))) goto return_error_cli_reg; return AST_MODULE_LOAD_SUCCESS; return_error_cli_reg: ast_cli_unregister_multiple(cli_console, ARRAY_LEN(cli_console)); return_error_chan_reg: ast_channel_unregister(&console_tech); return_error_pa_init: Pa_Terminate(); return_error: if (pvts) ao2_ref(pvts, -1); pvts = NULL; ao2_ref(console_tech.capabilities, -1); console_tech.capabilities = NULL; pvt_destructor(&globals); return AST_MODULE_LOAD_DECLINE; } static int reload(void) { return load_config(1); } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Console Channel Driver", .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, ); asterisk-13.1.0/channels/chan_multicast_rtp.c0000644000000000000000000001470312364505025017750 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2009, Digium, Inc. * * Joshua Colp * Andreas 'MacBrody' Brodmann * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \author Joshua Colp * \author Andreas 'MacBrody' Broadmann * * \brief Multicast RTP Paging Channel * * \ingroup channel_drivers */ /*** MODULEINFO core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $") #include #include #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/sched.h" #include "asterisk/io.h" #include "asterisk/acl.h" #include "asterisk/callerid.h" #include "asterisk/file.h" #include "asterisk/cli.h" #include "asterisk/app.h" #include "asterisk/rtp_engine.h" #include "asterisk/causes.h" static const char tdesc[] = "Multicast RTP Paging Channel Driver"; /* Forward declarations */ static struct ast_channel *multicast_rtp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int multicast_rtp_call(struct ast_channel *ast, const char *dest, int timeout); static int multicast_rtp_hangup(struct ast_channel *ast); static struct ast_frame *multicast_rtp_read(struct ast_channel *ast); static int multicast_rtp_write(struct ast_channel *ast, struct ast_frame *f); /* Channel driver declaration */ static struct ast_channel_tech multicast_rtp_tech = { .type = "MulticastRTP", .description = tdesc, .requester = multicast_rtp_request, .call = multicast_rtp_call, .hangup = multicast_rtp_hangup, .read = multicast_rtp_read, .write = multicast_rtp_write, }; /*! \brief Function called when we should read a frame from the channel */ static struct ast_frame *multicast_rtp_read(struct ast_channel *ast) { return &ast_null_frame; } /*! \brief Function called when we should write a frame to the channel */ static int multicast_rtp_write(struct ast_channel *ast, struct ast_frame *f) { struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); return ast_rtp_instance_write(instance, f); } /*! \brief Function called when we should actually call the destination */ static int multicast_rtp_call(struct ast_channel *ast, const char *dest, int timeout) { struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); ast_queue_control(ast, AST_CONTROL_ANSWER); return ast_rtp_instance_activate(instance); } /*! \brief Function called when we should hang the channel up */ static int multicast_rtp_hangup(struct ast_channel *ast) { struct ast_rtp_instance *instance = ast_channel_tech_pvt(ast); ast_rtp_instance_destroy(instance); ast_channel_tech_pvt_set(ast, NULL); return 0; } /*! \brief Function called when we should prepare to call the destination */ static struct ast_channel *multicast_rtp_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { char *tmp = ast_strdupa(data), *multicast_type = tmp, *destination, *control; struct ast_rtp_instance *instance; struct ast_sockaddr control_address; struct ast_sockaddr destination_address; struct ast_channel *chan; struct ast_format_cap *caps = NULL; struct ast_format *fmt = NULL; fmt = ast_format_cap_get_format(cap, 0); ast_sockaddr_setnull(&control_address); /* If no type was given we can't do anything */ if (ast_strlen_zero(multicast_type)) { goto failure; } if (!(destination = strchr(tmp, '/'))) { goto failure; } *destination++ = '\0'; if ((control = strchr(destination, '/'))) { *control++ = '\0'; if (!ast_sockaddr_parse(&control_address, control, PARSE_PORT_REQUIRE)) { goto failure; } } if (!ast_sockaddr_parse(&destination_address, destination, PARSE_PORT_REQUIRE)) { goto failure; } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { goto failure; } if (!(instance = ast_rtp_instance_new("multicast", NULL, &control_address, multicast_type))) { goto failure; } if (!(chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids, requestor, 0, "MulticastRTP/%p", instance))) { ast_rtp_instance_destroy(instance); goto failure; } ast_rtp_instance_set_channel_id(instance, ast_channel_uniqueid(chan)); ast_rtp_instance_set_remote_address(instance, &destination_address); ast_channel_tech_set(chan, &multicast_rtp_tech); ast_format_cap_append(caps, fmt, 0); ast_channel_nativeformats_set(chan, caps); ast_channel_set_writeformat(chan, fmt); ast_channel_set_rawwriteformat(chan, fmt); ast_channel_set_readformat(chan, fmt); ast_channel_set_rawreadformat(chan, fmt); ast_channel_tech_pvt_set(chan, instance); ast_channel_unlock(chan); ao2_ref(fmt, -1); ao2_ref(caps, -1); return chan; failure: ao2_cleanup(fmt); ao2_cleanup(caps); *cause = AST_CAUSE_FAILURE; return NULL; } /*! \brief Function called when our module is loaded */ static int load_module(void) { if (!(multicast_rtp_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append_by_type(multicast_rtp_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN); if (ast_channel_register(&multicast_rtp_tech)) { ast_log(LOG_ERROR, "Unable to register channel class 'MulticastRTP'\n"); ao2_ref(multicast_rtp_tech.capabilities, -1); multicast_rtp_tech.capabilities = NULL; return AST_MODULE_LOAD_DECLINE; } return AST_MODULE_LOAD_SUCCESS; } /*! \brief Function called when our module is unloaded */ static int unload_module(void) { ast_channel_unregister(&multicast_rtp_tech); ao2_ref(multicast_rtp_tech.capabilities, -1); multicast_rtp_tech.capabilities = NULL; return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Multicast RTP Paging Channel", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_CHANNEL_DRIVER, ); asterisk-13.1.0/channels/chan_sip.c0000644000000000000000000451056712442055601015663 0ustar rootroot/* * 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. */ /*! * \file * \brief Implementation of Session Initiation Protocol * * \author Mark Spencer * * See Also: * \arg \ref AstCREDITS * * Implementation of RFC 3261 - without S/MIME, and experimental TCP and TLS support * Configuration file \link Config_sip sip.conf \endlink * * ********** IMPORTANT * * \note TCP/TLS support is EXPERIMENTAL and WILL CHANGE. This applies to configuration * settings, dialplan commands and dialplans apps/functions * See \ref sip_tcp_tls * * * ******** General TODO:s * \todo Better support of forking * \todo VIA branch tag transaction checking * \todo Transaction support * * ******** Wishlist: Improvements * - Support of SIP domains for devices, so that we match on username\@domain in the From: header * - Connect registrations with a specific device on the incoming call. It's not done * automatically in Asterisk * * \ingroup channel_drivers * * \par Overview of the handling of SIP sessions * The SIP channel handles several types of SIP sessions, or dialogs, * not all of them being "telephone calls". * - Incoming calls that will be sent to the PBX core * - Outgoing calls, generated by the PBX * - SIP subscriptions and notifications of states and voicemail messages * - SIP registrations, both inbound and outbound * - SIP peer management (peerpoke, OPTIONS) * - SIP text messages * * In the SIP channel, there's a list of active SIP dialogs, which includes * all of these when they are active. "sip show channels" in the CLI will * show most of these, excluding subscriptions which are shown by * "sip show subscriptions" * * \par incoming packets * Incoming packets are received in the monitoring thread, then handled by * sipsock_read() for udp only. In tcp, packets are read by the tcp_helper thread. * sipsock_read() function parses the packet and matches an existing * dialog or starts a new SIP dialog. * * sipsock_read sends the packet to handle_incoming(), that parses a bit more. * If it is a response to an outbound request, the packet is sent to handle_response(). * If it is a request, handle_incoming() sends it to one of a list of functions * depending on the request type - INVITE, OPTIONS, REFER, BYE, CANCEL etc * sipsock_read locks the ast_channel if it exists (an active call) and * unlocks it after we have processed the SIP message. * * A new INVITE is sent to handle_request_invite(), that will end up * starting a new channel in the PBX, the new channel after that executing * in a separate channel thread. This is an incoming "call". * When the call is answered, either by a bridged channel or the PBX itself * the sip_answer() function is called. * * The actual media - Video or Audio - is mostly handled by the RTP subsystem * in rtp.c * * \par Outbound calls * Outbound calls are set up by the PBX through the sip_request_call() * function. After that, they are activated by sip_call(). * * \par Hanging up * The PBX issues a hangup on both incoming and outgoing calls through * the sip_hangup() function */ /*! \li \ref chan_sip.c uses configuration files \ref sip.conf and \ref sip_notify.conf * \addtogroup configuration_file */ /*! \page sip.conf sip.conf * \verbinclude sip.conf.sample */ /*! \page sip_notify.conf sip_notify.conf * \verbinclude sip_notify.conf.sample */ /*! * \page sip_tcp_tls SIP TCP and TLS support * * \par tcpfixes TCP implementation changes needed * \todo Fix TCP/TLS handling in dialplan, SRV records, transfers and much more * \todo Save TCP/TLS sessions in registry * If someone registers a SIPS uri, this forces us to set up a TLS connection back. * \todo Add TCP/TLS information to function SIPPEER and CHANNEL function * \todo If tcpenable=yes, we must open a TCP socket on the same address as the IP for UDP. * The tcpbindaddr config option should only be used to open ADDITIONAL ports * So we should propably go back to * bindaddr= the default address to bind to. If tcpenable=yes, then bind this to both udp and TCP * if tlsenable=yes, open TLS port (provided we also have cert) * tcpbindaddr = extra address for additional TCP connections * tlsbindaddr = extra address for additional TCP/TLS connections * udpbindaddr = extra address for additional UDP connections * These three options should take multiple IP/port pairs * Note: Since opening additional listen sockets is a *new* feature we do not have today * the XXXbindaddr options needs to be disabled until we have support for it * * \todo re-evaluate the transport= setting in sip.conf. This is right now not well * thought of. If a device in sip.conf contacts us via TCP, we should not switch transport, * even if udp is the configured first transport. * * \todo Be prepared for one outbound and another incoming socket per pvt. This applies * specially to communication with other peers (proxies). * \todo We need to test TCP sessions with SIP proxies and in regards * to the SIP outbound specs. * \todo ;transport=tls was deprecated in RFC3261 and should not be used at all. See section 26.2.2. * * \todo If the message is smaller than the given Content-length, the request should get a 400 Bad request * message. If it's a response, it should be dropped. (RFC 3261, Section 18.3) * \todo Since we have had multidomain support in Asterisk for quite a while, we need to support * multiple domains in our TLS implementation, meaning one socket and one cert per domain * \todo Selection of transport for a request needs to be done after we've parsed all route headers, * also considering outbound proxy options. * First request: Outboundproxy, routes, (reg contact or URI. If URI doesn't have port: DNS naptr, srv, AAA) * Intermediate requests: Outboundproxy(only when forced), routes, contact/uri * DNS naptr support is crucial. A SIP uri might lead to a TLS connection. * Also note that due to outbound proxy settings, a SIPS uri might have to be sent on UDP (not to recommend though) * \todo Default transports are set to UDP, which cause the wrong behaviour when contacting remote * devices directly from the dialplan. UDP is only a fallback if no other method works, * in order to be compatible with RFC2543 (SIP/1.0) devices. For transactions that exceed the * MTU (like INIVTE with video, audio and RTT) TCP should be preferred. * * When dialling unconfigured peers (with no port number) or devices in external domains * NAPTR records MUST be consulted to find configured transport. If they are not found, * SRV records for both TCP and UDP should be checked. If there's a record for TCP, use that. * If there's no record for TCP, then use UDP as a last resort. If there's no SRV records, * \note this only applies if there's no outbound proxy configured for the session. If an outbound * proxy is configured, these procedures might apply for locating the proxy and determining * the transport to use for communication with the proxy. * \par Other bugs to fix ---- * __set_address_from_contact(const char *fullcontact, struct sockaddr_in *sin, int tcp) * - sets TLS port as default for all TCP connections, unless other port is given in contact. * parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req) * - assumes that the contact the UA registers is using the same transport as the REGISTER request, which is * a bad guess. * - Does not save any information about TCP/TLS connected devices, which is a severe BUG, as discussed on the mailing list. * get_destination(struct sip_pvt *p, struct sip_request *oreq) * - Doesn't store the information that we got an incoming SIPS request in the channel, so that * we can require a secure signalling path OUT of Asterisk (on SIP or IAX2). Possibly, the call should * fail on in-secure signalling paths if there's no override in our configuration. At least, provide a * channel variable in the dialplan. * get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoing_req) * - As above, if we have a SIPS: uri in the refer-to header * - Does not check transport in refer_to uri. */ /*** MODULEINFO res_crypto res_http_websocket extended ***/ /*! \page sip_session_timers SIP Session Timers in Asterisk Chan_sip The SIP Session-Timers is an extension of the SIP protocol that allows end-points and proxies to refresh a session periodically. The sessions are kept alive by sending a RE-INVITE or UPDATE request at a negotiated interval. If a session refresh fails then all the entities that support Session- Timers clear their internal session state. In addition, UAs generate a BYE request in order to clear the state in the proxies and the remote UA (this is done for the benefit of SIP entities in the path that do not support Session-Timers). The Session-Timers can be configured on a system-wide, per-user, or per-peer basis. The peruser/ per-peer settings override the global settings. The following new parameters have been added to the sip.conf file. session-timers=["accept", "originate", "refuse"] session-expires=[integer] session-minse=[integer] session-refresher=["uas", "uac"] The session-timers parameter in sip.conf defines the mode of operation of SIP session-timers feature in Asterisk. The Asterisk can be configured in one of the following three modes: 1. Accept :: In the "accept" mode, the Asterisk server honors session-timers requests made by remote end-points. A remote end-point can request Asterisk to engage session-timers by either sending it an INVITE request with a "Supported: timer" header in it or by responding to Asterisk's INVITE with a 200 OK that contains Session-Expires: header in it. In this mode, the Asterisk server does not request session-timers from remote end-points. This is the default mode. 2. Originate :: In the "originate" mode, the Asterisk server requests the remote end-points to activate session-timers in addition to honoring such requests made by the remote end-pints. In order to get as much protection as possible against hanging SIP channels due to network or end-point failures, Asterisk resends periodic re-INVITEs even if a remote end-point does not support the session-timers feature. 3. Refuse :: In the "refuse" mode, Asterisk acts as if it does not support session- timers for inbound or outbound requests. If a remote end-point requests session-timers in a dialog, then Asterisk ignores that request unless it's noted as a requirement (Require: header), in which case the INVITE is rejected with a 420 Bad Extension response. */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 429317 $") #include #include #include #include #include "asterisk/network.h" #include "asterisk/paths.h" /* need ast_config_AST_SYSTEM_NAME */ #include "asterisk/lock.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/sched.h" #include "asterisk/io.h" #include "asterisk/rtp_engine.h" #include "asterisk/udptl.h" #include "asterisk/acl.h" #include "asterisk/manager.h" #include "asterisk/callerid.h" #include "asterisk/cli.h" #include "asterisk/musiconhold.h" #include "asterisk/dsp.h" #include "asterisk/pickup.h" #include "asterisk/parking.h" #include "asterisk/srv.h" #include "asterisk/astdb.h" #include "asterisk/causes.h" #include "asterisk/utils.h" #include "asterisk/file.h" #include "asterisk/astobj2.h" #include "asterisk/dnsmgr.h" #include "asterisk/devicestate.h" #include "asterisk/monitor.h" #include "asterisk/netsock2.h" #include "asterisk/localtime.h" #include "asterisk/abstract_jb.h" #include "asterisk/threadstorage.h" #include "asterisk/translate.h" #include "asterisk/ast_version.h" #include "asterisk/data.h" #include "asterisk/aoc.h" #include "asterisk/message.h" #include "sip/include/sip.h" #include "sip/include/globals.h" #include "sip/include/config_parser.h" #include "sip/include/reqresp_parser.h" #include "sip/include/sip_utils.h" #include "asterisk/sdp_srtp.h" #include "asterisk/ccss.h" #include "asterisk/xml.h" #include "sip/include/dialog.h" #include "sip/include/dialplan_functions.h" #include "sip/include/security_events.h" #include "sip/include/route.h" #include "asterisk/sip_api.h" #include "asterisk/app.h" #include "asterisk/bridge.h" #include "asterisk/stasis.h" #include "asterisk/stasis_endpoints.h" #include "asterisk/stasis_system.h" #include "asterisk/stasis_channels.h" #include "asterisk/features_config.h" #include "asterisk/http_websocket.h" #include "asterisk/format_cache.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. For example you have added these 2 headers: SIPAddHeader(P-Asserted-Identity: sip:foo@bar); SIPAddHeader(P-Preferred-Identity: sip:bar@foo); // remove all headers SIPRemoveHeader(); // remove all P- headers SIPRemoveHeader(P-); // remove only the PAI header (note the : at the end) SIPRemoveHeader(P-Asserted-Identity:); Always returns 0. Send a custom INFO frame on specified channels. SIPSendCustomINFO() allows you to send a custom INFO message on all active SIP channels or on channels with the specified User Agent. This application is only available if TEST_FRAMEWORK is defined. 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. Please observe that contents of the SDP (an attachment to the SIP request) can't be accessed with this function. 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). 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. 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. show SIP peer (text format). The peer name you want to check. Show one SIP peer with details on current status. Qualify SIP peers. The peer name you want to qualify. Qualify a SIP peer. SIPQualifyPeerDone Show SIP registrations (text format). Lists all registration requests and status. Registrations will follow as separate events followed by a final event called RegistrationsComplete. Send a SIP notify. Peer to receive the notify. At least one variable pair must be specified. name=value 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. 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. The from parameter can be a configured peer name or in the form of "display-name" <URI>. Specifying a prefix of sip: will send the message as a SIP MESSAGE request. 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. ***/ static int min_expiry = DEFAULT_MIN_EXPIRY; /*!< Minimum accepted registration time */ static int max_expiry = DEFAULT_MAX_EXPIRY; /*!< Maximum accepted registration time */ static int default_expiry = DEFAULT_DEFAULT_EXPIRY; static int min_subexpiry = DEFAULT_MIN_EXPIRY; /*!< Minimum accepted subscription time */ static int max_subexpiry = DEFAULT_MAX_EXPIRY; /*!< Maximum accepted subscription time */ static int mwi_expiry = DEFAULT_MWI_EXPIRY; static int unauth_sessions = 0; static int authlimit = DEFAULT_AUTHLIMIT; static int authtimeout = DEFAULT_AUTHTIMEOUT; /*! \brief Global jitterbuffer configuration - by default, jb is disabled * \note Values shown here match the defaults shown in sip.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; /*!< Global jitterbuffer configuration */ static const char config[] = "sip.conf"; /*!< Main configuration file */ static const char notify_config[] = "sip_notify.conf"; /*!< Configuration file for sending Notify with CLI commands to reconfigure or reboot phones */ /*! \brief Readable descriptions of device states. * \note Should be aligned to above table as index */ static const struct invstate2stringtable { const enum invitestates state; const char *desc; } invitestate2string[] = { {INV_NONE, "None" }, {INV_CALLING, "Calling (Trying)"}, {INV_PROCEEDING, "Proceeding "}, {INV_EARLY_MEDIA, "Early media"}, {INV_COMPLETED, "Completed (done)"}, {INV_CONFIRMED, "Confirmed (up)"}, {INV_TERMINATED, "Done"}, {INV_CANCELLED, "Cancelled"} }; /*! \brief Subscription types that we support. We support * - dialoginfo updates (really device status, not dialog info as was the original intent of the standard) * - SIMPLE presence used for device status * - Voicemail notification subscriptions */ static const struct cfsubscription_types { enum subscriptiontype type; const char * const event; const char * const mediatype; const char * const text; } subscription_types[] = { { NONE, "-", "unknown", "unknown" }, /* RFC 4235: SIP Dialog event package */ { DIALOG_INFO_XML, "dialog", "application/dialog-info+xml", "dialog-info+xml" }, { CPIM_PIDF_XML, "presence", "application/cpim-pidf+xml", "cpim-pidf+xml" }, /* RFC 3863 */ { PIDF_XML, "presence", "application/pidf+xml", "pidf+xml" }, /* RFC 3863 */ { XPIDF_XML, "presence", "application/xpidf+xml", "xpidf+xml" }, /* Pre-RFC 3863 with MS additions */ { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" } /* RFC 3842: Mailbox notification */ }; /*! \brief The core structure to setup dialogs. We parse incoming messages by using * structure and then route the messages according to the type. * * \note Note that sip_methods[i].id == i must hold or the code breaks */ static const struct cfsip_methods { enum sipmethod id; int need_rtp; /*!< when this is the 'primary' use for a pvt structure, does it need RTP? */ char * const text; enum can_create_dialog can_create; } sip_methods[] = { { SIP_UNKNOWN, RTP, "-UNKNOWN-",CAN_CREATE_DIALOG }, { SIP_RESPONSE, NO_RTP, "SIP/2.0", CAN_NOT_CREATE_DIALOG }, { SIP_REGISTER, NO_RTP, "REGISTER", CAN_CREATE_DIALOG }, { SIP_OPTIONS, NO_RTP, "OPTIONS", CAN_CREATE_DIALOG }, { SIP_NOTIFY, NO_RTP, "NOTIFY", CAN_CREATE_DIALOG }, { SIP_INVITE, RTP, "INVITE", CAN_CREATE_DIALOG }, { SIP_ACK, NO_RTP, "ACK", CAN_NOT_CREATE_DIALOG }, { SIP_PRACK, NO_RTP, "PRACK", CAN_NOT_CREATE_DIALOG }, { SIP_BYE, NO_RTP, "BYE", CAN_NOT_CREATE_DIALOG }, { SIP_REFER, NO_RTP, "REFER", CAN_CREATE_DIALOG }, { SIP_SUBSCRIBE, NO_RTP, "SUBSCRIBE",CAN_CREATE_DIALOG }, { SIP_MESSAGE, NO_RTP, "MESSAGE", CAN_CREATE_DIALOG }, { SIP_UPDATE, NO_RTP, "UPDATE", CAN_NOT_CREATE_DIALOG }, { SIP_INFO, NO_RTP, "INFO", CAN_NOT_CREATE_DIALOG }, { SIP_CANCEL, NO_RTP, "CANCEL", CAN_NOT_CREATE_DIALOG }, { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG }, { SIP_PING, NO_RTP, "PING", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD } }; /*! \brief Diversion header reasons * * The core defines a bunch of constants used to define * redirecting reasons. This provides a translation table * between those and the strings which may be present in * a SIP Diversion header */ static const struct sip_reasons { enum AST_REDIRECTING_REASON code; char * const text; } sip_reason_table[] = { { AST_REDIRECTING_REASON_UNKNOWN, "unknown" }, { AST_REDIRECTING_REASON_USER_BUSY, "user-busy" }, { AST_REDIRECTING_REASON_NO_ANSWER, "no-answer" }, { AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable" }, { AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional" }, { AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day" }, { AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" }, { AST_REDIRECTING_REASON_DEFLECTION, "deflection" }, { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" }, { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" }, { AST_REDIRECTING_REASON_AWAY, "away" }, { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"}, { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"}, }; /*! \name DefaultSettings Default setttings are used as a channel setting and as a default when configuring devices */ static char default_language[MAX_LANGUAGE]; /*!< Default language setting for new channels */ static char default_callerid[AST_MAX_EXTENSION]; /*!< Default caller ID for sip messages */ static char default_mwi_from[80]; /*!< Default caller ID for MWI updates */ static char default_fromdomain[AST_MAX_EXTENSION]; /*!< Default domain on outound messages */ static int default_fromdomainport; /*!< Default domain port on outbound messages */ static char default_notifymime[AST_MAX_EXTENSION]; /*!< Default MIME media type for MWI notify messages */ static char default_vmexten[AST_MAX_EXTENSION]; /*!< Default From Username on MWI updates */ static int default_qualify; /*!< Default Qualify= setting */ static int default_keepalive; /*!< Default keepalive= setting */ static char default_mohinterpret[MAX_MUSICCLASS]; /*!< Global setting for moh class to use when put on hold */ static char default_mohsuggest[MAX_MUSICCLASS]; /*!< Global setting for moh class to suggest when putting * a bridged channel on hold */ static char default_parkinglot[AST_MAX_CONTEXT]; /*!< Parkinglot */ static char default_engine[256]; /*!< Default RTP engine */ static int default_maxcallbitrate; /*!< Maximum bitrate for call */ static char default_zone[MAX_TONEZONE_COUNTRY]; /*!< Default tone zone for channels created from the SIP driver */ static unsigned int default_transports; /*!< Default Transports (enum ast_transport) that are acceptable */ static unsigned int default_primary_transport; /*!< Default primary Transport (enum ast_transport) for outbound connections to devices */ static struct sip_settings sip_cfg; /*!< SIP configuration data. \note in the future we could have multiple of these (per domain, per device group etc) */ /*!< use this macro when ast_uri_decode is dependent on pedantic checking to be on. */ #define SIP_PEDANTIC_DECODE(str) \ if (sip_cfg.pedanticsipchecking && !ast_strlen_zero(str)) { \ ast_uri_decode(str, ast_uri_sip_user); \ } \ static unsigned int chan_idx; /*!< used in naming sip channel */ static int global_match_auth_username; /*!< Match auth username if available instead of From: Default off. */ static int global_relaxdtmf; /*!< Relax DTMF */ static int global_prematuremediafilter; /*!< Enable/disable premature frames in a call (causing 183 early media) */ static int global_rtptimeout; /*!< Time out call if no RTP */ static int global_rtpholdtimeout; /*!< Time out call if no RTP during hold */ static int global_rtpkeepalive; /*!< Send RTP keepalives */ static int global_reg_timeout; /*!< Global time between attempts for outbound registrations */ static int global_regattempts_max; /*!< Registration attempts before giving up */ static int global_reg_retry_403; /*!< Treat 403 responses to registrations as 401 responses */ static int global_shrinkcallerid; /*!< enable or disable shrinking of caller id */ static int global_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 */ static unsigned int global_tos_sip; /*!< IP type of service for SIP packets */ static unsigned int global_tos_audio; /*!< IP type of service for audio RTP packets */ static unsigned int global_tos_video; /*!< IP type of service for video RTP packets */ static unsigned int global_tos_text; /*!< IP type of service for text RTP packets */ static unsigned int global_cos_sip; /*!< 802.1p class of service for SIP packets */ static unsigned int global_cos_audio; /*!< 802.1p class of service for audio RTP packets */ static unsigned int global_cos_video; /*!< 802.1p class of service for video RTP packets */ static unsigned int global_cos_text; /*!< 802.1p class of service for text RTP packets */ static unsigned int recordhistory; /*!< Record SIP history. Off by default */ static unsigned int dumphistory; /*!< Dump history to verbose before destroying SIP dialog */ static char global_useragent[AST_MAX_EXTENSION]; /*!< Useragent for the SIP channel */ static char global_sdpsession[AST_MAX_EXTENSION]; /*!< SDP session name for the SIP channel */ static char global_sdpowner[AST_MAX_EXTENSION]; /*!< SDP owner name for the SIP channel */ static int global_authfailureevents; /*!< Whether we send authentication failure manager events or not. Default no. */ static int global_t1; /*!< T1 time */ static int global_t1min; /*!< T1 roundtrip time minimum */ static int global_timer_b; /*!< Timer B - RFC 3261 Section 17.1.1.2 */ static unsigned int global_autoframing; /*!< Turn autoframing on or off. */ static int global_qualifyfreq; /*!< Qualify frequency */ static int global_qualify_gap; /*!< Time between our group of peer pokes */ static int global_qualify_peers; /*!< Number of peers to poke at a given time */ static enum st_mode global_st_mode; /*!< Mode of operation for Session-Timers */ static enum st_refresher_param global_st_refresher; /*!< Session-Timer refresher */ static int global_min_se; /*!< Lowest threshold for session refresh interval */ static int global_max_se; /*!< Highest threshold for session refresh interval */ static int global_store_sip_cause; /*!< Whether the MASTER_CHANNEL(HASH(SIP_CAUSE,[chan_name])) var should be set */ static int global_dynamic_exclude_static = 0; /*!< Exclude static peers from contact registrations */ static unsigned char global_refer_addheaders; /*!< Add extra headers to outgoing REFER */ /*@}*/ /*! * We use libxml2 in order to parse XML that may appear in the body of a SIP message. Currently, * the only usage is for parsing PIDF bodies of incoming PUBLISH requests in the call-completion * event package. This variable is set at module load time and may be checked at runtime to determine * if XML parsing support was found. */ static int can_parse_xml; /*! \name Object counters @{ * * \bug These counters are not handled in a thread-safe way ast_atomic_fetchadd_int() * should be used to modify these values. */ static int speerobjs = 0; /*!< Static peers */ static int rpeerobjs = 0; /*!< Realtime peers */ static int apeerobjs = 0; /*!< Autocreated peer objects */ /*! @} */ static struct ast_flags global_flags[3] = {{0}}; /*!< global SIP_ flags */ static unsigned int global_t38_maxdatagram; /*!< global T.38 FaxMaxDatagram override */ static struct stasis_subscription *network_change_sub; /*!< subscription id for network change events */ static struct stasis_subscription *acl_change_sub; /*!< subscription id for named ACL system change events */ static int network_change_sched_id = -1; static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */ AST_MUTEX_DEFINE_STATIC(netlock); /*! \brief 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(monlock); AST_MUTEX_DEFINE_STATIC(sip_reload_lock); /*! \brief This is the thread for the monitor which checks for input on the channels which are not currently in use. */ static pthread_t monitor_thread = AST_PTHREADT_NULL; static int sip_reloading = FALSE; /*!< Flag for avoiding multiple reloads at the same time */ static enum channelreloadreason sip_reloadreason; /*!< Reason for last reload/load of configuration */ struct ast_sched_context *sched; /*!< The scheduling context */ static struct io_context *io; /*!< The IO context */ static int *sipsock_read_id; /*!< ID of IO entry for sipsock FD */ struct sip_pkt; static AST_LIST_HEAD_STATIC(domain_list, domain); /*!< The SIP domain list */ AST_LIST_HEAD_NOLOCK(sip_history_head, sip_history); /*!< history list, entry in sip_pvt */ static enum sip_debug_e sipdebug; /*! \brief extra debugging for 'text' related events. * At the moment this is set together with sip_debug_console. * \note It should either go away or be implemented properly. */ static int sipdebug_text; static const struct _map_x_s referstatusstrings[] = { { REFER_IDLE, "" }, { REFER_SENT, "Request sent" }, { REFER_RECEIVED, "Request received" }, { REFER_CONFIRMED, "Confirmed" }, { REFER_ACCEPTED, "Accepted" }, { REFER_RINGING, "Target ringing" }, { REFER_200OK, "Done" }, { REFER_FAILED, "Failed" }, { REFER_NOAUTH, "Failed - auth failure" }, { -1, NULL} /* terminator */ }; /* --- Hash tables of various objects --------*/ #ifdef LOW_MEMORY static const int HASH_PEER_SIZE = 17; static const int HASH_DIALOG_SIZE = 17; static const int HASH_REGISTRY_SIZE = 17; #else static const int HASH_PEER_SIZE = 563; /*!< Size of peer hash table, prime number preferred! */ static const int HASH_DIALOG_SIZE = 563; static const int HASH_REGISTRY_SIZE = 563; #endif static const struct { enum ast_cc_service_type service; const char *service_string; } sip_cc_service_map [] = { [AST_CC_NONE] = { AST_CC_NONE, "" }, [AST_CC_CCBS] = { AST_CC_CCBS, "BS" }, [AST_CC_CCNR] = { AST_CC_CCNR, "NR" }, [AST_CC_CCNL] = { AST_CC_CCNL, "NL" }, }; static const struct { enum sip_cc_notify_state state; const char *state_string; } sip_cc_notify_state_map [] = { [CC_QUEUED] = {CC_QUEUED, "cc-state: queued"}, [CC_READY] = {CC_READY, "cc-state: ready"}, }; AST_LIST_HEAD_STATIC(epa_static_data_list, epa_backend); /*! * Used to create new entity IDs by ESCs. */ static int esc_etag_counter; static const int DEFAULT_PUBLISH_EXPIRES = 3600; #ifdef HAVE_LIBXML2 static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry); static const struct sip_esc_publish_callbacks cc_esc_publish_callbacks = { .initial_handler = cc_esc_publish_handler, .modify_handler = cc_esc_publish_handler, }; #endif /*! * \brief The Event State Compositors * * An Event State Compositor is an entity which * accepts PUBLISH requests and acts appropriately * based on these requests. * * The actual event_state_compositor structure is simply * an ao2_container of sip_esc_entrys. When an incoming * PUBLISH is received, we can match the appropriate sip_esc_entry * using the entity ID of the incoming PUBLISH. */ static struct event_state_compositor { enum subscriptiontype event; const char * name; const struct sip_esc_publish_callbacks *callbacks; struct ao2_container *compositor; } event_state_compositors [] = { #ifdef HAVE_LIBXML2 {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks}, #endif }; struct state_notify_data { int state; struct ao2_container *device_state_info; int presence_state; const char *presence_subtype; const char *presence_message; }; static const int ESC_MAX_BUCKETS = 37; /*! * \details * Here we implement the container for dialogs which are in the * dialog_needdestroy state to iterate only through the dialogs * unlink them instead of iterate through all dialogs */ struct ao2_container *dialogs_needdestroy; /*! * \details * 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 *dialogs_rtpcheck; /*! * \details * 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. */ static struct ao2_container *dialogs; #define sip_pvt_lock(x) ao2_lock(x) #define sip_pvt_trylock(x) ao2_trylock(x) #define sip_pvt_unlock(x) ao2_unlock(x) /*! \brief The table of TCP threads */ static struct ao2_container *threadt; /*! \brief The peer list: Users, Peers and Friends */ static struct ao2_container *peers; static struct ao2_container *peers_by_ip; /*! \brief A bogus peer, to be used when authentication should fail */ static struct sip_peer *bogus_peer; /*! \brief We can recognise the bogus peer by this invalid MD5 hash */ #define BOGUS_PEER_MD5SECRET "intentionally_invalid_md5_string" /*! \brief The register list: Other SIP proxies we register with and receive calls from */ static struct ao2_container *registry_list; /*! \brief The MWI subscription list */ static struct ao2_container *subscription_mwi_list; static int temp_pvt_init(void *); static void temp_pvt_cleanup(void *); /*! \brief A per-thread temporary pvt structure */ AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup); /*! \brief A per-thread buffer for transport to string conversion */ AST_THREADSTORAGE(sip_transport_str_buf); /*! \brief Size of the SIP transport buffer */ #define SIP_TRANSPORT_STR_BUFSIZE 128 /*! \brief Authentication container for realm authentication */ static struct sip_auth_container *authl = NULL; /*! \brief Global authentication container protection while adjusting the references. */ AST_MUTEX_DEFINE_STATIC(authl_lock); static struct ast_manager_event_blob *session_timeout_to_ami(struct stasis_message *msg); STASIS_MESSAGE_TYPE_DEFN_LOCAL(session_timeout_type, .to_ami = session_timeout_to_ami, ); /* --- Sockets and networking --------------*/ /*! \brief Main socket for UDP SIP communication. * * sipsock is shared between the SIP manager thread (which handles reload * requests), the udp io handler (sipsock_read()) and the user routines that * issue udp writes (using __sip_xmit()). * 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. */ static int sipsock = -1; struct ast_sockaddr bindaddr; /*!< UDP: The address we bind to */ /*! \brief our (internal) default address/port to put in SIP/SDP messages * 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. */ static struct ast_sockaddr internip; /*! \brief our external IP address/port for SIP sessions. * externaddr.sin_addr is only set when we know we might be behind * a NAT, and this is done using a variety of (mutually exclusive) * ways from the config file: * * + with "externaddr = host[:port]" we specify the address/port explicitly. * The address is looked up only once when (re)loading the config file; * * + with "externhost = host[:port]" we do a similar thing, but the * hostname is stored in externhost, and the hostname->IP mapping * is refreshed every 'externrefresh' seconds; * * Other variables (externhost, externexpire, externrefresh) are used * to support the above functions. */ static struct ast_sockaddr externaddr; /*!< External IP address if we are behind NAT */ static struct ast_sockaddr media_address; /*!< External RTP IP address if we are behind NAT */ static char externhost[MAXHOSTNAMELEN]; /*!< External host name */ static time_t externexpire; /*!< Expiration counter for re-resolving external host name in dynamic DNS */ static int externrefresh = 10; /*!< Refresh timer for DNS-based external address (dyndns) */ static uint16_t externtcpport; /*!< external tcp port */ static uint16_t externtlsport; /*!< external tls port */ /*! \brief 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. */ static struct ast_ha *localaddr; /*!< List of local networks, on the same side of NAT as this Asterisk */ static int ourport_tcp; /*!< The port used for TCP connections */ static int ourport_tls; /*!< The port used for TCP/TLS connections */ static struct ast_sockaddr debugaddr; static struct ast_config *notify_types = NULL; /*!< The list of manual NOTIFY types we know how to send */ /*! some list management macros. */ #define UNLINK(element, head, prev) do { \ if (prev) \ (prev)->next = (element)->next; \ else \ (head) = (element)->next; \ } while (0) struct ao2_container *sip_monitor_instances; struct show_peers_context; /*---------------------------- Forward declarations of functions in chan_sip.c */ /* Note: This is added to help splitting up chan_sip.c into several files in coming releases. */ /*--- PBX interface functions */ static struct ast_channel *sip_request_call(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); static int sip_devicestate(const char *data); static int sip_sendtext(struct ast_channel *ast, const char *text); static int sip_call(struct ast_channel *ast, const char *dest, int timeout); static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen); static int sip_hangup(struct ast_channel *ast); static int sip_answer(struct ast_channel *ast); static struct ast_frame *sip_read(struct ast_channel *ast); static int sip_write(struct ast_channel *ast, struct ast_frame *frame); static int sip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); static int sip_transfer(struct ast_channel *ast, const char *dest); static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int sip_senddigit_begin(struct ast_channel *ast, char digit); static int sip_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen); static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen); static const char *sip_get_callid(struct ast_channel *chan); static int handle_request_do(struct sip_request *req, struct ast_sockaddr *addr); static int sip_standard_port(enum ast_transport type, int port); static int sip_prepare_socket(struct sip_pvt *p); static int get_address_family_filter(unsigned int transport); /*--- Transmitting responses and requests */ static int sipsock_read(int *id, int fd, short events, void *ignore); static int __sip_xmit(struct sip_pvt *p, struct ast_str *data); static int __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, int resp, struct ast_str *data, int fatal, int sipmethod); static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp); static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); static int retrans_pkt(const void *data); static int transmit_response_using_temp(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg); static int transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req); static int transmit_response_reliable(struct sip_pvt *p, const char *msg, const struct sip_request *req); static int transmit_response_with_date(struct sip_pvt *p, const char *msg, const struct sip_request *req); static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp, int rpid); static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported); static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale); static int transmit_provisional_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, int with_sdp); static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable); static int transmit_request(struct sip_pvt *p, int sipmethod, uint32_t seqno, enum xmittype reliable, int newbranch); static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, uint32_t seqno, enum xmittype reliable, int newbranch); static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri); static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri); static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp); static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded); static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration); static int transmit_info_with_vidupdate(struct sip_pvt *p); static int transmit_message(struct sip_pvt *p, int init, int auth); static int transmit_refer(struct sip_pvt *p, const char *dest); static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten); static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate); static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state); static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader); static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno); static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno); static void copy_request(struct sip_request *dst, const struct sip_request *src); static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e); static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward); static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only); /* Misc dialog routines */ static int __sip_autodestruct(const void *data); static int update_call_counter(struct sip_pvt *fup, int event); static int auto_congest(const void *arg); static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *addr, const int intended_method); static void build_route(struct sip_pvt *p, struct sip_request *req, int backwards, int resp); static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, const char *pathbuf); static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr, struct sip_request *req, const char *uri); static int get_sip_pvt_from_replaces(const char *callid, const char *totag, const char *fromtag, struct sip_pvt **out_pvt, struct ast_channel **out_chan); static void check_pendings(struct sip_pvt *p); static void sip_set_owner(struct sip_pvt *p, struct ast_channel *chan); static void *sip_pickup_thread(void *stuff); static int sip_pickup(struct ast_channel *chan); static int sip_sipredirect(struct sip_pvt *p, const char *dest); static int is_method_allowed(unsigned int *allowed_methods, enum sipmethod method); /*--- Codec handling / SDP */ static void try_suggested_sip_codec(struct sip_pvt *p); static const char *get_sdp_iterate(int* start, struct sip_request *req, const char *name); static char get_sdp_line(int *start, int stop, struct sip_request *req, const char **value); static int find_sdp(struct sip_request *req); static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action); static int process_sdp_o(const char *o, struct sip_pvt *p); static int process_sdp_c(const char *c, struct ast_sockaddr *addr); static int process_sdp_a_sendonly(const char *a, int *sendonly); static int process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec); static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec); static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec); static int process_sdp_a_image(const char *a, struct sip_pvt *p); static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); static void start_ice(struct ast_rtp_instance *instance, int offer); static void add_codec_to_sdp(const struct sip_pvt *p, struct ast_format *codec, struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size, int *max_packet_size); static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, struct ast_str **m_buf, struct ast_str **a_buf, int debug); static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp, int add_audio, int add_t38); static void do_setnat(struct sip_pvt *p); static void stop_media_flows(struct sip_pvt *p); /*--- Authentication stuff */ static int reply_digest(struct sip_pvt *p, struct sip_request *req, char *header, int sipmethod, char *digest, int digest_len); static int build_reply_digest(struct sip_pvt *p, int method, char *digest, int digest_len); static enum check_auth_result check_auth(struct sip_pvt *p, struct sip_request *req, const char *username, const char *secret, const char *md5secret, int sipmethod, const char *uri, enum xmittype reliable); static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_request *req, int sipmethod, const char *uri, enum xmittype reliable, struct ast_sockaddr *addr, struct sip_peer **authpeer); static int check_user(struct sip_pvt *p, struct sip_request *req, int sipmethod, const char *uri, enum xmittype reliable, struct ast_sockaddr *addr); /*--- Domain handling */ static int check_sip_domain(const char *domain, char *context, size_t len); /* Check if domain is one of our local domains */ static int add_sip_domain(const char *domain, const enum domain_mode mode, const char *context); static void clear_sip_domains(void); /*--- SIP realm authentication */ static void add_realm_authentication(struct sip_auth_container **credentials, const char *configuration, int lineno); static struct sip_auth *find_realm_authentication(struct sip_auth_container *credentials, const char *realm); /*--- Misc functions */ static int check_rtp_timeout(struct sip_pvt *dialog, time_t t); static int reload_config(enum channelreloadreason reason); static void add_diversion(struct sip_request *req, struct sip_pvt *pvt); static int expire_register(const void *data); static void *do_monitor(void *data); static int restart_monitor(void); static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer); static struct ast_variable *copy_vars(struct ast_variable *src); static int dialog_find_multiple(void *obj, void *arg, int flags); static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt); /* static int sip_addrcmp(char *name, struct sockaddr_in *sin); Support for peer matching */ static int sip_refer_alloc(struct sip_pvt *p); static void sip_refer_destroy(struct sip_pvt *p); static int sip_notify_alloc(struct sip_pvt *p); static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context); static void set_peer_nat(const struct sip_pvt *p, struct sip_peer *peer); static void check_for_nat(const struct ast_sockaddr *them, struct sip_pvt *p); /*--- Device monitoring and Device/extension state/event handling */ static int extensionstate_update(const char *context, const char *exten, struct state_notify_data *data, struct sip_pvt *p, int force); static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data); static int sip_poke_noanswer(const void *data); static int sip_poke_peer(struct sip_peer *peer, int force); static void sip_poke_all_peers(void); static void sip_peer_hold(struct sip_pvt *p, int hold); static void mwi_event_cb(void *, struct stasis_subscription *, struct stasis_message *); static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); static void sip_keepalive_all_peers(void); /*--- Applications, functions, CLI and manager command helpers */ static const char *sip_nat_mode(const struct sip_pvt *p); static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *transfermode2str(enum transfermodes mode) attribute_const; static int peer_status(struct sip_peer *peer, char *status, int statuslen); static char *sip_show_sched(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char * _sip_show_peers(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]); static struct sip_peer *_sip_show_peers_one(int fd, struct mansession *s, struct show_peers_context *cont, struct sip_peer *peer); static char *sip_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static void print_group(int fd, ast_group_t group, int crlf); static void print_named_groups(int fd, struct ast_namedgroups *groups, int crlf); static const char *dtmfmode2str(int mode) attribute_const; static int str2dtmfmode(const char *str) attribute_unused; static const char *insecure2str(int mode) attribute_const; static const char *allowoverlap2str(int mode) attribute_const; static void cleanup_stale_contexts(char *new, char *old); static const char *domain_mode_to_text(const enum domain_mode mode); static char *sip_show_domains(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[]); static char *sip_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *_sip_qualify_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[]); static char *sip_qualify_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_mwi(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static const char *subscription_type2str(enum subscriptiontype subtype) attribute_pure; static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype); static char *complete_sip_peer(const char *word, int state, int flags2); static char *complete_sip_registered_peer(const char *word, int state, int flags2); static char *complete_sip_show_history(const char *line, const char *word, int pos, int state); static char *complete_sip_show_peer(const char *line, const char *word, int pos, int state); static char *complete_sip_unregister(const char *line, const char *word, int pos, int state); static char *complete_sip_notify(const char *line, const char *word, int pos, int state); static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_channelstats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_do_debug_ip(int fd, const char *arg); static char *sip_do_debug_peer(int fd, const char *arg); static char *sip_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_set_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static int sip_dtmfmode(struct ast_channel *chan, const char *data); static int sip_addheader(struct ast_channel *chan, const char *data); static int sip_do_reload(enum channelreloadreason reason); static char *sip_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static int ast_sockaddr_resolve_first_af(struct ast_sockaddr *addr, const char *name, int flag, int family); static int ast_sockaddr_resolve_first(struct ast_sockaddr *addr, const char *name, int flag); static int ast_sockaddr_resolve_first_transport(struct ast_sockaddr *addr, const char *name, int flag, unsigned int transport); /*--- Debugging Functions for enabling debug per IP or fully, or enabling history logging for a SIP dialog */ static void sip_dump_history(struct sip_pvt *dialog); /* Dump history to debuglog at end of dialog, before destroying data */ static inline int sip_debug_test_addr(const struct ast_sockaddr *addr); static inline int sip_debug_test_pvt(struct sip_pvt *p); static void append_history_full(struct sip_pvt *p, const char *fmt, ...); static void sip_dump_history(struct sip_pvt *dialog); /*--- Device object handling */ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only); static int update_call_counter(struct sip_pvt *fup, int event); static void sip_destroy_peer(struct sip_peer *peer); static void sip_destroy_peer_fn(void *peer); static void set_peer_defaults(struct sip_peer *peer); static struct sip_peer *temp_peer(const char *name); static void register_peer_exten(struct sip_peer *peer, int onoff); static int sip_poke_peer_s(const void *data); static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req); static void reg_source_db(struct sip_peer *peer); static void destroy_association(struct sip_peer *peer); static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno); static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *v); static void set_socket_transport(struct sip_socket *socket, int transport); static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags); /* Realtime device support */ static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *username, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms, const char *path); static void update_peer(struct sip_peer *p, int expire); static struct ast_variable *get_insecure_variable_from_config(struct ast_config *config); static const char *get_name_from_variable(const struct ast_variable *var); static struct sip_peer *realtime_peer(const char *peername, struct ast_sockaddr *sin, char *callbackexten, int devstate_only, int which_objects); static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); /*--- Internal UA client handling (outbound registrations) */ static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_sockaddr *us, struct sip_pvt *p); static void sip_registry_destroy(void *reg); static int sip_register(const char *value, int lineno); static const char *regstate2str(enum sipregistrystate regstate) attribute_const; static int sip_reregister(const void *data); static int __sip_do_register(struct sip_registry *r); static int sip_reg_timeout(const void *data); static void sip_send_all_registers(void); static int sip_reinvite_retry(const void *data); /*--- Parsing SIP requests and responses */ static int determine_firstline_parts(struct sip_request *req); static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype); static const char *gettag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize); static int find_sip_method(const char *msg); static unsigned int parse_allowed_methods(struct sip_request *req); static unsigned int set_pvt_allowed_methods(struct sip_pvt *pvt, struct sip_request *req); static int parse_request(struct sip_request *req); static const char *referstatus2str(enum referstatus rstatus) attribute_pure; static int method_match(enum sipmethod id, const char *name); static void parse_copy(struct sip_request *dst, const struct sip_request *src); static void parse_oli(struct sip_request *req, struct ast_channel *chan); static const char *find_alias(const char *name, const char *_default); static const char *__get_header(const struct sip_request *req, const char *name, int *start); static void lws2sws(struct ast_str *msgbuf); static void extract_uri(struct sip_pvt *p, struct sip_request *req); static char *remove_uri_parameters(char *uri); static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoing_req); static int get_also_info(struct sip_pvt *p, struct sip_request *oreq); static int parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req); static int set_address_from_contact(struct sip_pvt *pvt); static void check_via(struct sip_pvt *p, const struct sip_request *req); static int get_rpid(struct sip_pvt *p, struct sip_request *oreq); static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason, char **reason_str); static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id); static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout); static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen); static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen); static int get_domain(const char *str, char *domain, int len); static void get_realm(struct sip_pvt *p, const struct sip_request *req); static char *get_content(struct sip_request *req); /*-- TCP connection handling ---*/ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session); static void *sip_tcp_worker_fn(void *); /*--- Constructing requests and responses */ static void initialize_initreq(struct sip_pvt *p, struct sip_request *req); static int init_req(struct sip_request *req, int sipmethod, const char *recip); static void deinit_req(struct sip_request *req); static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, uint32_t seqno, int newbranch); static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri); static int init_resp(struct sip_request *resp, const char *msg); static inline int resp_needs_contact(const char *msg, enum sipmethod method); static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req); static const struct ast_sockaddr *sip_real_dst(const struct sip_pvt *p); static void build_via(struct sip_pvt *p); static int create_addr_from_peer(struct sip_pvt *r, struct sip_peer *peer); static int create_addr(struct sip_pvt *dialog, const char *opeer, struct ast_sockaddr *addr, int newdialog); static char *generate_random_string(char *buf, size_t size); static void build_callid_pvt(struct sip_pvt *pvt); static void change_callid_pvt(struct sip_pvt *pvt, const char *callid); static void build_callid_registry(struct sip_registry *reg, const struct ast_sockaddr *ourip, const char *fromdomain); static void build_localtag_registry(struct sip_registry *reg); static void make_our_tag(struct sip_pvt *pvt); static int add_header(struct sip_request *req, const char *var, const char *value); static int add_max_forwards(struct sip_pvt *dialog, struct sip_request *req); static int add_content(struct sip_request *req, const char *line); static int finalize_content(struct sip_request *req); static void destroy_msg_headers(struct sip_pvt *pvt); static int add_text(struct sip_request *req, struct sip_pvt *p); static int add_digit(struct sip_request *req, char digit, unsigned int duration, int mode); static int add_rpid(struct sip_request *req, struct sip_pvt *p); static int add_vidupdate(struct sip_request *req); static void add_route(struct sip_request *req, struct sip_route *route, int skip); static int copy_header(struct sip_request *req, const struct sip_request *orig, const char *field); static int copy_all_header(struct sip_request *req, const struct sip_request *orig, const char *field); static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const struct sip_request *orig, const char *field); static void set_destination(struct sip_pvt *p, const char *uri); static void add_date(struct sip_request *req); static void add_expires(struct sip_request *req, int expires); static void build_contact(struct sip_pvt *p); /*------Request handling functions */ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock); static int handle_request_update(struct sip_pvt *p, struct sip_request *req); static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, const char *e, int *nounlock); static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock); static int handle_request_bye(struct sip_pvt *p, struct sip_request *req); static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *sin, const char *e); static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req); static int handle_request_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e); static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e); static void handle_request_info(struct sip_pvt *p, struct sip_request *req); static int handle_request_options(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e); static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int *nounlock, struct sip_pvt *replaces_pvt, struct ast_channel *replaces_chan); static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e); static int local_attended_transfer(struct sip_pvt *transferer, struct ast_channel *transferer_chan, uint32_t seqno, int *nounlock); /*------Response handling functions */ static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); static void handle_response_refer(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); static void handle_response_subscribe(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); static int handle_response_register(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno); /*------ SRTP Support -------- */ static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a); /*------ T38 Support --------- */ static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans); static void change_t38_state(struct sip_pvt *p, int state); /*------ Session-Timers functions --------- */ static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp); static int proc_session_timer(const void *vp); static void stop_session_timer(struct sip_pvt *p); static void start_session_timer(struct sip_pvt *p); static void restart_session_timer(struct sip_pvt *p); static const char *strefresherparam2str(enum st_refresher r); static int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher_param *const p_ref); static int parse_minse(const char *p_hdrval, int *const p_interval); static int st_get_se(struct sip_pvt *, int max); static enum st_refresher st_get_refresher(struct sip_pvt *); static enum st_mode st_get_mode(struct sip_pvt *, int no_cached); static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p); /*------- RTP Glue functions -------- */ static int sip_set_rtp_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); /*!--- SIP MWI Subscription support */ static int sip_subscribe_mwi(const char *value, int lineno); static void sip_subscribe_mwi_destroy(void *data); static void sip_send_all_mwi_subscriptions(void); static int sip_subscribe_mwi_do(const void *data); static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi); /*! \brief 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_request_call, /* called with chan unlocked */ .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */ .call = sip_call, /* called with chan locked */ .send_html = sip_sendhtml, .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_senddigit_begin, /* called with chan unlocked */ .send_digit_end = sip_senddigit_end, .early_bridge = ast_rtp_instance_early_bridge, .send_text = sip_sendtext, /* called with chan locked */ .func_channel_read = sip_acf_channel_read, .setoption = sip_setoption, .queryoption = sip_queryoption, .get_pvt_uniqueid = sip_get_callid, }; /*! \brief This version of the sip channel tech has no send_digit_begin * callback so that the core knows that the channel does not want * DTMF BEGIN frames. * The struct is initialized just before registering the channel driver, * and is for use with channels using SIP INFO DTMF. */ struct ast_channel_tech sip_tech_info; /*------- CC Support -------- */ static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan); static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent); static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent); static void sip_cc_agent_respond(struct ast_cc_agent *agent, enum ast_cc_agent_response_reason reason); static int sip_cc_agent_status_request(struct ast_cc_agent *agent); static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent); static int sip_cc_agent_recall(struct ast_cc_agent *agent); static void sip_cc_agent_destructor(struct ast_cc_agent *agent); static struct ast_cc_agent_callbacks sip_cc_agent_callbacks = { .type = "SIP", .init = sip_cc_agent_init, .start_offer_timer = sip_cc_agent_start_offer_timer, .stop_offer_timer = sip_cc_agent_stop_offer_timer, .respond = sip_cc_agent_respond, .status_request = sip_cc_agent_status_request, .start_monitoring = sip_cc_agent_start_monitoring, .callee_available = sip_cc_agent_recall, .destructor = sip_cc_agent_destructor, }; /* -------- End of declarations of structures, constants and forward declarations of functions Below starts actual code ------------------------ */ static int sip_epa_register(const struct epa_static_data *static_data) { struct epa_backend *backend = ast_calloc(1, sizeof(*backend)); if (!backend) { return -1; } backend->static_data = static_data; AST_LIST_LOCK(&epa_static_data_list); AST_LIST_INSERT_TAIL(&epa_static_data_list, backend, next); AST_LIST_UNLOCK(&epa_static_data_list); return 0; } static void sip_epa_unregister_all(void) { struct epa_backend *backend; AST_LIST_LOCK(&epa_static_data_list); while ((backend = AST_LIST_REMOVE_HEAD(&epa_static_data_list, next))) { ast_free(backend); } AST_LIST_UNLOCK(&epa_static_data_list); } static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry); static void cc_epa_destructor(void *data) { struct sip_epa_entry *epa_entry = data; struct cc_epa_entry *cc_entry = epa_entry->instance_data; ast_free(cc_entry); } static const struct epa_static_data cc_epa_static_data = { .event = CALL_COMPLETION, .name = "call-completion", .handle_error = cc_handle_publish_error, .destructor = cc_epa_destructor, }; static const struct epa_static_data *find_static_data(const char * const event_package) { const struct epa_backend *backend = NULL; AST_LIST_LOCK(&epa_static_data_list); AST_LIST_TRAVERSE(&epa_static_data_list, backend, next) { if (!strcmp(backend->static_data->name, event_package)) { break; } } AST_LIST_UNLOCK(&epa_static_data_list); return backend ? backend->static_data : NULL; } static struct sip_epa_entry *create_epa_entry (const char * const event_package, const char * const destination) { struct sip_epa_entry *epa_entry; const struct epa_static_data *static_data; if (!(static_data = find_static_data(event_package))) { return NULL; } if (!(epa_entry = ao2_t_alloc(sizeof(*epa_entry), static_data->destructor, "Allocate new EPA entry"))) { return NULL; } epa_entry->static_data = static_data; ast_copy_string(epa_entry->destination, destination, sizeof(epa_entry->destination)); return epa_entry; } static enum ast_cc_service_type service_string_to_service_type(const char * const service_string) { enum ast_cc_service_type service; for (service = AST_CC_CCBS; service <= AST_CC_CCNL; ++service) { if (!strcasecmp(service_string, sip_cc_service_map[service].service_string)) { return service; } } return AST_CC_NONE; } /* Even state compositors code */ static void esc_entry_destructor(void *obj) { struct sip_esc_entry *esc_entry = obj; if (esc_entry->sched_id > -1) { AST_SCHED_DEL(sched, esc_entry->sched_id); } } static int esc_hash_fn(const void *obj, const int flags) { const struct sip_esc_entry *entry = obj; return ast_str_hash(entry->entity_tag); } static int esc_cmp_fn(void *obj, void *arg, int flags) { struct sip_esc_entry *entry1 = obj; struct sip_esc_entry *entry2 = arg; return (!strcmp(entry1->entity_tag, entry2->entity_tag)) ? (CMP_MATCH | CMP_STOP) : 0; } static struct event_state_compositor *get_esc(const char * const event_package) { int i; for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) { if (!strcasecmp(event_package, event_state_compositors[i].name)) { return &event_state_compositors[i]; } } return NULL; } static struct sip_esc_entry *get_esc_entry(const char * entity_tag, struct event_state_compositor *esc) { struct sip_esc_entry *entry; struct sip_esc_entry finder; ast_copy_string(finder.entity_tag, entity_tag, sizeof(finder.entity_tag)); entry = ao2_find(esc->compositor, &finder, OBJ_POINTER); return entry; } static int publish_expire(const void *data) { struct sip_esc_entry *esc_entry = (struct sip_esc_entry *) data; struct event_state_compositor *esc = get_esc(esc_entry->event); ast_assert(esc != NULL); ao2_unlink(esc->compositor, esc_entry); ao2_ref(esc_entry, -1); return 0; } static void create_new_sip_etag(struct sip_esc_entry *esc_entry, int is_linked) { int new_etag = ast_atomic_fetchadd_int(&esc_etag_counter, +1); struct event_state_compositor *esc = get_esc(esc_entry->event); ast_assert(esc != NULL); if (is_linked) { ao2_unlink(esc->compositor, esc_entry); } snprintf(esc_entry->entity_tag, sizeof(esc_entry->entity_tag), "%d", new_etag); ao2_link(esc->compositor, esc_entry); } static struct sip_esc_entry *create_esc_entry(struct event_state_compositor *esc, struct sip_request *req, const int expires) { struct sip_esc_entry *esc_entry; int expires_ms; if (!(esc_entry = ao2_alloc(sizeof(*esc_entry), esc_entry_destructor))) { return NULL; } esc_entry->event = esc->name; expires_ms = expires * 1000; /* Bump refcount for scheduler */ ao2_ref(esc_entry, +1); esc_entry->sched_id = ast_sched_add(sched, expires_ms, publish_expire, esc_entry); /* Note: This links the esc_entry into the ESC properly */ create_new_sip_etag(esc_entry, 0); return esc_entry; } static int initialize_escs(void) { int i, res = 0; for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) { if (!((event_state_compositors[i].compositor) = ao2_container_alloc(ESC_MAX_BUCKETS, esc_hash_fn, esc_cmp_fn))) { res = -1; } } return res; } static void destroy_escs(void) { int i; for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) { ao2_cleanup(event_state_compositors[i].compositor); } } static int find_by_notify_uri_helper(void *obj, void *arg, int flags) { struct ast_cc_agent *agent = obj; struct sip_cc_agent_pvt *agent_pvt = agent->private_data; const char *uri = arg; return !sip_uri_cmp(agent_pvt->notify_uri, uri) ? CMP_MATCH | CMP_STOP : 0; } static struct ast_cc_agent *find_sip_cc_agent_by_notify_uri(const char * const uri) { struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_notify_uri_helper, (char *)uri, "SIP"); return agent; } static int find_by_subscribe_uri_helper(void *obj, void *arg, int flags) { struct ast_cc_agent *agent = obj; struct sip_cc_agent_pvt *agent_pvt = agent->private_data; const char *uri = arg; return !sip_uri_cmp(agent_pvt->subscribe_uri, uri) ? CMP_MATCH | CMP_STOP : 0; } static struct ast_cc_agent *find_sip_cc_agent_by_subscribe_uri(const char * const uri) { struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_subscribe_uri_helper, (char *)uri, "SIP"); return agent; } static int find_by_callid_helper(void *obj, void *arg, int flags) { struct ast_cc_agent *agent = obj; struct sip_cc_agent_pvt *agent_pvt = agent->private_data; struct sip_pvt *call_pvt = arg; return !strcmp(agent_pvt->original_callid, call_pvt->callid) ? CMP_MATCH | CMP_STOP : 0; } static struct ast_cc_agent *find_sip_cc_agent_by_original_callid(struct sip_pvt *pvt) { struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_callid_helper, pvt, "SIP"); return agent; } static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan) { struct sip_cc_agent_pvt *agent_pvt = ast_calloc(1, sizeof(*agent_pvt)); struct sip_pvt *call_pvt = ast_channel_tech_pvt(chan); if (!agent_pvt) { return -1; } ast_assert(!strcmp(ast_channel_tech(chan)->type, "SIP")); ast_copy_string(agent_pvt->original_callid, call_pvt->callid, sizeof(agent_pvt->original_callid)); ast_copy_string(agent_pvt->original_exten, call_pvt->exten, sizeof(agent_pvt->original_exten)); agent_pvt->offer_timer_id = -1; agent->private_data = agent_pvt; sip_pvt_lock(call_pvt); ast_set_flag(&call_pvt->flags[0], SIP_OFFER_CC); sip_pvt_unlock(call_pvt); return 0; } static int sip_offer_timer_expire(const void *data) { struct ast_cc_agent *agent = (struct ast_cc_agent *) data; struct sip_cc_agent_pvt *agent_pvt = agent->private_data; agent_pvt->offer_timer_id = -1; return ast_cc_failed(agent->core_id, "SIP agent %s's offer timer expired", agent->device_name); } static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; int when; when = ast_get_cc_offer_timer(agent->cc_params) * 1000; agent_pvt->offer_timer_id = ast_sched_add(sched, when, sip_offer_timer_expire, agent); return 0; } static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; AST_SCHED_DEL(sched, agent_pvt->offer_timer_id); return 0; } static void sip_cc_agent_respond(struct ast_cc_agent *agent, enum ast_cc_agent_response_reason reason) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; sip_pvt_lock(agent_pvt->subscribe_pvt); ast_set_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); if (reason == AST_CC_AGENT_RESPONSE_SUCCESS || !ast_strlen_zero(agent_pvt->notify_uri)) { /* The second half of this if statement may be a bit hard to grasp, * so here's an explanation. When a subscription comes into * chan_sip, as long as it is not malformed, it will be passed * to the CC core. If the core senses an out-of-order state transition, * then the core will call this callback with the "reason" set to a * failure condition. * However, an out-of-order state transition will occur during a resubscription * for CC. In such a case, we can see that we have already generated a notify_uri * and so we can detect that this isn't a *real* failure. Rather, it is just * something the core doesn't recognize as a legitimate SIP state transition. * Thus we respond with happiness and flowers. */ transmit_response(agent_pvt->subscribe_pvt, "200 OK", &agent_pvt->subscribe_pvt->initreq); transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_QUEUED); } else { transmit_response(agent_pvt->subscribe_pvt, "500 Internal Error", &agent_pvt->subscribe_pvt->initreq); } sip_pvt_unlock(agent_pvt->subscribe_pvt); agent_pvt->is_available = TRUE; } static int sip_cc_agent_status_request(struct ast_cc_agent *agent) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; enum ast_device_state state = agent_pvt->is_available ? AST_DEVICE_NOT_INUSE : AST_DEVICE_INUSE; return ast_cc_agent_status_response(agent->core_id, state); } static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent) { /* To start monitoring just means to wait for an incoming PUBLISH * to tell us that the caller has become available again. No special * action is needed */ return 0; } static int sip_cc_agent_recall(struct ast_cc_agent *agent) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; /* If we have received a PUBLISH beforehand stating that the caller in question * is not available, we can save ourself a bit of effort here and just report * the caller as busy */ if (!agent_pvt->is_available) { return ast_cc_agent_caller_busy(agent->core_id, "Caller %s is busy, reporting to the core", agent->device_name); } /* Otherwise, we transmit a NOTIFY to the caller and await either * a PUBLISH or an INVITE */ sip_pvt_lock(agent_pvt->subscribe_pvt); transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_READY); sip_pvt_unlock(agent_pvt->subscribe_pvt); return 0; } static void sip_cc_agent_destructor(struct ast_cc_agent *agent) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; if (!agent_pvt) { /* The agent constructor probably failed. */ return; } sip_cc_agent_stop_offer_timer(agent); if (agent_pvt->subscribe_pvt) { sip_pvt_lock(agent_pvt->subscribe_pvt); if (!ast_test_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { /* If we haven't sent a 200 OK for the SUBSCRIBE dialog yet, then we need to send a response letting * the subscriber know something went wrong */ transmit_response(agent_pvt->subscribe_pvt, "500 Internal Server Error", &agent_pvt->subscribe_pvt->initreq); } sip_pvt_unlock(agent_pvt->subscribe_pvt); agent_pvt->subscribe_pvt = dialog_unref(agent_pvt->subscribe_pvt, "SIP CC agent destructor: Remove ref to subscription"); } ast_free(agent_pvt); } static int sip_monitor_instance_hash_fn(const void *obj, const int flags) { const struct sip_monitor_instance *monitor_instance = obj; return monitor_instance->core_id; } static int sip_monitor_instance_cmp_fn(void *obj, void *arg, int flags) { struct sip_monitor_instance *monitor_instance1 = obj; struct sip_monitor_instance *monitor_instance2 = arg; return monitor_instance1->core_id == monitor_instance2->core_id ? CMP_MATCH | CMP_STOP : 0; } static void sip_monitor_instance_destructor(void *data) { struct sip_monitor_instance *monitor_instance = data; if (monitor_instance->subscription_pvt) { sip_pvt_lock(monitor_instance->subscription_pvt); monitor_instance->subscription_pvt->expiry = 0; transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 0, monitor_instance->subscribe_uri); sip_pvt_unlock(monitor_instance->subscription_pvt); dialog_unref(monitor_instance->subscription_pvt, "Unref monitor instance ref of subscription pvt"); } if (monitor_instance->suspension_entry) { monitor_instance->suspension_entry->body[0] = '\0'; transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_REMOVE ,monitor_instance->notify_uri); ao2_t_ref(monitor_instance->suspension_entry, -1, "Decrementing suspension entry refcount in sip_monitor_instance_destructor"); } ast_string_field_free_memory(monitor_instance); } static struct sip_monitor_instance *sip_monitor_instance_init(int core_id, const char * const subscribe_uri, const char * const peername, const char * const device_name) { struct sip_monitor_instance *monitor_instance = ao2_alloc(sizeof(*monitor_instance), sip_monitor_instance_destructor); if (!monitor_instance) { return NULL; } if (ast_string_field_init(monitor_instance, 256)) { ao2_ref(monitor_instance, -1); return NULL; } ast_string_field_set(monitor_instance, subscribe_uri, subscribe_uri); ast_string_field_set(monitor_instance, peername, peername); ast_string_field_set(monitor_instance, device_name, device_name); monitor_instance->core_id = core_id; ao2_link(sip_monitor_instances, monitor_instance); return monitor_instance; } static int find_sip_monitor_instance_by_subscription_pvt(void *obj, void *arg, int flags) { struct sip_monitor_instance *monitor_instance = obj; return monitor_instance->subscription_pvt == arg ? CMP_MATCH | CMP_STOP : 0; } static int find_sip_monitor_instance_by_suspension_entry(void *obj, void *arg, int flags) { struct sip_monitor_instance *monitor_instance = obj; return monitor_instance->suspension_entry == arg ? CMP_MATCH | CMP_STOP : 0; } static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id); static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor); static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor); static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id); static void sip_cc_monitor_destructor(void *private_data); static struct ast_cc_monitor_callbacks sip_cc_monitor_callbacks = { .type = "SIP", .request_cc = sip_cc_monitor_request_cc, .suspend = sip_cc_monitor_suspend, .unsuspend = sip_cc_monitor_unsuspend, .cancel_available_timer = sip_cc_monitor_cancel_available_timer, .destructor = sip_cc_monitor_destructor, }; static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id) { struct sip_monitor_instance *monitor_instance = monitor->private_data; enum ast_cc_service_type service = monitor->service_offered; int when; if (!monitor_instance) { return -1; } if (!(monitor_instance->subscription_pvt = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL, NULL))) { return -1; } when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) : ast_get_ccnr_available_timer(monitor->interface->config_params); sip_pvt_lock(monitor_instance->subscription_pvt); ast_set_flag(&monitor_instance->subscription_pvt->flags[0], SIP_OUTGOING); create_addr(monitor_instance->subscription_pvt, monitor_instance->peername, 0, 1); ast_sip_ouraddrfor(&monitor_instance->subscription_pvt->sa, &monitor_instance->subscription_pvt->ourip, monitor_instance->subscription_pvt); monitor_instance->subscription_pvt->subscribed = CALL_COMPLETION; monitor_instance->subscription_pvt->expiry = when; transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 2, monitor_instance->subscribe_uri); sip_pvt_unlock(monitor_instance->subscription_pvt); ao2_t_ref(monitor, +1, "Adding a ref to the monitor for the scheduler"); *available_timer_id = ast_sched_add(sched, when * 1000, ast_cc_available_timer_expire, monitor); return 0; } static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity) { struct ast_str *body = ast_str_alloca(size); char tuple_id[32]; generate_random_string(tuple_id, sizeof(tuple_id)); /* We'll make this a bare-bones pidf body. In state_notify_build_xml, the PIDF * body gets a lot more extra junk that isn't necessary, so we'll leave it out here. */ ast_str_append(&body, 0, "\n"); /* XXX The entity attribute is currently set to the peer name associated with the * dialog. This is because we currently only call this function for call-completion * PUBLISH bodies. In such cases, the entity is completely disregarded. For other * event packages, it may be crucial to have a proper URI as the presentity so this * should be revisited as support is expanded. */ ast_str_append(&body, 0, "\n", presentity); ast_str_append(&body, 0, "\n", tuple_id); ast_str_append(&body, 0, "%s\n", state == CC_OPEN ? "open" : "closed"); ast_str_append(&body, 0, "\n"); ast_str_append(&body, 0, "\n"); ast_copy_string(pidf_body, ast_str_buffer(body), size); return 0; } static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor) { struct sip_monitor_instance *monitor_instance = monitor->private_data; enum sip_publish_type publish_type; struct cc_epa_entry *cc_entry; if (!monitor_instance) { return -1; } if (!monitor_instance->suspension_entry) { /* We haven't yet allocated the suspension entry, so let's give it a shot */ if (!(monitor_instance->suspension_entry = create_epa_entry("call-completion", monitor_instance->peername))) { ast_log(LOG_WARNING, "Unable to allocate sip EPA entry for call-completion\n"); ao2_ref(monitor_instance, -1); return -1; } if (!(cc_entry = ast_calloc(1, sizeof(*cc_entry)))) { ast_log(LOG_WARNING, "Unable to allocate space for instance data of EPA entry for call-completion\n"); ao2_ref(monitor_instance, -1); return -1; } cc_entry->core_id = monitor->core_id; monitor_instance->suspension_entry->instance_data = cc_entry; publish_type = SIP_PUBLISH_INITIAL; } else { publish_type = SIP_PUBLISH_MODIFY; cc_entry = monitor_instance->suspension_entry->instance_data; } cc_entry->current_state = CC_CLOSED; if (ast_strlen_zero(monitor_instance->notify_uri)) { /* If we have no set notify_uri, then what this means is that we have * not received a NOTIFY from this destination stating that he is * currently available. * * This situation can arise when the core calls the suspend callbacks * of multiple destinations. If one of the other destinations aside * from this one notified Asterisk that he is available, then there * is no reason to take any suspension action on this device. Rather, * we should return now and if we receive a NOTIFY while monitoring * is still "suspended" then we can immediately respond with the * proper PUBLISH to let this endpoint know what is going on. */ return 0; } construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername); return transmit_publish(monitor_instance->suspension_entry, publish_type, monitor_instance->notify_uri); } static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor) { struct sip_monitor_instance *monitor_instance = monitor->private_data; struct cc_epa_entry *cc_entry; if (!monitor_instance) { return -1; } ast_assert(monitor_instance->suspension_entry != NULL); cc_entry = monitor_instance->suspension_entry->instance_data; cc_entry->current_state = CC_OPEN; if (ast_strlen_zero(monitor_instance->notify_uri)) { /* This means we are being asked to unsuspend a call leg we never * sent a PUBLISH on. As such, there is no reason to send another * PUBLISH at this point either. We can just return instead. */ return 0; } construct_pidf_body(CC_OPEN, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername); return transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_MODIFY, monitor_instance->notify_uri); } static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id) { if (*sched_id != -1) { AST_SCHED_DEL(sched, *sched_id); ao2_t_ref(monitor, -1, "Removing scheduler's reference to the monitor"); } return 0; } static void sip_cc_monitor_destructor(void *private_data) { struct sip_monitor_instance *monitor_instance = private_data; ao2_unlink(sip_monitor_instances, monitor_instance); ast_module_unref(ast_module_info->self); } static int sip_get_cc_information(struct sip_request *req, char *subscribe_uri, size_t size, enum ast_cc_service_type *service) { char *call_info = ast_strdupa(sip_get_header(req, "Call-Info")); char *uri; char *purpose; char *service_str; static const char cc_purpose[] = "purpose=call-completion"; static const int cc_purpose_len = sizeof(cc_purpose) - 1; if (ast_strlen_zero(call_info)) { /* No Call-Info present. Definitely no CC offer */ return -1; } uri = strsep(&call_info, ";"); while ((purpose = strsep(&call_info, ";"))) { if (!strncmp(purpose, cc_purpose, cc_purpose_len)) { break; } } if (!purpose) { /* We didn't find the appropriate purpose= parameter. Oh well */ return -1; } /* Okay, call-completion has been offered. Let's figure out what type of service this is */ while ((service_str = strsep(&call_info, ";"))) { if (!strncmp(service_str, "m=", 2)) { break; } } if (!service_str) { /* So they didn't offer a particular service, We'll just go with CCBS since it really * doesn't matter anyway */ service_str = "BS"; } else { /* We already determined that there is an "m=" so no need to check * the result of this strsep */ strsep(&service_str, "="); } if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) { /* Invalid service offered */ return -1; } ast_copy_string(subscribe_uri, get_in_brackets(uri), size); return 0; } /* * \brief Determine what, if any, CC has been offered and queue a CC frame if possible * * After taking care of some formalities to be sure that this call is eligible for CC, * we first try to see if we can make use of native CC. We grab the information from * the passed-in sip_request (which is always a response to an INVITE). If we can * use native CC monitoring for the call, then so be it. * * If native cc monitoring is not possible or not supported, then we will instead attempt * to use generic monitoring. Falling back to generic from a failed attempt at using native * monitoring will only work if the monitor policy of the endpoint is "always" * * \param pvt The current dialog. Contains CC parameters for the endpoint * \param req The response to the INVITE we want to inspect * \param service The service to use if generic monitoring is to be used. For native * monitoring, we get the service from the SIP response itself */ static void sip_handle_cc(struct sip_pvt *pvt, struct sip_request *req, enum ast_cc_service_type service) { enum ast_cc_monitor_policies monitor_policy = ast_get_cc_monitor_policy(pvt->cc_params); int core_id; char interface_name[AST_CHANNEL_NAME]; if (monitor_policy == AST_CC_MONITOR_NEVER) { /* Don't bother, just return */ return; } if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) { /* For some reason, CC is invalid, so don't try it! */ return; } ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name)); if (monitor_policy == AST_CC_MONITOR_ALWAYS || monitor_policy == AST_CC_MONITOR_NATIVE) { char subscribe_uri[SIPBUFSIZE]; char device_name[AST_CHANNEL_NAME]; enum ast_cc_service_type offered_service; struct sip_monitor_instance *monitor_instance; if (sip_get_cc_information(req, subscribe_uri, sizeof(subscribe_uri), &offered_service)) { /* If CC isn't being offered to us, or for some reason the CC offer is * not formatted correctly, then it may still be possible to use generic * call completion since the monitor policy may be "always" */ goto generic; } ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name)); if (!(monitor_instance = sip_monitor_instance_init(core_id, subscribe_uri, pvt->peername, device_name))) { /* Same deal. We can try using generic still */ goto generic; } /* We bump the refcount of chan_sip because once we queue this frame, the CC core * will have a reference to callbacks in this module. We decrement the module * refcount once the monitor destructor is called */ ast_module_ref(ast_module_info->self); ast_queue_cc_frame(pvt->owner, "SIP", pvt->dialstring, offered_service, monitor_instance); ao2_ref(monitor_instance, -1); return; } generic: if (monitor_policy == AST_CC_MONITOR_GENERIC || monitor_policy == AST_CC_MONITOR_ALWAYS) { ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, interface_name, service, NULL); } } /*! \brief Working TLS connection configuration */ static struct ast_tls_config sip_tls_cfg; /*! \brief Default TLS connection configuration */ static struct ast_tls_config default_tls_cfg; /*! \brief The TCP server definition */ static struct ast_tcptls_session_args sip_tcp_desc = { .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_tcp_worker_fn, }; /*! \brief The TCP/TLS server definition */ static struct ast_tcptls_session_args sip_tls_desc = { .accept_fd = -1, .master = AST_PTHREADT_NULL, .tls_cfg = &sip_tls_cfg, .poll_timeout = -1, .name = "SIP TLS server", .accept_fn = ast_tcptls_server_root, .worker_fn = sip_tcp_worker_fn, }; /*! \brief Append to SIP dialog history \return Always returns 0 */ #define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args) struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func) { if (p) #ifdef REF_DEBUG __ao2_ref_debug(p, 1, tag, file, line, func); #else ao2_ref(p, 1); #endif else ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n"); return p; } struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func) { if (p) #ifdef REF_DEBUG __ao2_ref_debug(p, -1, tag, file, line, func); #else ao2_ref(p, -1); #endif return NULL; } /*! \brief map from an integer value to a string. * If no match is found, return errorstring */ static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring) { const struct _map_x_s *cur; for (cur = table; cur->s; cur++) { if (cur->x == x) { return cur->s; } } return errorstring; } /*! \brief map from a string to an integer value, case insensitive. * If no match is found, return errorvalue. */ static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue) { const struct _map_x_s *cur; for (cur = table; cur->s; cur++) { if (!strcasecmp(cur->s, s)) { return cur->x; } } return errorvalue; } static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text) { enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN; int i; for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) { if (!strcasecmp(text, sip_reason_table[i].text)) { ast = sip_reason_table[i].code; break; } } return ast; } static const char *sip_reason_code_to_str(struct ast_party_redirecting_reason *reason, int *table_lookup) { int code = reason->code; /* If there's a specific string set, then we just * use it. */ if (!ast_strlen_zero(reason->str)) { /* If we care about whether this can be found in * the table, then we need to check about that. */ if (table_lookup) { /* If the string is literally "unknown" then don't bother with the lookup * because it can lead to a false negative. */ if (!strcasecmp(reason->str, "unknown") || sip_reason_str_to_code(reason->str) != AST_REDIRECTING_REASON_UNKNOWN) { *table_lookup = TRUE; } else { *table_lookup = FALSE; } } return reason->str; } if (table_lookup) { *table_lookup = TRUE; } if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) { return sip_reason_table[code].text; } return "unknown"; } /*! * \brief generic function for determining if a correct transport is being * used to contact a peer * * this is done as a macro so that the "tmpl" var can be passed either a * sip_request or a sip_peer */ #define check_request_transport(peer, tmpl) ({ \ int ret = 0; \ if (peer->socket.type == tmpl->socket.type) \ ; \ else if (!(peer->transports & tmpl->socket.type)) {\ ast_log(LOG_ERROR, \ "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \ sip_get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \ ); \ ret = 1; \ } else if (peer->socket.type & AST_TRANSPORT_TLS) { \ ast_log(LOG_WARNING, \ "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \ peer->name, sip_get_transport(tmpl->socket.type) \ ); \ } else { \ ast_debug(1, \ "peer '%s' has contacted us over %s even though we prefer %s.\n", \ peer->name, sip_get_transport(tmpl->socket.type), sip_get_transport(peer->socket.type) \ ); \ }\ (ret); \ }) /*! \brief * duplicate a list of channel variables, \return the copy. */ static struct ast_variable *copy_vars(struct ast_variable *src) { struct ast_variable *res = NULL, *tmp, *v = NULL; for (v = src ; v ; v = v->next) { if ((tmp = ast_variable_new(v->name, v->value, v->file))) { tmp->next = res; res = tmp; } } return res; } static void tcptls_packet_destructor(void *obj) { struct tcptls_packet *packet = obj; ast_free(packet->data); } static void sip_tcptls_client_args_destructor(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); } static void sip_threadinfo_destructor(void *obj) { struct sip_threadinfo *th = obj; struct tcptls_packet *packet; if (th->alert_pipe[1] > -1) { close(th->alert_pipe[0]); } if (th->alert_pipe[1] > -1) { close(th->alert_pipe[1]); } th->alert_pipe[0] = th->alert_pipe[1] = -1; while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) { ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue"); } if (th->tcptls_session) { ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object"); } } /*! \brief creates a sip_threadinfo object and links it into the threadt table. */ static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport) { struct sip_threadinfo *th; if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) { return NULL; } th->alert_pipe[0] = th->alert_pipe[1] = -1; if (pipe(th->alert_pipe) == -1) { ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo"); 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_threadinfo object"); th->tcptls_session = tcptls_session; th->type = transport ? transport : (tcptls_session->ssl ? AST_TRANSPORT_TLS: AST_TRANSPORT_TCP); ao2_t_link(threadt, th, "Adding new tcptls helper thread"); ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains"); return th; } /*! \brief used to indicate to a tcptls thread that data is ready to be written */ static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len) { int res = len; struct sip_threadinfo *th = NULL; struct tcptls_packet *packet = NULL; struct sip_threadinfo tmp = { .tcptls_session = tcptls_session, }; enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA; if (!tcptls_session) { return XMIT_ERROR; } ao2_lock(tcptls_session); if ((tcptls_session->fd == -1) || !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) || !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) || !(packet->data = ast_str_create(len))) { goto tcptls_write_setup_error; } /* goto tcptls_write_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(th); if (write(th->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 = XMIT_ERROR; } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */ AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry); } ao2_unlock(th); ao2_unlock(tcptls_session); ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it"); return res; tcptls_write_setup_error: if (th) { ao2_t_ref(th, -1, "In sip_tcptls_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 XMIT_ERROR; } /*! \brief SIP TCP connection handler */ static void *sip_tcp_worker_fn(void *data) { struct ast_tcptls_session_instance *tcptls_session = data; return _sip_tcp_helper_thread(tcptls_session); } /*! \brief SIP WebSocket connection handler */ static void sip_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers) { int res; if (ast_websocket_set_nonblock(session)) { goto end; } if (ast_websocket_set_timeout(session, sip_cfg.websocket_write_timeout)) { goto end; } while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) { char *payload; uint64_t payload_len; enum ast_websocket_opcode opcode; int fragmented; if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) { /* We err on the side of caution and terminate the session if any error occurs */ break; } if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) { struct sip_request req = { 0, }; char data[payload_len + 1]; if (!(req.data = ast_str_create(payload_len + 1))) { goto end; } strncpy(data, payload, payload_len); data[payload_len] = '\0'; if (ast_str_set(&req.data, -1, "%s", data) == AST_DYNSTR_BUILD_FAILED) { deinit_req(&req); goto end; } req.socket.fd = ast_websocket_fd(session); set_socket_transport(&req.socket, ast_websocket_is_secure(session) ? AST_TRANSPORT_WSS : AST_TRANSPORT_WS); req.socket.ws_session = session; handle_request_do(&req, ast_websocket_remote_address(session)); deinit_req(&req); } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) { break; } } end: ast_websocket_unref(session); } /*! \brief Check if the authtimeout has expired. * \param start the time when the session started * * \retval 0 the timeout has expired * \retval -1 error * \return the number of milliseconds until the timeout will expire */ static int sip_check_authtimeout(time_t start) { int timeout; time_t now; if(time(&now) == -1) { ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno)); return -1; } timeout = (authtimeout - (now - start)) * 1000; if (timeout < 0) { /* we have timed out */ return 0; } return timeout; } /*! * \brief Indication of a TCP message's integrity */ enum message_integrity { /*! * The message has an error in it with * regards to its Content-Length header */ MESSAGE_INVALID, /*! * The message is incomplete */ MESSAGE_FRAGMENT, /*! * The data contains a complete message * plus a fragment of another. */ MESSAGE_FRAGMENT_COMPLETE, /*! * The message is complete */ MESSAGE_COMPLETE, }; /*! * \brief * Get the content length from an unparsed SIP message * * \param message The unparsed SIP message headers * \return The value of the Content-Length header or -1 if message is invalid */ static int read_raw_content_length(const char *message) { char *content_length_str; int content_length = -1; struct ast_str *msg_copy; char *msg; /* Using a ast_str because lws2sws takes one of those */ if (!(msg_copy = ast_str_create(strlen(message) + 1))) { return -1; } ast_str_set(&msg_copy, 0, "%s", message); if (sip_cfg.pedanticsipchecking) { lws2sws(msg_copy); } msg = ast_str_buffer(msg_copy); /* Let's find a Content-Length header */ if ((content_length_str = strcasestr(msg, "\nContent-Length:"))) { content_length_str += sizeof("\nContent-Length:") - 1; } else if ((content_length_str = strcasestr(msg, "\nl:"))) { content_length_str += sizeof("\nl:") - 1; } else { /* 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." */ goto done; } /* Double-check that this is a complete header */ if (!strchr(content_length_str, '\n')) { goto done; } if (sscanf(content_length_str, "%30d", &content_length) != 1) { content_length = -1; } done: ast_free(msg_copy); return content_length; } /*! * \brief Check that a message received over TCP is a full message * * This will take the information read in and then determine if * 1) The message is a full SIP request * 2) The message is a partial SIP request * 3) The message contains a full SIP request along with another partial request * \param data The unparsed incoming SIP message. * \param request The resulting request with extra fragments removed. * \param overflow If the message contains more than a full request, this is the remainder of the message * \return The resulting integrity of the message */ static enum message_integrity check_message_integrity(struct ast_str **request, struct ast_str **overflow) { char *message = ast_str_buffer(*request); char *body; int content_length; int message_len = ast_str_strlen(*request); int body_len; /* Important pieces to search for in a SIP request are \r\n\r\n. This * marks either * 1) The division between the headers and body * 2) The end of the SIP request */ body = strstr(message, "\r\n\r\n"); if (!body) { /* This is clearly a partial message since we haven't reached an end * yet. */ return MESSAGE_FRAGMENT; } body += sizeof("\r\n\r\n") - 1; body_len = message_len - (body - message); body[-1] = '\0'; content_length = read_raw_content_length(message); body[-1] = '\n'; if (content_length < 0) { return MESSAGE_INVALID; } else if (content_length == 0) { /* We've definitely received an entire message. We need * to check if there's also a fragment of another message * in addition. */ if (body_len == 0) { return MESSAGE_COMPLETE; } else { ast_str_append(overflow, 0, "%s", body); ast_str_truncate(*request, message_len - body_len); return MESSAGE_FRAGMENT_COMPLETE; } } /* Positive content length. Let's see what sort of * message body we're dealing with. */ if (body_len < content_length) { /* We don't have the full message body yet */ return MESSAGE_FRAGMENT; } else if (body_len > content_length) { /* We have the full message plus a fragment of a further * message */ ast_str_append(overflow, 0, "%s", body + content_length); ast_str_truncate(*request, message_len - (body_len - content_length)); return MESSAGE_FRAGMENT_COMPLETE; } else { /* Yay! Full message with no extra content */ return MESSAGE_COMPLETE; } } /*! * \brief Read SIP request or response from a TCP/TLS connection * * \param req The request structure to be filled in * \param tcptls_session The TCP/TLS connection from which to read * \retval -1 Failed to read data * \retval 0 Successfully read data */ static int sip_tcptls_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session, int authenticated, time_t start) { enum message_integrity message_integrity = MESSAGE_FRAGMENT; while (message_integrity == MESSAGE_FRAGMENT) { size_t datalen; if (ast_str_strlen(tcptls_session->overflow_buf) == 0) { char readbuf[4097]; int timeout; int res; if (!tcptls_session->client && !authenticated) { if ((timeout = sip_check_authtimeout(start)) < 0) { return -1; } if (timeout == 0) { ast_debug(2, "SIP TCP/TLS server timed out\n"); return -1; } } else { timeout = -1; } res = ast_wait_for_input(tcptls_session->fd, 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_tcptls_server_read(tcptls_session, readbuf, sizeof(readbuf) - 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; } readbuf[res] = '\0'; ast_str_append(&req->data, 0, "%s", readbuf); } 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; } message_integrity = check_message_integrity(&req->data, &tcptls_session->overflow_buf); } return 0; } /*! \brief SIP TCP thread management function This function reads from the socket, parses the packet into a request */ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session) { int res, timeout = -1, authenticated = 0, flags; time_t start; struct sip_request req = { 0, } , reqcpy = { 0, }; struct sip_threadinfo *me = NULL; char buf[1024] = ""; struct pollfd fds[2] = { { 0 }, { 0 }, }; struct ast_tcptls_session_args *ca = 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 threadt table. * 3. Last, the tcptls_session must be started. */ if (!tcptls_session->client) { if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) { /* unauth_sessions is decremented in the cleanup code */ goto cleanup; } if ((flags = fcntl(tcptls_session->fd, F_GETFL)) == -1) { ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno)); goto cleanup; } flags |= O_NONBLOCK; if (fcntl(tcptls_session->fd, F_SETFL, flags) == -1) { ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno)); goto cleanup; } if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? AST_TRANSPORT_TLS : AST_TRANSPORT_TCP))) { goto cleanup; } ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread"); } else { struct sip_threadinfo tmp = { .tcptls_session = tcptls_session, }; if ((!(ca = tcptls_session->parent)) || (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) || (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) { goto cleanup; } } flags = 1; if (setsockopt(tcptls_session->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) { ast_log(LOG_ERROR, "error enabling TCP keep-alives on sip socket: %s\n", strerror(errno)); goto cleanup; } me->threadid = pthread_self(); ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP"); /* set up pollfd to watch for reads on both the socket and the alert_pipe */ fds[0].fd = tcptls_session->fd; fds[1].fd = me->alert_pipe[0]; fds[0].events = fds[1].events = POLLIN | POLLPRI; if (!(req.data = ast_str_create(SIP_MIN_PACKET))) { goto cleanup; } if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET))) { 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_tcptls_stream_set_exclusive_input(tcptls_session->stream_cookie, 0); ast_tcptls_stream_set_timeout_sequence(tcptls_session->stream_cookie, ast_tvnow(), tcptls_session->client ? -1 : (authtimeout * 1000)); for (;;) { struct ast_str *str_save; if (!tcptls_session->client && req.authenticated && !authenticated) { authenticated = 1; ast_tcptls_stream_set_timeout_disable(tcptls_session->stream_cookie); ast_atomic_fetchadd_int(&unauth_sessions, -1); } /* calculate the timeout for unauthenticated server sessions */ if (!tcptls_session->client && !authenticated ) { if ((timeout = sip_check_authtimeout(start)) < 0) { goto cleanup; } if (timeout == 0) { ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "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", tcptls_session->ssl ? "TLS": "TCP", res); goto cleanup; } else if (res == 0) { /* timeout */ ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "TLS": "TCP"); goto cleanup; } } /* * handle the socket event, check for both reads from the socket fd or TCP overflow buffer, * and writes from alert_pipe fd. */ if (fds[0].revents || (ast_str_strlen(tcptls_session->overflow_buf) > 0)) { /* there is data on the socket to be read */ fds[0].revents = 0; /* clear request structure */ str_save = req.data; memset(&req, 0, sizeof(req)); req.data = str_save; ast_str_reset(req.data); str_save = reqcpy.data; memset(&reqcpy, 0, sizeof(reqcpy)); reqcpy.data = str_save; ast_str_reset(reqcpy.data); memset(buf, 0, sizeof(buf)); if (tcptls_session->ssl) { set_socket_transport(&req.socket, AST_TRANSPORT_TLS); req.socket.port = htons(ourport_tls); } else { set_socket_transport(&req.socket, AST_TRANSPORT_TCP); req.socket.port = htons(ourport_tcp); } req.socket.fd = tcptls_session->fd; res = sip_tcptls_read(&req, tcptls_session, authenticated, start); if (res < 0) { goto cleanup; } req.socket.tcptls_session = tcptls_session; req.socket.ws_session = NULL; handle_request_do(&req, &tcptls_session->remote_address); } if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */ enum sip_tcptls_alert alert; struct tcptls_packet *packet; fds[1].revents = 0; if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) { ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno)); continue; } switch (alert) { case TCPTLS_ALERT_STOP: goto cleanup; case TCPTLS_ALERT_DATA: ao2_lock(me); if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) { ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty\n"); } ao2_unlock(me); if (packet) { if (ast_tcptls_server_write(tcptls_session, 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"); } break; default: ast_log(LOG_ERROR, "Unknown tcptls thread alert '%u'\n", alert); } } } ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP"); cleanup: if (tcptls_session && !tcptls_session->client && !authenticated) { ast_atomic_fetchadd_int(&unauth_sessions, -1); } if (me) { ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing"); ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref"); } deinit_req(&reqcpy); deinit_req(&req); /* if client, we own the parent session arguments and must decrement ref */ if (ca) { ao2_t_ref(ca, -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; } #ifdef REF_DEBUG struct sip_peer *_ref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func) { if (peer) __ao2_ref_debug(peer, 1, tag, file, line, func); else ast_log(LOG_ERROR, "Attempt to Ref a null peer pointer\n"); return peer; } void *_unref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func) { if (peer) __ao2_ref_debug(peer, -1, tag, file, line, func); return NULL; } #else /*! * helper functions to unreference various types of objects. * By handling them this way, we don't have to declare the * destructor on each call, which removes the chance of errors. */ void *sip_unref_peer(struct sip_peer *peer, char *tag) { ao2_t_ref(peer, -1, tag); return NULL; } struct sip_peer *sip_ref_peer(struct sip_peer *peer, char *tag) { ao2_t_ref(peer, 1, tag); return peer; } #endif /* REF_DEBUG */ static void peer_sched_cleanup(struct sip_peer *peer) { if (peer->pokeexpire != -1) { AST_SCHED_DEL_UNREF(sched, peer->pokeexpire, sip_unref_peer(peer, "removing poke peer ref")); } if (peer->expire != -1) { AST_SCHED_DEL_UNREF(sched, peer->expire, sip_unref_peer(peer, "remove register expire ref")); } if (peer->keepalivesend != -1) { AST_SCHED_DEL_UNREF(sched, peer->keepalivesend, sip_unref_peer(peer, "remove keepalive peer ref")); } } typedef enum { SIP_PEERS_MARKED, SIP_PEERS_ALL, } peer_unlink_flag_t; /* this func is used with ao2_callback to unlink/delete all marked or linked peers, depending on arg */ static int match_and_cleanup_peer_sched(void *peerobj, void *arg, int flags) { struct sip_peer *peer = peerobj; peer_unlink_flag_t which = *(peer_unlink_flag_t *)arg; if (which == SIP_PEERS_ALL || peer->the_mark) { peer_sched_cleanup(peer); if (peer->dnsmgr) { ast_dnsmgr_release(peer->dnsmgr); peer->dnsmgr = NULL; sip_unref_peer(peer, "Release peer from dnsmgr"); } return CMP_MATCH; } return 0; } static void unlink_peers_from_tables(peer_unlink_flag_t flag) { ao2_t_callback(peers, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers"); ao2_t_callback(peers_by_ip, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers_by_ip"); } /* \brief Unlink all marked peers from ao2 containers */ static void unlink_marked_peers_from_tables(void) { unlink_peers_from_tables(SIP_PEERS_MARKED); } static void unlink_all_peers_from_tables(void) { unlink_peers_from_tables(SIP_PEERS_ALL); } /*! \brief 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. * * \param pvt The sip_pvt for which we wish to set the outboundproxy * \param proxy The sip_proxy which we will point pvt towards. * \return Returns void */ static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy) { struct sip_proxy *old_obproxy = pvt->outboundproxy; /* The sip_cfg.outboundproxy is statically allocated, and so * we don't ever need to adjust refcounts for it */ if (proxy && proxy != &sip_cfg.outboundproxy) { ao2_ref(proxy, +1); } pvt->outboundproxy = proxy; if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) { ao2_ref(old_obproxy, -1); } } /*! * \brief Unlink a dialog from the dialogs container, as well as any other places * that it may be currently stored. * * \note A reference to the dialog must be held before calling this function, and this * function does not release that reference. */ void dialog_unlink_all(struct sip_pvt *dialog) { struct sip_pkt *cp; struct ast_channel *owner; dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done"); ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink"); ao2_t_unlink(dialogs_needdestroy, dialog, "unlinking dialog_needdestroy via ao2_unlink"); ao2_t_unlink(dialogs_rtpcheck, dialog, "unlinking dialog_rtpcheck via ao2_unlink"); /* Unlink us from the owner (channel) if we have one */ owner = sip_pvt_lock_full(dialog); if (owner) { ast_debug(1, "Detaching from channel %s\n", ast_channel_name(owner)); ast_channel_tech_pvt_set(owner, dialog_unref(ast_channel_tech_pvt(owner), "resetting channel dialog ptr in unlink_all")); ast_channel_unlock(owner); ast_channel_unref(owner); sip_set_owner(dialog, NULL); } sip_pvt_unlock(dialog); if (dialog->registry) { if (dialog->registry->call == dialog) { dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all"); } ao2_t_replace(dialog->registry, NULL, "delete dialog->registry"); } if (dialog->stateid != -1) { ast_extension_state_del(dialog->stateid, cb_extensionstate); dialog->stateid = -1; } /* Remove link from peer to subscription of MWI */ if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog) { dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); } if (dialog->relatedpeer && dialog->relatedpeer->call == dialog) { dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself"); } /* remove all current packets in this dialog */ while((cp = dialog->packets)) { dialog->packets = dialog->packets->next; AST_SCHED_DEL(sched, cp->retransid); dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy"); if (cp->data) { ast_free(cp->data); } ast_free(cp); } AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr")); AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr")); if (dialog->autokillid > -1) { AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr")); } if (dialog->request_queue_sched_id > -1) { AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr")); } AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr")); if (dialog->t38id > -1) { AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); } if (dialog->stimer) { stop_session_timer(dialog); } dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time"); } static void append_history_full(struct sip_pvt *p, const char *fmt, ...) __attribute__((format(printf, 2, 3))); /*! \brief Convert transfer status to string */ static const char *referstatus2str(enum referstatus rstatus) { return map_x_s(referstatusstrings, rstatus, ""); } static inline void pvt_set_needdestroy(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. */ } append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason); if (!pvt->needdestroy) { pvt->needdestroy = 1; ao2_t_link(dialogs_needdestroy, pvt, "link pvt into dialogs_needdestroy container"); } } /*! \brief Initialize the initital request packet in the pvt structure. This packet is used for creating replies and future requests in a dialog */ static void initialize_initreq(struct sip_pvt *p, struct sip_request *req) { if (p->initreq.headers) { ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid); } else { ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid); } /* Use this as the basis */ copy_request(&p->initreq, req); parse_request(&p->initreq); if (req->debug) { ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines); } } /*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */ static void sip_alreadygone(struct sip_pvt *dialog) { ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid); dialog->alreadygone = 1; } /*! Resolve DNS srv name or host name in a sip_proxy structure */ static int 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->ip, proxy->name, 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->ip.ss.ss_family = get_address_family_filter(AST_TRANSPORT_UDP); /* Filter address family */ if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) { ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name); return FALSE; } } ast_sockaddr_set_port(&proxy->ip, proxy->port); proxy->last_dnsupdate = time(NULL); return TRUE; } /*! \brief Parse proxy string and return an ao2_alloc'd proxy. If dest is * non-NULL, no allocation is performed and dest is used instead. * On error NULL is returned. */ static struct sip_proxy *proxy_from_config(const char *proxy, int sipconf_lineno, struct sip_proxy *dest) { char *mutable_proxy, *sep, *name; int allocated = 0; if (!dest) { dest = ao2_alloc(sizeof(struct sip_proxy), NULL); if (!dest) { ast_log(LOG_WARNING, "Unable to allocate config storage for proxy\n"); return NULL; } allocated = 1; } /* Format is: [transport://]name[:port][,force] */ mutable_proxy = ast_skip_blanks(ast_strdupa(proxy)); sep = strchr(mutable_proxy, ','); if (sep) { *sep++ = '\0'; dest->force = !strncasecmp(ast_skip_blanks(sep), "force", 5); } else { dest->force = FALSE; } sip_parse_host(mutable_proxy, sipconf_lineno, &name, &dest->port, &dest->transport); /* Check that there is a name at all */ if (ast_strlen_zero(name)) { if (allocated) { ao2_ref(dest, -1); } else { dest->name[0] = '\0'; } return NULL; } ast_copy_string(dest->name, name, sizeof(dest->name)); /* Resolve host immediately */ proxy_update(dest); return dest; } /*! \brief 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 port_str2int(const char *pt, unsigned int standard) { int port = standard; if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) { port = standard; } return port; } /*! \brief Get default outbound proxy or global proxy */ static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer) { if (dialog && dialog->options && dialog->options->outboundproxy) { if (sipdebug) { ast_debug(1, "OBPROXY: Applying dialplan set OBproxy to this call\n"); } append_history(dialog, "OBproxy", "Using dialplan obproxy %s", dialog->options->outboundproxy->name); return dialog->options->outboundproxy; } if (peer && peer->outboundproxy) { if (sipdebug) { ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n"); } append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name); return peer->outboundproxy; } if (sip_cfg.outboundproxy.name[0]) { if (sipdebug) { ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n"); } append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name); return &sip_cfg.outboundproxy; } if (sipdebug) { ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n"); } return NULL; } /*! \brief 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. * following Jon Postel's rule: Be gentle in what you accept, strict with what you send */ static int method_match(enum sipmethod id, const char *name) { int len = strlen(sip_methods[id].text); int l_name = name ? strlen(name) : 0; /* true if the string is long enough, and ends with whitespace, and matches */ return (l_name >= len && name && name[len] < 33 && !strncasecmp(sip_methods[id].text, name, len)); } /*! \brief find_sip_method: Find SIP method from header */ static int find_sip_method(const char *msg) { int i, res = 0; if (ast_strlen_zero(msg)) { return 0; } for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) { if (method_match(i, msg)) { res = sip_methods[i].id; } } return res; } /*! \brief See if we pass debug IP filter */ static inline int sip_debug_test_addr(const struct ast_sockaddr *addr) { /* Can't debug if sipdebug is not enabled */ if (!sipdebug) { return 0; } /* A null debug_addr means we'll debug any address */ if (ast_sockaddr_isnull(&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(&debugaddr)) { return !ast_sockaddr_cmp(&debugaddr, addr); } else { return !ast_sockaddr_cmp_addr(&debugaddr, addr); } } /*! \brief The real destination address for a write */ static const struct ast_sockaddr *sip_real_dst(const struct sip_pvt *p) { if (p->outboundproxy) { return &p->outboundproxy->ip; } return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa; } /*! \brief Display SIP nat mode */ static const char *sip_nat_mode(const struct sip_pvt *p) { return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT"; } /*! \brief Test PVT for debugging output */ static inline int sip_debug_test_pvt(struct sip_pvt *p) { if (!sipdebug) { return 0; } return sip_debug_test_addr(sip_real_dst(p)); } /*! \brief Return int representing a bit field of transport types found in const char *transport */ static int get_transport_str2enum(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; } if (!strcasecmp(transport, "ws")) { res |= AST_TRANSPORT_WS; } if (!strcasecmp(transport, "wss")) { res |= AST_TRANSPORT_WSS; } return res; } /*! \brief Return configuration of transports for a device */ static inline const char *get_transport_list(unsigned int transports) { char *buf; if (!transports) { return "UNKNOWN"; } if (!(buf = ast_threadstorage_get(&sip_transport_str_buf, SIP_TRANSPORT_STR_BUFSIZE))) { return ""; } memset(buf, 0, SIP_TRANSPORT_STR_BUFSIZE); if (transports & AST_TRANSPORT_UDP) { strncat(buf, "UDP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf)); } if (transports & AST_TRANSPORT_TCP) { strncat(buf, "TCP,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf)); } if (transports & AST_TRANSPORT_TLS) { strncat(buf, "TLS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf)); } if (transports & AST_TRANSPORT_WS) { strncat(buf, "WS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf)); } if (transports & AST_TRANSPORT_WSS) { strncat(buf, "WSS,", SIP_TRANSPORT_STR_BUFSIZE - strlen(buf)); } /* Remove the trailing ',' if present */ if (strlen(buf)) { buf[strlen(buf) - 1] = 0; } return buf; } /*! \brief Return transport as string */ const char *sip_get_transport(enum ast_transport t) { switch (t) { case AST_TRANSPORT_UDP: return "UDP"; case AST_TRANSPORT_TCP: return "TCP"; case AST_TRANSPORT_TLS: return "TLS"; case AST_TRANSPORT_WS: case AST_TRANSPORT_WSS: return "WS"; } return "UNKNOWN"; } /*! \brief Return protocol string for srv dns query */ static inline const char *get_srv_protocol(enum ast_transport t) { switch (t) { case AST_TRANSPORT_UDP: return "udp"; case AST_TRANSPORT_WS: return "ws"; case AST_TRANSPORT_TLS: case AST_TRANSPORT_TCP: return "tcp"; case AST_TRANSPORT_WSS: return "wss"; } return "udp"; } /*! \brief Return service string for srv dns query */ static inline const char *get_srv_service(enum ast_transport t) { switch (t) { case AST_TRANSPORT_TCP: case AST_TRANSPORT_UDP: case AST_TRANSPORT_WS: return "sip"; case AST_TRANSPORT_TLS: case AST_TRANSPORT_WSS: return "sips"; } return "sip"; } /*! \brief Return transport of dialog. \note 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. \todo Fix this function to work correctly */ static inline const char *get_transport_pvt(struct sip_pvt *p) { if (p->outboundproxy && p->outboundproxy->transport) { set_socket_transport(&p->socket, p->outboundproxy->transport); } return sip_get_transport(p->socket.type); } /*! * \internal * \brief Transmit SIP message * * \details * Sends a SIP request or response on a given socket (in the pvt) * \note * Called by retrans_pkt, send_request, send_response and __sip_reliable_xmit * * \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures. */ static int __sip_xmit(struct sip_pvt *p, struct ast_str *data) { int res = 0; const struct ast_sockaddr *dst = sip_real_dst(p); ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s\n", ast_str_buffer(data), get_transport_pvt(p), ast_sockaddr_stringify(dst)); if (sip_prepare_socket(p) < 0) { return XMIT_ERROR; } if (p->socket.type == AST_TRANSPORT_UDP) { res = ast_sendto(p->socket.fd, ast_str_buffer(data), ast_str_strlen(data), 0, dst); } else if (p->socket.tcptls_session) { res = sip_tcptls_write(p->socket.tcptls_session, ast_str_buffer(data), ast_str_strlen(data)); } else if (p->socket.ws_session) { if (!(res = ast_websocket_write(p->socket.ws_session, AST_WEBSOCKET_OPCODE_TEXT, ast_str_buffer(data), ast_str_strlen(data)))) { /* The WebSocket API just returns 0 on success and -1 on failure, while this code expects the payload length to be returned */ res = ast_str_strlen(data); } } else { ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n"); return XMIT_ERROR; } 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 = XMIT_ERROR; /* Don't bother with trying to transmit again */ } } 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; } /*! \brief Build a Via header for a request */ static void build_via(struct sip_pvt *p) { /* Work around buggy UNIDEN UIP200 firmware */ const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : ""; /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */ snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s;branch=z9hG4bK%08x%s", get_transport_pvt(p), ast_sockaddr_stringify_remote(&p->ourip), (unsigned)p->branch, rport); } /*! \brief 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. */ static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_sockaddr *us, struct sip_pvt *p) { 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 = 0; ast_sockaddr_copy(us, &internip); /* starting guess for the internal address */ /* 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)) { if (localaddr && !ast_sockaddr_isnull(&externaddr) && !ast_sockaddr_is_any(&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 = localaddr && !ast_sockaddr_isnull(&externaddr) && ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ; } if (want_remap && (!sip_cfg.matchexternaddrlocally || !ast_apply_ha(localaddr, us)) ) { /* if we used externhost, see if it is time to refresh the info */ if (externexpire && time(NULL) >= externexpire) { if (ast_sockaddr_resolve_first(&externaddr, externhost, 0)) { ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost); } externexpire = time(NULL) + externrefresh; } if (!ast_sockaddr_isnull(&externaddr)) { ast_sockaddr_copy(us, &externaddr); switch (p->socket.type) { case AST_TRANSPORT_TCP: if (!externtcpport && ast_sockaddr_port(&externaddr)) { /* for consistency, default to the externaddr port */ externtcpport = ast_sockaddr_port(&externaddr); } ast_sockaddr_set_port(us, externtcpport); break; case AST_TRANSPORT_TLS: ast_sockaddr_set_port(us, externtlsport); break; case AST_TRANSPORT_UDP: if (!ast_sockaddr_port(&externaddr)) { ast_sockaddr_set_port(us, ast_sockaddr_port(&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 (p->socket.type) { case AST_TRANSPORT_TCP: if (!ast_sockaddr_is_any(&sip_tcp_desc.local_address)) { ast_sockaddr_copy(us, &sip_tcp_desc.local_address); } else { ast_sockaddr_set_port(us, ast_sockaddr_port(&sip_tcp_desc.local_address)); } break; case AST_TRANSPORT_TLS: if (!ast_sockaddr_is_any(&sip_tls_desc.local_address)) { ast_sockaddr_copy(us, &sip_tls_desc.local_address); } else { ast_sockaddr_set_port(us, ast_sockaddr_port(&sip_tls_desc.local_address)); } break; case AST_TRANSPORT_UDP: /* fall through on purpose */ default: if (!ast_sockaddr_is_any(&bindaddr)) { ast_sockaddr_copy(us, &bindaddr); } if (!ast_sockaddr_port(us)) { ast_sockaddr_set_port(us, ast_sockaddr_port(&bindaddr)); } } } ast_debug(3, "Setting AST_TRANSPORT_%s with address %s\n", sip_get_transport(p->socket.type), ast_sockaddr_stringify(us)); } /*! \brief Append to SIP dialog history with arg list */ static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap) { char buf[80], *c = buf; /* max history length */ struct sip_history *hist; int l; vsnprintf(buf, sizeof(buf), fmt, ap); strsep(&c, "\r\n"); /* Trim up everything after \r or \n */ l = strlen(buf) + 1; if (!(hist = ast_calloc(1, sizeof(*hist) + l))) { return; } if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) { ast_free(hist); return; } memcpy(hist->event, buf, l); if (p->history_entries == MAX_HISTORY_ENTRIES) { struct sip_history *oldest; oldest = AST_LIST_REMOVE_HEAD(p->history, list); p->history_entries--; ast_free(oldest); } AST_LIST_INSERT_TAIL(p->history, hist, list); p->history_entries++; } /*! \brief Append to SIP dialog history with arg list */ static void append_history_full(struct sip_pvt *p, const char *fmt, ...) { va_list ap; if (!p) { return; } if (!p->do_history && !recordhistory && !dumphistory) { return; } va_start(ap, fmt); append_history_va(p, fmt, ap); va_end(ap); return; } /*! \brief Retransmit SIP message if no answer (Called from scheduler) */ static int retrans_pkt(const void *data) { struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL; int reschedule = DEFAULT_RETRANS; int xmitres = 0; /* how many ms until retrans timeout is reached */ int64_t diff = pkt->retrans_stop_time - ast_tvdiff_ms(ast_tvnow(), pkt->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 > pkt->retrans_stop_time)) { pkt->retrans_stop = 1; } /* Lock channel PVT */ sip_pvt_lock(pkt->owner); if (!pkt->retrans_stop) { pkt->retrans++; if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */ if (sipdebug) { ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method); } } else { int siptimer_a; if (sipdebug) { ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method); } if (!pkt->timer_a) { pkt->timer_a = 2 ; } else { pkt->timer_a = 2 * pkt->timer_a; } /* For non-invites, a maximum of 4 secs */ siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */ if (pkt->method != SIP_INVITE && siptimer_a > 4000) { siptimer_a = 4000; } /* Reschedule re-transmit */ reschedule = siptimer_a; ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans + 1, siptimer_a, pkt->timer_t1, pkt->retransid); } if (sip_debug_test_pvt(pkt->owner)) { const struct ast_sockaddr *dst = sip_real_dst(pkt->owner); ast_verbose("Retransmitting #%d (%s) to %s:\n%s\n---\n", pkt->retrans, sip_nat_mode(pkt->owner), ast_sockaddr_stringify(dst), ast_str_buffer(pkt->data)); } append_history(pkt->owner, "ReTx", "%d %s", reschedule, ast_str_buffer(pkt->data)); xmitres = __sip_xmit(pkt->owner, pkt->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 (xmitres != XMIT_ERROR) { if (reschedule >= diff) { pkt->retrans_stop = 1; reschedule = diff; } sip_pvt_unlock(pkt->owner); return reschedule; } } /* 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. */ pkt->retransid = -1; /* Kill this scheduler item */ if (pkt->method != SIP_OPTIONS && xmitres == 0) { if (pkt->is_fatal || sipdebug) { /* 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\n" "Packet timed out after %dms with no response\n", pkt->owner->callid, pkt->seqno, pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request", (int) ast_tvdiff_ms(ast_tvnow(), pkt->time_sent)); } } else if (pkt->method == SIP_OPTIONS && sipdebug) { ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions\n", pkt->owner->callid); } if (xmitres == XMIT_ERROR) { ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid); append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)"); } else { append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)"); } if (pkt->is_fatal) { while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) { sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */ usleep(1); sip_pvt_lock(pkt->owner); } if (pkt->owner->owner && !ast_channel_hangupcause(pkt->owner->owner)) { ast_channel_hangupcause_set(pkt->owner->owner, AST_CAUSE_NO_USER_RESPONSE); } if (pkt->owner->owner) { 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", pkt->owner->callid); if (pkt->is_resp && (pkt->response_code >= 200) && (pkt->response_code < 300) && pkt->owner->pendinginvite && ast_test_flag(&pkt->owner->flags[1], SIP_PAGE2_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." */ pkt->owner->invitestate = INV_TERMINATED; pkt->owner->pendinginvite = 0; } else { /* there is nothing left to do, mark the dialog as gone */ sip_alreadygone(pkt->owner); } ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_NO_USER_RESPONSE); ast_channel_unlock(pkt->owner->owner); } else { /* If no channel owner, destroy now */ /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */ if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) { pvt_set_needdestroy(pkt->owner, "no response to critical packet"); sip_alreadygone(pkt->owner); append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately"); } } } else if (pkt->owner->pendinginvite == pkt->seqno) { ast_log(LOG_WARNING, "Timeout on %s on non-critical invite transaction.\n", pkt->owner->callid); pkt->owner->invitestate = INV_TERMINATED; pkt->owner->pendinginvite = 0; check_pendings(pkt->owner); } if (pkt->method == SIP_BYE) { /* We're not getting answers on SIP BYE's. Tear down the call anyway. */ sip_alreadygone(pkt->owner); if (pkt->owner->owner) { ast_channel_unlock(pkt->owner->owner); } append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway."); pvt_set_needdestroy(pkt->owner, "no response to BYE"); } /* Remove the packet */ for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) { if (cur == pkt) { UNLINK(cur, pkt->owner->packets, prev); sip_pvt_unlock(pkt->owner); if (pkt->owner) { pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now"); } if (pkt->data) { ast_free(pkt->data); } pkt->data = NULL; ast_free(pkt); return 0; } } /* error case */ ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n"); sip_pvt_unlock(pkt->owner); return 0; } /*! * \internal * \brief Transmit packet with retransmits * \return 0 on success, -1 on failure to allocate packet */ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, int resp, struct ast_str *data, int fatal, int sipmethod) { struct sip_pkt *pkt = NULL; int siptimer_a = DEFAULT_RETRANS; int xmitres = 0; unsigned respid; if (sipmethod == SIP_INVITE) { /* Note this is a pending invite */ p->pendinginvite = seqno; } /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */ /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */ /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */ if (!(p->socket.type & AST_TRANSPORT_UDP)) { xmitres = __sip_xmit(p, data); /* Send packet */ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */ append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)"); return AST_FAILURE; } else { return AST_SUCCESS; } } if (!(pkt = ast_calloc(1, sizeof(*pkt)))) { return AST_FAILURE; } /* copy data, add a terminator and save length */ if (!(pkt->data = ast_str_create(ast_str_strlen(data)))) { ast_free(pkt); return AST_FAILURE; } ast_str_set(&pkt->data, 0, "%s%s", ast_str_buffer(data), "\0"); /* copy other parameters from the caller */ pkt->method = sipmethod; pkt->seqno = seqno; pkt->is_resp = resp; pkt->is_fatal = fatal; pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner"); pkt->next = p->packets; p->packets = pkt; /* Add it to the queue */ if (resp) { /* Parse out the response code */ if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) { pkt->response_code = respid; } } pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */ pkt->retransid = -1; if (pkt->timer_t1) { siptimer_a = pkt->timer_t1; } pkt->time_sent = ast_tvnow(); /* time packet was sent */ pkt->retrans_stop_time = 64 * (pkt->timer_t1 ? pkt->timer_t1 : DEFAULT_TIMER_T1); /* time in ms after pkt->time_sent to stop retransmission */ /* Schedule retransmission */ AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1); if (sipdebug) { ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid); } xmitres = __sip_xmit(pkt->owner, pkt->data); /* Send packet */ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)"); ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n"); AST_SCHED_DEL(sched, pkt->retransid); p->packets = pkt->next; pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now"); ast_free(pkt->data); ast_free(pkt); return AST_FAILURE; } else { /* This is odd, but since the retrans timer starts at 500ms and the do_monitor thread * only wakes up every 1000ms by default, we have to poke 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 (monitor_thread != AST_PTHREADT_NULL) { pthread_kill(monitor_thread, SIGURG); } return AST_SUCCESS; } } /*! \brief 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 dialog_unref(). */ static int __sip_autodestruct(const void *data) { struct sip_pvt *p = (struct sip_pvt *)data; struct ast_channel *owner; /* If this is a subscription, tell the phone that we got a timeout */ if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) { struct state_notify_data data = { 0, }; data.state = AST_EXTENSION_DEACTIVATED; transmit_state_notify(p, &data, 1, TRUE); /* Send last notification */ p->subscribed = NONE; append_history(p, "Subscribestatus", "timeout"); ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->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 (p->packets) { if (!p->needdestroy) { char method_str[31]; ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : ""); append_history(p, "ReliableXmit", "timeout"); if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) { if (p->ongoing_reinvite || method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) { pvt_set_needdestroy(p, "autodestruct"); } } return 10000; } else { /* They've had their chance to respond. Time to bail */ __sip_pretend_ack(p); } } /* Reset schedule ID */ p->autokillid = -1; /* * Lock both the pvt and the channel safely so that we can queue up a frame. */ owner = sip_pvt_lock_full(p); if (owner) { ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner %s in place (Method: %s). Rescheduling destruction for 10000 ms\n", p->callid, ast_channel_name(owner), sip_methods[p->method].text); ast_queue_hangup_with_cause(owner, AST_CAUSE_PROTOCOL_ERROR); ast_channel_unlock(owner); ast_channel_unref(owner); sip_pvt_unlock(p); return 10000; } else if (p->refer && !p->alreadygone) { ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid); stop_media_flows(p); transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1); append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } else { append_history(p, "AutoDestroy", "%s", p->callid); ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid); sip_pvt_unlock(p); dialog_unlink_all(p); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */ sip_pvt_lock(p); /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */ /* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */ /* sip_destroy also absorbs the reference */ } sip_pvt_unlock(p); dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it."); return 0; } /*! \brief Schedule final destruction of SIP dialog. This can not be canceled. * This function is used to keep a dialog around for a period of time in order * to properly respond to any retransmits. */ void sip_scheddestroy_final(struct sip_pvt *p, int ms) { if (p->final_destruction_scheduled) { return; /* already set final destruction */ } sip_scheddestroy(p, ms); if (p->autokillid != -1) { p->final_destruction_scheduled = 1; } } /*! \brief Schedule destruction of SIP dialog */ void sip_scheddestroy(struct sip_pvt *p, int ms) { if (p->final_destruction_scheduled) { return; /* already set final destruction */ } if (ms < 0) { if (p->timer_t1 == 0) { p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */ } if (p->timer_b == 0) { p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */ } ms = p->timer_t1 * 64; } if (sip_debug_test_pvt(p)) { ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text); } if (sip_cancel_destroy(p)) { ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } if (p->do_history) { append_history(p, "SchedDestroy", "%d ms", ms); } p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct")); if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0) { stop_session_timer(p); } } /*! \brief Cancel destruction of SIP dialog. * Be careful as this also absorbs the reference - if you call it * from within the scheduler, this might be the last reference. */ int sip_cancel_destroy(struct sip_pvt *p) { if (p->final_destruction_scheduled) { return 0; } if (p->autokillid > -1) { append_history(p, "CancelDestroy", ""); AST_SCHED_DEL_UNREF(sched, p->autokillid, dialog_unref(p, "remove ref for autokillid")); } return 0; } /*! \brief Acknowledges receipt of a packet and stops retransmission * called with p locked*/ int __sip_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod) { struct sip_pkt *cur, *prev = NULL; 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 (p->outboundproxy && !p->outboundproxy->force) { ref_proxy(p, NULL); } for (cur = p->packets; cur; prev = cur, cur = cur->next) { if (cur->seqno != seqno || cur->is_resp != resp) { continue; } if (cur->is_resp || cur->method == sipmethod) { res = TRUE; msg = "Found"; if (!resp && (seqno == p->pendinginvite)) { ast_debug(1, "Acked pending invite %u\n", p->pendinginvite); p->pendinginvite = 0; } if (cur->retransid > -1) { if (sipdebug) ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid); } /* This odd section is designed to thwart a * race condition in the packet scheduler. There are * two conditions under which deleting the packet from the * scheduler can fail. * * 1. The packet has been removed from the scheduler because retransmission * is being attempted. The problem is that if the packet is currently attempting * retransmission and we are at this point in the code, then that MUST mean * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the * lock temporarily to allow retransmission. * * 2. The packet has reached its maximum number of retransmissions and has * been permanently removed from the packet scheduler. If this is the case, then * the packet's retransid will be set to -1. The atomicity of the setting and checking * of the retransid to -1 is ensured since in both cases p's lock is held. */ while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) { sip_pvt_unlock(p); usleep(1); sip_pvt_lock(p); } UNLINK(cur, p->packets, prev); dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt"); if (cur->data) { ast_free(cur->data); } ast_free(cur); break; } } ast_debug(1, "Stopping retransmission on '%s' of %s %u: Match %s\n", p->callid, resp ? "Response" : "Request", seqno, msg); return res; } /*! \brief Pretend to ack all packets * called with p locked */ void __sip_pretend_ack(struct sip_pvt *p) { struct sip_pkt *cur = NULL; while (p->packets) { int method; if (cur == p->packets) { ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text); return; } cur = p->packets; method = (cur->method) ? cur->method : find_sip_method(ast_str_buffer(cur->data)); __sip_ack(p, cur->seqno, cur->is_resp, method); } } /*! \brief Acks receipt of packet, keep it around (used for provisional responses) */ int __sip_semi_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod) { struct sip_pkt *cur; int res = FALSE; for (cur = p->packets; cur; cur = cur->next) { if (cur->seqno == seqno && cur->is_resp == resp && (cur->is_resp || method_match(sipmethod, ast_str_buffer(cur->data)))) { /* this is our baby */ if (cur->retransid > -1) { if (sipdebug) ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text); } AST_SCHED_DEL(sched, cur->retransid); res = TRUE; break; } } ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %u: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found"); return res; } /*! \brief Copy SIP request, parse it */ static void parse_copy(struct sip_request *dst, const struct sip_request *src) { copy_request(dst, src); parse_request(dst); } /*! \brief add a blank line if no body */ static void add_blank(struct sip_request *req) { if (!req->lines) { /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */ ast_str_append(&req->data, 0, "\r\n"); } } static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp) { const char *msg = NULL; struct ast_channel *chan; int res = 0; int old_sched_id = pvt->provisional_keepalive_sched_id; chan = sip_pvt_lock_full(pvt); /* Check that nothing has changed while we were waiting for the lock */ if (old_sched_id != pvt->provisional_keepalive_sched_id) { /* Keepalive has been cancelled or rescheduled, clean up and leave */ if (chan) { ast_channel_unlock(chan); chan = ast_channel_unref(chan); } sip_pvt_unlock(pvt); dialog_unref(pvt, "dialog ref for provisional keepalive"); return 0; } if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) { msg = "183 Session Progress"; } if (pvt->invitestate < INV_COMPLETED) { if (with_sdp) { transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE); } else { transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq); } res = PROVIS_KEEPALIVE_TIMEOUT; } if (chan) { ast_channel_unlock(chan); chan = ast_channel_unref(chan); } if (!res) { pvt->provisional_keepalive_sched_id = -1; } sip_pvt_unlock(pvt); if (!res) { dialog_unref(pvt, "dialog ref for provisional keepalive"); } return res; } static int send_provisional_keepalive(const void *data) { struct sip_pvt *pvt = (struct sip_pvt *) data; return send_provisional_keepalive_full(pvt, 0); } static int send_provisional_keepalive_with_sdp(const void *data) { struct sip_pvt *pvt = (void *) data; return send_provisional_keepalive_full(pvt, 1); } static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp) { AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr")); pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT, with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback")); } static void add_required_respheader(struct sip_request *req) { struct ast_str *str; int i; if (!req->reqsipoptions) { return; } str = ast_str_create(32); for (i = 0; i < ARRAY_LEN(sip_options); ++i) { if (!(req->reqsipoptions & sip_options[i].id)) { continue; } if (ast_str_strlen(str) > 0) { ast_str_append(&str, 0, ", "); } ast_str_append(&str, 0, "%s", sip_options[i].text); } if (ast_str_strlen(str) > 0) { add_header(req, "Require", ast_str_buffer(str)); } ast_free(str); } /*! \brief Transmit response on SIP request*/ static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno) { int res; finalize_content(req); add_blank(req); if (sip_debug_test_pvt(p)) { const struct ast_sockaddr *dst = sip_real_dst(p); ast_verbose("\n<--- %sTransmitting (%s) to %s --->\n%s\n<------------>\n", reliable ? "Reliably " : "", sip_nat_mode(p), ast_sockaddr_stringify(dst), ast_str_buffer(req->data)); } if (p->do_history) { struct sip_request tmp = { .rlpart1 = 0, }; parse_copy(&tmp, req); append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", ast_str_buffer(tmp.data), sip_get_header(&tmp, "CSeq"), (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlpart2) : sip_methods[tmp.method].text); deinit_req(&tmp); } /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */ if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) { AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr")); } res = (reliable) ? __sip_reliable_xmit(p, seqno, 1, req->data, (reliable == XMIT_CRITICAL), req->method) : __sip_xmit(p, req->data); deinit_req(req); if (res > 0) { return 0; } return res; } /*! * \internal * \brief Send SIP Request to the other part of the dialogue * \return see \ref __sip_xmit */ static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, uint32_t seqno) { int res; /* If we have an outbound proxy, reset peer address Only do this once. */ if (p->outboundproxy) { p->sa = p->outboundproxy->ip; } finalize_content(req); add_blank(req); if (sip_debug_test_pvt(p)) { if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT)) { ast_verbose("%sTransmitting (NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", ast_sockaddr_stringify(&p->recv), ast_str_buffer(req->data)); } else { ast_verbose("%sTransmitting (no NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", ast_sockaddr_stringify(&p->sa), ast_str_buffer(req->data)); } } if (p->do_history) { struct sip_request tmp = { .rlpart1 = 0, }; parse_copy(&tmp, req); append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", ast_str_buffer(tmp.data), sip_get_header(&tmp, "CSeq"), sip_methods[tmp.method].text); deinit_req(&tmp); } res = (reliable) ? __sip_reliable_xmit(p, seqno, 0, req->data, (reliable == XMIT_CRITICAL), req->method) : __sip_xmit(p, req->data); deinit_req(req); return res; } static void enable_dsp_detect(struct sip_pvt *p) { int features = 0; if (p->dsp) { return; } if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) || (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) { if (p->rtp) { ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND); } features |= DSP_FEATURE_DIGIT_DETECT; } if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) { features |= DSP_FEATURE_FAX_DETECT; } if (!features) { return; } if (!(p->dsp = ast_dsp_new())) { return; } ast_dsp_set_features(p->dsp, features); if (global_relaxdtmf) { ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF); } } static void disable_dsp_detect(struct sip_pvt *p) { if (p->dsp) { ast_dsp_free(p->dsp); p->dsp = NULL; } } /*! \brief Set an option on a SIP dialog */ static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen) { int res = -1; struct sip_pvt *p = ast_channel_tech_pvt(chan); if (!p) { ast_log(LOG_ERROR, "Attempt to Ref a null pointer. sip private structure is gone!\n"); return -1; } sip_pvt_lock(p); switch (option) { case AST_OPTION_FORMAT_READ: if (p->rtp) { res = ast_rtp_instance_set_read_format(p->rtp, *(struct ast_format **) data); } break; case AST_OPTION_FORMAT_WRITE: if (p->rtp) { res = ast_rtp_instance_set_write_format(p->rtp, *(struct ast_format **) data); } break; case AST_OPTION_DIGIT_DETECT: if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) || (ast_test_flag(&p->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)); if (*cp) { enable_dsp_detect(p); } else { disable_dsp_detect(p); } res = 0; } break; case AST_OPTION_SECURE_SIGNALING: p->req_secure_signaling = *(unsigned int *) data; res = 0; break; case AST_OPTION_SECURE_MEDIA: ast_set2_flag(&p->flags[1], *(unsigned int *) data, SIP_PAGE2_USE_SRTP); res = 0; break; default: break; } sip_pvt_unlock(p); return res; } /*! \brief Query an option on a SIP dialog */ static 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 *p = (struct sip_pvt *) ast_channel_tech_pvt(chan); char *cp; sip_pvt_lock(p); 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(&p->flags[1], SIP_PAGE2_T38SUPPORT)) { switch (p->t38.state) { case T38_LOCAL_REINVITE: case T38_PEER_REINVITE: state = T38_STATE_NEGOTIATING; break; case T38_ENABLED: state = T38_STATE_NEGOTIATED; break; case T38_REJECTED: state = T38_STATE_REJECTED; break; default: state = T38_STATE_UNKNOWN; } } *((enum ast_t38_state *) data) = state; res = 0; break; case AST_OPTION_DIGIT_DETECT: cp = (char *) data; *cp = p->dsp ? 1 : 0; ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", ast_channel_name(chan)); break; case AST_OPTION_SECURE_SIGNALING: *((unsigned int *) data) = p->req_secure_signaling; res = 0; break; case AST_OPTION_SECURE_MEDIA: *((unsigned int *) data) = ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP) ? 1 : 0; res = 0; break; case AST_OPTION_DEVICE_NAME: if (p && p->outgoing_call) { cp = (char *) data; ast_copy_string(cp, p->dialstring, *datalen); res = 0; } /* We purposely break with a return of -1 in the * implied else case here */ break; default: break; } sip_pvt_unlock(p); return res; } /*! \brief 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 *find_closing_quote(const char *start, const char *lim) { char last_char = '\0'; const char *s; for (s = start; *s && s != lim; last_char = *s++) { if (*s == '"' && last_char != '\\') break; } return s; } /*! \brief Send message with Access-URL header, if this is an HTML URL only! */ static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen) { struct sip_pvt *p = ast_channel_tech_pvt(chan); if (subclass != AST_HTML_URL) return -1; ast_string_field_build(p, url, "<%s>;mode=active", data); if (sip_debug_test_pvt(p)) ast_debug(1, "Send URL %s, state = %u!\n", data, ast_channel_state(chan)); switch (ast_channel_state(chan)) { case AST_STATE_RING: transmit_response(p, "100 Trying", &p->initreq); break; case AST_STATE_RINGING: transmit_response(p, "180 Ringing", &p->initreq); break; case AST_STATE_UP: if (!p->pendinginvite) { /* We are up, and have no outstanding invite */ transmit_reinvite_with_sdp(p, FALSE, FALSE); } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { ast_set_flag(&p->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)); } return 0; } /*! \brief Deliver SIP call ID for the call */ static const char *sip_get_callid(struct ast_channel *chan) { return ast_channel_tech_pvt(chan) ? ((struct sip_pvt *) ast_channel_tech_pvt(chan))->callid : ""; } /*! * \internal * \brief Send SIP MESSAGE text within a call * \note Called from PBX core sendtext() application */ static int sip_sendtext(struct ast_channel *ast, const char *text) { struct sip_pvt *dialog = ast_channel_tech_pvt(ast); int debug; if (!dialog) { return -1; } /* NOT ast_strlen_zero, because a zero-length message is specifically * allowed by RFC 3428 (See section 10, Examples) */ if (!text) { return 0; } if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) { ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n"); return 0; } debug = sip_debug_test_pvt(dialog); if (debug) { ast_verbose("Sending text %s on %s\n", text, ast_channel_name(ast)); } /* Setup to send text message */ sip_pvt_lock(dialog); destroy_msg_headers(dialog); ast_string_field_set(dialog, msg_body, text); transmit_message(dialog, 0, 0); sip_pvt_unlock(dialog); return 0; } /*! \brief 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. */ static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms, const char *path) { char port[10]; char ipaddr[INET6_ADDRSTRLEN]; char regseconds[20]; char *tablename = NULL; char str_lastms[20]; const char *sysname = ast_config_AST_SYSTEM_NAME; char *syslabel = NULL; time_t nowtime = time(NULL) + expirey; const char *fc = fullcontact ? "fullcontact" : NULL; int realtimeregs = ast_check_realtime("sipregs"); tablename = realtimeregs ? "sipregs" : "sippeers"; snprintf(str_lastms, sizeof(str_lastms), "%d", lastms); snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */ ast_copy_string(ipaddr, ast_sockaddr_isnull(addr) ? "" : ast_sockaddr_stringify_addr(addr), sizeof(ipaddr)); ast_copy_string(port, ast_sockaddr_port(addr) ? ast_sockaddr_stringify_port(addr) : "", sizeof(port)); if (ast_strlen_zero(sysname)) { /* No system name, disable this */ sysname = NULL; } else if (sip_cfg.rtsave_sysname) { syslabel = "regserver"; } /* XXX IMPORTANT: Anytime you add a new parameter to be updated, you * must also add it to contrib/scripts/asterisk.ldap-schema, * contrib/scripts/asterisk.ldif, * and to configs/res_ldap.conf.sample as described in * bugs 15156 and 15895 */ /* This is ugly, we need something better ;-) */ if (sip_cfg.rtsave_path) { if (fc) { ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr, "port", port, "regseconds", regseconds, deprecated_username ? "username" : "defaultuser", defaultuser, "useragent", useragent, "lastms", str_lastms, "path", path, /* Path data can be NULL */ fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */ } else { ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr, "port", port, "regseconds", regseconds, "useragent", useragent, "lastms", str_lastms, deprecated_username ? "username" : "defaultuser", defaultuser, "path", path, /* Path data can be NULL */ syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */ } } else { if (fc) { ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr, "port", port, "regseconds", regseconds, deprecated_username ? "username" : "defaultuser", defaultuser, "useragent", useragent, "lastms", str_lastms, fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */ } else { ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr, "port", port, "regseconds", regseconds, "useragent", useragent, "lastms", str_lastms, deprecated_username ? "username" : "defaultuser", defaultuser, syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */ } } } /*! \brief Automatically add peer extension to dial plan */ static void register_peer_exten(struct sip_peer *peer, int onoff) { char multi[256]; char *stringp, *ext, *context; struct pbx_find_info q = { .stacklen = 0 }; /* XXX note that sip_cfg.regcontext is both a global 'enable' flag and * the name of the global regexten context, if not specified * individually. */ if (ast_strlen_zero(sip_cfg.regcontext)) return; ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi)); stringp = multi; while ((ext = strsep(&stringp, "&"))) { if ((context = strchr(ext, '@'))) { *context++ = '\0'; /* split ext@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_cfg.regcontext; } if (onoff) { if (!ast_exists_extension(NULL, context, ext, 1, NULL)) { ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop", ast_strdup(peer->name), ast_free_ptr, "SIP"); } } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) { ast_context_remove_extension(context, ext, 1, NULL); } } } /*! Destroy mailbox subscriptions */ static void destroy_mailbox(struct sip_mailbox *mailbox) { if (mailbox->event_sub) { mailbox->event_sub = stasis_unsubscribe(mailbox->event_sub); } ast_free(mailbox); } /*! Destroy all peer-related mailbox subscriptions */ static void clear_peer_mailboxes(struct sip_peer *peer) { struct sip_mailbox *mailbox; while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry))) destroy_mailbox(mailbox); } static void sip_destroy_peer_fn(void *peer) { sip_destroy_peer(peer); } /*! \brief Destroy peer object from memory */ static void sip_destroy_peer(struct sip_peer *peer) { 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. */ clear_peer_mailboxes(peer); if (peer->outboundproxy) { ao2_ref(peer->outboundproxy, -1); peer->outboundproxy = NULL; } /* Delete it, it needs to disappear */ if (peer->call) { dialog_unlink_all(peer->call); peer->call = dialog_unref(peer->call, "peer->call is being unset"); } if (peer->mwipvt) { /* We have an active subscription, delete it */ dialog_unlink_all(peer->mwipvt); peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt"); } if (peer->chanvars) { ast_variables_destroy(peer->chanvars); peer->chanvars = NULL; } sip_route_clear(&peer->path); register_peer_exten(peer, FALSE); ast_free_acl_list(peer->acl); ast_free_acl_list(peer->directmediaacl); if (peer->selfdestruct) ast_atomic_fetchadd_int(&apeerobjs, -1); else if (!ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && peer->is_realtime) { ast_atomic_fetchadd_int(&rpeerobjs, -1); ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs); } else ast_atomic_fetchadd_int(&speerobjs, -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; } else if (peer->socket.ws_session) { ast_websocket_unref(peer->socket.ws_session); peer->socket.ws_session = NULL; } peer->named_callgroups = ast_unref_namedgroups(peer->named_callgroups); peer->named_pickupgroups = ast_unref_namedgroups(peer->named_pickupgroups); ast_cc_config_params_destroy(peer->cc_params); ast_string_field_free_memory(peer); ao2_cleanup(peer->caps); ast_rtp_dtls_cfg_free(&peer->dtls_cfg); ast_endpoint_shutdown(peer->endpoint); peer->endpoint = NULL; } /*! \brief Update peer data in database (if used) */ static void update_peer(struct sip_peer *p, int expire) { int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS); if (sip_cfg.peer_rtupdate && (p->is_realtime || rtcachefriends)) { struct ast_str *r = sip_route_list(&p->path, 0, 0); if (r) { realtime_update_peer(p->name, &p->addr, p->username, p->fullcontact, p->useragent, expire, p->deprecated_username, p->lastms, ast_str_buffer(r)); ast_free(r); } } } static struct ast_variable *get_insecure_variable_from_config(struct ast_config *cfg) { struct ast_variable *var = NULL; struct ast_flags flags = {0}; char *cat = NULL; const char *insecure; while ((cat = ast_category_browse(cfg, cat))) { insecure = ast_variable_retrieve(cfg, cat, "insecure"); set_insecure_flags(&flags, insecure, -1); if (ast_test_flag(&flags, SIP_INSECURE_PORT)) { var = ast_category_root(cfg, cat); break; } } return var; } static struct ast_variable *get_insecure_variable_from_sippeers(const char *column, const char *value) { struct ast_config *peerlist; struct ast_variable *var = NULL; if ((peerlist = ast_load_realtime_multientry("sippeers", column, value, "insecure LIKE", "%port%", SENTINEL))) { if ((var = get_insecure_variable_from_config(peerlist))) { /* Must clone, because var will get freed along with * peerlist. */ var = ast_variables_dup(var); } ast_config_destroy(peerlist); } return var; } /* Yes.. the only column that makes sense to pass is "ipaddr", but for * consistency's sake, we require the column name to be passed. As extra * argument, we take a pointer to var. We already got the info, so we better * return it and save the caller a query. If return value is nonzero, then *var * is nonzero too (and the other way around). */ static struct ast_variable *get_insecure_variable_from_sipregs(const char *column, const char *value, struct ast_variable **var) { struct ast_variable *varregs = NULL; struct ast_config *regs, *peers; char *regscat; const char *regname; if (!(regs = ast_load_realtime_multientry("sipregs", column, value, SENTINEL))) { return NULL; } /* Load *all* peers that are probably insecure=port */ if (!(peers = ast_load_realtime_multientry("sippeers", "insecure LIKE", "%port%", SENTINEL))) { ast_config_destroy(regs); return NULL; } /* Loop over the sipregs that match IP address and attempt to find an * insecure=port match to it in sippeers. */ regscat = NULL; while ((regscat = ast_category_browse(regs, regscat)) && (regname = ast_variable_retrieve(regs, regscat, "name"))) { char *peerscat; const char *peername; peerscat = NULL; while ((peerscat = ast_category_browse(peers, peerscat)) && (peername = ast_variable_retrieve(peers, peerscat, "name"))) { if (!strcasecmp(regname, peername)) { /* Ensure that it really is insecure=port and * not something else. */ const char *insecure = ast_variable_retrieve(peers, peerscat, "insecure"); struct ast_flags flags = {0}; set_insecure_flags(&flags, insecure, -1); if (ast_test_flag(&flags, SIP_INSECURE_PORT)) { /* ENOMEM checks till the bitter end. */ if ((varregs = ast_variables_dup(ast_category_root(regs, regscat)))) { if (!(*var = ast_variables_dup(ast_category_root(peers, peerscat)))) { ast_variables_destroy(varregs); varregs = NULL; } } goto done; } } } } done: ast_config_destroy(regs); ast_config_destroy(peers); return varregs; } static const char *get_name_from_variable(const struct ast_variable *var) { /* Don't expect this to return non-NULL. Both NULL and empty * values can cause the option to get removed from the variable * list. This is called on ast_variables gotten from both * ast_load_realtime and ast_load_realtime_multientry. * - ast_load_realtime removes options with empty values * - ast_load_realtime_multientry does not! * For consistent behaviour, we check for the empty name and * return NULL instead. */ const struct ast_variable *tmp; for (tmp = var; tmp; tmp = tmp->next) { if (!strcasecmp(tmp->name, "name")) { if (!ast_strlen_zero(tmp->value)) { return tmp->value; } break; } } return NULL; } /* If varregs is NULL, we don't use sipregs. * Using empty if-bodies instead of goto's while avoiding unnecessary indents */ static int realtime_peer_by_name(const char *const *name, struct ast_sockaddr *addr, const char *ipaddr, struct ast_variable **var, struct ast_variable **varregs) { /* Peer by name and host=dynamic */ if ((*var = ast_load_realtime("sippeers", "name", *name, "host", "dynamic", SENTINEL))) { ; /* Peer by name and host=IP */ } else if (addr && !(*var = ast_load_realtime("sippeers", "name", *name, "host", ipaddr, SENTINEL))) { ; /* Peer by name and host=HOSTNAME */ } else if ((*var = ast_load_realtime("sippeers", "name", *name, SENTINEL))) { /*!\note * 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) { struct ast_variable *tmp; for (tmp = *var; tmp; tmp = tmp->next) { if (!strcasecmp(tmp->name, "host")) { struct ast_sockaddr *addrs = NULL; if (ast_sockaddr_resolve(&addrs, tmp->value, PARSE_PORT_FORBID, get_address_family_filter(AST_TRANSPORT_UDP)) <= 0 || ast_sockaddr_cmp(&addrs[0], addr)) { /* No match */ ast_variables_destroy(*var); *var = NULL; } ast_free(addrs); break; } } } } /* Did we find anything? */ if (*var) { if (varregs) { *varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL); } return 1; } return 0; } /* 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 *realtime_peer_get_sippeer_helper(const char **name, struct ast_variable **varregs) { struct ast_variable *var = NULL; const char *old_name = *name; *name = get_name_from_variable(*varregs); if (!*name || !(var = ast_load_realtime("sippeers", "name", *name, SENTINEL))) { if (!*name) { ast_log(LOG_WARNING, "Found sipreg but it has no name\n"); } ast_variables_destroy(*varregs); *varregs = NULL; *name = old_name; } return var; } /* 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 realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, const char *ipaddr, const char *callbackexten, struct ast_variable **var, struct ast_variable **varregs) { char portstring[6]; /* up to 5 digits plus null terminator */ ast_copy_string(portstring, ast_sockaddr_stringify_port(addr), sizeof(portstring)); /* We're not finding this peer by this name anymore. Reset it. */ *name = NULL; /* First check for fixed IP hosts with matching callbackextensions, if specified */ if (!ast_strlen_zero(callbackexten) && (*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, "callbackextension", callbackexten, SENTINEL))) { ; /* Check for fixed IP hosts */ } else if ((*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL))) { ; /* Check for registered hosts (in sipregs) */ } else if (varregs && (*varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL)) && (*var = realtime_peer_get_sippeer_helper(name, varregs))) { ; /* Check for registered hosts (in sippeers) */ } else if (!varregs && (*var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL))) { ; /* We couldn't match on ipaddress and port, so we need to check if port is insecure */ } else if ((*var = get_insecure_variable_from_sippeers("host", ipaddr))) { ; /* Same as above, but try the IP address field (in sipregs) * Observe that it fetches the name/var at the same time, without the * realtime_peer_get_sippeer_helper. Also note that it is quite inefficient. * Avoid sipregs if possible. */ } else if (varregs && (*varregs = get_insecure_variable_from_sipregs("ipaddr", ipaddr, var))) { ; /* Same as above, but try the IP address field (in sippeers) */ } else if (!varregs && (*var = get_insecure_variable_from_sippeers("ipaddr", ipaddr))) { ; } /* Nothing found? */ if (!*var) { return 0; } /* 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 = get_name_from_variable(*var))) { ast_log(LOG_WARNING, "Found peer for IP %s but it has no name\n", ipaddr); ast_variables_destroy(*var); *var = NULL; if (varregs && *varregs) { ast_variables_destroy(*varregs); *varregs = NULL; } return 0; } /* Make sure varregs is populated if var is. The inverse, * ensuring that var is set when varregs is, is taken * care of by realtime_peer_get_sippeer_helper(). */ if (varregs && !*varregs) { *varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL); } return 1; } static int register_realtime_peers_with_callbackextens(void) { struct ast_config *cfg; char *cat = NULL; if (!(ast_check_realtime("sippeers"))) { return 0; } /* This is hacky. We want name to be the cat, so it is the first property */ if (!(cfg = ast_load_realtime_multientry("sippeers", "name LIKE", "%", "callbackextension LIKE", "%", SENTINEL))) { return -1; } while ((cat = ast_category_browse(cfg, cat))) { struct sip_peer *peer; struct ast_variable *var = ast_category_root(cfg, cat); if (!(peer = build_peer(cat, var, NULL, TRUE, FALSE))) { continue; } ast_log(LOG_NOTICE, "Created realtime peer '%s' for registration\n", peer->name); peer->is_realtime = 1; sip_unref_peer(peer, "register_realtime_peers: Done registering releasing"); } ast_config_destroy(cfg); return 0; } /*! \brief realtime_peer: Get peer from realtime storage * Checks the "sippeers" realtime family from extconfig.conf * Checks the "sipregs" realtime family from extconfig.conf if it's configured. * This returns a pointer to a peer and because we use build_peer, we can rest * assured that the refcount is bumped. * * \note 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 newpeername. */ static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockaddr *addr, char *callbackexten, int devstate_only, int which_objects) { struct sip_peer *peer = NULL; struct ast_variable *var = NULL; struct ast_variable *varregs = NULL; char ipaddr[INET6_ADDRSTRLEN]; int realtimeregs = ast_check_realtime("sipregs"); if (addr) { ast_copy_string(ipaddr, ast_sockaddr_stringify_addr(addr), sizeof(ipaddr)); } else { ipaddr[0] = '\0'; } if (newpeername && realtime_peer_by_name(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) { ; } else if (addr && realtime_peer_by_addr(&newpeername, addr, ipaddr, callbackexten, &var, realtimeregs ? &varregs : NULL)) { ; } else { return NULL; } /* If we're looking for users, don't return peers (although this check * should probably be done in realtime_peer_by_* instead...) */ if (which_objects == FINDUSERS) { struct ast_variable *tmp; for (tmp = var; tmp; tmp = tmp->next) { if (!strcasecmp(tmp->name, "type") && (!strcasecmp(tmp->value, "peer"))) { goto cleanup; } } } /* Peer found in realtime, now build it in memory */ peer = build_peer(newpeername, var, varregs, TRUE, devstate_only); if (!peer) { goto cleanup; } /* Previous versions of Asterisk did not require the type field to be * set for real time peers. This statement preserves that behavior. */ if (peer->type == 0) { if (which_objects == FINDUSERS) { peer->type = SIP_TYPE_USER; } else if (which_objects == FINDPEERS) { peer->type = SIP_TYPE_PEER; } else { peer->type = SIP_TYPE_PEER | SIP_TYPE_USER; } } ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs); if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) { /* Cache peer */ ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS); if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) { AST_SCHED_REPLACE_UNREF(peer->expire, sched, sip_cfg.rtautoclear * 1000, expire_register, peer, sip_unref_peer(_data, "remove registration ref"), sip_unref_peer(peer, "remove registration ref"), sip_ref_peer(peer, "add registration ref")); } ao2_t_link(peers, peer, "link peer into peers table"); if (!ast_sockaddr_isnull(&peer->addr)) { ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); } } peer->is_realtime = 1; cleanup: ast_variables_destroy(var); ast_variables_destroy(varregs); return peer; } /* Function to assist finding peers by name only */ static int find_by_name(void *obj, void *arg, void *data, int flags) { struct sip_peer *search = obj, *match = arg; int *which_objects = data; /* Usernames in SIP uri's are case sensitive. Domains are not */ if (strcmp(search->name, match->name)) { return 0; } switch (*which_objects) { case FINDUSERS: if (!(search->type & SIP_TYPE_USER)) { return 0; } break; case FINDPEERS: if (!(search->type & SIP_TYPE_PEER)) { return 0; } break; case FINDALLDEVICES: break; } return CMP_MATCH | CMP_STOP; } static struct sip_peer *sip_find_peer_full(const char *peer, struct ast_sockaddr *addr, char *callbackexten, int realtime, int which_objects, int devstate_only, int transport) { struct sip_peer *p = NULL; struct sip_peer tmp_peer; if (peer) { ast_copy_string(tmp_peer.name, peer, sizeof(tmp_peer.name)); p = ao2_t_callback_data(peers, OBJ_POINTER, find_by_name, &tmp_peer, &which_objects, "ao2_find in peers table"); } else if (addr) { /* search by addr? */ ast_sockaddr_copy(&tmp_peer.addr, addr); tmp_peer.flags[0].flags = 0; tmp_peer.transports = transport; p = ao2_t_callback_data(peers_by_ip, OBJ_POINTER, peer_ipcmp_cb_full, &tmp_peer, callbackexten, "ao2_find in peers_by_ip table"); if (!p) { ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT); p = ao2_t_callback_data(peers_by_ip, OBJ_POINTER, peer_ipcmp_cb_full, &tmp_peer, callbackexten, "ao2_find in peers_by_ip table 2"); if (p) { return p; } } } if (!p && (realtime || devstate_only)) { /* realtime_peer will return a peer with matching callbackexten if possible, otherwise one matching * without the callbackexten */ p = realtime_peer(peer, addr, callbackexten, devstate_only, which_objects); if (p) { switch (which_objects) { case FINDUSERS: if (!(p->type & SIP_TYPE_USER)) { sip_unref_peer(p, "Wrong type of realtime SIP endpoint"); return NULL; } break; case FINDPEERS: if (!(p->type & SIP_TYPE_PEER)) { sip_unref_peer(p, "Wrong type of realtime SIP endpoint"); return NULL; } break; case FINDALLDEVICES: break; } } } return p; } /*! * \brief Locate device by name or ip address * \param peer, addr, realtime, devstate_only, transport * \param which_objects Define which objects should be matched when doing a lookup * by name. Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES. * Note that this option is not used at all when doing a lookup by IP. * * This is used on find matching device on name or ip/port. * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs. * * \note Avoid using this function in new functions if there is a way to avoid it, * since it might cause a database lookup. */ struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int realtime, int which_objects, int devstate_only, int transport) { return sip_find_peer_full(peer, addr, NULL, realtime, which_objects, devstate_only, transport); } static struct sip_peer *sip_find_peer_by_ip_and_exten(struct ast_sockaddr *addr, char *callbackexten, int transport) { return sip_find_peer_full(NULL, addr, callbackexten, TRUE, FINDPEERS, FALSE, transport); } /*! \brief Set nat mode on the various data sockets */ static void do_setnat(struct sip_pvt *p) { const char *mode; int natflags; natflags = ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP); mode = natflags ? "On" : "Off"; if (p->rtp) { ast_debug(1, "Setting NAT on RTP to %s\n", mode); ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_NAT, natflags); } if (p->vrtp) { ast_debug(1, "Setting NAT on VRTP to %s\n", mode); ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_NAT, natflags); } if (p->udptl) { ast_debug(1, "Setting NAT on UDPTL to %s\n", mode); ast_udptl_setnat(p->udptl, natflags); } if (p->trtp) { ast_debug(1, "Setting NAT on TRTP to %s\n", mode); ast_rtp_instance_set_prop(p->trtp, AST_RTP_PROPERTY_NAT, natflags); } } /*! \brief Change the T38 state on a SIP dialog */ static void change_t38_state(struct sip_pvt *p, int state) { int old = p->t38.state; struct ast_channel *chan = p->owner; struct ast_control_t38_parameters parameters = { .request_response = 0 }; /* Don't bother changing if we are already in the state wanted */ if (old == state) return; p->t38.state = state; ast_debug(2, "T38 state changed to %u on channel %s\n", p->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 T38_PEER_REINVITE: parameters = p->t38.their_parms; parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl); parameters.request_response = AST_T38_REQUEST_NEGOTIATE; ast_udptl_set_tag(p->udptl, "%s", ast_channel_name(chan)); break; case T38_ENABLED: parameters = p->t38.their_parms; parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl); parameters.request_response = AST_T38_NEGOTIATED; ast_udptl_set_tag(p->udptl, "%s", ast_channel_name(chan)); break; case T38_REJECTED: case T38_DISABLED: if (old == T38_ENABLED) { parameters.request_response = AST_T38_TERMINATED; } else if (old == T38_LOCAL_REINVITE) { parameters.request_response = AST_T38_REFUSED; } break; case 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 (parameters.request_response) ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); } /*! \brief Set the global T38 capabilities on a SIP dialog structure */ static void set_t38_capabilities(struct sip_pvt *p) { if (p->udptl) { if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY) { ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_FEC) { ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC); } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL) { ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE); } } } static void copy_socket_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; } else if (to_sock->ws_session) { ast_websocket_unref(to_sock->ws_session); to_sock->ws_session = NULL; } if (from_sock->tcptls_session) { ao2_ref(from_sock->tcptls_session, +1); } else if (from_sock->ws_session) { ast_websocket_ref(from_sock->ws_session); } *to_sock = *from_sock; } /*! \brief Initialize DTLS-SRTP support on an RTP instance */ static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp) { struct ast_rtp_engine_dtls *dtls; if (!dialog->dtls_cfg.enabled) { return 0; } if (!ast_rtp_engine_srtp_is_registered()) { ast_log(LOG_ERROR, "No SRTP module loaded, can't setup SRTP session.\n"); return -1; } if (!(dtls = ast_rtp_instance_get_dtls(rtp))) { ast_log(LOG_ERROR, "No DTLS-SRTP support present on engine for RTP instance '%p', was it compiled with support for it?\n", rtp); return -1; } if (dtls->set_configuration(rtp, &dialog->dtls_cfg)) { ast_log(LOG_ERROR, "Attempted to set an invalid DTLS-SRTP configuration on RTP instance '%p'\n", rtp); return -1; } if (!(*srtp = ast_sdp_srtp_alloc())) { ast_log(LOG_ERROR, "Failed to create required SRTP structure on RTP instance '%p'\n", rtp); return -1; } return 0; } /*! \brief Initialize RTP portion of a dialog * \return -1 on failure, 0 on success */ static int dialog_initialize_rtp(struct sip_pvt *dialog) { struct ast_sockaddr bindaddr_tmp; struct ast_rtp_engine_ice *ice; if (!sip_methods[dialog->method].need_rtp) { return 0; } ast_sockaddr_copy(&bindaddr_tmp, &bindaddr); if (!(dialog->rtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr_tmp, NULL))) { return -1; } if (!ast_test_flag(&dialog->flags[2], SIP_PAGE3_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(dialog->rtp))) { ice->stop(dialog->rtp); } if (dialog_initialize_dtls_srtp(dialog, dialog->rtp, &dialog->srtp)) { return -1; } if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT_ALWAYS) || (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) && (ast_format_cap_has_type(dialog->caps, AST_MEDIA_TYPE_VIDEO)))) { if (!(dialog->vrtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr_tmp, NULL))) { return -1; } if (!ast_test_flag(&dialog->flags[2], SIP_PAGE3_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(dialog->vrtp))) { ice->stop(dialog->vrtp); } if (dialog_initialize_dtls_srtp(dialog, dialog->vrtp, &dialog->vsrtp)) { return -1; } ast_rtp_instance_set_timeout(dialog->vrtp, dialog->rtptimeout); ast_rtp_instance_set_hold_timeout(dialog->vrtp, dialog->rtpholdtimeout); ast_rtp_instance_set_keepalive(dialog->vrtp, dialog->rtpkeepalive); ast_rtp_instance_set_prop(dialog->vrtp, AST_RTP_PROPERTY_RTCP, 1); ast_rtp_instance_set_qos(dialog->vrtp, global_tos_video, global_cos_video, "SIP VIDEO"); } if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_TEXTSUPPORT)) { if (!(dialog->trtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr_tmp, NULL))) { return -1; } if (!ast_test_flag(&dialog->flags[2], SIP_PAGE3_ICE_SUPPORT) && (ice = ast_rtp_instance_get_ice(dialog->trtp))) { ice->stop(dialog->trtp); } if (dialog_initialize_dtls_srtp(dialog, dialog->trtp, &dialog->tsrtp)) { return -1; } /* Do not timeout text as its not constant*/ ast_rtp_instance_set_keepalive(dialog->trtp, dialog->rtpkeepalive); ast_rtp_instance_set_prop(dialog->trtp, AST_RTP_PROPERTY_RTCP, 1); } ast_rtp_instance_set_timeout(dialog->rtp, dialog->rtptimeout); ast_rtp_instance_set_hold_timeout(dialog->rtp, dialog->rtpholdtimeout); ast_rtp_instance_set_keepalive(dialog->rtp, dialog->rtpkeepalive); ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_RTCP, 1); ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE)); ast_rtp_instance_set_qos(dialog->rtp, global_tos_audio, global_cos_audio, "SIP RTP"); do_setnat(dialog); return 0; } static int __set_address_from_contact(const char *fullcontact, struct ast_sockaddr *addr, int tcp); /*! \brief 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. * * \return -1 on error, 0 on success. * */ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer) { struct sip_auth_container *credentials; /* this checks that the dialog is contacting the peer on a valid * transport type based on the peers transport configuration, * otherwise, this function bails out */ if (dialog->socket.type && check_request_transport(peer, dialog)) return -1; copy_socket_data(&dialog->socket, &peer->socket); if (!(ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) && (!peer->maxms || ((peer->lastms >= 0) && (peer->lastms <= peer->maxms)))) { dialog->sa = ast_sockaddr_isnull(&peer->addr) ? peer->defaddr : peer->addr; dialog->recv = dialog->sa; } else return -1; /* XXX TODO: get flags directly from peer only as they are needed using dialog->relatedpeer */ ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); /* Take the peer's caps */ if (peer->caps) { ast_format_cap_remove_by_type(dialog->caps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(dialog->caps, peer->caps, AST_MEDIA_TYPE_UNKNOWN); } dialog->amaflags = peer->amaflags; ast_string_field_set(dialog, engine, peer->engine); ast_rtp_dtls_cfg_copy(&peer->dtls_cfg, &dialog->dtls_cfg); dialog->rtptimeout = peer->rtptimeout; dialog->rtpholdtimeout = peer->rtpholdtimeout; dialog->rtpkeepalive = peer->rtpkeepalive; sip_route_copy(&dialog->route, &peer->path); if (!sip_route_empty(&dialog->route)) { /* Parse SIP URI of first route-set hop and use it as target address */ __set_address_from_contact(sip_route_first_uri(&dialog->route), &dialog->sa, dialog->socket.type == AST_TRANSPORT_TLS ? 1 : 0); } if (dialog_initialize_rtp(dialog)) { return -1; } if (dialog->rtp) { /* Audio */ ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE)); /* Set Frame packetization */ dialog->autoframing = peer->autoframing; ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(dialog->rtp), ast_format_cap_get_framing(dialog->caps)); } /* XXX TODO: get fields directly from peer only as they are needed using dialog->relatedpeer */ ast_string_field_set(dialog, peername, peer->name); ast_string_field_set(dialog, authname, peer->username); ast_string_field_set(dialog, username, peer->username); ast_string_field_set(dialog, peersecret, peer->secret); ast_string_field_set(dialog, peermd5secret, peer->md5secret); ast_string_field_set(dialog, mohsuggest, peer->mohsuggest); ast_string_field_set(dialog, mohinterpret, peer->mohinterpret); ast_string_field_set(dialog, tohost, peer->tohost); ast_string_field_set(dialog, fullcontact, peer->fullcontact); ast_string_field_set(dialog, accountcode, peer->accountcode); ast_string_field_set(dialog, context, peer->context); ast_string_field_set(dialog, cid_num, peer->cid_num); ast_string_field_set(dialog, cid_name, peer->cid_name); ast_string_field_set(dialog, cid_tag, peer->cid_tag); ast_string_field_set(dialog, mwi_from, peer->mwi_from); if (!ast_strlen_zero(peer->parkinglot)) { ast_string_field_set(dialog, parkinglot, peer->parkinglot); } ast_string_field_set(dialog, engine, peer->engine); ref_proxy(dialog, obproxy_get(dialog, peer)); dialog->callgroup = peer->callgroup; dialog->pickupgroup = peer->pickupgroup; ast_unref_namedgroups(dialog->named_callgroups); dialog->named_callgroups = ast_ref_namedgroups(peer->named_callgroups); ast_unref_namedgroups(dialog->named_pickupgroups); dialog->named_pickupgroups = ast_ref_namedgroups(peer->named_pickupgroups); ast_copy_string(dialog->zone, peer->zone, sizeof(dialog->zone)); dialog->allowtransfer = peer->allowtransfer; dialog->jointnoncodeccapability = dialog->noncodeccapability; /* Update dialog authorization credentials */ ao2_lock(peer); credentials = peer->auth; if (credentials) { ao2_t_ref(credentials, +1, "Ref peer auth for dialog"); } ao2_unlock(peer); ao2_lock(dialog); if (dialog->peerauth) { ao2_t_ref(dialog->peerauth, -1, "Unref old dialog peer auth"); } dialog->peerauth = credentials; ao2_unlock(dialog); dialog->maxcallbitrate = peer->maxcallbitrate; dialog->disallowed_methods = peer->disallowed_methods; ast_cc_copy_config_params(dialog->cc_params, peer->cc_params); if (ast_strlen_zero(dialog->tohost)) ast_string_field_set(dialog, tohost, ast_sockaddr_stringify_host_remote(&dialog->sa)); if (!ast_strlen_zero(peer->fromdomain)) { ast_string_field_set(dialog, fromdomain, peer->fromdomain); if (!dialog->initreq.headers) { char *new_callid; char *tmpcall = ast_strdupa(dialog->callid); /* this sure looks to me like we are going to change the callid on this dialog!! */ new_callid = strchr(tmpcall, '@'); if (new_callid) { int callid_size; *new_callid = '\0'; /* Change the dialog callid. */ callid_size = strlen(tmpcall) + strlen(peer->fromdomain) + 2; new_callid = ast_alloca(callid_size); snprintf(new_callid, callid_size, "%s@%s", tmpcall, peer->fromdomain); change_callid_pvt(dialog, new_callid); } } } if (!ast_strlen_zero(peer->fromuser)) { ast_string_field_set(dialog, fromuser, peer->fromuser); } if (!ast_strlen_zero(peer->language)) { ast_string_field_set(dialog, language, peer->language); } /* Set timer T1 to RTT for this peer (if known by qualify=) */ /* Minimum is settable or default to 100 ms */ /* If there is a maxms and lastms from a qualify use that over a manual T1 value. Otherwise, use the peer's T1 value. */ if (peer->maxms && peer->lastms) { dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms; } else { dialog->timer_t1 = peer->timer_t1; } /* Set timer B to control transaction timeouts, the peer setting is the default and overrides the known timer */ if (peer->timer_b) { dialog->timer_b = peer->timer_b; } else { dialog->timer_b = 64 * dialog->timer_t1; } if ((ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) || (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) { dialog->noncodeccapability |= AST_RTP_DTMF; } else { dialog->noncodeccapability &= ~AST_RTP_DTMF; } dialog->directmediaacl = ast_duplicate_acl_list(peer->directmediaacl); if (peer->call_limit) { ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT); } if (!dialog->portinuri) { dialog->portinuri = peer->portinuri; } dialog->chanvars = copy_vars(peer->chanvars); if (peer->fromdomainport) { dialog->fromdomainport = peer->fromdomainport; } dialog->callingpres = peer->callingpres; return 0; } /*! \brief The default sip port for the given transport */ static inline int default_sip_port(enum ast_transport type) { return type == AST_TRANSPORT_TLS ? STANDARD_TLS_PORT : STANDARD_SIP_PORT; } /*! \brief create address structure from device name * Or, if peer not found, find it in the global DNS * returns TRUE (-1) on failure, FALSE on success */ static int create_addr(struct sip_pvt *dialog, const char *opeer, struct ast_sockaddr *addr, int newdialog) { struct sip_peer *peer; char *peername, *peername2, *hostn; char host[MAXHOSTNAMELEN]; char service[MAXHOSTNAMELEN]; int srv_ret = 0; int tportno; AST_DECLARE_APP_ARGS(hostport, AST_APP_ARG(host); AST_APP_ARG(port); ); peername = ast_strdupa(opeer); peername2 = ast_strdupa(opeer); AST_NONSTANDARD_RAW_ARGS(hostport, peername2, ':'); if (hostport.port) dialog->portinuri = 1; dialog->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */ dialog->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */ peer = sip_find_peer(peername, NULL, TRUE, FINDPEERS, FALSE, 0); if (peer) { int res; if (newdialog) { set_socket_transport(&dialog->socket, 0); } res = create_addr_from_peer(dialog, peer); dialog->relatedpeer = sip_ref_peer(peer, "create_addr: setting dialog's relatedpeer pointer"); sip_unref_peer(peer, "create_addr: unref peer from sip_find_peer hashtab lookup"); return res; } else if (ast_check_digits(peername)) { /* 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", peername); return -1; } else { dialog->rtptimeout = global_rtptimeout; dialog->rtpholdtimeout = global_rtpholdtimeout; dialog->rtpkeepalive = global_rtpkeepalive; if (dialog_initialize_rtp(dialog)) { return -1; } } ast_string_field_set(dialog, tohost, hostport.host); dialog->allowed_methods &= ~sip_cfg.disallowed_methods; /* Get the outbound proxy information */ ref_proxy(dialog, obproxy_get(dialog, NULL)); if (addr) { /* This address should be updated using dnsmgr */ ast_sockaddr_copy(&dialog->sa, addr); } else { /* Let's see if we can find the host in DNS. First try DNS SRV records, then hostname lookup */ /*! \todo Fix this function. When we ask for SRV, we should check all transports In the future, we should first check NAPTR to find out transport preference */ hostn = peername; /* 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_cfg.srvlookup) { snprintf(service, sizeof(service), "_%s._%s.%s", get_srv_service(dialog->socket.type), get_srv_protocol(dialog->socket.type), peername); if ((srv_ret = ast_get_srv(NULL, host, sizeof(host), &tportno, service)) > 0) { hostn = host; } } if (ast_sockaddr_resolve_first_transport(&dialog->sa, hostn, 0, dialog->socket.type ? dialog->socket.type : AST_TRANSPORT_UDP)) { ast_log(LOG_WARNING, "No such host: %s\n", peername); return -1; } if (srv_ret > 0) { ast_sockaddr_set_port(&dialog->sa, tportno); } } if (!dialog->socket.type) set_socket_transport(&dialog->socket, AST_TRANSPORT_UDP); if (!dialog->socket.port) { dialog->socket.port = htons(ast_sockaddr_port(&bindaddr)); } if (!ast_sockaddr_port(&dialog->sa)) { ast_sockaddr_set_port(&dialog->sa, default_sip_port(dialog->socket.type)); } ast_sockaddr_copy(&dialog->recv, &dialog->sa); return 0; } /*! \brief Scheduled congestion on a call. * Only called by the scheduler, must return the reference when done. */ static int auto_congest(const void *arg) { struct sip_pvt *p = (struct sip_pvt *)arg; sip_pvt_lock(p); p->initid = -1; /* event gone, will not be rescheduled */ if (p->owner) { /* XXX fails on possible deadlock */ if (!ast_channel_trylock(p->owner)) { append_history(p, "Cong", "Auto-congesting (timer)"); ast_queue_control(p->owner, AST_CONTROL_CONGESTION); ast_channel_unlock(p->owner); } /* Give the channel a chance to act before we proceed with destruction */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } sip_pvt_unlock(p); dialog_unref(p, "unreffing arg passed into auto_congest callback (p->initid)"); return 0; } /*! \brief Initiate SIP call from PBX * used from the dial() application */ static int sip_call(struct ast_channel *ast, const char *dest, int timeout) { int res; struct sip_pvt *p = ast_channel_tech_pvt(ast); /* chan is locked, so the reference cannot go away */ struct varshead *headp; struct ast_var_t *current; const char *referer = NULL; /* SIP referrer */ int cc_core_id; char uri[SIPBUFSIZE] = ""; if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "sip_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } if (ast_cc_is_recall(ast, &cc_core_id, "SIP")) { char device_name[AST_CHANNEL_NAME]; struct ast_cc_monitor *recall_monitor; struct sip_monitor_instance *monitor_instance; ast_channel_get_device_name(ast, device_name, sizeof(device_name)); if ((recall_monitor = ast_cc_get_monitor_by_recall_core_id(cc_core_id, device_name))) { monitor_instance = recall_monitor->private_data; ast_copy_string(uri, monitor_instance->notify_uri, sizeof(uri)); ao2_t_ref(recall_monitor, -1, "Got the URI we need so unreffing monitor"); } } /* Check whether there is vxml_url, distinctive ring variables */ headp = ast_channel_varshead(ast); AST_LIST_TRAVERSE(headp, current, entries) { /* Check whether there is a VXML_URL variable */ if (!p->options->vxml_url && !strcmp(ast_var_name(current), "VXML_URL")) { p->options->vxml_url = ast_var_value(current); } else if (!p->options->uri_options && !strcmp(ast_var_name(current), "SIP_URI_OPTIONS")) { p->options->uri_options = ast_var_value(current); } else if (!p->options->addsipheaders && !strncmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) { /* Check whether there is a variable with a name starting with SIPADDHEADER */ p->options->addsipheaders = 1; } else if (!strcmp(ast_var_name(current), "SIPFROMDOMAIN")) { ast_string_field_set(p, fromdomain, ast_var_value(current)); } else if (!strcmp(ast_var_name(current), "SIPTRANSFER")) { /* This is a transferred call */ p->options->transfer = 1; } else if (!strcmp(ast_var_name(current), "SIPTRANSFER_REFERER")) { /* This is the referrer */ referer = ast_var_value(current); } else if (!strcmp(ast_var_name(current), "SIPTRANSFER_REPLACES")) { /* We're replacing a call. */ p->options->replaces = ast_var_value(current); } else if (!strcmp(ast_var_name(current), "SIP_MAX_FORWARDS")) { if (sscanf(ast_var_value(current), "%30d", &(p->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 (p->req_secure_signaling && p->socket.type != AST_TRANSPORT_TLS) { ast_log(LOG_WARNING, "Encrypted signaling is required\n"); ast_channel_hangupcause_set(ast, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); return -1; } if (ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP)) { if (ast_test_flag(&p->flags[0], SIP_REINVITE)) { ast_debug(1, "Direct media not possible when using SRTP, ignoring canreinvite setting\n"); ast_clear_flag(&p->flags[0], SIP_REINVITE); } if (p->rtp && !p->srtp && !(p->srtp = ast_sdp_srtp_alloc())) { ast_log(LOG_WARNING, "SRTP audio setup failed\n"); return -1; } if (p->vrtp && !p->vsrtp && !(p->vsrtp = ast_sdp_srtp_alloc())) { ast_log(LOG_WARNING, "SRTP video setup failed\n"); return -1; } if (p->trtp && !p->tsrtp && !(p->tsrtp = ast_sdp_srtp_alloc())) { ast_log(LOG_WARNING, "SRTP text setup failed\n"); return -1; } } res = 0; ast_set_flag(&p->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(&p->flags[1], SIP_PAGE2_FAX_DETECT_T38); if (p->options->transfer) { char buf[SIPBUFSIZE / 2]; if (referer) { if (sipdebug) ast_debug(3, "Call for %s transferred by %s\n", p->username, referer); snprintf(buf, sizeof(buf)-1, "-> %s (via %s)", p->cid_name, referer); } else snprintf(buf, sizeof(buf)-1, "-> %s", p->cid_name); ast_string_field_set(p, cid_name, buf); } ast_debug(1, "Outgoing Call for %s\n", p->username); res = update_call_counter(p, INC_CALL_RINGING); if (res == -1) { ast_channel_hangupcause_set(ast, AST_CAUSE_USER_BUSY); return res; } p->callingpres = ast_party_id_presentation(&ast_channel_caller(ast)->id); ast_rtp_instance_available_formats(p->rtp, p->caps, p->prefcaps, p->jointcaps); p->jointnoncodeccapability = p->noncodeccapability; /* If there are no audio formats left to offer, punt */ if (!(ast_format_cap_has_type(p->jointcaps, AST_MEDIA_TYPE_AUDIO))) { ast_log(LOG_WARNING, "No audio format found to offer. Cancelling call to %s\n", p->username); res = -1; } else { int xmitres; struct ast_party_connected_line connected; struct ast_set_party_connected_line update_connected; sip_pvt_lock(p); /* Supply initial connected line information if available. */ memset(&update_connected, 0, sizeof(update_connected)); ast_party_connected_line_init(&connected); if (!ast_strlen_zero(p->cid_num) || (p->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { update_connected.id.number = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) p->cid_num; connected.id.number.presentation = p->callingpres; } if (!ast_strlen_zero(p->cid_name) || (p->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { update_connected.id.name = 1; connected.id.name.valid = 1; connected.id.name.str = (char *) p->cid_name; connected.id.name.presentation = p->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 *) p->cid_tag; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; ast_channel_queue_connected_line_update(ast, &connected, &update_connected); } xmitres = transmit_invite(p, SIP_INVITE, 1, 2, uri); if (xmitres == XMIT_ERROR) { sip_pvt_unlock(p); return -1; } p->invitestate = INV_CALLING; /* Initialize auto-congest time */ AST_SCHED_REPLACE_UNREF(p->initid, sched, p->timer_b, auto_congest, p, dialog_unref(_data, "dialog ptr dec when SCHED_REPLACE del op succeeded"), dialog_unref(p, "dialog ptr dec when SCHED_REPLACE add failed"), dialog_ref(p, "dialog ptr inc when SCHED_REPLACE add succeeded") ); sip_pvt_unlock(p); } return res; } /*! \brief Destroy registry object Objects created with the register= statement in static configuration */ static void sip_registry_destroy(void *obj) { struct sip_registry *reg = obj; /* Really delete */ ast_debug(3, "Destroying registry entry for %s@%s\n", reg->username, reg->hostname); if (reg->call) { /* Clear registry before destroying to ensure we don't get reentered trying to grab the registry lock */ ao2_t_replace(reg->call->registry, NULL, "destroy reg->call->registry"); ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", reg->username, reg->hostname); dialog_unlink_all(reg->call); reg->call = dialog_unref(reg->call, "unref reg->call"); /* reg->call = sip_destroy(reg->call); */ } AST_SCHED_DEL(sched, reg->expire); AST_SCHED_DEL(sched, reg->timeout); ast_string_field_free_memory(reg); } /*! \brief Destroy MWI subscription object */ static void sip_subscribe_mwi_destroy(void *data) { struct sip_subscription_mwi *mwi = data; if (mwi->call) { mwi->call->mwi = NULL; mwi->call = dialog_unref(mwi->call, "sip_subscription_mwi destruction"); } AST_SCHED_DEL(sched, mwi->resub); ast_string_field_free_memory(mwi); } /*! \brief Destroy SDP media offer list */ static void offered_media_list_destroy(struct sip_pvt *p) { struct offered_media *offer; while ((offer = AST_LIST_REMOVE_HEAD(&p->offered_media, next))) { ast_free(offer->decline_m_line); ast_free(offer); } } /*! \brief Execute destruction of SIP dialog structure, release memory */ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) { struct sip_request *req; /* Destroy Session-Timers if allocated */ if (p->stimer) { p->stimer->quit_flag = 1; stop_session_timer(p); ast_free(p->stimer); p->stimer = NULL; } if (sip_debug_test_pvt(p)) ast_verbose("Really destroying SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text); if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) { update_call_counter(p, DEC_CALL_LIMIT); ast_debug(2, "This call did not properly clean up call limits. Call ID %s\n", p->callid); } /* Unlink us from the owner if we have one */ if (p->owner) { if (lockowner) ast_channel_lock(p->owner); ast_debug(1, "Detaching from %s\n", ast_channel_name(p->owner)); ast_channel_tech_pvt_set(p->owner, NULL); /* Make sure that the channel knows its backend is going away */ ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); if (lockowner) ast_channel_unlock(p->owner); /* Give the channel a chance to react before deallocation */ usleep(1); } /* Remove link from peer to subscription of MWI */ if (p->relatedpeer && p->relatedpeer->mwipvt == p) p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); if (p->relatedpeer && p->relatedpeer->call == p) p->relatedpeer->call = dialog_unref(p->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself"); if (p->relatedpeer) p->relatedpeer = sip_unref_peer(p->relatedpeer,"unsetting a dialog relatedpeer field in sip_destroy"); if (p->registry) { if (p->registry->call == p) p->registry->call = dialog_unref(p->registry->call, "nulling out the registry's call dialog field in unlink_all"); ao2_t_replace(p->registry, NULL, "delete p->registry"); } if (p->mwi) { p->mwi->call = NULL; p->mwi = NULL; } if (dumphistory) sip_dump_history(p); if (p->options) { if (p->options->outboundproxy) { ao2_ref(p->options->outboundproxy, -1); } ast_free(p->options); p->options = NULL; } if (p->outboundproxy) { ref_proxy(p, NULL); } if (p->notify) { ast_variables_destroy(p->notify->headers); ast_free(p->notify->content); ast_free(p->notify); p->notify = NULL; } if (p->rtp) { ast_rtp_instance_destroy(p->rtp); p->rtp = NULL; } if (p->vrtp) { ast_rtp_instance_destroy(p->vrtp); p->vrtp = NULL; } if (p->trtp) { ast_rtp_instance_destroy(p->trtp); p->trtp = NULL; } if (p->udptl) { ast_udptl_destroy(p->udptl); p->udptl = NULL; } sip_refer_destroy(p); sip_route_clear(&p->route); deinit_req(&p->initreq); /* Clear history */ if (p->history) { struct sip_history *hist; while ( (hist = AST_LIST_REMOVE_HEAD(p->history, list)) ) { ast_free(hist); p->history_entries--; } ast_free(p->history); p->history = NULL; } while ((req = AST_LIST_REMOVE_HEAD(&p->request_queue, next))) { ast_free(req); } offered_media_list_destroy(p); if (p->chanvars) { ast_variables_destroy(p->chanvars); p->chanvars = NULL; } destroy_msg_headers(p); if (p->srtp) { ast_sdp_srtp_destroy(p->srtp); p->srtp = NULL; } if (p->vsrtp) { ast_sdp_srtp_destroy(p->vsrtp); p->vsrtp = NULL; } if (p->tsrtp) { ast_sdp_srtp_destroy(p->tsrtp); p->tsrtp = NULL; } if (p->directmediaacl) { p->directmediaacl = ast_free_acl_list(p->directmediaacl); } ast_string_field_free_memory(p); ast_cc_config_params_destroy(p->cc_params); p->cc_params = NULL; if (p->epa_entry) { ao2_ref(p->epa_entry, -1); p->epa_entry = NULL; } if (p->socket.tcptls_session) { ao2_ref(p->socket.tcptls_session, -1); p->socket.tcptls_session = NULL; } else if (p->socket.ws_session) { ast_websocket_unref(p->socket.ws_session); p->socket.ws_session = NULL; } if (p->peerauth) { ao2_t_ref(p->peerauth, -1, "Removing active peer authentication"); p->peerauth = NULL; } p->named_callgroups = ast_unref_namedgroups(p->named_callgroups); p->named_pickupgroups = ast_unref_namedgroups(p->named_pickupgroups); ao2_cleanup(p->caps); ao2_cleanup(p->jointcaps); ao2_cleanup(p->peercaps); ao2_cleanup(p->redircaps); ao2_cleanup(p->prefcaps); ast_rtp_dtls_cfg_free(&p->dtls_cfg); if (p->last_device_state_info) { ao2_ref(p->last_device_state_info, -1); p->last_device_state_info = NULL; } /* Lastly, kill the callid associated with the pvt */ if (p->logger_callid) { ast_callid_unref(p->logger_callid); } } /*! \brief update_call_counter: Handle call_limit for SIP devices * Setting a call-limit will cause calls above the limit not to be accepted. * * Remember that for a type=friend, there's one limit for the user and * another for the peer, not a combined call limit. * This will cause unexpected behaviour in subscriptions, since a "friend" * is *two* devices in Asterisk, not one. * * Thought: For realtime, we should probably update storage with inuse counter... * * \return 0 if call is ok (no call limit, below threshold) * -1 on rejection of call * */ static int update_call_counter(struct sip_pvt *fup, int event) { char name[256]; int *inuse = NULL, *call_limit = NULL, *ringing = NULL; int outgoing = fup->outgoing_call; struct sip_peer *p = NULL; ast_debug(3, "Updating call counter for %s call\n", outgoing ? "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(&fup->flags[0], SIP_CALL_LIMIT) && !ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD)) return 0; ast_copy_string(name, fup->username, sizeof(name)); /* Check the list of devices */ if (fup->relatedpeer) { p = sip_ref_peer(fup->relatedpeer, "ref related peer for update_call_counter"); inuse = &p->inuse; call_limit = &p->call_limit; ringing = &p->ringing; ast_copy_string(name, fup->peername, sizeof(name)); } if (!p) { ast_debug(2, "%s is not a local device, no call limit\n", name); return 0; } switch(event) { /* incoming and outgoing affects the inuse counter */ case DEC_CALL_LIMIT: /* Decrement inuse count if applicable */ if (inuse) { sip_pvt_lock(fup); ao2_lock(p); if (*inuse > 0) { if (ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) { (*inuse)--; ast_clear_flag(&fup->flags[0], SIP_INC_COUNT); } } else { *inuse = 0; } ao2_unlock(p); sip_pvt_unlock(fup); } /* Decrement ringing count if applicable */ if (ringing) { sip_pvt_lock(fup); ao2_lock(p); if (*ringing > 0) { if (ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) { (*ringing)--; ast_clear_flag(&fup->flags[0], SIP_INC_RINGING); } } else { *ringing = 0; } ao2_unlock(p); sip_pvt_unlock(fup); } /* Decrement onhold count if applicable */ sip_pvt_lock(fup); ao2_lock(p); if (ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD) && sip_cfg.notifyhold) { ast_clear_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD); ao2_unlock(p); sip_pvt_unlock(fup); sip_peer_hold(fup, FALSE); } else { ao2_unlock(p); sip_pvt_unlock(fup); } if (sipdebug) ast_debug(2, "Call %s %s '%s' removed from call limit %d\n", outgoing ? "to" : "from", "peer", name, *call_limit); break; case INC_CALL_RINGING: case INC_CALL_LIMIT: /* If call limit is active and we have reached the limit, reject the call */ if (*call_limit > 0 ) { if (*inuse >= *call_limit) { ast_log(LOG_NOTICE, "Call %s %s '%s' rejected due to usage limit of %d\n", outgoing ? "to" : "from", "peer", name, *call_limit); sip_unref_peer(p, "update_call_counter: unref peer p, call limit exceeded"); return -1; } } if (ringing && (event == INC_CALL_RINGING)) { sip_pvt_lock(fup); ao2_lock(p); if (!ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) { (*ringing)++; ast_set_flag(&fup->flags[0], SIP_INC_RINGING); } ao2_unlock(p); sip_pvt_unlock(fup); } if (inuse) { sip_pvt_lock(fup); ao2_lock(p); if (!ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) { (*inuse)++; ast_set_flag(&fup->flags[0], SIP_INC_COUNT); } ao2_unlock(p); sip_pvt_unlock(fup); } if (sipdebug) { ast_debug(2, "Call %s %s '%s' is %d out of %d\n", outgoing ? "to" : "from", "peer", name, *inuse, *call_limit); } break; case DEC_CALL_RINGING: if (ringing) { sip_pvt_lock(fup); ao2_lock(p); if (ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) { if (*ringing > 0) { (*ringing)--; } ast_clear_flag(&fup->flags[0], SIP_INC_RINGING); } ao2_unlock(p); sip_pvt_unlock(fup); } break; default: ast_log(LOG_ERROR, "update_call_counter(%s, %d) called with no event!\n", name, event); } if (p) { ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", p->name); sip_unref_peer(p, "update_call_counter: sip_unref_peer from call counter"); } return 0; } static void sip_destroy_fn(void *p) { sip_destroy(p); } /*! \brief Destroy SIP call structure. * Make it return NULL so the caller can do things like * foo = sip_destroy(foo); * and reduce the chance of bugs due to dangling pointers. */ struct sip_pvt *sip_destroy(struct sip_pvt *p) { ast_debug(3, "Destroying SIP dialog %s\n", p->callid); __sip_destroy(p, TRUE, TRUE); return NULL; } /*! \brief Convert SIP hangup causes to Asterisk hangup causes */ int hangup_sip2cause(int cause) { /* Possible values taken from causes.h */ 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; } /*! \brief Convert Asterisk hangup causes to SIP codes \verbatim Possible values from causes.h AST_CAUSE_NOTDEFINED AST_CAUSE_NORMAL AST_CAUSE_BUSY AST_CAUSE_FAILURE AST_CAUSE_CONGESTION AST_CAUSE_UNALLOCATED In addition to these, a lot of PRI codes is defined in causes.h ...should we take care of them too ? Quote RFC 3398 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 \endverbatim */ const char *hangup_cause2sip(int cause) { switch (cause) { case AST_CAUSE_UNALLOCATED: /* 1 */ case AST_CAUSE_NO_ROUTE_DESTINATION: /* 3 IAX2: Can't find extension in context */ case AST_CAUSE_NO_ROUTE_TRANSIT_NET: /* 2 */ return "404 Not Found"; case AST_CAUSE_CONGESTION: /* 34 */ case AST_CAUSE_SWITCH_CONGESTION: /* 42 */ return "503 Service Unavailable"; case AST_CAUSE_NO_USER_RESPONSE: /* 18 */ return "408 Request Timeout"; case AST_CAUSE_NO_ANSWER: /* 19 */ case AST_CAUSE_UNREGISTERED: /* 20 */ return "480 Temporarily unavailable"; case AST_CAUSE_CALL_REJECTED: /* 21 */ return "403 Forbidden"; case AST_CAUSE_NUMBER_CHANGED: /* 22 */ return "410 Gone"; case AST_CAUSE_NORMAL_UNSPECIFIED: /* 31 */ 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: /* 29 */ return "501 Not Implemented"; case AST_CAUSE_CHAN_NOT_IMPLEMENTED: return "503 Service Unavailable"; /* Used in chan_iax2 */ 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; } static int reinvite_timeout(const void *data) { struct sip_pvt *dialog = (struct sip_pvt *) data; struct ast_channel *owner = sip_pvt_lock_full(dialog); dialog->reinviteid = -1; check_pendings(dialog); if (owner) { ast_channel_unlock(owner); ast_channel_unref(owner); } ao2_unlock(dialog); dialog_unref(dialog, "unref for reinvite timeout"); return 0; } /*! \brief sip_hangup: Hangup SIP call * Part of PBX interface, called from ast_hangup */ static int sip_hangup(struct ast_channel *ast) { struct sip_pvt *p = ast_channel_tech_pvt(ast); int needcancel = FALSE; int needdestroy = 0; struct ast_channel *oldowner = ast; if (!p) { ast_debug(1, "Asked to hangup channel that was not connected\n"); return 0; } if (ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) { ast_debug(1, "This call was answered elsewhere\n"); append_history(p, "Cancel", "Call answered elsewhere"); p->answered_elsewhere = TRUE; } /* Store hangupcause locally in PVT so we still have it before disconnect */ if (p->owner) p->hangupcause = ast_channel_hangupcause(p->owner); if (ast_test_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) { if (sipdebug) ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username); update_call_counter(p, DEC_CALL_LIMIT); } ast_debug(4, "SIP Transfer: Not hanging up right now... Rescheduling hangup for %s.\n", p->callid); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Really hang up next time */ if (p->owner) { sip_pvt_lock(p); oldowner = p->owner; sip_set_owner(p, NULL); /* Owner will be gone after we return, so take it away */ sip_pvt_unlock(p); ast_channel_tech_pvt_set(oldowner, dialog_unref(ast_channel_tech_pvt(oldowner), "unref oldowner->tech_pvt")); } ast_module_unref(ast_module_info->self); return 0; } ast_debug(1, "Hangup call %s, SIP callid %s\n", ast_channel_name(ast), p->callid); sip_pvt_lock(p); if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) { if (sipdebug) ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username); update_call_counter(p, DEC_CALL_LIMIT); } /* Determine how to disconnect */ if (p->owner != ast) { ast_log(LOG_WARNING, "Huh? We aren't the owner? Can't hangup call.\n"); sip_pvt_unlock(p); 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 (p->invitestate < INV_COMPLETED && ast_channel_state(p->owner) != AST_STATE_UP) { needcancel = TRUE; ast_debug(4, "Hanging up channel in state %s (not UP)\n", ast_state2str(ast_channel_state(ast))); } stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */ append_history(p, needcancel ? "Cancel" : "Hangup", "Cause %s", ast_cause2str(p->hangupcause)); /* Disconnect */ disable_dsp_detect(p); sip_set_owner(p, NULL); ast_channel_tech_pvt_set(ast, NULL); ast_module_unref(ast_module_info->self); /* 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 (p->alreadygone) needdestroy = 1; /* Set destroy flag at end of this function */ else if (p->invitestate != INV_CALLING) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* Start the process if it's not already started */ if (!p->alreadygone && p->initreq.data && ast_str_strlen(p->initreq.data)) { if (needcancel) { /* Outgoing call, not up */ if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) { /* if we can't send right now, mark it pending */ if (p->invitestate == INV_CALLING) { /* We can't send anything in CALLING state */ ast_set_flag(&p->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_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); append_history(p, "DELAY", "Not sending cancel, waiting for timeout"); } else { struct sip_pkt *cur; for (cur = p->packets; cur; cur = cur->next) { __sip_semi_ack(p, cur->seqno, cur->is_resp, cur->method ? cur->method : find_sip_method(ast_str_buffer(cur->data))); } p->invitestate = INV_CANCELLED; /* Send a new request: CANCEL */ transmit_request(p, SIP_CANCEL, p->lastinvite, XMIT_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 = 0; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } } else { /* Incoming call, not up */ const char *res; AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr")); if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause))) transmit_response_reliable(p, res, &p->initreq); else transmit_response_reliable(p, "603 Declined", &p->initreq); p->invitestate = INV_TERMINATED; } } else { /* Call is in UP state, send BYE */ if (p->stimer->st_active == TRUE) { stop_session_timer(p); } if (!p->pendinginvite) { char *quality; char quality_buf[AST_MAX_USER_FIELD]; if (p->rtp) { struct ast_rtp_instance *p_rtp; p_rtp = p->rtp; ao2_ref(p_rtp, +1); ast_channel_unlock(oldowner); sip_pvt_unlock(p); ast_rtp_instance_set_stats_vars(oldowner, p_rtp); ao2_ref(p_rtp, -1); ast_channel_lock(oldowner); sip_pvt_lock(p); } /* * The channel variables are set below just to get the AMI * VarSet event because the channel is being hungup. */ if (p->rtp || p->vrtp || p->trtp) { ast_channel_stage_snapshot(oldowner); } if (p->rtp && (quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { if (p->do_history) { append_history(p, "RTCPaudio", "Quality:%s", quality); } pbx_builtin_setvar_helper(oldowner, "RTPAUDIOQOS", quality); } if (p->vrtp && (quality = ast_rtp_instance_get_quality(p->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { if (p->do_history) { append_history(p, "RTCPvideo", "Quality:%s", quality); } pbx_builtin_setvar_helper(oldowner, "RTPVIDEOQOS", quality); } if (p->trtp && (quality = ast_rtp_instance_get_quality(p->trtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { if (p->do_history) { append_history(p, "RTCPtext", "Quality:%s", quality); } pbx_builtin_setvar_helper(oldowner, "RTPTEXTQOS", quality); } if (p->rtp || p->vrtp || p->trtp) { ast_channel_stage_snapshot_done(oldowner); } /* Send a hangup */ if (ast_channel_state(oldowner) == AST_STATE_UP) { transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1); } } 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(&p->flags[0], SIP_PENDINGBYE); ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE); AST_SCHED_DEL_UNREF(sched, p->waitid, dialog_unref(p, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr")); if (sip_cancel_destroy(p)) { ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } /* 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 (p->ongoing_reinvite && p->reinviteid < 0) { p->reinviteid = ast_sched_add(sched, 32 * p->timer_t1, reinvite_timeout, dialog_ref(p, "ref for reinvite_timeout")); } } } } if (needdestroy) { pvt_set_needdestroy(p, "hangup"); } sip_pvt_unlock(p); dialog_unref(p, "unref ast->tech_pvt"); return 0; } /*! \brief Try setting the codecs suggested by the SIP_CODEC channel variable */ static void try_suggested_sip_codec(struct sip_pvt *p) { const char *codec_list; char *codec_list_copy; struct ast_format_cap *original_jointcaps; char *codec; int first_codec = 1; char *strtok_ptr; if (p->outgoing_call) { codec_list = pbx_builtin_getvar_helper(p->owner, "SIP_CODEC_OUTBOUND"); } else if (!(codec_list = pbx_builtin_getvar_helper(p->owner, "SIP_CODEC_INBOUND"))) { codec_list = pbx_builtin_getvar_helper(p->owner, "SIP_CODEC"); } if (ast_strlen_zero(codec_list)) { return; } codec_list_copy = ast_strdupa(codec_list); original_jointcaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!original_jointcaps) { return; } ast_format_cap_append_from_cap(original_jointcaps, p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); for (codec = strtok_r(codec_list_copy, ",", &strtok_ptr); codec; codec = strtok_r(NULL, ",", &strtok_ptr)) { struct ast_format *fmt; codec = ast_strip(codec); fmt = ast_format_cache_get(codec); if (!fmt) { 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(original_jointcaps, fmt) != 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(p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(p->jointcaps, fmt, 0); ast_format_cap_remove_by_type(p->caps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(p->caps, fmt, 0); first_codec = 0; } 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(p->jointcaps, fmt, 0); ast_format_cap_append(p->caps, fmt, 0); } } else { ast_log(AST_LOG_NOTICE, "Ignoring ${SIP_CODEC*} variable because it is not shared by both ends: %s\n", codec); } ao2_ref(fmt, -1); } ao2_ref(original_jointcaps, -1); return; } /*! \brief sip_answer: Answer SIP call , send 200 OK on Invite * Part of PBX interface */ static int sip_answer(struct ast_channel *ast) { int res = 0; struct sip_pvt *p = ast_channel_tech_pvt(ast); int oldsdp = FALSE; if (!p) { ast_debug(1, "Asked to answer channel %s without tech pvt; ignoring\n", ast_channel_name(ast)); return res; } sip_pvt_lock(p); if (ast_channel_state(ast) != AST_STATE_UP) { try_suggested_sip_codec(p); if (ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT)) { oldsdp = TRUE; } ast_setstate(ast, AST_STATE_UP); ast_debug(1, "SIP answering channel: %s\n", ast_channel_name(ast)); ast_rtp_instance_update_source(p->rtp); res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, oldsdp, TRUE); ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); /* RFC says the session timer starts counting on 200, * not on INVITE. */ if (p->stimer->st_active == TRUE) { start_session_timer(p); } } sip_pvt_unlock(p); return res; } /*! \brief Send frame to media channel (rtp) */ static int sip_write(struct ast_channel *ast, struct ast_frame *frame) { struct sip_pvt *p = ast_channel_tech_pvt(ast); int res = 0; switch (frame->frametype) { case AST_FRAME_VOICE: if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *codec_buf = ast_str_alloca(64); 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(ast), &codec_buf), ast_format_get_name(ast_channel_readformat(ast)), ast_format_get_name(ast_channel_writeformat(ast))); return 0; } if (p) { sip_pvt_lock(p); if (p->t38.state == T38_ENABLED) { /* drop frame, can't sent VOICE frames while in T.38 mode */ sip_pvt_unlock(p); break; } else if (p->rtp) { /* If channel is not up, activate early media session */ if ((ast_channel_state(ast) != AST_STATE_UP) && !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { ast_rtp_instance_update_source(p->rtp); if (!global_prematuremediafilter) { p->invitestate = INV_EARLY_MEDIA; transmit_provisional_response(p, "183 Session Progress", &p->initreq, TRUE); ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } } if (p->invitestate > INV_EARLY_MEDIA || (p->invitestate == INV_EARLY_MEDIA && ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT))) { p->lastrtptx = time(NULL); res = ast_rtp_instance_write(p->rtp, frame); } } sip_pvt_unlock(p); } break; case AST_FRAME_VIDEO: if (p) { sip_pvt_lock(p); if (p->vrtp) { /* Activate video early media */ if ((ast_channel_state(ast) != AST_STATE_UP) && !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { p->invitestate = INV_EARLY_MEDIA; transmit_provisional_response(p, "183 Session Progress", &p->initreq, TRUE); ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } if (p->invitestate > INV_EARLY_MEDIA || (p->invitestate == INV_EARLY_MEDIA && ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT))) { p->lastrtptx = time(NULL); res = ast_rtp_instance_write(p->vrtp, frame); } } sip_pvt_unlock(p); } break; case AST_FRAME_TEXT: if (p) { sip_pvt_lock(p); if (p->red) { ast_rtp_red_buffer(p->trtp, frame); } else { if (p->trtp) { /* Activate text early media */ if ((ast_channel_state(ast) != AST_STATE_UP) && !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { p->invitestate = INV_EARLY_MEDIA; transmit_provisional_response(p, "183 Session Progress", &p->initreq, TRUE); ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } if (p->invitestate > INV_EARLY_MEDIA || (p->invitestate == INV_EARLY_MEDIA && ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT))) { p->lastrtptx = time(NULL); res = ast_rtp_instance_write(p->trtp, frame); } } } sip_pvt_unlock(p); } break; case AST_FRAME_IMAGE: return 0; break; case AST_FRAME_MODEM: if (p) { sip_pvt_lock(p); /* 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(ast) == AST_STATE_UP) && p->udptl && (p->t38.state == T38_ENABLED)) { res = ast_udptl_write(p->udptl, frame); } sip_pvt_unlock(p); } break; default: ast_log(LOG_WARNING, "Can't send %u type frames with SIP write\n", frame->frametype); return 0; } return res; } /*! \brief sip_fixup: Fix up a channel: If a channel is consumed, this is called. Basically update any ->owner links */ static int sip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { int ret = -1; struct sip_pvt *p; 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 || !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; } p = ast_channel_tech_pvt(newchan); sip_pvt_lock(p); append_history(p, "Masq", "Old channel: %s\n", ast_channel_name(oldchan)); append_history(p, "Masq (cont)", "...new owner: %s\n", ast_channel_name(newchan)); if (p->owner != oldchan) ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, p->owner); else { sip_set_owner(p, 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_set_rtp_peer(newchan, NULL, NULL, 0, 0, 0); ret = 0; } ast_debug(3, "SIP Fixup: New owner for dialogue %s: %s (Old parent: %s)\n", p->callid, ast_channel_name(p->owner), ast_channel_name(oldchan)); sip_pvt_unlock(p); return ret; } static int sip_senddigit_begin(struct ast_channel *ast, char digit) { struct sip_pvt *p = ast_channel_tech_pvt(ast); int res = 0; if (!p) { ast_debug(1, "Asked to begin DTMF digit on channel %s with no pvt; ignoring\n", ast_channel_name(ast)); return res; } sip_pvt_lock(p); switch (ast_test_flag(&p->flags[0], SIP_DTMF)) { case SIP_DTMF_INBAND: res = -1; /* Tell Asterisk to generate inband indications */ break; case SIP_DTMF_RFC2833: if (p->rtp) ast_rtp_instance_dtmf_begin(p->rtp, digit); break; default: break; } sip_pvt_unlock(p); return res; } /*! \brief Send DTMF character on SIP channel within one call, we're able to transmit in many methods simultaneously */ static int sip_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct sip_pvt *p = ast_channel_tech_pvt(ast); int res = 0; if (!p) { ast_debug(1, "Asked to end DTMF digit on channel %s with no pvt; ignoring\n", ast_channel_name(ast)); return res; } sip_pvt_lock(p); switch (ast_test_flag(&p->flags[0], SIP_DTMF)) { case SIP_DTMF_INFO: case SIP_DTMF_SHORTINFO: transmit_info_with_digit(p, digit, duration); break; case SIP_DTMF_RFC2833: if (p->rtp) ast_rtp_instance_dtmf_end_with_duration(p->rtp, digit, duration); break; case SIP_DTMF_INBAND: res = -1; /* Tell Asterisk to stop inband indications */ break; } sip_pvt_unlock(p); return res; } /*! \brief Transfer SIP call */ static int sip_transfer(struct ast_channel *ast, const char *dest) { struct sip_pvt *p = ast_channel_tech_pvt(ast); int res; if (!p) { ast_debug(1, "Asked to transfer channel %s with no pvt; ignoring\n", ast_channel_name(ast)); return -1; } if (dest == NULL) /* functions below do not take a NULL */ dest = ""; sip_pvt_lock(p); if (ast_channel_state(ast) == AST_STATE_RING) res = sip_sipredirect(p, dest); else res = transmit_refer(p, dest); sip_pvt_unlock(p); return res; } /*! \brief Helper function which updates T.38 capability information and triggers a reinvite */ static int interpret_t38_parameters(struct sip_pvt *p, const struct ast_control_t38_parameters *parameters) { int res = 0; if (!ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) || !p->udptl) { return -1; } switch (parameters->request_response) { case AST_T38_NEGOTIATED: case AST_T38_REQUEST_NEGOTIATE: /* Request T38 */ /* Negotiation can not take place without a valid max_ifp value. */ if (!parameters->max_ifp) { if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); } change_t38_state(p, T38_REJECTED); break; } else if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); p->t38.our_parms = *parameters; /* modify our parameters to conform to the peer's parameters, * based on the rules in the ITU T.38 recommendation */ if (!p->t38.their_parms.fill_bit_removal) { p->t38.our_parms.fill_bit_removal = FALSE; } if (!p->t38.their_parms.transcoding_mmr) { p->t38.our_parms.transcoding_mmr = FALSE; } if (!p->t38.their_parms.transcoding_jbig) { p->t38.our_parms.transcoding_jbig = FALSE; } p->t38.our_parms.version = MIN(p->t38.our_parms.version, p->t38.their_parms.version); p->t38.our_parms.rate_management = p->t38.their_parms.rate_management; ast_udptl_set_local_max_ifp(p->udptl, p->t38.our_parms.max_ifp); change_t38_state(p, T38_ENABLED); transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL); } else if (p->t38.state != T38_ENABLED) { p->t38.our_parms = *parameters; ast_udptl_set_local_max_ifp(p->udptl, p->t38.our_parms.max_ifp); change_t38_state(p, T38_LOCAL_REINVITE); if (!p->pendinginvite) { transmit_reinvite_with_sdp(p, TRUE, FALSE); } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); } } break; case AST_T38_TERMINATED: case AST_T38_REFUSED: case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); change_t38_state(p, T38_REJECTED); transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); } else if (p->t38.state == T38_ENABLED) transmit_reinvite_with_sdp(p, FALSE, FALSE); break; case AST_T38_REQUEST_PARMS: { /* Application wants remote's parameters re-sent */ struct ast_control_t38_parameters parameters = p->t38.their_parms; if (p->t38.state == T38_PEER_REINVITE) { AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr")); parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl); parameters.request_response = AST_T38_REQUEST_NEGOTIATE; if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); } /* we need to return a positive value here, so that applications that * send this request can determine conclusively whether it was accepted or not... * older versions of chan_sip would just silently accept it and return zero. */ res = AST_T38_REQUEST_PARMS; } break; } default: res = -1; break; } return res; } /*! * \internal * \brief Create and initialize UDPTL for the specified dialog * * \param p SIP private structure to create UDPTL object for * \pre p is locked * \pre p->owner is locked * * \note In the case of failure, SIP_PAGE2_T38SUPPORT is cleared on p * * \return 0 on success, any other value on failure */ static int initialize_udptl(struct sip_pvt *p) { int natflags = ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP); if (!ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) { return 1; } /* If we've already initialized T38, don't take any further action */ if (p->udptl) { return 0; } /* T38 can be supported by this dialog, create it and set the derived properties */ if ((p->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, &bindaddr))) { if (p->owner) { ast_channel_set_fd(p->owner, 5, ast_udptl_fd(p->udptl)); } ast_udptl_setqos(p->udptl, global_tos_audio, global_cos_audio); p->t38_maxdatagram = p->relatedpeer ? p->relatedpeer->t38_maxdatagram : global_t38_maxdatagram; set_t38_capabilities(p); ast_debug(1, "Setting NAT on UDPTL to %s\n", natflags ? "On" : "Off"); ast_udptl_setnat(p->udptl, natflags); } else { ast_log(AST_LOG_WARNING, "UDPTL creation failed - disabling T38 for this dialog\n"); ast_clear_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT); return 1; } return 0; } static int sipinfo_send( struct ast_channel *chan, struct ast_variable *headers, const char *content_type, const char *content, const char *useragent_filter) { struct sip_pvt *p; 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; } p = ast_channel_tech_pvt(chan); sip_pvt_lock(p); if (!(ast_strlen_zero(useragent_filter))) { int match = (strstr(p->useragent, useragent_filter)) ? 1 : 0; if (!match) { goto cleanup; } } reqprep(&req, p, SIP_INFO, 0, 1); for (var = headers; var; var = var->next) { add_header(&req, var->name, var->value); } if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) { add_header(&req, "Content-Type", content_type); add_content(&req, content); } res = send_request(p, &req, XMIT_RELIABLE, p->ocseq); cleanup: sip_pvt_unlock(p); ast_channel_unlock(chan); return res; } /*! \brief Play indication to user * With SIP a lot of indications is sent as messages, letting the device play the indication - busy signal, congestion etc \return -1 to force ast_indicate to send indication in audio, 0 if SIP can handle the indication by sending a message */ static int sip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) { struct sip_pvt *p = ast_channel_tech_pvt(ast); int res = 0; if (!p) { ast_debug(1, "Asked to indicate condition on channel %s with no pvt; ignoring\n", ast_channel_name(ast)); return res; } sip_pvt_lock(p); switch(condition) { case AST_CONTROL_RINGING: if (ast_channel_state(ast) == AST_STATE_RING) { p->invitestate = INV_EARLY_MEDIA; if (!ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) || (ast_test_flag(&p->flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NEVER)) { /* Send 180 ringing if out-of-band seems reasonable */ transmit_provisional_response(p, "180 Ringing", &p->initreq, 0); ast_set_flag(&p->flags[0], SIP_RINGING); if (ast_test_flag(&p->flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_YES) break; } else { /* Well, if it's not reasonable, just send in-band */ } } res = -1; break; case AST_CONTROL_BUSY: if (ast_channel_state(ast) != AST_STATE_UP) { transmit_response_reliable(p, "486 Busy Here", &p->initreq); p->invitestate = INV_COMPLETED; sip_alreadygone(p); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); break; } res = -1; break; case AST_CONTROL_CONGESTION: if (ast_channel_state(ast) != AST_STATE_UP) { transmit_response_reliable(p, "503 Service Unavailable", &p->initreq); p->invitestate = INV_COMPLETED; sip_alreadygone(p); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); break; } res = -1; break; case AST_CONTROL_INCOMPLETE: if (ast_channel_state(ast) != AST_STATE_UP) { switch (ast_test_flag(&p->flags[1], SIP_PAGE2_ALLOWOVERLAP)) { case SIP_PAGE2_ALLOWOVERLAP_YES: transmit_response_reliable(p, "484 Address Incomplete", &p->initreq); p->invitestate = INV_COMPLETED; sip_alreadygone(p); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); break; case SIP_PAGE2_ALLOWOVERLAP_DTMF: /* Just wait for inband DTMF digits */ break; default: /* it actually means no support for overlap */ transmit_response_reliable(p, "404 Not Found", &p->initreq); p->invitestate = INV_COMPLETED; sip_alreadygone(p); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); break; } } break; case AST_CONTROL_PROCEEDING: if ((ast_channel_state(ast) != AST_STATE_UP) && !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { transmit_response(p, "100 Trying", &p->initreq); p->invitestate = INV_PROCEEDING; break; } res = -1; break; case AST_CONTROL_PROGRESS: if ((ast_channel_state(ast) != AST_STATE_UP) && !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { p->invitestate = INV_EARLY_MEDIA; /* SIP_PROG_INBAND_NEVER means sending 180 ringing in place of a 183 */ if (ast_test_flag(&p->flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_NEVER) { transmit_provisional_response(p, "183 Session Progress", &p->initreq, TRUE); ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } else if (ast_channel_state(ast) == AST_STATE_RING && !ast_test_flag(&p->flags[0], SIP_RINGING)) { transmit_provisional_response(p, "180 Ringing", &p->initreq, 0); ast_set_flag(&p->flags[0], SIP_RINGING); } break; } res = -1; break; case AST_CONTROL_HOLD: ast_rtp_instance_update_source(p->rtp); ast_moh_start(ast, data, p->mohinterpret); break; case AST_CONTROL_UNHOLD: ast_rtp_instance_update_source(p->rtp); ast_moh_stop(ast); break; case AST_CONTROL_VIDUPDATE: /* Request a video frame update */ if (p->vrtp && !p->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(ast), 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 fr; fr.frametype = AST_FRAME_CONTROL; fr.subclass.integer = AST_CONTROL_VIDUPDATE; res = ast_rtp_instance_write(p->vrtp, &fr); } else { transmit_info_with_vidupdate(p); } } 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 *parameters = data; if (!initialize_udptl(p)) { res = interpret_t38_parameters(p, parameters); } } break; case AST_CONTROL_SRCUPDATE: ast_rtp_instance_update_source(p->rtp); break; case AST_CONTROL_SRCCHANGE: ast_rtp_instance_change_source(p->rtp); break; case AST_CONTROL_CONNECTED_LINE: update_connectedline(p, data, datalen); break; case AST_CONTROL_REDIRECTING: update_redirecting(p, data, datalen); break; case AST_CONTROL_AOC: { struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, ast); if (!decoded) { 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(p->owner)); ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); } break; case AST_AOC_D: case AST_AOC_E: if (ast_test_flag(&p->flags[2], SIP_PAGE3_SNOM_AOC)) { transmit_info_with_aoc(p, decoded); } break; 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. */ 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; } sip_pvt_unlock(p); return res; } /*! * \brief Initiate a call in the SIP channel * * \note called from sip_request_call (calls from the pbx ) for * outbound channels and from handle_request_invite for inbound * channels * * \pre i is locked * * \return New ast_channel locked. */ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, struct ast_callid *callid) { struct ast_format_cap *caps; struct ast_channel *tmp; struct ast_variable *v = NULL; struct ast_format *fmt; struct ast_format_cap *what = NULL; /* SHALLOW COPY DO NOT DESTROY! */ struct ast_str *codec_buf = ast_str_alloca(64); int needvideo = 0; int needtext = 0; char *exten; caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { return NULL; } { const char *my_name; /* pick a good name */ if (title) { my_name = title; } else { my_name = ast_strdupa(i->fromdomain); } /* Don't hold a sip pvt lock while we allocate a channel */ sip_pvt_unlock(i); if (i->relatedpeer && i->relatedpeer->endpoint) { tmp = ast_channel_alloc_with_endpoint(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, i->relatedpeer->endpoint, "SIP/%s-%08x", my_name, (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1)); } else { tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "SIP/%s-%08x", my_name, (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1)); } } if (!tmp) { ast_log(LOG_WARNING, "Unable to allocate AST channel structure for SIP channel\n"); ao2_ref(caps, -1); sip_pvt_lock(i); return NULL; } ast_channel_stage_snapshot(tmp); /* If we sent in a callid, bind it to the channel. */ if (callid) { ast_channel_callid_set(tmp, callid); } sip_pvt_lock(i); ast_channel_cc_params_init(tmp, i->cc_params); ast_channel_caller(tmp)->id.tag = ast_strdup(i->cid_tag); ast_channel_tech_set(tmp, (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO || ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO) ? &sip_tech_info : &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(i->jointcaps)) { /* The joint capabilities of us and peer */ what = i->jointcaps; } else if (ast_format_cap_count(i->caps)) { /* Our configured capability for this peer */ what = i->caps; } else { what = sip_cfg.caps; } /* Set the native formats */ ast_format_cap_append_from_cap(caps, what, AST_MEDIA_TYPE_UNKNOWN); /* Use only the preferred audio format, which is stored at the '0' index */ fmt = ast_format_cap_get_best_by_type(what, AST_MEDIA_TYPE_AUDIO); /* get the best audio format */ if (fmt) { ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO); /* remove only the other audio formats */ ast_format_cap_append(caps, fmt, 0); /* add our best choice back */ } else { /* If we don't have an audio format, try to get something */ fmt = ast_format_cap_get_format(caps, 0); if (!fmt) { ast_log(LOG_WARNING, "No compatible formats could be found for %s\n", ast_channel_name(tmp)); ao2_ref(caps, -1); tmp = ast_channel_unref(tmp); return NULL; } } ast_channel_nativeformats_set(tmp, caps); ao2_ref(caps, -1); ast_debug(3, "*** Our native formats are %s \n", ast_format_cap_get_names(ast_channel_nativeformats(tmp), &codec_buf)); ast_debug(3, "*** Joint capabilities are %s \n", ast_format_cap_get_names(i->jointcaps, &codec_buf)); ast_debug(3, "*** Our capabilities are %s \n", ast_format_cap_get_names(i->caps, &codec_buf)); ast_debug(3, "*** AST_CODEC_CHOOSE formats are %s \n", ast_format_get_name(fmt)); if (ast_format_cap_count(i->prefcaps)) { ast_debug(3, "*** Our preferred formats from the incoming channel are %s \n", ast_format_cap_get_names(i->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 (i->vrtp) { if (ast_test_flag(&i->flags[1], SIP_PAGE2_VIDEOSUPPORT)) needvideo = 1; else if (ast_format_cap_count(i->prefcaps)) needvideo = ast_format_cap_has_type(i->prefcaps, AST_MEDIA_TYPE_VIDEO); /* Outbound call */ else needvideo = ast_format_cap_has_type(i->jointcaps, AST_MEDIA_TYPE_VIDEO); /* Inbound call */ } if (i->trtp) { if (ast_format_cap_count(i->prefcaps)) needtext = ast_format_cap_has_type(i->prefcaps, AST_MEDIA_TYPE_TEXT); /* Outbound call */ else needtext = ast_format_cap_has_type(i->jointcaps, AST_MEDIA_TYPE_TEXT); /* Inbound call */ } if (needvideo) { ast_debug(3, "This channel can handle video! HOLLYWOOD next!\n"); } else { ast_debug(3, "This channel will not be able to handle video.\n"); } enable_dsp_detect(i); if ((ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) || (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) { if (i->rtp) { ast_rtp_instance_dtmf_mode_set(i->rtp, AST_RTP_DTMF_MODE_INBAND); } } else if (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) { if (i->rtp) { ast_rtp_instance_dtmf_mode_set(i->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 initialize_udptl */ if (i->rtp) { ast_channel_set_fd(tmp, 0, ast_rtp_instance_fd(i->rtp, 0)); ast_channel_set_fd(tmp, 1, ast_rtp_instance_fd(i->rtp, 1)); ast_rtp_instance_set_write_format(i->rtp, fmt); ast_rtp_instance_set_read_format(i->rtp, fmt); } if (needvideo && i->vrtp) { ast_channel_set_fd(tmp, 2, ast_rtp_instance_fd(i->vrtp, 0)); ast_channel_set_fd(tmp, 3, ast_rtp_instance_fd(i->vrtp, 1)); } if (needtext && i->trtp) { ast_channel_set_fd(tmp, 4, ast_rtp_instance_fd(i->trtp, 0)); } if (i->udptl) { ast_channel_set_fd(tmp, 5, ast_udptl_fd(i->udptl)); } if (state == AST_STATE_RING) { ast_channel_rings_set(tmp, 1); } ast_channel_adsicpe_set(tmp, AST_ADSI_UNAVAILABLE); ast_channel_set_writeformat(tmp, fmt); ast_channel_set_rawwriteformat(tmp, fmt); ast_channel_set_readformat(tmp, fmt); ast_channel_set_rawreadformat(tmp, fmt); ao2_ref(fmt, -1); ast_channel_tech_pvt_set(tmp, dialog_ref(i, "sip_new: set chan->tech_pvt to i")); ast_channel_callgroup_set(tmp, i->callgroup); ast_channel_pickupgroup_set(tmp, i->pickupgroup); ast_channel_named_callgroups_set(tmp, i->named_callgroups); ast_channel_named_pickupgroups_set(tmp, i->named_pickupgroups); ast_channel_caller(tmp)->id.name.presentation = i->callingpres; ast_channel_caller(tmp)->id.number.presentation = i->callingpres; if (!ast_strlen_zero(i->parkinglot)) { ast_channel_parkinglot_set(tmp, i->parkinglot); } if (!ast_strlen_zero(i->accountcode)) { ast_channel_accountcode_set(tmp, i->accountcode); } if (i->amaflags) { ast_channel_amaflags_set(tmp, i->amaflags); } if (!ast_strlen_zero(i->language)) { ast_channel_language_set(tmp, i->language); } if (!ast_strlen_zero(i->zone)) { struct ast_tone_zone *zone; if (!(zone = ast_get_indication_zone(i->zone))) { ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone. Check indications.conf for available country codes.\n", i->zone); } ast_channel_zone_set(tmp, zone); } sip_set_owner(i, tmp); ast_module_ref(ast_module_info->self); ast_channel_context_set(tmp, i->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(i->exten); sip_pvt_unlock(i); ast_channel_unlock(tmp); if (!ast_exists_extension(NULL, i->context, i->exten, 1, i->cid_num)) { ast_uri_decode(exten, ast_uri_sip_user); } ast_channel_lock(tmp); sip_pvt_lock(i); ast_channel_exten_set(tmp, exten); /* Don't use ast_set_callerid() here because it will * generate an unnecessary NewCallerID event */ if (!ast_strlen_zero(i->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num); } if (!ast_strlen_zero(i->rdnis)) { ast_channel_redirecting(tmp)->from.number.valid = 1; ast_channel_redirecting(tmp)->from.number.str = ast_strdup(i->rdnis); } if (!ast_strlen_zero(i->exten) && strcmp(i->exten, "s")) { ast_channel_dialed(tmp)->number.str = ast_strdup(i->exten); } ast_channel_priority_set(tmp, 1); if (!ast_strlen_zero(i->uri)) { pbx_builtin_setvar_helper(tmp, "SIPURI", i->uri); } if (!ast_strlen_zero(i->domain)) { pbx_builtin_setvar_helper(tmp, "SIPDOMAIN", i->domain); } if (!ast_strlen_zero(i->tel_phone_context)) { pbx_builtin_setvar_helper(tmp, "SIPURIPHONECONTEXT", i->tel_phone_context); } if (!ast_strlen_zero(i->callid)) { pbx_builtin_setvar_helper(tmp, "SIPCALLID", i->callid); } if (i->rtp) { ast_jb_configure(tmp, &global_jbconf); } if (!i->relatedpeer) { ast_set_flag(ast_channel_flags(tmp), AST_FLAG_DISABLE_DEVSTATE_CACHE); } /* Set channel variables for this call from configuration */ for (v = i->chanvars ; v ; v = v->next) { char valuebuf[1024]; pbx_builtin_setvar_helper(tmp, v->name, ast_get_encoded_str(v->value, valuebuf, sizeof(valuebuf))); } if (i->do_history) { append_history(i, "NewChan", "Channel %s - from %s", ast_channel_name(tmp), i->callid); } ast_channel_stage_snapshot_done(tmp); return tmp; } /*! \brief 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 *get_sdp_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 = REQ_OFFSET_TO_STR(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 ""; } /*! \brief 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 get_sdp_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 = REQ_OFFSET_TO_STR(req, line[(*start)++]); if (line[1] == '=') { type = line[0]; *value = ast_skip_blanks(line + 2); break; } } return type; } /*! \brief Get a specific line from the message content */ static char *get_content_line(struct sip_request *req, char *name, char delimiter) { int i; int len = strlen(name); const char *line; for (i = 0; i < req->lines; i++) { line = REQ_OFFSET_TO_STR(req, line[i]); if (!strncasecmp(line, name, len) && line[len] == delimiter) { return ast_skip_blanks(line + len + 1); } } return ""; } /*! \brief Structure for conversion between compressed SIP and "normal" SIP headers */ struct cfalias { const char *fullname; const char *shortname; }; static const struct cfalias aliases[] = { { "Content-Type", "c" }, { "Content-Encoding", "e" }, { "From", "f" }, { "Call-ID", "i" }, { "Contact", "m" }, { "Content-Length", "l" }, { "Subject", "s" }, { "To", "t" }, { "Supported", "k" }, { "Refer-To", "r" }, { "Referred-By", "b" }, { "Allow-Events", "u" }, { "Event", "o" }, { "Via", "v" }, { "Accept-Contact", "a" }, { "Reject-Contact", "j" }, { "Request-Disposition", "d" }, { "Session-Expires", "x" }, { "Identity", "y" }, { "Identity-Info", "n" }, }; /*! \brief Find compressed SIP alias */ static const char *find_alias(const char *name, const char *_default) { int x; for (x = 0; x < ARRAY_LEN(aliases); x++) { if (!strcasecmp(aliases[x].fullname, name)) return aliases[x].shortname; } return _default; } /*! \brief Find full SIP alias */ static const char *find_full_alias(const char *name, const char *_default) { int x; if (strlen(name) == 1) { /* We have a short header name to convert. */ for (x = 0; x < ARRAY_LEN(aliases); ++x) { if (!strcasecmp(aliases[x].shortname, name)) return aliases[x].fullname; } } return _default; } static const char *__get_header(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. * Anyways, pedanticsipchecking controls whether we allow spaces before ':', * and we always allow spaces after that for compatibility. */ const char *sname = find_alias(name, NULL); int x, len = strlen(name), slen = (sname ? 1 : 0); for (x = *start; x < req->headers; x++) { const char *header = REQ_OFFSET_TO_STR(req, header[x]); int smatch = 0, match = !strncasecmp(header, name, len); if (slen) { smatch = !strncasecmp(header, sname, slen); } if (match || smatch) { /* skip name */ const char *r = header + (match ? len : slen ); if (sip_cfg.pedanticsipchecking) { r = ast_skip_blanks(r); } if (*r == ':') { *start = x+1; return ast_skip_blanks(r+1); } } } /* Don't return NULL, so sip_get_header is always a valid pointer */ return ""; } /*! \brief Get header from SIP request \return Always return something, so don't check for NULL because it won't happen :-) */ const char *sip_get_header(const struct sip_request *req, const char *name) { int start = 0; return __get_header(req, name, &start); } AST_THREADSTORAGE(sip_content_buf); /*! \brief Get message body content */ static char *get_content(struct sip_request *req) { struct ast_str *str; int i; if (!(str = ast_str_thread_get(&sip_content_buf, 128))) { return NULL; } ast_str_reset(str); for (i = 0; i < req->lines; i++) { if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[i])) < 0) { return NULL; } } return ast_str_buffer(str); } /*! \brief Read RTP from network */ static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p, int *faxdetect) { /* Retrieve audio/etc from channel. Assumes p->lock is already held. */ struct ast_frame *f; if (!p->rtp) { /* We have no RTP allocated for this channel */ return &ast_null_frame; } switch(ast_channel_fdno(ast)) { case 0: f = ast_rtp_instance_read(p->rtp, 0); /* RTP Audio */ break; case 1: f = ast_rtp_instance_read(p->rtp, 1); /* RTCP Control Channel */ break; case 2: f = ast_rtp_instance_read(p->vrtp, 0); /* RTP Video */ break; case 3: f = ast_rtp_instance_read(p->vrtp, 1); /* RTCP Control Channel for video */ break; case 4: f = ast_rtp_instance_read(p->trtp, 0); /* RTP Text */ if (sipdebug_text) { struct ast_str *out = ast_str_create(f->datalen * 4 + 6); int i; unsigned char* arr = f->data.ptr; do { if (!out) { break; } for (i = 0; i < f->datalen; i++) { ast_str_append(&out, 0, "%c", (arr[i] > ' ' && arr[i] < '}') ? arr[i] : '.'); } ast_str_append(&out, 0, " -> "); for (i = 0; i < f->datalen; i++) { ast_str_append(&out, 0, "%02X ", (unsigned)arr[i]); } ast_verb(0, "%s\n", ast_str_buffer(out)); ast_free(out); } while (0); } break; case 5: f = ast_udptl_read(p->udptl); /* UDPTL for T.38 */ break; default: f = &ast_null_frame; } /* Don't forward RFC2833 if we're not supposed to */ if (f && (f->frametype == AST_FRAME_DTMF_BEGIN || f->frametype == AST_FRAME_DTMF_END) && (ast_test_flag(&p->flags[0], SIP_DTMF) != SIP_DTMF_RFC2833)) { ast_debug(1, "Ignoring DTMF (%c) RTP frame because dtmfmode is not RFC2833\n", f->subclass.integer); ast_frfree(f); return &ast_null_frame; } /* We already hold the channel lock */ if (!p->owner || (f && f->frametype != AST_FRAME_VOICE)) { return f; } if (f && ast_format_cap_iscompatible_format(ast_channel_nativeformats(p->owner), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_format_cap *caps; if (ast_format_cap_iscompatible_format(p->jointcaps, f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { ast_debug(1, "Bogus frame of format '%s' received from '%s'!\n", ast_format_get_name(f->subclass.format), ast_channel_name(p->owner)); ast_frfree(f); return &ast_null_frame; } ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(f->subclass.format)); caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(p->owner), AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO); ast_format_cap_append(caps, f->subclass.format, 0); ast_channel_nativeformats_set(p->owner, caps); ao2_ref(caps, -1); } ast_set_read_format(p->owner, ast_channel_readformat(p->owner)); ast_set_write_format(p->owner, ast_channel_writeformat(p->owner)); } if (f && p->dsp) { f = ast_dsp_process(p->owner, p->dsp, f); if (f && f->frametype == AST_FRAME_DTMF) { if (f->subclass.integer == 'f') { ast_debug(1, "Fax CNG detected on %s\n", ast_channel_name(ast)); *faxdetect = 1; /* If we only needed this DSP for fax detection purposes we can just drop it now */ if (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) { ast_dsp_set_features(p->dsp, DSP_FEATURE_DIGIT_DETECT); } else { ast_dsp_free(p->dsp); p->dsp = NULL; } } else { ast_debug(1, "* Detected inband DTMF '%c'\n", f->subclass.integer); } } } return f; } /*! \brief Read SIP RTP from channel */ static struct ast_frame *sip_read(struct ast_channel *ast) { struct ast_frame *fr; struct sip_pvt *p = ast_channel_tech_pvt(ast); int faxdetected = FALSE; sip_pvt_lock(p); fr = sip_rtp_read(ast, p, &faxdetected); p->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(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) { if (strcmp(ast_channel_exten(ast), "fax")) { const char *target_context = S_OR(ast_channel_macrocontext(ast), ast_channel_context(ast)); /* We need to unlock 'ast' 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. */ sip_pvt_unlock(p); ast_channel_unlock(ast); if (ast_exists_extension(ast, target_context, "fax", 1, S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, NULL))) { ast_channel_lock(ast); sip_pvt_lock(p); ast_verb(2, "Redirecting '%s' to fax extension due to CNG detection\n", ast_channel_name(ast)); pbx_builtin_setvar_helper(ast, "FAXEXTEN", ast_channel_exten(ast)); if (ast_async_goto(ast, target_context, "fax", 1)) { ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(ast), target_context); } ast_frfree(fr); fr = &ast_null_frame; } else { ast_channel_lock(ast); sip_pvt_lock(p); ast_log(LOG_NOTICE, "FAX CNG detected but no fax extension\n"); } } } /* Only allow audio through if they sent progress with SDP, or if the channel is actually answered */ if (fr && fr->frametype == AST_FRAME_VOICE && p->invitestate != INV_EARLY_MEDIA && ast_channel_state(ast) != AST_STATE_UP) { ast_frfree(fr); fr = &ast_null_frame; } sip_pvt_unlock(p); return fr; } /*! \brief Generate 32 byte random string for callid's etc */ static char *generate_random_string(char *buf, size_t size) { long val[4]; int x; for (x=0; x<4; x++) val[x] = 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; } static char *generate_uri(struct sip_pvt *pvt, char *buf, size_t size) { struct ast_str *uri = ast_str_alloca(size); ast_str_set(&uri, 0, "%s", pvt->socket.type == AST_TRANSPORT_TLS ? "sips:" : "sip:"); /* Here would be a great place to generate a UUID, but for now we'll * use the handy random string generation function we already have */ ast_str_append(&uri, 0, "%s", generate_random_string(buf, size)); ast_str_append(&uri, 0, "@%s", ast_sockaddr_stringify_remote(&pvt->ourip)); ast_copy_string(buf, ast_str_buffer(uri), size); return buf; } /*! * \brief Build SIP Call-ID value for a non-REGISTER transaction * * \note The passed in pvt must not be in a dialogs container * since this function changes the hash key used by the * container. */ static void build_callid_pvt(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", generate_random_string(buf, sizeof(buf)), host); } /*! \brief Unlink the given object from the container and return TRUE if it was in the container. */ #define CONTAINER_UNLINK(container, obj, tag) \ ({ \ int found = 0; \ typeof((obj)) __removed_obj; \ __removed_obj = ao2_t_callback((container), \ OBJ_UNLINK | OBJ_POINTER, ao2_match_by_addr, (obj), (tag)); \ if (__removed_obj) { \ ao2_ref(__removed_obj, -1); \ found = 1; \ } \ found; \ }) /*! * \internal * \brief Safely change the callid of the given SIP dialog. * * \param pvt SIP private structure to change callid * \param callid Specified new callid to use. NULL if generate new callid. * * \return Nothing */ static void change_callid_pvt(struct sip_pvt *pvt, const char *callid) { int in_dialog_container; int in_rtp_container; char *oldid = ast_strdupa(pvt->callid); ao2_lock(dialogs); ao2_lock(dialogs_rtpcheck); in_dialog_container = CONTAINER_UNLINK(dialogs, pvt, "About to change the callid -- remove the old name"); in_rtp_container = CONTAINER_UNLINK(dialogs_rtpcheck, pvt, "About to change the callid -- remove the old name"); if (callid) { ast_string_field_set(pvt, callid, callid); } else { build_callid_pvt(pvt); } if (in_dialog_container) { ao2_t_link(dialogs, pvt, "New dialog callid -- inserted back into table"); } if (in_rtp_container) { ao2_t_link(dialogs_rtpcheck, pvt, "New dialog callid -- inserted back into table"); } ao2_unlock(dialogs_rtpcheck); ao2_unlock(dialogs); if (strcmp(oldid, pvt->callid)) { ast_debug(1, "SIP call-id changed from '%s' to '%s'\n", oldid, pvt->callid); } } /*! \brief Build SIP Call-ID value for a REGISTER transaction */ static void build_callid_registry(struct sip_registry *reg, 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(reg, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host); } /*! \brief Build SIP From tag value for REGISTER */ static void build_localtag_registry(struct sip_registry *reg) { ast_string_field_build(reg, localtag, "as%08lx", (unsigned long)ast_random()); } /*! \brief Make our SIP dialog tag */ static void make_our_tag(struct sip_pvt *pvt) { ast_string_field_build(pvt, tag, "as%08lx", (unsigned long)ast_random()); } /*! \brief Allocate Session-Timers struct w/in dialog */ static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p) { struct sip_st_dlg *stp; if (p->stimer) { ast_log(LOG_ERROR, "Session-Timer struct already allocated\n"); return p->stimer; } if (!(stp = ast_calloc(1, sizeof(struct sip_st_dlg)))) return NULL; p->stimer = stp; stp->st_schedid = -1; /* Session-Timers ast_sched scheduler id */ return p->stimer; } static void sip_pvt_callid_set(struct sip_pvt *pvt, struct ast_callid *callid) { if (pvt->logger_callid) { ast_callid_unref(pvt->logger_callid); } ast_callid_ref(callid); pvt->logger_callid = callid; } /*! \brief Allocate sip_pvt structure, set defaults and link in the container. * Returns a reference to the object so whoever uses it later must * remember to release the reference. */ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, const int intended_method, struct sip_request *req, struct ast_callid *logger_callid) { struct sip_pvt *p; if (!(p = ao2_t_alloc(sizeof(*p), sip_destroy_fn, "allocate a dialog(pvt) struct"))) return NULL; if (ast_string_field_init(p, 512)) { ao2_t_ref(p, -1, "failed to string_field_init, drop p"); return NULL; } if (!(p->cc_params = ast_cc_config_params_init())) { ao2_t_ref(p, -1, "Yuck, couldn't allocate cc_params struct. Get rid o' p"); return NULL; } if (logger_callid) { sip_pvt_callid_set(p, logger_callid); } p->caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); p->jointcaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); p->peercaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); p->redircaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); p->prefcaps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!p->caps|| !p->jointcaps || !p->peercaps || !p->redircaps || !p->prefcaps) { ao2_cleanup(p->caps); ao2_cleanup(p->jointcaps); ao2_cleanup(p->peercaps); ao2_cleanup(p->redircaps); ao2_cleanup(p->prefcaps); ao2_t_ref(p, -1, "Yuck, couldn't allocate format capabilities. Get rid o' p"); 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_get_header(req, "Cseq"); uint32_t seqno; /* get branch parameter from initial Request that started this dialog */ via = parse_via(sip_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(p, initviabranch, via->branch); ast_string_field_set(p, initviasentby, via->sent_by); } free_via(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 find_call */ if (!ast_strlen_zero(cseq) && (sscanf(cseq, "%30u", &seqno) == 1)) { p->init_icseq = seqno; } /* Later in ast_sip_ouraddrfor we need this to choose the right ip and port for the specific transport */ set_socket_transport(&p->socket, req->socket.type); } else { set_socket_transport(&p->socket, AST_TRANSPORT_UDP); } p->socket.fd = -1; p->method = intended_method; p->initid = -1; p->waitid = -1; p->reinviteid = -1; p->autokillid = -1; p->request_queue_sched_id = -1; p->provisional_keepalive_sched_id = -1; p->t38id = -1; p->subscribed = NONE; p->stateid = -1; p->sessionversion_remote = -1; p->session_modify = TRUE; p->stimer = NULL; ast_copy_string(p->zone, default_zone, sizeof(p->zone)); p->maxforwards = sip_cfg.default_max_forwards; if (intended_method != SIP_OPTIONS) { /* Peerpoke has it's own system */ p->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */ p->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */ } if (!addr) { p->ourip = internip; } else { ast_sockaddr_copy(&p->sa, addr); ast_sip_ouraddrfor(&p->sa, &p->ourip, p); } /* Copy global flags to this PVT at setup. */ ast_copy_flags(&p->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&p->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&p->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY); p->do_history = recordhistory; p->branch = ast_random(); make_our_tag(p); p->ocseq = INITIAL_CSEQ; p->allowed_methods = UINT_MAX; if (sip_methods[intended_method].need_rtp) { p->maxcallbitrate = default_maxcallbitrate; p->autoframing = global_autoframing; } if (useglobal_nat && addr) { /* Setup NAT structure according to global settings if we have an address */ ast_sockaddr_copy(&p->recv, addr); check_via(p, req); do_setnat(p); } if (p->method != SIP_REGISTER) { ast_string_field_set(p, fromdomain, default_fromdomain); p->fromdomainport = default_fromdomainport; } build_via(p); if (!callid) build_callid_pvt(p); else ast_string_field_set(p, callid, callid); /* Assign default music on hold class */ ast_string_field_set(p, mohinterpret, default_mohinterpret); ast_string_field_set(p, mohsuggest, default_mohsuggest); ast_format_cap_append_from_cap(p->caps, sip_cfg.caps, AST_MEDIA_TYPE_UNKNOWN); p->allowtransfer = sip_cfg.allowtransfer; if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) || (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) { p->noncodeccapability |= AST_RTP_DTMF; } ast_string_field_set(p, context, sip_cfg.default_context); ast_string_field_set(p, parkinglot, default_parkinglot); ast_string_field_set(p, engine, default_engine); AST_LIST_HEAD_INIT_NOLOCK(&p->request_queue); AST_LIST_HEAD_INIT_NOLOCK(&p->offered_media); /* Add to active dialog list */ ao2_t_link(dialogs, p, "link pvt into dialogs table"); ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", callid ? callid : p->callid, sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP"); return p; } /*! * \brief Process the Via header according to RFC 3261 section 18.2.2. * \param p a sip_pvt structure that will be modified according to the received * header * \param req a sip request with a Via header to process * * 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 addres the request came from in the * respprep() function). * * \retval -1 error * \retval 0 success */ static int process_via(struct sip_pvt *p, const struct sip_request *req) { struct sip_via *via = parse_via(sip_get_header(req, "Via")); if (!via) { ast_log(LOG_ERROR, "error processing via header\n"); return -1; } if (via->maddr) { if (ast_sockaddr_resolve_first_transport(&p->sa, via->maddr, PARSE_PORT_FORBID, p->socket.type)) { ast_log(LOG_WARNING, "Can't find address for maddr '%s'\n", via->maddr); ast_log(LOG_ERROR, "error processing via header\n"); free_via(via); return -1; } if (ast_sockaddr_is_ipv4_multicast(&p->sa)) { setsockopt(sipsock, IPPROTO_IP, IP_MULTICAST_TTL, &via->ttl, sizeof(via->ttl)); } } ast_sockaddr_set_port(&p->sa, via->port ? via->port : STANDARD_SIP_PORT); free_via(via); return 0; } /* \brief arguments used for Request/Response to matching */ struct match_req_args { int method; const char *callid; const char *totag; const char *fromtag; uint32_t seqno; /* Set if this method is a Response */ int respid; /* Set if the method is a Request */ const char *ruri; const char *viabranch; const char *viasentby; /* Set this if the Authentication header is present in the Request. */ int authentication_present; }; enum match_req_res { SIP_REQ_MATCH, SIP_REQ_NOT_MATCH, SIP_REQ_LOOP_DETECTED, /* multiple incoming requests with same call-id but different branch parameters have been detected */ SIP_REQ_FORKED, /* An outgoing request has been forked as result of receiving two differing 200ok responses. */ }; /* * \brief Match a incoming Request/Response to a dialog * * \retval enum match_req_res indicating if the dialog matches the arg */ static enum match_req_res match_req_to_dialog(struct sip_pvt *sip_pvt_ptr, struct match_req_args *arg) { const char *init_ruri = NULL; if (sip_pvt_ptr->initreq.headers) { init_ruri = REQ_OFFSET_TO_STR(&sip_pvt_ptr->initreq, rlpart2); } /* * Match Tags and call-id to Dialog */ if (!ast_strlen_zero(arg->callid) && strcmp(sip_pvt_ptr->callid, arg->callid)) { /* call-id does not match. */ return SIP_REQ_NOT_MATCH; } if (arg->method == SIP_RESPONSE) { /* Verify fromtag of response matches the tag we gave them. */ if (strcmp(arg->fromtag, sip_pvt_ptr->tag)) { /* fromtag from response does not match our tag */ return SIP_REQ_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(sip_pvt_ptr->theirtag) && ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { if (ast_strlen_zero(arg->totag)) { /* missing totag when they already gave us one earlier */ return SIP_REQ_NOT_MATCH; } /* compare the totag of response with the tag we have stored for them */ if (strcmp(arg->totag, sip_pvt_ptr->theirtag)) { /* totag did not match what we had stored for them. */ char invite_branch[32] = { 0, }; if (sip_pvt_ptr->invite_branch) { snprintf(invite_branch, sizeof(invite_branch), "z9hG4bK%08x", (unsigned)sip_pvt_ptr->invite_branch); } /* Forked Request Detection * * If this is a 200ok 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->respid == 200) && !ast_strlen_zero(invite_branch) && !ast_strlen_zero(arg->viabranch) && !strcmp(invite_branch, arg->viabranch)) { return SIP_REQ_FORKED; } /* The totag did not match the one we had stored, and this is not a Forked Request. */ return SIP_REQ_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, sip_pvt_ptr->theirtag)) { /* their tag does not match the one was have stored for them */ return SIP_REQ_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, sip_pvt_ptr->tag))) { /* totag from Request does not match our tag */ return SIP_REQ_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 */ if ((arg->method != SIP_RESPONSE) && /* must be a Request */ ast_strlen_zero(arg->totag) && /* must not have a totag */ (sip_pvt_ptr->init_icseq == arg->seqno) && /* the cseq must be the same as this dialogs initial cseq */ !ast_strlen_zero(sip_pvt_ptr->initviabranch) && /* The dialog must have started with a RFC3261 compliant branch tag */ init_ruri) { /* the dialog must have an initial request uri associated with it */ /* 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, sip_pvt_ptr->initviabranch) || ast_strlen_zero(arg->viasentby) || strcmp(arg->viasentby, sip_pvt_ptr->initviasentby)) { /* At this point, this request does not match this Dialog.*/ /* if methods are different this is just a mismatch */ if ((sip_pvt_ptr->method != arg->method)) { return SIP_REQ_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_REQ_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_REQ_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. */ if ((arg->method != SIP_RESPONSE) && /* Must be a Request type to even begin checking this */ ast_strlen_zero(arg->totag) && /* no totag is present to match */ arg->authentication_present && /* Authentication header is present in Request */ sip_uri_cmp(init_ruri, arg->ruri)) { /* Compare the Request URI of both the last Request and this new one */ /* Authentication was provided, but the Request URI did not match the last one on this dialog. */ return SIP_REQ_NOT_MATCH; } return SIP_REQ_MATCH; } /*! \brief This function creates a dialog to handle a forked request. This dialog * exists only to properly terminiate the the forked request immediately. */ static void forked_invite_init(struct sip_request *req, const char *new_theirtag, struct sip_pvt *original, struct ast_sockaddr *addr) { struct sip_pvt *p; const char *callid; struct ast_callid *logger_callid; sip_pvt_lock(original); callid = ast_strdupa(original->callid); logger_callid = original->logger_callid; if (logger_callid) { ast_callid_ref(logger_callid); } sip_pvt_unlock(original); p = sip_alloc(callid, addr, 1, SIP_INVITE, req, logger_callid); if (logger_callid) { ast_callid_unref(logger_callid); } if (!p) { return; /* alloc error */ } /* Lock p and original private structures. */ sip_pvt_lock(p); while (sip_pvt_trylock(original)) { /* Can't use DEADLOCK_AVOIDANCE since p is an ao2 object */ sip_pvt_unlock(p); sched_yield(); sip_pvt_lock(p); } p->invitestate = INV_TERMINATED; p->ocseq = original->ocseq; p->branch = original->branch; memcpy(&p->flags, &original->flags, sizeof(p->flags)); copy_request(&p->initreq, &original->initreq); ast_string_field_set(p, theirtag, new_theirtag); ast_string_field_set(p, tag, original->tag); ast_string_field_set(p, uri, original->uri); ast_string_field_set(p, our_contact, original->our_contact); ast_string_field_set(p, fullcontact, original->fullcontact); sip_pvt_unlock(original); parse_ok_contact(p, req); build_route(p, req, 1, 0); transmit_request(p, SIP_ACK, p->ocseq, XMIT_UNRELIABLE, TRUE); transmit_request(p, SIP_BYE, 0, XMIT_RELIABLE, TRUE); pvt_set_needdestroy(p, "forked request"); /* this dialog will terminate once the BYE is responed to or times out. */ sip_pvt_unlock(p); dialog_unref(p, "setup forked invite termination"); } /*! \internal * * \brief Locks both pvt and pvt owner if owner is present. * * \note This function gives a ref to pvt->owner if it is present and locked. * This reference must be decremented after pvt->owner is unlocked. * * \note This function will never give you up, * \note This function will never let you down. * \note This function will run around and desert you. * * \pre pvt is not locked * \post pvt is locked * \post pvt->owner is locked and its reference count is increased (if pvt->owner is not NULL) * * \returns a pointer to the locked and reffed pvt->owner channel if it exists. */ static 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 */ sip_pvt_lock(pvt); chan = pvt->owner; if (chan) { /* 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 */ sip_pvt_unlock(pvt); /* Look, no deadlock avoidance, hooray! */ ast_channel_lock(chan); sip_pvt_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); sip_pvt_unlock(pvt); } /* If owner exists, it is locked and reffed */ return pvt->owner; } /*! \brief Set the owning channel on the \ref sip_pvt object */ static void sip_set_owner(struct sip_pvt *p, struct ast_channel *chan) { p->owner = chan; if (p->rtp) { ast_rtp_instance_set_channel_id(p->rtp, p->owner ? ast_channel_uniqueid(p->owner) : ""); } if (p->vrtp) { ast_rtp_instance_set_channel_id(p->vrtp, p->owner ? ast_channel_uniqueid(p->owner) : ""); } if (p->trtp) { ast_rtp_instance_set_channel_id(p->trtp, p->owner ? ast_channel_uniqueid(p->owner) : ""); } } /*! \brief find or create a dialog structure for an incoming SIP message. * Connect incoming SIP message to current dialog or create new dialog structure * Returns a reference to the sip_pvt object, remember to give it back once done. * Called by handle_request_do */ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *addr, const int intended_method) { char totag[128]; char fromtag[128]; const char *callid = sip_get_header(req, "Call-ID"); const char *from = sip_get_header(req, "From"); const char *to = sip_get_header(req, "To"); const char *cseq = sip_get_header(req, "Cseq"); struct sip_pvt *sip_pvt_ptr; uint32_t seqno; /* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now) */ /* sip_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 (intended_method != SIP_RESPONSE && intended_method != SIP_ACK) { transmit_response_using_temp(callid, addr, 1, intended_method, req, "400 Bad Request"); } return NULL; /* Invalid packet */ } if (sip_cfg.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 (gettag(req, "To", totag, sizeof(totag))) req->has_to_tag = 1; /* Used in handle_request/response */ gettag(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].text , 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) { transmit_response_using_temp(callid, addr, 1, intended_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].text , callid, from ); return NULL; } } if (!sip_cfg.pedanticsipchecking) { struct sip_pvt tmp_dialog = { .callid = callid, }; sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find in dialogs"); if (sip_pvt_ptr) { /* well, if we don't find it-- what IS in there? */ /* Found the call */ return sip_pvt_ptr; } } else { /* in pedantic mode! -- do the fancy search */ struct sip_pvt tmp_dialog = { .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 match_req_args args = { 0, }; int found; struct ao2_iterator *iterator = ao2_t_callback(dialogs, OBJ_POINTER | OBJ_MULTIPLE, dialog_find_multiple, &tmp_dialog, "pedantic ao2_find in dialogs"); struct sip_via *via = NULL; 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 = REQ_OFFSET_TO_STR(req, rlpart2); via = parse_via(sip_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_get_header(req, "Authorization")) || !ast_strlen_zero(sip_get_header(req, "Proxy-Authorization"))) { args.authentication_present = 1; } /* if it is a response, get the response code */ if (req->method == SIP_RESPONSE) { const char* e = ast_skip_blanks(REQ_OFFSET_TO_STR(req, rlpart2)); int respid; if (!ast_strlen_zero(e) && (sscanf(e, "%30d", &respid) == 1)) { args.respid = respid; } } /* Iterate a list of dialogs already matched by Call-id */ while (iterator && (sip_pvt_ptr = ao2_iterator_next(iterator))) { sip_pvt_lock(sip_pvt_ptr); found = match_req_to_dialog(sip_pvt_ptr, &args); sip_pvt_unlock(sip_pvt_ptr); switch (found) { case SIP_REQ_MATCH: ao2_iterator_destroy(iterator); dialog_unref(fork_pvt, "unref fork_pvt"); free_via(via); return sip_pvt_ptr; /* return pvt with ref */ case SIP_REQ_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. */ transmit_response_using_temp(callid, addr, 1, intended_method, req, "482 (Loop Detected)"); dialog_unref(sip_pvt_ptr, "pvt did not match incoming SIP msg, unref from search."); ao2_iterator_destroy(iterator); dialog_unref(fork_pvt, "unref fork_pvt"); free_via(via); return NULL; case SIP_REQ_FORKED: dialog_unref(fork_pvt, "throwing way pvt to fork off of."); fork_pvt = dialog_ref(sip_pvt_ptr, "this pvt has a forked request, save this off to copy information into new dialog\n"); /* fall through */ case SIP_REQ_NOT_MATCH: default: dialog_unref(sip_pvt_ptr, "pvt did not match incoming SIP msg, unref from search"); break; } } if (iterator) { ao2_iterator_destroy(iterator); } /* 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) { forked_invite_init(req, args.totag, fork_pvt, addr); dialog_unref(fork_pvt, "throwing way old forked pvt"); free_via(via); return NULL; } fork_pvt = dialog_unref(fork_pvt, "throwing way pvt to fork off of"); } free_via(via); } /* end of pedantic mode Request/Reponse to Dialog matching */ /* See if the method is capable of creating a dialog */ if (sip_methods[intended_method].can_create == CAN_CREATE_DIALOG) { struct sip_pvt *p = NULL; struct ast_callid *logger_callid = NULL; if (intended_method == SIP_INVITE) { logger_callid = ast_create_callid(); } /* Ok, time to create a new SIP dialog object, a pvt */ if (!(p = sip_alloc(callid, addr, 1, intended_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_alloc. Without a dialog we can't retransmit and handle ACKs and all that, but at least send an error message. Sorry, we apologize for the inconvienience */ transmit_response_using_temp(callid, addr, 1, intended_method, req, "500 Server internal error"); ast_debug(4, "Failed allocating SIP dialog, sending 500 Server internal error and giving up\n"); } /* If we created an ast_callid for an invite, we need to free it now. */ if (logger_callid) { ast_callid_unref(logger_callid); } return p; /* can be NULL */ } else if( sip_methods[intended_method].can_create == CAN_CREATE_DIALOG_UNSUPPORTED_METHOD) { /* A method we do not support, let's take it on the volley */ transmit_response_using_temp(callid, addr, 1, intended_method, req, "501 Method Not Implemented"); ast_debug(2, "Got a request with unsupported SIP method.\n"); } else if (intended_method != SIP_RESPONSE && intended_method != SIP_ACK) { /* This is a request outside of a dialog that we don't know about */ transmit_response_using_temp(callid, addr, 1, intended_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 (intended_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; } /*! \brief create sip_registry object from register=> line in sip.conf and link into reg container */ static int sip_register(const char *value, int lineno) { struct sip_registry *reg; reg = ao2_t_find(registry_list, value, OBJ_SEARCH_KEY, "check for existing registry"); if (reg) { ao2_t_ref(reg, -1, "throw away found registry"); return 0; } if (!(reg = ao2_t_alloc(sizeof(*reg), 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(reg, 256)) { ao2_t_ref(reg, -1, "failed to string_field_init, drop reg"); return -1; } ast_string_field_set(reg, configvalue, value); if (sip_parse_register_line(reg, default_expiry, value, lineno)) { ao2_t_ref(reg, -1, "failure to parse, unref the reg pointer"); return -1; } /* set default expiry if necessary */ if (reg->refresh && !reg->expiry && !reg->configured_expiry) { reg->refresh = reg->expiry = reg->configured_expiry = default_expiry; } ao2_t_link(registry_list, reg, "link reg to registry_list"); ao2_t_ref(reg, -1, "unref the reg pointer"); return 0; } /*! \brief Parse mwi=> line in sip.conf and add to list */ static int sip_subscribe_mwi(const char *value, int lineno) { struct sip_subscription_mwi *mwi; int portnum = 0; enum ast_transport transport = AST_TRANSPORT_UDP; char buf[256] = ""; char *username = NULL, *hostname = NULL, *secret = NULL, *authuser = NULL, *porta = NULL, *mailbox = NULL; if (!value) { return -1; } ast_copy_string(buf, value, sizeof(buf)); username = buf; if ((hostname = strrchr(buf, '@'))) { *hostname++ = '\0'; } else { return -1; } if ((secret = strchr(username, ':'))) { *secret++ = '\0'; if ((authuser = strchr(secret, ':'))) { *authuser++ = '\0'; } } if ((mailbox = strchr(hostname, '/'))) { *mailbox++ = '\0'; } if (ast_strlen_zero(username) || ast_strlen_zero(hostname) || ast_strlen_zero(mailbox)) { ast_log(LOG_WARNING, "Format for MWI subscription is user[:secret[:authuser]]@host[:port]/mailbox at line %d\n", lineno); return -1; } if ((porta = strchr(hostname, ':'))) { *porta++ = '\0'; if (!(portnum = atoi(porta))) { ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno); return -1; } } if (!(mwi = ao2_t_alloc(sizeof(*mwi), sip_subscribe_mwi_destroy, "allocate an mwi struct"))) { return -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); mwi->resub = -1; mwi->portno = portnum; mwi->transport = transport; ao2_t_link(subscription_mwi_list, mwi, "link new mwi object"); ao2_t_ref(mwi, -1, "unref to match ao2_t_alloc"); return 0; } static void mark_method_allowed(unsigned int *allowed_methods, enum sipmethod method) { (*allowed_methods) |= (1 << method); } static void mark_method_unallowed(unsigned int *allowed_methods, enum sipmethod method) { (*allowed_methods) &= ~(1 << method); } /*! \brief Check if method is allowed for a device or a dialog */ static int is_method_allowed(unsigned int *allowed_methods, enum sipmethod method) { return ((*allowed_methods) >> method) & 1; } static void mark_parsed_methods(unsigned int *methods, char *methods_str) { char *method; for (method = strsep(&methods_str, ","); !ast_strlen_zero(method); method = strsep(&methods_str, ",")) { int id = find_sip_method(ast_skip_blanks(method)); if (id == SIP_UNKNOWN) { continue; } mark_method_allowed(methods, id); } } /*! * \brief 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. * \param req The SIP request we are parsing * \retval The methods allowed */ static unsigned int parse_allowed_methods(struct sip_request *req) { char *allow = ast_strdupa(sip_get_header(req, "Allow")); unsigned int allowed_methods = SIP_UNKNOWN; if (ast_strlen_zero(allow)) { /* I have witnessed that REGISTER requests from Polycom phones do not * place the phone's allowed methods in an Allow header. Instead, they place the * allowed methods in a methods= parameter in the Contact header. */ char *contact = ast_strdupa(sip_get_header(req, "Contact")); char *methods = strstr(contact, ";methods="); if (ast_strlen_zero(methods)) { /* RFC 3261 states: * * "The absence of an Allow header field MUST NOT be * interpreted to mean that the UA sending the message supports no * methods. Rather, it implies that the UA is not providing any * information on what methods it supports." * * For simplicity, we'll assume that the peer allows all known * SIP methods if they have no Allow header. We can then clear out the necessary * bits if the peer lets us know that we have sent an unsupported method. */ return UINT_MAX; } allow = ast_strip_quoted(methods + 9, "\"", "\""); } mark_parsed_methods(&allowed_methods, allow); return allowed_methods; } /*! A wrapper for parse_allowed_methods 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_PAGE2_RPID_UPDATE flag. * * \param pvt The sip_pvt we are setting the allowed_methods for * \param req The request which we are parsing * \retval The methods alloweded by the sip_pvt */ static unsigned int set_pvt_allowed_methods(struct sip_pvt *pvt, struct sip_request *req) { pvt->allowed_methods = parse_allowed_methods(req); if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_RPID_UPDATE)) { mark_method_allowed(&pvt->allowed_methods, SIP_UPDATE); } pvt->allowed_methods &= ~(pvt->disallowed_methods); return pvt->allowed_methods; } /*! \brief Parse multiline SIP headers into one header This is enabled if pedanticsipchecking is enabled */ static void lws2sws(struct ast_str *data) { char *msgbuf = ast_str_buffer(data); int len = ast_str_strlen(data); int h = 0, t = 0; int lws = 0; for (; h < len;) { /* Eliminate all CRs */ if (msgbuf[h] == '\r') { h++; continue; } /* Check for end-of-line */ if (msgbuf[h] == '\n') { /* Check for end-of-message */ if (h + 1 == len) break; /* Check for a continuation line */ if (msgbuf[h + 1] == ' ' || msgbuf[h + 1] == '\t') { /* Merge continuation line */ h++; continue; } /* Propagate LF and start new line */ msgbuf[t++] = msgbuf[h++]; lws = 0; continue; } if (msgbuf[h] == ' ' || msgbuf[h] == '\t') { if (lws) { h++; continue; } msgbuf[t++] = msgbuf[h++]; lws = 1; continue; } msgbuf[t++] = msgbuf[h++]; if (lws) lws = 0; } msgbuf[t] = '\0'; ast_str_update(data); } /*! \brief Parse a SIP message \note this function is used both on incoming and outgoing packets */ static int parse_request(struct sip_request *req) { char *c = ast_str_buffer(req->data); ptrdiff_t *dst = req->header; int i = 0, lim = SIP_MAX_HEADERS - 1; unsigned int skipping_headers = 0; ptrdiff_t current_header_offset = 0; char *previous_header = ""; req->header[0] = 0; req->headers = -1; /* mark that we are working on the header */ for (; *c; c++) { if (*c == '\r') { /* remove \r */ *c = '\0'; } else if (*c == '\n') { /* end of this line */ *c = '\0'; current_header_offset = (c + 1) - ast_str_buffer(req->data); previous_header = ast_str_buffer(req->data) + dst[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 = 0; } dst[i] = current_header_offset; /* record start of next line */ continue; } if (sipdebug) { 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 */ dst = 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; dst = req->line; i = 0; lim = SIP_MAX_LINES - 1; skipping_headers = 1; } } } dst[i] = current_header_offset; /* 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) + dst[i]; if ((i < lim) && !ast_strlen_zero(previous_header)) { if (sipdebug) { 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 (*c) { ast_log(LOG_WARNING, "Too many lines, skipping <%s>\n", c); } /* Split up the first line parts */ return determine_firstline_parts(req); } /*! \brief Determine whether a SIP message contains an SDP in its body \param req the SIP request to process \return 1 if SDP found, 0 if not found Also updates req->sdp_start and req->sdp_count to indicate where the SDP lives in the message body. */ static int find_sdp(struct sip_request *req) { const char *content_type; const char *content_length; const char *search; char *boundary; unsigned int x; int boundaryisquoted = FALSE; int found_application_sdp = FALSE; int found_end_of_headers = FALSE; content_length = sip_get_header(req, "Content-Length"); if (!ast_strlen_zero(content_length)) { if (sscanf(content_length, "%30u", &x) != 1) { ast_log(LOG_WARNING, "Invalid Content-Length: %s\n", content_length); return 0; } /* Content-Length of zero means there can't possibly be an SDP here, even if the Content-Type says there is */ if (x == 0) return 0; } content_type = sip_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 ? 1 : 0; } /* if it's not multipart/mixed, there cannot be an SDP */ if (strncasecmp(content_type, "multipart/mixed", 15)) return 0; /* 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 0; if (ast_strlen_zero(search)) return 0; /* If the boundary is quoted with ", remove quote */ if (*search == '\"') { search++; boundaryisquoted = 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 (boundaryisquoted) 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 (x = 0; x < (req->lines); x++) { const char *line = REQ_OFFSET_TO_STR(req, line[x]); if (!strncasecmp(line, boundary, strlen(boundary))){ if (found_application_sdp && found_end_of_headers) { req->sdp_count = (x - 1) - req->sdp_start; return 1; } 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 = x; found_end_of_headers = TRUE; } } } if (found_application_sdp && found_end_of_headers) { req->sdp_count = x - req->sdp_start; return TRUE; } return FALSE; } /*! \brief Change hold state for a call */ static void change_hold_state(struct sip_pvt *dialog, struct sip_request *req, int holdstate, int sendonly) { if (sip_cfg.notifyhold && (!holdstate || !ast_test_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD))) { sip_peer_hold(dialog, holdstate); } append_history(dialog, holdstate ? "Hold" : "Unhold", "%s", ast_str_buffer(req->data)); if (!holdstate) { /* Put off remote hold */ ast_clear_flag(&dialog->flags[1], SIP_PAGE2_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(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD); if (sendonly == 1) /* One directional hold (sendonly/recvonly) */ ast_set_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD_ONEDIR); else if (sendonly == 2) /* Inactive stream */ ast_set_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD_INACTIVE); else ast_set_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD_ACTIVE); return; } /*! \internal * \brief Returns whether or not the address is null or ANY / unspecified (0.0.0.0 or ::) * \retval TRUE if the address is null or any * \retval FALSE if the address it not null or any * \note In some circumstances, calls should be placed on hold if either of these conditions exist. */ static int sockaddr_is_null_or_any(const struct ast_sockaddr *addr) { return ast_sockaddr_isnull(addr) || ast_sockaddr_is_any(addr); } /*! \brief Check the media stream list to see if the given type already exists */ static int has_media_stream(struct sip_pvt *p, enum media_type m) { struct offered_media *offer = NULL; AST_LIST_TRAVERSE(&p->offered_media, offer, next) { if (m == offer->type) { return 1; } } return 0; } /*! \brief Process SIP SDP offer, select formats and activate media channels If offer is rejected, we will not change any properties of the call Return 0 on success, a negative value on errors. Must be called after find_sdp(). */ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action) { int res = 0; /* Iterators for SDP parsing */ int start = req->sdp_start; int next = start; int iterator = 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 offered_media *offer; /* 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 *sa = 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 portno = -1; /*!< RTP audio destination port number */ int vportno = -1; /*!< RTP video destination port number */ int tportno = -1; /*!< RTP text destination port number */ int udptlportno = -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; /* Others */ int sendonly = -1; unsigned int numberofports; int last_rtpmap_codec = 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(p); /* START UNKNOWN */ struct ast_str *codec_buf = ast_str_alloca(64); struct ast_format *tmp_fmt; /* END UNKNOWN */ /* Initial check */ if (!p->rtp) { ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n"); res = -1; goto process_sdp_cleanup; } if (!peercapability || !vpeercapability || !tpeercapability || !newpeercapability || !newjointcapability) { res = -1; goto process_sdp_cleanup; } if (ast_rtp_codecs_payloads_initialize(&newaudiortp) || ast_rtp_codecs_payloads_initialize(&newvideortp) || ast_rtp_codecs_payloads_initialize(&newtextrtp)) { res = -1; goto process_sdp_cleanup; } /* Update our last rtprx when we receive an SDP, too */ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */ offered_media_list_destroy(p); /* Scan for the first media stream (m=) line to limit scanning of globals */ nextm = get_sdp_iterate(&next, req, "m"); if (ast_strlen_zero(nextm)) { ast_log(LOG_WARNING, "Insufficient information for SDP (m= not found)\n"); res = -1; goto process_sdp_cleanup; } /* Scan session level SDP parameters (lines before first media stream) */ while ((type = get_sdp_line(&iterator, 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 (!process_sdp_o(value, p)) { res = (p->session_modify == FALSE) ? 0 : -1; goto process_sdp_cleanup; } processed = TRUE; break; case 'c': if (process_sdp_c(value, &sessionsa)) { processed = TRUE; sa = &sessionsa; vsa = sa; tsa = sa; isa = sa; } break; case 'a': if (process_sdp_a_sendonly(value, &sendonly)) { processed = TRUE; } else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) processed = TRUE; else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) processed = TRUE; else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) processed = TRUE; else if (process_sdp_a_image(value, p)) processed = TRUE; if (process_sdp_a_ice(value, p, p->rtp)) { processed = TRUE; } if (process_sdp_a_ice(value, p, p->vrtp)) { processed = TRUE; } if (process_sdp_a_ice(value, p, p->trtp)) { processed = TRUE; } if (process_sdp_a_dtls(value, p, p->rtp)) { processed = TRUE; if (p->srtp) { ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK); } } if (process_sdp_a_dtls(value, p, p->vrtp)) { processed = TRUE; if (p->vsrtp) { ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK); } } if (process_sdp_a_dtls(value, p, p->trtp)) { processed = TRUE; if (p->tsrtp) { ast_set_flag(p->tsrtp, AST_SRTP_CRYPTO_OFFER_OK); } } 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 */ p->novideo = TRUE; p->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; char protocol[18] = {0,}; unsigned int x; struct ast_rtp_engine_dtls *dtls; numberofports = 0; len = -1; start = next; m = nextm; iterator = next; nextm = get_sdp_iterate(&next, req, "m"); if (!(offer = ast_calloc(1, sizeof(*offer)))) { ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer list\n"); res = -1; goto process_sdp_cleanup; } AST_LIST_INSERT_TAIL(&p->offered_media, offer, next); offer->type = SDP_UNKNOWN; /* Check for 'audio' media offer */ if (strncmp(m, "audio ", 6) == 0) { if ((sscanf(m, "audio %30u/%30u %17s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || (sscanf(m, "audio %30u %17s %n", &x, 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 (!(offer->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 process_sdp_cleanup; } /* guaranteed to be exactly the right length */ sprintf(offer->decline_m_line, "m=audio 0 %s %s\r\n", protocol, codecs); if (x == 0) { ast_debug(1, "Ignoring audio media offer because port number is zero\n"); continue; } if (has_media_stream(p, SDP_AUDIO)) { ast_log(LOG_WARNING, "Declining non-primary audio stream: %s\n", m); continue; } /* Check number of ports offered for stream */ if (numberofports > 1) { ast_log(LOG_WARNING, "%u ports offered for audio media, not supported by Asterisk. Will try anyway...\n", numberofports); } if ((!strcmp(protocol, "RTP/SAVPF") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && !ast_test_flag(&p->flags[2], SIP_PAGE3_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 = 1; ast_set_flag(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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 = 1; ast_clear_flag(&p->flags[2], SIP_PAGE3_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 = 1; processed_crypto = 1; if (p->srtp) { ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK); } } else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { secure_audio = 1; } else if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) && (!(dtls = ast_rtp_instance_get_dtls(p->rtp)) || !dtls->active(p->rtp))) { ast_log(LOG_WARNING, "Received UDP/TLS in audio offer but DTLS is not 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; offer->type = SDP_AUDIO; portno = x; /* 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 process_sdp_cleanup; } if (debug) { ast_verbose("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 process_sdp_cleanup; } } /* Check for 'video' media offer */ else if (strncmp(m, "video ", 6) == 0) { if ((sscanf(m, "video %30u/%30u %17s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || (sscanf(m, "video %30u %17s %n", &x, 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 (!(offer->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 process_sdp_cleanup; } /* guaranteed to be exactly the right length */ sprintf(offer->decline_m_line, "m=video 0 %s %s\r\n", protocol, codecs); if (x == 0) { ast_debug(1, "Ignoring video stream offer because port number is zero\n"); continue; } /* Check number of ports offered for stream */ if (numberofports > 1) { ast_log(LOG_WARNING, "%u ports offered for video stream, not supported by Asterisk. Will try anyway...\n", numberofports); } if (has_media_stream(p, 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(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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 = 1; processed_crypto = 1; if (p->vsrtp || (p->vsrtp = ast_sdp_srtp_alloc())) { ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK); } } else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) { secure_video = 1; } else if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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; p->novideo = FALSE; offer->type = SDP_VIDEO; vportno = x; /* 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 process_sdp_cleanup; } if (debug) { ast_verbose("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 process_sdp_cleanup; } } /* Check for 'text' media offer */ else if (strncmp(m, "text ", 5) == 0) { if ((sscanf(m, "text %30u/%30u %17s %n", &x, &numberofports, protocol, &len) == 3 && len > 0) || (sscanf(m, "text %30u %17s %n", &x, 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 (!(offer->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 process_sdp_cleanup; } /* guaranteed to be exactly the right length */ sprintf(offer->decline_m_line, "m=text 0 %s %s\r\n", protocol, codecs); if (x == 0) { ast_debug(1, "Ignoring text stream offer because port number is zero\n"); continue; } /* Check number of ports offered for stream */ if (numberofports > 1) { ast_log(LOG_WARNING, "%u ports offered for text stream, not supported by Asterisk. Will try anyway...\n", numberofports); } if (!strcmp(protocol, "RTP/AVPF") && !ast_test_flag(&p->flags[2], SIP_PAGE3_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(&p->flags[2], SIP_PAGE3_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 (has_media_stream(p, SDP_TEXT)) { ast_log(LOG_WARNING, "Declining non-primary text stream: %s\n", m); continue; } text = TRUE; p->notext = FALSE; offer->type = SDP_TEXT; tportno = x; /* 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 process_sdp_cleanup; } if (debug) { ast_verbose("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 process_sdp_cleanup; } } /* Check for 'image' media offer */ else if (strncmp(m, "image ", 6) == 0) { if (((sscanf(m, "image %30u udptl t38%n", &x, &len) == 1 && len > 0) || (sscanf(m, "image %30u UDPTL t38%n", &x, &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 (!(offer->decline_m_line = ast_malloc(22))) { ast_log(LOG_WARNING, "Failed to allocate memory for SDP offer declination\n"); res = -1; goto process_sdp_cleanup; } /* guaranteed to be exactly the right length */ strcpy(offer->decline_m_line, "m=image 0 udptl t38\r\n"); if (x == 0) { ast_debug(1, "Ignoring image stream offer because port number is zero\n"); continue; } if (initialize_udptl(p)) { ast_log(LOG_WARNING, "Failed to initialize UDPTL, declining image stream\n"); continue; } if (has_media_stream(p, SDP_IMAGE)) { ast_log(LOG_WARNING, "Declining non-primary image stream: %s\n", m); continue; } image = TRUE; if (debug) { ast_verbose("Got T.38 offer in SDP in dialog %s\n", p->callid); } offer->type = SDP_IMAGE; udptlportno = x; if (p->t38.state != T38_ENABLED) { memset(&p->t38.their_parms, 0, sizeof(p->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(p->udptl, UDPTL_ERROR_CORRECTION_NONE); } } else if (sscanf(m, "image %30u %17s t38%n", &x, 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 (!(offer->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 process_sdp_cleanup; } /* guaranteed to be exactly the right length */ sprintf(offer->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 process_sdp_cleanup; } } else { char type[20] = {0,}; if ((sscanf(m, "%19s %30u/%30u %n", type, &x, &numberofports, &len) == 3 && len > 0) || (sscanf(m, "%19s %30u %n", type, &x, &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 (!(offer->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 process_sdp_cleanup; } /* guaranteed to be long enough */ sprintf(offer->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 process_sdp_cleanup; } } /* Media stream specific parameters */ while ((type = get_sdp_line(&iterator, next - 1, req, &value)) != '\0') { int processed = FALSE; switch (type) { case 'c': if (audio) { if (process_sdp_c(value, &audiosa)) { processed = TRUE; sa = &audiosa; } } else if (video) { if (process_sdp_c(value, &videosa)) { processed = TRUE; vsa = &videosa; } } else if (text) { if (process_sdp_c(value, &textsa)) { processed = TRUE; tsa = &textsa; } } else if (image) { if (process_sdp_c(value, &imagesa)) { processed = TRUE; isa = &imagesa; } } break; case 'a': /* Audio specific scanning */ if (audio) { if (process_sdp_a_ice(value, p, p->rtp)) { processed = TRUE; } else if (process_sdp_a_dtls(value, p, p->rtp)) { processed_crypto = TRUE; processed = TRUE; if (p->srtp) { ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK); } } else if (process_sdp_a_sendonly(value, &sendonly)) { processed = TRUE; } else if (!processed_crypto && process_crypto(p, p->rtp, &p->srtp, value)) { processed_crypto = TRUE; processed = TRUE; } else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) { processed = TRUE; } } /* Video specific scanning */ else if (video) { if (process_sdp_a_ice(value, p, p->vrtp)) { processed = TRUE; } else if (process_sdp_a_dtls(value, p, p->vrtp)) { processed_crypto = TRUE; processed = TRUE; if (p->vsrtp) { ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK); } } else if (!processed_crypto && process_crypto(p, p->vrtp, &p->vsrtp, value)) { processed_crypto = TRUE; processed = TRUE; } else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) { processed = TRUE; } } /* Text (T.140) specific scanning */ else if (text) { if (process_sdp_a_ice(value, p, p->trtp)) { processed = TRUE; } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) { processed = TRUE; } else if (!processed_crypto && process_crypto(p, p->trtp, &p->tsrtp, value)) { processed_crypto = TRUE; processed = TRUE; } } /* Image (T.38 FAX) specific scanning */ else if (image) { if (process_sdp_a_image(value, p)) 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 process_sdp_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 process_sdp_cleanup; } } /* Sanity checks */ if (!sa && !vsa && !tsa && !isa) { ast_log(LOG_WARNING, "Insufficient information in SDP (c=)...\n"); res = -1; goto process_sdp_cleanup; } if ((portno == -1) && (vportno == -1) && (tportno == -1) && (udptlportno == -1)) { ast_log(LOG_WARNING, "Failing due to no acceptable offer found\n"); res = -1; goto process_sdp_cleanup; } if (secure_audio && !(p->srtp && (ast_test_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK)))) { ast_log(LOG_WARNING, "Can't provide secure audio requested in SDP offer\n"); res = -1; goto process_sdp_cleanup; } if (!secure_audio && p->srtp) { ast_log(LOG_WARNING, "We are requesting SRTP for audio, but they responded without it!\n"); res = -1; goto process_sdp_cleanup; } if (secure_video && !(p->vsrtp && (ast_test_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK)))) { ast_log(LOG_WARNING, "Can't provide secure video requested in SDP offer\n"); res = -1; goto process_sdp_cleanup; } if (!p->novideo && !secure_video && p->vsrtp) { ast_log(LOG_WARNING, "We are requesting SRTP for video, but they responded without it!\n"); res = -1; goto process_sdp_cleanup; } if (!(secure_audio || secure_video) && ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP)) { ast_log(LOG_WARNING, "Matched device setup to use SRTP, but request was not!\n"); res = -1; goto process_sdp_cleanup; } if (udptlportno == -1) { change_t38_state(p, T38_DISABLED); } /* 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(p->caps, newpeercapability, newjointcapability); if (!ast_format_cap_count(newjointcapability) && udptlportno == -1) { ast_log(LOG_NOTICE, "No compatible codecs, not accepting this offer!\n"); /* Do NOT Change current setting */ res = -1; goto process_sdp_cleanup; } newnoncodeccapability = p->noncodeccapability & peernoncodeccapability; if (debug) { /* shame on whoever coded this.... */ struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *vpeer_buf = ast_str_alloca(64); struct ast_str *tpeer_buf = ast_str_alloca(64); struct ast_str *joint_buf = ast_str_alloca(64); ast_verbose("Capabilities: us - %s, peer - audio=%s/video=%s/text=%s, combined - %s\n", ast_format_cap_get_names(p->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)); } if (debug) { struct ast_str *s1 = ast_str_alloca(SIPBUFSIZE); struct ast_str *s2 = ast_str_alloca(SIPBUFSIZE); struct ast_str *s3 = ast_str_alloca(SIPBUFSIZE); ast_verbose("Non-codec capabilities (dtmf): us - %s, peer - %s, combined - %s\n", ast_rtp_lookup_mime_multiple2(s1, NULL, p->noncodeccapability, 0, 0), ast_rtp_lookup_mime_multiple2(s2, NULL, peernoncodeccapability, 0, 0), ast_rtp_lookup_mime_multiple2(s3, NULL, newnoncodeccapability, 0, 0)); } if (portno != -1 || vportno != -1 || tportno != -1) { /* We are now ready to change the sip session and RTP structures with the offered codecs, since they are acceptable */ ast_format_cap_remove_by_type(p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->jointcaps, newjointcapability, AST_MEDIA_TYPE_UNKNOWN); /* Our joint codec profile for this call */ ast_format_cap_remove_by_type(p->peercaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->peercaps, newpeercapability, AST_MEDIA_TYPE_UNKNOWN); /* The other side's capability in latest offer */ p->jointnoncodeccapability = newnoncodeccapability; /* DTMF capabilities */ /* respond with single most preferred joint codec, limiting the other side's choice */ if (ast_test_flag(&p->flags[1], SIP_PAGE2_PREFERRED_CODEC)) { unsigned int framing; tmp_fmt = ast_format_cap_get_format(p->jointcaps, 0); framing = ast_format_cap_get_format_framing(p->jointcaps, tmp_fmt); ast_format_cap_remove_by_type(p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(p->jointcaps, tmp_fmt, framing); ao2_ref(tmp_fmt, -1); } } /* Setup audio address and port */ if (p->rtp) { if (sa && portno > 0) { start_ice(p->rtp, (req->method != SIP_RESPONSE) ? 0 : 1); ast_sockaddr_set_port(sa, portno); ast_rtp_instance_set_remote_address(p->rtp, sa); if (debug) { ast_verbose("Peer audio RTP is at port %s\n", ast_sockaddr_stringify(sa)); } ast_rtp_codecs_payloads_copy(&newaudiortp, ast_rtp_instance_get_codecs(p->rtp), p->rtp); /* Ensure RTCP is enabled since it may be inactive if we're coming back from a T.38 session */ ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 1); /* Ensure audio RTCP reads are enabled */ if (p->owner) { ast_channel_set_fd(p->owner, 1, ast_rtp_instance_fd(p->rtp, 1)); } if (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO) { ast_clear_flag(&p->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(&p->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(p->rtp, AST_RTP_PROPERTY_DTMF, 1); ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&p->flags[1], SIP_PAGE2_RFC2833_COMPENSATE)); } else { ast_set_flag(&p->flags[0], SIP_DTMF_INBAND); } } } else if (udptlportno > 0) { if (debug) ast_verbose("Got T.38 Re-invite without audio. Keeping RTP active during T.38 session.\n"); /* Prevent audio RTCP reads */ if (p->owner) { ast_channel_set_fd(p->owner, 1, -1); } /* Silence RTCP while audio RTP is inactive */ ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 0); } else { ast_rtp_instance_stop(p->rtp); if (debug) ast_verbose("Peer doesn't provide audio\n"); } } /* Setup video address and port */ if (p->vrtp) { if (vsa && vportno > 0) { start_ice(p->vrtp, (req->method != SIP_RESPONSE) ? 0 : 1); ast_sockaddr_set_port(vsa, vportno); ast_rtp_instance_set_remote_address(p->vrtp, vsa); if (debug) { ast_verbose("Peer video RTP is at port %s\n", ast_sockaddr_stringify(vsa)); } ast_rtp_codecs_payloads_copy(&newvideortp, ast_rtp_instance_get_codecs(p->vrtp), p->vrtp); } else { ast_rtp_instance_stop(p->vrtp); if (debug) ast_verbose("Peer doesn't provide video\n"); } } /* Setup text address and port */ if (p->trtp) { if (tsa && tportno > 0) { start_ice(p->trtp, (req->method != SIP_RESPONSE) ? 0 : 1); ast_sockaddr_set_port(tsa, tportno); ast_rtp_instance_set_remote_address(p->trtp, tsa); if (debug) { ast_verbose("Peer T.140 RTP is at port %s\n", ast_sockaddr_stringify(tsa)); } if (ast_format_cap_iscompatible_format(p->jointcaps, ast_format_t140_red) != AST_FORMAT_CMP_NOT_EQUAL) { p->red = 1; ast_rtp_red_init(p->trtp, 300, red_data_pt, 2); } else { p->red = 0; } ast_rtp_codecs_payloads_copy(&newtextrtp, ast_rtp_instance_get_codecs(p->trtp), p->trtp); } else { ast_rtp_instance_stop(p->trtp); if (debug) ast_verbose("Peer doesn't provide T.140\n"); } } /* Setup image address and port */ if (p->udptl) { if (isa && udptlportno > 0) { if (ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP) && ast_test_flag(&p->flags[1], SIP_PAGE2_UDPTL_DESTINATION)) { ast_rtp_instance_get_remote_address(p->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, udptlportno); ast_udptl_set_peer(p->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(p->udptl)) { /* setting to zero will force a default if none was provided by the SDP */ ast_udptl_set_far_max_datagram(p->udptl, 0); } /* Remote party offers T38, we need to update state */ if ((t38action == SDP_T38_ACCEPT) && (p->t38.state == T38_LOCAL_REINVITE)) { change_t38_state(p, T38_ENABLED); } else if ((t38action == SDP_T38_INITIATE) && p->owner && p->lastinvite) { change_t38_state(p, 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(&p->flags[1], SIP_PAGE2_FAX_DETECT_T38)) { ast_channel_lock(p->owner); if (strcmp(ast_channel_exten(p->owner), "fax")) { const char *target_context = S_OR(ast_channel_macrocontext(p->owner), ast_channel_context(p->owner)); ast_channel_unlock(p->owner); if (ast_exists_extension(p->owner, target_context, "fax", 1, S_COR(ast_channel_caller(p->owner)->id.number.valid, ast_channel_caller(p->owner)->id.number.str, NULL))) { ast_verb(2, "Redirecting '%s' to fax extension due to peer T.38 re-INVITE\n", ast_channel_name(p->owner)); pbx_builtin_setvar_helper(p->owner, "FAXEXTEN", ast_channel_exten(p->owner)); if (ast_async_goto(p->owner, target_context, "fax", 1)) { ast_log(LOG_NOTICE, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(p->owner), target_context); } } else { ast_log(LOG_NOTICE, "T.38 re-INVITE detected but no fax extension\n"); } } else { ast_channel_unlock(p->owner); } } } } else { change_t38_state(p, T38_DISABLED); ast_udptl_stop(p->udptl); if (debug) ast_debug(1, "Peer doesn't provide T.38 UDPTL\n"); } } if ((portno == -1) && (p->t38.state != T38_DISABLED) && (p->t38.state != T38_REJECTED)) { ast_debug(3, "Have T.38 but no audio, accepting offer anyway\n"); res = 0; goto process_sdp_cleanup; } /* Ok, we're going with this offer */ ast_debug(2, "We're settling with these formats: %s\n", ast_format_cap_get_names(p->jointcaps, &codec_buf)); if (!p->owner) { /* There's no open channel owning us so we can return here. For a re-invite or so, we proceed */ res = 0; goto process_sdp_cleanup; } ast_debug(4, "We have an owner, now see if we need to change this call\n"); if (ast_format_cap_has_type(p->jointcaps, AST_MEDIA_TYPE_AUDIO)) { struct ast_format_cap *caps; unsigned int framing; if (debug) { struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *joint_buf = ast_str_alloca(64); ast_debug(1, "Setting native formats after processing SDP. peer joint formats %s, old nativeformats %s\n", ast_format_cap_get_names(p->jointcaps, &joint_buf), ast_format_cap_get_names(ast_channel_nativeformats(p->owner), &cap_buf)); } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { tmp_fmt = ast_format_cap_get_format(p->jointcaps, 0); framing = ast_format_cap_get_format_framing(p->jointcaps, tmp_fmt); ast_format_cap_append(caps, tmp_fmt, 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(p->owner, caps); ao2_ref(caps, -1); ao2_ref(tmp_fmt, -1); } ast_set_read_format(p->owner, ast_channel_readformat(p->owner)); ast_set_write_format(p->owner, ast_channel_writeformat(p->owner)); } if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) && (!ast_sockaddr_isnull(sa) || !ast_sockaddr_isnull(vsa) || !ast_sockaddr_isnull(tsa) || !ast_sockaddr_isnull(isa)) && (!sendonly || sendonly == -1)) { if (!ast_test_flag(&p->flags[2], SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL)) { ast_queue_unhold(p->owner); } /* Activate a re-invite */ ast_queue_frame(p->owner, &ast_null_frame); change_hold_state(p, req, FALSE, sendonly); } else if ((sockaddr_is_null_or_any(sa) && sockaddr_is_null_or_any(vsa) && sockaddr_is_null_or_any(tsa) && sockaddr_is_null_or_any(isa)) || (sendonly && sendonly != -1)) { if (!ast_test_flag(&p->flags[2], SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL)) { ast_queue_hold(p->owner, p->mohsuggest); } if (sendonly) ast_rtp_instance_stop(p->rtp); /* RTCP needs to go ahead, even if we're on hold!!! */ /* Activate a re-invite */ ast_queue_frame(p->owner, &ast_null_frame); change_hold_state(p, req, TRUE, sendonly); } process_sdp_cleanup: if (res) { offered_media_list_destroy(p); } 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 process_sdp_o(const char *o, struct sip_pvt *p) { char *o_copy; char *token; int64_t rua_version; /* 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. */ p->session_modify = TRUE; if (ast_strlen_zero(o)) { ast_log(LOG_WARNING, "SDP syntax error. SDP without an o= line\n"); return FALSE; } 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, " "); /* Skip session-id */ if (!o_copy) { ast_log(LOG_WARNING, "SDP syntax error in o= line session-id\n"); return FALSE; } token = strsep(&o_copy, " "); /* Version */ if (!o_copy) { ast_log(LOG_WARNING, "SDP syntax error in o= line\n"); return FALSE; } if (!sscanf(token, "%30" SCNd64, &rua_version)) { ast_log(LOG_WARNING, "SDP syntax error in o= line version\n"); return FALSE; } /* 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 * 3) otherwise, if the new SDP version number is higher than the * old one, we accept it * 4) otherwise, if this SDP is in response to us requesting a switch * to T.38, we accept the SDP, but also generate a warning message * that this peer should have the 'ignoresdpversion' option set, * because it is not following the SDP offer/answer RFC; if we did * not request a switch to T.38, then we stop parsing the SDP, as it * has not changed from the previous version */ if (ast_test_flag(&p->flags[1], SIP_PAGE2_IGNORESDPVERSION) || (p->sessionversion_remote < 0) || (p->sessionversion_remote < rua_version)) { p->sessionversion_remote = rua_version; } else { if (p->t38.state == T38_LOCAL_REINVITE) { p->sessionversion_remote = rua_version; ast_log(LOG_WARNING, "Call %s responded to our T.38 reinvite without changing SDP version; 'ignoresdpversion' should be set for this peer.\n", p->callid); } else { p->session_modify = FALSE; ast_debug(2, "Call %s responded to our reinvite without changing SDP version; ignoring SDP.\n", p->callid); return FALSE; } } return TRUE; } static int process_sdp_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 process_sdp_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 process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance) { struct ast_rtp_engine_ice *ice; int found = FALSE; char ufrag[256], pwd[256], foundation[32], 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: %31s %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) { 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 process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance) { struct ast_rtp_engine_dtls *dtls; int found = FALSE; char value[256], hash[32]; if (!instance || !p->dtls_cfg.enabled || !(dtls = ast_rtp_instance_get_dtls(instance))) { return found; } if (sscanf(a, "setup: %255s", value) == 1) { found = TRUE; if (!strcasecmp(value, "active")) { dtls->set_setup(instance, AST_RTP_DTLS_SETUP_ACTIVE); } else if (!strcasecmp(value, "passive")) { dtls->set_setup(instance, AST_RTP_DTLS_SETUP_PASSIVE); } else if (!strcasecmp(value, "actpass")) { dtls->set_setup(instance, AST_RTP_DTLS_SETUP_ACTPASS); } else if (!strcasecmp(value, "holdconn")) { dtls->set_setup(instance, AST_RTP_DTLS_SETUP_HOLDCONN); } else { ast_log(LOG_WARNING, "Unsupported setup attribute value '%s' received on dialog '%s'\n", value, p->callid); } } else if (sscanf(a, "connection: %255s", value) == 1) { found = TRUE; if (!strcasecmp(value, "new")) { dtls->reset(instance); } else if (!strcasecmp(value, "existing")) { /* Since they want to just use what already exists we go on as if nothing happened */ } else { ast_log(LOG_WARNING, "Unsupported connection attribute value '%s' received on dialog '%s'\n", value, p->callid); } } else if (sscanf(a, "fingerprint: %31s %255s", hash, value) == 2) { found = TRUE; if (!strcasecmp(hash, "sha-1")) { dtls->set_fingerprint(instance, AST_RTP_DTLS_HASH_SHA1, value); } else if (!strcasecmp(hash, "sha-256")) { dtls->set_fingerprint(instance, AST_RTP_DTLS_HASH_SHA256, value); } else { ast_log(LOG_WARNING, "Unsupported fingerprint hash type '%s' received on dialog '%s'\n", hash, p->callid); } } return found; } static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec) { int found = FALSE; unsigned int codec; char mimeSubtype[128]; char fmtp_string[256]; unsigned int sample_rate; int debug = sip_debug_test_pvt(p); 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 && p->autoframing) { ast_debug(1, "Setting framing to %ld\n", framing); ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), framing); } found = TRUE; } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { /* We have a rtpmap to handle */ if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mimeSubtype, ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0, sample_rate))) { if (debug) ast_verbose("Found audio description format %s for ID %u\n", mimeSubtype, codec); //found_rtpmap_codecs[last_rtpmap_codec] = codec; (*last_rtpmap_codec)++; found = TRUE; } else { ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); if (debug) ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); } } else { if (debug) ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, 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_siren7) == AST_FORMAT_CMP_EQUAL) { if (sscanf(fmtp_string, "bitrate=%30u", &bit_rate) == 1) { if (bit_rate != 32000) { ast_log(LOG_WARNING, "Got Siren7 offer at %u bps, but only 32000 bps supported; ignoring.\n", bit_rate); ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); } else { found = TRUE; } } } else if (ast_format_cmp(format, ast_format_siren14) == AST_FORMAT_CMP_EQUAL) { if (sscanf(fmtp_string, "bitrate=%30u", &bit_rate) == 1) { if (bit_rate != 48000) { ast_log(LOG_WARNING, "Got Siren14 offer at %u bps, but only 48000 bps supported; ignoring.\n", bit_rate); ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); } else { found = TRUE; } } } else 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 process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec) { int found = FALSE; unsigned int codec; char mimeSubtype[128]; unsigned int sample_rate; int debug = sip_debug_test_pvt(p); char fmtp_string[256]; if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { /* We have a rtpmap to handle */ if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { /* Note: should really look at the '#chans' params too */ if (!strncasecmp(mimeSubtype, "H26", 3) || !strncasecmp(mimeSubtype, "MP4", 3) || !strncasecmp(mimeSubtype, "VP8", 3)) { if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mimeSubtype, 0, sample_rate))) { if (debug) ast_verbose("Found video description format %s for ID %u\n", mimeSubtype, codec); //found_rtpmap_codecs[last_rtpmap_codec] = codec; (*last_rtpmap_codec)++; found = TRUE; } else { ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); if (debug) ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); } } } else { if (debug) ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, 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); } } return found; } static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec) { int found = FALSE; unsigned int codec; char mimeSubtype[128]; unsigned int sample_rate; char *red_cp; int debug = sip_debug_test_pvt(p); if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { /* We have a rtpmap to handle */ if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { if (!strncasecmp(mimeSubtype, "T140", 4)) { /* Text */ if (p->trtp) { /* ast_verbose("Adding t140 mimeSubtype to textrtp struct\n"); */ ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); found = TRUE; } } else if (!strncasecmp(mimeSubtype, "RED", 3)) { /* Text with Redudancy */ if (p->trtp) { ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); sprintf(red_fmtp, "fmtp:%u ", codec); if (debug) ast_verbose("RED submimetype has payload type: %u\n", codec); found = TRUE; } } } else { if (debug) ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); } } else if (!strncmp(a, red_fmtp, strlen(red_fmtp))) { /* 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(red_cp, "/"); while (red_cp && (*red_num_gen)++ < AST_RED_MAX_GENERATION) { sscanf(red_cp, "%30u", (unsigned *)&red_data_pt[*red_num_gen]); red_cp = strtok(NULL, "/"); } red_cp = red_fmtp; found = TRUE; } return found; } static int process_sdp_a_image(const char *a, struct sip_pvt *p) { int found = FALSE; char s[256]; unsigned int x; char *attrib = ast_strdupa(a); char *pos; if (initialize_udptl(p)) { 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). */ for (pos = attrib; *pos; ++pos) { *pos = tolower(*pos); } 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: p->t38.their_parms.rate = AST_T38_RATE_14400; break; case 12000: p->t38.their_parms.rate = AST_T38_RATE_12000; break; case 9600: p->t38.their_parms.rate = AST_T38_RATE_9600; break; case 7200: p->t38.their_parms.rate = AST_T38_RATE_7200; break; case 4800: p->t38.their_parms.rate = AST_T38_RATE_4800; break; case 2400: p->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); p->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) p->t38_maxdatagram >= 0) && ((unsigned int) p->t38_maxdatagram > x)) { ast_debug(1, "Overriding T38FaxMaxDatagram '%u' with '%d'\n", x, p->t38_maxdatagram); x = p->t38_maxdatagram; } ast_debug(3, "FaxMaxDatagram: %u\n", x); ast_udptl_set_far_max_datagram(p->udptl, x); found = TRUE; } else if ((strncmp(attrib, "t38faxfillbitremoval", sizeof("t38faxfillbitremoval") - 1) == 0)) { if (sscanf(attrib, "t38faxfillbitremoval:%30u", &x) == 1) { ast_debug(3, "FillBitRemoval: %u\n", x); if (x == 1) { p->t38.their_parms.fill_bit_removal = TRUE; } } else { ast_debug(3, "FillBitRemoval\n"); p->t38.their_parms.fill_bit_removal = TRUE; } found = TRUE; } else if ((strncmp(attrib, "t38faxtranscodingmmr", sizeof("t38faxtranscodingmmr") - 1) == 0)) { if (sscanf(attrib, "t38faxtranscodingmmr:%30u", &x) == 1) { ast_debug(3, "Transcoding MMR: %u\n", x); if (x == 1) { p->t38.their_parms.transcoding_mmr = TRUE; } } else { ast_debug(3, "Transcoding MMR\n"); p->t38.their_parms.transcoding_mmr = TRUE; } found = TRUE; } else if ((strncmp(attrib, "t38faxtranscodingjbig", sizeof("t38faxtranscodingjbig") - 1) == 0)) { if (sscanf(attrib, "t38faxtranscodingjbig:%30u", &x) == 1) { ast_debug(3, "Transcoding JBIG: %u\n", x); if (x == 1) { p->t38.their_parms.transcoding_jbig = TRUE; } } else { ast_debug(3, "Transcoding JBIG\n"); p->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")) p->t38.their_parms.rate_management = AST_T38_RATE_MANAGEMENT_LOCAL_TCF; else if (!strcasecmp(s, "transferredTCF")) p->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(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY); } else if (!strcasecmp(s, "t38UDPFEC")) { ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC); } else { ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE); } found = TRUE; } return found; } /*! \brief 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. */ static int add_supported(struct sip_pvt *pvt, struct sip_request *req) { char supported_value[SIPBUFSIZE]; int res; sprintf(supported_value, "replaces%s%s", (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) ? ", timer" : "", ast_test_flag(&pvt->flags[0], SIP_USEPATH) ? ", path" : ""); res = add_header(req, "Supported", supported_value); return res; } /*! \brief Add header to SIP message */ static int add_header(struct sip_request *req, const char *var, const char *value) { if (req->headers == SIP_MAX_HEADERS) { ast_log(LOG_WARNING, "Out of SIP header space\n"); return -1; } if (req->lines) { ast_log(LOG_WARNING, "Can't add more headers when lines have been added\n"); return -1; } if (sip_cfg.compactheaders) { var = find_alias(var, var); } ast_str_append(&req->data, 0, "%s: %s\r\n", var, value); req->header[req->headers] = ast_str_strlen(req->data); req->headers++; return 0; } /*! * \pre dialog is assumed to be locked while calling this function * \brief Add 'Max-Forwards' header to SIP message */ static int add_max_forwards(struct sip_pvt *dialog, struct sip_request *req) { char clen[10]; snprintf(clen, sizeof(clen), "%d", dialog->maxforwards); return add_header(req, "Max-Forwards", clen); } /*! \brief Add 'Content-Length' header and content to SIP message */ static int finalize_content(struct sip_request *req) { char clen[10]; if (req->lines) { ast_log(LOG_WARNING, "finalize_content() called on a message that has already been finalized\n"); return -1; } snprintf(clen, sizeof(clen), "%zu", ast_str_strlen(req->content)); add_header(req, "Content-Length", clen); 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) ? 1 : 0; return 0; } /*! \brief Add content (not header) to SIP message */ static int 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 -1; } ast_str_append(&req->content, 0, "%s", line); return 0; } /*! \brief Copy one header field from one request to another */ static int copy_header(struct sip_request *req, const struct sip_request *orig, const char *field) { const char *tmp = sip_get_header(orig, field); if (!ast_strlen_zero(tmp)) /* Add what we're responding to */ return add_header(req, field, tmp); ast_log(LOG_NOTICE, "No field '%s' present to copy\n", field); return -1; } /*! \brief Copy all headers from one request to another */ static int copy_all_header(struct sip_request *req, const struct sip_request *orig, const char *field) { int start = 0; int copied = 0; for (;;) { const char *tmp = __get_header(orig, field, &start); if (ast_strlen_zero(tmp)) break; /* Add what we're responding to */ add_header(req, field, tmp); copied++; } return copied ? 0 : -1; } /*! \brief Copy SIP VIA Headers from the request to the response \note 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. \verbatim We always add ;received= to the topmost via header. \endverbatim Received: RFC 3261, rport RFC 3581 */ static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const struct sip_request *orig, const char *field) { int copied = 0; int start = 0; for (;;) { char new[512]; const char *oh = __get_header(orig, field, &start); if (ast_strlen_zero(oh)) break; if (!copied) { /* Only check for empty rport in topmost via header */ char leftmost[512], *others, *rport; /* Only work on leftmost value */ ast_copy_string(leftmost, oh, sizeof(leftmost)); others = strchr(leftmost, ','); if (others) *others++ = '\0'; /* Find ;rport; (empty request) */ rport = strstr(leftmost, ";rport"); if (rport && *(rport+6) == '=') rport = NULL; /* We already have a parameter to rport */ if (((ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT)) || (rport && ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)))) { /* We need to add received port - rport */ char *end; rport = strstr(leftmost, ";rport"); if (rport) { end = strchr(rport + 1, ';'); if (end) memmove(rport, end, strlen(end) + 1); else *rport = '\0'; } /* Add rport to first VIA header if requested */ snprintf(new, sizeof(new), "%s;received=%s;rport=%d%s%s", leftmost, ast_sockaddr_stringify_addr_remote(&p->recv), ast_sockaddr_port(&p->recv), others ? "," : "", others ? others : ""); } else { /* We should *always* add a received to the topmost via */ snprintf(new, sizeof(new), "%s;received=%s%s%s", leftmost, ast_sockaddr_stringify_addr_remote(&p->recv), others ? "," : "", others ? others : ""); } oh = new; /* the header to copy */ } /* else add the following via headers untouched */ add_header(req, field, oh); copied++; } if (!copied) { ast_log(LOG_NOTICE, "No header field '%s' present to copy\n", field); return -1; } return 0; } /*! \brief Add route header into request per learned route */ static void add_route(struct sip_request *req, struct sip_route *route, int skip) { struct ast_str *r; if (sip_route_empty(route)) { return; } if ((r = sip_route_list(route, 0, skip))) { if (ast_str_strlen(r)) { add_header(req, "Route", ast_str_buffer(r)); } ast_free(r); } } /*! \brief 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][;...] \endverbatim * 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. */ static void set_destination(struct sip_pvt *p, const char *uri) { char *trans, *maddr, hostname[256]; const char *h; int hn; int debug=sip_debug_test_pvt(p); int tls_on = FALSE; if (debug) ast_verbose("set_destination: Parsing <%s> for address/port to send to\n", uri); if ((trans = strcasestr(uri, ";transport="))) { trans += strlen(";transport="); if (!strncasecmp(trans, "ws", 2)) { if (debug) ast_verbose("set_destination: URI is for WebSocket, we can't set destination\n"); return; } } /* Find and parse hostname */ h = strchr(uri, '@'); if (h) ++h; else { h = uri; if (!strncasecmp(h, "sip:", 4)) { h += 4; } else if (!strncasecmp(h, "sips:", 5)) { h += 5; tls_on = TRUE; } } hn = strcspn(h, ";>") + 1; if (hn > sizeof(hostname)) hn = sizeof(hostname); ast_copy_string(hostname, h, hn); /* XXX bug here if string has been trimmed to sizeof(hostname) */ h += hn - 1; /*! \todo XXX If we have sip_cfg.srvlookup on, then look for NAPTR/SRV, * otherwise, just look for A records */ if (ast_sockaddr_resolve_first_transport(&p->sa, hostname, 0, p->socket.type)) { ast_log(LOG_WARNING, "Can't find address for host '%s'\n", hostname); return; } /* Got the hostname - but maybe there's a "maddr=" to override address? */ maddr = strstr(h, "maddr="); if (maddr) { int port; maddr += 6; hn = strspn(maddr, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789-.:[]") + 1; if (hn > sizeof(hostname)) hn = sizeof(hostname); ast_copy_string(hostname, maddr, hn); port = ast_sockaddr_port(&p->sa); /*! \todo XXX If we have sip_cfg.srvlookup on, then look for * NAPTR/SRV, otherwise, just look for A records */ if (ast_sockaddr_resolve_first_transport(&p->sa, hostname, PARSE_PORT_FORBID, p->socket.type)) { ast_log(LOG_WARNING, "Can't find address for host '%s'\n", hostname); return; } ast_sockaddr_set_port(&p->sa, port); } if (!ast_sockaddr_port(&p->sa)) { ast_sockaddr_set_port(&p->sa, tls_on ? STANDARD_TLS_PORT : STANDARD_SIP_PORT); } if (debug) { ast_verbose("set_destination: set destination to %s\n", ast_sockaddr_stringify(&p->sa)); } } /*! \brief Initialize SIP response, based on SIP request */ static int init_resp(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))) goto e_return; if (!(resp->content = ast_str_create(SIP_MIN_PACKET))) goto e_free_data; resp->header[0] = 0; ast_str_set(&resp->data, 0, "SIP/2.0 %s\r\n", msg); resp->headers++; return 0; e_free_data: ast_free(resp->data); resp->data = NULL; e_return: return -1; } /*! \brief Initialize SIP request */ static int init_req(struct sip_request *req, int sipmethod, const char *recip) { /* Initialize a request */ memset(req, 0, sizeof(*req)); if (!(req->data = ast_str_create(SIP_MIN_PACKET))) goto e_return; if (!(req->content = ast_str_create(SIP_MIN_PACKET))) goto e_free_data; req->method = sipmethod; req->header[0] = 0; ast_str_set(&req->data, 0, "%s %s SIP/2.0\r\n", sip_methods[sipmethod].text, recip); req->headers++; return 0; e_free_data: ast_free(req->data); req->data = NULL; e_return: return -1; } /*! \brief Deinitialize SIP response/request */ static void deinit_req(struct sip_request *req) { if (req->data) { ast_free(req->data); req->data = NULL; } if (req->content) { ast_free(req->content); req->content = NULL; } } /*! \brief Test if this response needs a contact header */ static inline int resp_needs_contact(const char *msg, enum sipmethod 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) this * function returns 1. * * - RFC 3261 (ACK, BYE, CANCEL, INVITE, OPTIONS, REGISTER) * - RFC 2976 (INFO) * - RFC 3262 (PRACK) * - RFC 3265 (SUBSCRIBE, NOTIFY) * - RFC 3311 (UPDATE) * - RFC 3428 (MESSAGE) * - RFC 3515 (REFER) * - RFC 3903 (PUBLISH) */ 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 1; break; /* 2xx, 3xx, 485 */ case SIP_REGISTER: case SIP_OPTIONS: if (msg[0] == '2' || msg[0] == '3' || !strncmp(msg, "485", 3)) return 1; break; /* 3xx, 485 */ case SIP_BYE: case SIP_PRACK: case SIP_MESSAGE: case SIP_PUBLISH: if (msg[0] == '3' || !strncmp(msg, "485", 3)) return 1; break; /* 2xx, 3xx, 4xx, 5xx, 6xx */ case SIP_REFER: if (msg[0] >= '2' && msg[0] <= '6') return 1; break; /* contact will not be included for everything else */ case SIP_ACK: case SIP_CANCEL: case SIP_INFO: case SIP_PING: default: return 0; } return 0; } /*! \brief Prepare SIP response packet */ static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req) { char newto[256]; const char *ot; init_resp(resp, msg); copy_via_headers(p, resp, req, "Via"); if (msg[0] == '1' || msg[0] == '2') copy_all_header(resp, req, "Record-Route"); copy_header(resp, req, "From"); ot = sip_get_header(req, "To"); if (!strcasestr(ot, "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(p->theirtag) && ast_test_flag(&p->flags[0], SIP_OUTGOING)) snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->theirtag); else if (p->tag && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->tag); else ast_copy_string(newto, ot, sizeof(newto)); ot = newto; } add_header(resp, "To", ot); copy_header(resp, req, "Call-ID"); copy_header(resp, req, "CSeq"); if (!ast_strlen_zero(global_useragent)) add_header(resp, "Server", global_useragent); add_header(resp, "Allow", ALLOWED_METHODS); add_supported(p, resp); /* If this is an invite, add Session-Timers related headers if the feature is active for this session */ if (p->method == SIP_INVITE && p->stimer && p->stimer->st_active == TRUE) { char se_hdr[256]; snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval, p->stimer->st_ref == SESSION_TIMER_REFRESHER_US ? "uas" : "uac"); add_header(resp, "Session-Expires", se_hdr); /* 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 (p->stimer->st_ref == SESSION_TIMER_REFRESHER_THEM || (p->stimer->st_ref == SESSION_TIMER_REFRESHER_US && p->stimer->st_active_peer_ua == TRUE)) { resp->reqsipoptions |= SIP_OPT_TIMER; } } if (msg[0] == '2' && (p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_PUBLISH)) { /* For registration responses, we also need expiry and contact info */ add_expires(resp, p->expiry); if (p->expiry) { /* Only add contact if we have an expiry time */ char contact[SIPBUFSIZE]; const char *contact_uri = p->method == SIP_SUBSCRIBE ? p->our_contact : p->fullcontact; char *brackets = strchr(contact_uri, '<'); snprintf(contact, sizeof(contact), "%s%s%s;expires=%d", brackets ? "" : "<", contact_uri, brackets ? "" : ">", p->expiry); add_header(resp, "Contact", contact); /* Not when we unregister */ } if (p->method == SIP_REGISTER && ast_test_flag(&p->flags[0], SIP_USEPATH)) { copy_header(resp, req, "Path"); } } else if (!ast_strlen_zero(p->our_contact) && resp_needs_contact(msg, p->method)) { add_header(resp, "Contact", p->our_contact); } if (!ast_strlen_zero(p->url)) { add_header(resp, "Access-URL", p->url); ast_string_field_set(p, 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 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). */ p->sa = p->recv; if (process_via(p, req)) { ast_log(LOG_WARNING, "error processing via header, will send response to originating address\n"); } return 0; } /*! \brief Initialize a SIP request message (not the initial one in a dialog) */ static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, uint32_t seqno, int newbranch) { struct sip_request *orig = &p->initreq; char stripped[80]; char tmp[80]; char newto[256]; const char *c; const char *ot, *of; int is_strict = FALSE; /*!< Strict routing flag */ int is_outbound = ast_test_flag(&p->flags[0], SIP_OUTGOING); /* Session direction */ snprintf(p->lastmsg, sizeof(p->lastmsg), "Tx: %s", sip_methods[sipmethod].text); if (!seqno) { p->ocseq++; seqno = p->ocseq; } /* A CANCEL must have the same branch as the INVITE that it is canceling. */ if (sipmethod == SIP_CANCEL) { p->branch = p->invite_branch; build_via(p); } else if (newbranch && (sipmethod == SIP_INVITE)) { p->branch ^= ast_random(); p->invite_branch = p->branch; build_via(p); } else if (newbranch) { p->branch ^= ast_random(); build_via(p); } /* Check for strict or loose router */ if (sip_route_is_strict(&p->route)) { is_strict = TRUE; if (sipdebug) ast_debug(1, "Strict routing enforced for session %s\n", p->callid); } if (sipmethod == SIP_CANCEL) { c = REQ_OFFSET_TO_STR(&p->initreq, rlpart2); /* Use original URI */ } else if (sipmethod == SIP_ACK) { /* Use URI from Contact: in 200 OK (if INVITE) (we only have the contacturi on INVITEs) */ if (!ast_strlen_zero(p->okcontacturi)) { c = is_strict ? sip_route_first_uri(&p->route) : p->okcontacturi; } else { c = REQ_OFFSET_TO_STR(&p->initreq, rlpart2); } } else if (!ast_strlen_zero(p->okcontacturi)) { /* Use for BYE or REINVITE */ c = is_strict ? sip_route_first_uri(&p->route) : p->okcontacturi; } else if (!ast_strlen_zero(p->uri)) { c = p->uri; } else { char *n; /* We have no URI, use To: or From: header as URI (depending on direction) */ ast_copy_string(stripped, sip_get_header(orig, is_outbound ? "To" : "From"), sizeof(stripped)); n = get_in_brackets(stripped); c = remove_uri_parameters(n); } init_req(req, sipmethod, c); snprintf(tmp, sizeof(tmp), "%u %s", seqno, sip_methods[sipmethod].text); add_header(req, "Via", p->via); /* * Use the learned route set unless this is a CANCEL on 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. */ if (!sip_route_empty(&p->route) && !(sipmethod == SIP_CANCEL || (sipmethod == SIP_ACK && (p->invitestate == INV_COMPLETED || p->invitestate == INV_CANCELLED)))) { if (p->socket.type != AST_TRANSPORT_UDP && p->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(&p->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 { set_destination(p, sip_route_first_uri(&p->route)); } add_route(req, &p->route, is_strict ? 1 : 0); } add_max_forwards(p, req); ot = sip_get_header(orig, "To"); of = sip_get_header(orig, "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(ot, "tag=") && sipmethod != 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(p->theirtag)) snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->theirtag); else if (!is_outbound) snprintf(newto, sizeof(newto), "%s;tag=%s", ot, p->tag); else snprintf(newto, sizeof(newto), "%s", ot); ot = newto; } if (is_outbound) { add_header(req, "From", of); add_header(req, "To", ot); } else { add_header(req, "From", ot); add_header(req, "To", of); } /* Do not add Contact for MESSAGE, BYE and Cancel requests */ if (sipmethod != SIP_BYE && sipmethod != SIP_CANCEL && sipmethod != SIP_MESSAGE) add_header(req, "Contact", p->our_contact); copy_header(req, orig, "Call-ID"); add_header(req, "CSeq", tmp); if (!ast_strlen_zero(global_useragent)) add_header(req, "User-Agent", global_useragent); if (!ast_strlen_zero(p->url)) { add_header(req, "Access-URL", p->url); ast_string_field_set(p, 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. This should only be added in the INVITE transactions, not MESSAGE or REFER or other in-dialog messages. */ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_active_peer_ua == TRUE && sipmethod == SIP_INVITE) { char se_hdr[256]; snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval, p->stimer->st_ref == SESSION_TIMER_REFRESHER_US ? "uac" : "uas"); add_header(req, "Session-Expires", se_hdr); snprintf(se_hdr, sizeof(se_hdr), "%d", st_get_se(p, FALSE)); add_header(req, "Min-SE", se_hdr); } return 0; } /*! \brief Base transmit response function */ static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable) { struct sip_request resp; uint32_t seqno = 0; if (reliable && (sscanf(sip_get_header(req, "CSeq"), "%30u ", &seqno) != 1)) { ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", sip_get_header(req, "CSeq")); return -1; } respprep(&resp, p, msg, req); if (ast_test_flag(&p->flags[0], SIP_SENDRPID) && ast_test_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND) && (!strncmp(msg, "180", 3) || !strncmp(msg, "183", 3))) { ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); add_rpid(&resp, p); } if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { add_cc_call_info_to_response(p, &resp); } /* If we are sending a 302 Redirect we can add a diversion header if the redirect information is set */ if (!strncmp(msg, "302", 3)) { add_diversion(&resp, p); } /* If we are cancelling an incoming invite for some reason, add information about the reason why we are doing this in clear text */ if (p->method == SIP_INVITE && msg[0] != '1') { char buf[20]; if (ast_test_flag(&p->flags[1], SIP_PAGE2_Q850_REASON)) { int hangupcause = 0; if (p->owner && ast_channel_hangupcause(p->owner)) { hangupcause = ast_channel_hangupcause(p->owner); } else if (p->hangupcause) { hangupcause = p->hangupcause; } else { int respcode; if (sscanf(msg, "%30d ", &respcode)) hangupcause = hangup_sip2cause(respcode); } if (hangupcause) { sprintf(buf, "Q.850;cause=%i", hangupcause & 0x7f); add_header(&resp, "Reason", buf); } } if (p->owner && ast_channel_hangupcause(p->owner)) { add_header(&resp, "X-Asterisk-HangupCause", ast_cause2str(ast_channel_hangupcause(p->owner))); snprintf(buf, sizeof(buf), "%d", ast_channel_hangupcause(p->owner)); add_header(&resp, "X-Asterisk-HangupCauseCode", buf); } } return send_response(p, &resp, reliable, seqno); } static int transmit_response_with_sip_etag(struct sip_pvt *p, const char *msg, const struct sip_request *req, struct sip_esc_entry *esc_entry, int need_new_etag) { struct sip_request resp; if (need_new_etag) { create_new_sip_etag(esc_entry, 1); } respprep(&resp, p, msg, req); add_header(&resp, "SIP-ETag", esc_entry->entity_tag); return send_response(p, &resp, 0, 0); } static int temp_pvt_init(void *data) { struct sip_pvt *p = data; p->do_history = 0; /* XXX do we need it ? isn't already all 0 ? */ return ast_string_field_init(p, 512); } static void temp_pvt_cleanup(void *data) { struct sip_pvt *p = data; ast_string_field_free_memory(p); ast_free(data); } /*! \brief Transmit response, no retransmits, using a temporary pvt structure */ static int transmit_response_using_temp(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg) { struct sip_pvt *p = NULL; if (!(p = ast_threadstorage_get(&ts_temp_pvt, sizeof(*p)))) { ast_log(LOG_ERROR, "Failed to get temporary pvt\n"); 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 */ p->method = intended_method; if (!addr) { ast_sockaddr_copy(&p->ourip, &internip); } else { ast_sockaddr_copy(&p->sa, addr); ast_sip_ouraddrfor(&p->sa, &p->ourip, p); } p->branch = ast_random(); make_our_tag(p); p->ocseq = INITIAL_CSEQ; if (useglobal_nat && addr) { ast_copy_flags(&p->flags[0], &global_flags[0], SIP_NAT_FORCE_RPORT); ast_copy_flags(&p->flags[2], &global_flags[2], SIP_PAGE3_NAT_AUTO_RPORT); ast_sockaddr_copy(&p->recv, addr); check_via(p, req); } ast_string_field_set(p, fromdomain, default_fromdomain); p->fromdomainport = default_fromdomainport; build_via(p); ast_string_field_set(p, callid, callid); copy_socket_data(&p->socket, &req->socket); /* Use this temporary pvt structure to send the message */ __transmit_response(p, msg, req, XMIT_UNRELIABLE); /* Free the string fields, but not the pool space */ ast_string_field_init(p, 0); return 0; } /*! \brief Transmit response, no retransmits */ static int transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req) { return __transmit_response(p, msg, req, XMIT_UNRELIABLE); } /*! \brief Transmit response, no retransmits */ static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported) { struct sip_request resp; respprep(&resp, p, msg, req); add_date(&resp); add_header(&resp, "Unsupported", unsupported); return send_response(p, &resp, XMIT_UNRELIABLE, 0); } /*! \brief Transmit 422 response with Min-SE header (Session-Timers) */ static int transmit_response_with_minse(struct sip_pvt *p, const char *msg, const struct sip_request *req, int minse_int) { struct sip_request resp; char minse_str[20]; respprep(&resp, p, msg, req); add_date(&resp); snprintf(minse_str, sizeof(minse_str), "%d", minse_int); add_header(&resp, "Min-SE", minse_str); return send_response(p, &resp, XMIT_UNRELIABLE, 0); } /*! \brief 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 */ static int transmit_response_reliable(struct sip_pvt *p, const char *msg, const struct sip_request *req) { return __transmit_response(p, msg, req, req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL); } /*! \brief Add date header to SIP message */ static void add_date(struct sip_request *req) { char tmp[256]; struct tm tm; time_t t = time(NULL); gmtime_r(&t, &tm); strftime(tmp, sizeof(tmp), "%a, %d %b %Y %T GMT", &tm); add_header(req, "Date", tmp); } /*! \brief Add Expires header to SIP message */ static void add_expires(struct sip_request *req, int expires) { char tmp[32]; snprintf(tmp, sizeof(tmp), "%d", expires); add_header(req, "Expires", tmp); } /*! \brief Append Retry-After header field when transmitting response */ static int transmit_response_with_retry_after(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *seconds) { struct sip_request resp; respprep(&resp, p, msg, req); add_header(&resp, "Retry-After", seconds); return send_response(p, &resp, XMIT_UNRELIABLE, 0); } /*! \brief Add date before transmitting response */ static int transmit_response_with_date(struct sip_pvt *p, const char *msg, const struct sip_request *req) { struct sip_request resp; respprep(&resp, p, msg, req); add_date(&resp); return send_response(p, &resp, XMIT_UNRELIABLE, 0); } /*! \brief Append Accept header, content length before transmitting response */ static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable) { struct sip_request resp; respprep(&resp, p, msg, req); add_header(&resp, "Accept", "application/sdp"); return send_response(p, &resp, reliable, 0); } /*! \brief Append Min-Expires header, content length before transmitting response */ static int transmit_response_with_minexpires(struct sip_pvt *p, const char *msg, const struct sip_request *req, int minexpires) { struct sip_request resp; char tmp[32]; snprintf(tmp, sizeof(tmp), "%d", minexpires); respprep(&resp, p, msg, req); add_header(&resp, "Min-Expires", tmp); return send_response(p, &resp, XMIT_UNRELIABLE, 0); } /*! \brief Respond with authorization request */ static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *nonce, enum xmittype reliable, const char *header, int stale) { struct sip_request resp; char tmp[512]; uint32_t seqno = 0; if (reliable && (sscanf(sip_get_header(req, "CSeq"), "%30u ", &seqno) != 1)) { ast_log(LOG_WARNING, "Unable to determine sequence number from '%s'\n", sip_get_header(req, "CSeq")); return -1; } /* Choose Realm */ get_realm(p, req); /* Stale means that they sent us correct authentication, but based it on an old challenge (nonce) */ snprintf(tmp, sizeof(tmp), "Digest algorithm=MD5, realm=\"%s\", nonce=\"%s\"%s", p->realm, nonce, stale ? ", stale=true" : ""); respprep(&resp, p, msg, req); add_header(&resp, header, tmp); append_history(p, "AuthChal", "Auth challenge sent for %s - nc %d", p->username, p->noncecount); return send_response(p, &resp, reliable, seqno); } /*! \brief Extract domain from SIP To/From header \return -1 on error, 1 if domain string is empty, 0 if domain was properly extracted \note TODO: Such code is all over SIP channel, there is a sense to organize this patern in one function */ static int get_domain(const char *str, char *domain, int len) { char tmpf[256]; char *a, *from; *domain = '\0'; ast_copy_string(tmpf, str, sizeof(tmpf)); from = get_in_brackets(tmpf); 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 ((a = strchr(from, ';'))) *a = '\0'; /* Strip port from domain if present */ for (a = from; *a != '\0'; ++a) { if (*a == ':' && bracket == 0) { *a = '\0'; break; } else if (*a == '[') { ++bracket; } else if (*a == ']') { --bracket; } } if ((a = strchr(from, '@'))) { *a = '\0'; ast_copy_string(domain, a + 1, len); } else ast_copy_string(domain, from, len); } return ast_strlen_zero(domain); } /*! \brief Choose realm based on From header and then To header or use globaly configured realm. Realm from From/To header should be listed among served domains in config file: domain=... */ static void get_realm(struct sip_pvt *p, const struct sip_request *req) { char domain[MAXHOSTNAMELEN]; if (!ast_strlen_zero(p->realm)) return; if (sip_cfg.domainsasrealm && !AST_LIST_EMPTY(&domain_list)) { /* Check From header first */ if (!get_domain(sip_get_header(req, "From"), domain, sizeof(domain))) { if (check_sip_domain(domain, NULL, 0)) { ast_string_field_set(p, realm, domain); return; } } /* Check To header */ if (!get_domain(sip_get_header(req, "To"), domain, sizeof(domain))) { if (check_sip_domain(domain, NULL, 0)) { ast_string_field_set(p, realm, domain); return; } } } /* Use default realm from config file */ ast_string_field_set(p, realm, sip_cfg.realm); } /*! * \internal * * \arg msg Only use a string constant for the msg, here, it is shallow copied * * \note assumes the sip_pvt is locked. */ static int transmit_provisional_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, int with_sdp) { int res; if (!(res = with_sdp ? transmit_response_with_sdp(p, msg, req, XMIT_UNRELIABLE, FALSE, FALSE) : transmit_response(p, msg, req))) { p->last_provisional = msg; update_provisional_keepalive(p, with_sdp); } return res; } /*! * \internal * \brief Destroy all additional MESSAGE headers. * * \param pvt SIP private dialog struct. * * \return Nothing */ static void destroy_msg_headers(struct sip_pvt *pvt) { struct sip_msg_hdr *doomed; while ((doomed = AST_LIST_REMOVE_HEAD(&pvt->msg_headers, next))) { ast_free(doomed); } } /*! * \internal * \brief Add a MESSAGE header to the dialog. * * \param pvt SIP private dialog struct. * \param hdr_name Name of header for MESSAGE. * \param hdr_value Value of header for MESSAGE. * * \return Nothing */ static void add_msg_header(struct sip_pvt *pvt, const char *hdr_name, const char *hdr_value) { size_t hdr_len_name; size_t hdr_len_value; struct sip_msg_hdr *node; char *pos; hdr_len_name = strlen(hdr_name) + 1; hdr_len_value = strlen(hdr_value) + 1; node = ast_calloc(1, sizeof(*node) + hdr_len_name + hdr_len_value); if (!node) { return; } pos = node->stuff; node->name = pos; strcpy(pos, hdr_name); pos += hdr_len_name; node->value = pos; strcpy(pos, hdr_value); AST_LIST_INSERT_TAIL(&pvt->msg_headers, node, next); } /*! \brief Add text body to SIP message */ static int add_text(struct sip_request *req, struct sip_pvt *p) { const char *content_type = NULL; struct sip_msg_hdr *node; /* Add any additional MESSAGE headers. */ AST_LIST_TRAVERSE(&p->msg_headers, node, next) { if (!strcasecmp(node->name, "Content-Type")) { /* Save content type */ content_type = node->value; } else { add_header(req, node->name, node->value); } } if (ast_strlen_zero(content_type)) { /* "Content-Type" not set - use default value */ content_type = "text/plain;charset=UTF-8"; } add_header(req, "Content-Type", content_type); /* XXX Convert \n's to \r\n's XXX */ add_content(req, p->msg_body); return 0; } /*! \brief Add DTMF INFO tone to sip message Mode = 0 for application/dtmf-relay (Cisco) 1 for application/dtmf */ static int add_digit(struct sip_request *req, char digit, unsigned int duration, int mode) { char tmp[256]; int event; if (mode) { /* Application/dtmf short version used by some implementations */ if ('0' <= digit && digit <= '9') { event = digit - '0'; } else if (digit == '*') { event = 10; } else if (digit == '#') { event = 11; } else if ('A' <= digit && digit <= 'D') { event = 12 + digit - 'A'; } else if ('a' <= digit && digit <= 'd') { event = 12 + digit - 'a'; } else { /* Unknown digit */ event = 0; } snprintf(tmp, sizeof(tmp), "%d\r\n", event); add_header(req, "Content-Type", "application/dtmf"); add_content(req, tmp); } else { /* Application/dtmf-relay as documented by Cisco */ snprintf(tmp, sizeof(tmp), "Signal=%c\r\nDuration=%u\r\n", digit, duration); add_header(req, "Content-Type", "application/dtmf-relay"); add_content(req, tmp); } return 0; } /*! * \pre if p->owner exists, it must be locked * \brief Add Remote-Party-ID header to SIP message */ static int add_rpid(struct sip_request *req, struct sip_pvt *p) { struct ast_str *tmp = ast_str_alloca(256); char tmp2[256]; char lid_name_buf[128]; char *lid_num; char *lid_name; int lid_pres; const char *fromdomain; const char *privacy = NULL; const char *screen = NULL; struct ast_party_id connected_id; const char *anonymous_string = "\"Anonymous\" "; if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { return 0; } if (!p->owner) { return 0; } connected_id = ast_channel_connected_effective_id(p->owner); lid_num = S_COR(connected_id.number.valid, connected_id.number.str, NULL); if (!lid_num) { return 0; } lid_name = S_COR(connected_id.name.valid, connected_id.name.str, NULL); if (!lid_name) { lid_name = lid_num; } ast_escape_quoted(lid_name, lid_name_buf, sizeof(lid_name_buf)); lid_pres = ast_party_id_presentation(&connected_id); if (((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) && (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) == SIP_PAGE2_TRUST_ID_OUTBOUND_NO)) { /* If pres is not allowed and we don't trust the peer, we don't apply an RPID header */ return 0; } fromdomain = p->fromdomain; if (!fromdomain || ((ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) == SIP_PAGE2_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(&p->ourip); } lid_num = ast_uri_encode(lid_num, tmp2, sizeof(tmp2), ast_uri_sip_user); if (ast_test_flag(&p->flags[0], SIP_SENDRPID_PAI)) { if (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) != SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY) { /* trust_id_outbound = yes - Always give full information even if it's private, but append a privacy header * When private data is included */ ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { add_header(req, "Privacy", "id"); } } else { /* trust_id_outbound = legacy - behave in a non RFC-3325 compliant manner and send anonymized data when * when handling private data. */ if ((lid_pres & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); } else { ast_str_set(&tmp, -1, "%s", anonymous_string); } } add_header(req, "P-Asserted-Identity", ast_str_buffer(tmp)); } else { ast_str_set(&tmp, -1, "\"%s\" ;party=%s", lid_name_buf, lid_num, fromdomain, p->outgoing_call ? "calling" : "called"); switch (lid_pres) { case AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED: case AST_PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN: privacy = "off"; screen = "no"; break; case AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN: case AST_PRES_ALLOWED_NETWORK_NUMBER: privacy = "off"; screen = "yes"; break; case AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED: case AST_PRES_PROHIB_USER_NUMBER_FAILED_SCREEN: privacy = "full"; screen = "no"; break; case AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN: case AST_PRES_PROHIB_NETWORK_NUMBER: privacy = "full"; screen = "yes"; break; case AST_PRES_NUMBER_NOT_AVAILABLE: break; default: if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { privacy = "full"; } else privacy = "off"; screen = "no"; break; } if (!ast_strlen_zero(privacy) && !ast_strlen_zero(screen)) { ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); } add_header(req, "Remote-Party-ID", ast_str_buffer(tmp)); } return 0; } /*! \brief add XML encoded media control with update \note XML: The only way to turn 0 bits of information into a few hundred. (markster) */ static int add_vidupdate(struct sip_request *req) { const char *xml_is_a_huge_waste_of_space = "\r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n"; add_header(req, "Content-Type", "application/media_control+xml"); add_content(req, xml_is_a_huge_waste_of_space); return 0; } /*! \brief Add ICE attributes to SDP */ static void add_ice_to_sdp(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 i; 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); } i = ao2_iterator_init(candidates, 0); while ((candidate = ao2_iterator_next(&i))) { 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_host(&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_host(&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(&i); ao2_ref(candidates, -1); } /*! \brief Start ICE negotiation on an RTP instance */ static void 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); } /*! \brief Add DTLS attributes to SDP */ static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf) { struct ast_rtp_engine_dtls *dtls; enum ast_rtp_dtls_hash hash; const char *fingerprint; if (!instance || !(dtls = ast_rtp_instance_get_dtls(instance)) || !dtls->active(instance)) { return; } switch (dtls->get_connection(instance)) { case AST_RTP_DTLS_CONNECTION_NEW: ast_str_append(a_buf, 0, "a=connection:new\r\n"); break; case AST_RTP_DTLS_CONNECTION_EXISTING: ast_str_append(a_buf, 0, "a=connection:existing\r\n"); break; default: break; } switch (dtls->get_setup(instance)) { case AST_RTP_DTLS_SETUP_ACTIVE: ast_str_append(a_buf, 0, "a=setup:active\r\n"); break; case AST_RTP_DTLS_SETUP_PASSIVE: ast_str_append(a_buf, 0, "a=setup:passive\r\n"); break; case AST_RTP_DTLS_SETUP_ACTPASS: ast_str_append(a_buf, 0, "a=setup:actpass\r\n"); break; case AST_RTP_DTLS_SETUP_HOLDCONN: ast_str_append(a_buf, 0, "a=setup:holdconn\r\n"); break; default: break; } hash = dtls->get_fingerprint_hash(instance); fingerprint = dtls->get_fingerprint(instance); if (fingerprint && (hash == AST_RTP_DTLS_HASH_SHA1 || hash == AST_RTP_DTLS_HASH_SHA256)) { ast_str_append(a_buf, 0, "a=fingerprint:%s %s\r\n", hash == AST_RTP_DTLS_HASH_SHA1 ? "SHA-1" : "SHA-256", fingerprint); } } /*! \brief Add codec offer to SDP offer/answer body in INVITE or 200 OK */ static void add_codec_to_sdp(const struct sip_pvt *p, 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_verbose("Adding codec %s to SDP\n", ast_format_get_name(format)); if (((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(p->rtp), 1, format, 0)) == -1) || !(mime = ast_rtp_lookup_mime_subtype2(1, format, 0, ast_test_flag(&p->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 { 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(p->caps, format); if (ast_format_cmp(format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { /* Indicate that we don't support VAD (G.729 annex B) */ ast_str_append(a_buf, 0, "a=fmtp:%d annexb=no\r\n", rtp_code); } else 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_ilbc) == AST_FORMAT_CMP_EQUAL) { /* Add information about us using only 20/30 ms packetization */ ast_str_append(a_buf, 0, "a=fmtp:%d mode=%u\r\n", rtp_code, framing); } else if (ast_format_cmp(format, ast_format_siren7) == AST_FORMAT_CMP_EQUAL) { /* Indicate that we only expect 32Kbps */ ast_str_append(a_buf, 0, "a=fmtp:%d bitrate=32000\r\n", rtp_code); } else if (ast_format_cmp(format, ast_format_siren14) == AST_FORMAT_CMP_EQUAL) { /* Indicate that we only expect 48Kbps */ ast_str_append(a_buf, 0, "a=fmtp:%d bitrate=48000\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); } } /*! \brief 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 add_vcodec_to_sdp(const struct sip_pvt *p, 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 (!p->vrtp) return; if (debug) ast_verbose("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(p->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); 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); } /*! \brief Add text codec offer to SDP offer/answer body in INVITE or 200 OK */ static void add_tcodec_to_sdp(const struct sip_pvt *p, struct ast_format *format, struct ast_str **m_buf, struct ast_str **a_buf, int debug, int *min_packet_size) { int rtp_code; if (!p->trtp) return; if (debug) ast_verbose("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(p->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(p->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); } } /*! \brief Get Max T.38 Transmission rate from T38 capabilities */ static unsigned int t38_get_rate(enum ast_control_t38_rate rate) { switch (rate) { case AST_T38_RATE_2400: return 2400; case AST_T38_RATE_4800: return 4800; case AST_T38_RATE_7200: return 7200; case AST_T38_RATE_9600: return 9600; case AST_T38_RATE_12000: return 12000; case AST_T38_RATE_14400: return 14400; default: return 0; } } /*! \brief Add RFC 2833 DTMF offer to SDP */ static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, struct ast_str **m_buf, struct ast_str **a_buf, int debug) { int rtp_code; if (debug) ast_verbose("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(p->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); } /*! \brief Set all IP media addresses for this call \note called from add_sdp() */ static void get_our_media_address(struct sip_pvt *p, 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 = 0; /* First, get our address */ ast_rtp_instance_get_local_address(p->rtp, addr); if (p->vrtp) { ast_rtp_instance_get_local_address(p->vrtp, vaddr); } if (p->trtp) { ast_rtp_instance_get_local_address(p->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(&p->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(&p->redirip)) { /* If we have a redirection IP, use it */ ast_sockaddr_copy(dest, &p->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: * * 1. Provided by the RTP engine. */ ast_sockaddr_copy(dest, !ast_sockaddr_isnull(&media_address) ? &media_address : !ast_sockaddr_is_any(addr) && !use_externip ? addr : &p->ourip); ast_sockaddr_set_port(dest, ast_sockaddr_port(addr)); } if (needvideo) { /* Determine video destination */ if (!ast_sockaddr_isnull(&p->vredirip)) { ast_sockaddr_copy(vdest, &p->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: * * 1. Provided by the RTP engine. */ ast_sockaddr_copy(vdest, !ast_sockaddr_isnull(&media_address) ? &media_address : !ast_sockaddr_is_any(vaddr) && !use_externip ? vaddr : &p->ourip); ast_sockaddr_set_port(vdest, ast_sockaddr_port(vaddr)); } } if (needtext) { /* Determine text destination */ if (!ast_sockaddr_isnull(&p->tredirip)) { ast_sockaddr_copy(tdest, &p->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: * * 1. Provided by the RTP engine. */ ast_sockaddr_copy(tdest, !ast_sockaddr_isnull(&media_address) ? &media_address : !ast_sockaddr_is_any(taddr) && !use_externip ? taddr : &p->ourip); ast_sockaddr_set_port(tdest, ast_sockaddr_port(taddr)); } } } static char *crypto_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32) { char *a_crypto; const char *orig_crypto; if (!srtp || dtls_enabled) { return NULL; } orig_crypto = ast_sdp_srtp_get_attrib(srtp, dtls_enabled, default_taglen_32); if (ast_strlen_zero(orig_crypto)) { return NULL; } if (ast_asprintf(&a_crypto, "a=crypto:%s\r\n", orig_crypto) == -1) { return NULL; } return a_crypto; } /*! \brief 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. */ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, 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 = AST_SUCCESS; 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 offered_media *offer; 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 x; struct ast_format *tmp_fmt; int needaudio = FALSE; int needvideo = FALSE; int needtext = FALSE; int debug = sip_debug_test_pvt(p); 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(64); /* Set the SDP session name */ snprintf(subject, sizeof(subject), "s=%s\r\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession); if (!alreadysent || !tmpcap) { res = AST_FAILURE; goto add_sdp_cleanup; } if (!p->rtp) { ast_log(LOG_WARNING, "No way to add SDP without an RTP structure\n"); res = AST_FAILURE; goto add_sdp_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 (!p->sessionid) { p->sessionid = (int)ast_random(); p->sessionversion = p->sessionid; } else { if (oldsdp == FALSE) p->sessionversion++; } if (add_audio) { doing_directmedia = (!ast_sockaddr_isnull(&p->redirip) && (ast_format_cap_count(p->redircaps))) ? TRUE : FALSE; if (doing_directmedia) { ast_format_cap_get_compatible(p->jointcaps, p->redircaps, tmpcap); ast_debug(1, "** Our native-bridge filtered capablity: %s\n", ast_format_cap_get_names(tmpcap, &codec_buf)); } else { ast_format_cap_append_from_cap(tmpcap, p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); } /* Check if we need audio */ if (ast_format_cap_has_type(tmpcap, AST_MEDIA_TYPE_AUDIO) || ast_format_cap_has_type(p->caps, AST_MEDIA_TYPE_AUDIO)) { needaudio = TRUE; } /* Check if we need video in this call */ if ((ast_format_cap_has_type(tmpcap, AST_MEDIA_TYPE_VIDEO)) && !p->novideo) { if (doing_directmedia && !ast_format_cap_has_type(tmpcap, AST_MEDIA_TYPE_VIDEO)) { ast_debug(2, "This call needs video offers, but caller probably did not offer it!\n"); } else if (p->vrtp) { needvideo = TRUE; ast_debug(2, "This call needs video offers!\n"); } else { ast_debug(2, "This call needs video offers, but there's no video support enabled!\n"); } } /* Check if we need text in this call */ if ((ast_format_cap_has_type(p->jointcaps, AST_MEDIA_TYPE_TEXT)) && !p->notext) { if (sipdebug_text) ast_verbose("We think we can do text\n"); if (p->trtp) { if (sipdebug_text) { ast_verbose("And we have a text rtp object\n"); } needtext = TRUE; ast_debug(2, "This call needs text offers! \n"); } else { ast_debug(2, "This call needs text offers, but there's no text support enabled ! \n"); } } /* XXX note, Video and Text are negated - 'true' means 'no' */ ast_debug(1, "** Our capability: %s Video flag: %s Text flag: %s\n", ast_format_cap_get_names(tmpcap, &codec_buf), p->novideo ? "True" : "False", p->notext ? "True" : "False"); ast_debug(1, "** Our prefcodec: %s \n", ast_format_cap_get_names(p->prefcaps, &codec_buf)); } get_our_media_address(p, needvideo, needtext, &addr, &vaddr, &taddr, &dest, &vdest, &tdest); snprintf(owner, sizeof(owner), "o=%s %d %d IN %s %s\r\n", ast_strlen_zero(global_sdpowner) ? "-" : global_sdpowner, p->sessionid, p->sessionversion, (ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest)) ? "IP6" : "IP4", ast_sockaddr_stringify_addr_remote(&dest)); 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(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) == SIP_PAGE2_CALL_ONHOLD_ONEDIR) { hold = "a=recvonly\r\n"; doing_directmedia = FALSE; } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) == SIP_PAGE2_CALL_ONHOLD_INACTIVE) { hold = "a=inactive\r\n"; doing_directmedia = FALSE; } else { hold = "a=sendrecv\r\n"; } if (debug) { ast_verbose("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 (needvideo) { v_a_crypto = crypto_get_attrib(p->vsrtp, p->dtls_cfg.enabled, ast_test_flag(&p->flags[2], SIP_PAGE3_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 ? 1 : 0, p->vrtp, ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF), ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP))); /* Build max bitrate string */ if (p->maxcallbitrate) snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate); if (debug) { ast_verbose("Video is at %s\n", ast_sockaddr_stringify(&vdest)); } if (!doing_directmedia) { if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { add_ice_to_sdp(p->vrtp, &a_video); } add_dtls_to_sdp(p->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 (needtext) { if (sipdebug_text) ast_verbose("Lets set up the text sdp\n"); t_a_crypto = crypto_get_attrib(p->tsrtp, p->dtls_cfg.enabled, ast_test_flag(&p->flags[2], SIP_PAGE3_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 ? 1 : 0, p->trtp, ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF), ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP))); if (debug) { /* XXX should I use tdest below ? */ ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr)); } if (!doing_directmedia) { if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { add_ice_to_sdp(p->trtp, &a_text); } add_dtls_to_sdp(p->trtp, &a_text); } } /* Start building generic SDP headers */ /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ a_crypto = crypto_get_attrib(p->srtp, p->dtls_cfg.enabled, ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32)); ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest), ast_sdp_get_rtp_profile(a_crypto ? 1 : 0, p->rtp, ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF), ast_test_flag(&p->flags[2], SIP_PAGE3_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 - Then preferences in order from sip.conf device config for this peer/user */ /* Unless otherwise configured, the prefcaps is added before the peer's * configured codecs. */ if (!ast_test_flag(&p->flags[2], SIP_PAGE3_IGNORE_PREFCAPS)) { for (x = 0; x < ast_format_cap_count(p->prefcaps); x++) { tmp_fmt = ast_format_cap_get_format(p->prefcaps, x); if ((ast_format_get_type(tmp_fmt) != AST_MEDIA_TYPE_AUDIO) || (ast_format_cap_iscompatible_format(tmpcap, tmp_fmt) == AST_FORMAT_CMP_NOT_EQUAL)) { ao2_ref(tmp_fmt, -1); continue; } add_codec_to_sdp(p, tmp_fmt, &m_audio, &a_audio, debug, &min_audio_packet_size, &max_audio_packet_size); ast_format_cap_append(alreadysent, tmp_fmt, 0); ao2_ref(tmp_fmt, -1); } } /* Now send any other common codecs */ for (x = 0; x < ast_format_cap_count(tmpcap); x++) { tmp_fmt = ast_format_cap_get_format(tmpcap, x); if (ast_format_cap_iscompatible_format(alreadysent, tmp_fmt) != AST_FORMAT_CMP_NOT_EQUAL) { ao2_ref(tmp_fmt, -1); continue; } if (ast_format_get_type(tmp_fmt) == AST_MEDIA_TYPE_AUDIO) { add_codec_to_sdp(p, tmp_fmt, &m_audio, &a_audio, debug, &min_audio_packet_size, &max_audio_packet_size); } else if (needvideo && ast_format_get_type(tmp_fmt) == AST_MEDIA_TYPE_VIDEO) { add_vcodec_to_sdp(p, tmp_fmt, &m_video, &a_video, debug, &min_video_packet_size); } else if (needtext && ast_format_get_type(tmp_fmt) == AST_MEDIA_TYPE_TEXT) { add_tcodec_to_sdp(p, tmp_fmt, &m_text, &a_text, debug, &min_text_packet_size); } ast_format_cap_append(alreadysent, tmp_fmt, 0); ao2_ref(tmp_fmt, -1); } /* Finally our remaining audio/video codecs */ for (x = 0; x < ast_format_cap_count(p->caps); x++) { tmp_fmt = ast_format_cap_get_format(p->caps, x); if (ast_format_cap_iscompatible_format(alreadysent, tmp_fmt) != AST_FORMAT_CMP_NOT_EQUAL) { ao2_ref(tmp_fmt, -1); continue; } if (ast_format_get_type(tmp_fmt) == AST_MEDIA_TYPE_AUDIO) { add_codec_to_sdp(p, tmp_fmt, &m_audio, &a_audio, debug, &min_audio_packet_size, &max_audio_packet_size); } else if (needvideo && ast_format_get_type(tmp_fmt) == AST_MEDIA_TYPE_VIDEO) { add_vcodec_to_sdp(p, tmp_fmt, &m_video, &a_video, debug, &min_video_packet_size); } else if (needtext && ast_format_get_type(tmp_fmt) == AST_MEDIA_TYPE_TEXT) { add_tcodec_to_sdp(p, tmp_fmt, &m_text, &a_text, debug, &min_text_packet_size); } ast_format_cap_append(alreadysent, tmp_fmt, 0); ao2_ref(tmp_fmt, -1); } /* Now add DTMF RFC2833 telephony-event as a codec */ for (x = 1LL; x <= AST_RTP_MAX; x <<= 1) { if (!(p->jointnoncodeccapability & x)) continue; add_noncodec_to_sdp(p, x, &m_audio, &a_audio, debug); } ast_debug(3, "-- Done with adding codecs to SDP\n"); if (!p->owner || ast_channel_timingfd(p->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 (!doing_directmedia) { if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) { add_ice_to_sdp(p->rtp, &a_audio); } add_dtls_to_sdp(p->rtp, &a_audio); } } if (add_t38) { /* Our T.38 end is */ ast_udptl_get_us(p->udptl, &udptladdr); /* We don't use directmedia for T.38, so keep the destination the same as our IP address. */ ast_sockaddr_copy(&udptldest, &p->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(&p->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(&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", p->t38.our_parms.version); ast_str_append(&a_modem, 0, "a=T38MaxBitRate:%u\r\n", t38_get_rate(p->t38.our_parms.rate)); if (p->t38.our_parms.fill_bit_removal) { ast_str_append(&a_modem, 0, "a=T38FaxFillBitRemoval\r\n"); } if (p->t38.our_parms.transcoding_mmr) { ast_str_append(&a_modem, 0, "a=T38FaxTranscodingMMR\r\n"); } if (p->t38.our_parms.transcoding_jbig) { ast_str_append(&a_modem, 0, "a=T38FaxTranscodingJBIG\r\n"); } switch (p->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(p->udptl)); switch (ast_udptl_get_error_correction_scheme(p->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 (needaudio) ast_str_append(&m_audio, 0, "\r\n"); if (needvideo) ast_str_append(&m_video, 0, "\r\n"); if (needtext) ast_str_append(&m_text, 0, "\r\n"); add_header(resp, "Content-Type", "application/sdp"); add_content(resp, version); add_content(resp, owner); add_content(resp, subject); add_content(resp, connection); /* only if video response is appropriate */ if (needvideo) { add_content(resp, bandwidth); } add_content(resp, session_time); /* if this is a response to an invite, order our offers properly */ if (!AST_LIST_EMPTY(&p->offered_media)) { AST_LIST_TRAVERSE(&p->offered_media, offer, next) { switch (offer->type) { case SDP_AUDIO: if (needaudio) { add_content(resp, ast_str_buffer(m_audio)); add_content(resp, ast_str_buffer(a_audio)); add_content(resp, hold); if (a_crypto) { add_content(resp, a_crypto); } } else { add_content(resp, offer->decline_m_line); } break; case SDP_VIDEO: if (needvideo) { /* only if video response is appropriate */ add_content(resp, ast_str_buffer(m_video)); add_content(resp, ast_str_buffer(a_video)); add_content(resp, hold); /* Repeat hold for the video stream */ if (v_a_crypto) { add_content(resp, v_a_crypto); } } else { add_content(resp, offer->decline_m_line); } break; case SDP_TEXT: if (needtext) { /* only if text response is appropriate */ add_content(resp, ast_str_buffer(m_text)); add_content(resp, ast_str_buffer(a_text)); add_content(resp, hold); /* Repeat hold for the text stream */ if (t_a_crypto) { add_content(resp, t_a_crypto); } } else { add_content(resp, offer->decline_m_line); } break; case SDP_IMAGE: if (add_t38) { add_content(resp, ast_str_buffer(m_modem)); add_content(resp, ast_str_buffer(a_modem)); } else { add_content(resp, offer->decline_m_line); } break; case SDP_UNKNOWN: add_content(resp, offer->decline_m_line); break; } } } else { /* generate new SDP from scratch, no offers */ if (needaudio) { add_content(resp, ast_str_buffer(m_audio)); add_content(resp, ast_str_buffer(a_audio)); add_content(resp, hold); if (a_crypto) { add_content(resp, a_crypto); } } if (needvideo) { /* only if video response is appropriate */ add_content(resp, ast_str_buffer(m_video)); add_content(resp, ast_str_buffer(a_video)); add_content(resp, hold); /* Repeat hold for the video stream */ if (v_a_crypto) { add_content(resp, v_a_crypto); } } if (needtext) { /* only if text response is appropriate */ add_content(resp, ast_str_buffer(m_text)); add_content(resp, ast_str_buffer(a_text)); add_content(resp, hold); /* Repeat hold for the text stream */ if (t_a_crypto) { add_content(resp, t_a_crypto); } } if (add_t38) { add_content(resp, ast_str_buffer(m_modem)); add_content(resp, ast_str_buffer(a_modem)); } } /* Update lastrtprx when we send our SDP */ p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */ /* * We unlink this dialog and link again into the * dialogs_rtpcheck container so its not in there twice. */ ao2_lock(dialogs_rtpcheck); ao2_t_unlink(dialogs_rtpcheck, p, "unlink pvt into dialogs_rtpcheck container"); ao2_t_link(dialogs_rtpcheck, p, "link pvt into dialogs_rtpcheck container"); ao2_unlock(dialogs_rtpcheck); ast_debug(3, "Done building SDP. Settling with this capability: %s\n", ast_format_cap_get_names(tmpcap, &codec_buf)); add_sdp_cleanup: ast_free(a_text); ast_free(a_video); ast_free(a_audio); ao2_cleanup(alreadysent); ao2_cleanup(tmpcap); return res; } /*! \brief Used for 200 OK and 183 early media */ static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans) { struct sip_request resp; uint32_t seqno; if (sscanf(sip_get_header(req, "CSeq"), "%30u ", &seqno) != 1) { ast_log(LOG_WARNING, "Unable to get seqno from '%s'\n", sip_get_header(req, "CSeq")); return -1; } respprep(&resp, p, msg, req); if (p->udptl) { add_sdp(&resp, p, 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", p->callid); if (retrans && !p->pendinginvite) p->pendinginvite = seqno; /* Buggy clients sends ACK on RINGING too */ return send_response(p, &resp, retrans, seqno); } /*! \brief copy SIP request (mostly used to save request for responses) */ static void copy_request(struct sip_request *dst, const struct sip_request *src) { /* XXX this function can encounter memory allocation errors, perhaps it * should return a value */ struct ast_str *duplicate = dst->data; struct ast_str *duplicate_content = dst->content; /* copy the entire request then restore the original data and content * members from the dst request */ *dst = *src; dst->data = duplicate; dst->content = duplicate_content; /* copy the data into the dst request */ if (!dst->data && !(dst->data = ast_str_create(ast_str_strlen(src->data) + 1))) { return; } ast_str_copy_string(&dst->data, src->data); /* copy the content into the dst request (if it exists) */ if (src->content) { if (!dst->content && !(dst->content = ast_str_create(ast_str_strlen(src->content) + 1))) { return; } ast_str_copy_string(&dst->content, src->content); } } static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp) { char uri[SIPBUFSIZE]; struct ast_str *header = ast_str_alloca(SIPBUFSIZE); struct ast_cc_agent *agent = find_sip_cc_agent_by_original_callid(p); struct sip_cc_agent_pvt *agent_pvt; if (!agent) { /* Um, what? How could the SIP_OFFER_CC flag be set but there not be an * agent? Oh well, we'll just warn and return without adding the header. */ ast_log(LOG_WARNING, "Can't find SIP CC agent for call '%s' even though OFFER_CC flag was set?\n", p->callid); return; } agent_pvt = agent->private_data; if (!ast_strlen_zero(agent_pvt->subscribe_uri)) { ast_copy_string(uri, agent_pvt->subscribe_uri, sizeof(uri)); } else { generate_uri(p, uri, sizeof(uri)); ast_copy_string(agent_pvt->subscribe_uri, uri, sizeof(agent_pvt->subscribe_uri)); } /* XXX Hardcode "NR" as the m reason for now. This should perhaps be changed * to be more accurate. This parameter has no bearing on the actual operation * of the feature; it's just there for informational purposes. */ ast_str_set(&header, 0, "<%s>;purpose=call-completion;m=%s", uri, "NR"); add_header(resp, "Call-Info", ast_str_buffer(header)); ao2_ref(agent, -1); } /*! \brief Used for 200 OK and 183 early media \return Will return XMIT_ERROR for network errors. */ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp, int rpid) { struct sip_request resp; uint32_t seqno; if (sscanf(sip_get_header(req, "CSeq"), "%30u ", &seqno) != 1) { ast_log(LOG_WARNING, "Unable to get seqno from '%s'\n", sip_get_header(req, "CSeq")); return -1; } respprep(&resp, p, msg, req); if (rpid == TRUE) { add_rpid(&resp, p); } if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { add_cc_call_info_to_response(p, &resp); } if (p->rtp) { if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) { ast_debug(1, "Setting framing from config on incoming call\n"); ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), ast_format_cap_get_framing(p->caps)); } ast_rtp_instance_activate(p->rtp); try_suggested_sip_codec(p); if (p->t38.state == T38_ENABLED) { add_sdp(&resp, p, oldsdp, TRUE, TRUE); } else { add_sdp(&resp, p, 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", p->callid); if (reliable && !p->pendinginvite) p->pendinginvite = seqno; /* Buggy clients sends ACK on RINGING too */ add_required_respheader(&resp); return send_response(p, &resp, reliable, seqno); } /*! \brief Parse first line of incoming SIP request */ static int determine_firstline_parts(struct sip_request *req) { char *e = ast_skip_blanks(ast_str_buffer(req->data)); /* there shouldn't be any */ char *local_rlpart1; if (!*e) return -1; req->rlpart1 = e - ast_str_buffer(req->data); /* method or protocol */ local_rlpart1 = e; e = ast_skip_nonblanks(e); if (*e) *e++ = '\0'; /* Get URI or status code */ e = ast_skip_blanks(e); if ( !*e ) return -1; ast_trim_blanks(e); if (!strcasecmp(local_rlpart1, "SIP/2.0") ) { /* We have a response */ if (strlen(e) < 3) /* status code is 3 digits */ return -1; req->rlpart2 = e - ast_str_buffer(req->data); } else { /* We have a request */ if ( *e == '<' ) { /* XXX the spec says it must not be in <> ! */ ast_debug(3, "Oops. Bogus uri in <> %s\n", e); e++; if (!*e) return -1; } req->rlpart2 = e - ast_str_buffer(req->data); /* URI */ e = ast_skip_nonblanks(e); if (*e) *e++ = '\0'; e = ast_skip_blanks(e); if (strcasecmp(e, "SIP/2.0") ) { ast_debug(3, "Skipping packet - Bad request protocol %s\n", e); return -1; } } return 1; } /*! \brief Transmit reinvite with SDP \note 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. */ static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp) { struct sip_request req; reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1); add_header(&req, "Allow", ALLOWED_METHODS); add_supported(p, &req); if (sipdebug) { if (oldsdp == TRUE) add_header(&req, "X-asterisk-Info", "SIP re-invite (Session-Timers)"); else add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)"); } if (ast_test_flag(&p->flags[0], SIP_SENDRPID)) add_rpid(&req, p); if (p->do_history) { append_history(p, "ReInv", "Re-invite sent"); } offered_media_list_destroy(p); try_suggested_sip_codec(p); if (t38version) { add_sdp(&req, p, oldsdp, FALSE, TRUE); } else { add_sdp(&req, p, oldsdp, TRUE, FALSE); } /* Use this as the basis */ initialize_initreq(p, &req); p->lastinvite = p->ocseq; ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Change direction of this dialog */ p->ongoing_reinvite = 1; return send_request(p, &req, XMIT_CRITICAL, p->ocseq); } /* \brief Remove URI parameters at end of URI, not in username part though */ static char *remove_uri_parameters(char *uri) { char *atsign; atsign = strchr(uri, '@'); /* First, locate the at sign */ if (!atsign) { atsign = uri; /* Ok hostname only, let's stick with the rest */ } atsign = strchr(atsign, ';'); /* Locate semi colon */ if (atsign) *atsign = '\0'; /* Kill at the semi colon */ return uri; } /*! \brief Check Contact: URI of SIP message */ static void extract_uri(struct sip_pvt *p, struct sip_request *req) { char stripped[SIPBUFSIZE]; char *c; ast_copy_string(stripped, sip_get_header(req, "Contact"), sizeof(stripped)); c = get_in_brackets(stripped); /* Cut the URI at the at sign after the @, not in the username part */ c = remove_uri_parameters(c); if (!ast_strlen_zero(c)) { ast_string_field_set(p, uri, c); } } /*! \brief Build contact header - the contact header we send out */ static void build_contact(struct sip_pvt *p) { char tmp[SIPBUFSIZE]; char *user = ast_uri_encode(p->exten, tmp, sizeof(tmp), ast_uri_sip_user); if (p->socket.type == AST_TRANSPORT_UDP) { ast_string_field_build(p, our_contact, "", user, ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify_remote(&p->ourip)); } else { ast_string_field_build(p, our_contact, "", user, ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify_remote(&p->ourip), sip_get_transport(p->socket.type)); } } /*! \brief Initiate new SIP request to peer/user */ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri) { struct ast_str *invite = ast_str_alloca(256); char from[256]; char to[256]; char tmp_n[SIPBUFSIZE/2]; /* build a local copy of 'n' if needed */ char tmp_l[SIPBUFSIZE/2]; /* build a local copy of 'l' if needed */ const char *l = NULL; /* XXX what is this, exactly ? */ const char *n = NULL; /* XXX what is this, exactly ? */ const char *d = NULL; /* domain in from header */ const char *urioptions = ""; int ourport; int cid_has_name = 1; int cid_has_num = 1; struct ast_party_id connected_id; if (ast_test_flag(&p->flags[0], SIP_USEREQPHONE)) { const char *s = p->username; /* being a string field, cannot be NULL */ /* Test p->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 (*s == '+') s++; for (; *s; s++) { if (!strchr(AST_DIGIT_ANYNUM, *s) ) break; } /* If we have only digits, add ;user=phone to the uri */ if (!*s) urioptions = ";user=phone"; } snprintf(p->lastmsg, sizeof(p->lastmsg), "Init: %s", sip_methods[sipmethod].text); if (ast_strlen_zero(p->fromdomain)) { d = ast_sockaddr_stringify_host_remote(&p->ourip); } if (p->owner) { connected_id = ast_channel_connected_effective_id(p->owner); if ((ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { if (connected_id.number.valid) { l = connected_id.number.str; } if (connected_id.name.valid) { n = 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 */ l = "anonymous"; n = CALLERID_UNKNOWN; d = FROMDOMAIN_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_send_mwi_to_peer. If things changed, then * we might end up putting the mwi_from setting into other types of NOTIFY * messages as well. */ if (sipmethod == SIP_NOTIFY && !ast_strlen_zero(p->mwi_from)) { l = p->mwi_from; } if (ast_strlen_zero(l)) { cid_has_num = 0; l = default_callerid; } if (ast_strlen_zero(n)) { cid_has_name = 0; n = l; } /* Allow user to be overridden */ if (!ast_strlen_zero(p->fromuser)) l = p->fromuser; else /* Save for any further attempts */ ast_string_field_set(p, fromuser, l); /* Allow user to be overridden */ if (!ast_strlen_zero(p->fromname)) n = p->fromname; else /* Save for any further attempts */ ast_string_field_set(p, fromname, n); /* Allow domain to be overridden */ if (!ast_strlen_zero(p->fromdomain)) d = p->fromdomain; else /* Save for any further attempts */ ast_string_field_set(p, fromdomain, d); ast_copy_string(tmp_l, l, sizeof(tmp_l)); if (sip_cfg.pedanticsipchecking) { ast_escape_quoted(n, tmp_n, sizeof(tmp_n)); n = tmp_n; ast_uri_encode(l, tmp_l, sizeof(tmp_l), ast_uri_sip_user); } ourport = (p->fromdomainport && (p->fromdomainport != STANDARD_SIP_PORT)) ? p->fromdomainport : ast_sockaddr_port(&p->ourip); /* If a caller id name was specified, add a display name. */ if (cid_has_name || !cid_has_num) { snprintf(from, sizeof(from), "\"%s\" ", n); } else { from[0] = '\0'; } if (!sip_standard_port(p->socket.type, ourport)) { size_t offset = strlen(from); snprintf(&from[offset], sizeof(from) - offset, ";tag=%s", tmp_l, d, ourport, p->tag); } else { size_t offset = strlen(from); snprintf(&from[offset], sizeof(from) - offset, ";tag=%s", tmp_l, d, p->tag); } 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(p->fullcontact)) { /* If we have full contact, trust it */ ast_str_append(&invite, 0, "%s", p->fullcontact); } else { /* Otherwise, use the username while waiting for registration */ ast_str_append(&invite, 0, "sip:"); if (!ast_strlen_zero(p->username)) { n = p->username; if (sip_cfg.pedanticsipchecking) { ast_uri_encode(n, tmp_n, sizeof(tmp_n), ast_uri_sip_user); n = tmp_n; } ast_str_append(&invite, 0, "%s@", n); } ast_str_append(&invite, 0, "%s", p->tohost); if (p->portinuri) { ast_str_append(&invite, 0, ":%d", ast_sockaddr_port(&p->sa)); } ast_str_append(&invite, 0, "%s", urioptions); } } /* If custom URI options have been provided, append them */ if (p->options && !ast_strlen_zero(p->options->uri_options)) ast_str_append(&invite, 0, ";%s", p->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(p, uri, ast_str_buffer(invite)); if (!ast_strlen_zero(p->todnid)) { /*! \todo Need to add back the VXML URL here at some point, possibly use build_string for all this junk */ if (!strchr(p->todnid, '@')) { /* We have no domain in the dnid */ snprintf(to, sizeof(to), "%s%s", p->todnid, p->tohost, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag); } else { snprintf(to, sizeof(to), "%s%s", p->todnid, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag); } } else { if (sipmethod == SIP_NOTIFY && !ast_strlen_zero(p->theirtag)) { /* If this is a NOTIFY, use the From: tag in the subscribe (RFC 3265) */ snprintf(to, sizeof(to), "<%s%s>;tag=%s", (strncasecmp(p->uri, "sip:", 4) ? "sip:" : ""), p->uri, p->theirtag); } else if (p->options && p->options->vxml_url) { /* If there is a VXML URL append it to the SIP URL */ snprintf(to, sizeof(to), "<%s>;%s", p->uri, p->options->vxml_url); } else { snprintf(to, sizeof(to), "<%s>", p->uri); } } init_req(req, sipmethod, p->uri); /* now tmp_n is available so reuse it to build the CSeq */ snprintf(tmp_n, sizeof(tmp_n), "%u %s", ++p->ocseq, sip_methods[sipmethod].text); add_header(req, "Via", p->via); add_max_forwards(p, req); /* 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. */ add_route(req, &p->route, 0); add_header(req, "From", from); add_header(req, "To", to); ast_string_field_set(p, exten, l); build_contact(p); add_header(req, "Contact", p->our_contact); add_header(req, "Call-ID", p->callid); add_header(req, "CSeq", tmp_n); if (!ast_strlen_zero(global_useragent)) { add_header(req, "User-Agent", global_useragent); } } /*! \brief 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. * * \param req The request/response to which we will add the header * \param pvt The sip_pvt which represents the call-leg */ static void add_diversion(struct sip_request *req, struct sip_pvt *pvt) { struct ast_party_id diverting_from; const char *reason; int found_in_table; char header_text[256]; char encoded_number[SIPBUFSIZE/2]; /* We skip this entirely if the configuration doesn't allow diversion headers */ if (!sip_cfg.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_cfg.pedanticsipchecking) { ast_uri_encode(diverting_from.number.str, encoded_number, sizeof(encoded_number), ast_uri_sip_user); } else { ast_copy_string(encoded_number, diverting_from.number.str, sizeof(encoded_number)); } reason = sip_reason_code_to_str(&ast_channel_redirecting(pvt->owner)->reason, &found_in_table); /* 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(header_text, sizeof(header_text), ";reason=%s%s%s", encoded_number, ast_sockaddr_stringify_host_remote(&pvt->ourip), found_in_table ? "" : "\"", reason, found_in_table ? "" : "\""); } else { char escaped_name[SIPBUFSIZE/2]; if (sip_cfg.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(header_text, sizeof(header_text), "\"%s\" ;reason=%s%s%s", escaped_name, encoded_number, ast_sockaddr_stringify_host_remote(&pvt->ourip), found_in_table ? "" : "\"", reason, found_in_table ? "" : "\""); } add_header(req, "Diversion", header_text); } static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri) { struct sip_pvt *pvt; int expires; epa_entry->publish_type = publish_type; if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_PUBLISH, NULL, NULL))) { return -1; } sip_pvt_lock(pvt); if (create_addr(pvt, epa_entry->destination, NULL, TRUE)) { sip_pvt_unlock(pvt); dialog_unlink_all(pvt); dialog_unref(pvt, "create_addr failed in transmit_publish. Unref dialog"); return -1; } ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt); ast_set_flag(&pvt->flags[0], SIP_OUTGOING); expires = (publish_type == SIP_PUBLISH_REMOVE) ? 0 : DEFAULT_PUBLISH_EXPIRES; pvt->expiry = expires; /* Bump refcount for sip_pvt's reference */ ao2_ref(epa_entry, +1); pvt->epa_entry = epa_entry; transmit_invite(pvt, SIP_PUBLISH, FALSE, 2, explicit_uri); sip_pvt_unlock(pvt); sip_scheddestroy(pvt, DEFAULT_TRANS_TIMEOUT); dialog_unref(pvt, "Done with the sip_pvt allocated for transmitting PUBLISH"); return 0; } /*! * \brief Build REFER/INVITE/OPTIONS/SUBSCRIBE message and transmit it * \param p sip_pvt structure * \param sipmethod * \param sdp unknown * \param init 0 = Prepare request within dialog, 1= prepare request, new branch, * 2= prepare new request and new dialog. do_proxy_auth calls this with init!=2 * \param explicit_uri */ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri) { struct sip_request req; struct ast_variable *var; if (init) {/* Bump branch even on initial requests */ p->branch ^= ast_random(); p->invite_branch = p->branch; build_via(p); } if (init > 1) { initreqprep(&req, p, sipmethod, explicit_uri); } else { /* If init=1, we should not generate a new branch. If it's 0, we need a new branch. */ reqprep(&req, p, sipmethod, 0, init ? 0 : 1); } if (p->options && p->options->auth) { add_header(&req, p->options->authheader, p->options->auth); } add_date(&req); if (sipmethod == SIP_REFER && p->refer) { /* Call transfer */ if (!ast_strlen_zero(p->refer->refer_to)) { add_header(&req, "Refer-To", p->refer->refer_to); } if (!ast_strlen_zero(p->refer->referred_by)) { add_header(&req, "Referred-By", p->refer->referred_by); } } else if (sipmethod == SIP_SUBSCRIBE) { if (p->subscribed == MWI_NOTIFICATION) { add_header(&req, "Event", "message-summary"); add_header(&req, "Accept", "application/simple-message-summary"); } else if (p->subscribed == CALL_COMPLETION) { add_header(&req, "Event", "call-completion"); add_header(&req, "Accept", "application/call-completion"); } add_expires(&req, p->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 (p->options && !ast_strlen_zero(p->options->replaces)) { add_header(&req, "Replaces", p->options->replaces); add_header(&req, "Require", "replaces"); } /* Add Session-Timers related headers */ if (st_get_mode(p, 0) == SESSION_TIMER_MODE_ORIGINATE || (st_get_mode(p, 0) == SESSION_TIMER_MODE_ACCEPT && st_get_se(p, FALSE) != DEFAULT_MIN_SE)) { char i2astr[10]; if (!p->stimer->st_interval) { p->stimer->st_interval = st_get_se(p, TRUE); } p->stimer->st_active = TRUE; if (st_get_mode(p, 0) == SESSION_TIMER_MODE_ORIGINATE) { snprintf(i2astr, sizeof(i2astr), "%d", p->stimer->st_interval); add_header(&req, "Session-Expires", i2astr); } snprintf(i2astr, sizeof(i2astr), "%d", st_get_se(p, FALSE)); add_header(&req, "Min-SE", i2astr); } add_header(&req, "Allow", ALLOWED_METHODS); add_supported(p, &req); if (p->owner && ((p->options && p->options->addsipheaders) || (p->refer && global_refer_addheaders))) { struct ast_channel *chan = p->owner; /* The owner channel */ struct varshead *headp; ast_channel_lock(chan); headp = ast_channel_varshead(chan); if (!headp) { ast_log(LOG_WARNING, "No Headp for the channel...ooops!\n"); } else { const struct ast_var_t *current; AST_LIST_TRAVERSE(headp, current, entries) { /* SIPADDHEADER: Add SIP header to outgoing call */ if (!strncmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) { char *content, *end; const char *header = ast_var_value(current); char *headdup = ast_strdupa(header); /* Strip of the starting " (if it's there) */ if (*headdup == '"') { headdup++; } if ((content = strchr(headdup, ':'))) { *content++ = '\0'; content = ast_skip_blanks(content); /* Skip white space */ /* Strip the ending " (if it's there) */ end = content + strlen(content) -1; if (*end == '"') { *end = '\0'; } add_header(&req, headdup, content); if (sipdebug) { ast_debug(1, "Adding SIP Header \"%s\" with content :%s: \n", headdup, content); } } } } } ast_channel_unlock(chan); } if ((sipmethod == SIP_INVITE || sipmethod == SIP_UPDATE) && ast_test_flag(&p->flags[0], SIP_SENDRPID)) add_rpid(&req, p); if (sipmethod == SIP_INVITE) { add_diversion(&req, p); } if (sdp) { offered_media_list_destroy(p); if (p->udptl && p->t38.state == T38_LOCAL_REINVITE) { ast_debug(1, "T38 is in state %u on channel %s\n", p->t38.state, p->owner ? ast_channel_name(p->owner) : ""); add_sdp(&req, p, FALSE, FALSE, TRUE); } else if (p->rtp) { try_suggested_sip_codec(p); add_sdp(&req, p, FALSE, TRUE, FALSE); } } else if (sipmethod == SIP_NOTIFY && p->notify) { for (var = p->notify->headers; var; var = var->next) { add_header(&req, var->name, var->value); } if (ast_str_strlen(p->notify->content)) { add_content(&req, ast_str_buffer(p->notify->content)); } } else if (sipmethod == SIP_PUBLISH) { switch (p->epa_entry->static_data->event) { case CALL_COMPLETION: add_header(&req, "Event", "call-completion"); add_expires(&req, p->expiry); if (p->epa_entry->publish_type != SIP_PUBLISH_INITIAL) { add_header(&req, "SIP-If-Match", p->epa_entry->entity_tag); } if (!ast_strlen_zero(p->epa_entry->body)) { add_header(&req, "Content-Type", "application/pidf+xml"); add_content(&req, p->epa_entry->body); } default: break; } } if (!p->initreq.headers || init > 2) { initialize_initreq(p, &req); } if (sipmethod == SIP_INVITE || sipmethod == SIP_SUBSCRIBE) { p->lastinvite = p->ocseq; } return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq); } /*! \brief Send a subscription or resubscription for MWI */ static int sip_subscribe_mwi_do(const void *data) { struct sip_subscription_mwi *mwi = (struct sip_subscription_mwi*)data; if (!mwi) { return -1; } mwi->resub = -1; __sip_subscribe_mwi_do(mwi); ao2_t_ref(mwi, -1, "unref mwi to balance ast_sched_add"); return 0; } static void on_dns_update_registry(struct ast_sockaddr *old, struct ast_sockaddr *new, void *data) { struct sip_registry *reg = data; const char *old_str; /* This shouldn't happen, but just in case */ if (ast_sockaddr_isnull(new)) { ast_debug(1, "Empty sockaddr change...ignoring!\n"); return; } if (!ast_sockaddr_port(new)) { ast_sockaddr_set_port(new, reg->portno); } old_str = ast_strdupa(ast_sockaddr_stringify(old)); ast_debug(1, "Changing registry %s from %s to %s\n", S_OR(reg->peername, reg->hostname), old_str, ast_sockaddr_stringify(new)); ast_sockaddr_copy(®->us, new); } static void on_dns_update_peer(struct ast_sockaddr *old, struct ast_sockaddr *new, void *data) { struct sip_peer *peer = data; const char *old_str; /* This shouldn't happen, but just in case */ if (ast_sockaddr_isnull(new)) { ast_debug(1, "Empty sockaddr change...ignoring!\n"); return; } if (!ast_sockaddr_isnull(&peer->addr)) { ao2_unlink(peers_by_ip, peer); } if (!ast_sockaddr_port(new)) { ast_sockaddr_set_port(new, default_sip_port(peer->socket.type)); } old_str = ast_strdupa(ast_sockaddr_stringify(old)); ast_debug(1, "Changing peer %s address from %s to %s\n", peer->name, old_str, ast_sockaddr_stringify(new)); ao2_lock(peer); ast_sockaddr_copy(&peer->addr, new); ao2_unlock(peer); ao2_link(peers_by_ip, peer); } static void on_dns_update_mwi(struct ast_sockaddr *old, struct ast_sockaddr *new, void *data) { struct sip_subscription_mwi *mwi = data; const char *old_str; /* This shouldn't happen, but just in case */ if (ast_sockaddr_isnull(new)) { ast_debug(1, "Empty sockaddr change...ignoring!\n"); return; } old_str = ast_strdupa(ast_sockaddr_stringify(old)); ast_debug(1, "Changing mwi %s from %s to %s\n", mwi->hostname, old_str, ast_sockaddr_stringify(new)); ast_sockaddr_copy(&mwi->us, new); } /*! \brief Actually setup an MWI subscription or resubscribe */ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi) { /* If we have no DNS manager let's do a lookup */ if (!mwi->dnsmgr) { char transport[MAXHOSTNAMELEN]; snprintf(transport, sizeof(transport), "_%s._%s", get_srv_service(mwi->transport), get_srv_protocol(mwi->transport)); mwi->us.ss.ss_family = get_address_family_filter(mwi->transport); /* Filter address family */ ao2_t_ref(mwi, +1, "dnsmgr reference to mwi"); ast_dnsmgr_lookup_cb(mwi->hostname, &mwi->us, &mwi->dnsmgr, sip_cfg.srvlookup ? transport : NULL, on_dns_update_mwi, 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->call) { transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 0, NULL); return 0; } /* Create a dialog that we will use for the subscription */ if (!(mwi->call = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL, NULL))) { return -1; } ref_proxy(mwi->call, obproxy_get(mwi->call, NULL)); if (!ast_sockaddr_port(&mwi->us) && mwi->portno) { ast_sockaddr_set_port(&mwi->us, mwi->portno); } /* Setup the destination of our subscription */ if (create_addr(mwi->call, mwi->hostname, &mwi->us, 0)) { dialog_unlink_all(mwi->call); mwi->call = dialog_unref(mwi->call, "unref dialog after unlink_all"); return 0; } mwi->call->expiry = mwi_expiry; if (!mwi->dnsmgr && mwi->portno) { ast_sockaddr_set_port(&mwi->call->sa, mwi->portno); ast_sockaddr_set_port(&mwi->call->recv, mwi->portno); } else { mwi->portno = ast_sockaddr_port(&mwi->call->sa); } /* Set various other information */ if (!ast_strlen_zero(mwi->authuser)) { ast_string_field_set(mwi->call, peername, mwi->authuser); ast_string_field_set(mwi->call, authname, mwi->authuser); ast_string_field_set(mwi->call, fromuser, mwi->authuser); } else { ast_string_field_set(mwi->call, peername, mwi->username); ast_string_field_set(mwi->call, authname, mwi->username); ast_string_field_set(mwi->call, fromuser, mwi->username); } ast_string_field_set(mwi->call, username, mwi->username); if (!ast_strlen_zero(mwi->secret)) { ast_string_field_set(mwi->call, peersecret, mwi->secret); } set_socket_transport(&mwi->call->socket, mwi->transport); mwi->call->socket.port = htons(mwi->portno); ast_sip_ouraddrfor(&mwi->call->sa, &mwi->call->ourip, mwi->call); build_contact(mwi->call); build_via(mwi->call); /* Change the dialog callid. */ change_callid_pvt(mwi->call, NULL); ast_set_flag(&mwi->call->flags[0], SIP_OUTGOING); /* Associate the call with us */ mwi->call->mwi = ao2_t_bump(mwi, "Reference mwi from it's call"); mwi->call->subscribed = MWI_NOTIFICATION; /* Actually send the packet */ transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 2, NULL); return 0; } /*! * \internal * \brief Find the channel that is causing the RINGING update, ref'd */ static struct ast_channel *find_ringing_channel(struct ao2_container *device_state_info, struct sip_pvt *p) { struct ao2_iterator citer; struct ast_device_state_info *device_state; struct ast_channel *c = NULL; struct timeval tv = {0,}; /* iterate ringing devices and get the oldest of all causing channels */ citer = ao2_iterator_init(device_state_info, 0); for (; (device_state = ao2_iterator_next(&citer)); ao2_ref(device_state, -1)) { if (!device_state->causing_channel || (device_state->device_state != AST_DEVICE_RINGING && device_state->device_state != AST_DEVICE_RINGINUSE)) { continue; } ast_channel_lock(device_state->causing_channel); if (ast_tvzero(tv) || ast_tvcmp(ast_channel_creationtime(device_state->causing_channel), tv) < 0) { c = device_state->causing_channel; tv = ast_channel_creationtime(c); } ast_channel_unlock(device_state->causing_channel); } ao2_iterator_destroy(&citer); return c ? ast_channel_ref(c) : NULL; } /* XXX Candidate for moving into its own file */ static int allow_notify_user_presence(struct sip_pvt *p) { return (strstr(p->useragent, "Digium")) ? 1 : 0; } /*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */ static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto) { enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; const char *statestring = "terminated"; const char *pidfstate = "--"; const char *pidfnote ="Ready"; char hint[AST_MAX_EXTENSION]; switch (data->state) { case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): statestring = (sip_cfg.notifyringing) ? "early" : "confirmed"; local_state = NOTIFY_INUSE; pidfstate = "busy"; pidfnote = "Ringing"; break; case AST_EXTENSION_RINGING: statestring = "early"; local_state = NOTIFY_INUSE; pidfstate = "busy"; pidfnote = "Ringing"; break; case AST_EXTENSION_INUSE: statestring = "confirmed"; local_state = NOTIFY_INUSE; pidfstate = "busy"; pidfnote = "On the phone"; break; case AST_EXTENSION_BUSY: statestring = "confirmed"; local_state = NOTIFY_CLOSED; pidfstate = "busy"; pidfnote = "On the phone"; break; case AST_EXTENSION_UNAVAILABLE: statestring = "terminated"; local_state = NOTIFY_CLOSED; pidfstate = "away"; pidfnote = "Unavailable"; break; case AST_EXTENSION_ONHOLD: statestring = "confirmed"; local_state = NOTIFY_CLOSED; pidfstate = "busy"; pidfnote = "On hold"; break; case AST_EXTENSION_NOT_INUSE: default: /* Default setting */ break; } /* Check which device/devices we are watching and if they are registered */ if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) { char *hint2; char *individual_hint = NULL; int hint_count = 0, unavailable_count = 0; /* strip off any possible PRESENCE providers from hint */ if ((hint2 = strrchr(hint, ','))) { *hint2 = '\0'; } hint2 = hint; while ((individual_hint = strsep(&hint2, "&"))) { hint_count++; if (ast_device_state(individual_hint) == AST_DEVICE_UNAVAILABLE) unavailable_count++; } /* If none of the hinted devices are registered, we will * override notification and show no availability. */ if (hint_count > 0 && hint_count == unavailable_count) { local_state = NOTIFY_CLOSED; pidfstate = "away"; pidfnote = "Not online"; } } switch (subscribed) { case XPIDF_XML: case CPIM_PIDF_XML: ast_str_append(tmp, 0, "\n" "\n" "\n"); ast_str_append(tmp, 0, "\n", mfrom); ast_str_append(tmp, 0, "\n", exten); ast_str_append(tmp, 0, "
\n", mto); ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); ast_str_append(tmp, 0, "
\n
\n
\n"); break; case PIDF_XML: /* Eyebeam supports this format */ ast_str_append(tmp, 0, "\n" "\n", mfrom); ast_str_append(tmp, 0, "\n"); if (pidfstate[0] != '-') { ast_str_append(tmp, 0, "\n", pidfstate); } ast_str_append(tmp, 0, "\n"); ast_str_append(tmp, 0, "%s\n", pidfnote); /* Note */ ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ ast_str_append(tmp, 0, "%s\n", mto); if (pidfstate[0] == 'b') /* Busy? Still open ... */ ast_str_append(tmp, 0, "open\n"); else ast_str_append(tmp, 0, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed"); if (allow_notify_user_presence(p) && (data->presence_state != AST_PRESENCE_INVALID) && (data->presence_state != AST_PRESENCE_NOT_SET)) { ast_str_append(tmp, 0, "\n"); ast_str_append(tmp, 0, "\n"); ast_str_append(tmp, 0, "\n"); ast_str_append(tmp, 0, "%s\n", ast_presence_state2str(data->presence_state), S_OR(data->presence_subtype, ""), S_OR(data->presence_message, "")); ast_str_append(tmp, 0, "\n"); ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", "PresenceState: %s\r\n" "Subtype: %s\r\n" "Message: %s", ast_presence_state2str(data->presence_state), S_OR(data->presence_subtype, ""), S_OR(data->presence_message, "")); } ast_str_append(tmp, 0, "\n\n"); break; case DIALOG_INFO_XML: /* SNOM subscribes in this format */ ast_str_append(tmp, 0, "\n"); ast_str_append(tmp, 0, "\n", p->dialogver, full ? "full" : "partial", mto); if (data->state > 0 && (data->state & AST_EXTENSION_RINGING) && sip_cfg.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(mto); /* It may seem odd to base the remote_target on the To header here, * but testing by reporters on issue ASTERISK-16735 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(mto); ast_xml_escape(exten, local_display, sizeof(local_display)); ast_xml_escape(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_cfg.notifycid) { struct ast_channel *callee; callee = find_ringing_channel(data->device_state_info, p); if (callee) { static char *anonymous = "anonymous"; static char *invalid = "anonymous.invalid"; char *cid_num; char *connected_num; int need; int cid_num_restricted, connected_num_restricted; ast_channel_lock(callee); cid_num_restricted = (ast_channel_caller(callee)->id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED; cid_num = S_COR(ast_channel_caller(callee)->id.number.valid, S_COR(cid_num_restricted, anonymous, ast_channel_caller(callee)->id.number.str), ""); need = strlen(cid_num) + (cid_num_restricted ? strlen(invalid) : strlen(p->fromdomain)) + sizeof("sip:@"); local_target = ast_alloca(need); snprintf(local_target, need, "sip:%s@%s", cid_num, cid_num_restricted ? invalid : p->fromdomain); ast_xml_escape(S_COR(ast_channel_caller(callee)->id.name.valid, S_COR((ast_channel_caller(callee)->id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED, anonymous, ast_channel_caller(callee)->id.name.str), ""), local_display, sizeof(local_display)); connected_num_restricted = (ast_channel_connected(callee)->id.number.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED; connected_num = S_COR(ast_channel_connected(callee)->id.number.valid, S_COR(connected_num_restricted, anonymous, ast_channel_connected(callee)->id.number.str), ""); need = strlen(connected_num) + (connected_num_restricted ? strlen(invalid) : strlen(p->fromdomain)) + sizeof("sip:@"); remote_target = ast_alloca(need); snprintf(remote_target, need, "sip:%s@%s", connected_num, connected_num_restricted ? invalid : p->fromdomain); ast_xml_escape(S_COR(ast_channel_connected(callee)->id.name.valid, S_COR((ast_channel_connected(callee)->id.name.presentation & AST_PRES_RESTRICTION) == AST_PRES_RESTRICTED, anonymous, ast_channel_connected(callee)->id.name.str), ""), remote_display, sizeof(remote_display)); ast_channel_unlock(callee); callee = ast_channel_unref(callee); } /* 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_cfg.pedanticsipchecking) { ast_str_append(tmp, 0, "\n", exten, p->callid, p->theirtag, p->tag); } else { ast_str_append(tmp, 0, "\n", exten, p->callid); } ast_str_append(tmp, 0, "\n" /* See the limitations of this above. Luckily the phone seems to still be happy when these values are not correct. */ "%s\n" "\n" "\n" "\n" "%s\n" "\n" "\n", remote_display, remote_target, remote_target, local_display, local_target, local_target); } else { ast_str_append(tmp, 0, "\n", exten); } } else { ast_str_append(tmp, 0, "\n", exten); } ast_str_append(tmp, 0, "%s\n", statestring); if (data->state == AST_EXTENSION_ONHOLD) { ast_str_append(tmp, 0, "\n\n" "\n" "\n\n", mto); } ast_str_append(tmp, 0, "\n\n"); break; case NONE: default: break; } } static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state) { struct sip_request req; struct sip_cc_agent_pvt *agent_pvt = agent->private_data; char uri[SIPBUFSIZE]; char state_str[64]; char subscription_state_hdr[64]; if (state < CC_QUEUED || state > CC_READY) { ast_log(LOG_WARNING, "Invalid state provided for transmit_cc_notify (%u)\n", state); return -1; } reqprep(&req, subscription, SIP_NOTIFY, 0, TRUE); snprintf(state_str, sizeof(state_str), "%s\r\n", sip_cc_notify_state_map[state].state_string); add_header(&req, "Event", "call-completion"); add_header(&req, "Content-Type", "application/call-completion"); snprintf(subscription_state_hdr, sizeof(subscription_state_hdr), "active;expires=%d", subscription->expiry); add_header(&req, "Subscription-State", subscription_state_hdr); if (state == CC_READY) { generate_uri(subscription, agent_pvt->notify_uri, sizeof(agent_pvt->notify_uri)); snprintf(uri, sizeof(uri) - 1, "cc-URI: %s\r\n", agent_pvt->notify_uri); } add_content(&req, state_str); if (state == CC_READY) { add_content(&req, uri); } return send_request(subscription, &req, XMIT_RELIABLE, subscription->ocseq); } /*! \brief Used in the SUBSCRIBE notification subsystem (RFC3265) */ static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout) { struct ast_str *tmp = ast_str_alloca(4000); char from[256], to[256]; char *c, *mfrom, *mto; struct sip_request req; const struct cfsubscription_types *subscriptiontype; /* If the subscription has not yet been accepted do not send a NOTIFY */ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { return 0; } memset(from, 0, sizeof(from)); memset(to, 0, sizeof(to)); subscriptiontype = find_subscription_type(p->subscribed); ast_copy_string(from, sip_get_header(&p->initreq, "From"), sizeof(from)); c = get_in_brackets(from); if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); return -1; } mfrom = remove_uri_parameters(c); ast_copy_string(to, sip_get_header(&p->initreq, "To"), sizeof(to)); c = get_in_brackets(to); if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); return -1; } mto = remove_uri_parameters(c); reqprep(&req, p, SIP_NOTIFY, 0, 1); switch(data->state) { case AST_EXTENSION_DEACTIVATED: if (timeout) add_header(&req, "Subscription-State", "terminated;reason=timeout"); else { add_header(&req, "Subscription-State", "terminated;reason=probation"); add_header(&req, "Retry-After", "60"); } break; case AST_EXTENSION_REMOVED: add_header(&req, "Subscription-State", "terminated;reason=noresource"); break; default: if (p->expiry) add_header(&req, "Subscription-State", "active"); else /* Expired */ add_header(&req, "Subscription-State", "terminated;reason=timeout"); } switch (p->subscribed) { case XPIDF_XML: case CPIM_PIDF_XML: add_header(&req, "Event", subscriptiontype->event); state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); add_header(&req, "Content-Type", subscriptiontype->mediatype); p->dialogver++; break; case PIDF_XML: /* Eyebeam supports this format */ add_header(&req, "Event", subscriptiontype->event); state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); add_header(&req, "Content-Type", subscriptiontype->mediatype); p->dialogver++; break; case DIALOG_INFO_XML: /* SNOM subscribes in this format */ add_header(&req, "Event", subscriptiontype->event); state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); add_header(&req, "Content-Type", subscriptiontype->mediatype); p->dialogver++; break; case NONE: default: break; } add_content(&req, ast_str_buffer(tmp)); p->pendinginvite = p->ocseq; /* Remember that we have a pending NOTIFY in order not to confuse the NOTIFY subsystem */ /* Send as XMIT_CRITICAL as we may never receive a 200 OK Response which clears p->pendinginvite. * * extensionstate_update() uses p->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. */ /* RFC6665 4.2.2. Sending State Information to Subscribers * If the NOTIFY request fails due to expiration of SIP Timer F (transaction timeout), * the notifier SHOULD remove the subscription. */ return send_request(p, &req, XMIT_CRITICAL, p->ocseq); } /*! \brief Notify user of messages waiting in voicemail (RFC3842) \note - Notification only works for registered peers with mailbox= definitions in sip.conf - We use the SIP Event package message-summary MIME type defaults to "application/simple-message-summary"; */ static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten) { struct sip_request req; struct ast_str *out = ast_str_alloca(500); int ourport = (p->fromdomainport && (p->fromdomainport != STANDARD_SIP_PORT)) ? p->fromdomainport : ast_sockaddr_port(&p->ourip); const char *domain; const char *exten = S_OR(vmexten, default_vmexten); initreqprep(&req, p, SIP_NOTIFY, NULL); add_header(&req, "Event", "message-summary"); add_header(&req, "Content-Type", default_notifymime); ast_str_append(&out, 0, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no"); /* domain initialization occurs here because initreqprep changes ast_sockaddr_stringify string. */ domain = S_OR(p->fromdomain, ast_sockaddr_stringify_host_remote(&p->ourip)); if (!sip_standard_port(p->socket.type, ourport)) { if (p->socket.type == AST_TRANSPORT_UDP) { ast_str_append(&out, 0, "Message-Account: sip:%s@%s:%d\r\n", exten, domain, ourport); } else { ast_str_append(&out, 0, "Message-Account: sip:%s@%s:%d;transport=%s\r\n", exten, domain, ourport, sip_get_transport(p->socket.type)); } } else { if (p->socket.type == AST_TRANSPORT_UDP) { ast_str_append(&out, 0, "Message-Account: sip:%s@%s\r\n", exten, domain); } else { ast_str_append(&out, 0, "Message-Account: sip:%s@%s;transport=%s\r\n", exten, domain, sip_get_transport(p->socket.type)); } } /* Cisco has a bug in the SIP stack where it can't accept the (0/0) notification. This can temporarily be disabled in sip.conf with the "buggymwi" option */ ast_str_append(&out, 0, "Voice-Message: %d/%d%s\r\n", newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_BUGGY_MWI) ? "" : " (0/0)")); if (p->subscribed) { if (p->expiry) { add_header(&req, "Subscription-State", "active"); } else { /* Expired */ add_header(&req, "Subscription-State", "terminated;reason=timeout"); } } add_content(&req, ast_str_buffer(out)); if (!p->initreq.headers) { initialize_initreq(p, &req); } return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } /*! \brief Notify a transferring party of the status of transfer (RFC3515) */ static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate) { struct sip_request req; char tmp[SIPBUFSIZE/2]; reqprep(&req, p, SIP_NOTIFY, 0, 1); snprintf(tmp, sizeof(tmp), "refer;id=%d", cseq); add_header(&req, "Event", tmp); add_header(&req, "Subscription-state", terminate ? "terminated;reason=noresource" : "active"); add_header(&req, "Content-Type", "message/sipfrag;version=2.0"); add_header(&req, "Allow", ALLOWED_METHODS); add_supported(p, &req); snprintf(tmp, sizeof(tmp), "SIP/2.0 %s\r\n", message); add_content(&req, tmp); if (!p->initreq.headers) { initialize_initreq(p, &req); } return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } static int manager_sipnotify(struct mansession *s, const struct message *m) { const char *channame = astman_get_header(m, "Channel"); struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL); struct sip_pvt *p; struct ast_variable *header, *var; if (ast_strlen_zero(channame)) { astman_send_error(s, m, "SIPNotify requires a channel name"); return 0; } if (!strncasecmp(channame, "sip/", 4)) { channame += 4; } if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, NULL))) { astman_send_error(s, m, "Unable to build sip pvt data for notify (memory/socket error)"); return 0; } if (create_addr(p, channame, NULL, 0)) { /* Maybe they're not registered, etc. */ dialog_unlink_all(p); dialog_unref(p, "unref dialog inside for loop" ); /* sip_destroy(p); */ astman_send_error(s, m, "Could not create address"); return 0; } /* Notify is outgoing call */ ast_set_flag(&p->flags[0], SIP_OUTGOING); sip_notify_alloc(p); p->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(p->notify->content)) ast_str_append(&p->notify->content, 0, "\r\n"); ast_str_append(&p->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; } } sip_scheddestroy(p, SIP_TRANS_TIMEOUT); transmit_invite(p, SIP_NOTIFY, 0, 2, NULL); dialog_unref(p, "bump down the count of p since we're done with it."); astman_send_ack(s, m, "Notify Sent"); ast_variables_destroy(vars); return 0; } /*! \brief Send a provisional response indicating that a call was redirected */ static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen) { struct sip_request resp; if (ast_channel_state(p->owner) == AST_STATE_UP || ast_test_flag(&p->flags[0], SIP_OUTGOING)) { return; } respprep(&resp, p, "181 Call is being forwarded", &p->initreq); add_diversion(&resp, p); send_response(p, &resp, XMIT_UNRELIABLE, 0); } /*! \brief Notify peer that the connected line has changed */ static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen) { struct ast_party_id connected_id = ast_channel_connected_effective_id(p->owner); if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { return; } if (!connected_id.number.valid || ast_strlen_zero(connected_id.number.str)) { return; } append_history(p, "ConnectedLine", "%s party is now %s <%s>", ast_test_flag(&p->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(p->owner) == AST_STATE_UP || ast_test_flag(&p->flags[0], SIP_OUTGOING)) { struct sip_request req; if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1); add_header(&req, "Allow", ALLOWED_METHODS); add_supported(p, &req); add_rpid(&req, p); add_sdp(&req, p, FALSE, TRUE, FALSE); initialize_initreq(p, &req); p->lastinvite = p->ocseq; ast_set_flag(&p->flags[0], SIP_OUTGOING); p->invitestate = INV_CALLING; send_request(p, &req, XMIT_CRITICAL, p->ocseq); } else if ((is_method_allowed(&p->allowed_methods, SIP_UPDATE)) && (!ast_strlen_zero(p->okcontacturi))) { reqprep(&req, p, SIP_UPDATE, 0, 1); add_rpid(&req, p); add_header(&req, "X-Asterisk-rpid-update", "Yes"); send_request(p, &req, XMIT_CRITICAL, p->ocseq); } else { /* We cannot send the update yet, so we have to wait until we can */ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); } } else { ast_set_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); if (ast_test_flag(&p->flags[1], SIP_PAGE2_RPID_IMMEDIATE)) { struct sip_request resp; if ((ast_channel_state(p->owner) == AST_STATE_RING) && !ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT)) { ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); respprep(&resp, p, "180 Ringing", &p->initreq); add_rpid(&resp, p); send_response(p, &resp, XMIT_UNRELIABLE, 0); ast_set_flag(&p->flags[0], SIP_RINGING); } else if (ast_channel_state(p->owner) == AST_STATE_RINGING) { ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); respprep(&resp, p, "183 Session Progress", &p->initreq); add_rpid(&resp, p); send_response(p, &resp, XMIT_UNRELIABLE, 0); ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } else { ast_debug(1, "Unable able to send update to '%s' in state '%s'\n", ast_channel_name(p->owner), ast_state2str(ast_channel_state(p->owner))); } } } } static const struct _map_x_s regstatestrings[] = { { REG_STATE_FAILED, "Failed" }, { REG_STATE_UNREGISTERED, "Unregistered"}, { REG_STATE_REGSENT, "Request Sent"}, { REG_STATE_AUTHSENT, "Auth. Sent"}, { REG_STATE_REGISTERED, "Registered"}, { REG_STATE_REJECTED, "Rejected"}, { REG_STATE_TIMEOUT, "Timeout"}, { REG_STATE_NOAUTH, "No Authentication"}, { -1, NULL } /* terminator */ }; /*! \brief Convert registration state status to string */ static const char *regstate2str(enum sipregistrystate regstate) { return map_x_s(regstatestrings, regstate, "Unknown"); } static void sip_publish_registry(const char *username, const char *domain, const char *status) { ast_system_publish_registry("SIP", username, domain, status, NULL); } /*! \brief 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. */ static int sip_reregister(const void *data) { /* if we are here, we know that we need to reregister. */ struct sip_registry *r = (struct sip_registry *) data; /* if we couldn't get a reference to the registry object, punt */ if (!r) { return 0; } if (r->call && r->call->do_history) { append_history(r->call, "RegistryRenew", "Account: %s@%s", r->username, r->hostname); } /* Since registry's are only added/removed by the the monitor thread, this may be overkill to reference/dereference at all here */ if (sipdebug) { ast_log(LOG_NOTICE, " -- Re-registration for %s@%s\n", r->username, r->hostname); } r->expire = -1; r->expiry = r->configured_expiry; __sip_do_register(r); ao2_t_ref(r, -1, "unref the re-register scheduled event"); return 0; } /*! \brief Register with SIP proxy \return see \ref __sip_xmit */ static int __sip_do_register(struct sip_registry *r) { int res; res = transmit_register(r, SIP_REGISTER, NULL, NULL); return res; } /*! \brief Registration timeout, register again * Registered as a timeout handler during transmit_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. */ static int sip_reg_timeout(const void *data) { /* if we are here, our registration timed out, so we'll just do it over */ struct sip_registry *r = (struct sip_registry *)data; /* the ref count should have been bumped when the sched item was added */ struct sip_pvt *p; /* if we couldn't get a reference to the registry object, punt */ if (!r) { return 0; } if (r->dnsmgr) { /* If the registration has timed out, maybe the IP changed. Force a refresh. */ ast_dnsmgr_refresh(r->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 (r->call) { /* Unlink us, destroy old call. Locking is not relevant here because all this happens in the single SIP manager thread. */ p = r->call; sip_pvt_lock(p); pvt_set_needdestroy(p, "registration timeout"); /* Pretend to ACK anything just in case */ __sip_pretend_ack(p); sip_pvt_unlock(p); /* decouple the two objects */ /* p->registry == r, so r has 2 refs, and the unref won't take the object away */ ao2_t_replace(p->registry, NULL, "p->registry unreffed"); r->call = dialog_unref(r->call, "unrefing r->call"); } /* If we have a limit, stop registration and give up */ r->timeout = -1; if (global_regattempts_max && r->regattempts >= global_regattempts_max) { /* Ok, enough is enough. Don't try any more */ /* We could add an external notification here... steal it from app_voicemail :-) */ ast_log(LOG_NOTICE, " -- Last Registration Attempt #%d failed, Giving up forever trying to register '%s@%s'\n", r->regattempts, r->username, r->hostname); r->regstate = REG_STATE_FAILED; } else { r->regstate = REG_STATE_UNREGISTERED; transmit_register(r, SIP_REGISTER, NULL, NULL); ast_log(LOG_NOTICE, " -- Registration for '%s@%s' timed out, trying again (Attempt #%d)\n", r->username, r->hostname, r->regattempts); } sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate)); ao2_t_ref(r, -1, "unreffing registry_unref r"); return 0; } static const char *sip_sanitized_host(const char *host) { struct ast_sockaddr addr = { { 0, 0, }, }; /* 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. */ if (!ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID)) { return host; } return ast_sockaddr_stringify_host_remote(&addr); } /*! \brief Transmit register to SIP proxy or UA * auth = NULL on the initial registration (from sip_reregister()) */ static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader) { struct sip_request req; char from[256]; char to[256]; char tmp[80]; char addr[80]; struct sip_pvt *p; struct sip_peer *peer = NULL; int res; int portno = 0; /* exit if we are already in process with this registrar ?*/ if (r == NULL || ((auth == NULL) && (r->regstate == REG_STATE_REGSENT || r->regstate == REG_STATE_AUTHSENT))) { if (r) { ast_log(LOG_NOTICE, "Strange, trying to register %s@%s when registration already pending\n", r->username, r->hostname); } return 0; } if (r->dnsmgr == NULL) { char transport[MAXHOSTNAMELEN]; peer = sip_find_peer(r->hostname, NULL, TRUE, FINDPEERS, FALSE, 0); snprintf(transport, sizeof(transport), "_%s._%s",get_srv_service(r->transport), get_srv_protocol(r->transport)); /* have to use static sip_get_transport function */ r->us.ss.ss_family = get_address_family_filter(r->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. obproxy_get is safe to call with either of r->call * or peer NULL. Since we're only concerned with its existence, we're not going to * bother getting a ref to the proxy*/ if (!obproxy_get(r->call, peer)) { ao2_t_ref(r, +1, "add reg ref for dnsmgr"); ast_dnsmgr_lookup_cb(peer ? peer->tohost : r->hostname, &r->us, &r->dnsmgr, sip_cfg.srvlookup ? transport : NULL, on_dns_update_registry, r); if (!r->dnsmgr) { /*dnsmgr refresh disabled, no reference added! */ ao2_t_ref(r, -1, "remove reg ref, dnsmgr disabled"); } } if (peer) { peer = sip_unref_peer(peer, "removing peer ref for dnsmgr_lookup"); } } if (r->call) { /* We have a registration */ if (!auth) { ast_log(LOG_WARNING, "Already have a REGISTER going on to %s@%s?? \n", r->username, r->hostname); return 0; } else { p = dialog_ref(r->call, "getting a copy of the r->call dialog in transmit_register"); ast_string_field_set(p, 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 (!r->callid_valid) { build_callid_registry(r, &internip, default_fromdomain); build_localtag_registry(r); r->callid_valid = TRUE; } /* Allocate SIP dialog for registration */ if (!(p = sip_alloc( r->callid, NULL, 0, SIP_REGISTER, NULL, NULL))) { 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(p, tag, r->localtag); if (p->do_history) { append_history(p, "RegistryInit", "Account: %s@%s", r->username, r->hostname); } p->socket.type = r->transport; /* Use port number specified if no SRV record was found */ if (!ast_sockaddr_isnull(&r->us)) { if (!ast_sockaddr_port(&r->us) && r->portno) { ast_sockaddr_set_port(&r->us, r->portno); } /* 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_find_peer(S_OR(r->peername, r->hostname), NULL, TRUE, FINDPEERS, FALSE, 0))) { if (ast_sockaddr_cmp(&peer->addr, &r->us)) { on_dns_update_peer(&peer->addr, &r->us, peer); } peer = sip_unref_peer(peer, "unref after sip_find_peer"); } } /* Find address to hostname */ if (create_addr(p, S_OR(r->peername, r->hostname), &r->us, 0)) { /* we have what we hope is a temporary network error, * probably DNS. We need to reschedule a registration try */ dialog_unlink_all(p); p = dialog_unref(p, "unref dialog after unlink_all"); if (r->timeout > -1) { AST_SCHED_REPLACE_UNREF(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r, ao2_t_ref(_data, -1, "del for REPLACE of registry ptr"), ao2_t_ref(r, -1, "object ptr dec when SCHED_REPLACE add failed"), ao2_t_ref(r, +1, "add for REPLACE registry ptr")); ast_log(LOG_WARNING, "Still have a registration timeout for %s@%s (create_addr() error), %d\n", r->username, r->hostname, r->timeout); } else { r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, ao2_t_bump(r, "add for REPLACE registry ptr")); ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n", r->username, r->hostname, global_reg_timeout); } r->regattempts++; return 0; } /* Copy back Call-ID in case create_addr changed it */ ast_string_field_set(r, callid, p->callid); if (!r->dnsmgr && r->portno) { ast_sockaddr_set_port(&p->sa, r->portno); ast_sockaddr_set_port(&p->recv, r->portno); } if (!ast_strlen_zero(p->fromdomain)) { portno = (p->fromdomainport) ? p->fromdomainport : STANDARD_SIP_PORT; } else if (!ast_strlen_zero(r->regdomain)) { portno = (r->regdomainport) ? r->regdomainport : STANDARD_SIP_PORT; } else { portno = ast_sockaddr_port(&p->sa); } ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Registration is outgoing call */ r->call = dialog_ref(p, "copying dialog into registry r->call"); /* Save pointer to SIP dialog */ p->registry = ao2_t_bump(r, "transmit_register: addref to p->registry in transmit_register"); /* Add pointer to registry in packet */ if (!ast_strlen_zero(r->secret)) { /* Secret (password) */ ast_string_field_set(p, peersecret, r->secret); } if (!ast_strlen_zero(r->md5secret)) ast_string_field_set(p, peermd5secret, r->md5secret); /* User name in this realm - if authuser is set, use that, otherwise use username */ if (!ast_strlen_zero(r->authuser)) { ast_string_field_set(p, peername, r->authuser); ast_string_field_set(p, authname, r->authuser); } else if (!ast_strlen_zero(r->username)) { ast_string_field_set(p, peername, r->username); ast_string_field_set(p, authname, r->username); ast_string_field_set(p, fromuser, r->username); } if (!ast_strlen_zero(r->username)) { ast_string_field_set(p, username, r->username); } /* Save extension in packet */ if (!ast_strlen_zero(r->callback)) { ast_string_field_set(p, exten, r->callback); } /* Set transport and port so the correct contact is built */ set_socket_transport(&p->socket, r->transport); if (r->transport == AST_TRANSPORT_TLS || r->transport == AST_TRANSPORT_TCP) { p->socket.port = htons(ast_sockaddr_port(&sip_tcp_desc.local_address)); } /* 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 */ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); build_contact(p); } /* set up a timeout */ if (auth == NULL) { if (r->timeout > -1) { ast_log(LOG_WARNING, "Still have a registration timeout, #%d - deleting it\n", r->timeout); } AST_SCHED_REPLACE_UNREF(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r, ao2_t_ref(_data, -1, "reg ptr unrefed from del in SCHED_REPLACE"), ao2_t_ref(r, -1, "reg ptr unrefed from add failure in SCHED_REPLACE"), ao2_t_ref(r, +1, "reg ptr reffed from add in SCHED_REPLACE")); ast_debug(1, "Scheduled a registration timeout for %s id #%d \n", r->hostname, r->timeout); } snprintf(from, sizeof(from), ";tag=%s", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost)), p->tag); if (!ast_strlen_zero(p->theirtag)) { snprintf(to, sizeof(to), ";tag=%s", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost)), p->theirtag); } else { snprintf(to, sizeof(to), "", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost))); } /* Fromdomain is what we are registering to, regardless of actual host name from SRV */ if (portno && portno != STANDARD_SIP_PORT) { snprintf(addr, sizeof(addr), "sip:%s:%d", S_OR(p->fromdomain,S_OR(r->regdomain, sip_sanitized_host(r->hostname))), portno); } else { snprintf(addr, sizeof(addr), "sip:%s", S_OR(p->fromdomain,S_OR(r->regdomain, sip_sanitized_host(r->hostname)))); } ast_string_field_set(p, uri, addr); p->branch ^= ast_random(); init_req(&req, sipmethod, addr); /* Add to CSEQ */ snprintf(tmp, sizeof(tmp), "%u %s", ++r->ocseq, sip_methods[sipmethod].text); p->ocseq = r->ocseq; build_via(p); add_header(&req, "Via", p->via); add_max_forwards(p, &req); add_header(&req, "From", from); add_header(&req, "To", to); add_header(&req, "Call-ID", p->callid); add_header(&req, "CSeq", tmp); add_supported(p, &req); if (!ast_strlen_zero(global_useragent)) add_header(&req, "User-Agent", global_useragent); if (auth) { /* Add auth header */ add_header(&req, authheader, auth); } else if (!ast_strlen_zero(r->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 (sipdebug) { ast_debug(1, " >>> Re-using Auth data for %s@%s\n", r->username, r->hostname); } ast_string_field_set(p, realm, r->realm); ast_string_field_set(p, nonce, r->nonce); ast_string_field_set(p, domain, r->authdomain); ast_string_field_set(p, opaque, r->opaque); ast_string_field_set(p, qop, r->qop); p->noncecount = ++r->noncecount; memset(digest, 0, sizeof(digest)); if(!build_reply_digest(p, sipmethod, digest, sizeof(digest))) { add_header(&req, "Authorization", digest); } else { ast_log(LOG_NOTICE, "No authorization available for authentication of registration to %s@%s\n", r->username, r->hostname); } } add_expires(&req, r->expiry); add_header(&req, "Contact", p->our_contact); initialize_initreq(p, &req); if (sip_debug_test_pvt(p)) { ast_verbose("REGISTER %d headers, %d lines\n", p->initreq.headers, p->initreq.lines); } r->regstate = auth ? REG_STATE_AUTHSENT : REG_STATE_REGSENT; r->regattempts++; /* Another attempt */ ast_debug(4, "REGISTER attempt %d to %s@%s\n", r->regattempts, r->username, r->hostname); res = send_request(p, &req, XMIT_CRITICAL, p->ocseq); dialog_unref(p, "p is finished here at the end of transmit_register"); return res; } /*! * \brief Transmit with SIP MESSAGE method * \note The p->msg_headers and p->msg_body are already setup. */ static int transmit_message(struct sip_pvt *p, int init, int auth) { struct sip_request req; if (init) { initreqprep(&req, p, SIP_MESSAGE, NULL); initialize_initreq(p, &req); } else { reqprep(&req, p, SIP_MESSAGE, 0, 1); } if (auth) { return transmit_request_with_auth(p, SIP_MESSAGE, p->ocseq, XMIT_RELIABLE, 0); } else { add_text(&req, p); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } } /*! \brief Allocate SIP refer structure */ static int sip_refer_alloc(struct sip_pvt *p) { sip_refer_destroy(p); p->refer = ast_calloc_with_stringfields(1, struct sip_refer, 512); return p->refer ? 1 : 0; } /*! \brief Destroy SIP refer structure */ static void sip_refer_destroy(struct sip_pvt *p) { if (p->refer) { ast_string_field_free_memory(p->refer); ast_free(p->refer); p->refer = NULL; } } /*! \brief Allocate SIP refer structure */ static int sip_notify_alloc(struct sip_pvt *p) { p->notify = ast_calloc(1, sizeof(struct sip_notify)); if (p->notify) { p->notify->content = ast_str_create(128); } return p->notify ? 1 : 0; } /*! \brief Transmit SIP REFER message (initiated by the transfer() dialplan application \note this is currently broken as we have no way of telling the dialplan engine whether a transfer succeeds or fails. \todo Fix the transfer() dialplan function so that a transfer may fail */ static int transmit_refer(struct sip_pvt *p, const char *dest) { char from[256]; const char *of; char *c; char referto[256]; int use_tls=FALSE; if (sipdebug) { ast_debug(1, "SIP transfer of %s to %s\n", p->callid, dest); } /* Are we transfering an inbound or outbound call ? */ if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) { of = sip_get_header(&p->initreq, "To"); } else { of = sip_get_header(&p->initreq, "From"); } ast_copy_string(from, of, sizeof(from)); of = get_in_brackets(from); ast_string_field_set(p, from, of); if (!strncasecmp(of, "sip:", 4)) { of += 4; } else if (!strncasecmp(of, "sips:", 5)) { of += 5; use_tls = TRUE; } else { ast_log(LOG_NOTICE, "From address missing 'sip(s):', assuming sip:\n"); } /* Get just the username part */ if (strchr(dest, '@')) { c = NULL; } else if ((c = strchr(of, '@'))) { *c++ = '\0'; } if (c) { snprintf(referto, sizeof(referto), "", use_tls ? "s" : "", dest, c); } else { snprintf(referto, sizeof(referto), "", use_tls ? "s" : "", dest); } /* save in case we get 407 challenge */ sip_refer_alloc(p); ast_string_field_set(p->refer, refer_to, referto); ast_string_field_set(p->refer, referred_by, p->our_contact); p->refer->status = REFER_SENT; /* Set refer status */ return transmit_invite(p, SIP_REFER, FALSE, 0, NULL); /* We should propably wait for a NOTIFY here until we ack the transfer */ /* Maybe fork a new thread and wait for a STATUS of REFER_200OK on the refer status before returning to app_transfer */ /*! \todo In theory, we should hang around and wait for a reply, before returning to the dial plan here. Don't know really how that would affect the transfer() app or the pbx, but, well, to make this useful we should have a STATUS code on transfer(). */ } /*! \brief Send SIP INFO advice of charge message */ static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded) { struct sip_request req; struct ast_str *str = ast_str_alloca(512); const struct ast_aoc_unit_entry *unit_entry = ast_aoc_get_unit_info(decoded, 0); enum ast_aoc_charge_type charging = ast_aoc_get_charge_type(decoded); reqprep(&req, p, SIP_INFO, 0, 1); if (ast_aoc_get_msg_type(decoded) == AST_AOC_D) { ast_str_append(&str, 0, "type=active;"); } else if (ast_aoc_get_msg_type(decoded) == AST_AOC_E) { ast_str_append(&str, 0, "type=terminated;"); } else { /* unsupported message type */ return -1; } switch (charging) { case AST_AOC_CHARGE_FREE: ast_str_append(&str, 0, "free-of-charge;"); break; case AST_AOC_CHARGE_CURRENCY: ast_str_append(&str, 0, "charging;"); ast_str_append(&str, 0, "charging-info=currency;"); ast_str_append(&str, 0, "amount=%u;", ast_aoc_get_currency_amount(decoded)); ast_str_append(&str, 0, "multiplier=%s;", ast_aoc_get_currency_multiplier_decimal(decoded)); if (!ast_strlen_zero(ast_aoc_get_currency_name(decoded))) { ast_str_append(&str, 0, "currency=%s;", ast_aoc_get_currency_name(decoded)); } break; case AST_AOC_CHARGE_UNIT: ast_str_append(&str, 0, "charging;"); ast_str_append(&str, 0, "charging-info=pulse;"); if (unit_entry) { ast_str_append(&str, 0, "recorded-units=%u;", unit_entry->amount); } break; default: ast_str_append(&str, 0, "not-available;"); }; add_header(&req, "AOC", ast_str_buffer(str)); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } /*! \brief Send SIP INFO dtmf message, see Cisco documentation on cisco.com */ static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration) { struct sip_request req; reqprep(&req, p, SIP_INFO, 0, 1); add_digit(&req, digit, duration, (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO)); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } /*! \brief Send SIP INFO with video update request */ static int transmit_info_with_vidupdate(struct sip_pvt *p) { struct sip_request req; reqprep(&req, p, SIP_INFO, 0, 1); add_vidupdate(&req); return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } /*! \brief Transmit generic SIP request returns XMIT_ERROR if transmit failed with a critical error (don't retry) */ static int transmit_request(struct sip_pvt *p, int sipmethod, uint32_t seqno, enum xmittype reliable, int newbranch) { struct sip_request resp; reqprep(&resp, p, sipmethod, seqno, newbranch); if (sipmethod == SIP_CANCEL && p->answered_elsewhere) { add_header(&resp, "Reason", "SIP;cause=200;text=\"Call completed elsewhere\""); } if (sipmethod == SIP_ACK) { p->invitestate = INV_CONFIRMED; } return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq); } /*! \brief return the request and response header for a 401 or 407 code */ void sip_auth_headers(enum sip_auth_type code, char **header, char **respheader) { if (code == WWW_AUTH) { /* 401 */ *header = "WWW-Authenticate"; *respheader = "Authorization"; } else if (code == PROXY_AUTH) { /* 407 */ *header = "Proxy-Authenticate"; *respheader = "Proxy-Authorization"; } else { ast_verbose("-- wrong response code %u\n", code); *header = *respheader = "Invalid"; } } /*! \brief Transmit SIP request, auth added */ static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, uint32_t seqno, enum xmittype reliable, int newbranch) { struct sip_request resp; reqprep(&resp, p, sipmethod, seqno, newbranch); if (!ast_strlen_zero(p->realm)) { char digest[1024]; memset(digest, 0, sizeof(digest)); if(!build_reply_digest(p, sipmethod, digest, sizeof(digest))) { char *dummy, *response; enum sip_auth_type code = p->options ? p->options->auth_type : PROXY_AUTH; /* XXX force 407 if unknown */ sip_auth_headers(code, &dummy, &response); add_header(&resp, response, digest); } else { ast_log(LOG_WARNING, "No authentication available for call %s\n", p->callid); } } switch (sipmethod) { case SIP_BYE: { char buf[20]; /* * We are hanging up. If we know a cause for that, send it in * clear text to make debugging easier. */ if (ast_test_flag(&p->flags[1], SIP_PAGE2_Q850_REASON) && p->hangupcause) { snprintf(buf, sizeof(buf), "Q.850;cause=%d", p->hangupcause & 0x7f); add_header(&resp, "Reason", buf); } add_header(&resp, "X-Asterisk-HangupCause", ast_cause2str(p->hangupcause)); snprintf(buf, sizeof(buf), "%d", p->hangupcause); add_header(&resp, "X-Asterisk-HangupCauseCode", buf); break; } case SIP_MESSAGE: add_text(&resp, p); break; default: break; } return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq); } /*! \brief Remove registration data from realtime database or AST/DB when registration expires */ static void destroy_association(struct sip_peer *peer) { int realtimeregs = ast_check_realtime("sipregs"); char *tablename = (realtimeregs) ? "sipregs" : "sippeers"; if (!sip_cfg.ignore_regexpire) { if (peer->rt_fromcontact && sip_cfg.peer_rtupdate) { ast_update_realtime(tablename, "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "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); } } } static void set_socket_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; } else if (socket->ws_session) { ast_websocket_unref(socket->ws_session); socket->ws_session = NULL; } } } /*! \brief Expire registration of SIP peer */ static int expire_register(const void *data) { struct sip_peer *peer = (struct sip_peer *)data; if (!peer) { /* Hmmm. We have no peer. Weird. */ return 0; } peer->expire = -1; peer->portinuri = 0; destroy_association(peer); /* remove registration data from storage */ set_socket_transport(&peer->socket, peer->default_outbound_transport); if (peer->socket.tcptls_session) { ao2_ref(peer->socket.tcptls_session, -1); peer->socket.tcptls_session = NULL; } else if (peer->socket.ws_session) { ast_websocket_unref(peer->socket.ws_session); peer->socket.ws_session = 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); } register_peer_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 and autocreated peers */ if (peer->is_realtime) { ast_debug(3, "-REALTIME- peer expired registration. Name: %s. Realtime peer objects now %d\n", peer->name, rpeerobjs); } if (peer->selfdestruct || ast_test_flag(&peer->flags[1], SIP_PAGE2_RTAUTOCLEAR)) { ao2_t_unlink(peers, peer, "ao2_unlink of peer from peers table"); } if (!ast_sockaddr_isnull(&peer->addr)) { /* We still need to unlink the peer from the peers_by_ip table, * otherwise we end up with multiple copies hanging around each * time a registration expires and the peer re-registers. */ ao2_t_unlink(peers_by_ip, peer, "ao2_unlink of peer from peers_by_ip table"); } /* Only clear the addr after we check for destruction. The addr must remain * in order to unlink from the peers_by_ip container correctly */ memset(&peer->addr, 0, sizeof(peer->addr)); sip_unref_peer(peer, "removing peer ref for expire_register"); return 0; } /*! \brief Poke peer (send qualify to check if peer is alive and well) */ static int sip_poke_peer_s(const void *data) { struct sip_peer *peer = (struct sip_peer *)data; struct sip_peer *foundpeer; peer->pokeexpire = -1; foundpeer = ao2_find(peers, peer, OBJ_POINTER); if (!foundpeer) { sip_unref_peer(peer, "removing poke peer ref"); return 0; } else if (foundpeer->name != peer->name) { sip_unref_peer(foundpeer, "removing above peer ref"); sip_unref_peer(peer, "removing poke peer ref"); return 0; } sip_unref_peer(foundpeer, "removing above peer ref"); sip_poke_peer(peer, 0); sip_unref_peer(peer, "removing poke peer ref"); return 0; } /*! \brief Get registration details from Asterisk DB */ static void reg_source_db(struct sip_peer *peer) { char data[256]; char path[SIPBUFSIZE * 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_cfg.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 poke peer immediately, just schedule it within qualifyfreq */ AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, ast_random() % ((peer->qualifyfreq) ? peer->qualifyfreq : global_qualifyfreq) + 1, sip_poke_peer_s, peer, sip_unref_peer(_data, "removing poke peer ref"), sip_unref_peer(peer, "removing poke peer ref"), sip_ref_peer(peer, "adding poke peer ref")); } AST_SCHED_REPLACE_UNREF(peer->expire, sched, (expire + 10) * 1000, expire_register, peer, sip_unref_peer(_data, "remove registration ref"), sip_unref_peer(peer, "remove registration ref"), sip_ref_peer(peer, "add registration ref")); register_peer_exten(peer, TRUE); if (!ast_db_get("SIP/RegistryPath", peer->name, path, sizeof(path))) { build_path(NULL, peer, NULL, path); } } /*! \brief Save contact header for 200 OK on INVITE */ static int parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req) { char contact[SIPBUFSIZE]; char *c; /* Look for brackets */ ast_copy_string(contact, sip_get_header(req, "Contact"), sizeof(contact)); c = get_in_brackets(contact); /* Save full contact to call pvt for later bye or re-invite */ ast_string_field_set(pvt, fullcontact, c); /* Save URI for later ACKs, BYE or RE-invites */ ast_string_field_set(pvt, okcontacturi, c); /* We should return false for URI:s we can't handle, like tel:, mailto:,ldap: etc */ return TRUE; } /*! \brief parse uri in a way that allows semicolon stripping if legacy mode is enabled * * \note This calls parse_uri which has the unexpected property that passing more * arguments results in more splitting. Most common is to leave out the pass * argument, causing user to contain user:pass if available. */ static int parse_uri_legacy_check(char *uri, const char *scheme, char **user, char **pass, char **hostport, char **transport) { int ret = parse_uri(uri, scheme, user, pass, hostport, transport); if (sip_cfg.legacy_useroption_parsing) { /* if legacy mode is active, strip semis from the user field */ char *p; if ((p = strchr(uri, (int)';'))) { *p = '\0'; } } return ret; } static int __set_address_from_contact(const char *fullcontact, struct ast_sockaddr *addr, int tcp) { char *hostport, *transport; char contact_buf[256]; char *contact; /* Work on a copy */ ast_copy_string(contact_buf, fullcontact, sizeof(contact_buf)); contact = contact_buf; /* * 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 (parse_uri_legacy_check(contact, "sip:,sips:", &contact, NULL, &hostport, &transport)) { ast_log(LOG_WARNING, "Invalid contact uri %s (missing sip: or sips:), attempting to use anyway\n", fullcontact); } /* XXX This could block for a long time XXX */ /* We should only do this if it's a name, not an IP */ /* \todo - if there's no PORT number in contact - we are required to check NAPTR/SRV records to find transport, port address and hostname. If there's a port number, we have to assume that the hostport part is a host name and only look for an A/AAAA record in DNS. */ /* If we took in an invalid URI, hostport may not have been initialized */ /* ast_sockaddr_resolve requires an initialized hostport string. */ if (ast_strlen_zero(hostport)) { ast_log(LOG_WARNING, "Invalid URI: parse_uri failed to acquire hostport\n"); return -1; } if (ast_sockaddr_resolve_first_transport(addr, hostport, 0, get_transport_str2enum(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)) { ast_sockaddr_set_port(addr, (get_transport_str2enum(transport) == AST_TRANSPORT_TLS || !strncasecmp(fullcontact, "sips", 4)) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT); } return 0; } /*! \brief Change the other partys IP address based on given contact */ static int set_address_from_contact(struct sip_pvt *pvt) { 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. */ /*! \todo We need to save the TRANSPORT here too */ pvt->sa = pvt->recv; return 0; } return __set_address_from_contact(pvt->fullcontact, &pvt->sa, pvt->socket.type == AST_TRANSPORT_TLS ? 1 : 0); } /*! \brief Parse contact header and save registration (peer registration) */ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req) { char contact[SIPBUFSIZE]; char data[SIPBUFSIZE]; const char *expires = sip_get_header(req, "Expires"); int expire = atoi(expires); char *curi = NULL, *hostport = NULL, *transport = NULL; int transport_type; const char *useragent; struct ast_sockaddr oldsin, testsa; char *firstcuri = NULL; int start = 0; int wildcard_found = 0; int single_binding_found = 0; ast_copy_string(contact, __get_header(req, "Contact", &start), sizeof(contact)); if (ast_strlen_zero(expires)) { /* No expires header, try look in Contact: */ char *s = strcasestr(contact, ";expires="); if (s) { expires = strsep(&s, ";"); /* trim ; and beyond */ if (sscanf(expires + 9, "%30d", &expire) != 1) { expire = default_expiry; } } else { /* Nothing has been specified */ expire = default_expiry; } } if (expire > max_expiry) { expire = max_expiry; } if (expire < min_expiry && expire != 0) { expire = min_expiry; } pvt->expiry = expire; copy_socket_data(&pvt->socket, &req->socket); do { /* Look for brackets */ curi = contact; if (strchr(contact, '<') == NULL) /* No <, check for ; and strip it */ strsep(&curi, ";"); /* This is Header options, not URI options */ curi = get_in_brackets(contact); if (!firstcuri) { firstcuri = ast_strdupa(curi); } if (!strcasecmp(curi, "*")) { wildcard_found = 1; } else { single_binding_found = 1; } if (wildcard_found && (ast_strlen_zero(expires) || expire != 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 PARSE_REGISTER_FAILED; } ast_copy_string(contact, __get_header(req, "Contact", &start), sizeof(contact)); } while (!ast_strlen_zero(contact)); curi = firstcuri; /* 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(curi) && 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(sched, peer->expire); } return PARSE_REGISTER_QUERY; } else if (!strcasecmp(curi, "*") || !expire) { /* Unregister this peer */ /* This means remove all registrations and return OK */ AST_SCHED_DEL_UNREF(sched, peer->expire, sip_unref_peer(peer, "remove register expire ref")); ast_verb(3, "Unregistered SIP '%s'\n", peer->name); expire_register(sip_ref_peer(peer,"add ref for explicit expire_register")); return PARSE_REGISTER_UPDATE; } /* Store whatever we got as a contact from the client */ ast_string_field_set(peer, fullcontact, curi); /* For the 200 OK, we should use the received contact */ ast_string_field_build(pvt, our_contact, "<%s>", curi); /* Make sure it's a SIP URL */ if (ast_strlen_zero(curi) || parse_uri_legacy_check(curi, "sip:,sips:", &curi, NULL, &hostport, &transport)) { ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:/sips:) trying to use anyway\n"); } /* handle the transport type specified in Contact header. */ if (!(transport_type = get_transport_str2enum(transport))) { transport_type = pvt->socket.type; } /* if the peer's socket type is different than the Registration * transport type, change it. If it got this far, it is a * supported type, but check just in case */ if ((peer->socket.type != transport_type) && (peer->transports & transport_type)) { set_socket_transport(&peer->socket, transport_type); } oldsin = peer->addr; /* If we were already linked into the peers_by_ip container unlink ourselves so nobody can find us */ if (!ast_sockaddr_isnull(&peer->addr) && (!peer->is_realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS))) { ao2_t_unlink(peers_by_ip, peer, "ao2_unlink of peer from peers_by_ip table"); } if ((transport_type != AST_TRANSPORT_WS) && (transport_type != AST_TRANSPORT_WSS) && (!ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) && !ast_test_flag(&peer->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"); /* XXX This could block for a long time XXX */ /*! \todo Check NAPTR/SRV if we have not got a port in the URI */ if (ast_sockaddr_resolve_first_transport(&testsa, hostport, 0, 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 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) ? TRUE : FALSE; if (!ast_sockaddr_port(&testsa)) { ast_sockaddr_set_port(&testsa, default_sip_port(transport_type)); } 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_cfg.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 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)) { copy_socket_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_PAGE2_RTCACHEFRIENDS)) { ao2_t_link(peers_by_ip, peer, "ao2_link into peers_by_ip table"); } /* Save SIP options profile */ peer->sipoptions = pvt->sipoptions; if (!ast_strlen_zero(curi) && ast_strlen_zero(peer->username)) { ast_string_field_set(peer, username, curi); } AST_SCHED_DEL_UNREF(sched, peer->expire, sip_unref_peer(peer, "remove register expire ref")); if (peer->is_realtime && !ast_test_flag(&peer->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { peer->expire = -1; } else { peer->expire = ast_sched_add(sched, (expire + 10) * 1000, expire_register, sip_ref_peer(peer, "add registration ref")); if (peer->expire == -1) { sip_unref_peer(peer, "remote registration ref"); } } if (!build_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); } snprintf(data, sizeof(data), "%s:%d:%s:%s", ast_sockaddr_stringify(&peer->addr), expire, peer->username, peer->fullcontact); /* We might not immediately be able to reconnect via TCP, but try caching it anyhow */ if (!peer->rt_fromcontact || !sip_cfg.peer_rtupdate) { if (!sip_route_empty(&peer->path)) { struct ast_str *r = sip_route_list(&peer->path, 0, 0); if (r) { ast_db_put("SIP/RegistryPath", peer->name, ast_str_buffer(r)); ast_free(r); } } 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 (ast_sockaddr_cmp(&peer->addr, &oldsin)) { ast_verb(3, "Registered SIP '%s' at %s\n", peer->name, ast_sockaddr_stringify(&peer->addr)); } sip_pvt_unlock(pvt); sip_poke_peer(peer, 0); sip_pvt_lock(pvt); register_peer_exten(peer, 1); /* Save User agent */ useragent = sip_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 PARSE_REGISTER_UPDATE; } /*! \brief Build route list from Record-Route header * * \param p * \param req * \param backwards * \param resp the SIP response code or 0 for a request * */ static void build_route(struct sip_pvt *p, 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(&p->route) && p->route_persistent) { ast_debug(1, "build_route: Retaining previous route: <%s>\n", sip_route_first_uri(&p->route)); return; } sip_route_clear(&p->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)) { p->route_persistent = 1; } /* Build a tailq, then assign it to p->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 = __get_header(req, "Record-Route", &start); if (*header == '\0') { break; } sip_route_process_header(&p->route, header, backwards); } /* Only append the contact if we are dealing with a strict router or have no route */ if (sip_route_empty(&p->route) || sip_route_is_strict(&p->route)) { /* 2nd append the Contact: if there is one */ /* Can be multiple Contact headers, comma separated values - we just take the first */ int len; header = sip_get_header(req, "Contact"); if (strchr(header, '<')) { get_in_brackets_const(header, &header, &len); } else { len = strlen(header); } if (header && len) { sip_route_add(&p->route, header, len, 0); } } /* For debugging dump what we ended up with */ if (sip_debug_test_pvt(p)) { sip_route_dump(&p->route); } } /*! \brief 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 */ static int build_path(struct sip_pvt *p, 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, "build_path: do not use Path headers\n"); return -1; } ast_debug(2, "build_path: try to build pre-loaded route-set by parsing Path headers\n"); if (req) { int start = 0; const char *header; for (;;) { header = __get_header(req, "Path", &start); if (*header == '\0') { break; } sip_route_process_header(&peer->path, header, 0); } } else if (pathbuf) { sip_route_process_header(&peer->path, pathbuf, 0); } /* 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 (p && sip_debug_test_pvt(p)) { sip_route_dump(&peer->path); } return 0; } /*! \brief 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. */ static void build_nonce(struct sip_pvt *p, int forceupdate) { if (p->stalenonce || forceupdate || ast_strlen_zero(p->nonce)) { ast_string_field_build(p, nonce, "%08lx", (unsigned long)ast_random()); /* Create nonce for challenge */ p->stalenonce = 0; } } /*! \brief Takes the digest response and parses it */ void sip_digest_parser(char *c, struct digestkeys *keys) { struct digestkeys *i = i; while(c && *(c = ast_skip_blanks(c)) ) { /* lookup for keys */ for (i = keys; i->key != NULL; i++) { const char *separator = ","; /* default */ if (strncasecmp(c, i->key, strlen(i->key)) != 0) { continue; } /* Found. Skip keyword, take text in quotes or up to the separator. */ c += strlen(i->key); if (*c == '"') { /* in quotes. Skip first and look for last */ c++; separator = "\""; } i->s = c; strsep(&c, separator); break; } if (i->key == NULL) { /* not found, jump after space or comma */ strsep(&c, " ,"); } } } /*! \brief Check user authorization from peer definition Some actions, like REGISTER and INVITEs from peers require authentication (if peer have secret set) \return 0 on success, non-zero on error */ static enum check_auth_result check_auth(struct sip_pvt *p, struct sip_request *req, const char *username, const char *secret, const char *md5secret, int sipmethod, const char *uri, enum xmittype reliable) { const char *response; char *reqheader, *respheader; const char *authtoken; char a1_hash[256]; char resp_hash[256]=""; char *c; int is_bogus_peer = 0; int wrongnonce = FALSE; int good_response; const char *usednonce = p->nonce; struct ast_str *buf; int res; /* table of recognised keywords, and their value in the digest */ struct digestkeys keys[] = { [K_RESP] = { "response=", "" }, [K_URI] = { "uri=", "" }, [K_USER] = { "username=", "" }, [K_NONCE] = { "nonce=", "" }, [K_LAST] = { NULL, NULL} }; /* Always OK if no secret */ if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { return AUTH_SUCCESSFUL; } /* 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 */ response = "401 Unauthorized"; /* * Note the apparent swap of arguments below, compared to other * usages of sip_auth_headers(). */ sip_auth_headers(WWW_AUTH, &respheader, &reqheader); authtoken = sip_get_header(req, reqheader); if (req->ignore && !ast_strlen_zero(p->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 */ transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, 0); /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } return AUTH_CHALLENGE_SENT; } else if (ast_strlen_zero(p->nonce) || ast_strlen_zero(authtoken)) { /* We have no auth, so issue challenge and request authentication */ build_nonce(p, 1); /* Create nonce for challenge */ transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, 0); /* Schedule auto destroy in 32 seconds */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return AUTH_CHALLENGE_SENT; } /* --- We have auth, so check it */ /* 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 (!(buf = ast_str_thread_get(&check_auth_buf, CHECK_AUTH_BUF_INITLEN))) { return AUTH_SECRET_FAILED; /*! XXX \todo need a better return code here */ } /* Make a copy of the response and parse it */ res = ast_str_set(&buf, 0, "%s", authtoken); if (res == AST_DYNSTR_BUILD_FAILED) { return AUTH_SECRET_FAILED; /*! XXX \todo need a better return code here */ } c = ast_str_buffer(buf); sip_digest_parser(c, 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, BOGUS_PEER_MD5SECRET) == 0) { is_bogus_peer = 1; } /* Verify that digest username matches the username we auth as */ if (strcmp(username, keys[K_USER].s) && !is_bogus_peer) { ast_log(LOG_WARNING, "username mismatch, have <%s>, digest has <%s>\n", username, keys[K_USER].s); /* Oops, we're trying something here */ return 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(p->nonce, keys[K_NONCE].s) || p->stalenonce) { /* XXX it was 'n'casecmp ? */ wrongnonce = TRUE; usednonce = keys[K_NONCE].s; } else { p->stalenonce = 1; /* now, since the nonce has a response, mark it as stale so it can't be sent or responded to again */ } if (!ast_strlen_zero(md5secret)) { ast_copy_string(a1_hash, md5secret, sizeof(a1_hash)); } else { char a1[256]; snprintf(a1, sizeof(a1), "%s:%s:%s", username, p->realm, secret); ast_md5_hash(a1_hash, a1); } /* compute the expected response to compare with what we received */ { char a2[256]; char a2_hash[256]; char resp[256]; snprintf(a2, sizeof(a2), "%s:%s", sip_methods[sipmethod].text, S_OR(keys[K_URI].s, 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); } good_response = keys[K_RESP].s && !strncasecmp(keys[K_RESP].s, resp_hash, strlen(resp_hash)) && !is_bogus_peer; /* lastly, check that the peer isn't the fake peer */ if (wrongnonce) { if (good_response) { if (sipdebug) ast_log(LOG_NOTICE, "Correct auth, but based on stale nonce received from '%s'\n", sip_get_header(req, "From")); /* We got working auth token, based on stale nonce . */ build_nonce(p, 0); transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, TRUE); } else { /* Everything was wrong, so give the device one more try with a new challenge */ if (!req->ignore) { if (sipdebug) { ast_log(LOG_NOTICE, "Bad authentication received from '%s'\n", sip_get_header(req, "To")); } build_nonce(p, 1); } else { if (sipdebug) { ast_log(LOG_NOTICE, "Duplicate authentication received from '%s'\n", sip_get_header(req, "To")); } } transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, FALSE); } /* Schedule auto destroy in 32 seconds */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return AUTH_CHALLENGE_SENT; } if (good_response) { append_history(p, "AuthOK", "Auth challenge successful for %s", username); return 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 AUTH_SECRET_FAILED; } /*! \brief Change onhold state of a peer using a pvt structure */ static void sip_peer_hold(struct sip_pvt *p, int hold) { if (!p->relatedpeer) { return; } /* If they put someone on hold, increment the value... otherwise decrement it */ ast_atomic_fetchadd_int(&p->relatedpeer->onhold, (hold ? +1 : -1)); /* Request device state update */ ast_devstate_changed(AST_DEVICE_UNKNOWN, (ast_test_flag(ast_channel_flags(p->owner), AST_FLAG_DISABLE_DEVSTATE_CACHE) ? AST_DEVSTATE_NOT_CACHABLE : AST_DEVSTATE_CACHABLE), "SIP/%s", p->relatedpeer->name); return; } /*! \brief Receive MWI events that we have subscribed to */ static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { char *peer_name = userdata; struct sip_peer *peer = sip_find_peer(peer_name, NULL, TRUE, FINDALLDEVICES, FALSE, 0); if (stasis_subscription_final_message(sub, msg)) { if (peer) { /* configuration reloaded */ return; } ast_free(peer_name); return; } if (peer && ast_mwi_state_type() == stasis_message_type(msg)) { sip_send_mwi_to_peer(peer, 0); } ao2_cleanup(peer); } static void network_change_stasis_subscribe(void) { if (!network_change_sub) { network_change_sub = stasis_subscribe(ast_system_topic(), network_change_stasis_cb, NULL); } } static void network_change_stasis_unsubscribe(void) { network_change_sub = stasis_unsubscribe_and_join(network_change_sub); } static void acl_change_stasis_subscribe(void) { if (!acl_change_sub) { acl_change_sub = stasis_subscribe(ast_security_topic(), acl_change_stasis_cb, NULL); } } static void acl_change_event_stasis_unsubscribe(void) { acl_change_sub = stasis_unsubscribe_and_join(acl_change_sub); } static int network_change_sched_cb(const void *data) { network_change_sched_id = -1; sip_send_all_registers(); sip_send_all_mwi_subscriptions(); return 0; } static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { /* This callback is only concerned with network change messages from the system topic. */ if (stasis_message_type(message) != ast_network_change_type()) { return; } ast_verb(1, "SIP, got a network change message, renewing all SIP registrations.\n"); if (network_change_sched_id == -1) { network_change_sched_id = ast_sched_add(sched, 1000, network_change_sched_cb, NULL); } } static void cb_extensionstate_destroy(int id, void *data) { struct sip_pvt *p = data; dialog_unref(p, "the extensionstate containing this dialog ptr was destroyed"); } /*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem \note If you add an "hint" priority to the extension in the dial plan, you will get notifications on device state changes */ static int extensionstate_update(const char *context, const char *exten, struct state_notify_data *data, struct sip_pvt *p, int force) { sip_pvt_lock(p); switch (data->state) { case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ case AST_EXTENSION_REMOVED: /* Extension is gone */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); /* Delete subscription in 32 secs */ ast_verb(2, "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, data->state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username); p->subscribed = NONE; append_history(p, "Subscribestatus", "%s", data->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 ((p->laststate == data->state && (~data->state & AST_EXTENSION_RINGING)) && (p->last_presence_state == data->presence_state && !strcmp(p->last_presence_subtype, data->presence_subtype) && !strcmp(p->last_presence_message, data->presence_message))) { /* don't notify unchanged state or unchanged early-state causing parties again */ sip_pvt_unlock(p); return 0; } else if (data->state & AST_EXTENSION_RINGING) { /* check if another channel than last time is ringing now to be notified */ struct ast_channel *ringing = find_ringing_channel(data->device_state_info, p); if (ringing) { if (!ast_tvcmp(ast_channel_creationtime(ringing), p->last_ringing_channel_time)) { /* we assume here that no two channels have the exact same creation time */ ao2_ref(ringing, -1); sip_pvt_unlock(p); return 0; } else { p->last_ringing_channel_time = ast_channel_creationtime(ringing); ao2_ref(ringing, -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 (data->device_state_info) { ao2_ref(data->device_state_info, 1); } ao2_cleanup(p->last_device_state_info); p->last_device_state_info = data->device_state_info; p->laststate = data->state; p->last_presence_state = data->presence_state; ast_string_field_set(p, last_presence_subtype, S_OR(data->presence_subtype, "")); ast_string_field_set(p, last_presence_message, S_OR(data->presence_message, "")); break; } if (p->subscribed != NONE) { /* Only send state NOTIFY if we know the format */ if (!p->pendinginvite) { transmit_state_notify(p, data, 1, FALSE); if (p->last_device_state_info) { /* We don't need the saved ref anymore, don't keep channels ref'd. */ ao2_ref(p->last_device_state_info, -1); p->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(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE); } } if (!force) { ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : ""); } sip_pvt_unlock(p); return 0; } /*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem \note If you add an "hint" priority to the extension in the dial plan, you will get notifications on device state changes */ static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data) { struct sip_pvt *p = data; struct state_notify_data notify_data = { .state = info->exten_state, .device_state_info = info->device_state_info, .presence_state = info->presence_state, .presence_subtype = info->presence_subtype, .presence_message = info->presence_message, }; if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) { /* ignore a presence triggered update if we know the useragent doesn't care */ return 0; } return extensionstate_update(context, exten, ¬ify_data, p, FALSE); } /*! \brief Send a fake 401 Unauthorized response when the administrator wants to hide the names of local devices from fishers */ static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable) { /* We have to emulate EXACTLY what we'd get with a good peer * and a bad password, or else we leak information. */ const char *response = "401 Unauthorized"; const char *reqheader = "Authorization"; const char *respheader = "WWW-Authenticate"; const char *authtoken; struct ast_str *buf; char *c; /* table of recognised keywords, and their value in the digest */ enum keys { K_NONCE, K_LAST }; struct x { const char *key; const char *s; } *i, keys[] = { [K_NONCE] = { "nonce=", "" }, [K_LAST] = { NULL, NULL} }; authtoken = sip_get_header(req, reqheader); if (req->ignore && !ast_strlen_zero(p->nonce) && ast_strlen_zero(authtoken)) { /* This is a retransmitted invite/register/etc, don't reconstruct authentication * information */ transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, 0); /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } else if (ast_strlen_zero(p->nonce) || ast_strlen_zero(authtoken)) { /* We have no auth, so issue challenge and request authentication */ build_nonce(p, 1); transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, 0); /* Schedule auto destroy in 32 seconds */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } if (!(buf = ast_str_thread_get(&check_auth_buf, CHECK_AUTH_BUF_INITLEN))) { __transmit_response(p, "403 Forbidden", &p->initreq, reliable); return; } /* Make a copy of the response and parse it */ if (ast_str_set(&buf, 0, "%s", authtoken) == AST_DYNSTR_BUILD_FAILED) { __transmit_response(p, "403 Forbidden", &p->initreq, reliable); return; } c = ast_str_buffer(buf); while (c && *(c = ast_skip_blanks(c))) { /* lookup for keys */ for (i = keys; i->key != NULL; i++) { const char *separator = ","; /* default */ if (strncasecmp(c, i->key, strlen(i->key)) != 0) { continue; } /* Found. Skip keyword, take text in quotes or up to the separator. */ c += strlen(i->key); if (*c == '"') { /* in quotes. Skip first and look for last */ c++; separator = "\""; } i->s = c; strsep(&c, separator); break; } if (i->key == NULL) { /* not found, jump after space or comma */ strsep(&c, " ,"); } } /* Verify nonce from request matches our nonce. If not, send 401 with new nonce */ if (strcasecmp(p->nonce, keys[K_NONCE].s)) { if (!req->ignore) { build_nonce(p, 1); } transmit_response_with_auth(p, response, req, p->nonce, reliable, respheader, FALSE); /* Schedule auto destroy in 32 seconds */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } else { __transmit_response(p, "403 Forbidden", &p->initreq, reliable); } } /*! * 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) *\verbatim * * sip:user:password;user-parameters@host:port;uri-parameters?headers * sips:user:password;user-parameters@host:port;uri-parameters?headers * *\endverbatim * \todo As this function does not support user-parameters, it's considered broken * and needs fixing. */ static char *terminate_uri(char *uri) { char *t = uri; while (*t && *t > ' ' && *t != ';') { t++; } *t = '\0'; return uri; } /*! \brief Terminate a host:port at the ':' * \param hostport The address of the hostport string * * \note In the case of a bracket-enclosed IPv6 address, the hostport variable * will contain the non-bracketed host as a result of calling this function. */ static void extract_host_from_hostport(char **hostport) { char *dont_care; ast_sockaddr_split_hostport(*hostport, hostport, &dont_care, PARSE_PORT_IGNORE); } /*! * \internal * \brief Helper function to update a peer's lastmsgssent value */ static void update_peer_lastmsgssent(struct sip_peer *peer, int value, int locked) { if (!locked) { ao2_lock(peer); } peer->lastmsgssent = value; if (!locked) { ao2_unlock(peer); } } /*! * \brief Verify registration of user * * \details * - 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" */ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr, struct sip_request *req, const char *uri) { enum check_auth_result res = AUTH_NOT_FOUND; struct sip_peer *peer; char tmp[256]; char *c, *name, *unused_password, *domain; char *uri2 = ast_strdupa(uri); int send_mwi = 0; terminate_uri(uri2); ast_copy_string(tmp, sip_get_header(req, "To"), sizeof(tmp)); c = get_in_brackets(tmp); c = remove_uri_parameters(c); if (parse_uri_legacy_check(c, "sip:,sips:", &name, &unused_password, &domain, NULL)) { ast_log(LOG_NOTICE, "Invalid to address: '%s' from %s (missing sip:) trying to use anyway...\n", c, ast_sockaddr_stringify_addr(addr)); return -1; } SIP_PEDANTIC_DECODE(name); SIP_PEDANTIC_DECODE(domain); extract_host_from_hostport(&domain); if (ast_strlen_zero(domain)) { /* , never good */ transmit_response(p, "404 Not found", &p->initreq); return 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(&domain_list)) { if (!check_sip_domain(domain, NULL, 0)) { if (sip_cfg.alwaysauthreject) { transmit_fake_auth_response(p, &p->initreq, XMIT_UNRELIABLE); } else { transmit_response(p, "404 Not found (unknown domain)", &p->initreq); } return AUTH_UNKNOWN_DOMAIN; } } ast_string_field_set(p, exten, name); build_contact(p); 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_get_header(req, "Expires"); int expire = atoi(expires); if (ast_strlen_zero(expires)) { /* No expires header; look in Contact */ if ((expires = strcasestr(sip_get_header(req, "Contact"), ";expires="))) { expire = atoi(expires + 9); } } if (!ast_strlen_zero(expires) && expire == 0) { transmit_response_with_date(p, "200 OK", req); return 0; } } peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0); /* If we don't want username disclosure, use the bogus_peer when a user * is not found. */ if (!peer && sip_cfg.alwaysauthreject && sip_cfg.autocreatepeer == AUTOPEERS_DISABLED) { peer = bogus_peer; sip_ref_peer(peer, "register_verify: ref the bogus_peer"); } if (!(peer && ast_apply_acl(peer->acl, addr, "SIP Peer ACL: "))) { /* Peer fails ACL check */ if (peer) { sip_unref_peer(peer, "register_verify: sip_unref_peer: from sip_find_peer operation"); peer = NULL; res = AUTH_ACL_FAILED; } else { res = 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 = AUTH_PEER_NOT_DYNAMIC; } else { set_peer_nat(p, peer); ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_NAT_FORCE_RPORT); if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, SIP_REGISTER, uri2, XMIT_UNRELIABLE))) { if (sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); if (check_request_transport(peer, req)) { ast_set_flag(&p->flags[0], SIP_PENDINGBYE); transmit_response_with_date(p, "403 Forbidden", req); res = AUTH_BAD_TRANSPORT; } else { /* We have a successful registration attempt with proper authentication, now, update the peer */ switch (parse_register_contact(p, peer, req)) { case PARSE_REGISTER_DENIED: ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); transmit_response_with_date(p, "603 Denied", req); res = 0; break; case PARSE_REGISTER_FAILED: ast_log(LOG_WARNING, "Failed to parse contact info\n"); transmit_response_with_date(p, "400 Bad Request", req); res = 0; break; case PARSE_REGISTER_QUERY: ast_string_field_set(p, fullcontact, peer->fullcontact); transmit_response_with_date(p, "200 OK", req); res = 0; send_mwi = 1; break; case PARSE_REGISTER_UPDATE: ast_string_field_set(p, fullcontact, peer->fullcontact); /* If expiry is 0, peer has been unregistered already */ if (p->expiry != 0) { update_peer(peer, p->expiry); } /* Say OK and ask subsystem to retransmit msg counter */ transmit_response_with_date(p, "200 OK", req); send_mwi = 1; res = 0; break; } } } } ao2_unlock(peer); } if (!peer && sip_cfg.autocreatepeer != AUTOPEERS_DISABLED) { /* Create peer if we have autocreate mode enabled */ peer = temp_peer(name); if (peer) { ao2_t_link(peers, peer, "link peer into peer table"); if (!ast_sockaddr_isnull(&peer->addr)) { ao2_t_link(peers_by_ip, peer, "link peer into peers-by-ip table"); } ao2_lock(peer); if (sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); switch (parse_register_contact(p, peer, req)) { case PARSE_REGISTER_DENIED: ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); transmit_response_with_date(p, "403 Forbidden", req); res = 0; break; case PARSE_REGISTER_FAILED: ast_log(LOG_WARNING, "Failed to parse contact info\n"); transmit_response_with_date(p, "400 Bad Request", req); res = 0; break; case PARSE_REGISTER_QUERY: ast_string_field_set(p, fullcontact, peer->fullcontact); transmit_response_with_date(p, "200 OK", req); send_mwi = 1; res = 0; break; case PARSE_REGISTER_UPDATE: ast_string_field_set(p, fullcontact, peer->fullcontact); /* Say OK and ask subsystem to retransmit msg counter */ transmit_response_with_date(p, "200 OK", req); 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(addr)); ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); } send_mwi = 1; res = 0; break; } ao2_unlock(peer); } } if (!res) { if (send_mwi) { sip_pvt_unlock(p); sip_send_mwi_to_peer(peer, 0); sip_pvt_lock(p); } else { update_peer_lastmsgssent(peer, -1, 0); } 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 AUTH_SECRET_FAILED: /* Wrong password in authentication. Go away, don't try again until you fixed it */ transmit_response(p, "403 Forbidden", &p->initreq); if (global_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", "AUTH_SECRET_FAILED", "address", peer_addr, "port", peer_port); } break; case 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 AUTH_NOT_FOUND: case AUTH_PEER_NOT_DYNAMIC: case AUTH_ACL_FAILED: if (sip_cfg.alwaysauthreject) { transmit_fake_auth_response(p, &p->initreq, XMIT_UNRELIABLE); if (global_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 == AUTH_PEER_NOT_DYNAMIC ? "AUTH_PEER_NOT_DYNAMIC" : "URI_NOT_FOUND", "address", peer_addr, "port", peer_port); } } else { /* URI not found */ if (res == AUTH_PEER_NOT_DYNAMIC) { transmit_response(p, "403 Forbidden", &p->initreq); if (global_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", "AUTH_PEER_NOT_DYNAMIC", "address", peer_addr, "port", peer_port); } } else { transmit_response(p, "404 Not found", &p->initreq); if (global_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 == AUTH_USERNAME_MISMATCH) ? "AUTH_USERNAME_MISMATCH" : "URI_NOT_FOUND", "address", peer_addr, "port", peer_port); } } } break; case AUTH_BAD_TRANSPORT: default: break; } if (peer && peer->endpoint) { ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); } } if (peer) { sip_unref_peer(peer, "register_verify: sip_unref_peer: tossing stack peer pointer at end of func"); } return res; } /*! \brief Translate referring cause */ static void sip_set_redirstr(struct sip_pvt *p, char *reason) { if (!strcmp(reason, "unknown")) { ast_string_field_set(p, redircause, "UNKNOWN"); } else if (!strcmp(reason, "user-busy")) { ast_string_field_set(p, redircause, "BUSY"); } else if (!strcmp(reason, "no-answer")) { ast_string_field_set(p, redircause, "NOANSWER"); } else if (!strcmp(reason, "unavailable")) { ast_string_field_set(p, redircause, "UNREACHABLE"); } else if (!strcmp(reason, "unconditional")) { ast_string_field_set(p, redircause, "UNCONDITIONAL"); } else if (!strcmp(reason, "time-of-day")) { ast_string_field_set(p, redircause, "UNKNOWN"); } else if (!strcmp(reason, "do-not-disturb")) { ast_string_field_set(p, redircause, "UNKNOWN"); } else if (!strcmp(reason, "deflection")) { ast_string_field_set(p, redircause, "UNKNOWN"); } else if (!strcmp(reason, "follow-me")) { ast_string_field_set(p, redircause, "UNKNOWN"); } else if (!strcmp(reason, "out-of-service")) { ast_string_field_set(p, redircause, "UNREACHABLE"); } else if (!strcmp(reason, "away")) { ast_string_field_set(p, redircause, "UNREACHABLE"); } else { ast_string_field_set(p, redircause, "UNKNOWN"); } } /*! \brief Parse the parts of the P-Asserted-Identity header * on an incoming packet. Returns 1 if a valid header is found * and it is different from the current caller id. */ static int get_pai(struct sip_pvt *p, struct sip_request *req) { char pai[256]; char privacy[64]; char *cid_num = NULL; char *cid_name = NULL; char emptyname[1] = ""; int callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; char *uri = NULL; int is_anonymous = 0, do_update = 1, no_name = 0; ast_copy_string(pai, sip_get_header(req, "P-Asserted-Identity"), sizeof(pai)); if (ast_strlen_zero(pai)) { return 0; } /* use the reqresp_parser function get_name_and_number*/ if (get_name_and_number(pai, &cid_name, &cid_num)) { return 0; } if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { ast_shrink_phone_number(cid_num); } uri = get_in_brackets(pai); if (!strncasecmp(uri, "sip:anonymous@anonymous.invalid", 31)) { callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; /*XXX Assume no change in cid_num. Perhaps it should be * blanked? */ ast_free(cid_num); is_anonymous = 1; cid_num = (char *)p->cid_num; } ast_copy_string(privacy, sip_get_header(req, "Privacy"), sizeof(privacy)); if (!ast_strlen_zero(privacy) && !strncmp(privacy, "id", 2)) { callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; } if (!cid_name) { no_name = 1; cid_name = (char *)emptyname; } /* Only return true if the supplied caller id is different */ if (!strcasecmp(p->cid_num, cid_num) && !strcasecmp(p->cid_name, cid_name) && p->callingpres == callingpres) { do_update = 0; } else { ast_string_field_set(p, cid_num, cid_num); ast_string_field_set(p, cid_name, cid_name); p->callingpres = callingpres; if (p->owner) { ast_set_callerid(p->owner, cid_num, cid_name, NULL); ast_channel_caller(p->owner)->id.name.presentation = callingpres; ast_channel_caller(p->owner)->id.number.presentation = callingpres; } } /* get_name_and_number allocates memory for cid_num and cid_name so we have to free it */ if (!is_anonymous) { ast_free(cid_num); } if (!no_name) { ast_free(cid_name); } return do_update; } /*! \brief 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. */ static int get_rpid(struct sip_pvt *p, struct sip_request *oreq) { char tmp[256]; struct sip_request *req; char *cid_num = ""; char *cid_name = ""; int callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; char *privacy = ""; char *screen = ""; char *start, *end; if (!ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) return 0; req = oreq; if (!req) req = &p->initreq; ast_copy_string(tmp, sip_get_header(req, "Remote-Party-ID"), sizeof(tmp)); if (ast_strlen_zero(tmp)) { return get_pai(p, req); } /* * RPID is not: * rpid = (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 = tmp; /* Quoted (note that we're not dealing with escapes properly) */ if (*start == '"') { *start++ = '\0'; end = strchr(start, '"'); if (!end) 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'; end = strchr(start, '@'); if (!end) return 0; *end++ = '\0'; if (strncasecmp(start, "sip:", 4)) return 0; cid_num = start + 4; if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) ast_shrink_phone_number(cid_num); start = end; end = strchr(start, '>'); if (!end) return 0; *end++ = '\0'; if (*end) { start = end; if (*start != ';') return 0; *start++ = '\0'; while (!ast_strlen_zero(start)) { end = strchr(start, ';'); if (end) *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(p->cid_num, cid_num) && !strcasecmp(p->cid_name, cid_name) && p->callingpres == callingpres) return 0; ast_string_field_set(p, cid_num, cid_num); ast_string_field_set(p, cid_name, cid_name); p->callingpres = callingpres; if (p->owner) { ast_set_callerid(p->owner, cid_num, cid_name, NULL); ast_channel_caller(p->owner)->id.name.presentation = callingpres; ast_channel_caller(p->owner)->id.number.presentation = callingpres; } return 1; } /*! \brief Get referring dnis * * \param p dialog information * \param oreq The request to retrieve RDNIS from * \param[out] name The name of the party redirecting the call. * \param[out] number The number of the party redirecting the call. * \param[out] reason_code The numerical code corresponding to the reason for the redirection. * \param[out] reason_str A string describing the reason for redirection. Will never be zero-length * * \retval -1 Could not retrieve RDNIS information * \retval 0 RDNIS successfully retrieved */ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason_code, char **reason_str) { char tmp[256], *exten, *rexten, *rdomain, *rname = NULL; char *params, *reason_param = NULL; struct sip_request *req; req = oreq ? oreq : &p->initreq; ast_copy_string(tmp, sip_get_header(req, "Diversion"), sizeof(tmp)); if (ast_strlen_zero(tmp)) return -1; if ((params = strchr(tmp, '>'))) { params = strchr(params, ';'); } exten = get_in_brackets(tmp); if (!strncasecmp(exten, "sip:", 4)) { exten += 4; } else if (!strncasecmp(exten, "sips:", 5)) { exten += 5; } else { ast_log(LOG_WARNING, "Huh? Not an RDNIS SIP header (%s)?\n", exten); return -1; } /* Get diversion-reason param if present */ if (params) { *params = '\0'; /* Cut off parameters */ params++; while (*params == ';' || *params == ' ') params++; /* Check if we have a reason parameter */ if ((reason_param = strcasestr(params, "reason="))) { char *end; reason_param+=7; if ((end = strchr(reason_param, ';'))) { *end = '\0'; } /* Remove enclosing double-quotes */ if (*reason_param == '"') reason_param = ast_strip_quoted(reason_param, "\"", "\""); if (!ast_strlen_zero(reason_param)) { sip_set_redirstr(p, reason_param); if (p->owner) { pbx_builtin_setvar_helper(p->owner, "__PRIREDIRECTREASON", p->redircause); pbx_builtin_setvar_helper(p->owner, "__SIPREDIRECTREASON", reason_param); } } } } rdomain = exten; rexten = strsep(&rdomain, "@"); /* trim anything after @ */ if (p->owner) pbx_builtin_setvar_helper(p->owner, "__SIPRDNISDOMAIN", rdomain); if (sip_debug_test_pvt(p)) { ast_verbose("RDNIS for this call is %s (reason %s)\n", exten, S_OR(reason_param, "")); } /*ast_string_field_set(p, rdnis, rexten);*/ if (*tmp == '\"') { char *end_quote; rname = tmp + 1; end_quote = strchr(rname, '\"'); if (end_quote) { *end_quote = '\0'; } } if (number) { *number = ast_strdup(rexten); } if (name && rname) { *name = ast_strdup(rname); } if (!ast_strlen_zero(reason_param)) { if (reason_code) { *reason_code = sip_reason_str_to_code(reason_param); } if (reason_str) { *reason_str = ast_strdup(reason_param); } } return 0; } /*! * \brief Find out who the call is for. * * \details * 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. * * \return 0 on success (found a matching extension), non-zero on failure * * \note 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. */ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id) { char tmp[256] = "", *uri, *unused_password, *domain; RAII_VAR(char *, tmpf, NULL, ast_free); char *from = NULL; struct sip_request *req; char *decoded_uri; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(p->owner), 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 { /* Don't need to duplicate since channel is locked for the duration of this function */ pickupexten = pickup_cfg->pickupexten; } req = oreq; if (!req) { req = &p->initreq; } /* Find the request URI */ if (req->rlpart2) { ast_copy_string(tmp, REQ_OFFSET_TO_STR(req, rlpart2), sizeof(tmp)); } uri = ast_strdupa(get_in_brackets(tmp)); if (parse_uri_legacy_check(uri, "sip:,sips:,tel:", &uri, &unused_password, &domain, NULL)) { ast_log(LOG_WARNING, "Not a SIP header (%s)?\n", uri); return SIP_GET_DEST_INVALID_URI; } SIP_PEDANTIC_DECODE(domain); SIP_PEDANTIC_DECODE(uri); extract_host_from_hostport(&domain); if (strncasecmp(get_in_brackets(tmp), "tel:", 4)) { ast_string_field_set(p, domain, domain); } else { ast_string_field_set(p, tel_phone_context, 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 */ /* XXX Why is this done in get_destination? Isn't it already done? Needs to be checked */ tmpf = ast_strdup(sip_get_header(req, "From")); if (!ast_strlen_zero(tmpf)) { from = get_in_brackets(tmpf); if (parse_uri_legacy_check(from, "sip:,sips:,tel:", &from, NULL, &domain, NULL)) { ast_log(LOG_WARNING, "Not a SIP header (%s)?\n", from); return SIP_GET_DEST_INVALID_URI; } SIP_PEDANTIC_DECODE(from); SIP_PEDANTIC_DECODE(domain); extract_host_from_hostport(&domain); ast_string_field_set(p, fromdomain, domain); } if (!AST_LIST_EMPTY(&domain_list)) { char domain_context[AST_MAX_EXTENSION]; domain_context[0] = '\0'; if (!check_sip_domain(p->domain, domain_context, sizeof(domain_context))) { if (!sip_cfg.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].text, p->domain); return SIP_GET_DEST_REFUSED; } } /* If we don't have a peer (i.e. we're a guest call), * overwrite the original context */ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_HAVEPEERCONTEXT) && !ast_strlen_zero(domain_context)) { ast_string_field_set(p, 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(p->subscribecontext)) { ast_string_field_set(p, context, p->subscribecontext); } if (sip_debug_test_pvt(p)) { ast_verbose("Looking for %s in %s (domain %s)\n", uri, p->context, p->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) { char hint[AST_MAX_EXTENSION]; int which = 0; if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, uri) || (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, decoded_uri) && (which = 1))) { if (!oreq) { ast_string_field_set(p, exten, which ? decoded_uri : uri); } return SIP_GET_DEST_EXTEN_FOUND; } else { return SIP_GET_DEST_EXTEN_NOT_FOUND; } } else { struct ast_cc_agent *agent; /* 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, p->context, uri, 1, S_OR(p->cid_num, from))) { if (!oreq) { ast_string_field_set(p, exten, uri); } return SIP_GET_DEST_EXTEN_FOUND; } if (ast_exists_extension(NULL, p->context, decoded_uri, 1, S_OR(p->cid_num, from)) || !strcmp(decoded_uri, pickupexten)) { if (!oreq) { ast_string_field_set(p, exten, decoded_uri); } return SIP_GET_DEST_EXTEN_FOUND; } if ((agent = find_sip_cc_agent_by_notify_uri(tmp))) { struct sip_cc_agent_pvt *agent_pvt = agent->private_data; /* This is a CC recall. We can set p's extension to the exten from * the original INVITE */ ast_string_field_set(p, exten, agent_pvt->original_exten); /* And we need to let the CC core know that the caller is attempting * his recall */ ast_cc_agent_recalling(agent->core_id, "SIP caller %s is attempting recall", agent->device_name); if (cc_recall_core_id) { *cc_recall_core_id = agent->core_id; } ao2_ref(agent, -1); return SIP_GET_DEST_EXTEN_FOUND; } } if (ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP) && (ast_canmatch_extension(NULL, p->context, uri, 1, S_OR(p->cid_num, from)) || ast_canmatch_extension(NULL, p->context, decoded_uri, 1, S_OR(p->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_GET_DEST_EXTEN_MATCHMORE; } return SIP_GET_DEST_EXTEN_NOT_FOUND; } /*! \brief 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 * * \param callid Callid to search for * \param totag to-tag parameter from Replaces * \param fromtag from-tag parameter from Replaces * \param[out] out_pvt The found sip_pvt. * \param[out] out_chan The found sip_pvt's owner channel. * \retval 0 Success * \retval non-zero Failure */ static int get_sip_pvt_from_replaces(const char *callid, const char *totag, const char *fromtag, struct sip_pvt **out_pvt, struct ast_channel **out_chan) { struct sip_pvt *sip_pvt_ptr; struct sip_pvt tmp_dialog = { .callid = callid, }; if (totag) { ast_debug(4, "Looking for callid %s (fromtag %s totag %s)\n", callid, fromtag ? fromtag : "", totag ? totag : ""); } /* Search dialogs and find the match */ sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find of dialog in dialogs table"); if (sip_pvt_ptr) { /* Go ahead and lock it (and its owner) before returning */ SCOPED_LOCK(lock, sip_pvt_ptr, sip_pvt_lock, sip_pvt_unlock); if (sip_cfg.pedanticsipchecking) { unsigned char frommismatch = 0, tomismatch = 0; if (ast_strlen_zero(fromtag)) { ast_debug(4, "Matched %s call for callid=%s - no from tag specified, pedantic check fails\n", sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid); return -1; } if (ast_strlen_zero(totag)) { ast_debug(4, "Matched %s call for callid=%s - no to tag specified, pedantic check fails\n", sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid); return -1; } /* RFC 3891 * > 3. 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. */ frommismatch = !!strcmp(fromtag, sip_pvt_ptr->theirtag); tomismatch = !!strcmp(totag, sip_pvt_ptr->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 ((frommismatch && ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) || tomismatch) { if (frommismatch) { ast_debug(4, "Matched %s call for callid=%s - pedantic from tag check fails; their tag is %s our tag is %s\n", sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid, fromtag, sip_pvt_ptr->theirtag); } if (tomismatch) { ast_debug(4, "Matched %s call for callid=%s - pedantic to tag check fails; their tag is %s our tag is %s\n", sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid, totag, sip_pvt_ptr->tag); } return -1; } } if (totag) ast_debug(4, "Matched %s call - their tag is %s Our tag is %s\n", sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->theirtag, sip_pvt_ptr->tag); *out_pvt = sip_pvt_ptr; if (out_chan) { *out_chan = sip_pvt_ptr->owner ? ast_channel_ref(sip_pvt_ptr->owner) : NULL; } } return 0; } /*! \brief Call transfer support (the REFER method) * Extracts Refer headers into pvt dialog structure * * \note 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. */ static int get_refer_info(struct sip_pvt *transferer, struct sip_request *outgoing_req) { const char *p_referred_by = NULL; char *h_refer_to = NULL; char *h_referred_by = NULL; char *refer_to; const char *p_refer_to; char *referred_by_uri = NULL; char *ptr; struct sip_request *req = NULL; const char *transfer_context = NULL; struct sip_refer *refer; req = outgoing_req; refer = transferer->refer; if (!req) { req = &transferer->initreq; } p_refer_to = sip_get_header(req, "Refer-To"); if (ast_strlen_zero(p_refer_to)) { ast_log(LOG_WARNING, "Refer-To Header missing. Skipping transfer.\n"); return -2; /* Syntax error */ } h_refer_to = ast_strdupa(p_refer_to); refer_to = get_in_brackets(h_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; } /* Get referred by header if it exists */ p_referred_by = sip_get_header(req, "Referred-By"); /* Give useful transfer information to the dialplan */ if (transferer->owner) { RAII_VAR(struct ast_channel *, peer, NULL, ast_channel_cleanup); RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); RAII_VAR(struct ast_channel *, owner_ref, NULL, ast_channel_cleanup); /* Grab a reference to transferer->owner to prevent it from going away */ owner_ref = ast_channel_ref(transferer->owner); /* Established locking order here is bridge, channel, pvt * and the bridge will be locked during ast_channel_bridge_peer */ ast_channel_unlock(owner_ref); sip_pvt_unlock(transferer); peer = ast_channel_bridge_peer(owner_ref); if (peer) { pbx_builtin_setvar_helper(peer, "SIPREFERRINGCONTEXT", S_OR(transferer->context, NULL)); pbx_builtin_setvar_helper(peer, "__SIPREFERREDBYHDR", S_OR(p_referred_by, NULL)); ast_channel_unlock(peer); } owner_relock = sip_pvt_lock_full(transferer); if (!owner_relock) { ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); return -5; } } if (!ast_strlen_zero(p_referred_by)) { h_referred_by = ast_strdupa(p_referred_by); referred_by_uri = get_in_brackets(h_referred_by); if (!strncasecmp(referred_by_uri, "sip:", 4)) { referred_by_uri += 4; /* Skip sip: */ } else if (!strncasecmp(referred_by_uri, "sips:", 5)) { referred_by_uri += 5; /* Skip sips: */ } else { ast_log(LOG_WARNING, "Huh? Not a sip: header (Referred-by: %s). Skipping.\n", referred_by_uri); referred_by_uri = NULL; } } /* Check for arguments in the refer_to header */ if ((ptr = strcasestr(refer_to, "replaces="))) { char *to = NULL, *from = NULL, *callid; /* This is an attended transfer */ refer->attendedtransfer = 1; callid = ast_strdupa(ptr + 9); ast_uri_decode(callid, ast_uri_sip_user); if ((ptr = strchr(callid, ';'))) { /* Find options */ *ptr++ = '\0'; } ast_string_field_set(refer, replaces_callid, callid); if (ptr) { /* Find the different tags before we destroy the string */ to = strcasestr(ptr, "to-tag="); from = strcasestr(ptr, "from-tag="); } /* Grab the to header */ if (to) { ptr = to + 7; if ((to = strchr(ptr, '&'))) { *to = '\0'; } if ((to = strchr(ptr, ';'))) { *to = '\0'; } ast_string_field_set(refer, replaces_callid_totag, ptr); } if (from) { ptr = from + 9; if ((from = strchr(ptr, '&'))) { *from = '\0'; } if ((from = strchr(ptr, ';'))) { *from = '\0'; } ast_string_field_set(refer, replaces_callid_fromtag, ptr); } if (!strcmp(refer->replaces_callid, transferer->callid) && (!sip_cfg.pedanticsipchecking || (!strcmp(refer->replaces_callid_fromtag, transferer->theirtag) && !strcmp(refer->replaces_callid_totag, transferer->tag)))) { ast_log(LOG_WARNING, "Got an attempt to replace own Call-ID on %s\n", transferer->callid); return -4; } if (!sip_cfg.pedanticsipchecking) { ast_debug(2, "Attended transfer: Will use Replace-Call-ID : %s (No check of from/to tags)\n", refer->replaces_callid); } else { ast_debug(2, "Attended transfer: Will use Replace-Call-ID : %s F-tag: %s T-tag: %s\n", refer->replaces_callid, refer->replaces_callid_fromtag ? refer->replaces_callid_fromtag : "", refer->replaces_callid_totag ? refer->replaces_callid_totag : ""); } } if ((ptr = strchr(refer_to, '@'))) { /* Separate domain */ char *urioption = NULL, *domain; int bracket = 0; *ptr++ = '\0'; if ((urioption = strchr(ptr, ';'))) { /* Separate urioptions */ *urioption++ = '\0'; } domain = ptr; /* Remove :port */ for (; *ptr != '\0'; ++ptr) { if (*ptr == ':' && bracket == 0) { *ptr = '\0'; break; } else if (*ptr == '[') { ++bracket; } else if (*ptr == ']') { --bracket; } } SIP_PEDANTIC_DECODE(domain); SIP_PEDANTIC_DECODE(urioption); /* Save the domain for the dial plan */ ast_string_field_set(refer, refer_to_domain, domain); if (urioption) { ast_string_field_set(refer, refer_to_urioption, urioption); } } if ((ptr = strchr(refer_to, ';'))) /* Remove options */ *ptr = '\0'; SIP_PEDANTIC_DECODE(refer_to); ast_string_field_set(refer, refer_to, refer_to); if (referred_by_uri) { if ((ptr = strchr(referred_by_uri, ';'))) /* Remove options */ *ptr = '\0'; SIP_PEDANTIC_DECODE(referred_by_uri); ast_string_field_build(refer, referred_by, "", referred_by_uri); } else { ast_string_field_set(refer, referred_by, ""); } /* Determine transfer context */ if (transferer->owner) { /* By default, use the context in the channel sending the REFER */ transfer_context = pbx_builtin_getvar_helper(transferer->owner, "TRANSFER_CONTEXT"); if (ast_strlen_zero(transfer_context)) { transfer_context = ast_channel_macrocontext(transferer->owner); } } if (ast_strlen_zero(transfer_context)) { transfer_context = S_OR(transferer->context, sip_cfg.default_context); } ast_string_field_set(refer, refer_to_context, transfer_context); /* Either an existing extension or the parking extension */ if (refer->attendedtransfer || ast_exists_extension(NULL, transfer_context, refer_to, 1, NULL)) { if (sip_debug_test_pvt(transferer)) { ast_verbose("SIP transfer to extension %s@%s by %s\n", refer_to, transfer_context, S_OR(referred_by_uri, "Unknown")); } /* We are ready to transfer to the extension */ return 0; } if (sip_debug_test_pvt(transferer)) ast_verbose("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; } /*! \brief Call transfer support (old way, deprecated by the IETF) * \note does not account for SIPS: uri requirements, nor check transport */ static int get_also_info(struct sip_pvt *p, struct sip_request *oreq) { char tmp[256] = "", *c, *a; struct sip_request *req = oreq ? oreq : &p->initreq; struct sip_refer *refer = NULL; const char *transfer_context = NULL; if (!sip_refer_alloc(p)) { return -1; } refer = p->refer; ast_copy_string(tmp, sip_get_header(req, "Also"), sizeof(tmp)); c = get_in_brackets(tmp); if (parse_uri_legacy_check(c, "sip:,sips:", &c, NULL, &a, NULL)) { ast_log(LOG_WARNING, "Huh? Not a SIP header in Also: transfer (%s)?\n", c); return -1; } SIP_PEDANTIC_DECODE(c); SIP_PEDANTIC_DECODE(a); if (!ast_strlen_zero(a)) { ast_string_field_set(refer, refer_to_domain, a); } if (sip_debug_test_pvt(p)) ast_verbose("Looking for %s in %s\n", c, p->context); /* Determine transfer context */ if (p->owner) { /* By default, use the context in the channel sending the REFER */ transfer_context = pbx_builtin_getvar_helper(p->owner, "TRANSFER_CONTEXT"); if (ast_strlen_zero(transfer_context)) { transfer_context = ast_channel_macrocontext(p->owner); } } if (ast_strlen_zero(transfer_context)) { transfer_context = S_OR(p->context, sip_cfg.default_context); } if (ast_exists_extension(NULL, transfer_context, c, 1, NULL)) { /* This is a blind transfer */ ast_debug(1, "SIP Bye-also transfer to Extension %s@%s \n", c, transfer_context); ast_string_field_set(refer, refer_to, c); ast_string_field_set(refer, referred_by, ""); ast_string_field_set(refer, refer_contact, ""); /* Set new context */ ast_string_field_set(p, context, transfer_context); return 0; } else if (ast_canmatch_extension(NULL, p->context, c, 1, NULL)) { return 1; } return -1; } /*! \brief Set the peers nat flags if they are using auto_* settings */ static void set_peer_nat(const struct sip_pvt *p, struct sip_peer *peer) { if (!p || !peer) { return; } if (ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) { if (p->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_PAGE3_NAT_AUTO_COMEDIA)) { if (p->natdetected) { ast_set_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP); } else { ast_clear_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP); } } } /*! \brief 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. */ static void check_for_nat(const struct ast_sockaddr *addr, struct sip_pvt *p) { if (!addr || !p) { return; } if (ast_sockaddr_cmp_addr(addr, &p->recv)) { char *tmp_str = ast_strdupa(ast_sockaddr_stringify_addr(addr)); ast_debug(3, "NAT detected for %s / %s\n", tmp_str, ast_sockaddr_stringify_addr(&p->recv)); p->natdetected = 1; if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) { ast_set_flag(&p->flags[0], SIP_NAT_FORCE_RPORT); } if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) { ast_set_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP); } } else { p->natdetected = 0; if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) { ast_clear_flag(&p->flags[0], SIP_NAT_FORCE_RPORT); } if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) { ast_clear_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP); } } } /*! \brief check Via: header for hostname, port and rport request/answer */ static void check_via(struct sip_pvt *p, const struct sip_request *req) { char via[512]; char *c, *maddr; struct ast_sockaddr tmp = { { 0, } }; uint16_t port; ast_copy_string(via, sip_get_header(req, "Via"), sizeof(via)); /* If this is via WebSocket we don't use the Via header contents at all */ if (!strncasecmp(via, "SIP/2.0/WS", 10)) { return; } /* Work on the leftmost value of the topmost Via header */ c = strchr(via, ','); if (c) *c = '\0'; /* Check for rport */ c = strstr(via, ";rport"); if (c && (c[6] != '=')) { /* rport query, not answer */ ast_set_flag(&p->flags[1], SIP_PAGE2_RPORT_PRESENT); ast_set_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT); } /* Check for maddr */ maddr = strstr(via, "maddr="); if (maddr) { maddr += 6; c = maddr + strspn(maddr, "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.:[]"); *c = '\0'; } c = strchr(via, ';'); if (c) *c = '\0'; c = strchr(via, ' '); if (c) { *c = '\0'; c = ast_skip_blanks(c+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(&p->sa, maddr, 0)) { p->sa = p->recv; } if (ast_sockaddr_resolve_first(&tmp, c, 0)) { ast_log(LOG_WARNING, "Could not resolve socket address for '%s'\n", c); port = STANDARD_SIP_PORT; } else if (!(port = ast_sockaddr_port(&tmp))) { port = STANDARD_SIP_PORT; ast_sockaddr_set_port(&tmp, port); } ast_sockaddr_set_port(&p->sa, port); check_for_nat(&tmp, p); if (sip_debug_test_pvt(p)) { ast_verbose("Sending to %s (%s)\n", ast_sockaddr_stringify(sip_real_dst(p)), sip_nat_mode(p)); } } } /*! \brief Validate device authentication */ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, struct sip_request *req, int sipmethod, struct ast_sockaddr *addr, struct sip_peer **authpeer, enum xmittype reliable, char *calleridname, char *uri2) { enum check_auth_result res; int debug = sip_debug_test_addr(addr); struct sip_peer *peer; if (sipmethod == SIP_SUBSCRIBE) { /* For subscribes, match on device name only; for other methods, * match on IP address-port of the incoming request. */ peer = sip_find_peer(of, NULL, TRUE, FINDALLDEVICES, FALSE, 0); } else { /* First find devices based on username (avoid all type=peer's) */ peer = sip_find_peer(of, NULL, TRUE, FINDUSERS, FALSE, 0); /* Then find devices based on IP */ if (!peer) { char *uri_tmp, *callback = NULL, *dummy; uri_tmp = ast_strdupa(uri2); parse_uri(uri_tmp, "sip:,sips:,tel:", &callback, &dummy, &dummy, &dummy); if (!ast_strlen_zero(callback) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callback, p->socket.type))) { ; /* found, fall through */ } else { peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); } } } if (!peer) { if (debug) { ast_verbose("No matching peer for '%s' from '%s'\n", of, ast_sockaddr_stringify(&p->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_cfg.allowguest || !sip_cfg.alwaysauthreject) { return 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. */ peer = bogus_peer; sip_ref_peer(peer, "sip_ref_peer: check_peer_ok: must ref bogus_peer so unreffing it does not fail"); } /* build_peer, called through sip_find_peer, is not able to check the * sip_pvt->natdetected flag in order to determine if the peer is behind * NAT or not when SIP_PAGE3_NAT_AUTO_RPORT or SIP_PAGE3_NAT_AUTO_COMEDIA * are set on the peer. So we check for that here and set the peer's * address accordingly. */ set_peer_nat(p, peer); if (p->natdetected && ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) { ast_sockaddr_copy(&peer->addr, &p->recv); } 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, of); sip_unref_peer(peer, "sip_unref_peer: check_peer_ok: from sip_find_peer call, early return of AUTH_ACL_FAILED"); return AUTH_ACL_FAILED; } if (debug && peer != bogus_peer) { ast_verbose("Found peer '%s' for '%s' from %s\n", peer->name, of, ast_sockaddr_stringify(&p->recv)); } /* Set Frame packetization */ if (p->rtp) { ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), ast_format_cap_get_framing(peer->caps)); p->autoframing = peer->autoframing; } /* Take the peer */ ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) && p->udptl) { p->t38_maxdatagram = peer->t38_maxdatagram; set_t38_capabilities(p); } ast_rtp_dtls_cfg_copy(&peer->dtls_cfg, &p->dtls_cfg); /* Copy SIP extensions profile to peer */ /* XXX is this correct before a successful auth ? */ if (p->sipoptions) peer->sipoptions = p->sipoptions; do_setnat(p); ast_string_field_set(p, peersecret, peer->secret); ast_string_field_set(p, peermd5secret, peer->md5secret); ast_string_field_set(p, subscribecontext, peer->subscribecontext); ast_string_field_set(p, mohinterpret, peer->mohinterpret); ast_string_field_set(p, mohsuggest, peer->mohsuggest); if (!ast_strlen_zero(peer->parkinglot)) { ast_string_field_set(p, parkinglot, peer->parkinglot); } ast_string_field_set(p, engine, peer->engine); p->disallowed_methods = peer->disallowed_methods; set_pvt_allowed_methods(p, req); ast_cc_copy_config_params(p->cc_params, peer->cc_params); if (peer->callingpres) /* Peer calling pres setting will override RPID */ p->callingpres = peer->callingpres; if (peer->maxms && peer->lastms) p->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms; else p->timer_t1 = peer->timer_t1; /* Set timer B to control transaction timeouts */ if (peer->timer_b) p->timer_b = peer->timer_b; else p->timer_b = 64 * p->timer_t1; p->allowtransfer = peer->allowtransfer; if (ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)) { /* Pretend there is no required authentication */ ast_string_field_set(p, peersecret, NULL); ast_string_field_set(p, peermd5secret, NULL); } if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { /* If we have a call limit, set flag */ if (peer->call_limit) ast_set_flag(&p->flags[0], SIP_CALL_LIMIT); ast_string_field_set(p, peername, peer->name); ast_string_field_set(p, authname, peer->name); ast_rtp_dtls_cfg_copy(&peer->dtls_cfg, &p->dtls_cfg); if (sipmethod == SIP_INVITE) { /* destroy old channel vars and copy in new ones. */ ast_variables_destroy(p->chanvars); p->chanvars = copy_vars(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(p, username, peer->username); /* Use the default username for authentication on outbound calls */ /* XXX this takes the name from the caller... can we override ? */ ast_string_field_set(p, authname, peer->username); } if (!get_rpid(p, req)) { if (!ast_strlen_zero(peer->cid_num)) { char *tmp = ast_strdupa(peer->cid_num); if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(tmp)) ast_shrink_phone_number(tmp); ast_string_field_set(p, cid_num, tmp); } if (!ast_strlen_zero(peer->cid_name)) ast_string_field_set(p, cid_name, peer->cid_name); if (peer->callingpres) p->callingpres = peer->callingpres; } if (!ast_strlen_zero(peer->cid_tag)) { ast_string_field_set(p, cid_tag, peer->cid_tag); } ast_string_field_set(p, fullcontact, peer->fullcontact); if (!ast_strlen_zero(peer->context)) { ast_string_field_set(p, context, peer->context); } if (!ast_strlen_zero(peer->messagecontext)) { ast_string_field_set(p, messagecontext, peer->messagecontext); } if (!ast_strlen_zero(peer->mwi_from)) { ast_string_field_set(p, mwi_from, peer->mwi_from); } ast_string_field_set(p, peersecret, peer->secret); ast_string_field_set(p, peermd5secret, peer->md5secret); ast_string_field_set(p, language, peer->language); ast_string_field_set(p, accountcode, peer->accountcode); p->amaflags = peer->amaflags; p->callgroup = peer->callgroup; p->pickupgroup = peer->pickupgroup; ast_unref_namedgroups(p->named_callgroups); p->named_callgroups = ast_ref_namedgroups(peer->named_callgroups); ast_unref_namedgroups(p->named_pickupgroups); p->named_pickupgroups = ast_ref_namedgroups(peer->named_pickupgroups); ast_format_cap_remove_by_type(p->caps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->caps, peer->caps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_remove_by_type(p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->jointcaps, peer->caps, AST_MEDIA_TYPE_UNKNOWN); ast_copy_string(p->zone, peer->zone, sizeof(p->zone)); if (peer->maxforwards > 0) { p->maxforwards = peer->maxforwards; } if (ast_format_cap_count(p->peercaps)) { struct ast_format_cap *joint; joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (joint) { ast_format_cap_get_compatible(p->jointcaps, p->peercaps, joint); ao2_ref(p->jointcaps, -1); p->jointcaps = joint; } } p->maxcallbitrate = peer->maxcallbitrate; if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) || (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) p->noncodeccapability |= AST_RTP_DTMF; else p->noncodeccapability &= ~AST_RTP_DTMF; p->jointnoncodeccapability = p->noncodeccapability; p->rtptimeout = peer->rtptimeout; p->rtpholdtimeout = peer->rtpholdtimeout; p->rtpkeepalive = peer->rtpkeepalive; if (!dialog_initialize_rtp(p)) { if (p->rtp) { ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), ast_format_cap_get_framing(peer->caps)); p->autoframing = peer->autoframing; } } else { res = AUTH_RTP_FAILED; } } sip_unref_peer(peer, "check_peer_ok: sip_unref_peer: tossing temp ptr to peer from sip_find_peer"); return res; } /*! \brief 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 \return 0 on success, non-zero on failure */ static enum check_auth_result check_user_full(struct sip_pvt *p, struct sip_request *req, int sipmethod, const char *uri, enum xmittype reliable, struct ast_sockaddr *addr, struct sip_peer **authpeer) { char *of, *name, *unused_password, *domain; RAII_VAR(char *, ofbuf, NULL, ast_free); /* beware, everyone starts pointing to this */ RAII_VAR(char *, namebuf, NULL, ast_free); enum check_auth_result res = AUTH_DONT_KNOW; char calleridname[256]; char *uri2 = ast_strdupa(uri); terminate_uri(uri2); /* trim extra stuff */ ofbuf = ast_strdup(sip_get_header(req, "From")); /* XXX here tries to map the username for invite things */ /* strip the display-name portion off the beginning of the FROM header. */ if (!(of = (char *) get_calleridname(ofbuf, calleridname, sizeof(calleridname)))) { ast_log(LOG_ERROR, "FROM header can not be parsed\n"); return res; } if (calleridname[0]) { ast_string_field_set(p, cid_name, calleridname); } if (ast_strlen_zero(p->exten)) { char *t = uri2; if (!strncasecmp(t, "sip:", 4)) { t += 4; } else if (!strncasecmp(t, "sips:", 5)) { t += 5; } else if (!strncasecmp(t, "tel:", 4)) { /* TEL URI INVITE */ t += 4; } ast_string_field_set(p, exten, t); t = strchr(p->exten, '@'); if (t) *t = '\0'; if (ast_strlen_zero(p->our_contact)) build_contact(p); } of = get_in_brackets(of); /* save the URI part of the From header */ ast_string_field_set(p, from, of); if (parse_uri_legacy_check(of, "sip:,sips:,tel:", &name, &unused_password, &domain, NULL)) { ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n"); } SIP_PEDANTIC_DECODE(name); SIP_PEDANTIC_DECODE(domain); extract_host_from_hostport(&domain); 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 *tmp = ast_strdupa(name); /* We need to be able to handle from-headers looking like */ tmp = strsep(&tmp, ";"); if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(tmp)) { ast_shrink_phone_number(tmp); } ast_string_field_set(p, cid_num, tmp); } if (global_match_auth_username) { /* * XXX 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. * Note2, at the moment we check both fields, though maybe we should * pick one or another depending on the request ? XXX */ const char *hdr = sip_get_header(req, "Authorization"); if (ast_strlen_zero(hdr)) { hdr = sip_get_header(req, "Proxy-Authorization"); } if (!ast_strlen_zero(hdr) && (hdr = strstr(hdr, "username=\""))) { namebuf = name = ast_strdup(hdr + strlen("username=\"")); name = strsep(&name, "\""); } } res = check_peer_ok(p, name, req, sipmethod, addr, authpeer, reliable, calleridname, uri2); if (res != AUTH_DONT_KNOW) { return res; } /* Finally, apply the guest policy */ if (sip_cfg.allowguest) { /* Ignore check_return warning from Coverity for get_rpid below. */ get_rpid(p, req); p->rtptimeout = global_rtptimeout; p->rtpholdtimeout = global_rtpholdtimeout; p->rtpkeepalive = global_rtpkeepalive; if (!dialog_initialize_rtp(p)) { res = AUTH_SUCCESSFUL; } else { res = AUTH_RTP_FAILED; } } else { res = AUTH_SECRET_FAILED; /* we don't want any guests, authentication will fail */ } if (ast_test_flag(&p->flags[1], SIP_PAGE2_RPORT_PRESENT)) { ast_set_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT); } return res; } /*! \brief Find user If we get a match, this will add a reference pointer to the user object, that needs to be unreferenced */ static int check_user(struct sip_pvt *p, struct sip_request *req, int sipmethod, const char *uri, enum xmittype reliable, struct ast_sockaddr *addr) { return check_user_full(p, req, sipmethod, uri, reliable, addr, NULL); } static int set_message_vars_from_req(struct ast_msg *msg, struct sip_request *req) { size_t x; char name_buf[1024]; char val_buf[1024]; const char *name; char *c; int res = 0; for (x = 0; x < req->headers; x++) { const char *header = REQ_OFFSET_TO_STR(req, header[x]); if ((c = strchr(header, ':'))) { ast_copy_string(name_buf, header, MIN((c - header + 1), sizeof(name_buf))); ast_copy_string(val_buf, ast_skip_blanks(c + 1), sizeof(val_buf)); ast_trim_blanks(name_buf); /* Convert header name to full name alias. */ name = find_full_alias(name_buf, name_buf); res = ast_msg_set_var(msg, name, val_buf); if (res) { break; } } } return res; } /*! \brief Receive SIP MESSAGE method messages \note We only handle messages within current calls currently Reference: RFC 3428 */ static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e) { char *buf; size_t len; struct ast_frame f; const char *content_type = sip_get_header(req, "Content-Type"); struct ast_msg *msg; int res; char *from; char *to; char from_name[50]; char stripped[SIPBUFSIZE]; enum sip_get_dest_result dest_result; if (strncmp(content_type, "text/plain", strlen("text/plain"))) { /* No text/plain attachment */ transmit_response(p, "415 Unsupported Media Type", req); /* Good enough, or? */ if (!p->owner) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } return; } if (!(buf = get_content(req))) { ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid); transmit_response(p, "500 Internal Server Error", req); if (!p->owner) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } return; } /* Strip trailing line feeds from message body. (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 (p->owner) { if (sip_debug_test_pvt(p)) { ast_verbose("SIP Text message received: '%s'\n", buf); } memset(&f, 0, sizeof(f)); f.frametype = AST_FRAME_TEXT; f.subclass.integer = 0; f.offset = 0; f.data.ptr = buf; f.datalen = strlen(buf) + 1; ast_queue_frame(p->owner, &f); transmit_response(p, "202 Accepted", req); /* We respond 202 accepted, since we relay the message */ return; } /* * At this point MESSAGE is outside of a call. * * NOTE: p->owner is NULL so no additional check is needed after * this point. */ if (!sip_cfg.accept_outofcall_message) { /* Message outside of a call, we do not support that */ ast_debug(1, "MESSAGE outside of a call administratively disabled.\n"); transmit_response(p, "405 Method Not Allowed", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } copy_request(&p->initreq, req); if (sip_cfg.auth_message_requests) { int res; set_pvt_allowed_methods(p, req); res = check_user(p, req, SIP_MESSAGE, e, XMIT_UNRELIABLE, addr); if (res == AUTH_CHALLENGE_SENT) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } if (res < 0) { /* Something failed in authentication */ ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From")); transmit_response(p, "403 Forbidden", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } /* 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. */ peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, 0, p->socket.type); if (peer) { /* Only if no auth is required. */ if (ast_strlen_zero(peer->secret) && ast_strlen_zero(peer->md5secret)) { ast_string_field_set(p, context, peer->context); } if (!ast_strlen_zero(peer->messagecontext)) { ast_string_field_set(p, messagecontext, peer->messagecontext); } ast_string_field_set(p, peername, peer->name); peer = sip_unref_peer(peer, "from sip_find_peer() 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(p->messagecontext)) { ast_string_field_set(p, context, p->messagecontext); } else if (!ast_strlen_zero(sip_cfg.messagecontext)) { ast_string_field_set(p, context, sip_cfg.messagecontext); } dest_result = get_destination(p, NULL, NULL); switch (dest_result) { case SIP_GET_DEST_REFUSED: /* Okay to send 403 since this is after auth processing */ transmit_response(p, "403 Forbidden", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; case SIP_GET_DEST_INVALID_URI: transmit_response(p, "416 Unsupported URI Scheme", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; 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())) { transmit_response(p, "500 Internal Server Error", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } to = ast_strdupa(REQ_OFFSET_TO_STR(req, rlpart2)); from = ast_strdupa(sip_get_header(req, "From")); res = ast_msg_set_to(msg, "%s", to); /* Build "display" for from string. */ from = (char *) get_calleridname(from, from_name, sizeof(from_name)); from = get_in_brackets(from); if (from_name[0]) { char from_buf[128]; ast_escape_quoted(from_name, from_buf, sizeof(from_buf)); res |= ast_msg_set_from(msg, "\"%s\" <%s>", from_buf, from); } else { res |= ast_msg_set_from(msg, "<%s>", from); } res |= ast_msg_set_body(msg, "%s", buf); res |= ast_msg_set_context(msg, "%s", p->context); res |= ast_msg_set_var(msg, "SIP_RECVADDR", ast_sockaddr_stringify(&p->recv)); res |= ast_msg_set_tech(msg, "%s", "SIP"); if (!ast_strlen_zero(p->peername)) { res |= ast_msg_set_endpoint(msg, "%s", p->peername); res |= ast_msg_set_var(msg, "SIP_PEERNAME", p->peername); } ast_copy_string(stripped, sip_get_header(req, "Contact"), sizeof(stripped)); res |= ast_msg_set_var(msg, "SIP_FULLCONTACT", get_in_brackets(stripped)); res |= ast_msg_set_exten(msg, "%s", p->exten); res |= set_message_vars_from_req(msg, req); if (res) { ast_msg_destroy(msg); transmit_response(p, "500 Internal Server Error", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } if (ast_msg_has_destination(msg)) { ast_msg_queue(msg); transmit_response(p, "202 Accepted", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } /* Find a specific error cause to send */ switch (dest_result) { case SIP_GET_DEST_EXTEN_NOT_FOUND: case SIP_GET_DEST_EXTEN_MATCHMORE: transmit_response(p, "404 Not Found", req); break; case SIP_GET_DEST_EXTEN_FOUND: default: /* We should have sent the message already! */ ast_assert(0); transmit_response(p, "500 Internal Server Error", req); break; } sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_msg_destroy(msg); } /*! \brief CLI Command to show calls within limits set by call_limit */ static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%-25.25s %-15.15s %-15.15s \n" #define FORMAT2 "%-25.25s %-15.15s %-15.15s \n" char ilimits[40]; char iused[40]; int showall = FALSE; struct ao2_iterator i; struct sip_peer *peer; switch (cmd) { case CLI_INIT: e->command = "sip show inuse"; e->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; case CLI_GENERATE: return NULL; } if (a->argc < 3) return CLI_SHOWUSAGE; if (a->argc == 4 && !strcmp(a->argv[3], "all")) showall = TRUE; ast_cli(a->fd, FORMAT, "* Peer name", "In use", "Limit"); i = ao2_iterator_init(peers, 0); while ((peer = ao2_t_iterator_next(&i, "iterate thru peer table"))) { ao2_lock(peer); if (peer->call_limit) snprintf(ilimits, sizeof(ilimits), "%d", peer->call_limit); else ast_copy_string(ilimits, "N/A", sizeof(ilimits)); snprintf(iused, sizeof(iused), "%d/%d/%d", peer->inuse, peer->ringing, peer->onhold); if (showall || peer->call_limit) ast_cli(a->fd, FORMAT2, peer->name, iused, ilimits); ao2_unlock(peer); sip_unref_peer(peer, "toss iterator pointer"); } ao2_iterator_destroy(&i); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } /*! \brief Convert transfer mode to text string */ static char *transfermode2str(enum transfermodes mode) { if (mode == TRANSFER_OPENFORALL) return "open"; else if (mode == TRANSFER_CLOSED) return "closed"; return "strict"; } /*! \brief Report Peer status in character string * \return 0 if peer is unreachable, 1 if peer is online, -1 if unmonitored */ /* Session-Timer Modes */ static const struct _map_x_s stmodes[] = { { SESSION_TIMER_MODE_ACCEPT, "Accept"}, { SESSION_TIMER_MODE_ORIGINATE, "Originate"}, { SESSION_TIMER_MODE_REFUSE, "Refuse"}, { -1, NULL}, }; static const char *stmode2str(enum st_mode m) { return map_x_s(stmodes, m, "Unknown"); } static enum st_mode str2stmode(const char *s) { return map_s_x(stmodes, s, -1); } /* Session-Timer Refreshers */ static const struct _map_x_s strefresher_params[] = { { SESSION_TIMER_REFRESHER_PARAM_UNKNOWN, "unknown" }, { SESSION_TIMER_REFRESHER_PARAM_UAC, "uac" }, { SESSION_TIMER_REFRESHER_PARAM_UAS, "uas" }, { -1, NULL }, }; static const struct _map_x_s strefreshers[] = { { SESSION_TIMER_REFRESHER_AUTO, "auto" }, { SESSION_TIMER_REFRESHER_US, "us" }, { SESSION_TIMER_REFRESHER_THEM, "them" }, { -1, NULL }, }; static const char *strefresherparam2str(enum st_refresher r) { return map_x_s(strefresher_params, r, "Unknown"); } static enum st_refresher str2strefresherparam(const char *s) { return map_s_x(strefresher_params, s, -1); } /* Autocreatepeer modes */ static struct _map_x_s autopeermodes[] = { { AUTOPEERS_DISABLED, "Off"}, { AUTOPEERS_VOLATILE, "Volatile"}, { AUTOPEERS_PERSIST, "Persisted"}, { -1, NULL}, }; static const char *strefresher2str(enum st_refresher r) { return map_x_s(strefreshers, r, "Unknown"); } static const char *autocreatepeer2str(enum autocreatepeer_mode r) { return map_x_s(autopeermodes, r, "Unknown"); } static int peer_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; } /*! \brief Show active TCP connections */ static char *sip_show_tcp(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct sip_threadinfo *th; struct ao2_iterator i; #define FORMAT2 "%-47.47s %9.9s %6.6s\n" #define FORMAT "%-47.47s %-9.9s %-6.6s\n" switch (cmd) { case CLI_INIT: e->command = "sip show tcp"; e->usage = "Usage: sip show tcp\n" " Lists all active TCP/TLS sessions.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, FORMAT2, "Address", "Transport", "Type"); i = ao2_iterator_init(threadt, 0); while ((th = ao2_t_iterator_next(&i, "iterate through tcp threads for 'sip show tcp'"))) { ast_cli(a->fd, FORMAT, ast_sockaddr_stringify(&th->tcptls_session->remote_address), sip_get_transport(th->type), (th->tcptls_session->client ? "Client" : "Server")); ao2_t_ref(th, -1, "decrement ref from iterator"); } ao2_iterator_destroy(&i); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } /*! \brief CLI Command 'SIP Show Users' */ static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { regex_t regexbuf; int havepattern = FALSE; struct ao2_iterator user_iter; struct sip_peer *user; #define FORMAT "%-25.25s %-15.15s %-15.15s %-15.15s %-5.5s%-10.10s\n" switch (cmd) { case CLI_INIT: e->command = "sip show users"; e->usage = "Usage: sip show users [like ]\n" " Lists all known SIP users.\n" " Optional regular expression pattern is used to filter the user list.\n"; return NULL; case CLI_GENERATE: return NULL; } switch (a->argc) { case 5: if (!strcasecmp(a->argv[3], "like")) { if (regcomp(®exbuf, a->argv[4], REG_EXTENDED | REG_NOSUB)) return CLI_SHOWUSAGE; havepattern = TRUE; } else return CLI_SHOWUSAGE; case 3: break; default: return CLI_SHOWUSAGE; } ast_cli(a->fd, FORMAT, "Username", "Secret", "Accountcode", "Def.Context", "ACL", "Forcerport"); user_iter = ao2_iterator_init(peers, 0); while ((user = ao2_t_iterator_next(&user_iter, "iterate thru peers table"))) { ao2_lock(user); if (!(user->type & SIP_TYPE_USER)) { ao2_unlock(user); sip_unref_peer(user, "sip show users"); continue; } if (havepattern && regexec(®exbuf, user->name, 0, NULL, 0)) { ao2_unlock(user); sip_unref_peer(user, "sip show users"); continue; } ast_cli(a->fd, FORMAT, user->name, user->secret, user->accountcode, user->context, AST_CLI_YESNO(ast_acl_list_is_empty(user->acl) == 0), AST_CLI_YESNO(ast_test_flag(&user->flags[0], SIP_NAT_FORCE_RPORT))); ao2_unlock(user); sip_unref_peer(user, "sip show users"); } ao2_iterator_destroy(&user_iter); if (havepattern) regfree(®exbuf); return CLI_SUCCESS; #undef FORMAT } /*! \brief Show SIP registrations in the manager API */ static int manager_show_registry(struct mansession *s, const struct message *m) { const char *id = astman_get_header(m, "ActionID"); char idtext[256] = ""; int total = 0; struct ao2_iterator iter; struct sip_registry *iterator; if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); astman_send_listack(s, m, "Registrations will follow", "start"); iter = ao2_iterator_init(registry_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "manager_show_registry iter"))) { ao2_lock(iterator); astman_append(s, "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, iterator->hostname, iterator->portno ? iterator->portno : STANDARD_SIP_PORT, iterator->username, S_OR(iterator->regdomain,iterator->hostname), iterator->regdomainport ? iterator->regdomainport : STANDARD_SIP_PORT, iterator->refresh, regstate2str(iterator->regstate), (long) iterator->regtime.tv_sec); ao2_unlock(iterator); ao2_t_ref(iterator, -1, "manager_show_registry iter"); total++; } ao2_iterator_destroy(&iter); astman_append(s, "Event: RegistrationsComplete\r\n" "EventList: Complete\r\n" "ListItems: %d\r\n" "%s" "\r\n", total, idtext); return 0; } /*! \brief Show SIP peers in the manager API */ /* Inspired from chan_iax2 */ static int manager_sip_show_peers(struct mansession *s, const struct message *m) { const char *id = astman_get_header(m, "ActionID"); const char *a[] = {"sip", "show", "peers"}; char idtext[256] = ""; int total = 0; if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); astman_send_listack(s, m, "Peer status list will follow", "start"); /* List the peers in separate manager events */ _sip_show_peers(-1, &total, s, m, 3, a); /* Send final confirmation */ astman_append(s, "Event: PeerlistComplete\r\n" "EventList: Complete\r\n" "ListItems: %d\r\n" "%s" "\r\n", total, idtext); return 0; } /*! \brief CLI Show Peers command */ static char *sip_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "sip show peers"; e->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; case CLI_GENERATE: return NULL; } return _sip_show_peers(a->fd, NULL, NULL, NULL, a->argc, (const char **) a->argv); } int peercomparefunc(const void *a, const void *b); int peercomparefunc(const void *a, const void *b) { struct sip_peer **ap = (struct sip_peer **)a; struct sip_peer **bp = (struct sip_peer **)b; return strcmp((*ap)->name, (*bp)->name); } /* the last argument is left-aligned, so we don't need a size anyways */ #define PEERS_FORMAT2 "%-25.25s %-39.39s %-3.3s %-10.10s %-10.10s %-3.3s %-8s %-11s %-32.32s %s\n" /*! \brief Used in the sip_show_peers functions to pass parameters */ struct show_peers_context { regex_t regexbuf; int havepattern; char idtext[256]; int realtimepeers; int peers_mon_online; int peers_mon_offline; int peers_unmon_offline; int peers_unmon_online; }; /*! \brief Execute sip show peers command */ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const struct message *m, int argc, const char *argv[]) { struct show_peers_context cont = { .havepattern = FALSE, .idtext = "", .peers_mon_online = 0, .peers_mon_offline = 0, .peers_unmon_online = 0, .peers_unmon_offline = 0, }; struct sip_peer *peer; struct ao2_iterator* it_peers; int total_peers = 0; const char *id; struct sip_peer **peerarray; int k; cont.realtimepeers = ast_check_realtime("sippeers"); if (s) { /* Manager - get ActionID */ id = astman_get_header(m, "ActionID"); if (!ast_strlen_zero(id)) { snprintf(cont.idtext, sizeof(cont.idtext), "ActionID: %s\r\n", id); } } switch (argc) { case 5: if (!strcasecmp(argv[3], "like")) { if (regcomp(&cont.regexbuf, argv[4], REG_EXTENDED | REG_NOSUB)) { return CLI_SHOWUSAGE; } cont.havepattern = TRUE; } else { return CLI_SHOWUSAGE; } case 3: break; default: return CLI_SHOWUSAGE; } if (!s) { /* Normal list */ ast_cli(fd, PEERS_FORMAT2, "Name/username", "Host", "Dyn", "Forcerport", "Comedia", "ACL", "Port", "Status", "Description", (cont.realtimepeers ? "Realtime" : "")); } ao2_lock(peers); if (!(it_peers = ao2_callback(peers, OBJ_MULTIPLE, NULL, NULL))) { ast_log(AST_LOG_ERROR, "Unable to create iterator for peers container for sip show peers\n"); ao2_unlock(peers); return CLI_FAILURE; } if (!(peerarray = ast_calloc(sizeof(struct sip_peer *), ao2_container_count(peers)))) { ast_log(AST_LOG_ERROR, "Unable to allocate peer array for sip show peers\n"); ao2_iterator_destroy(it_peers); ao2_unlock(peers); return CLI_FAILURE; } ao2_unlock(peers); while ((peer = ao2_t_iterator_next(it_peers, "iterate thru peers table"))) { ao2_lock(peer); if (!(peer->type & SIP_TYPE_PEER)) { ao2_unlock(peer); sip_unref_peer(peer, "unref peer because it's actually a user"); continue; } if (cont.havepattern && regexec(&cont.regexbuf, peer->name, 0, NULL, 0)) { ao2_unlock(peer); sip_unref_peer(peer, "toss iterator peer ptr before continue"); continue; } peerarray[total_peers++] = peer; ao2_unlock(peer); } ao2_iterator_destroy(it_peers); qsort(peerarray, total_peers, sizeof(struct sip_peer *), peercomparefunc); for(k = 0; k < total_peers; k++) { peerarray[k] = _sip_show_peers_one(fd, s, &cont, peerarray[k]); } if (!s) { ast_cli(fd, "%d sip peers [Monitored: %d online, %d offline Unmonitored: %d online, %d offline]\n", total_peers, cont.peers_mon_online, cont.peers_mon_offline, cont.peers_unmon_online, cont.peers_unmon_offline); } if (cont.havepattern) { regfree(&cont.regexbuf); } if (total) { *total = total_peers; } ast_free(peerarray); return CLI_SUCCESS; } /*! \brief Emit informations for one peer during sip show peers command */ static struct sip_peer *_sip_show_peers_one(int fd, struct mansession *s, struct show_peers_context *cont, struct sip_peer *peer) { /* _sip_show_peers_one() is separated from _sip_show_peers() to properly free the ast_strdupa * (this is executed in a loop in _sip_show_peers() ) */ char name[256]; char status[20] = ""; char pstatus; /* * tmp_port and tmp_host store copies of ast_sockaddr_stringify strings since the * string pointers for that function aren't valid between subsequent calls to * ast_sockaddr_stringify functions */ char *tmp_port; char *tmp_host; tmp_port = ast_sockaddr_isnull(&peer->addr) ? "0" : ast_strdupa(ast_sockaddr_stringify_port(&peer->addr)); tmp_host = ast_sockaddr_isnull(&peer->addr) ? "(Unspecified)" : ast_strdupa(ast_sockaddr_stringify_addr(&peer->addr)); ao2_lock(peer); if (cont->havepattern && regexec(&cont->regexbuf, peer->name, 0, NULL, 0)) { ao2_unlock(peer); return sip_unref_peer(peer, "toss iterator peer ptr no match"); } if (!ast_strlen_zero(peer->username) && !s) { snprintf(name, sizeof(name), "%s/%s", peer->name, peer->username); } else { ast_copy_string(name, peer->name, sizeof(name)); } pstatus = peer_status(peer, status, sizeof(status)); if (pstatus == 1) { cont->peers_mon_online++; } else if (pstatus == 0) { cont->peers_mon_offline++; } else { if (ast_sockaddr_isnull(&peer->addr) || !ast_sockaddr_port(&peer->addr)) { cont->peers_unmon_offline++; } else { cont->peers_unmon_online++; } } if (!s) { /* Normal CLI list */ ast_cli(fd, PEERS_FORMAT2, name, tmp_host, peer->host_dynamic ? " D " : " ", /* Dynamic or not? */ force_rport_string(peer->flags), comedia_string(peer->flags), (!ast_acl_list_is_empty(peer->acl)) ? " A " : " ", /* permit/deny */ tmp_port, status, peer->description ? peer->description : "", cont->realtimepeers ? (peer->is_realtime ? "Cached RT" : "") : ""); } else { /* Manager format */ /* The names here need to be the same as other channels */ astman_append(s, "Event: PeerEntry\r\n%s" "Channeltype: SIP\r\n" "ObjectName: %s\r\n" "ChanObjectType: peer\r\n" /* "peer" or "user" */ "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\r\n", cont->idtext, peer->name, ast_sockaddr_isnull(&peer->addr) ? "-none-" : tmp_host, ast_sockaddr_isnull(&peer->addr) ? "0" : tmp_port, peer->host_dynamic ? "yes" : "no", /* Dynamic or not? */ ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT) ? "yes" : "no", ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? "yes" : "no", ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA) ? "yes" : "no", ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "yes" : "no", ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */ ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */ ast_acl_list_is_empty(peer->acl) ? "no" : "yes", /* permit/deny/acl */ status, cont->realtimepeers ? (peer->is_realtime ? "yes" : "no") : "no", peer->description); } ao2_unlock(peer); return sip_unref_peer(peer, "toss iterator peer ptr"); } #undef PEERS_FORMAT2 static int peer_dump_func(void *userobj, void *arg, int flags) { struct sip_peer *peer = userobj; int refc = ao2_t_ref(userobj, 0, ""); struct ast_cli_args *a = (struct ast_cli_args *) arg; ast_cli(a->fd, "name: %s\ntype: peer\nobjflags: %d\nrefcount: %d\n\n", peer->name, 0, refc); return 0; } static int dialog_dump_func(void *userobj, void *arg, int flags) { struct sip_pvt *pvt = userobj; int refc = ao2_t_ref(userobj, 0, ""); struct ast_cli_args *a = (struct ast_cli_args *) arg; ast_cli(a->fd, "name: %s\ntype: dialog\nobjflags: %d\nrefcount: %d\n\n", pvt->callid, 0, refc); return 0; } /*! \brief List all allocated SIP Objects (realtime or static) */ static char *sip_show_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct sip_registry *reg; struct ao2_iterator iter; switch (cmd) { case CLI_INIT: e->command = "sip show objects"; e->usage = "Usage: sip show objects\n" " Lists status of known SIP objects\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, "-= Peer objects: %d static, %d realtime, %d autocreate =-\n\n", speerobjs, rpeerobjs, apeerobjs); ao2_t_callback(peers, OBJ_NODATA, peer_dump_func, a, "initiate ao2_callback to dump peers"); ast_cli(a->fd, "-= Peer objects by IP =-\n\n"); ao2_t_callback(peers_by_ip, OBJ_NODATA, peer_dump_func, a, "initiate ao2_callback to dump peers_by_ip"); iter = ao2_iterator_init(registry_list, 0); ast_cli(a->fd, "-= Registry objects: %d =-\n\n", ao2_container_count(registry_list)); while ((reg = ao2_t_iterator_next(&iter, "sip_show_objects iter"))) { ao2_lock(reg); ast_cli(a->fd, "name: %s\n", reg->configvalue); ao2_unlock(reg); ao2_t_ref(reg, -1, "sip_show_objects iter"); } ao2_iterator_destroy(&iter); ast_cli(a->fd, "-= Dialog objects:\n\n"); ao2_t_callback(dialogs, OBJ_NODATA, dialog_dump_func, a, "initiate ao2_callback to dump dialogs"); return CLI_SUCCESS; } /*! \brief Print call group and pickup group */ static void print_group(int fd, ast_group_t group, int crlf) { char buf[256]; ast_cli(fd, crlf ? "%s\r\n" : "%s\n", ast_print_group(buf, sizeof(buf), group) ); } /*! \brief Print named call groups and pickup groups */ static void print_named_groups(int fd, struct ast_namedgroups *group, int crlf) { struct ast_str *buf = ast_str_create(1024); if (buf) { ast_cli(fd, crlf ? "%s\r\n" : "%s\n", ast_print_namedgroups(&buf, group) ); ast_free(buf); } } /*! \brief mapping between dtmf flags and strings */ static const struct _map_x_s dtmfstr[] = { { SIP_DTMF_RFC2833, "rfc2833" }, { SIP_DTMF_INFO, "info" }, { SIP_DTMF_SHORTINFO, "shortinfo" }, { SIP_DTMF_INBAND, "inband" }, { SIP_DTMF_AUTO, "auto" }, { -1, NULL }, /* terminator */ }; /*! \brief Convert DTMF mode to printable string */ static const char *dtmfmode2str(int mode) { return map_x_s(dtmfstr, mode, ""); } /*! \brief maps a string to dtmfmode, returns -1 on error */ static int str2dtmfmode(const char *str) { return map_s_x(dtmfstr, str, -1); } static const struct _map_x_s insecurestr[] = { { SIP_INSECURE_PORT, "port" }, { SIP_INSECURE_INVITE, "invite" }, { SIP_INSECURE_PORT | SIP_INSECURE_INVITE, "port,invite" }, { 0, "no" }, { -1, NULL }, /* terminator */ }; /*! \brief Convert Insecure setting to printable string */ static const char *insecure2str(int mode) { return map_x_s(insecurestr, mode, ""); } static const struct _map_x_s allowoverlapstr[] = { { SIP_PAGE2_ALLOWOVERLAP_YES, "Yes" }, { SIP_PAGE2_ALLOWOVERLAP_DTMF, "DTMF" }, { SIP_PAGE2_ALLOWOVERLAP_NO, "No" }, { -1, NULL }, /* terminator */ }; /*! \brief Convert AllowOverlap setting to printable string */ static const char *allowoverlap2str(int mode) { return map_x_s(allowoverlapstr, mode, ""); } static const struct _map_x_s trust_id_outboundstr[] = { { SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY, "Legacy" }, { SIP_PAGE2_TRUST_ID_OUTBOUND_NO, "No" }, { SIP_PAGE2_TRUST_ID_OUTBOUND_YES, "Yes" }, { -1, NULL }, /* terminator */ }; static const char *trust_id_outbound2str(int mode) { return map_x_s(trust_id_outboundstr, mode, ""); } /*! \brief Destroy disused contexts between reloads Only used in reload_config so the code for regcontext doesn't get ugly */ static void cleanup_stale_contexts(char *new, char *old) { char *oldcontext, *newcontext, *stalecontext, *stringp, newlist[AST_MAX_CONTEXT]; while ((oldcontext = strsep(&old, "&"))) { stalecontext = '\0'; ast_copy_string(newlist, new, sizeof(newlist)); stringp = newlist; while ((newcontext = strsep(&stringp, "&"))) { if (!strcmp(newcontext, oldcontext)) { /* This is not the context you're looking for */ stalecontext = '\0'; break; } else if (strcmp(newcontext, oldcontext)) { stalecontext = oldcontext; } } if (stalecontext) ast_context_destroy(ast_context_find(stalecontext), "SIP"); } } /*! * \brief Check RTP Timeout on dialogs * * \details This is used with ao2_callback to check rtptimeout * rtponholdtimeout and send rtpkeepalive packets. * * \return CMP_MATCH for items to be unlinked from dialogs_rtpcheck. */ static int dialog_checkrtp_cb(void *dialogobj, void *arg, int flags) { struct sip_pvt *dialog = dialogobj; time_t *t = arg; int match_status; if (sip_pvt_trylock(dialog)) { return 0; } if (dialog->rtp || dialog->vrtp) { match_status = check_rtp_timeout(dialog, *t); } else { /* Dialog has no active RTP or VRTP. unlink it from dialogs_rtpcheck. */ match_status = CMP_MATCH; } sip_pvt_unlock(dialog); return match_status; } /*! * \brief Match dialogs that need to be destroyed * * \details This is used with ao2_callback to unlink/delete all dialogs that * are marked needdestroy. * * \todo 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. */ static int dialog_needdestroy(void *dialogobj, void *arg, int flags) { struct sip_pvt *dialog = dialogobj; if (sip_pvt_trylock(dialog)) { /* Don't block the monitor thread. This function is called often enough * that we can wait for the next time around. */ return 0; } /* If we have sessions that needs to be destroyed, do it now */ /* Check if we have outstanding requests not responsed to or an active call - if that's the case, wait with destruction */ if (dialog->needdestroy && !dialog->packets && !dialog->owner) { /* We absolutely cannot destroy the rtp struct while a bridge is active or we WILL crash */ if (dialog->rtp && ast_rtp_instance_get_bridged(dialog->rtp)) { ast_debug(2, "Bridge still active. Delaying destruction of SIP dialog '%s' Method: %s\n", dialog->callid, sip_methods[dialog->method].text); sip_pvt_unlock(dialog); return 0; } if (dialog->vrtp && ast_rtp_instance_get_bridged(dialog->vrtp)) { ast_debug(2, "Bridge still active. Delaying destroy of SIP dialog '%s' Method: %s\n", dialog->callid, sip_methods[dialog->method].text); sip_pvt_unlock(dialog); return 0; } sip_pvt_unlock(dialog); /* no, the unlink should handle this: dialog_unref(dialog, "needdestroy: one more refcount decrement to allow dialog to be destroyed"); */ /* the CMP_MATCH will unlink this dialog from the dialog hash table */ dialog_unlink_all(dialog); return 0; /* the unlink_all should unlink this from the table, so.... no need to return a match */ } sip_pvt_unlock(dialog); return 0; } /*! \brief Remove temporary realtime objects from memory (CLI) */ /*! \todo XXXX Propably needs an overhaul after removal of the devices */ static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct sip_peer *peer, *pi; int prunepeer = FALSE; int multi = FALSE; const char *name = NULL; regex_t regexbuf; int havepattern = 0; struct ao2_iterator i; static const char * const choices[] = { "all", "like", NULL }; char *cmplt; if (cmd == CLI_INIT) { e->command = "sip prune realtime [peer|all]"; e->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 (a->pos == 4 && !strcasecmp(a->argv[3], "peer")) { cmplt = ast_cli_complete(a->word, choices, a->n); if (!cmplt) cmplt = complete_sip_peer(a->word, a->n - sizeof(choices), SIP_PAGE2_RTCACHEFRIENDS); return cmplt; } if (a->pos == 5 && !strcasecmp(a->argv[4], "like")) return complete_sip_peer(a->word, a->n, SIP_PAGE2_RTCACHEFRIENDS); return NULL; } switch (a->argc) { case 4: name = a->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 a single name, already set */ break; case 5: /* sip prune realtime {peer|like} name */ name = a->argv[4]; if (!strcasecmp(a->argv[3], "peer")) prunepeer = TRUE; else if (!strcasecmp(a->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; } break; case 6: name = a->argv[5]; multi = TRUE; /* sip prune realtime {peer} like name */ if (strcasecmp(a->argv[4], "like")) return CLI_SHOWUSAGE; if (!strcasecmp(a->argv[3], "peer")) { prunepeer = TRUE; } else return CLI_SHOWUSAGE; break; default: return CLI_SHOWUSAGE; } if (multi && name) { if (regcomp(®exbuf, name, REG_EXTENDED | REG_NOSUB)) { return CLI_SHOWUSAGE; } havepattern = 1; } if (multi) { if (prunepeer) { int pruned = 0; i = ao2_iterator_init(peers, 0); while ((pi = ao2_t_iterator_next(&i, "iterate thru peers table"))) { ao2_lock(pi); if (name && regexec(®exbuf, pi->name, 0, NULL, 0)) { ao2_unlock(pi); sip_unref_peer(pi, "toss iterator peer ptr before continue"); continue; }; if (ast_test_flag(&pi->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { pi->the_mark = 1; pruned++; } ao2_unlock(pi); sip_unref_peer(pi, "toss iterator peer ptr"); } ao2_iterator_destroy(&i); if (pruned) { unlink_marked_peers_from_tables(); ast_cli(a->fd, "%d peers pruned.\n", pruned); } else ast_cli(a->fd, "No peers found to prune.\n"); } } else { if (prunepeer) { struct sip_peer tmp; ast_copy_string(tmp.name, name, sizeof(tmp.name)); if ((peer = ao2_t_find(peers, &tmp, OBJ_POINTER | OBJ_UNLINK, "finding to unlink from peers"))) { if (!ast_sockaddr_isnull(&peer->addr)) { ao2_t_unlink(peers_by_ip, peer, "unlinking peer from peers_by_ip also"); } if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { ast_cli(a->fd, "Peer '%s' is not a Realtime peer, cannot be pruned.\n", name); /* put it back! */ ao2_t_link(peers, peer, "link peer into peer table"); if (!ast_sockaddr_isnull(&peer->addr)) { ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); } } else ast_cli(a->fd, "Peer '%s' pruned.\n", name); sip_unref_peer(peer, "sip_prune_realtime: sip_unref_peer: tossing temp peer ptr"); } else ast_cli(a->fd, "Peer '%s' not found.\n", name); } } if (havepattern) { regfree(®exbuf); } return CLI_SUCCESS; } /*! \brief Print domain mode to cli */ static const char *domain_mode_to_text(const enum domain_mode mode) { switch (mode) { case SIP_DOMAIN_AUTO: return "[Automatic]"; case SIP_DOMAIN_CONFIG: return "[Configured]"; } return ""; } /*! \brief CLI command to list local domains */ static char *sip_show_domains(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct domain *d; #define FORMAT "%-40.40s %-20.20s %-16.16s\n" switch (cmd) { case CLI_INIT: e->command = "sip show domains"; e->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; case CLI_GENERATE: return NULL; } if (AST_LIST_EMPTY(&domain_list)) { ast_cli(a->fd, "SIP Domain support not enabled.\n\n"); return CLI_SUCCESS; } else { ast_cli(a->fd, FORMAT, "Our local SIP domains:", "Context", "Set by"); AST_LIST_LOCK(&domain_list); AST_LIST_TRAVERSE(&domain_list, d, list) ast_cli(a->fd, FORMAT, d->domain, S_OR(d->context, "(default)"), domain_mode_to_text(d->mode)); AST_LIST_UNLOCK(&domain_list); ast_cli(a->fd, "\n"); return CLI_SUCCESS; } } #undef FORMAT /*! \brief Show SIP peers in the manager API */ static int manager_sip_show_peer(struct mansession *s, const struct message *m) { const char *a[4]; const char *peer; peer = astman_get_header(m, "Peer"); if (ast_strlen_zero(peer)) { astman_send_error(s, m, "Peer: missing."); return 0; } a[0] = "sip"; a[1] = "show"; a[2] = "peer"; a[3] = peer; _sip_show_peer(1, -1, s, m, 4, a); astman_append(s, "\r\n" ); return 0; } /*! \brief Show one peer in detail */ static char *sip_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "sip show peer"; e->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; case CLI_GENERATE: return complete_sip_show_peer(a->line, a->word, a->pos, a->n); } return _sip_show_peer(0, a->fd, NULL, NULL, a->argc, (const char **) a->argv); } static void send_manager_peer_status(struct mansession *s, struct sip_peer *peer, const char *idText) { char time[128] = ""; char status[128] = ""; if (peer->maxms) { if (peer->lastms < 0) { snprintf(status, sizeof(status), "PeerStatus: Unreachable\r\n"); } else if (peer->lastms > peer->maxms) { snprintf(status, sizeof(status), "PeerStatus: Lagged\r\n"); snprintf(time, sizeof(time), "Time: %d\r\n", peer->lastms); } else if (peer->lastms) { snprintf(status, sizeof(status), "PeerStatus: Reachable\r\n"); snprintf(time, sizeof(time), "Time: %d\r\n", peer->lastms); } else { snprintf(status, sizeof(status), "PeerStatus: Unknown\r\n"); } } else { snprintf(status, sizeof(status), "PeerStatus: Unmonitored\r\n"); } astman_append(s, "Event: PeerStatus\r\n" "Privilege: System\r\n" "ChannelType: SIP\r\n" "Peer: SIP/%s\r\n" "%s" "%s" "%s" "\r\n", peer->name, status, time, idText); } /*! \brief Show SIP peers in the manager API */ static int manager_sip_peer_status(struct mansession *s, const struct message *m) { const char *id = astman_get_header(m,"ActionID"); const char *peer_name = astman_get_header(m,"Peer"); char idText[256] = ""; struct sip_peer *peer = NULL; if (!ast_strlen_zero(id)) { snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); } if (!ast_strlen_zero(peer_name)) { /* strip SIP/ from the begining of the peer name */ if (strlen(peer_name) >= 4 && !strncasecmp("SIP/", peer_name, 4)) { peer_name += 4; } peer = sip_find_peer(peer_name, NULL, TRUE, FINDPEERS, FALSE, 0); if (!peer) { astman_send_error(s, m, "No such peer"); return 0; } } astman_send_ack(s, m, "Peer status will follow"); if (!peer) { struct ao2_iterator i = ao2_iterator_init(peers, 0); while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table for SIPpeerstatus"))) { ao2_lock(peer); send_manager_peer_status(s, peer, idText); ao2_unlock(peer); sip_unref_peer(peer, "unref peer for SIPpeerstatus"); } ao2_iterator_destroy(&i); } else { ao2_lock(peer); send_manager_peer_status(s, peer, idText); ao2_unlock(peer); sip_unref_peer(peer, "unref peer for SIPpeerstatus"); } astman_append(s, "Event: SIPpeerstatusComplete\r\n" "%s" "\r\n", idText); return 0; } static void publish_qualify_peer_done(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); } /*! \brief Send qualify message to peer from cli or manager. Mostly for debugging. */ static char *_sip_qualify_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[]) { struct sip_peer *peer; int load_realtime; if (argc < 4) return CLI_SHOWUSAGE; load_realtime = (argc == 5 && !strcmp(argv[4], "load")) ? TRUE : FALSE; if ((peer = sip_find_peer(argv[3], NULL, load_realtime, FINDPEERS, FALSE, 0))) { const char *id = astman_get_header(m,"ActionID"); if (type != 0) { astman_send_ack(s, m, "SIP peer found - will qualify"); } sip_poke_peer(peer, 1); publish_qualify_peer_done(id, argv[3]); sip_unref_peer(peer, "qualify: done with peer"); } else if (type == 0) { ast_cli(fd, "Peer '%s' not found\n", argv[3]); } else { astman_send_error(s, m, "Peer not found"); } return CLI_SUCCESS; } /*! \brief Qualify SIP peers in the manager API */ static int manager_sip_qualify_peer(struct mansession *s, const struct message *m) { const char *a[4]; const char *peer; peer = astman_get_header(m, "Peer"); if (ast_strlen_zero(peer)) { astman_send_error(s, m, "Peer: missing."); return 0; } a[0] = "sip"; a[1] = "qualify"; a[2] = "peer"; a[3] = peer; _sip_qualify_peer(1, -1, s, m, 4, a); return 0; } /*! \brief Send an OPTIONS packet to a SIP peer */ static char *sip_qualify_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "sip qualify peer"; e->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; case CLI_GENERATE: return complete_sip_show_peer(a->line, a->word, a->pos, a->n); } return _sip_qualify_peer(0, a->fd, NULL, NULL, a->argc, (const char **) a->argv); } /*! \brief list peer mailboxes to CLI */ static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer) { struct sip_mailbox *mailbox; AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { ast_str_append(mailbox_str, 0, "%s%s", mailbox->id, AST_LIST_NEXT(mailbox, entry) ? "," : ""); } } static struct _map_x_s faxecmodes[] = { { SIP_PAGE2_T38SUPPORT_UDPTL, "None"}, { SIP_PAGE2_T38SUPPORT_UDPTL_FEC, "FEC"}, { SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY, "Redundancy"}, { -1, NULL}, }; static const char *faxec2str(int faxec) { return map_x_s(faxecmodes, faxec, "Unknown"); } /*! \brief Show one peer in detail (main function) */ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct message *m, int argc, const char *argv[]) { char status[30] = ""; char cbuf[256]; struct sip_peer *peer; struct ast_str *codec_buf = ast_str_alloca(64); struct ast_variable *v; int x = 0, load_realtime; int realtimepeers; realtimepeers = ast_check_realtime("sippeers"); if (argc < 4) return CLI_SHOWUSAGE; load_realtime = (argc == 5 && !strcmp(argv[4], "load")) ? TRUE : FALSE; peer = sip_find_peer(argv[3], NULL, load_realtime, FINDPEERS, FALSE, 0); if (s) { /* Manager */ if (peer) { const char *id = astman_get_header(m, "ActionID"); astman_append(s, "Response: Success\r\n"); if (!ast_strlen_zero(id)) astman_append(s, "ActionID: %s\r\n", id); } else { snprintf (cbuf, sizeof(cbuf), "Peer %s not found.", argv[3]); astman_send_error(s, m, cbuf); return CLI_SUCCESS; } } if (peer && type==0 ) { /* Normal listing */ struct ast_str *mailbox_str = ast_str_alloca(512); struct ast_str *path; struct sip_auth_container *credentials; ao2_lock(peer); credentials = peer->auth; if (credentials) { ao2_t_ref(credentials, +1, "Ref peer auth for show"); } ao2_unlock(peer); ast_cli(fd, "\n\n"); ast_cli(fd, " * Name : %s\n", peer->name); ast_cli(fd, " Description : %s\n", peer->description); if (realtimepeers) { /* Realtime is enabled */ ast_cli(fd, " Realtime peer: %s\n", peer->is_realtime ? "Yes, cached" : "No"); } ast_cli(fd, " Secret : %s\n", ast_strlen_zero(peer->secret)?"":""); ast_cli(fd, " MD5Secret : %s\n", ast_strlen_zero(peer->md5secret)?"":""); ast_cli(fd, " Remote Secret: %s\n", ast_strlen_zero(peer->remotesecret)?"":""); if (credentials) { struct sip_auth *auth; AST_LIST_TRAVERSE(&credentials->list, auth, node) { ast_cli(fd, " Realm-auth : 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(credentials, -1, "Unref peer auth for show"); } ast_cli(fd, " Context : %s\n", peer->context); ast_cli(fd, " Record On feature : %s\n", peer->record_on_feature); ast_cli(fd, " Record Off feature : %s\n", peer->record_off_feature); ast_cli(fd, " Subscr.Cont. : %s\n", S_OR(peer->subscribecontext, "") ); ast_cli(fd, " Language : %s\n", peer->language); ast_cli(fd, " Tonezone : %s\n", peer->zone[0] != '\0' ? peer->zone : ""); if (!ast_strlen_zero(peer->accountcode)) ast_cli(fd, " Accountcode : %s\n", peer->accountcode); ast_cli(fd, " AMA flags : %s\n", ast_channel_amaflags2string(peer->amaflags)); ast_cli(fd, " Transfer mode: %s\n", transfermode2str(peer->allowtransfer)); ast_cli(fd, " CallingPres : %s\n", ast_describe_caller_presentation(peer->callingpres)); if (!ast_strlen_zero(peer->fromuser)) ast_cli(fd, " FromUser : %s\n", peer->fromuser); if (!ast_strlen_zero(peer->fromdomain)) ast_cli(fd, " FromDomain : %s Port %d\n", peer->fromdomain, (peer->fromdomainport) ? peer->fromdomainport : STANDARD_SIP_PORT); ast_cli(fd, " Callgroup : "); print_group(fd, peer->callgroup, 0); ast_cli(fd, " Pickupgroup : "); print_group(fd, peer->pickupgroup, 0); ast_cli(fd, " Named Callgr : "); print_named_groups(fd, peer->named_callgroups, 0); ast_cli(fd, " Nam. Pickupgr: "); print_named_groups(fd, peer->named_pickupgroups, 0); peer_mailboxes_to_str(&mailbox_str, peer); ast_cli(fd, " MOH Suggest : %s\n", peer->mohsuggest); ast_cli(fd, " Mailbox : %s\n", ast_str_buffer(mailbox_str)); ast_cli(fd, " VM Extension : %s\n", peer->vmexten); ast_cli(fd, " LastMsgsSent : %d/%d\n", (peer->lastmsgssent & 0x7fff0000) >> 16, peer->lastmsgssent & 0xffff); ast_cli(fd, " Call limit : %d\n", peer->call_limit); ast_cli(fd, " Max forwards : %d\n", peer->maxforwards); if (peer->busy_level) ast_cli(fd, " Busy level : %d\n", peer->busy_level); ast_cli(fd, " Dynamic : %s\n", AST_CLI_YESNO(peer->host_dynamic)); ast_cli(fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "")); ast_cli(fd, " MaxCallBR : %d kbps\n", peer->maxcallbitrate); ast_cli(fd, " Expire : %ld\n", ast_sched_when(sched, peer->expire)); ast_cli(fd, " Insecure : %s\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE))); ast_cli(fd, " Force rport : %s\n", force_rport_string(peer->flags)); ast_cli(fd, " Symmetric RTP: %s\n", comedia_string(peer->flags)); ast_cli(fd, " ACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->acl) == 0)); ast_cli(fd, " DirectMedACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->directmediaacl) == 0)); ast_cli(fd, " T.38 support : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(fd, " T.38 EC mode : %s\n", faxec2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(fd, " T.38 MaxDtgrm: %u\n", peer->t38_maxdatagram); ast_cli(fd, " DirectMedia : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA))); ast_cli(fd, " PromiscRedir : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR))); ast_cli(fd, " User=Phone : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USEREQPHONE))); ast_cli(fd, " Video Support: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT) || ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT_ALWAYS))); ast_cli(fd, " Text Support : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT))); ast_cli(fd, " Ign SDP ver : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_IGNORESDPVERSION))); ast_cli(fd, " Trust RPID : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_TRUSTRPID))); ast_cli(fd, " Send RPID : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_SENDRPID))); ast_cli(fd, " Path support : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[0], SIP_USEPATH))); if ((path = sip_route_list(&peer->path, 1, 0))) { ast_cli(fd, " Path : %s\n", ast_str_buffer(path)); ast_free(path); } ast_cli(fd, " TrustIDOutbnd: %s\n", trust_id_outbound2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND))); ast_cli(fd, " Subscriptions: %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE))); ast_cli(fd, " Overlap dial : %s\n", allowoverlap2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWOVERLAP))); if (peer->outboundproxy) ast_cli(fd, " Outb. proxy : %s %s\n", ast_strlen_zero(peer->outboundproxy->name) ? "" : peer->outboundproxy->name, peer->outboundproxy->force ? "(forced)" : ""); /* - is enumerated */ ast_cli(fd, " DTMFmode : %s\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF))); ast_cli(fd, " Timer T1 : %d\n", peer->timer_t1); ast_cli(fd, " Timer B : %d\n", peer->timer_b); ast_cli(fd, " ToHost : %s\n", peer->tohost); ast_cli(fd, " Addr->IP : %s\n", ast_sockaddr_stringify(&peer->addr)); ast_cli(fd, " Defaddr->IP : %s\n", ast_sockaddr_stringify(&peer->defaddr)); ast_cli(fd, " Prim.Transp. : %s\n", sip_get_transport(peer->socket.type)); ast_cli(fd, " Allowed.Trsp : %s\n", get_transport_list(peer->transports)); if (!ast_strlen_zero(sip_cfg.regcontext)) ast_cli(fd, " Reg. exten : %s\n", peer->regexten); ast_cli(fd, " Def. Username: %s\n", peer->username); ast_cli(fd, " SIP Options : "); if (peer->sipoptions) { int lastoption = -1; for (x = 0 ; x < ARRAY_LEN(sip_options); x++) { if (sip_options[x].id != lastoption) { if (peer->sipoptions & sip_options[x].id) ast_cli(fd, "%s ", sip_options[x].text); lastoption = x; } } } else ast_cli(fd, "(none)"); ast_cli(fd, "\n"); ast_cli(fd, " Codecs : %s\n", ast_format_cap_get_names(peer->caps, &codec_buf)); ast_cli(fd, " Auto-Framing : %s\n", AST_CLI_YESNO(peer->autoframing)); ast_cli(fd, " Status : "); peer_status(peer, status, sizeof(status)); ast_cli(fd, "%s\n", status); ast_cli(fd, " Useragent : %s\n", peer->useragent); ast_cli(fd, " Reg. Contact : %s\n", peer->fullcontact); ast_cli(fd, " Qualify Freq : %d ms\n", peer->qualifyfreq); ast_cli(fd, " Keepalive : %d ms\n", peer->keepalive * 1000); if (peer->chanvars) { ast_cli(fd, " Variables :\n"); for (v = peer->chanvars ; v ; v = v->next) ast_cli(fd, " %s = %s\n", v->name, v->value); } ast_cli(fd, " Sess-Timers : %s\n", stmode2str(peer->stimer.st_mode_oper)); ast_cli(fd, " Sess-Refresh : %s\n", strefresherparam2str(peer->stimer.st_ref)); ast_cli(fd, " Sess-Expires : %d secs\n", peer->stimer.st_max_se); ast_cli(fd, " Min-Sess : %d secs\n", peer->stimer.st_min_se); ast_cli(fd, " RTP Engine : %s\n", peer->engine); ast_cli(fd, " Parkinglot : %s\n", peer->parkinglot); ast_cli(fd, " Use Reason : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON))); ast_cli(fd, " Encryption : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP))); ast_cli(fd, "\n"); peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer ptr"); } else if (peer && type == 1) { /* manager listing */ char buffer[256]; struct ast_str *tmp_str = ast_str_alloca(512); astman_append(s, "Channeltype: SIP\r\n"); astman_append(s, "ObjectName: %s\r\n", peer->name); astman_append(s, "ChanObjectType: peer\r\n"); astman_append(s, "SecretExist: %s\r\n", ast_strlen_zero(peer->secret)?"N":"Y"); astman_append(s, "RemoteSecretExist: %s\r\n", ast_strlen_zero(peer->remotesecret)?"N":"Y"); astman_append(s, "MD5SecretExist: %s\r\n", ast_strlen_zero(peer->md5secret)?"N":"Y"); astman_append(s, "Context: %s\r\n", peer->context); if (!ast_strlen_zero(peer->subscribecontext)) { astman_append(s, "SubscribeContext: %s\r\n", peer->subscribecontext); } astman_append(s, "Language: %s\r\n", peer->language); astman_append(s, "ToneZone: %s\r\n", peer->zone[0] != '\0' ? peer->zone : ""); if (!ast_strlen_zero(peer->accountcode)) astman_append(s, "Accountcode: %s\r\n", peer->accountcode); astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(peer->amaflags)); astman_append(s, "CID-CallingPres: %s\r\n", ast_describe_caller_presentation(peer->callingpres)); if (!ast_strlen_zero(peer->fromuser)) astman_append(s, "SIP-FromUser: %s\r\n", peer->fromuser); if (!ast_strlen_zero(peer->fromdomain)) astman_append(s, "SIP-FromDomain: %s\r\nSip-FromDomain-Port: %d\r\n", peer->fromdomain, (peer->fromdomainport) ? peer->fromdomainport : STANDARD_SIP_PORT); astman_append(s, "Callgroup: "); astman_append(s, "%s\r\n", ast_print_group(buffer, sizeof(buffer), peer->callgroup)); astman_append(s, "Pickupgroup: "); astman_append(s, "%s\r\n", ast_print_group(buffer, sizeof(buffer), peer->pickupgroup)); astman_append(s, "Named Callgroup: "); astman_append(s, "%s\r\n", ast_print_namedgroups(&tmp_str, peer->named_callgroups)); ast_str_reset(tmp_str); astman_append(s, "Named Pickupgroup: "); astman_append(s, "%s\r\n", ast_print_namedgroups(&tmp_str, peer->named_pickupgroups)); ast_str_reset(tmp_str); astman_append(s, "MOHSuggest: %s\r\n", peer->mohsuggest); peer_mailboxes_to_str(&tmp_str, peer); astman_append(s, "VoiceMailbox: %s\r\n", ast_str_buffer(tmp_str)); astman_append(s, "TransferMode: %s\r\n", transfermode2str(peer->allowtransfer)); astman_append(s, "LastMsgsSent: %d\r\n", peer->lastmsgssent); astman_append(s, "Maxforwards: %d\r\n", peer->maxforwards); astman_append(s, "Call-limit: %d\r\n", peer->call_limit); astman_append(s, "Busy-level: %d\r\n", peer->busy_level); astman_append(s, "MaxCallBR: %d kbps\r\n", peer->maxcallbitrate); astman_append(s, "Dynamic: %s\r\n", peer->host_dynamic?"Y":"N"); astman_append(s, "Callerid: %s\r\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "")); astman_append(s, "RegExpire: %ld seconds\r\n", ast_sched_when(sched, peer->expire)); astman_append(s, "SIP-AuthInsecure: %s\r\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE))); astman_append(s, "SIP-Forcerport: %s\r\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT) ? (ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? "A" : "a") : (ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? "Y" : "N")); astman_append(s, "SIP-Comedia: %s\r\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA) ? (ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "A" : "a") : (ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "Y" : "N")); astman_append(s, "ACL: %s\r\n", (ast_acl_list_is_empty(peer->acl) ? "N" : "Y")); astman_append(s, "SIP-CanReinvite: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA)?"Y":"N")); astman_append(s, "SIP-DirectMedia: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA)?"Y":"N")); astman_append(s, "SIP-PromiscRedir: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR)?"Y":"N")); astman_append(s, "SIP-UserPhone: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_USEREQPHONE)?"Y":"N")); astman_append(s, "SIP-VideoSupport: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT)?"Y":"N")); astman_append(s, "SIP-TextSupport: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT)?"Y":"N")); astman_append(s, "SIP-T.38Support: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT)?"Y":"N")); astman_append(s, "SIP-T.38EC: %s\r\n", faxec2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT))); astman_append(s, "SIP-T.38MaxDtgrm: %u\r\n", peer->t38_maxdatagram); astman_append(s, "SIP-Sess-Timers: %s\r\n", stmode2str(peer->stimer.st_mode_oper)); astman_append(s, "SIP-Sess-Refresh: %s\r\n", strefresherparam2str(peer->stimer.st_ref)); astman_append(s, "SIP-Sess-Expires: %d\r\n", peer->stimer.st_max_se); astman_append(s, "SIP-Sess-Min: %d\r\n", peer->stimer.st_min_se); astman_append(s, "SIP-RTP-Engine: %s\r\n", peer->engine); astman_append(s, "SIP-Encryption: %s\r\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP) ? "Y" : "N"); /* - is enumerated */ astman_append(s, "SIP-DTMFmode: %s\r\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF))); astman_append(s, "ToHost: %s\r\n", peer->tohost); astman_append(s, "Address-IP: %s\r\nAddress-Port: %d\r\n", ast_sockaddr_stringify_addr(&peer->addr), ast_sockaddr_port(&peer->addr)); astman_append(s, "Default-addr-IP: %s\r\nDefault-addr-port: %d\r\n", ast_sockaddr_stringify_addr(&peer->defaddr), ast_sockaddr_port(&peer->defaddr)); astman_append(s, "Default-Username: %s\r\n", peer->username); if (!ast_strlen_zero(sip_cfg.regcontext)) astman_append(s, "RegExtension: %s\r\n", peer->regexten); astman_append(s, "Codecs: %s\r\n", ast_format_cap_get_names(peer->caps, &codec_buf)); astman_append(s, "Status: "); peer_status(peer, status, sizeof(status)); astman_append(s, "%s\r\n", status); astman_append(s, "SIP-Useragent: %s\r\n", peer->useragent); astman_append(s, "Reg-Contact: %s\r\n", peer->fullcontact); astman_append(s, "QualifyFreq: %d ms\r\n", peer->qualifyfreq); astman_append(s, "Parkinglot: %s\r\n", peer->parkinglot); if (peer->chanvars) { for (v = peer->chanvars ; v ; v = v->next) { astman_append(s, "ChanVariable: %s=%s\r\n", v->name, v->value); } } astman_append(s, "SIP-Use-Reason-Header: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON)) ? "Y" : "N"); astman_append(s, "Description: %s\r\n", peer->description); peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer"); } else { ast_cli(fd, "Peer %s not found.\n", argv[3]); ast_cli(fd, "\n"); } return CLI_SUCCESS; } /*! \brief Do completion on user name */ static char *complete_sip_user(const char *word, int state) { char *result = NULL; int wordlen = strlen(word); int which = 0; struct ao2_iterator user_iter; struct sip_peer *user; user_iter = ao2_iterator_init(peers, 0); while ((user = ao2_t_iterator_next(&user_iter, "iterate thru peers table"))) { ao2_lock(user); if (!(user->type & SIP_TYPE_USER)) { ao2_unlock(user); sip_unref_peer(user, "complete sip user"); continue; } /* locking of the object is not required because only the name and flags are being compared */ if (!strncasecmp(word, user->name, wordlen) && ++which > state) { result = ast_strdup(user->name); } ao2_unlock(user); sip_unref_peer(user, "complete sip user"); if (result) { break; } } ao2_iterator_destroy(&user_iter); return result; } /*! \brief Support routine for 'sip show user' CLI */ static char *complete_sip_show_user(const char *line, const char *word, int pos, int state) { if (pos == 3) return complete_sip_user(word, state); return NULL; } /*! \brief Show one user in detail */ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char cbuf[256]; struct sip_peer *user; struct ast_variable *v; int load_realtime; switch (cmd) { case CLI_INIT: e->command = "sip show user"; e->usage = "Usage: sip show user [load]\n" " Shows all details on one SIP user and the current status.\n" " Option \"load\" forces lookup of peer in realtime storage.\n"; return NULL; case CLI_GENERATE: return complete_sip_show_user(a->line, a->word, a->pos, a->n); } if (a->argc < 4) return CLI_SHOWUSAGE; /* Load from realtime storage? */ load_realtime = (a->argc == 5 && !strcmp(a->argv[4], "load")) ? TRUE : FALSE; if ((user = sip_find_peer(a->argv[3], NULL, load_realtime, FINDUSERS, FALSE, 0))) { ao2_lock(user); ast_cli(a->fd, "\n\n"); ast_cli(a->fd, " * Name : %s\n", user->name); ast_cli(a->fd, " Secret : %s\n", ast_strlen_zero(user->secret)?"":""); ast_cli(a->fd, " MD5Secret : %s\n", ast_strlen_zero(user->md5secret)?"":""); ast_cli(a->fd, " Context : %s\n", user->context); ast_cli(a->fd, " Language : %s\n", user->language); if (!ast_strlen_zero(user->accountcode)) ast_cli(a->fd, " Accountcode : %s\n", user->accountcode); ast_cli(a->fd, " AMA flags : %s\n", ast_channel_amaflags2string(user->amaflags)); ast_cli(a->fd, " Tonezone : %s\n", user->zone[0] != '\0' ? user->zone : ""); ast_cli(a->fd, " Transfer mode: %s\n", transfermode2str(user->allowtransfer)); ast_cli(a->fd, " MaxCallBR : %d kbps\n", user->maxcallbitrate); ast_cli(a->fd, " CallingPres : %s\n", ast_describe_caller_presentation(user->callingpres)); ast_cli(a->fd, " Call limit : %d\n", user->call_limit); ast_cli(a->fd, " Callgroup : "); print_group(a->fd, user->callgroup, 0); ast_cli(a->fd, " Pickupgroup : "); print_group(a->fd, user->pickupgroup, 0); ast_cli(a->fd, " Named Callgr : "); print_named_groups(a->fd, user->named_callgroups, 0); ast_cli(a->fd, " Nam. Pickupgr: "); print_named_groups(a->fd, user->named_pickupgroups, 0); ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "")); ast_cli(a->fd, " ACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(user->acl) == 0)); ast_cli(a->fd, " Sess-Timers : %s\n", stmode2str(user->stimer.st_mode_oper)); ast_cli(a->fd, " Sess-Refresh : %s\n", strefresherparam2str(user->stimer.st_ref)); ast_cli(a->fd, " Sess-Expires : %d secs\n", user->stimer.st_max_se); ast_cli(a->fd, " Sess-Min-SE : %d secs\n", user->stimer.st_min_se); ast_cli(a->fd, " RTP Engine : %s\n", user->engine); ast_cli(a->fd, " Auto-Framing: %s \n", AST_CLI_YESNO(user->autoframing)); if (user->chanvars) { ast_cli(a->fd, " Variables :\n"); for (v = user->chanvars ; v ; v = v->next) ast_cli(a->fd, " %s = %s\n", v->name, v->value); } ast_cli(a->fd, "\n"); ao2_unlock(user); sip_unref_peer(user, "sip show user"); } else { ast_cli(a->fd, "User %s not found.\n", a->argv[3]); ast_cli(a->fd, "\n"); } return CLI_SUCCESS; } static char *sip_show_sched(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_str *cbuf; struct ast_cb_names cbnames = {9, { "retrans_pkt", "__sip_autodestruct", "expire_register", "auto_congest", "sip_reg_timeout", "sip_poke_peer_s", "sip_poke_noanswer", "sip_reregister", "sip_reinvite_retry"}, { retrans_pkt, __sip_autodestruct, expire_register, auto_congest, sip_reg_timeout, sip_poke_peer_s, sip_poke_noanswer, sip_reregister, sip_reinvite_retry}}; switch (cmd) { case CLI_INIT: e->command = "sip show sched"; e->usage = "Usage: sip show sched\n" " Shows stats on what's in the sched queue at the moment\n"; return NULL; case CLI_GENERATE: return NULL; } cbuf = ast_str_alloca(2048); ast_cli(a->fd, "\n"); ast_sched_report(sched, &cbuf, &cbnames); ast_cli(a->fd, "%s", ast_str_buffer(cbuf)); return CLI_SUCCESS; } /*! \brief Show SIP Registry (registrations with other SIP proxies */ static char *sip_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT2 "%-39.39s %-6.6s %-12.12s %8.8s %-20.20s %-25.25s\n" #define FORMAT "%-39.39s %-6.6s %-12.12s %8d %-20.20s %-25.25s\n" char host[80]; char user[80]; char tmpdat[256]; struct ast_tm tm; int counter = 0; struct ao2_iterator iter; struct sip_registry *iterator; switch (cmd) { case CLI_INIT: e->command = "sip show registry"; e->usage = "Usage: sip show registry\n" " Lists all registration requests and status.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, FORMAT2, "Host", "dnsmgr", "Username", "Refresh", "State", "Reg.Time"); iter = ao2_iterator_init(registry_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "sip_show_registry iter"))) { ao2_lock(iterator); snprintf(host, sizeof(host), "%s:%d", iterator->hostname, iterator->portno ? iterator->portno : STANDARD_SIP_PORT); snprintf(user, sizeof(user), "%s", iterator->username); if (!ast_strlen_zero(iterator->regdomain)) { snprintf(tmpdat, sizeof(tmpdat), "%s", user); snprintf(user, sizeof(user), "%s@%s", tmpdat, iterator->regdomain);} if (iterator->regdomainport) { snprintf(tmpdat, sizeof(tmpdat), "%s", user); snprintf(user, sizeof(user), "%s:%d", tmpdat, iterator->regdomainport);} if (iterator->regtime.tv_sec) { ast_localtime(&iterator->regtime, &tm, NULL); ast_strftime(tmpdat, sizeof(tmpdat), "%a, %d %b %Y %T", &tm); } else tmpdat[0] = '\0'; ast_cli(a->fd, FORMAT, host, (iterator->dnsmgr) ? "Y" : "N", user, iterator->refresh, regstate2str(iterator->regstate), tmpdat); ao2_unlock(iterator); ao2_t_ref(iterator, -1, "sip_show_registry iter"); counter++; } ao2_iterator_destroy(&iter); ast_cli(a->fd, "%d SIP registrations.\n", counter); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } /*! \brief 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. */ static char *sip_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct sip_peer *peer; int load_realtime = 0; switch (cmd) { case CLI_INIT: e->command = "sip unregister"; e->usage = "Usage: sip unregister \n" " Unregister (force expiration) a SIP peer from the registry\n"; return NULL; case CLI_GENERATE: return complete_sip_unregister(a->line, a->word, a->pos, a->n); } if (a->argc != 3) return CLI_SHOWUSAGE; if ((peer = sip_find_peer(a->argv[2], NULL, load_realtime, FINDPEERS, TRUE, 0))) { if (peer->expire > 0) { AST_SCHED_DEL_UNREF(sched, peer->expire, sip_unref_peer(peer, "remove register expire ref")); expire_register(sip_ref_peer(peer, "ref for expire_register")); ast_cli(a->fd, "Unregistered peer \'%s\'\n\n", a->argv[2]); } else { ast_cli(a->fd, "Peer %s not registered\n", a->argv[2]); } sip_unref_peer(peer, "sip_unregister: sip_unref_peer via sip_unregister: done with peer from sip_find_peer call"); } else { ast_cli(a->fd, "Peer unknown: \'%s\'. Not unregistered.\n", a->argv[2]); } return CLI_SUCCESS; } /*! \brief Callback for show_chanstats */ static int show_chanstats_cb(void *__cur, void *__arg, int flags) { #define FORMAT2 "%-15.15s %-11.11s %-8.8s %-10.10s %-10.10s ( %%) %-6.6s %-10.10s %-10.10s ( %%) %-6.6s\n" #define FORMAT "%-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" struct sip_pvt *cur = __cur; struct ast_rtp_instance_stats stats; char durbuf[10]; struct ast_channel *c; struct __show_chan_arg *arg = __arg; int fd = arg->fd; sip_pvt_lock(cur); c = cur->owner; if (cur->subscribed != NONE) { /* Subscriptions */ sip_pvt_unlock(cur); return 0; /* don't care, we scan all channels */ } if (!cur->rtp) { if (sipdebug) { ast_cli(fd, "%-15.15s %-11.11s (inv state: %s) -- %s\n", ast_sockaddr_stringify_addr(&cur->sa), cur->callid, invitestate2string[cur->invitestate].desc, "-- No RTP active"); } sip_pvt_unlock(cur); return 0; /* don't care, we scan all channels */ } if (ast_rtp_instance_get_stats(cur->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) { sip_pvt_unlock(cur); ast_log(LOG_WARNING, "Could not get RTP stats.\n"); return 0; } if (c) { ast_format_duration_hh_mm_ss(ast_channel_get_duration(c), durbuf, sizeof(durbuf)); } else { durbuf[0] = '\0'; } ast_cli(fd, FORMAT, ast_sockaddr_stringify_addr(&cur->sa), cur->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 ); arg->numchans++; sip_pvt_unlock(cur); return 0; /* don't care, we scan all channels */ } /*! \brief SIP show channelstats CLI (main function) */ static char *sip_show_channelstats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct __show_chan_arg arg = { .fd = a->fd, .numchans = 0 }; switch (cmd) { case CLI_INIT: e->command = "sip show channelstats"; e->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; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, FORMAT2, "Peer", "Call ID", "Duration", "Recv: Pack", "Lost", "Jitter", "Send: Pack", "Lost", "Jitter"); /* iterate on the container and invoke the callback on each item */ ao2_t_callback(dialogs, OBJ_NODATA, show_chanstats_cb, &arg, "callback to sip show chanstats"); ast_cli(a->fd, "%d active SIP channel%s\n", arg.numchans, (arg.numchans != 1) ? "s" : ""); return CLI_SUCCESS; } #undef FORMAT #undef FORMAT2 /*! \brief List global settings for the SIP channel */ static char *sip_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int realtimepeers; int realtimeregs; struct ast_str *codec_buf = ast_str_alloca(64); const char *msg; /* temporary msg pointer */ struct sip_auth_container *credentials; switch (cmd) { case CLI_INIT: e->command = "sip show settings"; e->usage = "Usage: sip show settings\n" " Provides detailed list of the configuration of the SIP channel.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; realtimepeers = ast_check_realtime("sippeers"); realtimeregs = ast_check_realtime("sipregs"); ast_mutex_lock(&authl_lock); credentials = authl; if (credentials) { ao2_t_ref(credentials, +1, "Ref global auth for show"); } ast_mutex_unlock(&authl_lock); ast_cli(a->fd, "\n\nGlobal Settings:\n"); ast_cli(a->fd, "----------------\n"); ast_cli(a->fd, " UDP Bindaddress: %s\n", ast_sockaddr_stringify(&bindaddr)); if (ast_sockaddr_is_ipv6(&bindaddr) && ast_sockaddr_is_any(&bindaddr)) { ast_cli(a->fd, " ** Additional Info:\n"); ast_cli(a->fd, " [::] may include IPv4 in addition to IPv6, if such a feature is enabled in the OS.\n"); } ast_cli(a->fd, " TCP SIP Bindaddress: %s\n", sip_cfg.tcp_enabled != FALSE ? ast_sockaddr_stringify(&sip_tcp_desc.local_address) : "Disabled"); ast_cli(a->fd, " TLS SIP Bindaddress: %s\n", default_tls_cfg.enabled != FALSE ? ast_sockaddr_stringify(&sip_tls_desc.local_address) : "Disabled"); ast_cli(a->fd, " Videosupport: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_VIDEOSUPPORT))); ast_cli(a->fd, " Textsupport: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_TEXTSUPPORT))); ast_cli(a->fd, " Ignore SDP sess. ver.: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_IGNORESDPVERSION))); ast_cli(a->fd, " AutoCreate Peer: %s\n", autocreatepeer2str(sip_cfg.autocreatepeer)); ast_cli(a->fd, " Match Auth Username: %s\n", AST_CLI_YESNO(global_match_auth_username)); ast_cli(a->fd, " Allow unknown access: %s\n", AST_CLI_YESNO(sip_cfg.allowguest)); ast_cli(a->fd, " Allow subscriptions: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWSUBSCRIBE))); ast_cli(a->fd, " Allow overlap dialing: %s\n", allowoverlap2str(ast_test_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP))); ast_cli(a->fd, " Allow promisc. redir: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_PROMISCREDIR))); ast_cli(a->fd, " Enable call counters: %s\n", AST_CLI_YESNO(global_callcounter)); ast_cli(a->fd, " SIP domain support: %s\n", AST_CLI_YESNO(!AST_LIST_EMPTY(&domain_list))); ast_cli(a->fd, " Path support : %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_USEPATH))); ast_cli(a->fd, " Realm. auth: %s\n", AST_CLI_YESNO(credentials != NULL)); if (credentials) { struct sip_auth *auth; AST_LIST_TRAVERSE(&credentials->list, auth, node) { ast_cli(a->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(credentials, -1, "Unref global auth for show"); } ast_cli(a->fd, " Our auth realm %s\n", sip_cfg.realm); ast_cli(a->fd, " Use domains as realms: %s\n", AST_CLI_YESNO(sip_cfg.domainsasrealm)); ast_cli(a->fd, " Call to non-local dom.: %s\n", AST_CLI_YESNO(sip_cfg.allow_external_domains)); ast_cli(a->fd, " URI user is phone no: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_USEREQPHONE))); ast_cli(a->fd, " Always auth rejects: %s\n", AST_CLI_YESNO(sip_cfg.alwaysauthreject)); ast_cli(a->fd, " Direct RTP setup: %s\n", AST_CLI_YESNO(sip_cfg.directrtpsetup)); ast_cli(a->fd, " User Agent: %s\n", global_useragent); ast_cli(a->fd, " SDP Session Name: %s\n", ast_strlen_zero(global_sdpsession) ? "-" : global_sdpsession); ast_cli(a->fd, " SDP Owner Name: %s\n", ast_strlen_zero(global_sdpowner) ? "-" : global_sdpowner); ast_cli(a->fd, " Reg. context: %s\n", S_OR(sip_cfg.regcontext, "(not set)")); ast_cli(a->fd, " Regexten on Qualify: %s\n", AST_CLI_YESNO(sip_cfg.regextenonqualify)); ast_cli(a->fd, " Trust RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_TRUSTRPID))); ast_cli(a->fd, " Send RPID: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_SENDRPID))); ast_cli(a->fd, " Legacy userfield parse: %s\n", AST_CLI_YESNO(sip_cfg.legacy_useroption_parsing)); ast_cli(a->fd, " Send Diversion: %s\n", AST_CLI_YESNO(sip_cfg.send_diversion)); ast_cli(a->fd, " Caller ID: %s\n", default_callerid); if ((default_fromdomainport) && (default_fromdomainport != STANDARD_SIP_PORT)) { ast_cli(a->fd, " From: Domain: %s:%d\n", default_fromdomain, default_fromdomainport); } else { ast_cli(a->fd, " From: Domain: %s\n", default_fromdomain); } ast_cli(a->fd, " Record SIP history: %s\n", AST_CLI_ONOFF(recordhistory)); ast_cli(a->fd, " Auth. Failure Events: %s\n", AST_CLI_ONOFF(global_authfailureevents)); ast_cli(a->fd, " T.38 support: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(a->fd, " T.38 EC mode: %s\n", faxec2str(ast_test_flag(&global_flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(a->fd, " T.38 MaxDtgrm: %u\n", global_t38_maxdatagram); if (!realtimepeers && !realtimeregs) ast_cli(a->fd, " SIP realtime: Disabled\n" ); else ast_cli(a->fd, " SIP realtime: Enabled\n" ); ast_cli(a->fd, " Qualify Freq : %d ms\n", global_qualifyfreq); ast_cli(a->fd, " Q.850 Reason header: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_Q850_REASON))); ast_cli(a->fd, " Store SIP_CAUSE: %s\n", AST_CLI_YESNO(global_store_sip_cause)); ast_cli(a->fd, "\nNetwork QoS Settings:\n"); ast_cli(a->fd, "---------------------------\n"); ast_cli(a->fd, " IP ToS SIP: %s\n", ast_tos2str(global_tos_sip)); ast_cli(a->fd, " IP ToS RTP audio: %s\n", ast_tos2str(global_tos_audio)); ast_cli(a->fd, " IP ToS RTP video: %s\n", ast_tos2str(global_tos_video)); ast_cli(a->fd, " IP ToS RTP text: %s\n", ast_tos2str(global_tos_text)); ast_cli(a->fd, " 802.1p CoS SIP: %u\n", global_cos_sip); ast_cli(a->fd, " 802.1p CoS RTP audio: %u\n", global_cos_audio); ast_cli(a->fd, " 802.1p CoS RTP video: %u\n", global_cos_video); ast_cli(a->fd, " 802.1p CoS RTP text: %u\n", global_cos_text); ast_cli(a->fd, " Jitterbuffer enabled: %s\n", AST_CLI_YESNO(ast_test_flag(&global_jbconf, AST_JB_ENABLED))); if (ast_test_flag(&global_jbconf, AST_JB_ENABLED)) { ast_cli(a->fd, " Jitterbuffer forced: %s\n", AST_CLI_YESNO(ast_test_flag(&global_jbconf, AST_JB_FORCED))); ast_cli(a->fd, " Jitterbuffer max size: %ld\n", global_jbconf.max_size); ast_cli(a->fd, " Jitterbuffer resync: %ld\n", global_jbconf.resync_threshold); ast_cli(a->fd, " Jitterbuffer impl: %s\n", global_jbconf.impl); if (!strcasecmp(global_jbconf.impl, "adaptive")) { ast_cli(a->fd, " Jitterbuffer tgt extra: %ld\n", global_jbconf.target_extra); } ast_cli(a->fd, " Jitterbuffer log: %s\n", AST_CLI_YESNO(ast_test_flag(&global_jbconf, AST_JB_LOG))); } ast_cli(a->fd, "\nNetwork Settings:\n"); ast_cli(a->fd, "---------------------------\n"); /* determine if/how SIP address can be remapped */ if (localaddr == NULL) msg = "Disabled, no localnet list"; else if (ast_sockaddr_isnull(&externaddr)) msg = "Disabled"; else if (!ast_strlen_zero(externhost)) msg = "Enabled using externhost"; else msg = "Enabled using externaddr"; ast_cli(a->fd, " SIP address remapping: %s\n", msg); ast_cli(a->fd, " Externhost: %s\n", S_OR(externhost, "")); ast_cli(a->fd, " Externaddr: %s\n", ast_sockaddr_stringify(&externaddr)); ast_cli(a->fd, " Externrefresh: %d\n", externrefresh); { struct ast_ha *d; const char *prefix = "Localnet:"; for (d = localaddr; d ; prefix = "", d = d->next) { const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&d->addr)); const char *mask = ast_strdupa(ast_sockaddr_stringify_addr(&d->netmask)); ast_cli(a->fd, " %-24s%s/%s\n", prefix, addr, mask); } } ast_cli(a->fd, "\nGlobal Signalling Settings:\n"); ast_cli(a->fd, "---------------------------\n"); ast_cli(a->fd, " Codecs: %s\n", ast_format_cap_get_names(sip_cfg.caps, &codec_buf)); ast_cli(a->fd, " Relax DTMF: %s\n", AST_CLI_YESNO(global_relaxdtmf)); ast_cli(a->fd, " RFC2833 Compensation: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_RFC2833_COMPENSATE))); ast_cli(a->fd, " Symmetric RTP: %s\n", comedia_string(global_flags)); ast_cli(a->fd, " Compact SIP headers: %s\n", AST_CLI_YESNO(sip_cfg.compactheaders)); ast_cli(a->fd, " RTP Keepalive: %d %s\n", global_rtpkeepalive, global_rtpkeepalive ? "" : "(Disabled)" ); ast_cli(a->fd, " RTP Timeout: %d %s\n", global_rtptimeout, global_rtptimeout ? "" : "(Disabled)" ); ast_cli(a->fd, " RTP Hold Timeout: %d %s\n", global_rtpholdtimeout, global_rtpholdtimeout ? "" : "(Disabled)"); ast_cli(a->fd, " MWI NOTIFY mime type: %s\n", default_notifymime); ast_cli(a->fd, " DNS SRV lookup: %s\n", AST_CLI_YESNO(sip_cfg.srvlookup)); ast_cli(a->fd, " Pedantic SIP support: %s\n", AST_CLI_YESNO(sip_cfg.pedanticsipchecking)); ast_cli(a->fd, " Reg. min duration %d secs\n", min_expiry); ast_cli(a->fd, " Reg. max duration: %d secs\n", max_expiry); ast_cli(a->fd, " Reg. default duration: %d secs\n", default_expiry); ast_cli(a->fd, " Sub. min duration %d secs\n", min_subexpiry); ast_cli(a->fd, " Sub. max duration: %d secs\n", max_subexpiry); ast_cli(a->fd, " Outbound reg. timeout: %d secs\n", global_reg_timeout); ast_cli(a->fd, " Outbound reg. attempts: %d\n", global_regattempts_max); ast_cli(a->fd, " Outbound reg. retry 403:%d\n", global_reg_retry_403); ast_cli(a->fd, " Notify ringing state: %s\n", AST_CLI_YESNO(sip_cfg.notifyringing)); if (sip_cfg.notifyringing) { ast_cli(a->fd, " Include CID: %s%s\n", AST_CLI_YESNO(sip_cfg.notifycid), sip_cfg.notifycid == IGNORE_CONTEXT ? " (Ignoring context)" : ""); } ast_cli(a->fd, " Notify hold state: %s\n", AST_CLI_YESNO(sip_cfg.notifyhold)); ast_cli(a->fd, " SIP Transfer mode: %s\n", transfermode2str(sip_cfg.allowtransfer)); ast_cli(a->fd, " Max Call Bitrate: %d kbps\n", default_maxcallbitrate); ast_cli(a->fd, " Auto-Framing: %s\n", AST_CLI_YESNO(global_autoframing)); ast_cli(a->fd, " Outb. proxy: %s %s\n", ast_strlen_zero(sip_cfg.outboundproxy.name) ? "" : sip_cfg.outboundproxy.name, sip_cfg.outboundproxy.force ? "(forced)" : ""); ast_cli(a->fd, " Session Timers: %s\n", stmode2str(global_st_mode)); ast_cli(a->fd, " Session Refresher: %s\n", strefresherparam2str(global_st_refresher)); ast_cli(a->fd, " Session Expires: %d secs\n", global_max_se); ast_cli(a->fd, " Session Min-SE: %d secs\n", global_min_se); ast_cli(a->fd, " Timer T1: %d\n", global_t1); ast_cli(a->fd, " Timer T1 minimum: %d\n", global_t1min); ast_cli(a->fd, " Timer B: %d\n", global_timer_b); ast_cli(a->fd, " No premature media: %s\n", AST_CLI_YESNO(global_prematuremediafilter)); ast_cli(a->fd, " Max forwards: %d\n", sip_cfg.default_max_forwards); ast_cli(a->fd, "\nDefault Settings:\n"); ast_cli(a->fd, "-----------------\n"); ast_cli(a->fd, " Allowed transports: %s\n", get_transport_list(default_transports)); ast_cli(a->fd, " Outbound transport: %s\n", sip_get_transport(default_primary_transport)); ast_cli(a->fd, " Context: %s\n", sip_cfg.default_context); ast_cli(a->fd, " Record on feature: %s\n", sip_cfg.default_record_on_feature); ast_cli(a->fd, " Record off feature: %s\n", sip_cfg.default_record_off_feature); ast_cli(a->fd, " Force rport: %s\n", force_rport_string(global_flags)); ast_cli(a->fd, " DTMF: %s\n", dtmfmode2str(ast_test_flag(&global_flags[0], SIP_DTMF))); ast_cli(a->fd, " Qualify: %d\n", default_qualify); ast_cli(a->fd, " Keepalive: %d\n", default_keepalive); ast_cli(a->fd, " Use ClientCode: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_USECLIENTCODE))); ast_cli(a->fd, " Progress inband: %s\n", (ast_test_flag(&global_flags[0], SIP_PROG_INBAND) == SIP_PROG_INBAND_NEVER) ? "Never" : (AST_CLI_YESNO(ast_test_flag(&global_flags[0], SIP_PROG_INBAND) != SIP_PROG_INBAND_NO))); ast_cli(a->fd, " Language: %s\n", default_language); ast_cli(a->fd, " Tone zone: %s\n", default_zone[0] != '\0' ? default_zone : ""); ast_cli(a->fd, " MOH Interpret: %s\n", default_mohinterpret); ast_cli(a->fd, " MOH Suggest: %s\n", default_mohsuggest); ast_cli(a->fd, " Voice Mail Extension: %s\n", default_vmexten); if (realtimepeers || realtimeregs) { ast_cli(a->fd, "\nRealtime SIP Settings:\n"); ast_cli(a->fd, "----------------------\n"); ast_cli(a->fd, " Realtime Peers: %s\n", AST_CLI_YESNO(realtimepeers)); ast_cli(a->fd, " Realtime Regs: %s\n", AST_CLI_YESNO(realtimeregs)); ast_cli(a->fd, " Cache Friends: %s\n", AST_CLI_YESNO(ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS))); ast_cli(a->fd, " Update: %s\n", AST_CLI_YESNO(sip_cfg.peer_rtupdate)); ast_cli(a->fd, " Ignore Reg. Expire: %s\n", AST_CLI_YESNO(sip_cfg.ignore_regexpire)); ast_cli(a->fd, " Save sys. name: %s\n", AST_CLI_YESNO(sip_cfg.rtsave_sysname)); ast_cli(a->fd, " Save path header: %s\n", AST_CLI_YESNO(sip_cfg.rtsave_path)); ast_cli(a->fd, " Auto Clear: %d (%s)\n", sip_cfg.rtautoclear, ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR) ? "Enabled" : "Disabled"); } ast_cli(a->fd, "\n----\n"); return CLI_SUCCESS; } static char *sip_show_mwi(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%-30.30s %-12.12s %-10.10s %-10.10s\n" char host[80]; struct ao2_iterator iter; struct sip_subscription_mwi *iterator; switch (cmd) { case CLI_INIT: e->command = "sip show mwi"; e->usage = "Usage: sip show mwi\n" " Provides a list of MWI subscriptions and status.\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd, FORMAT, "Host", "Username", "Mailbox", "Subscribed"); iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "sip_show_mwi iter"))) { ao2_lock(iterator); snprintf(host, sizeof(host), "%s:%d", iterator->hostname, iterator->portno ? iterator->portno : STANDARD_SIP_PORT); ast_cli(a->fd, FORMAT, host, iterator->username, iterator->mailbox, AST_CLI_YESNO(iterator->subscribed)); ao2_unlock(iterator); ao2_t_ref(iterator, -1, "sip_show_mwi iter"); } ao2_iterator_destroy(&iter); return CLI_SUCCESS; #undef FORMAT } /*! \brief Show subscription type in string format */ static const char *subscription_type2str(enum subscriptiontype subtype) { int i; for (i = 1; i < ARRAY_LEN(subscription_types); i++) { if (subscription_types[i].type == subtype) { return subscription_types[i].text; } } return subscription_types[0].text; } /*! \brief Find subscription type in array */ static const struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype) { int i; for (i = 1; i < ARRAY_LEN(subscription_types); i++) { if (subscription_types[i].type == subtype) { return &subscription_types[i]; } } return &subscription_types[0]; } /* * We try to structure all functions that loop on data structures as * a handler for individual entries, and a mainloop that iterates * on the main data structure. This way, moving the code to containers * that support iteration through callbacks will be a lot easier. */ #define FORMAT4 "%-15.15s %-15.15s %-15.15s %-15.15s %-13.13s %-15.15s %-10.10s %-6.6d\n" #define FORMAT3 "%-15.15s %-15.15s %-15.15s %-15.15s %-13.13s %-15.15s %-10.10s %-6.6s\n" #define FORMAT2 "%-15.15s %-15.15s %-15.15s %-15.15s %-7.7s %-15.15s %-10.10s %-10.10s\n" #define FORMAT "%-15.15s %-15.15s %-15.15s %-15.15s %-3.3s %-3.3s %-15.15s %-10.10s %-10.10s\n" /*! \brief callback for show channel|subscription */ static int show_channels_cb(void *__cur, void *__arg, int flags) { struct sip_pvt *cur = __cur; struct __show_chan_arg *arg = __arg; const struct ast_sockaddr *dst; sip_pvt_lock(cur); dst = sip_real_dst(cur); /* XXX indentation preserved to reduce diff. Will be fixed later */ if (cur->subscribed == NONE && !arg->subscriptions) { /* set if SIP transfer in progress */ const char *referstatus = cur->refer ? referstatus2str(cur->refer->status) : ""; struct ast_str *codec_buf = ast_str_alloca(64); ast_cli(arg->fd, FORMAT, ast_sockaddr_stringify_addr(dst), S_OR(cur->username, S_OR(cur->cid_num, "(None)")), cur->callid, cur->owner ? ast_format_cap_get_names(ast_channel_nativeformats(cur->owner), &codec_buf) : "(nothing)", AST_CLI_YESNO(ast_test_flag(&cur->flags[1], SIP_PAGE2_CALL_ONHOLD)), cur->needdestroy ? "(d)" : "", cur->lastmsg , referstatus, cur->relatedpeer ? cur->relatedpeer->name : "" ); arg->numchans++; } if (cur->subscribed != NONE && arg->subscriptions) { struct ast_str *mailbox_str = ast_str_alloca(512); if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer) peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer); ast_cli(arg->fd, FORMAT4, ast_sockaddr_stringify_addr(dst), S_OR(cur->username, S_OR(cur->cid_num, "(None)")), cur->callid, /* the 'complete' exten/context is hidden in the refer_to field for subscriptions */ cur->subscribed == MWI_NOTIFICATION ? "--" : cur->subscribeuri, cur->subscribed == MWI_NOTIFICATION ? "" : ast_extension_state2str(cur->laststate), subscription_type2str(cur->subscribed), cur->subscribed == MWI_NOTIFICATION ? S_OR(ast_str_buffer(mailbox_str), "") : "", cur->expiry ); arg->numchans++; } sip_pvt_unlock(cur); return 0; /* don't care, we scan all channels */ } /*! \brief CLI for show channels or subscriptions. * This is a new-style CLI handler so a single function contains * the prototype for the function, the 'generator' to produce multiple * entries in case it is required, and the actual handler for the command. */ static char *sip_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct __show_chan_arg arg = { .fd = a->fd, .numchans = 0 }; if (cmd == CLI_INIT) { e->command = "sip show {channels|subscriptions}"; e->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 (a->argc != e->args) return CLI_SHOWUSAGE; arg.subscriptions = !strcasecmp(a->argv[e->args - 1], "subscriptions"); if (!arg.subscriptions) ast_cli(arg.fd, FORMAT2, "Peer", "User/ANR", "Call ID", "Format", "Hold", "Last Message", "Expiry", "Peer"); else ast_cli(arg.fd, FORMAT3, "Peer", "User", "Call ID", "Extension", "Last state", "Type", "Mailbox", "Expiry"); /* iterate on the container and invoke the callback on each item */ ao2_t_callback(dialogs, OBJ_NODATA, show_channels_cb, &arg, "callback to show channels"); /* print summary information */ ast_cli(arg.fd, "%d active SIP %s%s\n", arg.numchans, (arg.subscriptions ? "subscription" : "dialog"), ESS(arg.numchans)); /* ESS(n) returns an "s" if n>1 */ return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 #undef FORMAT3 } /*! \brief 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 *complete_sipch(const char *line, const char *word, int pos, int state) { int which=0; struct sip_pvt *cur; char *c = NULL; int wordlen = strlen(word); struct ao2_iterator i; if (pos != 3) { return NULL; } i = ao2_iterator_init(dialogs, 0); while ((cur = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { sip_pvt_lock(cur); if (!strncasecmp(word, cur->callid, wordlen) && ++which > state) { c = ast_strdup(cur->callid); sip_pvt_unlock(cur); dialog_unref(cur, "drop ref in iterator loop break"); break; } sip_pvt_unlock(cur); dialog_unref(cur, "drop ref in iterator loop"); } ao2_iterator_destroy(&i); return c; } /*! \brief Do completion on peer name */ static char *complete_sip_peer(const char *word, int state, int flags2) { char *result = NULL; int wordlen = strlen(word); int which = 0; struct ao2_iterator i = ao2_iterator_init(peers, 0); struct sip_peer *peer; while ((peer = ao2_t_iterator_next(&i, "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) && (!flags2 || ast_test_flag(&peer->flags[1], flags2)) && ++which > state) result = ast_strdup(peer->name); sip_unref_peer(peer, "toss iterator peer ptr before break"); if (result) { break; } } ao2_iterator_destroy(&i); return result; } /*! \brief Do completion on registered peer name */ static char *complete_sip_registered_peer(const char *word, int state, int flags2) { char *result = NULL; int wordlen = strlen(word); int which = 0; struct ao2_iterator i; struct sip_peer *peer; i = ao2_iterator_init(peers, 0); while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { if (!strncasecmp(word, peer->name, wordlen) && (!flags2 || ast_test_flag(&peer->flags[1], flags2)) && ++which > state && peer->expire > 0) result = ast_strdup(peer->name); if (result) { sip_unref_peer(peer, "toss iterator peer ptr before break"); break; } sip_unref_peer(peer, "toss iterator peer ptr"); } ao2_iterator_destroy(&i); return result; } /*! \brief Support routine for 'sip show history' CLI */ static char *complete_sip_show_history(const char *line, const char *word, int pos, int state) { if (pos == 3) return complete_sipch(line, word, pos, state); return NULL; } /*! \brief Support routine for 'sip show peer' CLI */ static char *complete_sip_show_peer(const char *line, const char *word, int pos, int state) { if (pos == 3) { return complete_sip_peer(word, state, 0); } return NULL; } /*! \brief Support routine for 'sip unregister' CLI */ static char *complete_sip_unregister(const char *line, const char *word, int pos, int state) { if (pos == 2) return complete_sip_registered_peer(word, state, 0); return NULL; } /*! \brief Support routine for 'sip notify' CLI */ static char *complete_sip_notify(const char *line, const char *word, int pos, int state) { char *c = NULL; if (pos == 2) { int which = 0; char *cat = NULL; int wordlen = strlen(word); /* do completion for notify type */ if (!notify_types) return NULL; while ( (cat = ast_category_browse(notify_types, cat)) ) { if (!strncasecmp(word, cat, wordlen) && ++which > state) { c = ast_strdup(cat); break; } } return c; } if (pos > 2) return complete_sip_peer(word, state, 0); return NULL; } /*! \brief Show details of one active dialog */ static char *sip_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct sip_pvt *cur; size_t len; int found = 0; struct ao2_iterator i; switch (cmd) { case CLI_INIT: e->command = "sip show channel"; e->usage = "Usage: sip show channel \n" " Provides detailed status on a given SIP dialog (identified by SIP call-id).\n"; return NULL; case CLI_GENERATE: return complete_sipch(a->line, a->word, a->pos, a->n); } if (a->argc != 4) return CLI_SHOWUSAGE; len = strlen(a->argv[3]); i = ao2_iterator_init(dialogs, 0); while ((cur = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { sip_pvt_lock(cur); if (!strncasecmp(cur->callid, a->argv[3], len)) { struct ast_str *strbuf; struct ast_str *codec_buf = ast_str_alloca(64); ast_cli(a->fd, "\n"); if (cur->subscribed != NONE) { ast_cli(a->fd, " * Subscription (type: %s)\n", subscription_type2str(cur->subscribed)); } else { ast_cli(a->fd, " * SIP Call\n"); } ast_cli(a->fd, " Curr. trans. direction: %s\n", ast_test_flag(&cur->flags[0], SIP_OUTGOING) ? "Outgoing" : "Incoming"); ast_cli(a->fd, " Call-ID: %s\n", cur->callid); ast_cli(a->fd, " Owner channel ID: %s\n", cur->owner ? ast_channel_name(cur->owner) : ""); ast_cli(a->fd, " Our Codec Capability: %s\n", ast_format_cap_get_names(cur->caps, &codec_buf)); ast_cli(a->fd, " Non-Codec Capability (DTMF): %d\n", cur->noncodeccapability); ast_cli(a->fd, " Their Codec Capability: %s\n", ast_format_cap_get_names(cur->peercaps, &codec_buf)); ast_cli(a->fd, " Joint Codec Capability: %s\n", ast_format_cap_get_names(cur->jointcaps, &codec_buf)); ast_cli(a->fd, " Format: %s\n", cur->owner ? ast_format_cap_get_names(ast_channel_nativeformats(cur->owner), &codec_buf) : "(nothing)" ); ast_cli(a->fd, " T.38 support %s\n", AST_CLI_YESNO(cur->udptl != NULL)); ast_cli(a->fd, " Video support %s\n", AST_CLI_YESNO(cur->vrtp != NULL)); ast_cli(a->fd, " MaxCallBR: %d kbps\n", cur->maxcallbitrate); ast_cli(a->fd, " Theoretical Address: %s\n", ast_sockaddr_stringify(&cur->sa)); ast_cli(a->fd, " Received Address: %s\n", ast_sockaddr_stringify(&cur->recv)); ast_cli(a->fd, " SIP Transfer mode: %s\n", transfermode2str(cur->allowtransfer)); ast_cli(a->fd, " Force rport: %s\n", force_rport_string(cur->flags)); if (ast_sockaddr_isnull(&cur->redirip)) { ast_cli(a->fd, " Audio IP: %s (local)\n", ast_sockaddr_stringify_addr(&cur->ourip)); } else { ast_cli(a->fd, " Audio IP: %s (Outside bridge)\n", ast_sockaddr_stringify_addr(&cur->redirip)); } ast_cli(a->fd, " Our Tag: %s\n", cur->tag); ast_cli(a->fd, " Their Tag: %s\n", cur->theirtag); ast_cli(a->fd, " SIP User agent: %s\n", cur->useragent); if (!ast_strlen_zero(cur->username)) { ast_cli(a->fd, " Username: %s\n", cur->username); } if (!ast_strlen_zero(cur->peername)) { ast_cli(a->fd, " Peername: %s\n", cur->peername); } if (!ast_strlen_zero(cur->uri)) { ast_cli(a->fd, " Original uri: %s\n", cur->uri); } if (!ast_strlen_zero(cur->cid_num)) { ast_cli(a->fd, " Caller-ID: %s\n", cur->cid_num); } ast_cli(a->fd, " Need Destroy: %s\n", AST_CLI_YESNO(cur->needdestroy)); ast_cli(a->fd, " Last Message: %s\n", cur->lastmsg); ast_cli(a->fd, " Promiscuous Redir: %s\n", AST_CLI_YESNO(ast_test_flag(&cur->flags[0], SIP_PROMISCREDIR))); if ((strbuf = sip_route_list(&cur->route, 1, 0))) { ast_cli(a->fd, " Route: %s\n", ast_str_buffer(strbuf)); ast_free(strbuf); } ast_cli(a->fd, " DTMF Mode: %s\n", dtmfmode2str(ast_test_flag(&cur->flags[0], SIP_DTMF))); ast_cli(a->fd, " SIP Options: "); if (cur->sipoptions) { int x; for (x = 0 ; x < ARRAY_LEN(sip_options); x++) { if (cur->sipoptions & sip_options[x].id) ast_cli(a->fd, "%s ", sip_options[x].text); } ast_cli(a->fd, "\n"); } else { ast_cli(a->fd, "(none)\n"); } if (!cur->stimer) { ast_cli(a->fd, " Session-Timer: Uninitiallized\n"); } else { ast_cli(a->fd, " Session-Timer: %s\n", cur->stimer->st_active ? "Active" : "Inactive"); if (cur->stimer->st_active == TRUE) { ast_cli(a->fd, " S-Timer Interval: %d\n", cur->stimer->st_interval); ast_cli(a->fd, " S-Timer Refresher: %s\n", strefresher2str(cur->stimer->st_ref)); ast_cli(a->fd, " S-Timer Sched Id: %d\n", cur->stimer->st_schedid); ast_cli(a->fd, " S-Timer Peer Sts: %s\n", cur->stimer->st_active_peer_ua ? "Active" : "Inactive"); ast_cli(a->fd, " S-Timer Cached Min-SE: %d\n", cur->stimer->st_cached_min_se); ast_cli(a->fd, " S-Timer Cached SE: %d\n", cur->stimer->st_cached_max_se); ast_cli(a->fd, " S-Timer Cached Ref: %s\n", strefresherparam2str(cur->stimer->st_cached_ref)); ast_cli(a->fd, " S-Timer Cached Mode: %s\n", stmode2str(cur->stimer->st_cached_mode)); } } /* add transport and media types */ ast_cli(a->fd, " Transport: %s\n", ast_transport2str(cur->socket.type)); ast_cli(a->fd, " Media: %s\n", cur->srtp ? "SRTP" : cur->rtp ? "RTP" : "None"); ast_cli(a->fd, "\n\n"); found++; } sip_pvt_unlock(cur); ao2_t_ref(cur, -1, "toss dialog ptr set by iterator_next"); } ao2_iterator_destroy(&i); if (!found) { ast_cli(a->fd, "No such SIP Call ID starting with '%s'\n", a->argv[3]); } return CLI_SUCCESS; } /*! \brief Show history details of one dialog */ static char *sip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct sip_pvt *cur; size_t len; int found = 0; struct ao2_iterator i; switch (cmd) { case CLI_INIT: e->command = "sip show history"; e->usage = "Usage: sip show history \n" " Provides detailed dialog history on a given SIP call (specified by call-id).\n"; return NULL; case CLI_GENERATE: return complete_sip_show_history(a->line, a->word, a->pos, a->n); } if (a->argc != 4) { return CLI_SHOWUSAGE; } if (!recordhistory) { ast_cli(a->fd, "\n***Note: History recording is currently DISABLED. Use 'sip set history on' to ENABLE.\n"); } len = strlen(a->argv[3]); i = ao2_iterator_init(dialogs, 0); while ((cur = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { sip_pvt_lock(cur); if (!strncasecmp(cur->callid, a->argv[3], len)) { struct sip_history *hist; int x = 0; ast_cli(a->fd, "\n"); if (cur->subscribed != NONE) { ast_cli(a->fd, " * Subscription\n"); } else { ast_cli(a->fd, " * SIP Call\n"); } if (cur->history) { AST_LIST_TRAVERSE(cur->history, hist, list) ast_cli(a->fd, "%d. %s\n", ++x, hist->event); } if (x == 0) { ast_cli(a->fd, "Call '%s' has no history\n", cur->callid); } found++; } sip_pvt_unlock(cur); ao2_t_ref(cur, -1, "toss dialog ptr from iterator_next"); } ao2_iterator_destroy(&i); if (!found) { ast_cli(a->fd, "No such SIP Call ID starting with '%s'\n", a->argv[3]); } return CLI_SUCCESS; } /*! \brief Dump SIP history to debug log file at end of lifespan for SIP dialog */ static void sip_dump_history(struct sip_pvt *dialog) { int x = 0; struct sip_history *hist; static int errmsg = 0; if (!dialog) { return; } if (!option_debug && !sipdebug) { if (!errmsg) { ast_log(LOG_NOTICE, "You must have debugging enabled (SIP or Asterisk) in order to dump SIP history.\n"); errmsg = 1; } return; } ast_debug(1, "\n---------- SIP HISTORY for '%s' \n", dialog->callid); if (dialog->subscribed) { ast_debug(1, " * Subscription\n"); } else { ast_debug(1, " * SIP Call\n"); } if (dialog->history) { AST_LIST_TRAVERSE(dialog->history, hist, list) ast_debug(1, " %-3.3d. %s\n", ++x, hist->event); } if (!x) { ast_debug(1, "Call '%s' has no history\n", dialog->callid); } ast_debug(1, "\n---------- END SIP HISTORY for '%s' \n", dialog->callid); } /*! \brief Receive SIP INFO Message */ static void handle_request_info(struct sip_pvt *p, struct sip_request *req) { const char *buf = ""; unsigned int event; const char *c = sip_get_header(req, "Content-Type"); /* Need to check the media/type */ if (!strcasecmp(c, "application/dtmf-relay") || !strcasecmp(c, "application/vnd.nortelnetworks.digits") || !strcasecmp(c, "application/dtmf")) { unsigned int duration = 0; if (!p->owner) { /* not a PBX call */ transmit_response(p, "481 Call leg/transaction does not exist", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } /* If dtmf-relay or vnd.nortelnetworks.digits, parse the signal and duration; * otherwise use the body as the signal */ if (strcasecmp(c, "application/dtmf")) { const char *tmp; if (ast_strlen_zero(buf = get_content_line(req, "Signal", '=')) && ast_strlen_zero(buf = get_content_line(req, "d", '='))) { ast_log(LOG_WARNING, "Unable to retrieve DTMF signal for INFO message on " "call %s\n", p->callid); transmit_response(p, "200 OK", req); return; } if (!ast_strlen_zero((tmp = get_content_line(req, "Duration", '=')))) { sscanf(tmp, "%30u", &duration); } } else { /* Type is application/dtmf, simply use what's in the message body */ buf = get_content(req); } /* An empty message body requires us to send a 200 OK */ if (ast_strlen_zero(buf)) { transmit_response(p, "200 OK", req); return; } 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", p->callid); transmit_response(p, "200 OK", req); return; } if (event == 16) { /* send a FLASH event */ struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_FLASH, } }; ast_queue_frame(p->owner, &f); if (sipdebug) { ast_verbose("* DTMF-relay event received: FLASH\n"); } } else { /* send a DTMF event */ struct ast_frame f = { AST_FRAME_DTMF, }; if (event < 10) { f.subclass.integer = '0' + event; } else if (event == 10) { f.subclass.integer = '*'; } else if (event == 11) { f.subclass.integer = '#'; } else { f.subclass.integer = 'A' + (event - 12); } f.len = duration; ast_queue_frame(p->owner, &f); if (sipdebug) { ast_verbose("* DTMF-relay event received: %c\n", (int) f.subclass.integer); } } transmit_response(p, "200 OK", req); return; } else if (!strcasecmp(c, "application/media_control+xml")) { /* Eh, we'll just assume it's a fast picture update for now */ if (p->owner) { ast_queue_control(p->owner, AST_CONTROL_VIDUPDATE); } transmit_response(p, "200 OK", req); return; } else if (!ast_strlen_zero(c = sip_get_header(req, "X-ClientCode"))) { /* Client code (from SNOM phone) */ if (ast_test_flag(&p->flags[0], SIP_USECLIENTCODE)) { if (p->owner) { ast_cdr_setuserfield(ast_channel_name(p->owner), c); } transmit_response(p, "200 OK", req); } else { transmit_response(p, "403 Forbidden", req); } return; } else if (!ast_strlen_zero(c = sip_get_header(req, "Record"))) { /* INFO messages generated by some phones to start/stop recording * on phone calls. */ char feat[AST_FEATURE_MAX_LEN]; int feat_res = -1; int j; struct ast_frame f = { AST_FRAME_DTMF, }; int suppress_warning = 0; /* Supress warning if the feature is blank */ if (!p->owner) { /* not a PBX call */ transmit_response(p, "481 Call leg/transaction does not exist", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } /* first, get the feature string, if it exists */ if (p->relatedpeer) { if (!strcasecmp(c, "on")) { if (ast_strlen_zero(p->relatedpeer->record_on_feature)) { suppress_warning = 1; } else { feat_res = ast_get_feature(p->owner, p->relatedpeer->record_on_feature, feat, sizeof(feat)); } } else if (!strcasecmp(c, "off")) { if (ast_strlen_zero(p->relatedpeer->record_off_feature)) { suppress_warning = 1; } else { feat_res = ast_get_feature(p->owner, p->relatedpeer->record_off_feature, feat, sizeof(feat)); } } else { ast_log(LOG_ERROR, "Received INFO requesting to record with invalid value: %s\n", c); } } if (feat_res || ast_strlen_zero(feat)) { if (!suppress_warning) { ast_log(LOG_WARNING, "Recording requested, but no One Touch Monitor registered. (See features.conf)\n"); } /* 403 means that we don't support this feature, so don't request it again */ transmit_response(p, "403 Forbidden", req); return; } /* Send the feature code to the PBX as DTMF, just like the handset had sent it */ f.len = 100; for (j = 0; j < strlen(feat); j++) { f.subclass.integer = feat[j]; ast_queue_frame(p->owner, &f); if (sipdebug) { ast_verbose("* DTMF-relay event faked: %c\n", f.subclass.integer); } } ast_debug(1, "Got a Request to Record the channel, state %s\n", c); transmit_response(p, "200 OK", req); return; } else if (ast_strlen_zero(c = sip_get_header(req, "Content-Length")) || !strcasecmp(c, "0")) { /* This is probably just a packet making sure the signalling is still up, just send back a 200 OK */ transmit_response(p, "200 OK", req); return; } /* Other type of INFO message, not really understood by Asterisk */ ast_log(LOG_WARNING, "Unable to parse INFO message from %s. Content %s\n", p->callid, buf); transmit_response(p, "415 Unsupported media type", req); return; } /*! \brief Enable SIP Debugging for a single IP */ static char *sip_do_debug_ip(int fd, const char *arg) { if (ast_sockaddr_resolve_first_af(&debugaddr, arg, 0, 0)) { return CLI_SHOWUSAGE; } ast_cli(fd, "SIP Debugging Enabled for IP: %s\n", ast_sockaddr_stringify_addr(&debugaddr)); sipdebug |= sip_debug_console; return CLI_SUCCESS; } /*! \brief Turn on SIP debugging for a given peer */ static char *sip_do_debug_peer(int fd, const char *arg) { struct sip_peer *peer = sip_find_peer(arg, NULL, TRUE, FINDPEERS, FALSE, 0); if (!peer) { ast_cli(fd, "No such peer '%s'\n", arg); } else if (ast_sockaddr_isnull(&peer->addr)) { ast_cli(fd, "Unable to get IP address of peer '%s'\n", arg); } else { ast_sockaddr_copy(&debugaddr, &peer->addr); ast_cli(fd, "SIP Debugging Enabled for IP: %s\n", ast_sockaddr_stringify_addr(&debugaddr)); sipdebug |= sip_debug_console; } if (peer) { sip_unref_peer(peer, "sip_do_debug_peer: sip_unref_peer, from sip_find_peer call"); } return CLI_SUCCESS; } /*! \brief Turn on SIP debugging (CLI command) */ static char *sip_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int oldsipdebug = sipdebug & sip_debug_console; const char *what; if (cmd == CLI_INIT) { e->command = "sip set debug {on|off|ip|peer}"; e->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 (a->pos == 4 && !strcasecmp(a->argv[3], "peer")) return complete_sip_peer(a->word, a->n, 0); return NULL; } what = a->argv[e->args-1]; /* guaranteed to exist */ if (a->argc == e->args) { /* on/off */ if (!strcasecmp(what, "on")) { sipdebug |= sip_debug_console; sipdebug_text = 1; /*! \note this can be a special debug command - "sip debug text" or something */ memset(&debugaddr, 0, sizeof(debugaddr)); ast_cli(a->fd, "SIP Debugging %senabled\n", oldsipdebug ? "re-" : ""); return CLI_SUCCESS; } else if (!strcasecmp(what, "off")) { sipdebug &= ~sip_debug_console; sipdebug_text = 0; ast_cli(a->fd, "SIP Debugging Disabled\n"); return CLI_SUCCESS; } } else if (a->argc == e->args + 1) { /* ip/peer */ if (!strcasecmp(what, "ip")) return sip_do_debug_ip(a->fd, a->argv[e->args]); else if (!strcasecmp(what, "peer")) return sip_do_debug_peer(a->fd, a->argv[e->args]); } return CLI_SHOWUSAGE; /* default, failure */ } /*! \brief Cli command to send SIP notify to peer */ static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_variable *varlist; int i; switch (cmd) { case CLI_INIT: e->command = "sip notify"; e->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; case CLI_GENERATE: return complete_sip_notify(a->line, a->word, a->pos, a->n); } if (a->argc < 4) return CLI_SHOWUSAGE; if (!notify_types) { ast_cli(a->fd, "No %s file found, or no types listed there\n", notify_config); return CLI_FAILURE; } varlist = ast_variable_browse(notify_types, a->argv[2]); if (!varlist) { ast_cli(a->fd, "Unable to find notify type '%s'\n", a->argv[2]); return CLI_FAILURE; } for (i = 3; i < a->argc; i++) { struct sip_pvt *p; char buf[512]; struct ast_variable *header, *var; if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, NULL))) { ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n"); return CLI_FAILURE; } if (create_addr(p, a->argv[i], NULL, 1)) { /* Maybe they're not registered, etc. */ dialog_unlink_all(p); dialog_unref(p, "unref dialog inside for loop" ); /* sip_destroy(p); */ ast_cli(a->fd, "Could not create address for '%s'\n", a->argv[i]); continue; } /* Notify is outgoing call */ ast_set_flag(&p->flags[0], SIP_OUTGOING); sip_notify_alloc(p); p->notify->headers = header = ast_variable_new("Subscription-State", "terminated", ""); for (var = varlist; var; var = var->next) { ast_copy_string(buf, var->value, sizeof(buf)); ast_unescape_semicolon(buf); if (!strcasecmp(var->name, "Content")) { if (ast_str_strlen(p->notify->content)) ast_str_append(&p->notify->content, 0, "\r\n"); ast_str_append(&p->notify->content, 0, "%s", buf); } 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->next = ast_variable_new(var->name, buf, ""); header = header->next; } } /* Now that we have the peer's address, set our ip and change callid */ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); build_via(p); change_callid_pvt(p, NULL); ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]); sip_scheddestroy(p, SIP_TRANS_TIMEOUT); transmit_invite(p, SIP_NOTIFY, 0, 2, NULL); dialog_unref(p, "bump down the count of p since we're done with it."); } return CLI_SUCCESS; } /*! \brief Enable/Disable SIP History logging (CLI) */ static char *sip_set_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "sip set history {on|off}"; e->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; case CLI_GENERATE: return NULL; } if (a->argc != e->args) return CLI_SHOWUSAGE; if (!strncasecmp(a->argv[e->args - 1], "on", 2)) { recordhistory = TRUE; ast_cli(a->fd, "SIP History Recording Enabled (use 'sip show history')\n"); } else if (!strncasecmp(a->argv[e->args - 1], "off", 3)) { recordhistory = FALSE; ast_cli(a->fd, "SIP History Recording Disabled\n"); } else { return CLI_SHOWUSAGE; } return CLI_SUCCESS; } /*! \brief Authenticate for outbound registration */ static int do_register_auth(struct sip_pvt *p, struct sip_request *req, enum sip_auth_type code) { char *header, *respheader; char digest[1024]; p->authtries++; sip_auth_headers(code, &header, &respheader); memset(digest, 0, sizeof(digest)); if (reply_digest(p, req, header, SIP_REGISTER, digest, sizeof(digest))) { /* There's nothing to use for authentication */ /* No digest challenge in request */ if (sip_debug_test_pvt(p) && p->registry) ast_verbose("No authentication challenge, sending blank registration to domain/host name %s\n", p->registry->hostname); /* No old challenge */ return -1; } if (p->do_history) append_history(p, "RegistryAuth", "Try: %d", p->authtries); if (sip_debug_test_pvt(p) && p->registry) ast_verbose("Responding to challenge, registration to domain/host name %s\n", p->registry->hostname); return transmit_register(p->registry, SIP_REGISTER, digest, respheader); } /*! \brief Add authentication on outbound SIP packet */ static int do_proxy_auth(struct sip_pvt *p, struct sip_request *req, enum sip_auth_type code, int sipmethod, int init) { char *header, *respheader; char digest[1024]; if (!p->options && !(p->options = ast_calloc(1, sizeof(*p->options)))) return -2; p->authtries++; sip_auth_headers(code, &header, &respheader); ast_debug(2, "Auth attempt %d on %s\n", p->authtries, sip_methods[sipmethod].text); memset(digest, 0, sizeof(digest)); if (reply_digest(p, req, header, sipmethod, digest, sizeof(digest) )) { /* No way to authenticate */ return -1; } /* Now we have a reply digest */ p->options->auth = digest; p->options->authheader = respheader; return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init, NULL); } /*! \brief reply to authentication for outbound registrations \return Returns -1 if we have no auth \note This is used for register= servers in sip.conf, SIP proxies we register with for receiving calls from. */ static int reply_digest(struct sip_pvt *p, struct sip_request *req, char *header, int sipmethod, char *digest, int digest_len) { char tmp[512]; char *c; char oldnonce[256]; /* table of recognised keywords, and places where they should be copied */ const struct x { const char *key; const ast_string_field *field; } *i, keys[] = { { "realm=", &p->realm }, { "nonce=", &p->nonce }, { "opaque=", &p->opaque }, { "qop=", &p->qop }, { "domain=", &p->domain }, { NULL, 0 }, }; ast_copy_string(tmp, sip_get_header(req, header), sizeof(tmp)); if (ast_strlen_zero(tmp)) return -1; if (strncasecmp(tmp, "Digest ", strlen("Digest "))) { ast_log(LOG_WARNING, "missing Digest.\n"); return -1; } c = tmp + strlen("Digest "); ast_copy_string(oldnonce, p->nonce, sizeof(oldnonce)); while (c && *(c = ast_skip_blanks(c))) { /* lookup for keys */ for (i = keys; i->key != NULL; i++) { char *src, *separator; if (strncasecmp(c, i->key, strlen(i->key)) != 0) continue; /* Found. Skip keyword, take text in quotes or up to the separator. */ c += strlen(i->key); if (*c == '"') { src = ++c; separator = "\""; } else { src = c; separator = ","; } strsep(&c, separator); /* clear separator and move ptr */ ast_string_field_ptr_set(p, i->field, src); break; } if (i->key == NULL) /* not found, try ',' */ strsep(&c, ","); } /* Reset nonce count */ if (strcmp(p->nonce, oldnonce)) p->noncecount = 0; /* Save auth data for following registrations */ if (p->registry) { struct sip_registry *r = p->registry; if (strcmp(r->nonce, p->nonce)) { ast_string_field_set(r, realm, p->realm); ast_string_field_set(r, nonce, p->nonce); ast_string_field_set(r, authdomain, p->domain); ast_string_field_set(r, opaque, p->opaque); ast_string_field_set(r, qop, p->qop); r->noncecount = 0; } } return build_reply_digest(p, sipmethod, digest, digest_len); } /*! \brief Build reply digest \return Returns -1 if we have no auth \note Build digest challenge for authentication of registrations and calls Also used for authentication of BYE */ static int build_reply_digest(struct sip_pvt *p, int method, char* digest, int digest_len) { char a1[256]; char a2[256]; char a1_hash[256]; char a2_hash[256]; char resp[256]; char resp_hash[256]; char uri[256]; char opaque[256] = ""; char cnonce[80]; const char *username; const char *secret; const char *md5secret; struct sip_auth *auth; /* Realm authentication credential */ struct sip_auth_container *credentials; if (!ast_strlen_zero(p->domain)) snprintf(uri, sizeof(uri), "%s:%s", p->socket.type == AST_TRANSPORT_TLS ? "sips" : "sip", p->domain); else if (!ast_strlen_zero(p->uri)) ast_copy_string(uri, p->uri, sizeof(uri)); else snprintf(uri, sizeof(uri), "%s:%s@%s", p->socket.type == AST_TRANSPORT_TLS ? "sips" : "sip", p->username, ast_sockaddr_stringify_host_remote(&p->sa)); snprintf(cnonce, sizeof(cnonce), "%08lx", (unsigned long)ast_random()); /* Check if we have peer credentials */ ao2_lock(p); credentials = p->peerauth; if (credentials) { ao2_t_ref(credentials, +1, "Ref peer auth for digest"); } ao2_unlock(p); auth = find_realm_authentication(credentials, p->realm); if (!auth) { /* If not, check global credentials */ if (credentials) { ao2_t_ref(credentials, -1, "Unref peer auth for digest"); } ast_mutex_lock(&authl_lock); credentials = authl; if (credentials) { ao2_t_ref(credentials, +1, "Ref global auth for digest"); } ast_mutex_unlock(&authl_lock); auth = find_realm_authentication(credentials, p->realm); } if (auth) { ast_debug(3, "use realm [%s] from peer [%s][%s]\n", auth->username, p->peername, p->username); username = auth->username; secret = auth->secret; md5secret = auth->md5secret; if (sipdebug) ast_debug(1, "Using realm %s authentication for call %s\n", p->realm, p->callid); } else { /* No authentication, use peer or register= config */ username = p->authname; secret = p->relatedpeer && !ast_strlen_zero(p->relatedpeer->remotesecret) ? p->relatedpeer->remotesecret : p->peersecret; md5secret = p->peermd5secret; } if (ast_strlen_zero(username)) { /* We have no authentication */ if (credentials) { ao2_t_ref(credentials, -1, "Unref auth for digest"); } return -1; } /* Calculate SIP digest response */ snprintf(a1, sizeof(a1), "%s:%s:%s", username, p->realm, secret); snprintf(a2, sizeof(a2), "%s:%s", sip_methods[method].text, 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); p->noncecount++; if (!ast_strlen_zero(p->qop)) snprintf(resp, sizeof(resp), "%s:%s:%08x:%s:%s:%s", a1_hash, p->nonce, (unsigned)p->noncecount, cnonce, "auth", a2_hash); else snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, p->nonce, a2_hash); ast_md5_hash(resp_hash, resp); /* only include the opaque string if it's set */ if (!ast_strlen_zero(p->opaque)) { snprintf(opaque, sizeof(opaque), ", opaque=\"%s\"", p->opaque); } /* XXX We hard code our qop to "auth" for now. XXX */ if (!ast_strlen_zero(p->qop)) snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\"%s, qop=auth, cnonce=\"%s\", nc=%08x", username, p->realm, uri, p->nonce, resp_hash, opaque, cnonce, (unsigned)p->noncecount); else snprintf(digest, digest_len, "Digest username=\"%s\", realm=\"%s\", algorithm=MD5, uri=\"%s\", nonce=\"%s\", response=\"%s\"%s", username, p->realm, uri, p->nonce, resp_hash, opaque); append_history(p, "AuthResp", "Auth response sent for %s in realm %s - nc %d", username, p->realm, p->noncecount); if (credentials) { ao2_t_ref(credentials, -1, "Unref auth for digest"); } return 0; } /*! \brief Read SIP header (dialplan function) */ static int func_header_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) { struct sip_pvt *p; const char *content = NULL; 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 (!IS_SIP_TECH(ast_channel_tech(chan))) { 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, data); if (!args.number) { number = 1; } else { sscanf(args.number, "%30d", &number); if (number < 1) number = 1; } p = ast_channel_tech_pvt(chan); /* If there is no private structure, this channel is no longer alive */ if (!p) { ast_channel_unlock(chan); return -1; } for (i = 0; i < number; i++) content = __get_header(&p->initreq, args.header, &start); if (ast_strlen_zero(content)) { ast_channel_unlock(chan); return -1; } ast_copy_string(buf, content, len); ast_channel_unlock(chan); return 0; } static struct ast_custom_function sip_header_function = { .name = "SIP_HEADER", .read = func_header_read, }; /*! \brief Dial plan function to check if domain is local */ static int func_check_sipdomain(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "CHECKSIPDOMAIN requires an argument - A domain name\n"); return -1; } if (check_sip_domain(data, NULL, 0)) ast_copy_string(buf, data, len); else buf[0] = '\0'; return 0; } static struct ast_custom_function checksipdomain_function = { .name = "CHECKSIPDOMAIN", .read = func_check_sipdomain, }; /*! \brief ${SIPPEER()} Dialplan function - reads peer data */ static int function_sippeer(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { struct sip_peer *peer; char *colname; if ((colname = strchr(data, ','))) { *colname++ = '\0'; } else { colname = "ip"; } if (!(peer = sip_find_peer(data, NULL, TRUE, FINDPEERS, FALSE, 0))) return -1; if (!strcasecmp(colname, "ip")) { ast_copy_string(buf, ast_sockaddr_stringify_addr(&peer->addr), len); } else if (!strcasecmp(colname, "port")) { snprintf(buf, len, "%d", ast_sockaddr_port(&peer->addr)); } else if (!strcasecmp(colname, "status")) { peer_status(peer, buf, len); } else if (!strcasecmp(colname, "language")) { ast_copy_string(buf, peer->language, len); } else if (!strcasecmp(colname, "regexten")) { ast_copy_string(buf, peer->regexten, len); } else if (!strcasecmp(colname, "limit")) { snprintf(buf, len, "%d", peer->call_limit); } else if (!strcasecmp(colname, "busylevel")) { snprintf(buf, len, "%d", peer->busy_level); } else if (!strcasecmp(colname, "curcalls")) { snprintf(buf, len, "%d", peer->inuse); } else if (!strcasecmp(colname, "maxforwards")) { snprintf(buf, len, "%d", peer->maxforwards); } else if (!strcasecmp(colname, "accountcode")) { ast_copy_string(buf, peer->accountcode, len); } else if (!strcasecmp(colname, "callgroup")) { ast_print_group(buf, len, peer->callgroup); } else if (!strcasecmp(colname, "pickupgroup")) { ast_print_group(buf, len, peer->pickupgroup); } else if (!strcasecmp(colname, "namedcallgroup")) { struct ast_str *tmp_str = ast_str_create(1024); if (tmp_str) { ast_copy_string(buf, ast_print_namedgroups(&tmp_str, peer->named_callgroups), len); ast_free(tmp_str); } } else if (!strcasecmp(colname, "namedpickupgroup")) { struct ast_str *tmp_str = ast_str_create(1024); if (tmp_str) { ast_copy_string(buf, ast_print_namedgroups(&tmp_str, peer->named_pickupgroups), len); ast_free(tmp_str); } } else if (!strcasecmp(colname, "useragent")) { ast_copy_string(buf, peer->useragent, len); } else if (!strcasecmp(colname, "mailbox")) { struct ast_str *mailbox_str = ast_str_alloca(512); peer_mailboxes_to_str(&mailbox_str, peer); ast_copy_string(buf, ast_str_buffer(mailbox_str), len); } else if (!strcasecmp(colname, "context")) { ast_copy_string(buf, peer->context, len); } else if (!strcasecmp(colname, "expire")) { snprintf(buf, len, "%d", peer->expire); } else if (!strcasecmp(colname, "dynamic")) { ast_copy_string(buf, peer->host_dynamic ? "yes" : "no", len); } else if (!strcasecmp(colname, "callerid_name")) { ast_copy_string(buf, peer->cid_name, len); } else if (!strcasecmp(colname, "callerid_num")) { ast_copy_string(buf, peer->cid_num, len); } else if (!strcasecmp(colname, "codecs")) { struct ast_str *codec_buf = ast_str_alloca(64); ast_format_cap_get_names(peer->caps, &codec_buf); ast_copy_string(buf, ast_str_buffer(codec_buf), len); } else if (!strcasecmp(colname, "encryption")) { snprintf(buf, len, "%u", ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP)); } else if (!strncasecmp(colname, "chanvar[", 8)) { char *chanvar=colname + 8; struct ast_variable *v; chanvar = strsep(&chanvar, "]"); for (v = peer->chanvars ; v ; v = v->next) { if (!strcasecmp(v->name, chanvar)) { ast_copy_string(buf, v->value, len); } } } else if (!strncasecmp(colname, "codec[", 6)) { char *codecnum; struct ast_format *codec; codecnum = colname + 6; /* move past the '[' */ codecnum = strsep(&codecnum, "]"); /* trim trailing ']' if any */ codec = ast_format_cap_get_format(peer->caps, atoi(codecnum)); if (codec) { ast_copy_string(buf, ast_format_get_name(codec), len); ao2_ref(codec, -1); } else { buf[0] = '\0'; } } else { buf[0] = '\0'; } sip_unref_peer(peer, "sip_unref_peer from function_sippeer, just before return"); return 0; } /*! \brief Structure to declare a dialplan function: SIPPEER */ static struct ast_custom_function sippeer_function = { .name = "SIPPEER", .read = function_sippeer, }; /*! \brief update redirecting information for a channel based on headers * */ static void change_redirecting_information(struct sip_pvt *p, struct sip_request *req, struct ast_party_redirecting *redirecting, struct ast_set_party_redirecting *update_redirecting, int set_call_forward) { char *redirecting_from_name = NULL; char *redirecting_from_number = NULL; char *redirecting_to_name = NULL; char *redirecting_to_number = NULL; char *reason_str = NULL; int reason = AST_REDIRECTING_REASON_UNCONDITIONAL; int is_response = req->method == SIP_RESPONSE; int res = 0; res = get_rdnis(p, req, &redirecting_from_name, &redirecting_from_number, &reason, &reason_str); if (res == -1) { if (is_response) { get_name_and_number(sip_get_header(req, "TO"), &redirecting_from_name, &redirecting_from_number); } else { return; } } /* At this point, all redirecting "from" info should be filled in appropriately * on to the "to" info */ if (is_response) { parse_moved_contact(p, req, &redirecting_to_name, &redirecting_to_number, set_call_forward); } else { get_name_and_number(sip_get_header(req, "TO"), &redirecting_to_name, &redirecting_to_number); } if (!ast_strlen_zero(redirecting_from_number)) { ast_debug(3, "Got redirecting from number %s\n", redirecting_from_number); update_redirecting->from.number = 1; redirecting->from.number.valid = 1; ast_free(redirecting->from.number.str); redirecting->from.number.str = redirecting_from_number; } if (!ast_strlen_zero(redirecting_from_name)) { ast_debug(3, "Got redirecting from name %s\n", redirecting_from_name); update_redirecting->from.name = 1; redirecting->from.name.valid = 1; ast_free(redirecting->from.name.str); redirecting->from.name.str = redirecting_from_name; } if (!ast_strlen_zero(p->cid_tag)) { ast_free(redirecting->from.tag); redirecting->from.tag = ast_strdup(p->cid_tag); ast_free(redirecting->to.tag); redirecting->to.tag = ast_strdup(p->cid_tag); } if (!ast_strlen_zero(redirecting_to_number)) { ast_debug(3, "Got redirecting to number %s\n", redirecting_to_number); update_redirecting->to.number = 1; redirecting->to.number.valid = 1; ast_free(redirecting->to.number.str); redirecting->to.number.str = redirecting_to_number; } if (!ast_strlen_zero(redirecting_to_name)) { ast_debug(3, "Got redirecting to name %s\n", redirecting_from_number); update_redirecting->to.name = 1; redirecting->to.name.valid = 1; ast_free(redirecting->to.name.str); redirecting->to.name.str = redirecting_to_name; } redirecting->reason.code = reason; if (reason_str) { ast_debug(3, "Got redirecting reason %s\n", reason_str); ast_free(redirecting->reason.str); redirecting->reason.str = reason_str; } } /*! \brief Parse 302 Moved temporalily response \todo XXX 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. */ static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward) { char contact[SIPBUFSIZE]; char *contact_name = NULL; char *contact_number = NULL; char *separator, *trans; char *domain; enum ast_transport transport = AST_TRANSPORT_UDP; ast_copy_string(contact, sip_get_header(req, "Contact"), sizeof(contact)); if ((separator = strchr(contact, ','))) *separator = '\0'; contact_number = get_in_brackets(contact); if ((trans = strcasestr(contact_number, ";transport="))) { trans += 11; if ((separator = strchr(trans, ';'))) *separator = '\0'; if (!strncasecmp(trans, "tcp", 3)) transport = AST_TRANSPORT_TCP; else if (!strncasecmp(trans, "tls", 3)) transport = AST_TRANSPORT_TLS; else { if (strncasecmp(trans, "udp", 3)) ast_debug(1, "received contact with an invalid transport, '%s'\n", contact_number); /* This will assume UDP for all unknown transports */ transport = AST_TRANSPORT_UDP; } } contact_number = remove_uri_parameters(contact_number); if (p->socket.tcptls_session) { ao2_ref(p->socket.tcptls_session, -1); p->socket.tcptls_session = NULL; } else if (p->socket.ws_session) { ast_websocket_unref(p->socket.ws_session); p->socket.ws_session = NULL; } set_socket_transport(&p->socket, transport); if (set_call_forward && ast_test_flag(&p->flags[0], SIP_PROMISCREDIR)) { char *host = NULL; if (!strncasecmp(contact_number, "sip:", 4)) contact_number += 4; else if (!strncasecmp(contact_number, "sips:", 5)) contact_number += 5; separator = strchr(contact_number, '/'); if (separator) *separator = '\0'; if ((host = strchr(contact_number, '@'))) { *host++ = '\0'; ast_debug(2, "Found promiscuous redirection to 'SIP/%s::::%s@%s'\n", contact_number, sip_get_transport(transport), host); if (p->owner) ast_channel_call_forward_build(p->owner, "SIP/%s::::%s@%s", contact_number, sip_get_transport(transport), host); } else { ast_debug(2, "Found promiscuous redirection to 'SIP/::::%s@%s'\n", sip_get_transport(transport), contact_number); if (p->owner) ast_channel_call_forward_build(p->owner, "SIP/::::%s@%s", sip_get_transport(transport), contact_number); } } else { separator = strchr(contact, '@'); if (separator) { *separator++ = '\0'; domain = separator; } else { /* No username part */ domain = contact; } separator = strchr(contact, '/'); /* WHEN do we hae a forward slash in the URI? */ if (separator) *separator = '\0'; if (!strncasecmp(contact_number, "sip:", 4)) contact_number += 4; else if (!strncasecmp(contact_number, "sips:", 5)) contact_number += 5; separator = strchr(contact_number, ';'); /* And username ; parameters? */ if (separator) *separator = '\0'; ast_uri_decode(contact_number, ast_uri_sip_user); if (set_call_forward) { ast_debug(2, "Received 302 Redirect to extension '%s' (domain %s)\n", contact_number, domain); if (p->owner) { pbx_builtin_setvar_helper(p->owner, "SIPDOMAIN", domain); ast_channel_call_forward_set(p->owner, contact_number); } } } /* We've gotten the number for the contact, now get the name */ if (*contact == '\"') { contact_name = contact + 1; if (!(separator = (char *)find_closing_quote(contact_name, NULL))) { ast_log(LOG_NOTICE, "No closing quote on name in Contact header? %s\n", contact); } *separator = '\0'; } if (name && !ast_strlen_zero(contact_name)) { *name = ast_strdup(contact_name); } if (number) { *number = ast_strdup(contact_number); } } /*! \brief Check pending actions on SIP call * * \note both sip_pvt and sip_pvt's owner channel (if present) * must be locked for this function. */ static void check_pendings(struct sip_pvt *p) { if (ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { if (p->reinviteid > -1) { /* Outstanding p->reinviteid timeout, so wait... */ return; } else if (p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA) { /* if we can't BYE, then this is really a pending CANCEL */ p->invitestate = INV_CANCELLED; transmit_request(p, SIP_CANCEL, p->lastinvite, XMIT_RELIABLE, FALSE); /* If the cancel occurred on an initial invite, cancel the pending BYE */ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { ast_clear_flag(&p->flags[0], SIP_PENDINGBYE); } /* 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 (p->pendinginvite && !p->ongoing_reinvite) return; if (p->owner) { ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); } /* Perhaps there is an SD change INVITE outstanding */ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, TRUE); ast_clear_flag(&p->flags[0], SIP_PENDINGBYE); } sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } else if (ast_test_flag(&p->flags[0], SIP_NEEDREINVITE)) { /* if we can't REINVITE, hold it for later */ if (p->pendinginvite || p->invitestate == INV_CALLING || p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA || p->waitid > 0) { ast_debug(2, "NOT Sending pending reinvite (yet) on '%s'\n", p->callid); } else { ast_debug(2, "Sending pending reinvite on '%s'\n", p->callid); /* Didn't get to reinvite yet, so do it now */ transmit_reinvite_with_sdp(p, (p->t38.state == T38_LOCAL_REINVITE ? TRUE : FALSE), FALSE); ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE); } } } /*! \brief Reset the NEEDREINVITE flag after waiting when we get 491 on a Re-invite to avoid race conditions between asterisk servers. Called from the scheduler. */ static int sip_reinvite_retry(const void *data) { struct sip_pvt *p = (struct sip_pvt *) data; struct ast_channel *owner; sip_pvt_lock(p); /* called from schedule thread which requires a lock */ while ((owner = p->owner) && ast_channel_trylock(owner)) { sip_pvt_unlock(p); usleep(1); sip_pvt_lock(p); } ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); p->waitid = -1; check_pendings(p); sip_pvt_unlock(p); if (owner) { ast_channel_unlock(owner); } dialog_unref(p, "unref the dialog ptr from sip_reinvite_retry, because it held a dialog ptr"); return 0; } /*! * \brief Handle authentication challenge for SIP UPDATE * * This function is only called upon the receipt of a 401/407 response to an UPDATE. */ static void handle_response_update(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { if (p->options) { p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH); } if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, SIP_UPDATE, 1)) { ast_log(LOG_NOTICE, "Failed to authenticate on UPDATE to '%s'\n", sip_get_header(&p->initreq, "From")); } } static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry) { struct cc_epa_entry *cc_entry = epa_entry->instance_data; struct sip_monitor_instance *monitor_instance = ao2_callback(sip_monitor_instances, 0, find_sip_monitor_instance_by_suspension_entry, epa_entry); const char *min_expires; if (!monitor_instance) { ast_log(LOG_WARNING, "Can't find monitor_instance corresponding to epa_entry %p.\n", epa_entry); return; } if (resp != 423) { ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name, "Received error response to our PUBLISH"); ao2_ref(monitor_instance, -1); return; } /* Allrighty, the other end doesn't like our Expires value. They think it's * too small, so let's see if they've provided a more sensible value. If they * haven't, then we'll just double our Expires value and see if they like that * instead. * * XXX Ideally this logic could be placed into its own function so that SUBSCRIBE, * PUBLISH, and REGISTER could all benefit from the same shared code. */ min_expires = sip_get_header(req, "Min-Expires"); if (ast_strlen_zero(min_expires)) { pvt->expiry *= 2; if (pvt->expiry < 0) { /* You dork! You overflowed! */ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name, "PUBLISH expiry overflowed"); ao2_ref(monitor_instance, -1); return; } } else if (sscanf(min_expires, "%30d", &pvt->expiry) != 1) { ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name, "Min-Expires has non-numeric value"); ao2_ref(monitor_instance, -1); return; } /* At this point, we have most certainly changed pvt->expiry, so try transmitting * again */ transmit_invite(pvt, SIP_PUBLISH, FALSE, 0, NULL); ao2_ref(monitor_instance, -1); } static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { struct sip_epa_entry *epa_entry = p->epa_entry; const char *etag = sip_get_header(req, "Sip-ETag"); ast_assert(epa_entry != NULL); if (resp == 401 || resp == 407) { ast_string_field_set(p, theirtag, NULL); if (p->options) { p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH); } if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, SIP_PUBLISH, 0)) { ast_log(LOG_NOTICE, "Failed to authenticate on PUBLISH to '%s'\n", sip_get_header(&p->initreq, "From")); pvt_set_needdestroy(p, "Failed to authenticate on PUBLISH"); sip_alreadygone(p); } return; } if (resp == 501 || resp == 405) { mark_method_unallowed(&p->allowed_methods, SIP_PUBLISH); } if (resp == 200) { p->authtries = 0; /* If I've read section 6, item 6 of RFC 3903 correctly, * an ESC will only generate a new etag when it sends a 200 OK */ if (!ast_strlen_zero(etag)) { ast_copy_string(epa_entry->entity_tag, etag, sizeof(epa_entry->entity_tag)); } /* The nominal case. Everything went well. Everybody is happy. * Each EPA will have a specific action to take as a result of this * development, so ... callbacks! */ if (epa_entry->static_data->handle_ok) { epa_entry->static_data->handle_ok(p, req, epa_entry); } } else { /* Rather than try to make individual callbacks for each error * type, there is just a single error callback. The callback * can distinguish between error messages and do what it needs to */ if (epa_entry->static_data->handle_error) { epa_entry->static_data->handle_error(p, resp, req, epa_entry); } } } /*! * \internal * \brief Set hangup source and cause. * * \param p SIP private. * \param cause Hangup cause to queue. Zero if no cause. * * \pre p and p->owner are locked. * * \return Nothing */ static void sip_queue_hangup_cause(struct sip_pvt *p, int cause) { struct ast_channel *owner = p->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); sip_pvt_unlock(p); 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. */ owner = sip_pvt_lock_full(p); if (owner) { ast_channel_unref(owner); } } /*! \brief Handle SIP response to INVITE dialogue */ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { int outgoing = ast_test_flag(&p->flags[0], SIP_OUTGOING); int res = 0; int xmitres = 0; int reinvite = ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); char *p_hdrval; int rtn; struct ast_party_connected_line connected; struct ast_set_party_connected_line update_connected; if (reinvite) { ast_debug(4, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", p->callid); } else { ast_debug(4, "SIP response %d to standard invite\n", resp); } if (p->alreadygone) { /* This call is already gone */ ast_debug(1, "Got response on call that is already terminated: %s (ignoring)\n", p->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(sched, p->initid, dialog_unref(p, "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 && p->invitestate == INV_CALLING) { p->invitestate = INV_PROCEEDING; } /* Final response, not 200 ? */ if (resp >= 300 && (p->invitestate == INV_CALLING || p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA )) { p->invitestate = INV_COMPLETED; } if ((resp >= 200 && reinvite)) { p->ongoing_reinvite = 0; if (p->reinviteid > -1) { AST_SCHED_DEL_UNREF(sched, p->reinviteid, dialog_unref(p, "unref dialog for reinvite timeout because of a final response")); } } /* Final response, clear out pending invite */ if ((resp == 200 || resp >= 300) && p->pendinginvite && seqno == p->pendinginvite) { p->pendinginvite = 0; } /* If this is a response to our initial INVITE, we need to set what we can use * for this peer. */ if (!reinvite) { set_pvt_allowed_methods(p, req); } switch (resp) { case 100: /* Trying */ case 101: /* Dialog establishment */ if (!req->ignore && p->invitestate != INV_CANCELLED && sip_cancel_destroy(p)) { ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } check_pendings(p); break; case 180: /* 180 Ringing */ case 182: /* 182 Queued */ if (!req->ignore && p->invitestate != INV_CANCELLED && sip_cancel_destroy(p)) { ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } /* Store Route-set from provisional SIP responses so * early-dialog request can be routed properly * */ parse_ok_contact(p, req); if (!reinvite) { build_route(p, req, 1, resp); } if (!req->ignore && p->owner) { if (get_rpid(p, req)) { /* Queue a connected line update */ ast_party_connected_line_init(&connected); memset(&update_connected, 0, sizeof(update_connected)); update_connected.id.number = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) p->cid_num; connected.id.number.presentation = p->callingpres; update_connected.id.name = 1; connected.id.name.valid = 1; connected.id.name.str = (char *) p->cid_name; connected.id.name.presentation = p->callingpres; /* Invalidate any earlier private connected id representation */ ast_set_party_id_all(&update_connected.priv); connected.id.tag = (char *) p->cid_tag; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; ast_channel_queue_connected_line_update(p->owner, &connected, &update_connected); } sip_handle_cc(p, req, AST_CC_CCNR); ast_queue_control(p->owner, AST_CONTROL_RINGING); if (ast_channel_state(p->owner) != AST_STATE_UP) { ast_setstate(p->owner, AST_STATE_RINGING); } } if (find_sdp(req)) { if (p->invitestate != INV_CANCELLED) { p->invitestate = INV_EARLY_MEDIA; } res = process_sdp(p, req, SDP_T38_NONE); if (!req->ignore && p->owner) { /* Queue a progress frame only if we have SDP in 180 or 182 */ ast_queue_control(p->owner, AST_CONTROL_PROGRESS); /* We have not sent progress, but we have been sent progress so enable early media */ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } ast_rtp_instance_activate(p->rtp); } check_pendings(p); break; case 181: /* Call Is Being Forwarded */ if (!req->ignore && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); /* Store Route-set from provisional SIP responses so * early-dialog request can be routed properly * */ parse_ok_contact(p, req); if (!reinvite) { build_route(p, req, 1, resp); } if (!req->ignore && p->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)); change_redirecting_information(p, 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(p->owner, &redirecting, &update_redirecting); ast_party_redirecting_free(&redirecting); sip_handle_cc(p, req, AST_CC_CCNR); } check_pendings(p); break; case 183: /* Session progress */ if (!req->ignore && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p)) { ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } /* Store Route-set from provisional SIP responses so * early-dialog request can be routed properly * */ parse_ok_contact(p, req); if (!reinvite) { build_route(p, req, 1, resp); } if (!req->ignore && p->owner) { if (get_rpid(p, req)) { /* Queue a connected line update */ ast_party_connected_line_init(&connected); memset(&update_connected, 0, sizeof(update_connected)); update_connected.id.number = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) p->cid_num; connected.id.number.presentation = p->callingpres; update_connected.id.name = 1; connected.id.name.valid = 1; connected.id.name.str = (char *) p->cid_name; connected.id.name.presentation = p->callingpres; /* Invalidate any earlier private connected id representation */ ast_set_party_id_all(&update_connected.priv); connected.id.tag = (char *) p->cid_tag; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; ast_channel_queue_connected_line_update(p->owner, &connected, &update_connected); } sip_handle_cc(p, req, AST_CC_CCNR); } if (find_sdp(req)) { if (p->invitestate != INV_CANCELLED) { p->invitestate = INV_EARLY_MEDIA; } res = process_sdp(p, req, SDP_T38_NONE); if (!req->ignore && p->owner) { /* Queue a progress frame */ ast_queue_control(p->owner, AST_CONTROL_PROGRESS); /* We have not sent progress, but we have been sent progress so enable early media */ ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT); } ast_rtp_instance_activate(p->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 && p->owner) { ast_queue_control(p->owner, AST_CONTROL_RINGING); } } check_pendings(p); break; case 200: /* 200 OK on invite - someone's answering our call */ if (!req->ignore && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p)) { ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } p->authtries = 0; if (find_sdp(req)) { if ((res = process_sdp(p, req, SDP_T38_ACCEPT)) && !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(&p->flags[0], SIP_PENDINGBYE); p->hangupcause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; if (p->owner) { ast_channel_hangupcause_set(p->owner, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); sip_queue_hangup_cause(p, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); } } } ast_rtp_instance_activate(p->rtp); } else if (!reinvite) { struct ast_sockaddr remote_address = {{0,}}; ast_rtp_instance_get_remote_address(p->rtp, &remote_address); if (ast_sockaddr_isnull(&remote_address) || (!ast_strlen_zero(p->theirprovtag) && strcmp(p->theirtag, p->theirprovtag))) { ast_log(LOG_WARNING, "Received response: \"200 OK\" from '%s' without SDP\n", p->relatedpeer->name); ast_set_flag(&p->flags[0], SIP_PENDINGBYE); ast_rtp_instance_activate(p->rtp); } } if (!req->ignore && p->owner) { int rpid_changed; rpid_changed = get_rpid(p, req); if (rpid_changed || !reinvite) { /* Queue a connected line update */ ast_party_connected_line_init(&connected); memset(&update_connected, 0, sizeof(update_connected)); if (rpid_changed || !ast_strlen_zero(p->cid_num) || (p->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { update_connected.id.number = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) p->cid_num; connected.id.number.presentation = p->callingpres; } if (rpid_changed || !ast_strlen_zero(p->cid_name) || (p->callingpres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { update_connected.id.name = 1; connected.id.name.valid = 1; connected.id.name.str = (char *) p->cid_name; connected.id.name.presentation = p->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 *) p->cid_tag; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; ast_channel_queue_connected_line_update(p->owner, &connected, &update_connected); } } } /* 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) { update_call_counter(p, DEC_CALL_RINGING); parse_ok_contact(p, req); /* Save Record-Route for any later requests we make on this dialogue */ if (!reinvite) { build_route(p, req, 1, resp); } if(set_address_from_contact(p)) { /* Bad contact - we don't know how to reach this device */ /* We need to ACK, but then send a bye */ if (sip_route_empty(&p->route) && !req->ignore) { ast_set_flag(&p->flags[0], SIP_PENDINGBYE); } } } if (!req->ignore && p->owner) { if (!reinvite && !res) { ast_queue_control(p->owner, AST_CONTROL_ANSWER); } else { /* RE-invite */ if (p->t38.state == T38_DISABLED || p->t38.state == T38_REJECTED) { ast_queue_control(p->owner, AST_CONTROL_UPDATE_RTP_PEER); } else { ast_queue_frame(p->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(&p->flags[0], SIP_PENDINGBYE); } } /* Check for Session-Timers related headers */ if (st_get_mode(p, 0) != SESSION_TIMER_MODE_REFUSE) { p_hdrval = (char*)sip_get_header(req, "Session-Expires"); if (!ast_strlen_zero(p_hdrval)) { /* UAS supports Session-Timers */ enum st_refresher_param st_ref_param; int tmp_st_interval = 0; rtn = parse_session_expires(p_hdrval, &tmp_st_interval, &st_ref_param); if (rtn != 0) { ast_set_flag(&p->flags[0], SIP_PENDINGBYE); } else if (tmp_st_interval < st_get_se(p, FALSE)) { ast_log(LOG_WARNING, "Got Session-Expires less than local Min-SE in 200 OK, tearing down call\n"); ast_set_flag(&p->flags[0], SIP_PENDINGBYE); } if (st_ref_param == SESSION_TIMER_REFRESHER_PARAM_UAC) { p->stimer->st_ref = SESSION_TIMER_REFRESHER_US; } else if (st_ref_param == SESSION_TIMER_REFRESHER_PARAM_UAS) { p->stimer->st_ref = SESSION_TIMER_REFRESHER_THEM; } else { ast_log(LOG_WARNING, "Unknown refresher on %s\n", p->callid); } if (tmp_st_interval) { p->stimer->st_interval = tmp_st_interval; } p->stimer->st_active = TRUE; p->stimer->st_active_peer_ua = TRUE; start_session_timer(p); } else { /* UAS doesn't support Session-Timers */ if (st_get_mode(p, 0) == SESSION_TIMER_MODE_ORIGINATE) { p->stimer->st_ref = SESSION_TIMER_REFRESHER_US; p->stimer->st_active_peer_ua = FALSE; start_session_timer(p); } } } /* If I understand this right, the branch is different for a non-200 ACK only */ p->invitestate = INV_TERMINATED; ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE); check_pendings(p); break; case 407: /* Proxy authentication */ case 401: /* Www auth */ /* First we ACK */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->options) { p->options->auth_type = resp; } /* Then we AUTH */ ast_string_field_set(p, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */ if (!req->ignore) { if (p->authtries < MAX_AUTHTRIES) { p->invitestate = INV_CALLING; } if (p->authtries == MAX_AUTHTRIES || do_proxy_auth(p, req, resp, SIP_INVITE, 1)) { ast_log(LOG_NOTICE, "Failed to authenticate on INVITE to '%s'\n", sip_get_header(&p->initreq, "From")); pvt_set_needdestroy(p, "failed to authenticate on INVITE"); sip_alreadygone(p); if (p->owner) { ast_queue_control(p->owner, AST_CONTROL_CONGESTION); } } } break; case 403: /* Forbidden */ /* First we ACK */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); ast_log(LOG_WARNING, "Received response: \"Forbidden\" from '%s'\n", sip_get_header(&p->initreq, "From")); if (!req->ignore && p->owner) { sip_queue_hangup_cause(p, hangup_sip2cause(resp)); } break; case 414: /* Bad request URI */ case 493: /* Undecipherable */ case 404: /* Not found */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->owner && !req->ignore) { sip_queue_hangup_cause(p, hangup_sip2cause(resp)); } break; case 481: /* Call leg does not exist */ /* Could be REFER caused INVITE with replaces */ ast_log(LOG_WARNING, "Re-invite to non-existing call leg on other UA. SIP dialog '%s'. Giving up.\n", p->callid); xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->owner) { ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(resp)); } break; case 422: /* Session-Timers: Session interval too small */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); ast_string_field_set(p, theirtag, NULL); proc_422_rsp(p, req); break; case 428: /* Use identity header - rfc 4474 - not supported by Asterisk yet */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); append_history(p, "Identity", "SIP identity is required. Not supported by Asterisk."); ast_log(LOG_WARNING, "SIP identity required by proxy. SIP dialog '%s'. Giving up.\n", p->callid); if (p->owner && !req->ignore) { ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(resp)); } 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. */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); append_history(p, "TempUnavailable", "Endpoint is temporarily unavailable."); if (p->owner && !req->ignore) { struct ast_party_redirecting redirecting; struct ast_set_party_redirecting update_redirecting; ast_party_redirecting_set_init(&redirecting, ast_channel_redirecting(p->owner)); memset(&update_redirecting, 0, sizeof(update_redirecting)); redirecting.reason.code = sip_reason_str_to_code(rest); redirecting.reason.str = ast_strdup(rest); ast_channel_queue_redirecting_update(p->owner, &redirecting, &update_redirecting); ast_party_redirecting_free(&redirecting); ast_queue_control(p->owner, AST_CONTROL_BUSY); } break; case 487: /* Cancelled transaction */ /* We have sent CANCEL on an outbound INVITE This transaction is already scheduled to be killed by sip_hangup(). */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->owner && !req->ignore) { ast_queue_hangup_with_cause(p->owner, AST_CAUSE_NORMAL_CLEARING); append_history(p, "Hangup", "Got 487 on CANCEL request from us. Queued AST hangup request"); } else if (!req->ignore) { update_call_counter(p, DEC_CALL_LIMIT); append_history(p, "Hangup", "Got 487 on CANCEL request from us on call without owner. Killing this dialog."); } check_pendings(p); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); break; case 415: /* Unsupported media type */ case 488: /* Not acceptable here */ case 606: /* Not Acceptable */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->udptl && p->t38.state == T38_LOCAL_REINVITE) { change_t38_state(p, T38_REJECTED); /* Try to reset RTP timers */ /* XXX Why is this commented away??? */ //ast_rtp_set_rtptimers_onhold(p->rtp); /* Trigger a reinvite back to audio */ transmit_reinvite_with_sdp(p, FALSE, FALSE); } else { /* We can't set up this call, so give up */ if (p->owner && !req->ignore) { ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(resp)); } } break; case 491: /* Pending */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->owner && !req->ignore) { if (ast_channel_state(p->owner) != AST_STATE_UP) { ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(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 (p->outgoing_call) { wait = 2100 + ast_random() % 2000; } else { wait = ast_random() % 2000; } p->waitid = ast_sched_add(sched, wait, sip_reinvite_retry, dialog_ref(p, "passing dialog ptr into sched structure based on waitid for sip_reinvite_retry.")); ast_debug(2, "Reinvite race. Scheduled sip_reinvite_retry in %d secs in handle_response_invite (waitid %d, dialog '%s')\n", wait, p->waitid, p->callid); } } break; case 408: /* Request timeout */ case 405: /* Not allowed */ case 501: /* Not implemented */ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); if (p->owner) { ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(resp)); } break; } if (xmitres == XMIT_ERROR) { ast_log(LOG_WARNING, "Could not transmit message in dialog %s\n", p->callid); } } /* \brief Handle SIP response in NOTIFY transaction We've sent a NOTIFY, now handle responses to it */ static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { switch (resp) { case 200: /* Notify accepted */ /* They got the notify, this is the end */ if (p->owner) { if (p->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(p->owner)); } } else { if (p->subscribed == NONE && !p->refer) { ast_debug(4, "Got 200 accepted on NOTIFY %s\n", p->callid); pvt_set_needdestroy(p, "received 200 response"); } if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) { struct state_notify_data data = { .state = p->laststate, .device_state_info = p->last_device_state_info, .presence_state = p->last_presence_state, .presence_subtype = p->last_presence_subtype, .presence_message = p->last_presence_message, }; /* Ready to send the next state we have on queue */ ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE); extensionstate_update(p->context, p->exten, &data, p, TRUE); } } break; case 401: /* Not www-authorized on SIP method */ case 407: /* Proxy auth */ if (!p->notify) { break; /* Only device notify can use NOTIFY auth */ } ast_string_field_set(p, theirtag, NULL); if (ast_strlen_zero(p->authname)) { ast_log(LOG_WARNING, "Asked to authenticate NOTIFY to %s but we have no matching peer or realm auth!\n", ast_sockaddr_stringify(&p->recv)); pvt_set_needdestroy(p, "unable to authenticate NOTIFY"); } if (p->authtries > 1 || do_proxy_auth(p, req, resp, SIP_NOTIFY, 0)) { ast_log(LOG_NOTICE, "Failed to authenticate on NOTIFY to '%s'\n", sip_get_header(&p->initreq, "From")); pvt_set_needdestroy(p, "failed to authenticate NOTIFY"); } break; case 481: /* Call leg does not exist */ pvt_set_needdestroy(p, "Received 481 response for NOTIFY"); break; } } /* \brief Handle SIP response in SUBSCRIBE transaction */ static void handle_response_subscribe(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { if (p->subscribed == CALL_COMPLETION) { struct sip_monitor_instance *monitor_instance; if (resp < 300) { return; } /* Final failure response received. */ monitor_instance = ao2_callback(sip_monitor_instances, 0, find_sip_monitor_instance_by_subscription_pvt, p); if (monitor_instance) { ast_cc_monitor_failed(monitor_instance->core_id, monitor_instance->device_name, "Received error response to our SUBSCRIBE"); } return; } if (p->subscribed != MWI_NOTIFICATION) { return; } if (!p->mwi) { return; } switch (resp) { case 200: /* Subscription accepted */ ast_debug(3, "Got 200 OK on subscription for MWI\n"); set_pvt_allowed_methods(p, req); if (p->options) { if (p->options->outboundproxy) { ao2_ref(p->options->outboundproxy, -1); } ast_free(p->options); p->options = NULL; } p->mwi->subscribed = 1; if ((p->mwi->resub = ast_sched_add(sched, mwi_expiry * 1000, sip_subscribe_mwi_do, ao2_t_bump(p->mwi, "mwi ast_sched_add"))) < 0) { ao2_t_ref(p->mwi, -1, "mwi ast_sched_add < 0"); } break; case 401: case 407: ast_string_field_set(p, theirtag, NULL); if (p->authtries > 1 || do_proxy_auth(p, req, resp, SIP_SUBSCRIBE, 0)) { ast_log(LOG_NOTICE, "Failed to authenticate on SUBSCRIBE to '%s'\n", sip_get_header(&p->initreq, "From")); p->mwi->call = NULL; ao2_t_ref(p->mwi, -1, "failed to authenticate SUBSCRIBE"); pvt_set_needdestroy(p, "failed to authenticate SUBSCRIBE"); } break; case 403: transmit_response_with_date(p, "200 OK", req); ast_log(LOG_WARNING, "Authentication failed while trying to subscribe for MWI.\n"); p->mwi->call = NULL; ao2_t_ref(p->mwi, -1, "received 403 response"); pvt_set_needdestroy(p, "received 403 response"); sip_alreadygone(p); break; case 404: ast_log(LOG_WARNING, "Subscription failed for MWI. The remote side said that a mailbox may not have been configured.\n"); p->mwi->call = NULL; ao2_t_ref(p->mwi, -1, "received 404 response"); pvt_set_needdestroy(p, "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"); p->mwi->call = NULL; ao2_t_ref(p->mwi, -1, "received 481 response"); pvt_set_needdestroy(p, "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"); p->mwi->call = NULL; ao2_t_ref(p->mwi, -1, "received 500/501 response"); pvt_set_needdestroy(p, "received serious error (500/501/493/414/400) response"); break; } } /* \brief Handle SIP response in REFER transaction We've sent a REFER, now handle responses to it */ static void handle_response_refer(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { enum ast_control_transfer message = AST_TRANSFER_FAILED; /* If no refer structure exists, then do nothing */ if (!p->refer) return; switch (resp) { case 202: /* Transfer accepted */ /* We need to do something here */ /* The transferee is now sending INVITE to target */ p->refer->status = REFER_ACCEPTED; /* Now wait for next message */ 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(p->authname)) { ast_log(LOG_WARNING, "Asked to authenticate REFER to %s but we have no matching peer or realm auth!\n", ast_sockaddr_stringify(&p->recv)); if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } pvt_set_needdestroy(p, "unable to authenticate REFER"); } if (p->authtries > 1 || do_proxy_auth(p, req, resp, SIP_REFER, 0)) { ast_log(LOG_NOTICE, "Failed to authenticate on REFER to '%s'\n", sip_get_header(&p->initreq, "From")); p->refer->status = REFER_NOAUTH; if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } pvt_set_needdestroy(p, "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", p->refer->refer_to); pvt_set_needdestroy(p, "received 405 response"); p->refer->status = REFER_FAILED; if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } break; case 481: /* Call leg does not exist */ /* A transfer with Replaces did not work */ /* OEJ: We should Set flag, cancel the REFER, go back to original call - but right now we can't */ ast_log(LOG_WARNING, "Remote host can't match REFER request to call '%s'. Giving up.\n", p->callid); if (p->owner) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); pvt_set_needdestroy(p, "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", p->refer->refer_to); pvt_set_needdestroy(p, "received 500/501 response"); p->refer->status = REFER_FAILED; if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } break; case 603: /* Transfer declined */ ast_log(LOG_NOTICE, "SIP transfer to %s declined, call miserably fails. \n", p->refer->refer_to); p->refer->status = REFER_FAILED; pvt_set_needdestroy(p, "received 603 response"); if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } 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 unxpected 2xx response (%d), confusion is possible. \n", p->refer->refer_to, resp); } else { ast_log(LOG_WARNING, "SIP transfer to %s with response (%d). \n", p->refer->refer_to, resp); } p->refer->status = REFER_FAILED; pvt_set_needdestroy(p, "received failure response"); if (p->owner) { ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } break; } } /*! \brief Handle responses on REGISTER to services */ static int handle_response_register(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { int expires, expires_ms; struct sip_registry *r; r = p->registry; switch (resp) { case 401: /* Unauthorized */ if (p->authtries == MAX_AUTHTRIES || do_register_auth(p, req, resp)) { ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s@%s' (Tries %d)\n", p->registry->username, p->registry->hostname, p->authtries); pvt_set_needdestroy(p, "failed to authenticate REGISTER"); } break; case 403: /* Forbidden */ if (global_reg_retry_403) { ast_log(LOG_NOTICE, "Treating 403 response to REGISTER as non-fatal for %s@%s\n", p->registry->username, p->registry->hostname); ast_string_field_set(r, nonce, ""); ast_string_field_set(p, nonce, ""); break; } ast_log(LOG_WARNING, "Forbidden - wrong password on authentication for REGISTER for '%s' to '%s'\n", p->registry->username, p->registry->hostname); AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 403")); r->regstate = REG_STATE_NOAUTH; sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate)); pvt_set_needdestroy(p, "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", p->registry->username, p->registry->hostname); pvt_set_needdestroy(p, "received 404 response"); if (r->call) r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 404"); r->regstate = REG_STATE_REJECTED; sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate)); AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 404")); break; case 407: /* Proxy auth */ if (p->authtries == MAX_AUTHTRIES || do_register_auth(p, req, resp)) { ast_log(LOG_NOTICE, "Failed to authenticate on REGISTER to '%s' (tries '%d')\n", sip_get_header(&p->initreq, "From"), p->authtries); pvt_set_needdestroy(p, "failed to authenticate REGISTER"); } break; case 408: /* Request timeout */ /* Got a timeout response, so reset the counter of failed responses */ if (r) { r->regattempts = 0; } else { ast_log(LOG_WARNING, "Got a 408 response to our REGISTER on call %s after we had destroyed the registry object\n", p->callid); } break; case 423: /* Interval too brief */ r->expiry = atoi(sip_get_header(req, "Min-Expires")); ast_log(LOG_WARNING, "Got 423 Interval too brief for service %s@%s, minimum is %d seconds\n", p->registry->username, p->registry->hostname, r->expiry); AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 423")); if (r->call) { r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 423"); pvt_set_needdestroy(p, "received 423 response"); } if (r->expiry > max_expiry) { ast_log(LOG_WARNING, "Required expiration time from %s@%s is too high, giving up\n", p->registry->username, p->registry->hostname); r->expiry = r->configured_expiry; r->regstate = REG_STATE_REJECTED; } else { r->regstate = REG_STATE_UNREGISTERED; transmit_register(r, SIP_REGISTER, NULL, NULL); } sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate)); 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, p->registry->username, p->registry->hostname); pvt_set_needdestroy(p, "received 4xx response"); if (r->call) r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 4xx"); r->regstate = REG_STATE_REJECTED; sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate)); AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 479")); break; case 200: /* 200 OK */ if (!r) { 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(p->peername, p->username)); pvt_set_needdestroy(p, "received erroneous 200 response"); return 0; } r->regstate = REG_STATE_REGISTERED; r->regtime = ast_tvnow(); /* Reset time of last successful registration */ sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate)); r->regattempts = 0; ast_debug(1, "Registration successful\n"); if (r->timeout > -1) { ast_debug(1, "Cancelling timeout %d\n", r->timeout); } AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 200")); if (r->call) r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 200"); ao2_t_replace(p->registry, NULL, "unref registry entry p->registry"); /* destroy dialog now to avoid interference with next register */ pvt_set_needdestroy(p, "Registration successfull"); /* set us up for re-registering * figure out how long we got registered for * according to section 6.13 of RFC, contact headers override * expires headers, so check those first */ expires = 0; /* XXX todo: try to save the extra call */ if (!ast_strlen_zero(sip_get_header(req, "Contact"))) { const char *contact = NULL; const char *tmptmp = NULL; int start = 0; for(;;) { contact = __get_header(req, "Contact", &start); /* this loop ensures we get a contact header about our register request */ if(!ast_strlen_zero(contact)) { if( (tmptmp=strstr(contact, p->our_contact))) { contact=tmptmp; break; } } else break; } tmptmp = strcasestr(contact, "expires="); if (tmptmp) { if (sscanf(tmptmp + 8, "%30d", &expires) != 1) { expires = 0; } } } if (!expires) expires=atoi(sip_get_header(req, "expires")); if (!expires) expires=default_expiry; expires_ms = expires * 1000; if (expires <= EXPIRY_GUARD_LIMIT) expires_ms -= MAX((expires_ms * EXPIRY_GUARD_PCT), EXPIRY_GUARD_MIN); else expires_ms -= EXPIRY_GUARD_SECS * 1000; if (sipdebug) ast_log(LOG_NOTICE, "Outbound Registration: Expiry for %s is %d sec (Scheduling reregistration in %d s)\n", r->hostname, expires, expires_ms/1000); r->refresh= (int) expires_ms / 1000; /* Schedule re-registration before we expire */ AST_SCHED_REPLACE_UNREF(r->expire, sched, expires_ms, sip_reregister, r, ao2_t_ref(_data, -1, "unref in REPLACE del fail"), ao2_t_ref(r, -1, "unref in REPLACE add fail"), ao2_t_ref(r, +1, "The Addition side of REPLACE")); } return 1; } /*! \brief Handle qualification responses (OPTIONS) */ static void handle_response_peerpoke(struct sip_pvt *p, int resp, struct sip_request *req) { struct sip_peer *peer = /* sip_ref_peer( */ p->relatedpeer /* , "bump refcount on p, as it is being used in this function(handle_response_peerpoke)")*/ ; /* hope this is already refcounted! */ int statechanged, is_reachable, was_reachable; int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps); /* * 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 (pingtime < 1) { /* zero = unknown, so round up to 1 */ pingtime = 1; } if (!peer->maxms) { /* this should never happens */ pvt_set_needdestroy(p, "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 = peer->lastms > 0 && peer->lastms <= peer->maxms; is_reachable = pingtime <= peer->maxms; statechanged = peer->lastms == 0 /* yes, unknown before */ || was_reachable != is_reachable; peer->lastms = pingtime; peer->call = dialog_unref(peer->call, "unref dialog peer->call"); if (statechanged) { const char *s = is_reachable ? "Reachable" : "Lagged"; char str_lastms[20]; snprintf(str_lastms, sizeof(str_lastms), "%d", pingtime); ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", peer->name, s, pingtime, peer->maxms); ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); if (sip_cfg.peer_rtupdate) { ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", str_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", s, "time", pingtime); ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); } if (is_reachable && sip_cfg.regextenonqualify) { register_peer_exten(peer, TRUE); } } pvt_set_needdestroy(p, "got OPTIONS response"); /* Try again eventually */ AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, is_reachable ? peer->qualifyfreq : DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer, sip_unref_peer(_data, "removing poke peer ref"), sip_unref_peer(peer, "removing poke peer ref"), sip_ref_peer(peer, "adding poke peer ref")); } /*! * \internal * \brief Handle responses to INFO messages * * \note The INFO method MUST NOT change the state of calls or * related sessions (RFC 2976). */ static void handle_response_info(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { int sipmethod = SIP_INFO; 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(&p->sa), resp, sip_methods[sipmethod].text); break; case 405: /* Method not allowed */ case 501: /* Not Implemented */ mark_method_unallowed(&p->allowed_methods, sipmethod); if (p->relatedpeer) { mark_method_allowed(&p->relatedpeer->disallowed_methods, sipmethod); } ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_sockaddr_stringify(&p->sa), sip_methods[sipmethod].text); break; default: if (300 <= resp && resp < 700) { ast_verb(3, "Got SIP %s response %d \"%s\" back from host '%s'\n", sip_methods[sipmethod].text, resp, rest, ast_sockaddr_stringify(&p->sa)); } break; } } /*! * \internal * \brief Handle auth requests to a MESSAGE request * \return TRUE if authentication failed. */ static int do_message_auth(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { char *header; char *respheader; char digest[1024]; if (p->options) { p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH); } if (p->authtries == MAX_AUTHTRIES) { ast_log(LOG_NOTICE, "Failed to authenticate MESSAGE with host '%s'\n", ast_sockaddr_stringify(&p->sa)); return -1; } ++p->authtries; sip_auth_headers((resp == 401 ? WWW_AUTH : PROXY_AUTH), &header, &respheader); memset(digest, 0, sizeof(digest)); if (reply_digest(p, req, header, 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 (p->do_history) { append_history(p, "MessageAuth", "Try: %d", p->authtries); } transmit_message(p, 0, 1); return 0; } /*! * \internal * \brief Handle responses to MESSAGE messages * * \note 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 handle_response_message(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { int sipmethod = SIP_MESSAGE; int in_dialog = ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); switch (resp) { case 401: /* Not www-authorized on SIP method */ case 407: /* Proxy auth required */ if (do_message_auth(p, resp, rest, req, seqno) && !in_dialog) { pvt_set_needdestroy(p, "MESSAGE authentication failed"); } break; case 405: /* Method not allowed */ case 501: /* Not Implemented */ mark_method_unallowed(&p->allowed_methods, sipmethod); if (p->relatedpeer) { mark_method_allowed(&p->relatedpeer->disallowed_methods, sipmethod); } ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_sockaddr_stringify(&p->sa), sip_methods[sipmethod].text); if (!in_dialog) { pvt_set_needdestroy(p, "MESSAGE not implemented or allowed"); } break; default: if (100 <= resp && resp < 200) { /* Must allow provisional responses for out-of-dialog requests. */ } else if (200 <= resp && resp < 300) { p->authtries = 0; /* Reset authentication counter */ if (!in_dialog) { pvt_set_needdestroy(p, "MESSAGE delivery accepted"); } } else if (300 <= resp && resp < 700) { ast_verb(3, "Got SIP %s response %d \"%s\" back from host '%s'\n", sip_methods[sipmethod].text, resp, rest, ast_sockaddr_stringify(&p->sa)); if (!in_dialog) { pvt_set_needdestroy(p, (300 <= resp && resp < 600) ? "MESSAGE delivery failed" : "MESSAGE delivery refused"); } } break; } } /*! \brief Immediately stop RTP, VRTP and UDPTL as applicable */ static void stop_media_flows(struct sip_pvt *p) { /* Immediately stop RTP, VRTP and UDPTL as applicable */ if (p->rtp) ast_rtp_instance_stop(p->rtp); if (p->vrtp) ast_rtp_instance_stop(p->vrtp); if (p->trtp) ast_rtp_instance_stop(p->trtp); if (p->udptl) ast_udptl_stop(p->udptl); } /*! \brief Handle SIP response in dialogue \note only called by handle_incoming */ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno) { struct ast_channel *owner; int sipmethod; const char *c = sip_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 */ char *c_copy = ast_strdupa(c); /* Skip the Cseq and its subsequent spaces */ const char *msg = ast_skip_blanks(ast_skip_nonblanks(c_copy)); if (!msg) msg = ""; sipmethod = find_sip_method(msg); owner = p->owner; if (owner) { const char *rp = NULL, *rh = NULL; ast_channel_hangupcause_set(owner, 0); if (ast_test_flag(&p->flags[1], SIP_PAGE2_Q850_REASON) && (rh = sip_get_header(req, "Reason"))) { rh = ast_skip_blanks(rh); if (!strncasecmp(rh, "Q.850", 5)) { int cause = ast_channel_hangupcause(owner); rp = strstr(rh, "cause="); if (rp && sscanf(rp + 6, "%30d", &cause) == 1) { ast_channel_hangupcause_set(owner, cause & 0x7f); if (req->debug) ast_verbose("Using Reason header for cause code: %d\n", ast_channel_hangupcause(owner)); } } } if (!ast_channel_hangupcause(owner)) ast_channel_hangupcause_set(owner, hangup_sip2cause(resp)); } if (p->socket.type == AST_TRANSPORT_UDP) { int ack_res = FALSE; /* 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 (sipmethod == SIP_INVITE) { ack_res = __sip_semi_ack(p, seqno, 0, sipmethod); } } else { ack_res = __sip_ack(p, seqno, 0, sipmethod); } 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 (sipmethod == SIP_INVITE && resp >= 200) { transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, resp < 300 ? TRUE: FALSE); } append_history(p, "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 (!p->owner && sipmethod == SIP_NOTIFY && p->pendinginvite) { p->pendinginvite = 0; } /* Get their tag if we haven't already */ if (ast_strlen_zero(p->theirtag) || (resp >= 200)) { char tag[128]; gettag(req, "To", tag, sizeof(tag)); ast_string_field_set(p, theirtag, tag); } else { /* Store theirtag to track for changes when 200 responses to invites are received without SDP */ ast_string_field_set(p, theirprovtag, p->theirtag); } /* This needs to be configurable on a channel/peer level, not mandatory for all communication. Sadly enough, NAT implementations are not so stable so we can always rely on these headers. Temporarily disabled, while waiting for fix. Fix assigned to Rizzo :-) */ /* check_via_response(p, req); */ /* 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) && sipmethod == SIP_BYE) { pvt_set_needdestroy(p, "received 4XX response to a BYE"); return; } if (p->relatedpeer && sipmethod == 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) handle_response_peerpoke(p, resp, req); } else if (sipmethod == SIP_REFER && resp >= 200) { handle_response_refer(p, resp, rest, req, seqno); } else if (sipmethod == SIP_PUBLISH) { /* SIP PUBLISH transcends this morass of doodoo and instead * we just always call the response handler. Good gravy! */ handle_response_publish(p, resp, rest, req, seqno); } else if (sipmethod == SIP_INFO) { /* More good gravy! */ handle_response_info(p, resp, rest, req, seqno); } else if (sipmethod == SIP_MESSAGE) { /* More good gravy! */ handle_response_message(p, resp, rest, req, seqno); } else if (sipmethod == SIP_NOTIFY) { /* The gravy train continues to roll */ handle_response_notify(p, resp, rest, req, seqno); } else if (ast_test_flag(&p->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 (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); break; case 200: /* 200 OK */ p->authtries = 0; /* Reset authentication counter */ if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, req, seqno); } else if (sipmethod == SIP_REGISTER) { handle_response_register(p, resp, rest, req, seqno); } else if (sipmethod == SIP_SUBSCRIBE) { ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); handle_response_subscribe(p, resp, rest, req, seqno); } else if (sipmethod == SIP_BYE) { /* Ok, we're ready to go */ pvt_set_needdestroy(p, "received 200 response"); ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); } break; case 401: /* Not www-authorized on SIP method */ case 407: /* Proxy auth required */ if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else if (sipmethod == SIP_SUBSCRIBE) handle_response_subscribe(p, resp, rest, req, seqno); else if (p->registry && sipmethod == SIP_REGISTER) handle_response_register(p, resp, rest, req, seqno); else if (sipmethod == SIP_UPDATE) { handle_response_update(p, resp, rest, req, seqno); } else if (sipmethod == SIP_BYE) { if (p->options) p->options->auth_type = resp; if (ast_strlen_zero(p->authname)) { ast_log(LOG_WARNING, "Asked to authenticate %s, to %s but we have no matching peer!\n", msg, ast_sockaddr_stringify(&p->recv)); pvt_set_needdestroy(p, "unable to authenticate BYE"); } else if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, sipmethod, 0)) { ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, sip_get_header(&p->initreq, "From")); pvt_set_needdestroy(p, "failed to authenticate BYE"); } } else { ast_log(LOG_WARNING, "Got authentication request (%d) on %s to '%s'\n", resp, sip_methods[sipmethod].text, sip_get_header(req, "To")); pvt_set_needdestroy(p, "received 407 response"); } break; case 403: /* Forbidden - we failed authentication */ if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else if (sipmethod == SIP_SUBSCRIBE) handle_response_subscribe(p, resp, rest, req, seqno); else if (p->registry && sipmethod == SIP_REGISTER) handle_response_register(p, resp, rest, req, seqno); else { ast_log(LOG_WARNING, "Forbidden - maybe wrong password on authentication for %s\n", msg); pvt_set_needdestroy(p, "received 403 response"); } break; case 400: /* Bad Request */ case 414: /* Request URI too long */ case 493: /* Undecipherable */ case 404: /* Not found */ if (p->registry && sipmethod == SIP_REGISTER) handle_response_register(p, resp, rest, req, seqno); else if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else if (sipmethod == SIP_SUBSCRIBE) handle_response_subscribe(p, resp, rest, req, seqno); else if (owner) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); break; case 423: /* Interval too brief */ if (sipmethod == SIP_REGISTER) handle_response_register(p, resp, rest, req, seqno); break; case 408: /* Request timeout - terminate dialog */ if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else if (sipmethod == SIP_REGISTER) handle_response_register(p, resp, rest, req, seqno); else if (sipmethod == SIP_BYE) { pvt_set_needdestroy(p, "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(p->owner, AST_CONTROL_CONGESTION); pvt_set_needdestroy(p, "received 408 response"); } break; case 428: case 422: /* Session-Timers: Session Interval Too Small */ if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, req, seqno); } break; case 480: if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, req, seqno); } else if (sipmethod == SIP_SUBSCRIBE) { handle_response_subscribe(p, resp, rest, req, seqno); } else if (owner) { /* No specific handler. Default to congestion */ ast_queue_control(p->owner, AST_CONTROL_CONGESTION); } break; case 481: /* Call leg does not exist */ if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, req, seqno); } else if (sipmethod == SIP_SUBSCRIBE) { handle_response_subscribe(p, resp, rest, req, seqno); } else if (sipmethod == 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[sipmethod].text, p->callid); } else if (sipmethod == 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[sipmethod].text, p->callid); } else { ast_log(LOG_WARNING, "Remote host can't match request %s to call '%s'. Giving up.\n", sip_methods[sipmethod].text, p->callid); /* Guessing that this is not an important request */ } break; case 487: if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); break; case 415: /* Unsupported media type */ case 488: /* Not acceptable here - codec error */ case 606: /* Not Acceptable */ if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); break; case 491: /* Pending */ if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else { ast_debug(1, "Got 491 on %s, unsupported. Call ID %s\n", sip_methods[sipmethod].text, p->callid); pvt_set_needdestroy(p, "received 491 response"); } break; case 405: /* Method not allowed */ case 501: /* Not Implemented */ mark_method_unallowed(&p->allowed_methods, sipmethod); if (p->relatedpeer) { mark_method_allowed(&p->relatedpeer->disallowed_methods, sipmethod); } if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_sockaddr_stringify(&p->sa), msg); break; default: if ((resp >= 200) && (resp < 300)) { /* on any 2XX response do the following */ if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, 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, rest, ast_sockaddr_stringify(&p->sa)); if (sipmethod == SIP_INVITE) stop_media_flows(p); /* 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 (p->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)); change_redirecting_information(p, req, &redirecting, &update_redirecting, TRUE); ast_channel_set_redirecting(p->owner, &redirecting, &update_redirecting); ast_party_redirecting_free(&redirecting); } /* Fall through */ case 486: /* Busy here */ case 600: /* Busy everywhere */ case 603: /* Decline */ if (p->owner) { sip_handle_cc(p, req, AST_CC_CCBS); ast_queue_control(p->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 (sipmethod == SIP_SUBSCRIBE) { handle_response_subscribe(p, resp, rest, req, seqno); break; } /* Fall through */ case 502: /* Bad gateway */ case 503: /* Service Unavailable */ case 504: /* Server Timeout */ if (owner) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); break; case 484: /* Address Incomplete */ if (owner && sipmethod != SIP_BYE) { switch (ast_test_flag(&p->flags[1], SIP_PAGE2_ALLOWOVERLAP)) { case SIP_PAGE2_ALLOWOVERLAP_YES: ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(resp)); break; default: ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(404)); break; } } break; default: /* Send hangup */ if (owner && sipmethod != SIP_BYE) ast_queue_hangup_with_cause(p->owner, hangup_sip2cause(resp)); break; } /* ACK on invite */ if (sipmethod == SIP_INVITE) transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE); sip_alreadygone(p); if (!p->owner) { pvt_set_needdestroy(p, "transaction completed"); } } else if ((resp >= 100) && (resp < 200)) { if (sipmethod == SIP_INVITE) { if (!req->ignore && sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); if (find_sdp(req)) process_sdp(p, req, SDP_T38_NONE); if (p->owner) { /* Queue a progress frame */ ast_queue_control(p->owner, AST_CONTROL_PROGRESS); } } } else ast_log(LOG_NOTICE, "Don't know how to handle a %d %s response from %s\n", resp, rest, p->owner ? ast_channel_name(p->owner) : ast_sockaddr_stringify(&p->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_verbose("SIP Response message for INCOMING dialog %s arrived\n", msg); if (sipmethod == 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]; gettag(req, "To", tag, sizeof(tag)); ast_string_field_set(p, theirtag, tag); } switch(resp) { case 200: if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, req, seqno); } else if (sipmethod == SIP_CANCEL) { ast_debug(1, "Got 200 OK on CANCEL\n"); /* Wait for 487, then destroy */ } else if (sipmethod == SIP_BYE) { pvt_set_needdestroy(p, "transaction completed"); } break; case 401: /* www-auth */ case 407: if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); else if (sipmethod == SIP_BYE) { if (p->authtries == MAX_AUTHTRIES || do_proxy_auth(p, req, resp, sipmethod, 0)) { ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, sip_get_header(&p->initreq, "From")); pvt_set_needdestroy(p, "failed to authenticate BYE"); } } break; case 481: /* Call leg does not exist */ if (sipmethod == SIP_INVITE) { /* Re-invite failed */ handle_response_invite(p, resp, rest, req, seqno); } else if (sipmethod == SIP_BYE) { pvt_set_needdestroy(p, "received 481 response"); } else if (sipdebug) { ast_debug(1, "Remote host can't match request %s to call '%s'. Giving up\n", sip_methods[sipmethod].text, p->callid); } break; case 501: /* Not Implemented */ if (sipmethod == SIP_INVITE) handle_response_invite(p, resp, rest, req, seqno); break; default: /* Errors without handlers */ if ((resp >= 100) && (resp < 200)) { if (sipmethod == SIP_INVITE) { /* re-invite */ if (!req->ignore && sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); } } else if ((resp >= 200) && (resp < 300)) { /* on any unrecognized 2XX response do the following */ if (sipmethod == SIP_INVITE) { handle_response_invite(p, resp, rest, 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, rest, ast_sockaddr_stringify(&p->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 (sipmethod == SIP_INVITE && sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); break; } } break; } } } /*! \brief SIP pickup support function * Starts in a new thread, then pickup the call */ static void *sip_pickup_thread(void *stuff) { struct ast_channel *chan; chan = stuff; if (ast_pickup_call(chan)) { ast_channel_hangupcause_set(chan, AST_CAUSE_CALL_REJECTED); } else { ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL_CLEARING); } ast_hangup(chan); ast_channel_unref(chan); chan = NULL; return NULL; } /*! \brief Pickup a call using the subsystem in features.c * This is executed in a separate thread */ static int sip_pickup(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; } /*! \brief Get tag from packet * * \return Returns the pointer to the provided tag buffer, * or NULL if the tag was not found. */ static const char *gettag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize) { const char *thetag; if (!tagbuf) return NULL; tagbuf[0] = '\0'; /* reset the buffer */ thetag = sip_get_header(req, header); thetag = strcasestr(thetag, ";tag="); if (thetag) { thetag += 5; ast_copy_string(tagbuf, thetag, tagbufsize); return strsep(&tagbuf, ";"); } return NULL; } static int handle_cc_notify(struct sip_pvt *pvt, struct sip_request *req) { struct sip_monitor_instance *monitor_instance = ao2_callback(sip_monitor_instances, 0, find_sip_monitor_instance_by_subscription_pvt, pvt); const char *status = get_content_line(req, "cc-state", ':'); struct cc_epa_entry *cc_entry; char *uri; if (!monitor_instance) { transmit_response(pvt, "400 Bad Request", req); return -1; } if (ast_strlen_zero(status)) { ao2_ref(monitor_instance, -1); transmit_response(pvt, "400 Bad Request", req); return -1; } if (!strcmp(status, "queued")) { /* We've been told that we're queued. This is the endpoint's way of telling * us that it has accepted our CC request. We need to alert the core of this * development */ ast_cc_monitor_request_acked(monitor_instance->core_id, "SIP endpoint %s accepted request", monitor_instance->device_name); transmit_response(pvt, "200 OK", req); ao2_ref(monitor_instance, -1); return 0; } /* It's open! Yay! */ uri = get_content_line(req, "cc-URI", ':'); if (ast_strlen_zero(uri)) { uri = get_in_brackets((char *)sip_get_header(req, "From")); } ast_string_field_set(monitor_instance, notify_uri, uri); if (monitor_instance->suspension_entry) { cc_entry = monitor_instance->suspension_entry->instance_data; if (cc_entry->current_state == CC_CLOSED) { /* If we've created a suspension entry and the current state is closed, then that means * we got a notice from the CC core earlier to suspend monitoring, but because this particular * call leg had not yet notified us that it was ready for recall, it meant that we * could not yet send a PUBLISH. Now, however, we can. */ construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername); transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_INITIAL, monitor_instance->notify_uri); } else { ast_cc_monitor_callee_available(monitor_instance->core_id, "SIP monitored callee has become available"); } } else { ast_cc_monitor_callee_available(monitor_instance->core_id, "SIP monitored callee has become available"); } ao2_ref(monitor_instance, -1); transmit_response(pvt, "200 OK", req); return 0; } /*! \brief Handle incoming notifications */ static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) { /* This is mostly a skeleton for future improvements */ /* Mostly created to return proper answers on notifications on outbound REFER's */ int res = 0; const char *event = sip_get_header(req, "Event"); char *sep; if( (sep = strchr(event, ';')) ) { /* XXX bug here - overwriting string ? */ *sep++ = '\0'; } if (sipdebug) ast_debug(2, "Got NOTIFY Event: %s\n", event); if (!strcmp(event, "refer")) { /* Save nesting depth for now, since there might be other events we will support in the future */ /* Handle REFER notifications */ char *buf, *cmd, *code; int respcode; int success = TRUE; /* 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(sip_get_header(req, "Content-Type"), "message/sipfrag", strlen("message/sipfrag"))) { /* We need a sipfrag */ transmit_response(p, "400 Bad request", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return -1; } /* Get the text of the attachment */ if (ast_strlen_zero(buf = get_content(req))) { ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", p->callid); transmit_response(p, "400 Bad request", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return -1; } /* From the RFC... A minimal, but complete, implementation can respond with a single NOTIFY containing either the body: SIP/2.0 100 Trying if the subscription is pending, the body: SIP/2.0 200 OK if the reference was successful, the body: SIP/2.0 503 Service Unavailable if the reference failed, or the body: SIP/2.0 603 Declined if the REFER request was accepted before approval to follow the reference could be obtained and that approval was subsequently denied (see Section 2.4.7). If there are several REFERs in the same dialog, we need to match the ID of the event header... */ 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 permenantly */ 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 (p->owner && success != -1) { enum ast_control_transfer message = success ? AST_TRANSFER_SUCCESS : AST_TRANSFER_FAILED; ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } /* Confirm that we received this packet */ transmit_response(p, "200 OK", req); } else if (!strcmp(event, "message-summary")) { const char *mailbox = NULL; char *c = ast_strdupa(get_content_line(req, "Voice-Message", ':')); if (!p->mwi) { struct sip_peer *peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); if (peer) { mailbox = ast_strdupa(peer->unsolicited_mailbox); sip_unref_peer(peer, "removing unsolicited mwi ref"); } } else { mailbox = p->mwi->mailbox; } if (!ast_strlen_zero(mailbox) && !ast_strlen_zero(c)) { char *old = strsep(&c, " "); char *new = strsep(&old, "/"); ast_publish_mwi_state(mailbox, "SIP_Remote", atoi(new), atoi(old)); transmit_response(p, "200 OK", req); } else { transmit_response(p, "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. */ transmit_response(p, "200 OK", req); } else if (!strcmp(event, "call-completion")) { res = handle_cc_notify(p, req); } else { /* We don't understand this event. */ transmit_response(p, "489 Bad event", req); res = -1; } if (!p->lastinvite) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return res; } /*! \brief Handle incoming OPTIONS request An OPTIONS request should be answered like an INVITE from the same UA, including SDP */ static int handle_request_options(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e) { const char *msg; enum sip_get_dest_result gotdest; int res; if (p->lastinvite) { /* if this is a request in an active dialog, just confirm that the dialog exists. */ transmit_response_with_allow(p, "200 OK", req, 0); return 0; } if (sip_cfg.auth_options_requests) { /* Do authentication if this OPTIONS request began the dialog */ copy_request(&p->initreq, req); set_pvt_allowed_methods(p, req); res = check_user(p, req, SIP_OPTIONS, e, XMIT_UNRELIABLE, addr); if (res == AUTH_CHALLENGE_SENT) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return 0; } if (res < 0) { /* Something failed in authentication */ ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From")); transmit_response(p, "403 Forbidden", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return 0; } } /* must go through authentication before getting here */ gotdest = get_destination(p, req, NULL); build_contact(p); if (ast_strlen_zero(p->context)) ast_string_field_set(p, context, sip_cfg.default_context); if (ast_shutting_down()) { msg = "503 Unavailable"; } else { msg = "404 Not Found"; switch (gotdest) { case SIP_GET_DEST_INVALID_URI: msg = "416 Unsupported URI scheme"; break; case SIP_GET_DEST_EXTEN_MATCHMORE: case SIP_GET_DEST_REFUSED: case SIP_GET_DEST_EXTEN_NOT_FOUND: //msg = "404 Not Found"; break; case SIP_GET_DEST_EXTEN_FOUND: msg = "200 OK"; break; } } transmit_response_with_allow(p, msg, req, 0); /* Destroy if this OPTIONS was the opening request, but not if it's in the middle of a normal call flow. */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return 0; } /*! \brief Handle the transfer part of INVITE with a replaces: header, * * This is used for call-pickup and for attended transfers initiated on * remote endpoints (i.e. a REFER received on a remote server). * * \note p and p->owner are locked upon entering this function. If the * call pickup or attended transfer is successful, then p->owner will * be unlocked upon exiting this function. This is communicated to the * caller through the nounlock parameter. * * \param p The sip_pvt where the INVITE with Replaces was received * \param req The incoming INVITE * \param[out] nounlock Indicator if p->owner should remained locked. On successful transfer, this will be set true. * \param replaces_pvt sip_pvt referenced by Replaces header * \param replaces_chan replaces_pvt's owner channel * \retval 0 Success * \retval non-zero Failure */ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int *nounlock, struct sip_pvt *replaces_pvt, struct ast_channel *replaces_chan) { struct ast_channel *c; RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); if (req->ignore) { return 0; } if (!p->owner) { /* What to do if no channel ??? */ ast_log(LOG_ERROR, "Unable to create new channel. Invite/replace failed.\n"); transmit_response_reliable(p, "503 Service Unavailable", req); append_history(p, "Xfer", "INVITE/Replace Failed. No new channel."); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return 1; } append_history(p, "Xfer", "INVITE/Replace received"); c = ast_channel_ref(p->owner); /* Fake call progress */ transmit_response(p, "100 Trying", req); ast_setstate(c, AST_STATE_RING); ast_debug(4, "Invite/Replaces: preparing to replace %s with %s\n", ast_channel_name(replaces_chan), ast_channel_name(c)); *nounlock = 1; ast_channel_unlock(c); sip_pvt_unlock(p); ast_raw_answer(c); ast_channel_lock(replaces_chan); bridge = ast_channel_get_bridge(replaces_chan); ast_channel_unlock(replaces_chan); if (bridge) { if (ast_bridge_impart(bridge, c, replaces_chan, NULL, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { ast_hangup(c); } } else { ast_channel_move(replaces_chan, c); ast_hangup(c); } sip_pvt_lock(p); return 0; } /*! \note No channel or pvt locks should be held while calling this function. */ static int do_magic_pickup(struct ast_channel *channel, const char *extension, 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", extension, sip_cfg.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(channel, pickup, ast_str_buffer(str)); return 0; } /*! \brief Called to deny a T38 reinvite if the core does not respond to our request */ static int sip_t38_abort(const void *data) { struct sip_pvt *p = (struct sip_pvt *) data; sip_pvt_lock(p); /* an application may have taken ownership of the T.38 negotiation on this * channel while we were waiting to grab the lock... if it did, the scheduler * id will have been reset to -1, which is our indication that we do *not* * want to abort the negotiation process */ if (p->t38id != -1) { change_t38_state(p, T38_REJECTED); transmit_response_reliable(p, "488 Not acceptable here", &p->initreq); p->t38id = -1; dialog_unref(p, "unref the dialog ptr from sip_t38_abort, because it held a dialog ptr"); } sip_pvt_unlock(p); return 0; } /*! * \brief 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. The way we distinguish * such an UPDATE is through the X-Asterisk-rpid-update header. * * Actually updating the media session may be some future work. */ static int handle_request_update(struct sip_pvt *p, struct sip_request *req) { if (ast_strlen_zero(sip_get_header(req, "X-Asterisk-rpid-update"))) { transmit_response(p, "501 Method Not Implemented", req); return 0; } if (!p->owner) { transmit_response(p, "481 Call/Transaction Does Not Exist", req); return 0; } if (get_rpid(p, req)) { 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 = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) p->cid_num; connected.id.number.presentation = p->callingpres; update_connected.id.name = 1; connected.id.name.valid = 1; connected.id.name.str = (char *) p->cid_name; connected.id.name.presentation = p->callingpres; /* Invalidate any earlier private connected id representation */ ast_set_party_id_all(&update_connected.priv); connected.id.tag = (char *) p->cid_tag; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; ast_channel_queue_connected_line_update(p->owner, &connected, &update_connected); } transmit_response(p, "200 OK", req); return 0; } /* * \internal \brief Check Session Timers for an INVITE request * * \retval 0 ok * \retval -1 failure */ static int handle_request_invite_st(struct sip_pvt *p, struct sip_request *req, const char *required, int reinvite) { const char *p_uac_se_hdr; /* UAC's Session-Expires header string */ const char *p_uac_min_se; /* UAC's requested Min-SE interval (char string) */ int uac_max_se = -1; /* UAC's Session-Expires in integer format */ int uac_min_se = -1; /* UAC's Min-SE in integer format */ int st_active = FALSE; /* Session-Timer on/off boolean */ int st_interval = 0; /* Session-Timer negotiated refresh interval */ enum st_refresher tmp_st_ref = SESSION_TIMER_REFRESHER_AUTO; /* Session-Timer refresher */ int dlg_min_se = -1; int dlg_max_se = global_max_se; int rtn; /* Session-Timers */ if ((p->sipoptions & SIP_OPT_TIMER)) { enum st_refresher_param st_ref_param = SESSION_TIMER_REFRESHER_PARAM_UNKNOWN; /* 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 (!p->stimer) { sip_st_alloc(p); } /* Parse the Session-Expires header */ p_uac_se_hdr = sip_get_header(req, "Session-Expires"); if (!ast_strlen_zero(p_uac_se_hdr)) { ast_debug(2, "INVITE also has \"Session-Expires\" header.\n"); rtn = parse_session_expires(p_uac_se_hdr, &uac_max_se, &st_ref_param); tmp_st_ref = (st_ref_param == SESSION_TIMER_REFRESHER_PARAM_UAC) ? SESSION_TIMER_REFRESHER_THEM : SESSION_TIMER_REFRESHER_US; if (rtn != 0) { transmit_response_reliable(p, "400 Session-Expires Invalid Syntax", req); return -1; } } /* Parse the Min-SE header */ p_uac_min_se = sip_get_header(req, "Min-SE"); if (!ast_strlen_zero(p_uac_min_se)) { ast_debug(2, "INVITE also has \"Min-SE\" header.\n"); rtn = parse_minse(p_uac_min_se, &uac_min_se); if (rtn != 0) { transmit_response_reliable(p, "400 Min-SE Invalid Syntax", req); return -1; } } dlg_min_se = st_get_se(p, FALSE); switch (st_get_mode(p, 1)) { case SESSION_TIMER_MODE_ACCEPT: case SESSION_TIMER_MODE_ORIGINATE: if (uac_max_se > 0 && uac_max_se < dlg_min_se) { transmit_response_with_minse(p, "422 Session Interval Too Small", req, dlg_min_se); return -1; } p->stimer->st_active_peer_ua = TRUE; st_active = TRUE; if (st_ref_param == SESSION_TIMER_REFRESHER_PARAM_UNKNOWN) { tmp_st_ref = st_get_refresher(p); } dlg_max_se = st_get_se(p, TRUE); if (uac_max_se > 0) { if (dlg_max_se >= uac_min_se) { st_interval = (uac_max_se < dlg_max_se) ? uac_max_se : dlg_max_se; } else { st_interval = uac_max_se; } } else if (uac_min_se > 0) { st_interval = MAX(dlg_max_se, uac_min_se); } else { st_interval = dlg_max_se; } break; case SESSION_TIMER_MODE_REFUSE: if (p->reqsipoptions & SIP_OPT_TIMER) { transmit_response_with_unsupported(p, "420 Option Disabled", req, required); ast_log(LOG_WARNING, "Received SIP INVITE with supported but disabled option: %s\n", required); return -1; } break; default: ast_log(LOG_ERROR, "Internal Error %u at %s:%d\n", st_get_mode(p, 1), __FILE__, __LINE__); 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 (st_get_mode(p, 1)) { case SESSION_TIMER_MODE_ORIGINATE: st_active = TRUE; st_interval = st_get_se(p, TRUE); tmp_st_ref = SESSION_TIMER_REFRESHER_US; p->stimer->st_active_peer_ua = (p->sipoptions & SIP_OPT_TIMER) ? TRUE : FALSE; break; default: break; } } if (reinvite == 0) { /* Session-Timers: Start session refresh timer based on negotiation/config */ if (st_active == TRUE) { p->stimer->st_active = TRUE; p->stimer->st_interval = st_interval; p->stimer->st_ref = tmp_st_ref; } } else { if (p->stimer->st_active == TRUE) { /* Session-Timers: 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", p->callid); /* The UAC may be adjusting the session-timers mid-session */ if (st_interval > 0) { p->stimer->st_interval = st_interval; p->stimer->st_ref = tmp_st_ref; } } } return 0; } /*! * \brief Handle incoming INVITE request * \note 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 handle_request_invite(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, const char *e, int *nounlock) { int res = INV_REQ_SUCCESS; int gotdest; const char *p_replaces; char *replace_id = NULL; const char *required; unsigned int required_profile = 0; struct ast_channel *c = NULL; /* New channel */ struct sip_peer *authpeer = NULL; /* Matching Peer */ int reinvite = 0; struct ast_party_redirecting redirecting; struct ast_set_party_redirecting update_redirecting; int supported_start = 0; int require_start = 0; char unsupported[256] = { 0, }; 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 (!p->sipoptions) { const char *supported = NULL; do { supported = __get_header(req, "Supported", &supported_start); if (!ast_strlen_zero(supported)) { p->sipoptions |= parse_sip_options(supported, NULL, 0); } } while (!ast_strlen_zero(supported)); } /* Find out what they require */ do { required = __get_header(req, "Require", &require_start); if (!ast_strlen_zero(required)) { required_profile |= parse_sip_options(required, unsupported, ARRAY_LEN(unsupported)); } } while (!ast_strlen_zero(required)); /* 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)) { transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, unsupported); ast_log(LOG_WARNING, "Received SIP INVITE with unsupported required extension: required:%s unsupported:%s\n", required, unsupported); p->invitestate = INV_COMPLETED; if (!p->lastinvite) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } res = -1; goto request_invite_cleanup; } /* The option tags may be present in Supported: or Require: headers. Include the Require: option tags for further processing as well */ p->sipoptions |= required_profile; p->reqsipoptions = required_profile; /* Check if this is a loop */ if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner && (p->invitestate != INV_TERMINATED && p->invitestate != INV_CONFIRMED) && ast_channel_state(p->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 different; const char *initial_rlpart2 = REQ_OFFSET_TO_STR(&p->initreq, rlpart2); const char *this_rlpart2 = REQ_OFFSET_TO_STR(req, rlpart2); if (sip_cfg.pedanticsipchecking) different = sip_uri_cmp(initial_rlpart2, this_rlpart2); else different = strcmp(initial_rlpart2, this_rlpart2); if (!different) { transmit_response(p, "482 Loop Detected", req); p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); res = INV_REQ_FAILED; goto request_invite_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. * * \todo XXX This needs to be reviewed. YOu don't change the request URI really, you route the packet * correctly instead... */ char *uri = ast_strdupa(this_rlpart2); char *at = strchr(uri, '@'); char *peerorhost; ast_debug(2, "Potential spiral detected. Original RURI was %s, new RURI is %s\n", initial_rlpart2, this_rlpart2); transmit_response(p, "100 Trying", req); if (at) { *at = '\0'; } /* Parse out "sip:" */ if ((peerorhost = strchr(uri, ':'))) { *peerorhost++ = '\0'; } ast_string_field_set(p, theirtag, NULL); /* Treat this as if there were a call forward instead... */ ast_channel_call_forward_set(p->owner, peerorhost); ast_queue_control(p->owner, AST_CONTROL_BUSY); res = INV_REQ_FAILED; goto request_invite_cleanup; } } if (!req->ignore && p->pendinginvite) { if (!ast_test_flag(&p->flags[0], SIP_OUTGOING) && (p->invitestate == INV_COMPLETED || p->invitestate == INV_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 p->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_ack will take care of this by clearing the p->pendinginvite and removing the response * from the previous transaction from the list of outstanding packets. */ __sip_ack(p, p->pendinginvite, 1, 0); } else { /* We already have a pending invite. Sorry. You are on hold. */ p->glareinvite = seqno; transmit_response_reliable(p, "491 Request Pending", req); check_via(p, req); ast_debug(1, "Got INVITE on call where we already have pending INVITE, deferring that - %s\n", p->callid); /* Don't destroy dialog here */ res = INV_REQ_FAILED; goto request_invite_cleanup; } } p_replaces = sip_get_header(req, "Replaces"); if (!ast_strlen_zero(p_replaces)) { /* We have a replaces header */ char *ptr; char *fromtag = NULL; char *totag = NULL; char *start, *to; int error = 0; if (p->owner) { ast_debug(3, "INVITE w Replaces on existing call? Refusing action. [%s]\n", p->callid); transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */ check_via(p, req); copy_request(&p->initreq, req); /* Do not destroy existing call */ res = INV_REQ_ERROR; goto request_invite_cleanup; } if (sipdebug) ast_debug(3, "INVITE part of call transfer. Replaces [%s]\n", p_replaces); /* Create a buffer we can manipulate */ replace_id = ast_strdupa(p_replaces); ast_uri_decode(replace_id, ast_uri_sip_user); if (!sip_refer_alloc(p)) { transmit_response_reliable(p, "500 Server Internal Error", req); append_history(p, "Xfer", "INVITE/Replace Failed. Out of memory."); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); p->invitestate = INV_COMPLETED; res = INV_REQ_ERROR; check_via(p, req); copy_request(&p->initreq, req); goto request_invite_cleanup; } /* Todo: (When we find phones that support this) if the replaces header contains ";early-only" we can only replace the call in early stage, not after it's up. If it's not in early mode, 486 Busy. */ /* Skip leading whitespace */ replace_id = ast_skip_blanks(replace_id); start = replace_id; while ( (ptr = strsep(&start, ";")) ) { ptr = ast_skip_blanks(ptr); /* XXX maybe unnecessary ? */ if ( (to = strcasestr(ptr, "to-tag=") ) ) totag = to + 7; /* skip the keyword */ else if ( (to = strcasestr(ptr, "from-tag=") ) ) { fromtag = to + 9; /* skip the keyword */ fromtag = strsep(&fromtag, "&"); /* trim what ? */ } } if (sipdebug) ast_debug(4, "Invite/replaces: Will use Replace-Call-ID : %s Fromtag: %s Totag: %s\n", replace_id, 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(replace_id, "pickup-", 7) == 0) { RAII_VAR(struct sip_pvt *, subscription, NULL, ao2_cleanup); RAII_VAR(struct ast_channel *, subscription_chan, NULL, ao2_cleanup); replace_id += 7; /* Worst case we are looking at \0 */ if (get_sip_pvt_from_replaces(replace_id, totag, fromtag, &subscription, &subscription_chan)) { ast_log(LOG_NOTICE, "Unable to find subscription with call-id: %s\n", replace_id); transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req); error = 1; } else { SCOPED_LOCK(lock, subscription, sip_pvt_lock, sip_pvt_unlock); ast_log(LOG_NOTICE, "Trying to pick up %s@%s\n", subscription->exten, subscription->context); ast_copy_string(pickup.exten, subscription->exten, sizeof(pickup.exten)); ast_copy_string(pickup.context, subscription->context, sizeof(pickup.context)); } } if (!error && ast_strlen_zero(pickup.exten) && get_sip_pvt_from_replaces(replace_id, totag, fromtag, &replaces_pvt, &replaces_chan)) { ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id); transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req); error = 1; } /* 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 == p) { ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", replace_id, p->callid); transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */ error = 1; } 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", replace_id); /* Check for better return code */ transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replace)", req); error = 1; } 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) { ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id); transmit_response_reliable(p, "603 Declined (Replaces)", req); error = 1; } if (error) { /* Give up this dialog */ append_history(p, "Xfer", "INVITE/Replace Failed."); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); p->invitestate = INV_COMPLETED; res = INV_REQ_ERROR; check_via(p, req); copy_request(&p->initreq, req); goto request_invite_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 = (p->initreq.headers ? TRUE : FALSE); if (sip_cancel_destroy(p)) ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); /* This also counts as a pending invite */ p->pendinginvite = seqno; check_via(p, req); copy_request(&p->initreq, req); /* Save this INVITE as the transaction basis */ if (sipdebug) ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid); if (!p->owner) { /* Not a re-invite */ if (req->debug) ast_verbose("Using INVITE request as basis request - %s\n", p->callid); if (newcall) append_history(p, "Invite", "New call: %s", p->callid); parse_ok_contact(p, req); } else { /* Re-invite on existing call */ ast_clear_flag(&p->flags[0], SIP_OUTGOING); /* This is now an inbound dialog */ if (get_rpid(p, req)) { 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 = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) p->cid_num; connected.id.number.presentation = p->callingpres; update_connected.id.name = 1; connected.id.name.valid = 1; connected.id.name.str = (char *) p->cid_name; connected.id.name.presentation = p->callingpres; /* Invalidate any earlier private connected id representation */ ast_set_party_id_all(&update_connected.priv); connected.id.tag = (char *) p->cid_tag; connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; ast_channel_queue_connected_line_update(p->owner, &connected, &update_connected); } /* Handle SDP here if we already have an owner */ if (find_sdp(req)) { if (process_sdp(p, req, SDP_T38_INITIATE)) { if (!ast_strlen_zero(sip_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. */ transmit_response_reliable(p, "415 Unsupported Media type", req); } else { transmit_response_reliable(p, "488 Not acceptable here", req); } if (!p->lastinvite) sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); res = INV_REQ_ERROR; goto request_invite_cleanup; } ast_queue_control(p->owner, AST_CONTROL_SRCUPDATE); } else { ast_format_cap_remove_by_type(p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->jointcaps, p->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(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) { ast_queue_unhold(p->owner); /* Activate a re-invite */ ast_queue_frame(p->owner, &ast_null_frame); change_hold_state(p, req, FALSE, 0); } } if (p->do_history) /* This is a response, note what it was for */ append_history(p, "ReInv", "Re-invite received"); } } else if (req->debug) ast_verbose("Ignoring this INVITE request\n"); if (!p->lastinvite && !req->ignore && !p->owner) { /* This is a new invite */ /* Handle authentication if this is our first invite */ int cc_recall_core_id = -1; set_pvt_allowed_methods(p, req); res = check_user_full(p, req, SIP_INVITE, e, XMIT_RELIABLE, addr, &authpeer); if (res == AUTH_CHALLENGE_SENT) { p->invitestate = INV_COMPLETED; /* Needs to restart in another INVITE transaction */ goto request_invite_cleanup; } if (res < 0) { /* Something failed in authentication */ ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From")); transmit_response_reliable(p, "403 Forbidden", req); p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); goto request_invite_cleanup; } /* Successful authentication and peer matching so record the peer related to this pvt (for easy access to peer settings) */ if (p->relatedpeer) { p->relatedpeer = sip_unref_peer(p->relatedpeer,"unsetting the relatedpeer field in the dialog, before it is set to something else."); } if (authpeer) { p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); } req->authenticated = 1; /* We have a successful authentication, process the SDP portion if there is one */ if (find_sdp(req)) { if (process_sdp(p, req, SDP_T38_INITIATE)) { /* 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_get_header(req, "Content-Encoding"))) { transmit_response_reliable(p, "415 Unsupported Media type", req); } else { /* Unacceptable codecs */ transmit_response_reliable(p, "488 Not acceptable here", req); } p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_debug(1, "No compatible codecs for this SIP call.\n"); res = INV_REQ_ERROR; goto request_invite_cleanup; } } else { /* No SDP in invite, call control session */ ast_format_cap_remove_by_type(p->jointcaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->jointcaps, p->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(p->context)) ast_string_field_set(p, context, sip_cfg.default_context); /* Check number of concurrent calls -vs- incoming limit HERE */ ast_debug(1, "Checking SIP call limits for device %s\n", p->username); if ((res = update_call_counter(p, INC_CALL_LIMIT))) { if (res < 0) { ast_log(LOG_NOTICE, "Failed to place call for device %s, too many calls\n", p->username); transmit_response_reliable(p, "480 Temporarily Unavailable (Call limit) ", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); p->invitestate = INV_COMPLETED; res = AUTH_SESSION_LIMIT; } goto request_invite_cleanup; } gotdest = get_destination(p, NULL, &cc_recall_core_id); /* Get destination right away */ extract_uri(p, req); /* Get the Contact URI */ build_contact(p); /* Build our contact header */ if (p->rtp) { ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&p->flags[1], SIP_PAGE2_RFC2833_COMPENSATE)); } if (!replace_id && (gotdest != SIP_GET_DEST_EXTEN_FOUND)) { /* No matching extension found */ switch(gotdest) { case SIP_GET_DEST_INVALID_URI: transmit_response_reliable(p, "416 Unsupported URI scheme", req); break; case SIP_GET_DEST_EXTEN_MATCHMORE: if (ast_test_flag(&p->flags[1], SIP_PAGE2_ALLOWOVERLAP) == SIP_PAGE2_ALLOWOVERLAP_YES) { transmit_response_reliable(p, "484 Address Incomplete", req); break; } /* * XXX We would have to implement collecting more digits in * chan_sip for any other schemes of overlap dialing. * * For SIP_PAGE2_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_GET_DEST_EXTEN_NOT_FOUND: { char *decoded_exten = ast_strdupa(p->exten); transmit_response_reliable(p, "404 Not Found", req); 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(p->username, p->peername), ast_sockaddr_stringify(&p->recv), decoded_exten, p->context); } break; case SIP_GET_DEST_REFUSED: default: transmit_response_reliable(p, "403 Forbidden", req); } /* end switch */ p->invitestate = INV_COMPLETED; update_call_counter(p, DEC_CALL_LIMIT); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); res = INV_REQ_FAILED; goto request_invite_cleanup; } else { /* If no extension was specified, use the s one */ /* Basically for calling to IP/Host name only */ if (ast_strlen_zero(p->exten)) ast_string_field_set(p, exten, "s"); /* Initialize our tag */ make_our_tag(p); if (handle_request_invite_st(p, req, required, reinvite)) { p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); res = INV_REQ_ERROR; goto request_invite_cleanup; } /* First invitation - create the channel. Allocation * failures are handled below. */ c = sip_new(p, AST_STATE_DOWN, S_OR(p->peername, NULL), NULL, NULL, p->logger_callid); if (cc_recall_core_id != -1) { ast_setup_cc_recall_datastore(c, cc_recall_core_id); ast_cc_agent_set_interfaces_chanvar(c); } *recount = 1; /* Save Record-Route for any later requests we make on this dialogue */ build_route(p, req, 0, 0); if (c) { ast_party_redirecting_init(&redirecting); memset(&update_redirecting, 0, sizeof(update_redirecting)); change_redirecting_information(p, req, &redirecting, &update_redirecting, FALSE); /*Will return immediately if no Diversion header is present */ ast_channel_set_redirecting(c, &redirecting, &update_redirecting); ast_party_redirecting_free(&redirecting); } } } else { ast_party_redirecting_init(&redirecting); memset(&update_redirecting, 0, sizeof(update_redirecting)); if (sipdebug) { if (!req->ignore) ast_debug(2, "Got a SIP re-invite for call %s\n", p->callid); else ast_debug(2, "Got a SIP re-transmit of INVITE for call %s\n", p->callid); } if (!req->ignore) reinvite = 1; if (handle_request_invite_st(p, req, required, reinvite)) { p->invitestate = INV_COMPLETED; if (!p->lastinvite) { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } res = INV_REQ_ERROR; goto request_invite_cleanup; } c = p->owner; change_redirecting_information(p, req, &redirecting, &update_redirecting, FALSE); /*Will return immediately if no Diversion header is present */ if (c) { ast_channel_set_redirecting(c, &redirecting, &update_redirecting); } ast_party_redirecting_free(&redirecting); } /* Check if OLI/ANI-II is present in From: */ parse_oli(req, p->owner); if (reinvite && p->stimer->st_active == TRUE) { restart_session_timer(p); } if (!req->ignore && p) p->lastinvite = seqno; if (c && replace_id) { /* Attended transfer or call pickup - we're the target */ if (!ast_strlen_zero(pickup.exten)) { append_history(p, "Xfer", "INVITE/Replace received"); /* Let the caller know we're giving it a shot */ transmit_response(p, "100 Trying", req); p->invitestate = INV_PROCEEDING; ast_setstate(c, AST_STATE_RING); /* Do the pickup itself */ ast_channel_unlock(c); *nounlock = 1; /* since p->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 * p->owner again, which can cause a deadlock if we already hold a lock on p. * Locking order is, channel then pvt. Dead lock avoidance must be used if * called the other way around. */ sip_pvt_unlock(p); do_magic_pickup(c, pickup.exten, pickup.context); /* Now we're either masqueraded or we failed to pickup, in either case we... */ ast_hangup(c); sip_pvt_lock(p); /* pvt is expected to remain locked on return, so re-lock it */ res = INV_REQ_FAILED; goto request_invite_cleanup; } else { /* Go and take over the target call */ if (sipdebug) ast_debug(4, "Sending this call to the invite/replaces handler %s\n", p->callid); res = handle_invite_replaces(p, req, nounlock, replaces_pvt, replaces_chan); goto request_invite_cleanup; } } if (c) { /* We have a call -either a new call or an old one (RE-INVITE) */ enum ast_channel_state c_state = ast_channel_state(c); RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(c), 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 (c_state != AST_STATE_UP && reinvite && (p->invitestate == INV_TERMINATED || p->invitestate == INV_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. */ c_state = AST_STATE_UP; } switch(c_state) { case AST_STATE_DOWN: ast_debug(2, "%s: New call is still down.... Trying... \n", ast_channel_name(c)); transmit_provisional_response(p, "100 Trying", req, 0); p->invitestate = INV_PROCEEDING; ast_setstate(c, AST_STATE_RING); if (strcmp(p->exten, pickupexten)) { /* Call to extension -start pbx on this call */ enum ast_pbx_result result; result = ast_pbx_start(c); switch(result) { case AST_PBX_FAILED: ast_log(LOG_WARNING, "Failed to start PBX :(\n"); p->invitestate = INV_COMPLETED; transmit_response_reliable(p, "503 Unavailable", req); break; case AST_PBX_CALL_LIMIT: ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n"); p->invitestate = INV_COMPLETED; transmit_response_reliable(p, "480 Temporarily Unavailable", req); res = AUTH_SESSION_LIMIT; break; case AST_PBX_SUCCESS: /* nothing to do */ break; } if (result) { /* Unlock locks so ast_hangup can do its magic */ ast_channel_unlock(c); *nounlock = 1; sip_pvt_unlock(p); ast_hangup(c); sip_pvt_lock(p); c = NULL; } } else { /* Pickup call in call group */ if (sip_pickup(c)) { ast_log(LOG_WARNING, "Failed to start Group pickup by %s\n", ast_channel_name(c)); transmit_response_reliable(p, "480 Temporarily Unavailable", req); sip_alreadygone(p); ast_channel_hangupcause_set(c, AST_CAUSE_FAILURE); /* Unlock locks so ast_hangup can do its magic */ ast_channel_unlock(c); *nounlock = 1; p->invitestate = INV_COMPLETED; sip_pvt_unlock(p); ast_hangup(c); sip_pvt_lock(p); c = NULL; } } break; case AST_STATE_RING: transmit_provisional_response(p, "100 Trying", req, 0); p->invitestate = INV_PROCEEDING; break; case AST_STATE_RINGING: transmit_provisional_response(p, "180 Ringing", req, 0); p->invitestate = INV_PROCEEDING; break; case AST_STATE_UP: ast_debug(2, "%s: This call is UP.... \n", ast_channel_name(c)); transmit_response(p, "100 Trying", req); if (p->t38.state == T38_PEER_REINVITE) { if (p->t38id > -1) { /* reset t38 abort timer */ AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "remove ref for t38id")); } p->t38id = ast_sched_add(sched, 5000, sip_t38_abort, dialog_ref(p, "passing dialog ptr into sched structure based on t38id for sip_t38_abort.")); } else if (p->t38.state == T38_ENABLED) { ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL))); } else if ((p->t38.state == T38_DISABLED) || (p->t38.state == T38_REJECTED)) { /* If this is not a re-invite or something to ignore - it's critical */ if (p->srtp && !ast_test_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK)) { ast_log(LOG_WARNING, "Target does not support required crypto\n"); transmit_response_reliable(p, "488 Not Acceptable Here (crypto)", req); } else { ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response_with_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)), p->session_modify == TRUE ? FALSE : TRUE, FALSE); ast_queue_control(p->owner, AST_CONTROL_UPDATE_RTP_PEER); } } p->invitestate = INV_TERMINATED; break; default: ast_log(LOG_WARNING, "Don't know how to handle INVITE in state %u\n", ast_channel_state(c)); transmit_response(p, "100 Trying", req); break; } } else { if (!req->ignore && p && (p->autokillid == -1)) { const char *msg; if ((!ast_format_cap_count(p->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"; } transmit_response_reliable(p, msg, req); p->invitestate = INV_COMPLETED; sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } } request_invite_cleanup: if (authpeer) { authpeer = sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_invite authpeer"); } return res; } /*! \brief Check for the presence of OLI tag(s) in the From header and set on the channel */ static void parse_oli(struct sip_request *req, struct ast_channel *chan) { const char *from = NULL; const char *s = NULL; int ani2 = 0; if (!chan || !req) { /* null pointers are not helpful */ return; } from = sip_get_header(req, "From"); if (ast_strlen_zero(from)) { /* no From header */ return; } /* Look for the possible OLI tags. */ if ((s = strcasestr(from, ";isup-oli="))) { s += 10; } else if ((s = strcasestr(from, ";ss7-oli="))) { s += 9; } else if ((s = strcasestr(from, ";oli="))) { s += 5; } if (ast_strlen_zero(s)) { /* OLI tag is missing, or present with nothing following the '=' sign */ return; } /* just in case OLI is quoted */ if (*s == '\"') { s++; } if (sscanf(s, "%d", &ani2)) { ast_channel_caller(chan)->ani2 = ani2; } return; } /*! \brief Find all call legs and bridge transferee with target * called from handle_request_refer * * \note this function assumes two locks to begin with, sip_pvt transferer and current.chan1 (the pvt's owner)... * 2 additional locks are held at the beginning of the function, targetcall_pvt, and targetcall_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 handle_request_do() that the pvt's owner it locked does not require an unlock. */ static int local_attended_transfer(struct sip_pvt *transferer, struct ast_channel *transferer_chan, uint32_t seqno, int *nounlock) { RAII_VAR(struct sip_pvt *, targetcall_pvt, NULL, ao2_cleanup); RAII_VAR(struct ast_channel *, targetcall_chan, NULL, ao2_cleanup); enum ast_transfer_result transfer_res; /* Check if the call ID of the replaces header does exist locally */ if (get_sip_pvt_from_replaces(transferer->refer->replaces_callid, transferer->refer->replaces_callid_totag, transferer->refer->replaces_callid_fromtag, &targetcall_pvt, &targetcall_chan)) { if (transferer->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 */ transmit_notify_with_sipfrag(transferer, seqno, "481 Call leg/transaction does not exist", TRUE); append_history(transferer, "Xfer", "Refer failed"); ast_clear_flag(&transferer->flags[0], SIP_GOTREFER); transferer->refer->status = 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 (!targetcall_chan) { /* No active channel */ ast_debug(4, "SIP attended transfer: Error: No owner of target call\n"); /* Cancel transfer */ transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE); append_history(transferer, "Xfer", "Refer failed"); ast_clear_flag(&transferer->flags[0], SIP_GOTREFER); transferer->refer->status = REFER_FAILED; return -1; } ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */ sip_pvt_unlock(transferer); ast_channel_unlock(transferer_chan); *nounlock = 1; transfer_res = ast_bridge_transfer_attended(transferer_chan, targetcall_chan); sip_pvt_lock(transferer); switch (transfer_res) { case AST_BRIDGE_TRANSFER_SUCCESS: transferer->refer->status = REFER_200OK; transmit_notify_with_sipfrag(transferer, seqno, "200 OK", TRUE); append_history(transferer, "Xfer", "Refer succeeded"); return 1; case AST_BRIDGE_TRANSFER_FAIL: transferer->refer->status = REFER_FAILED; transmit_notify_with_sipfrag(transferer, seqno, "500 Internal Server Error", TRUE); append_history(transferer, "Xfer", "Refer failed (internal error)"); ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); return -1; case AST_BRIDGE_TRANSFER_INVALID: transferer->refer->status = REFER_FAILED; transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE); append_history(transferer, "Xfer", "Refer failed (invalid bridge state)"); ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); return -1; case AST_BRIDGE_TRANSFER_NOT_PERMITTED: transferer->refer->status = REFER_FAILED; transmit_notify_with_sipfrag(transferer, seqno, "403 Forbidden", TRUE); append_history(transferer, "Xfer", "Refer failed (operation not permitted)"); ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); return -1; default: break; } return 1; } /*! * Data to set on a channel that runs dialplan * at the completion of a blind transfer */ struct blind_transfer_cb_data { /*! Contents of the REFER's Referred-by header */ const char *referred_by; /*! Domain of the URI in the REFER's Refer-To header */ const char *domain; /*! Contents of what to place in a Replaces header of an INVITE */ const char *replaces; /*! Redirecting information to set on the channel */ struct ast_party_redirecting redirecting; /*! Parts of the redirecting structure that are to be updated */ struct ast_set_party_redirecting update_redirecting; }; /*! * \internal * \brief 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. * * \param chan The new outbound channel * \param user_data A blind_transfer_cb_data struct * \param transfer_type Unused */ static void blind_transfer_cb(struct ast_channel *chan, struct transfer_channel_data *user_data_wrapper, enum ast_transfer_type transfer_type) { struct blind_transfer_cb_data *cb_data = user_data_wrapper->data; pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes"); pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REFERER", cb_data->referred_by); pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REPLACES", cb_data->replaces); pbx_builtin_setvar_helper(chan, "SIPDOMAIN", cb_data->domain); ast_channel_update_redirecting(chan, &cb_data->redirecting, &cb_data->update_redirecting); } /*! \brief Handle incoming REFER request */ /*! \page SIP_REFER SIP transfer Support (REFER) REFER is used for call transfer in SIP. We get a REFER to place a new call with an INVITE somwhere and then keep the transferor up-to-date of the transfer. If the transfer fails, get back on line with the orginal call. - REFER can be sent outside or inside of a dialog. Asterisk only accepts REFER inside of a dialog. - If we get a replaces header, it is an attended transfer \par Blind transfers The transferor provides the transferee with the transfer targets contact. The signalling between transferer or transferee should not be cancelled, so the call is recoverable if the transfer target can not be reached by the transferee. In this case, Asterisk receives a TRANSFER from the transferor, thus is the transferee. We should try to set up a call to the contact provided and if that fails, re-connect the current session. If the new call is set up, we issue a hangup. In this scenario, we are following section 5.2 in the SIP CC Transfer draft. (Transfer without a GRUU) \par Transfer with consultation hold In this case, the transferor talks to the transfer target before the transfer takes place. This is implemented with SIP hold and transfer. Note: The invite From: string could indicate a transfer. (Section 6. Transfer with consultation hold) The transferor places the transferee on hold, starts a call with the transfer target to alert them to the impending transfer, terminates the connection with the target, then proceeds with the transfer (as in Blind transfer above) \par Attended transfer The transferor places the transferee on hold, calls the transfer target to alert them, places the target on hold, then proceeds with the transfer using a Replaces header field in the Refer-to header. This will force the transfee to send an Invite to the target, with a replaces header that instructs the target to hangup the call between the transferor and the target. In this case, the Refer/to: uses the AOR address. (The same URI that the transferee used to establish the session with the transfer target (To: ). The Require: replaces header should be in the INVITE to avoid the wrong UA in a forked SIP proxy scenario to answer and have no call to replace with. The referred-by header is *NOT* required, but if we get it, can be copied into the INVITE to the transfer target to inform the target about the transferor "Any REFER request has to be appropriately authenticated.". We can't destroy dialogs, since we want the call to continue. */ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock) { char *refer_to = NULL; char *refer_to_context = NULL; int res = 0; struct blind_transfer_cb_data cb_data; enum ast_transfer_result transfer_res; RAII_VAR(struct ast_channel *, transferer, NULL, ast_channel_cleanup); RAII_VAR(struct ast_str *, replaces_str, NULL, ast_free_ptr); if (req->debug) { ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", p->callid, ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "callee" : "caller"); } if (!p->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", p->callid); transmit_response(p, "603 Declined (No dialog)", req); if (!req->ignore) { append_history(p, "Xfer", "Refer failed. Outside of dialog."); sip_alreadygone(p); pvt_set_needdestroy(p, "outside of dialog"); } return 0; } /* Check if transfer is allowed from this device */ if (p->allowtransfer == TRANSFER_CLOSED ) { /* Transfer not allowed, decline */ transmit_response(p, "603 Declined (policy)", req); append_history(p, "Xfer", "Refer failed. Allowtransfer == closed."); /* Do not destroy SIP session */ return 0; } if (!req->ignore && ast_test_flag(&p->flags[0], SIP_GOTREFER)) { /* Already have a pending REFER */ transmit_response(p, "491 Request pending", req); append_history(p, "Xfer", "Refer failed. Request pending."); return 0; } /* Allocate memory for call transfer data */ if (!sip_refer_alloc(p)) { transmit_response(p, "500 Internal Server Error", req); append_history(p, "Xfer", "Refer failed. Memory allocation error."); return -3; } res = get_refer_info(p, req); /* Extract headers */ p->refer->status = REFER_SENT; if (res != 0) { switch (res) { case -2: /* Syntax error */ transmit_response(p, "400 Bad Request (Refer-to missing)", req); append_history(p, "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: transmit_response(p, "603 Declined (Non sip: uri)", req); append_history(p, "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 */ transmit_response(p, "202 Accepted", req); append_history(p, "Xfer", "Refer failed. Bad extension."); transmit_notify_with_sipfrag(p, seqno, "404 Not found", TRUE); ast_clear_flag(&p->flags[0], SIP_GOTREFER); if (req->debug) { ast_debug(1, "SIP transfer to bad extension: %s\n", p->refer->refer_to); } break; } return 0; } if (ast_strlen_zero(p->context)) { ast_string_field_set(p, context, sip_cfg.default_context); } /* If we do not support SIP domains, all transfers are local */ if (sip_cfg.allow_external_domains && check_sip_domain(p->refer->refer_to_domain, NULL, 0)) { p->refer->localtransfer = 1; if (sipdebug) { ast_debug(3, "This SIP transfer is local : %s\n", p->refer->refer_to_domain); } } else if (AST_LIST_EMPTY(&domain_list) || check_sip_domain(p->refer->refer_to_domain, NULL, 0)) { /* This PBX doesn't bother with SIP domains or domain is local, so this transfer is local */ p->refer->localtransfer = 1; } else if (sipdebug) { ast_debug(3, "This SIP transfer is to a remote SIP extension (remote domain %s)\n", p->refer->refer_to_domain); } /* Is this a repeat of a current request? Ignore it */ /* Don't know what else to do right now. */ if (req->ignore) { return 0; } /* Get the transferer's channel */ transferer = ast_channel_ref(p->owner); if (sipdebug) { ast_debug(3, "SIP %s transfer: Transferer channel %s\n", p->refer->attendedtransfer ? "attended" : "blind", ast_channel_name(transferer)); } ast_set_flag(&p->flags[0], SIP_GOTREFER); /* From here on failures will be indicated with NOTIFY requests */ transmit_response(p, "202 Accepted", req); /* Attended transfer: Find all call legs and bridge transferee with target*/ if (p->refer->attendedtransfer) { /* both p and p->owner _MUST_ be locked while calling local_attended_transfer */ if ((res = local_attended_transfer(p, transferer, seqno, nounlock))) { ast_clear_flag(&p->flags[0], SIP_GOTREFER); return res; } /* Fall through for remote transfers that we did not find locally */ if (sipdebug) { 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(p->refer->refer_to); refer_to_context = ast_strdupa(p->refer->refer_to_context); ast_party_redirecting_init(&cb_data.redirecting); memset(&cb_data.update_redirecting, 0, sizeof(cb_data.update_redirecting)); change_redirecting_information(p, req, &cb_data.redirecting, &cb_data.update_redirecting, 0); cb_data.domain = ast_strdupa(p->refer->refer_to_domain); cb_data.referred_by = ast_strdupa(p->refer->referred_by); if (!ast_strlen_zero(p->refer->replaces_callid)) { replaces_str = ast_str_create(128); if (!replaces_str) { ast_log(LOG_NOTICE, "Unable to create Replaces string for remote attended transfer. Transfer failed\n"); ast_clear_flag(&p->flags[0], SIP_GOTREFER); ast_party_redirecting_free(&cb_data.redirecting); return -1; } ast_str_append(&replaces_str, 0, "%s%s%s%s%s", p->refer->replaces_callid, !ast_strlen_zero(p->refer->replaces_callid_totag) ? ";to-tag=" : "", S_OR(p->refer->replaces_callid_totag, ""), !ast_strlen_zero(p->refer->replaces_callid_fromtag) ? ";from-tag=" : "", S_OR(p->refer->replaces_callid_fromtag, "")); cb_data.replaces = ast_str_buffer(replaces_str); } else { cb_data.replaces = NULL; } if (!*nounlock) { ast_channel_unlock(p->owner); *nounlock = 1; } ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); sip_pvt_unlock(p); transfer_res = ast_bridge_transfer_blind(1, transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data); sip_pvt_lock(p); switch (transfer_res) { case AST_BRIDGE_TRANSFER_INVALID: res = -1; p->refer->status = REFER_FAILED; transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE); append_history(p, "Xfer", "Refer failed (only bridged calls)."); ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); break; case AST_BRIDGE_TRANSFER_NOT_PERMITTED: res = -1; p->refer->status = REFER_FAILED; transmit_notify_with_sipfrag(p, seqno, "403 Forbidden", TRUE); append_history(p, "Xfer", "Refer failed (bridge does not permit transfers)"); ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); break; case AST_BRIDGE_TRANSFER_FAIL: res = -1; p->refer->status = REFER_FAILED; transmit_notify_with_sipfrag(p, seqno, "500 Internal Server Error", TRUE); append_history(p, "Xfer", "Refer failed (internal error)"); ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); break; case AST_BRIDGE_TRANSFER_SUCCESS: res = 0; p->refer->status = REFER_200OK; transmit_notify_with_sipfrag(p, seqno, "200 OK", TRUE); append_history(p, "Xfer", "Refer succeeded."); break; default: break; } ast_clear_flag(&p->flags[0], SIP_GOTREFER); ast_party_redirecting_free(&cb_data.redirecting); return res; } /*! \brief Handle incoming CANCEL request */ static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req) { check_via(p, req); sip_alreadygone(p); if (p->owner && ast_channel_state(p->owner) == AST_STATE_UP) { /* This call is up, cancel is ignored, we need a bye */ transmit_response(p, "200 OK", req); ast_debug(1, "Got CANCEL on an answered call. Ignoring... \n"); return 0; } /* 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 (p->invitestate == INV_TERMINATED || p->invitestate == INV_COMPLETED) { __sip_pretend_ack(p); } if (p->invitestate != INV_TERMINATED) p->invitestate = INV_CANCELLED; if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) update_call_counter(p, DEC_CALL_LIMIT); stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */ if (p->owner) { sip_queue_hangup_cause(p, 0); } else { sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } if (ast_str_strlen(p->initreq.data) > 0) { struct sip_pkt *pkt, *prev_pkt; /* 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. */ for (pkt = p->packets, prev_pkt = NULL; pkt; prev_pkt = pkt, pkt = pkt->next) { if (pkt->seqno == p->lastinvite && pkt->response_code == 487) { AST_SCHED_DEL(sched, pkt->retransid); UNLINK(pkt, p->packets, prev_pkt); dialog_unref(pkt->owner, "unref packet->owner from dialog"); if (pkt->data) { ast_free(pkt->data); } ast_free(pkt); break; } } transmit_response_reliable(p, "487 Request Terminated", &p->initreq); transmit_response(p, "200 OK", req); return 1; } else { transmit_response(p, "481 Call Leg Does Not Exist", req); return 0; } } /*! \brief Handle incoming BYE request */ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req) { struct ast_channel *c=NULL; int res; const char *required; RAII_VAR(struct ast_channel *, peer_channel, NULL, ast_channel_cleanup); char quality_buf[AST_MAX_USER_FIELD], *quality; /* If we have an INCOMING invite that we haven't answered, terminate that transaction */ if (p->pendinginvite && !ast_test_flag(&p->flags[0], SIP_OUTGOING) && !req->ignore) { transmit_response_reliable(p, "487 Request Terminated", &p->initreq); } __sip_pretend_ack(p); p->invitestate = INV_TERMINATED; copy_request(&p->initreq, req); if (sipdebug) ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid); check_via(p, req); sip_alreadygone(p); if (p->owner) { RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); RAII_VAR(struct ast_channel *, owner_ref, NULL, ast_channel_cleanup); /* Grab a reference to p->owner to prevent it from going away */ owner_ref = ast_channel_ref(p->owner); /* Established locking order here is bridge, channel, pvt * and the bridge will be locked during ast_channel_bridge_peer */ ast_channel_unlock(owner_ref); sip_pvt_unlock(p); peer_channel = ast_channel_bridge_peer(owner_ref); owner_relock = sip_pvt_lock_full(p); 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 (p->rtp) { if (p->do_history) { if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { append_history(p, "RTCPaudio", "Quality:%s", quality); } if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER, quality_buf, sizeof(quality_buf)))) { append_history(p, "RTCPaudioJitter", "Quality:%s", quality); } if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS, quality_buf, sizeof(quality_buf)))) { append_history(p, "RTCPaudioLoss", "Quality:%s", quality); } if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT, quality_buf, sizeof(quality_buf)))) { append_history(p, "RTCPaudioRTT", "Quality:%s", quality); } } if (p->owner) { RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); RAII_VAR(struct ast_channel *, owner_ref, NULL, ast_channel_cleanup); struct ast_rtp_instance *p_rtp; /* Grab a reference to p->owner to prevent it from going away */ owner_ref = ast_channel_ref(p->owner); p_rtp = p->rtp; ao2_ref(p_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_ref); sip_pvt_unlock(p); ast_rtp_instance_set_stats_vars(owner_ref, p_rtp); ao2_ref(p_rtp, -1); if (peer_channel) { ast_channel_lock(peer_channel); if (IS_SIP_TECH(ast_channel_tech(peer_channel))) { struct sip_pvt *peer_pvt; peer_pvt = ast_channel_tech_pvt(peer_channel); if (peer_pvt) { ao2_ref(peer_pvt, +1); sip_pvt_lock(peer_pvt); if (peer_pvt->rtp) { struct ast_rtp_instance *peer_rtp; peer_rtp = peer_pvt->rtp; ao2_ref(peer_rtp, +1); ast_channel_unlock(peer_channel); sip_pvt_unlock(peer_pvt); ast_rtp_instance_set_stats_vars(peer_channel, peer_rtp); ao2_ref(peer_rtp, -1); ast_channel_lock(peer_channel); sip_pvt_lock(peer_pvt); } sip_pvt_unlock(peer_pvt); ao2_ref(peer_pvt, -1); } } ast_channel_unlock(peer_channel); } owner_relock = sip_pvt_lock_full(p); if (!owner_relock) { ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); return 0; } } } if (p->vrtp && (quality = ast_rtp_instance_get_quality(p->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { if (p->do_history) { append_history(p, "RTCPvideo", "Quality:%s", quality); } if (p->owner) { pbx_builtin_setvar_helper(p->owner, "RTPVIDEOQOS", quality); } } if (p->trtp && (quality = ast_rtp_instance_get_quality(p->trtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) { if (p->do_history) { append_history(p, "RTCPtext", "Quality:%s", quality); } if (p->owner) { pbx_builtin_setvar_helper(p->owner, "RTPTEXTQOS", quality); } } stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */ if (p->stimer) { stop_session_timer(p); /* Stop Session-Timer */ } if (!ast_strlen_zero(sip_get_header(req, "Also"))) { ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n", ast_sockaddr_stringify(&p->recv)); if (ast_strlen_zero(p->context)) ast_string_field_set(p, context, sip_cfg.default_context); res = get_also_info(p, req); if (!res) { c = p->owner; if (c) { if (peer_channel) { RAII_VAR(struct ast_channel *, owner_relock, NULL, ast_channel_cleanup); char *local_context = ast_strdupa(p->context); char *local_refer_to = ast_strdupa(p->refer->refer_to); /* Grab a reference to p->owner to prevent it from going away */ ast_channel_ref(c); /* Don't actually hangup here... */ ast_queue_unhold(c); ast_channel_unlock(c); /* async_goto can do a masquerade, no locks can be held during a masq */ sip_pvt_unlock(p); ast_async_goto(peer_channel, local_context, local_refer_to, 1); owner_relock = sip_pvt_lock_full(p); ast_channel_cleanup(c); if (!owner_relock) { ast_debug(3, "Unable to reacquire owner channel lock, channel is gone\n"); return 0; } } else { ast_queue_hangup(p->owner); } } } else { ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_sockaddr_stringify(&p->recv)); if (p->owner) ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR); } } else if (p->owner) { sip_queue_hangup_cause(p, 0); sip_scheddestroy_final(p, DEFAULT_TRANS_TIMEOUT); ast_debug(3, "Received bye, issuing owner hangup\n"); } else { sip_scheddestroy_final(p, DEFAULT_TRANS_TIMEOUT); ast_debug(3, "Received bye, no owner, selfdestruct soon.\n"); } ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); /* Find out what they require */ required = sip_get_header(req, "Require"); if (!ast_strlen_zero(required)) { char unsupported[256] = { 0, }; parse_sip_options(required, unsupported, ARRAY_LEN(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)) { transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, unsupported); ast_log(LOG_WARNING, "Received SIP BYE with unsupported required extension: required:%s unsupported:%s\n", required, unsupported); } else { transmit_response(p, "200 OK", req); } } else { transmit_response(p, "200 OK", req); } /* Destroy any pending invites so we won't try to do another * scheduled reINVITE. */ AST_SCHED_DEL_UNREF(sched, p->waitid, dialog_unref(p, "decrement refcount from sip_destroy because waitid won't be scheduled")); return 1; } /*! \brief Handle incoming MESSAGE request */ static int handle_request_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e) { if (!req->ignore) { if (req->debug) ast_verbose("Receiving message!\n"); receive_message(p, req, addr, e); } else transmit_response(p, "202 Accepted", req); return 1; } static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from); static const struct ast_msg_tech sip_msg_tech = { .name = "sip", .msg_send = sip_msg_send, }; /*! * \internal * \brief Check if the given header name is blocked. * * \details Determine if the given header name from the user is * blocked for outgoing MESSAGE packets. * * \param header_name Name of header to see if it is blocked. * * \retval TRUE if the given header is blocked. */ static int block_msg_header(const char *header_name) { int idx; /* * Don't block Content-Type or Max-Forwards headers because the * user can override them. */ static const char *hdr[] = { "To", "From", "Via", "Route", "Contact", "Call-ID", "CSeq", "Allow", "Content-Length", "Request-URI", }; for (idx = 0; idx < ARRAY_LEN(hdr); ++idx) { if (!strcasecmp(header_name, hdr[idx])) { /* Block addition of this header. */ return 1; } } return 0; } static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from) { struct sip_pvt *pvt; int res; char *to_uri; char *to_host; char *to_user; const char *var; const char *val; struct ast_msg_var_iterator *iter; struct sip_peer *peer_ptr; if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_MESSAGE, NULL, NULL))) { return -1; } for (iter = ast_msg_var_iterator_init(msg); ast_msg_var_iterator_next(msg, iter, &var, &val); ast_msg_var_unref_current(iter)) { if (!strcasecmp(var, "Request-URI")) { ast_string_field_set(pvt, fullcontact, val); break; } } ast_msg_var_iterator_destroy(iter); to_uri = ast_strdupa(to); to_uri = get_in_brackets(to_uri); parse_uri(to_uri, "sip:,sips:", &to_user, NULL, &to_host, NULL); if (ast_strlen_zero(to_host)) { ast_log(LOG_WARNING, "MESSAGE(to) is invalid for SIP - '%s'\n", to); dialog_unlink_all(pvt); dialog_unref(pvt, "MESSAGE(to) is invalid for SIP"); return -1; } if (!ast_strlen_zero(from)) { if ((peer_ptr = sip_find_peer(from, NULL, 0, 1, 0, 0))) { ast_string_field_set(pvt, fromname, S_OR(peer_ptr->cid_name, peer_ptr->name)); ast_string_field_set(pvt, fromuser, S_OR(peer_ptr->cid_num, peer_ptr->name)); sip_unref_peer(peer_ptr, "sip_unref_peer, from sip_msg_send, sip_find_peer"); } else if (strchr(from, '<')) { /* from is callerid-style */ char *sender; char *name = NULL, *location = NULL, *user = NULL, *domain = NULL; sender = ast_strdupa(from); ast_callerid_parse(sender, &name, &location); if (ast_strlen_zero(location)) { /* 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. */ location = name; name = NULL; } ast_string_field_set(pvt, fromname, name); if (strchr(location, ':')) { /* Must be a URI */ parse_uri(location, "sip:,sips:", &user, NULL, &domain, NULL); SIP_PEDANTIC_DECODE(user); SIP_PEDANTIC_DECODE(domain); extract_host_from_hostport(&domain); 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, location); } } else { /* assume we just have the name, use defaults for the rest */ ast_string_field_set(pvt, fromname, from); } } sip_pvt_lock(pvt); /* Look up the host to contact */ if (create_addr(pvt, to_host, NULL, TRUE)) { sip_pvt_unlock(pvt); dialog_unlink_all(pvt); dialog_unref(pvt, "create_addr failed sending a MESSAGE"); return -1; } if (!ast_strlen_zero(to_user)) { ast_string_field_set(pvt, username, to_user); } ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt); build_via(pvt); ast_set_flag(&pvt->flags[0], SIP_OUTGOING); /* XXX Does pvt->expiry need to be set? */ /* Save additional MESSAGE headers in case of authentication request. */ for (iter = ast_msg_var_iterator_init(msg); ast_msg_var_iterator_next(msg, iter, &var, &val); ast_msg_var_unref_current(iter)) { if (!strcasecmp(var, "Max-Forwards")) { /* Decrement Max-Forwards for SIP loop prevention. */ if (sscanf(val, "%30d", &pvt->maxforwards) != 1 || pvt->maxforwards < 1) { ast_msg_var_iterator_destroy(iter); sip_pvt_unlock(pvt); dialog_unlink_all(pvt); dialog_unref(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 (block_msg_header(var)) { /* Block addition of this header. */ continue; } add_msg_header(pvt, var, val); } ast_msg_var_iterator_destroy(iter); ast_string_field_set(pvt, msg_body, ast_msg_get_body(msg)); res = transmit_message(pvt, 1, 0); sip_pvt_unlock(pvt); sip_scheddestroy(pvt, DEFAULT_TRANS_TIMEOUT); dialog_unref(pvt, "sent a MESSAGE"); return res; } static enum sip_publish_type determine_sip_publish_type(struct sip_request *req, const char * const event, const char * const etag, const char * const expires, int *expires_int) { int etag_present = !ast_strlen_zero(etag); int body_present = req->lines > 0; ast_assert(expires_int != NULL); if (ast_strlen_zero(expires)) { /* Section 6, item 4, second bullet point of RFC 3903 says to * use a locally-configured default expiration if none is provided * in the request */ *expires_int = DEFAULT_PUBLISH_EXPIRES; } else if (sscanf(expires, "%30d", expires_int) != 1) { return SIP_PUBLISH_UNKNOWN; } if (*expires_int == 0) { return SIP_PUBLISH_REMOVE; } else if (!etag_present && body_present) { return SIP_PUBLISH_INITIAL; } else if (etag_present && !body_present) { return SIP_PUBLISH_REFRESH; } else if (etag_present && body_present) { return SIP_PUBLISH_MODIFY; } return SIP_PUBLISH_UNKNOWN; } #ifdef HAVE_LIBXML2 static int pidf_validate_tuple(struct ast_xml_node *tuple_node) { const char *id; int status_found = FALSE; struct ast_xml_node *tuple_children; struct ast_xml_node *tuple_children_iterator; /* Tuples have to have an id attribute or they're invalid */ if (!(id = ast_xml_get_attribute(tuple_node, "id"))) { ast_log(LOG_WARNING, "Tuple XML element has no attribute 'id'\n"); return FALSE; } /* We don't care what it actually is, just that it's there */ ast_xml_free_attr(id); /* This is a tuple. It must have a status element */ if (!(tuple_children = ast_xml_node_get_children(tuple_node))) { /* The tuple has no children. It sucks */ ast_log(LOG_WARNING, "Tuple XML element has no child elements\n"); return FALSE; } for (tuple_children_iterator = tuple_children; tuple_children_iterator; tuple_children_iterator = ast_xml_node_get_next(tuple_children_iterator)) { /* Similar to the wording used regarding tuples, the status element should appear * first. However, we will once again relax things and accept the status at any * position. We will enforce that only a single status element can be present. */ if (strcmp(ast_xml_node_get_name(tuple_children_iterator), "status")) { /* Not the status, we don't care */ continue; } if (status_found == TRUE) { /* THERE CAN BE ONLY ONE!!! */ ast_log(LOG_WARNING, "Multiple status elements found in tuple. Only one allowed\n"); return FALSE; } status_found = TRUE; } return status_found; } static int pidf_validate_presence(struct ast_xml_doc *doc) { struct ast_xml_node *presence_node = ast_xml_get_root(doc); struct ast_xml_node *child_nodes; struct ast_xml_node *node_iterator; struct ast_xml_ns *ns; const char *entity; const char *namespace; const char presence_namespace[] = "urn:ietf:params:xml:ns:pidf"; if (!presence_node) { ast_log(LOG_WARNING, "Unable to retrieve root node of the XML document\n"); return FALSE; } /* Okay, we managed to open the document! YAY! Now, let's start making sure it's all PIDF-ified * correctly. */ if (strcmp(ast_xml_node_get_name(presence_node), "presence")) { ast_log(LOG_WARNING, "Root node of PIDF document is not 'presence'. Invalid\n"); return FALSE; } /* The presence element must have an entity attribute and an xmlns attribute. Furthermore * the xmlns attribute must be "urn:ietf:params:xml:ns:pidf" */ if (!(entity = ast_xml_get_attribute(presence_node, "entity"))) { ast_log(LOG_WARNING, "Presence element of PIDF document has no 'entity' attribute\n"); return FALSE; } /* We're not interested in what the entity is, just that it exists */ ast_xml_free_attr(entity); if (!(ns = ast_xml_find_namespace(doc, presence_node, NULL))) { ast_log(LOG_WARNING, "Couldn't find default namespace...\n"); return FALSE; } namespace = ast_xml_get_ns_href(ns); if (ast_strlen_zero(namespace) || strcmp(namespace, presence_namespace)) { ast_log(LOG_WARNING, "PIDF document has invalid namespace value %s\n", namespace); return FALSE; } if (!(child_nodes = ast_xml_node_get_children(presence_node))) { ast_log(LOG_WARNING, "PIDF document has no elements as children of 'presence'. Invalid\n"); return FALSE; } /* Check for tuple elements. RFC 3863 says that PIDF documents can have any number of * tuples, including 0. The big thing here is that if there are tuple elements present, * they have to have a single status element within. * * The RFC is worded such that tuples should appear as the first elements as children of * the presence element. However, we'll be accepting of documents which may place other elements * before the tuple(s). */ for (node_iterator = child_nodes; node_iterator; node_iterator = ast_xml_node_get_next(node_iterator)) { if (strcmp(ast_xml_node_get_name(node_iterator), "tuple")) { /* Not a tuple. We don't give a rat's hind quarters */ continue; } if (pidf_validate_tuple(node_iterator) == FALSE) { ast_log(LOG_WARNING, "Unable to validate tuple\n"); return FALSE; } } return TRUE; } /*! * \brief Makes sure that body is properly formatted PIDF * * Specifically, we check that the document has a "presence" element * at the root and that within that, there is at least one "tuple" element * that contains a "status" element. * * XXX This function currently assumes a default namespace is used. Of course * if you're not using a default namespace, you're probably a stupid jerk anyway. * * \param req The SIP request to check * \param[out] pidf_doc The validated PIDF doc. * \retval FALSE The XML was malformed or the basic PIDF structure was marred * \retval TRUE The PIDF document is of a valid format */ static int sip_pidf_validate(struct sip_request *req, struct ast_xml_doc **pidf_doc) { struct ast_xml_doc *doc; const char *content_type = sip_get_header(req, "Content-Type"); char *pidf_body; int res; if (ast_strlen_zero(content_type) || strcmp(content_type, "application/pidf+xml")) { ast_log(LOG_WARNING, "Content type is not PIDF\n"); return FALSE; } if (!(pidf_body = get_content(req))) { ast_log(LOG_WARNING, "Unable to get PIDF body\n"); return FALSE; } if (!(doc = ast_xml_read_memory(pidf_body, strlen(pidf_body)))) { ast_log(LOG_WARNING, "Unable to open XML PIDF document. Is it malformed?\n"); return FALSE; } res = pidf_validate_presence(doc); if (res == TRUE) { *pidf_doc = doc; } else { ast_xml_close(doc); } return res; } static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry) { const char *uri = REQ_OFFSET_TO_STR(req, rlpart2); struct ast_cc_agent *agent; struct sip_cc_agent_pvt *agent_pvt; struct ast_xml_doc *pidf_doc = NULL; const char *basic_status = NULL; struct ast_xml_node *presence_node; struct ast_xml_node *presence_children; struct ast_xml_node *tuple_node; struct ast_xml_node *tuple_children; struct ast_xml_node *status_node; struct ast_xml_node *status_children; struct ast_xml_node *basic_node; int res = 0; if (!((agent = find_sip_cc_agent_by_notify_uri(uri)) || (agent = find_sip_cc_agent_by_subscribe_uri(uri)))) { ast_log(LOG_WARNING, "Could not find agent using uri '%s'\n", uri); transmit_response(pvt, "412 Conditional Request Failed", req); return -1; } agent_pvt = agent->private_data; if (sip_pidf_validate(req, &pidf_doc) == FALSE) { res = -1; goto cc_publish_cleanup; } /* It's important to note that the PIDF validation routine has no knowledge * of what we specifically want in this instance. A valid PIDF document could * have no tuples, or it could have tuples whose status element has no basic * element contained within. While not violating the PIDF spec, these are * insufficient for our needs in this situation */ presence_node = ast_xml_get_root(pidf_doc); if (!(presence_children = ast_xml_node_get_children(presence_node))) { ast_log(LOG_WARNING, "No tuples within presence element.\n"); res = -1; goto cc_publish_cleanup; } if (!(tuple_node = ast_xml_find_element(presence_children, "tuple", NULL, NULL))) { ast_log(LOG_NOTICE, "Couldn't find tuple node?\n"); res = -1; goto cc_publish_cleanup; } /* We already made sure that the tuple has a status node when we validated the PIDF * document earlier. So there's no need to enclose this operation in an if statement. */ tuple_children = ast_xml_node_get_children(tuple_node); /* coverity[null_returns: FALSE] */ status_node = ast_xml_find_element(tuple_children, "status", NULL, NULL); if (!(status_children = ast_xml_node_get_children(status_node))) { ast_log(LOG_WARNING, "No basic elements within status element.\n"); res = -1; goto cc_publish_cleanup; } if (!(basic_node = ast_xml_find_element(status_children, "basic", NULL, NULL))) { ast_log(LOG_WARNING, "Couldn't find basic node?\n"); res = -1; goto cc_publish_cleanup; } basic_status = ast_xml_get_text(basic_node); if (ast_strlen_zero(basic_status)) { ast_log(LOG_NOTICE, "NOthing in basic node?\n"); res = -1; goto cc_publish_cleanup; } if (!strcmp(basic_status, "open")) { agent_pvt->is_available = TRUE; ast_cc_agent_caller_available(agent->core_id, "Received PUBLISH stating SIP caller %s is available", agent->device_name); } else if (!strcmp(basic_status, "closed")) { agent_pvt->is_available = FALSE; ast_cc_agent_caller_busy(agent->core_id, "Received PUBLISH stating SIP caller %s is busy", agent->device_name); } else { ast_log(LOG_NOTICE, "Invalid content in basic element: %s\n", basic_status); } cc_publish_cleanup: if (basic_status) { ast_xml_free_text(basic_status); } if (pidf_doc) { ast_xml_close(pidf_doc); } ao2_ref(agent, -1); if (res) { transmit_response(pvt, "400 Bad Request", req); } return res; } #endif /* HAVE_LIBXML2 */ static int handle_sip_publish_initial(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const int expires) { struct sip_esc_entry *esc_entry = create_esc_entry(esc, req, expires); int res = 0; if (!esc_entry) { transmit_response(p, "503 Internal Server Failure", req); return -1; } if (esc->callbacks->initial_handler) { res = esc->callbacks->initial_handler(p, req, esc, esc_entry); } if (!res) { transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 0); } ao2_ref(esc_entry, -1); return res; } static int handle_sip_publish_refresh(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag, const int expires) { struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc); int expires_ms = expires * 1000; int res = 0; if (!esc_entry) { transmit_response(p, "412 Conditional Request Failed", req); return -1; } AST_SCHED_REPLACE_UNREF(esc_entry->sched_id, sched, expires_ms, publish_expire, esc_entry, ao2_ref(_data, -1), ao2_ref(esc_entry, -1), ao2_ref(esc_entry, +1)); if (esc->callbacks->refresh_handler) { res = esc->callbacks->refresh_handler(p, req, esc, esc_entry); } if (!res) { transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1); } ao2_ref(esc_entry, -1); return res; } static int handle_sip_publish_modify(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag, const int expires) { struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc); int expires_ms = expires * 1000; int res = 0; if (!esc_entry) { transmit_response(p, "412 Conditional Request Failed", req); return -1; } AST_SCHED_REPLACE_UNREF(esc_entry->sched_id, sched, expires_ms, publish_expire, esc_entry, ao2_ref(_data, -1), ao2_ref(esc_entry, -1), ao2_ref(esc_entry, +1)); if (esc->callbacks->modify_handler) { res = esc->callbacks->modify_handler(p, req, esc, esc_entry); } if (!res) { transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1); } ao2_ref(esc_entry, -1); return res; } static int handle_sip_publish_remove(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag) { struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc); int res = 0; if (!esc_entry) { transmit_response(p, "412 Conditional Request Failed", req); return -1; } AST_SCHED_DEL(sched, esc_entry->sched_id); /* Scheduler's ref of the esc_entry */ ao2_ref(esc_entry, -1); if (esc->callbacks->remove_handler) { res = esc->callbacks->remove_handler(p, req, esc, esc_entry); } if (!res) { transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1); } /* Ref from finding the esc_entry earlier in function */ ao2_unlink(esc->compositor, esc_entry); ao2_ref(esc_entry, -1); return res; } static int handle_request_publish(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const uint32_t seqno, const char *uri) { const char *etag = sip_get_header(req, "SIP-If-Match"); const char *event = sip_get_header(req, "Event"); struct event_state_compositor *esc; enum sip_publish_type publish_type; const char *expires_str = sip_get_header(req, "Expires"); int expires_int; int auth_result; int handler_result = -1; if (ast_strlen_zero(event)) { transmit_response(p, "489 Bad Event", req); pvt_set_needdestroy(p, "missing Event: header"); return -1; } if (!(esc = get_esc(event))) { transmit_response(p, "489 Bad Event", req); pvt_set_needdestroy(p, "unknown event package in publish"); return -1; } auth_result = check_user(p, req, SIP_PUBLISH, uri, XMIT_UNRELIABLE, addr); if (auth_result == AUTH_CHALLENGE_SENT) { p->lastinvite = seqno; return 0; } else if (auth_result < 0) { ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", sip_get_header(req, "From")); transmit_response(p, "403 Forbidden", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_string_field_set(p, theirtag, NULL); return 0; } else if (auth_result == AUTH_SUCCESSFUL && p->lastinvite) { /* We need to stop retransmitting the 401 */ __sip_ack(p, p->lastinvite, 1, 0); } publish_type = determine_sip_publish_type(req, event, etag, expires_str, &expires_int); if (expires_int > max_expiry) { expires_int = max_expiry; } else if (expires_int < min_expiry && expires_int > 0) { transmit_response_with_minexpires(p, "423 Interval too small", req, min_expiry); pvt_set_needdestroy(p, "Expires is less that the min expires allowed."); return 0; } p->expiry = expires_int; /* It is the responsibility of these handlers to formulate any response * sent for a PUBLISH */ switch (publish_type) { case SIP_PUBLISH_UNKNOWN: transmit_response(p, "400 Bad Request", req); break; case SIP_PUBLISH_INITIAL: handler_result = handle_sip_publish_initial(p, req, esc, expires_int); break; case SIP_PUBLISH_REFRESH: handler_result = handle_sip_publish_refresh(p, req, esc, etag, expires_int); break; case SIP_PUBLISH_MODIFY: handler_result = handle_sip_publish_modify(p, req, esc, etag, expires_int); break; case SIP_PUBLISH_REMOVE: handler_result = handle_sip_publish_remove(p, req, esc, etag); break; default: transmit_response(p, "400 Impossible Condition", req); break; } if (!handler_result && p->expiry > 0) { sip_scheddestroy(p, (p->expiry + 10) * 1000); } else { pvt_set_needdestroy(p, "forcing expiration"); } return handler_result; } /*! * \internal * \brief Subscribe to MWI events for the specified peer * * \note The peer cannot be locked during this method. sip_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. */ static void add_peer_mwi_subs(struct sip_peer *peer) { struct sip_mailbox *mailbox; AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { struct stasis_topic *mailbox_specific_topic; mailbox->event_sub = stasis_unsubscribe(mailbox->event_sub); mailbox_specific_topic = ast_mwi_topic(mailbox->id); if (mailbox_specific_topic) { char *peer_name = ast_strdup(peer->name); if (!peer_name) { return; } mailbox->event_sub = stasis_subscribe_pool(mailbox_specific_topic, mwi_event_cb, peer_name); } } } static int handle_cc_subscribe(struct sip_pvt *p, struct sip_request *req) { const char *uri = REQ_OFFSET_TO_STR(req, rlpart2); char *param_separator; struct ast_cc_agent *agent; struct sip_cc_agent_pvt *agent_pvt; const char *expires_str = sip_get_header(req, "Expires"); int expires = -1; /* Just need it to be non-zero */ if (!ast_strlen_zero(expires_str)) { sscanf(expires_str, "%30d", &expires); } if ((param_separator = strchr(uri, ';'))) { *param_separator = '\0'; } p->subscribed = CALL_COMPLETION; if (!(agent = find_sip_cc_agent_by_subscribe_uri(uri))) { if (!expires) { /* Typically, if a 0 Expires reaches us and we can't find * the corresponding agent, it means that the CC transaction * has completed and so the calling side is just trying to * clean up its subscription. We'll just respond with a * 200 OK and be done with it */ transmit_response(p, "200 OK", req); return 0; } ast_log(LOG_WARNING, "Invalid URI '%s' in CC subscribe\n", uri); transmit_response(p, "404 Not Found", req); return -1; } agent_pvt = agent->private_data; if (!expires) { /* We got sent a SUBSCRIBE and found an agent. This means that CC * is being canceled. */ ast_cc_failed(agent->core_id, "CC is being canceled by %s", agent->device_name); transmit_response(p, "200 OK", req); ao2_ref(agent, -1); return 0; } agent_pvt->subscribe_pvt = dialog_ref(p, "SIP CC agent gains reference to subscription dialog"); ast_cc_agent_accept_request(agent->core_id, "SIP caller %s has requested CC via SUBSCRIBE", agent->device_name); /* We don't send a response here. That is done in the agent's ack callback or in the * agent destructor, should a failure occur before we have responded */ ao2_ref(agent, -1); return 0; } /*! \brief Handle incoming SUBSCRIBE request */ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) { int res = 0; struct sip_peer *authpeer = NULL; char *event = ast_strdupa(sip_get_header(req, "Event")); /* Get Event package name */ int resubscribe = (p->subscribed != NONE) && !req->ignore; char *options; if (p->initreq.headers) { /* We already have a dialog */ if (p->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 */ transmit_response(p, "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", p->callid, sip_methods[p->initreq.method].text); return 0; } else if (req->debug) { if (resubscribe) ast_debug(1, "Got a re-subscribe on existing subscription %s\n", p->callid); else ast_debug(1, "Got a new subscription %s (possibly with auth) or retransmission\n", p->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_cfg.allowsubscribe) { transmit_response(p, "403 Forbidden (policy)", req); pvt_set_needdestroy(p, "forbidden"); return 0; } if (!req->ignore && !resubscribe) { /* Set up dialog, new subscription */ const char *to = sip_get_header(req, "To"); char totag[128]; set_pvt_allowed_methods(p, 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) && gettag(req, "To", totag, sizeof(totag))) { if (req->debug) ast_verbose("Received resubscription for a dialog we no longer know about. Telling remote side to subscribe again.\n"); transmit_response(p, "481 Subscription does not exist", req); pvt_set_needdestroy(p, "subscription does not exist"); return 0; } /* Use this as the basis */ if (req->debug) ast_verbose("Creating new subscription\n"); copy_request(&p->initreq, req); if (sipdebug) ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid); check_via(p, req); build_route(p, req, 0, 0); } else if (req->debug && req->ignore) ast_verbose("Ignoring this SUBSCRIBE request\n"); /* Find parameters to Event: header value and remove them for now */ if (ast_strlen_zero(event)) { transmit_response(p, "489 Bad Event", req); ast_debug(2, "Received SIP subscribe for unknown event package: \n"); pvt_set_needdestroy(p, "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 (p->subscribed == NONE || resubscribe) { res = check_user_full(p, req, SIP_SUBSCRIBE, e, XMIT_UNRELIABLE, addr, &authpeer); /* if an authentication response was sent, we are done here */ if (res == AUTH_CHALLENGE_SENT) /* authpeer = NULL here */ return 0; if (res != AUTH_SUCCESSFUL) { ast_log(LOG_NOTICE, "Failed to authenticate device %s for SUBSCRIBE\n", sip_get_header(req, "From")); transmit_response(p, "403 Forbidden", req); pvt_set_needdestroy(p, "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(&p->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)) { transmit_response(p, "403 Forbidden (policy)", req); pvt_set_needdestroy(p, "subscription not allowed"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 1)"); } return 0; } /* Get full contact header - this needs to be used as a request URI in NOTIFY's */ parse_ok_contact(p, req); build_contact(p); /* Initialize tag for new subscriptions */ if (ast_strlen_zero(p->tag)) { make_our_tag(p); } if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842 */ int gotdest; const char *accept; int start = 0; enum subscriptiontype subscribed = NONE; const char *unknown_accept = NULL; /* Get destination right away */ gotdest = get_destination(p, NULL, NULL); if (gotdest != SIP_GET_DEST_EXTEN_FOUND) { if (gotdest == SIP_GET_DEST_INVALID_URI) { transmit_response(p, "416 Unsupported URI scheme", req); } else { transmit_response(p, "404 Not Found", req); } pvt_set_needdestroy(p, "subscription target not found"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 2)"); } return 0; } /* Header from Xten Eye-beam Accept: multipart/related, application/rlmi+xml, application/pidf+xml, application/xpidf+xml */ accept = __get_header(req, "Accept", &start); while ((subscribed == NONE) && !ast_strlen_zero(accept)) { if (strstr(accept, "application/pidf+xml")) { if (strstr(p->useragent, "Polycom")) { subscribed = XPIDF_XML; /* Older versions of Polycom firmware will claim pidf+xml, but really they only support xpidf+xml */ } else { subscribed = PIDF_XML; /* RFC 3863 format */ } } else if (strstr(accept, "application/dialog-info+xml")) { subscribed = DIALOG_INFO_XML; /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ } else if (strstr(accept, "application/cpim-pidf+xml")) { subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ } else if (strstr(accept, "application/xpidf+xml")) { subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */ } else { unknown_accept = accept; } /* check to see if there is another Accept header present */ accept = __get_header(req, "Accept", &start); } if (!start) { if (p->subscribed == NONE) { /* if the subscribed field is not already set, and there is no accept header... */ transmit_response(p, "489 Bad Event", req); ast_log(LOG_WARNING,"SUBSCRIBE failure: no Accept header: pvt: " "stateid: %d, laststate: %d, dialogver: %u, subscribecont: " "'%s', subscribeuri: '%s'\n", p->stateid, p->laststate, p->dialogver, p->subscribecontext, p->subscribeuri); pvt_set_needdestroy(p, "no Accept header"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 2)"); } return 0; } /* if p->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 == NONE) { /* Can't find a format for events that we know about */ char buf[200]; if (!ast_strlen_zero(unknown_accept)) { snprintf(buf, sizeof(buf), "489 Bad Event (format %s)", unknown_accept); } else { snprintf(buf, sizeof(buf), "489 Bad Event"); } transmit_response(p, buf, req); 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)p->subscribed, p->stateid, p->laststate, p->dialogver, p->subscribecontext, p->subscribeuri); pvt_set_needdestroy(p, "unrecognized format"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 2)"); } return 0; } else { p->subscribed = subscribed; } } else if (!strcmp(event, "message-summary")) { int start = 0; int found_supported = 0; const char *accept; accept = __get_header(req, "Accept", &start); while (!found_supported && !ast_strlen_zero(accept)) { found_supported = strcmp(accept, "application/simple-message-summary") ? 0 : 1; if (!found_supported) { ast_debug(3, "Received SIP mailbox subscription for unknown format: %s\n", accept); } accept = __get_header(req, "Accept", &start); } /* If !start, there is no Accept header at all */ if (start && !found_supported) { /* Format requested that we do not support */ transmit_response(p, "406 Not Acceptable", req); ast_debug(2, "Received SIP mailbox subscription for unknown format: %s\n", accept); pvt_set_needdestroy(p, "unknown format"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from 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) { transmit_response(p, "404 Not found", req); } else { transmit_response(p, "404 Not found (no mailbox)", req); ast_log(LOG_NOTICE, "Received SIP subscribe for peer without mailbox: %s\n", S_OR(authpeer->name, "")); } pvt_set_needdestroy(p, "received 404 response"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 3)"); } return 0; } p->subscribed = MWI_NOTIFICATION; if (ast_test_flag(&authpeer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY)) { ao2_unlock(p); add_peer_mwi_subs(authpeer); ao2_lock(p); } if (authpeer->mwipvt != p) { /* Destroy old PVT if this is a new one */ /* We only allow one subscription per peer */ if (authpeer->mwipvt) { dialog_unlink_all(authpeer->mwipvt); authpeer->mwipvt = dialog_unref(authpeer->mwipvt, "unref dialog authpeer->mwipvt"); } authpeer->mwipvt = dialog_ref(p, "setting peers' mwipvt to p"); } if (p->relatedpeer != authpeer) { if (p->relatedpeer) { sip_unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr"); } p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); } /* Do not release authpeer here */ } else if (!strcmp(event, "call-completion")) { handle_cc_subscribe(p, req); } else { /* At this point, Asterisk does not understand the specified event */ transmit_response(p, "489 Bad Event", req); ast_debug(2, "Received SIP subscribe for unknown event package: %s\n", event); pvt_set_needdestroy(p, "unknown event package"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 5)"); } return 0; } if (!req->ignore) { p->lastinvite = seqno; } if (!p->needdestroy) { p->expiry = atoi(sip_get_header(req, "Expires")); /* check if the requested expiry-time is within the approved limits from sip.conf */ if (p->expiry > max_subexpiry) { p->expiry = max_subexpiry; } else if (p->expiry < min_subexpiry && p->expiry > 0) { transmit_response_with_minexpires(p, "423 Interval too small", req, min_subexpiry); 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", p->exten, p->context, p->expiry, min_subexpiry); pvt_set_needdestroy(p, "Expires is less that the min expires allowed."); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 6)"); } return 0; } if (sipdebug) { const char *action = p->expiry > 0 ? "Adding" : "Removing"; if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer) { ast_debug(2, "%s subscription for mailbox notification - peer %s\n", action, p->relatedpeer->name); } else if (p->subscribed == CALL_COMPLETION) { ast_debug(2, "%s CC subscription for peer %s\n", action, p->username); } else { ast_debug(2, "%s subscription for extension %s context %s for peer %s\n", action, p->exten, p->context, p->username); } } if (p->autokillid > -1 && sip_cancel_destroy(p)) /* Remove subscription expiry for renewals */ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n"); if (p->expiry > 0) sip_scheddestroy(p, (p->expiry + 10) * 1000); /* Set timer for destruction of call at expiration */ if (p->subscribed == MWI_NOTIFICATION) { ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response(p, "200 OK", req); if (p->relatedpeer) { /* Send first notification */ struct sip_peer *peer = p->relatedpeer; sip_ref_peer(peer, "ensure a peer ref is held during MWI sending"); ao2_unlock(p); sip_send_mwi_to_peer(peer, 0); ao2_lock(p); sip_unref_peer(peer, "release a peer ref now that MWI is sent"); } } else if (p->subscribed != CALL_COMPLETION) { struct state_notify_data data = { 0, }; char *subtype = NULL; char *message = NULL; struct ao2_container *device_state_info = NULL; if (p->expiry > 0 && !resubscribe) { /* Add subscription for extension state from the PBX core */ if (p->stateid != -1) { ast_extension_state_del(p->stateid, cb_extensionstate); } dialog_ref(p, "copying dialog ptr into extension state struct"); p->stateid = ast_extension_state_add_destroy_extended(p->context, p->exten, cb_extensionstate, cb_extensionstate_destroy, p); if (p->stateid == -1) { dialog_unref(p, "copying dialog ptr into extension state struct failed"); } } sip_pvt_unlock(p); data.state = ast_extension_state_extended(NULL, p->context, p->exten, &device_state_info); sip_pvt_lock(p); if (data.state < 0) { ao2_cleanup(device_state_info); if (p->expiry > 0) { ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", p->exten, p->context, ast_sockaddr_stringify(&p->sa)); } transmit_response(p, "404 Not found", req); pvt_set_needdestroy(p, "no extension for SUBSCRIBE"); if (authpeer) { sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 6)"); } return 0; } if (allow_notify_user_presence(p)) { data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); data.presence_subtype = subtype; data.presence_message = message; } ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response(p, "200 OK", req); /* RFC 3265: A notification must be sent on every subscribe, so force it */ data.device_state_info = device_state_info; if (data.state & AST_EXTENSION_RINGING) { /* save last_ringing_channel_time if this state really contains a ringing channel * because extensionstate_update() doesn't do it if forced */ struct ast_channel *ringing = find_ringing_channel(data.device_state_info, p); if (ringing) { p->last_ringing_channel_time = ast_channel_creationtime(ringing); ao2_ref(ringing, -1); } /* 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. */ } extensionstate_update(p->context, p->exten, &data, p, TRUE); append_history(p, "Subscribestatus", "%s", ast_extension_state2str(data.state)); /* hide the 'complete' exten/context in the refer_to field for later display */ ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->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 (!p->expiry) { pvt_set_needdestroy(p, "forcing expiration"); } } if (authpeer) { sip_unref_peer(authpeer, "unref pointer into (*authpeer)"); } return 1; } /*! \brief Handle incoming REGISTER request */ static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e) { enum check_auth_result res; /* If this is not the intial request, and the initial request isn't * a register, something screwy happened, so bail */ if (p->initreq.headers && p->initreq.method != SIP_REGISTER) { ast_log(LOG_WARNING, "Ignoring spurious REGISTER with Call-ID: %s\n", p->callid); return -1; } /* Use this as the basis */ copy_request(&p->initreq, req); if (sipdebug) ast_debug(4, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid); check_via(p, req); if ((res = register_verify(p, addr, req, e)) < 0) { const char *reason; switch (res) { case AUTH_SECRET_FAILED: reason = "Wrong password"; break; case AUTH_USERNAME_MISMATCH: reason = "Username/auth name mismatch"; break; case AUTH_NOT_FOUND: reason = "No matching peer found"; break; case AUTH_UNKNOWN_DOMAIN: reason = "Not a local domain"; break; case AUTH_PEER_NOT_DYNAMIC: reason = "Peer is not supposed to register"; break; case AUTH_ACL_FAILED: reason = "Device does not match ACL"; break; case AUTH_BAD_TRANSPORT: reason = "Device not configured to use this transport type"; break; case 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_get_header(req, "To"), ast_sockaddr_stringify(addr), reason); append_history(p, "RegRequest", "Failed : Account %s : %s", sip_get_header(req, "To"), reason); } else { req->authenticated = 1; append_history(p, "RegRequest", "Succeeded : Account %s", sip_get_header(req, "To")); } if (res != AUTH_CHALLENGE_SENT) { /* Destroy the session, but keep us around for just a bit in case they don't get our 200 OK */ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); } return res; } /*! * \brief Handle incoming SIP requests (methods) * \note * This is where all incoming requests go first. * \note * called with p and p->owner locked */ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock) { /* Called with p->lock held, as well as p->owner->lock if appropriate, keeping things relatively static */ const char *cmd; const char *cseq; const char *useragent; const char *via; const char *callid; int via_pos = 0; uint32_t seqno; int len; int respid; int res = 0; const char *e; int error = 0; int oldmethod = p->method; int acked = 0; /* 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_get_header(req, "Cseq"); cmd = REQ_OFFSET_TO_STR(req, header[0]); /* Save the via_pos so we can check later that responses only have 1 Via header */ via = __get_header(req, "Via", &via_pos); /* This must exist already because we've called find_call by now */ callid = sip_get_header(req, "Call-ID"); /* Must have Cseq */ if (ast_strlen_zero(cmd) || 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 = 1; } if (!error && sscanf(cseq, "%30u%n", &seqno, &len) != 1) { ast_log(LOG_ERROR, "No seqno in '%s'. Dropping incomplete message.\n", cmd); error = 1; } if (error) { if (!p->initreq.headers) { /* New call */ pvt_set_needdestroy(p, "no headers"); } return -1; } /* Get the command XXX */ cmd = REQ_OFFSET_TO_STR(req, rlpart1); e = ast_skip_blanks(REQ_OFFSET_TO_STR(req, rlpart2)); /* Save useragent of the client */ useragent = sip_get_header(req, "User-Agent"); if (!ast_strlen_zero(useragent)) ast_string_field_set(p, 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 (ast_strlen_zero(e)) { return 0; } if (sscanf(e, "%30d %n", &respid, &len) != 1) { ast_log(LOG_WARNING, "Invalid response: '%s'\n", e); return 0; } if (respid <= 0) { ast_log(LOG_WARNING, "Invalid SIP response code: '%d'\n", respid); 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(__get_header(req, "via", &via_pos))) { ast_log(LOG_WARNING, "Misrouted SIP response '%s' with Call-ID '%s', too many vias\n", e, callid); return 0; } if (p->ocseq && (p->ocseq < seqno)) { ast_debug(1, "Ignoring out of order response %u (expecting %u)\n", seqno, p->ocseq); return -1; } else { if ((respid == 200) || ((respid >= 300) && (respid <= 399))) { extract_uri(p, req); } if (p->owner) { struct ast_control_pvt_cause_code *cause_code; int data_size = sizeof(*cause_code); /* size of the string making up the cause code is "SIP " + cause length */ data_size += 4 + strlen(REQ_OFFSET_TO_STR(req, rlpart2)); cause_code = ast_alloca(data_size); memset(cause_code, 0, data_size); ast_copy_string(cause_code->chan_name, ast_channel_name(p->owner), AST_CHANNEL_NAME); snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "SIP %s", REQ_OFFSET_TO_STR(req, rlpart2)); cause_code->ast_cause = hangup_sip2cause(respid); if (global_store_sip_cause) { cause_code->emulate_sip_cause = 1; } ast_queue_control_data(p->owner, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size); ast_channel_hangupcause_hash_set(p->owner, cause_code, data_size); } handle_response(p, respid, e + len, req, seqno); } return 0; } /* New SIP request coming in (could be new request in existing SIP dialog as well...) */ p->method = req->method; /* Find out which SIP method they are using */ ast_debug(4, "**** Received %s (%u) - Command in SIP %s\n", sip_methods[p->method].text, sip_methods[p->method].id, cmd); if (p->icseq && (p->icseq > seqno) ) { if (p->pendinginvite && seqno == p->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, p->icseq); if (req->method == SIP_INVITE) { unsigned int ran = (ast_random() % 10) + 1; char seconds[4]; snprintf(seconds, sizeof(seconds), "%u", ran); transmit_response_with_retry_after(p, "500 Server error", req, seconds); /* respond according to RFC 3261 14.2 with Retry-After betwewn 0 and 10 */ } else if (req->method != SIP_ACK) { transmit_response(p, "500 Server error", req); /* We must respond according to RFC 3261 sec 12.2 */ } return -1; } } else if (p->icseq && p->icseq == seqno && req->method != SIP_ACK && (p->method != SIP_CANCEL || p->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 = 1; ast_debug(3, "Ignoring SIP message because of retransmit (%s Seqno %u, ours %u)\n", sip_methods[p->method].text, p->icseq, seqno); } /* RFC 3261 section 9. "CANCEL has no effect on a request to which a UAS has * already given a final response." */ if (!p->pendinginvite && (req->method == SIP_CANCEL)) { transmit_response(p, "481 Call/Transaction Does Not Exist", req); return res; } if (seqno >= p->icseq) /* Next should follow monotonically (but not necessarily incrementally -- thanks again to the genius authors of SIP -- increasing */ p->icseq = seqno; /* Find their tag if we haven't got it */ if (ast_strlen_zero(p->theirtag)) { char tag[128]; gettag(req, "From", tag, sizeof(tag)); ast_string_field_set(p, theirtag, tag); } snprintf(p->lastmsg, sizeof(p->lastmsg), "Rx: %s", cmd); if (sip_cfg.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 (!p->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]; gettag(req, "To", totag, sizeof(totag)); ast_string_field_set(p, tag, totag); p->pendinginvite = p->icseq; transmit_response_reliable(p, "481 Call/Transaction Does Not Exist", req); /* Will cease to exist after ACK */ return res; } else if (req->method != SIP_ACK) { transmit_response(p, "481 Call/Transaction Does Not Exist", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return res; } /* Otherwise, this is an ACK. It will always have a to-tag */ } } if (!e && (p->method == SIP_INVITE || p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_NOTIFY || p->method == SIP_PUBLISH)) { transmit_response(p, "400 Bad request", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return -1; } /* Handle various incoming SIP methods in requests */ switch (p->method) { case SIP_OPTIONS: res = handle_request_options(p, req, addr, e); break; case SIP_INVITE: res = handle_request_invite(p, req, addr, seqno, recount, e, nounlock); if (res < 9) { sip_report_security_event(p, req, res); } switch (res) { case INV_REQ_SUCCESS: res = 1; break; case INV_REQ_FAILED: res = 0; break; case INV_REQ_ERROR: res = -1; break; default: res = 0; break; } break; case SIP_REFER: res = handle_request_refer(p, req, seqno, nounlock); break; case SIP_CANCEL: res = handle_request_cancel(p, req); break; case SIP_BYE: res = handle_request_bye(p, req); break; case SIP_MESSAGE: res = handle_request_message(p, req, addr, e); break; case SIP_PUBLISH: res = handle_request_publish(p, req, addr, seqno, e); break; case SIP_SUBSCRIBE: res = handle_request_subscribe(p, req, addr, seqno, e); break; case SIP_REGISTER: res = handle_request_register(p, req, addr, e); sip_report_security_event(p, req, res); break; case SIP_INFO: if (req->debug) ast_verbose("Receiving INFO!\n"); if (!req->ignore) handle_request_info(p, req); else /* if ignoring, transmit response */ transmit_response(p, "200 OK", req); break; case SIP_NOTIFY: res = handle_request_notify(p, req, addr, seqno, e); break; case SIP_UPDATE: res = handle_request_update(p, req); break; case SIP_ACK: /* Make sure we don't ignore this */ if (seqno == p->pendinginvite) { p->invitestate = INV_TERMINATED; p->pendinginvite = 0; acked = __sip_ack(p, seqno, 1 /* response */, 0); if (p->owner && find_sdp(req)) { if (process_sdp(p, req, SDP_T38_NONE)) { return -1; } if (ast_test_flag(&p->flags[0], SIP_DIRECT_MEDIA)) { ast_queue_control(p->owner, AST_CONTROL_SRCCHANGE); } } check_pendings(p); } else if (p->glareinvite == seqno) { /* handle ack for the 491 pending sent for glareinvite */ p->glareinvite = 0; acked = __sip_ack(p, seqno, 1, 0); } if (!acked) { /* Got an ACK that did not match anything. Ignore * silently and restore previous method */ p->method = oldmethod; } if (!p->lastinvite && ast_strlen_zero(p->nonce)) { pvt_set_needdestroy(p, "unmatched ACK"); } break; default: transmit_response_with_allow(p, "501 Method Not Implemented", req, 0); ast_log(LOG_NOTICE, "Unknown SIP command '%s' from '%s'\n", cmd, ast_sockaddr_stringify(&p->sa)); /* If this is some new method, and we don't have a call, destroy it now */ if (!p->initreq.headers) { pvt_set_needdestroy(p, "unimplemented method"); } break; } return res; } /*! \brief Read data from SIP UDP socket \note sipsock_read locks the owner channel while we are processing the SIP message \return 1 on error, 0 on success \note Successful messages is connected to SIP call and forwarded to handle_incoming() */ static int sipsock_read(int *id, int fd, short events, void *ignore) { struct sip_request req; struct ast_sockaddr addr; int res; static char readbuf[65535]; memset(&req, 0, sizeof(req)); res = ast_recvfrom(fd, readbuf, sizeof(readbuf) - 1, 0, &addr); if (res < 0) { #if !defined(__FreeBSD__) if (errno == EAGAIN) ast_log(LOG_NOTICE, "SIP: Received packet with bad UDP checksum\n"); else #endif if (errno != ECONNREFUSED) ast_log(LOG_WARNING, "Recv error: %s\n", strerror(errno)); return 1; } readbuf[res] = '\0'; if (!(req.data = ast_str_create(SIP_MIN_PACKET))) { return 1; } if (ast_str_set(&req.data, 0, "%s", readbuf) == AST_DYNSTR_BUILD_FAILED) { return -1; } req.socket.fd = sipsock; set_socket_transport(&req.socket, AST_TRANSPORT_UDP); req.socket.tcptls_session = NULL; req.socket.port = htons(ast_sockaddr_port(&bindaddr)); handle_request_do(&req, &addr); deinit_req(&req); return 1; } /*! \brief Handle incoming SIP message - request or response This is used for all transports (udp, tcp and tcp/tls) */ static int handle_request_do(struct sip_request *req, struct ast_sockaddr *addr) { struct sip_pvt *p; struct ast_channel *owner_chan_ref = NULL; int recount = 0; int nounlock = 0; if (sip_debug_test_addr(addr)) /* Set the debug flag early on packet level */ req->debug = 1; if (sip_cfg.pedanticsipchecking) lws2sws(req->data); /* Fix multiline headers */ if (req->debug) { ast_verbose("\n<--- SIP read from %s:%s --->\n%s\n<------------->\n", sip_get_transport(req->socket.type), ast_sockaddr_stringify(addr), ast_str_buffer(req->data)); } if (parse_request(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 = find_sip_method(REQ_OFFSET_TO_STR(req, rlpart1)); if (req->debug) ast_verbose("--- (%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(&netlock); /* Find the active SIP dialog or create a new one */ p = find_call(req, addr, req->method); /* returns p with a reference only. _NOT_ locked*/ if (p == NULL) { ast_debug(1, "Invalid SIP message - rejected , no callid, len %zu\n", ast_str_strlen(req->data)); ast_mutex_unlock(&netlock); return 1; } if (p->logger_callid) { ast_callid_threadassoc_add(p->logger_callid); } /* Lock both the pvt and the owner if owner is present. This will * not fail. */ owner_chan_ref = sip_pvt_lock_full(p); copy_socket_data(&p->socket, &req->socket); ast_sockaddr_copy(&p->recv, addr); /* if we have an owner, then this request has been authenticated */ if (p->owner) { req->authenticated = 1; } if (p->do_history) /* This is a request or response, note what it was for */ append_history(p, "Rx", "%s / %s / %s", ast_str_buffer(req->data), sip_get_header(req, "CSeq"), REQ_OFFSET_TO_STR(req, rlpart2)); if (handle_incoming(p, req, addr, &recount, &nounlock) == -1) { /* Request failed */ ast_debug(1, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : ""); } if (recount) { ast_update_use_count(); } if (p->owner && !nounlock) { ast_channel_unlock(p->owner); } if (owner_chan_ref) { ast_channel_unref(owner_chan_ref); } sip_pvt_unlock(p); ast_mutex_unlock(&netlock); if (p->logger_callid) { ast_callid_threadassoc_remove(); } ao2_t_ref(p, -1, "throw away dialog ptr from find_call at end of routine"); /* p is gone after the return */ return 1; } /*! \brief Returns the port to use for this socket * * \param type The type of transport used * \param port Port we are checking to see if it's the standard port. * \note port is expected in host byte order */ static int sip_standard_port(enum ast_transport type, int port) { if (type & AST_TRANSPORT_TLS) return port == STANDARD_TLS_PORT; else return port == STANDARD_SIP_PORT; } static int threadinfo_locate_cb(void *obj, void *arg, int flags) { struct sip_threadinfo *th = obj; struct ast_sockaddr *s = arg; if (!ast_sockaddr_cmp(s, &th->tcptls_session->remote_address)) { return CMP_MATCH | CMP_STOP; } return 0; } /*! * \brief Find thread for TCP/TLS session (based on IP/Port * * \note This function returns an astobj2 reference */ static struct ast_tcptls_session_instance *sip_tcp_locate(struct ast_sockaddr *s) { struct sip_threadinfo *th; struct ast_tcptls_session_instance *tcptls_instance = NULL; if ((th = ao2_callback(threadt, 0, threadinfo_locate_cb, s))) { tcptls_instance = (ao2_ref(th->tcptls_session, +1), th->tcptls_session); ao2_t_ref(th, -1, "decrement ref from callback"); } return tcptls_instance; } /*! * \brief Helper for dns resolution to filter by address family. * * \note return 0 if addr is [::] else it returns addr's family. */ int get_address_family_filter(unsigned int transport) { const struct ast_sockaddr *addr = NULL; if ((transport == AST_TRANSPORT_UDP) || !transport) { addr = &bindaddr; } else if (transport == AST_TRANSPORT_TCP || transport == AST_TRANSPORT_WS) { addr = &sip_tcp_desc.local_address; } else if (transport == AST_TRANSPORT_TLS || transport == AST_TRANSPORT_WSS) { addr = &sip_tls_desc.local_address; } if (ast_sockaddr_is_ipv6(addr) && ast_sockaddr_is_any(addr)) { return 0; } return addr->ss.ss_family; } /*! \todo Get socket for dialog, prepare if needed, and return file handle */ static int sip_prepare_socket(struct sip_pvt *p) { struct sip_socket *s = &p->socket; static const char name[] = "SIP socket"; struct sip_threadinfo *th = NULL; struct ast_tcptls_session_instance *tcptls_session; struct ast_tcptls_session_args *ca; struct ast_sockaddr sa_tmp; pthread_t launched; /* check to see if a socket is already active */ if ((s->fd != -1) && (s->type == AST_TRANSPORT_UDP)) { return s->fd; } if ((s->type & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && (s->tcptls_session) && (s->tcptls_session->fd != -1)) { return s->tcptls_session->fd; } if ((s->type & (AST_TRANSPORT_WS | AST_TRANSPORT_WSS))) { return s->ws_session ? ast_websocket_fd(s->ws_session) : -1; } /*! \todo Check this... This might be wrong, depending on the proxy configuration If proxy is in "force" mode its correct. */ if (p->outboundproxy && p->outboundproxy->transport) { s->type = p->outboundproxy->transport; } if (s->type == AST_TRANSPORT_UDP) { s->fd = sipsock; return s->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(&sa_tmp, sip_real_dst(p)); if ((tcptls_session = sip_tcp_locate(&sa_tmp))) { s->fd = tcptls_session->fd; if (s->tcptls_session) { ao2_ref(s->tcptls_session, -1); s->tcptls_session = NULL; } s->tcptls_session = tcptls_session; return s->fd; /* 2. Thread not found, if tcptls_session already exists, it once had a thread and is now terminated */ } else if (s->tcptls_session) { return s->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 (!(ca = ao2_alloc(sizeof(*ca), sip_tcptls_client_args_destructor)) || !(ca->name = ast_strdup(name))) { goto create_tcptls_session_fail; } ca->accept_fd = -1; ast_sockaddr_copy(&ca->remote_address,sip_real_dst(p)); /* if type is TLS, we need to create a tls cfg for this session arg */ if (s->type == AST_TRANSPORT_TLS) { if (!(ca->tls_cfg = ast_calloc(1, sizeof(*ca->tls_cfg)))) { goto create_tcptls_session_fail; } memcpy(ca->tls_cfg, &default_tls_cfg, sizeof(*ca->tls_cfg)); if (!(ca->tls_cfg->certfile = ast_strdup(default_tls_cfg.certfile)) || !(ca->tls_cfg->pvtfile = ast_strdup(default_tls_cfg.pvtfile)) || !(ca->tls_cfg->cipher = ast_strdup(default_tls_cfg.cipher)) || !(ca->tls_cfg->cafile = ast_strdup(default_tls_cfg.cafile)) || !(ca->tls_cfg->capath = ast_strdup(default_tls_cfg.capath))) { goto create_tcptls_session_fail; } /* this host is used as the common name in ssl/tls */ if (!ast_strlen_zero(p->tohost)) { ast_copy_string(ca->hostname, p->tohost, sizeof(ca->hostname)); } } /* Create a client connection for address, this does not start the connection, just sets it up. */ if (!(s->tcptls_session = ast_tcptls_client_create(ca))) { goto create_tcptls_session_fail; } s->fd = s->tcptls_session->fd; /* client connections need to have the sip_threadinfo 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 * threadt container. */ if (!(th = sip_threadinfo_create(s->tcptls_session, s->type))) { goto create_tcptls_session_fail; } /* Give the new thread a reference to the tcptls_session */ ao2_ref(s->tcptls_session, +1); if (ast_pthread_create_detached_background(&launched, NULL, sip_tcp_worker_fn, s->tcptls_session)) { ast_debug(1, "Unable to launch '%s'.", ca->name); ao2_ref(s->tcptls_session, -1); /* take away the thread ref we just gave it */ goto create_tcptls_session_fail; } return s->fd; create_tcptls_session_fail: if (ca) { ao2_t_ref(ca, -1, "failed to create client, getting rid of client tcptls_session arguments"); } if (s->tcptls_session) { ast_tcptls_close_session_file(s->tcptls_session); s->fd = -1; ao2_ref(s->tcptls_session, -1); s->tcptls_session = NULL; } if (th) { ao2_t_unlink(threadt, th, "Removing tcptls thread info object, thread failed to open"); } return -1; } /*! * \brief Get cached MWI info * \return TRUE if found MWI in cache */ static int get_cached_mwi(struct sip_peer *peer, int *new, int *old) { struct sip_mailbox *mailbox; int in_cache; in_cache = 0; AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { 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->id); if (!msg) { continue; } mwi_state = stasis_message_data(msg); *new += mwi_state->new_msgs; *old += mwi_state->old_msgs; in_cache = 1; } return in_cache; } /*! \brief Send message waiting indication to alert peer that they've got voicemail * \note Both peer and associated sip_pvt must be unlocked prior to calling this function * \returns -1 on failure, 0 on success */ static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only) { /* Called with peer lock, but releases it */ struct sip_pvt *p; 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_PAGE2_SUBSCRIBEMWIONLY) && !peer->mwipvt) { update_peer_lastmsgssent(peer, -1, 1); 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)) { update_peer_lastmsgssent(peer, -1, 1); ao2_unlock(peer); return -1; } /* Attempt to use cached mwi to get message counts. */ if (!get_cached_mwi(peer, &newmsgs, &oldmsgs) && !cache_only) { /* Fall back to manually checking the mailbox if not cache_only and get_cached_mwi failed */ struct ast_str *mailbox_str = ast_str_alloca(512); peer_mailboxes_to_str(&mailbox_str, peer); /* if there is no mailbox do nothing */ if (!ast_str_strlen(mailbox_str)) { ao2_unlock(peer); return -1; } ao2_unlock(peer); /* If there is no mailbox do nothing */ if (!ast_str_strlen(mailbox_str)) { update_peer_lastmsgssent(peer, -1, 0); return 0; } ast_app_inboxcount(ast_str_buffer(mailbox_str), &newmsgs, &oldmsgs); ao2_lock(peer); } if (peer->mwipvt) { /* Base message on subscription */ p = dialog_ref(peer->mwipvt, "sip_send_mwi_to_peer: Setting dialog ptr p from peer->mwipvt"); ao2_unlock(peer); } else { ao2_unlock(peer); /* Build temporary dialog for this message */ if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, NULL))) { update_peer_lastmsgssent(peer, -1, 0); return -1; } /* If we don't set the socket type to 0, then create_addr_from_peer will fail immediately if the peer * uses any transport other than UDP. We set the type to 0 here and then let create_addr_from_peer copy * the peer's socket information to the sip_pvt we just allocated */ set_socket_transport(&p->socket, 0); if (create_addr_from_peer(p, peer)) { /* Maybe they're not registered, etc. */ dialog_unlink_all(p); dialog_unref(p, "unref dialog p just created via sip_alloc"); update_peer_lastmsgssent(peer, -1, 0); return -1; } /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); build_via(p); ao2_lock(peer); if (!ast_strlen_zero(peer->mwi_from)) { ast_string_field_set(p, mwi_from, peer->mwi_from); } else if (!ast_strlen_zero(default_mwi_from)) { ast_string_field_set(p, mwi_from, default_mwi_from); } ao2_unlock(peer); /* Change the dialog callid. */ change_callid_pvt(p, NULL); /* Destroy this session after 32 secs */ sip_scheddestroy(p, DEFAULT_TRANS_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 */ sip_pvt_lock(p); /* Send MWI */ ast_set_flag(&p->flags[0], SIP_OUTGOING); /* the following will decrement the refcount on p as it finishes */ transmit_notify_with_mwi(p, newmsgs, oldmsgs, vmexten); sip_pvt_unlock(p); dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi_to_peer."); update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); return 0; } static struct ast_manager_event_blob *session_timeout_to_ami(struct stasis_message *msg) { RAII_VAR(struct ast_str *, channel_string, NULL, ast_free); struct ast_channel_blob *obj = stasis_message_data(msg); const char *source = ast_json_string_get(ast_json_object_get(obj->blob, "source")); channel_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_string) { return NULL; } return ast_manager_event_blob_create(EVENT_FLAG_CALL, "SessionTimeout", "%s" "Source: %s\r\n", ast_str_buffer(channel_string), source); } /*! \brief Sends a session timeout channel blob used to produce SessionTimeout AMI messages */ static void send_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); blob = ast_json_pack("{s: s}", "source", source); if (!blob) { return; } ast_channel_publish_blob(chan, session_timeout_type(), blob); } /*! * \brief helper function for the monitoring thread -- seems to be called with the assumption that the dialog is locked * * \return CMP_MATCH for items to be unlinked from dialogs_rtpcheck. */ static int check_rtp_timeout(struct sip_pvt *dialog, time_t t) { int timeout; int hold_timeout; int keepalive; if (!dialog->rtp) { /* * We have no RTP. Since we don't do much with video RTP for * now, stop checking this dialog. */ return CMP_MATCH; } /* If we have no active owner, no need to check timers */ if (!dialog->owner) { return CMP_MATCH; } /* If the call is redirected outside Asterisk, no need to check timers */ if (!ast_sockaddr_isnull(&dialog->redirip)) { return CMP_MATCH; } /* If the call is involved in a T38 fax session do not check RTP timeout */ if (dialog->t38.state == T38_ENABLED) { return CMP_MATCH; } /* If the call is not in UP state return for later check. */ if (ast_channel_state(dialog->owner) != AST_STATE_UP) { return 0; } /* Store these values locally to avoid multiple function calls */ timeout = ast_rtp_instance_get_timeout(dialog->rtp); hold_timeout = ast_rtp_instance_get_hold_timeout(dialog->rtp); keepalive = ast_rtp_instance_get_keepalive(dialog->rtp); /* If we have no timers set, return now */ if (!keepalive && !timeout && !hold_timeout) { return CMP_MATCH; } /* Check AUDIO RTP keepalives */ if (dialog->lastrtptx && keepalive && (t > dialog->lastrtptx + keepalive)) { /* Need to send an empty RTP packet */ dialog->lastrtptx = time(NULL); ast_rtp_instance_sendcng(dialog->rtp, 0); } /*! \todo Check video RTP keepalives Do we need to move the lastrtptx to the RTP structure to have one for audio and one for video? It really does belong to the RTP structure. */ /* Check AUDIO RTP timers */ if (dialog->lastrtprx && (timeout || hold_timeout) && (t > dialog->lastrtprx + timeout)) { if (!ast_test_flag(&dialog->flags[1], SIP_PAGE2_CALL_ONHOLD) || (hold_timeout && (t > dialog->lastrtprx + hold_timeout))) { /* Needs a hangup */ if (timeout) { if (!dialog->owner || ast_channel_trylock(dialog->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(dialog->owner), (long) (t - dialog->lastrtprx)); send_session_timeout(dialog->owner, "RTPTimeout"); /* Issue a softhangup */ ast_softhangup_nolock(dialog->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(dialog->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(dialog->rtp, 0); ast_rtp_instance_set_hold_timeout(dialog->rtp, 0); if (dialog->vrtp) { ast_rtp_instance_set_timeout(dialog->vrtp, 0); ast_rtp_instance_set_hold_timeout(dialog->vrtp, 0); } /* finally unlink the dialog from dialogs_rtpcheck. */ return CMP_MATCH; } } } return 0; } /*! \brief The SIP monitoring thread \note 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 *do_monitor(void *data) { int res; time_t t; int reloading; /* Add an I/O event to our SIP UDP socket */ if (sipsock > -1) { sipsock_read_id = ast_io_add(io, sipsock, sipsock_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) { ast_verb(1, "Reloading SIP\n"); sip_do_reload(sip_reloadreason); /* Change the I/O fd of our UDP socket */ if (sipsock > -1) { if (sipsock_read_id) { sipsock_read_id = ast_io_change(io, sipsock_read_id, sipsock, NULL, 0, NULL); } else { sipsock_read_id = ast_io_add(io, sipsock, sipsock_read, AST_IO_IN, NULL); } } else if (sipsock_read_id) { ast_io_remove(io, sipsock_read_id); sipsock_read_id = NULL; } } /* Check for dialogs needing to be killed */ t = time(NULL); /* * Check dialogs with rtp and rtptimeout. * All dialogs which have rtp are in dialogs_rtpcheck. */ ao2_t_callback(dialogs_rtpcheck, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, dialog_checkrtp_cb, &t, "callback to check rtptimeout and hangup calls if necessary"); /* * Check dialogs marked to be destroyed. * All dialogs with needdestroy set are in dialogs_needdestroy. */ ao2_t_callback(dialogs_needdestroy, OBJ_NODATA | OBJ_MULTIPLE, dialog_needdestroy, NULL, "callback to check dialogs which need to be destroyed"); /* XXX TODO 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(sched); if ((res < 0) || (res > 1000)) { res = 1000; } res = ast_io_wait(io, res); if (res > 20) { ast_debug(1, "chan_sip: ast_io_wait ran %d all at once\n", res); } ast_mutex_lock(&monlock); res = ast_sched_runq(sched); if (res >= 20) { ast_debug(1, "chan_sip: ast_sched_runq ran %d all at once\n", res); } ast_mutex_unlock(&monlock); } /* Never reached */ return NULL; } /*! \brief Start the channel monitor thread */ static int restart_monitor(void) { /* If we're supposed to be stopped -- stay stopped */ if (monitor_thread == AST_PTHREADT_STOP) return 0; ast_mutex_lock(&monlock); if (monitor_thread == pthread_self()) { ast_mutex_unlock(&monlock); ast_log(LOG_WARNING, "Cannot kill myself\n"); return -1; } if (monitor_thread != AST_PTHREADT_NULL) { /* Wake up the thread */ pthread_kill(monitor_thread, SIGURG); } else { /* Start a new monitor */ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) { ast_mutex_unlock(&monlock); ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); return -1; } } ast_mutex_unlock(&monlock); return 0; } static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { if (stasis_message_type(message) != ast_named_acl_change_type()) { return; } ast_log(LOG_NOTICE, "Reloading chan_sip in response to ACL change event.\n"); ast_mutex_lock(&sip_reload_lock); if (sip_reloading) { ast_verbose("Previous SIP reload not yet done\n"); } else { sip_reloading = TRUE; sip_reloadreason = CHANNEL_ACL_RELOAD; } ast_mutex_unlock(&sip_reload_lock); restart_monitor(); } /*! \brief Session-Timers: Restart session timer */ static void restart_session_timer(struct sip_pvt *p) { if (p->stimer->st_active == TRUE) { ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid); AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid, dialog_unref(p, "Removing session timer ref")); start_session_timer(p); } } /*! \brief Session-Timers: Stop session timer */ static void stop_session_timer(struct sip_pvt *p) { if (p->stimer->st_active == TRUE) { p->stimer->st_active = FALSE; ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid); AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid, dialog_unref(p, "removing session timer ref")); } } /*! \brief Session-Timers: Start session timer */ static void start_session_timer(struct sip_pvt *p) { unsigned int timeout_ms; if (p->stimer->st_schedid > -1) { /* in the event a timer is already going, stop it */ ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid); AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid, dialog_unref(p, "unref stimer->st_schedid from dialog")); } /* * 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 = (1000 * p->stimer->st_interval); if (p->stimer->st_ref == SESSION_TIMER_REFRESHER_US) { timeout_ms /= 2; } else { timeout_ms -= MIN(timeout_ms / 3, 32000); } p->stimer->st_schedid = ast_sched_add(sched, timeout_ms, proc_session_timer, dialog_ref(p, "adding session timer ref")); if (p->stimer->st_schedid < 0) { dialog_unref(p, "removing session timer ref"); ast_log(LOG_ERROR, "ast_sched_add failed - %s\n", p->callid); } else { p->stimer->st_active = TRUE; ast_debug(2, "Session timer started: %d - %s %ums\n", p->stimer->st_schedid, p->callid, timeout_ms); } } /*! \brief Session-Timers: Process session refresh timeout event */ static int proc_session_timer(const void *vp) { struct sip_pvt *p = (struct sip_pvt *) vp; int res = 0; if (!p->stimer) { ast_log(LOG_WARNING, "Null stimer in proc_session_timer - %s\n", p->callid); goto return_unref; } ast_debug(2, "Session timer expired: %d - %s\n", p->stimer->st_schedid, p->callid); if (!p->owner) { goto return_unref; } if ((p->stimer->st_active != TRUE) || (ast_channel_state(p->owner) != AST_STATE_UP)) { goto return_unref; } if (p->stimer->st_ref == SESSION_TIMER_REFRESHER_US) { res = 1; if (T38_ENABLED == p->t38.state) { transmit_reinvite_with_sdp(p, TRUE, TRUE); } else { transmit_reinvite_with_sdp(p, FALSE, TRUE); } } else { if (p->stimer->quit_flag) { goto return_unref; } ast_log(LOG_WARNING, "Session-Timer expired - %s\n", p->callid); sip_pvt_lock(p); while (p->owner && ast_channel_trylock(p->owner)) { sip_pvt_unlock(p); usleep(1); if (p->stimer && p->stimer->quit_flag) { goto return_unref; } sip_pvt_lock(p); } send_session_timeout(p->owner, "SIPSessionTimer"); ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(p->owner); sip_pvt_unlock(p); } return_unref: if (!res) { /* An error occurred. Stop session timer processing */ if (p->stimer) { ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid); /* Don't pass go, don't collect $200.. we are the scheduled * callback. We can rip ourself out here. */ p->stimer->st_schedid = -1; /* Calling stop_session_timer is nice for consistent debug * logs. */ stop_session_timer(p); } /* If we are not asking to be rescheduled, then we need to release our * reference to the dialog. */ dialog_unref(p, "removing session timer ref"); } return res; } /*! \brief Session-Timers: Function for parsing Min-SE header */ int parse_minse (const char *p_hdrval, int *const p_interval) { if (ast_strlen_zero(p_hdrval)) { ast_log(LOG_WARNING, "Null Min-SE header\n"); return -1; } *p_interval = 0; p_hdrval = ast_skip_blanks(p_hdrval); if (!sscanf(p_hdrval, "%30d", p_interval)) { ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval); return -1; } ast_debug(2, "Received Min-SE: %d\n", *p_interval); return 0; } /*! \brief Session-Timers: Function for parsing Session-Expires header */ int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher_param *const p_ref) { char *p_token; int ref_idx; char *p_se_hdr; if (ast_strlen_zero(p_hdrval)) { ast_log(LOG_WARNING, "Null Session-Expires header\n"); return -1; } *p_ref = SESSION_TIMER_REFRESHER_PARAM_UNKNOWN; *p_interval = 0; p_se_hdr = ast_strdupa(p_hdrval); p_se_hdr = ast_skip_blanks(p_se_hdr); while ((p_token = strsep(&p_se_hdr, ";"))) { p_token = ast_skip_blanks(p_token); if (!sscanf(p_token, "%30d", p_interval)) { ast_log(LOG_WARNING, "Parsing of Session-Expires failed\n"); return -1; } ast_debug(2, "Session-Expires: %d\n", *p_interval); if (!p_se_hdr) continue; p_se_hdr = ast_skip_blanks(p_se_hdr); ref_idx = strlen("refresher="); if (!strncasecmp(p_se_hdr, "refresher=", ref_idx)) { p_se_hdr += ref_idx; p_se_hdr = ast_skip_blanks(p_se_hdr); if (!strncasecmp(p_se_hdr, "uac", strlen("uac"))) { *p_ref = SESSION_TIMER_REFRESHER_PARAM_UAC; ast_debug(2, "Refresher: UAC\n"); } else if (!strncasecmp(p_se_hdr, "uas", strlen("uas"))) { *p_ref = SESSION_TIMER_REFRESHER_PARAM_UAS; ast_debug(2, "Refresher: UAS\n"); } else { ast_log(LOG_WARNING, "Invalid refresher value %s\n", p_se_hdr); return -1; } break; } } return 0; } /*! \brief Handle 422 response to INVITE with session-timer requested Session-Timers: An INVITE originated by Asterisk that asks for session-timers support from the UAS can result into a 422 response. This is how a UAS or an intermediary proxy server tells Asterisk that the session refresh interval offered by Asterisk is too low for them. The proc_422_rsp() function handles a 422 response. It extracts the Min-SE header that comes back in 422 and sends a new INVITE accordingly. */ static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp) { int rtn; const char *p_hdrval; int minse; p_hdrval = sip_get_header(rsp, "Min-SE"); if (ast_strlen_zero(p_hdrval)) { ast_log(LOG_WARNING, "422 response without a Min-SE header %s\n", p_hdrval); return; } rtn = parse_minse(p_hdrval, &minse); if (rtn != 0) { ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval); return; } p->stimer->st_cached_min_se = minse; if (p->stimer->st_interval < minse) { p->stimer->st_interval = minse; } transmit_invite(p, SIP_INVITE, 1, 2, NULL); } /*! \brief Get Max or Min SE (session timer expiry) * \param p pointer to the SIP dialog * \param max if true, get max se, otherwise min se */ int st_get_se(struct sip_pvt *p, int max) { if (max == TRUE) { if (p->stimer->st_cached_max_se) { return p->stimer->st_cached_max_se; } if (p->relatedpeer) { p->stimer->st_cached_max_se = p->relatedpeer->stimer.st_max_se; return (p->stimer->st_cached_max_se); } p->stimer->st_cached_max_se = global_max_se; return (p->stimer->st_cached_max_se); } /* Find Min SE timer */ if (p->stimer->st_cached_min_se) { return p->stimer->st_cached_min_se; } if (p->relatedpeer) { p->stimer->st_cached_min_se = p->relatedpeer->stimer.st_min_se; return (p->stimer->st_cached_min_se); } p->stimer->st_cached_min_se = global_min_se; return (p->stimer->st_cached_min_se); } /*! \brief Get the entity (UAC or UAS) that's acting as the session-timer refresher * \note 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. * \param p pointer to the SIP dialog */ enum st_refresher st_get_refresher(struct sip_pvt *p) { if (p->stimer->st_cached_ref != SESSION_TIMER_REFRESHER_AUTO) { return p->stimer->st_cached_ref; } if (p->relatedpeer) { p->stimer->st_cached_ref = (p->relatedpeer->stimer.st_ref == SESSION_TIMER_REFRESHER_PARAM_UAC) ? SESSION_TIMER_REFRESHER_THEM : SESSION_TIMER_REFRESHER_US; return p->stimer->st_cached_ref; } p->stimer->st_cached_ref = (global_st_refresher == SESSION_TIMER_REFRESHER_PARAM_UAC) ? SESSION_TIMER_REFRESHER_THEM : SESSION_TIMER_REFRESHER_US; return p->stimer->st_cached_ref; } /*! * \brief Get the session-timer mode * \param p pointer to the SIP dialog * \param no_cached Set this to true in order to force a peername lookup on * the session timer mode. */ enum st_mode st_get_mode(struct sip_pvt *p, int no_cached) { if (!p->stimer) { sip_st_alloc(p); if (!p->stimer) { return SESSION_TIMER_MODE_INVALID; } } if (!no_cached && p->stimer->st_cached_mode != SESSION_TIMER_MODE_INVALID) return p->stimer->st_cached_mode; if (p->relatedpeer) { p->stimer->st_cached_mode = p->relatedpeer->stimer.st_mode_oper; return p->stimer->st_cached_mode; } p->stimer->st_cached_mode = global_st_mode; return global_st_mode; } /*! \brief Send keep alive packet to peer */ static int sip_send_keepalive(const void *data) { struct sip_peer *peer = (struct sip_peer*) data; int res = 0; const char keepalive[] = "\r\n"; peer->keepalivesend = -1; if (!peer->keepalive || ast_sockaddr_isnull(&peer->addr)) { sip_unref_peer(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, sizeof(keepalive), 0, &peer->addr); } else if ((peer->socket.type & (AST_TRANSPORT_TCP | AST_TRANSPORT_TLS)) && (peer->socket.tcptls_session) && (peer->socket.tcptls_session->fd != -1)) { res = sip_tcptls_write(peer->socket.tcptls_session, keepalive, sizeof(keepalive)); } else if (peer->socket.type == AST_TRANSPORT_UDP) { res = ast_sendto(sipsock, keepalive, sizeof(keepalive), 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 = XMIT_ERROR; /* Don't bother with trying to transmit again */ } } if (res != sizeof(keepalive)) { ast_log(LOG_WARNING, "sip_send_keepalive to %s returned %d: %s\n", ast_sockaddr_stringify(&peer->addr), res, strerror(errno)); } AST_SCHED_REPLACE_UNREF(peer->keepalivesend, sched, peer->keepalive * 1000, sip_send_keepalive, peer, sip_unref_peer(_data, "removing keepalive peer ref"), sip_unref_peer(peer, "removing keepalive peer ref"), sip_ref_peer(peer, "adding keepalive peer ref")); sip_unref_peer(peer, "release keepalive peer ref"); return 0; } /*! \brief React to lack of answer to Qualify poke */ static int sip_poke_noanswer(const void *data) { struct sip_peer *peer = (struct sip_peer *)data; peer->pokeexpire = -1; if (peer->lastms > -1) { ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Last qualify: %d\n", peer->name, peer->lastms); if (sip_cfg.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_cfg.regextenonqualify) { register_peer_exten(peer, FALSE); } } if (peer->call) { dialog_unlink_all(peer->call); peer->call = dialog_unref(peer->call, "unref dialog peer->call"); /* peer->call = sip_destroy(peer->call);*/ } /* 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); } /* Try again quickly */ AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer, sip_unref_peer(_data, "removing poke peer ref"), sip_unref_peer(peer, "removing poke peer ref"), sip_ref_peer(peer, "adding poke peer ref")); /* Release the ref held by the running scheduler entry */ sip_unref_peer(peer, "release peer poke noanswer ref"); return 0; } /*! \brief Check availability of peer, also keep NAT open \note 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. \note Do *not* hold a pvt lock while calling this function. This function calls sip_alloc, which can cause a deadlock if another sip_pvt is held. */ static int sip_poke_peer(struct sip_peer *peer, int force) { struct sip_pvt *p; int xmitres = 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(sched, peer->pokeexpire, sip_unref_peer(peer, "removing poke peer ref")); peer->lastms = 0; if (peer->call) { peer->call = dialog_unref(peer->call, "unref dialog peer->call"); } return 0; } if (peer->call) { if (sipdebug) { ast_log(LOG_NOTICE, "Still have a QUALIFY dialog active, deleting\n"); } dialog_unlink_all(peer->call); peer->call = dialog_unref(peer->call, "unref dialog peer->call"); /* peer->call = sip_destroy(peer->call); */ } if (!(p = sip_alloc(NULL, NULL, 0, SIP_OPTIONS, NULL, NULL))) { return -1; } peer->call = dialog_ref(p, "copy sip alloc from p to peer->call"); p->sa = peer->addr; p->recv = peer->addr; copy_socket_data(&p->socket, &peer->socket); ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); sip_route_copy(&p->route, &peer->path); if (!sip_route_empty(&p->route)) { /* Parse SIP URI of first route-set hop and use it as target address */ __set_address_from_contact(sip_route_first_uri(&p->route), &p->sa, p->socket.type == AST_TRANSPORT_TLS ? 1 : 0); } /* Get the outbound proxy information */ ref_proxy(p, obproxy_get(p, peer)); /* Send OPTIONs to peer's fullcontact */ if (!ast_strlen_zero(peer->fullcontact)) { ast_string_field_set(p, fullcontact, peer->fullcontact); } if (!ast_strlen_zero(peer->fromuser)) { ast_string_field_set(p, fromuser, peer->fromuser); } if (!ast_strlen_zero(peer->tohost)) { ast_string_field_set(p, tohost, peer->tohost); } else { ast_string_field_set(p, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); } /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); build_via(p); /* Change the dialog callid. */ change_callid_pvt(p, NULL); AST_SCHED_DEL_UNREF(sched, peer->pokeexpire, sip_unref_peer(peer, "removing poke peer ref")); if (p->relatedpeer) p->relatedpeer = sip_unref_peer(p->relatedpeer,"unsetting the relatedpeer field in the dialog, before it is set to something else."); p->relatedpeer = sip_ref_peer(peer, "setting the relatedpeer field in the dialog"); ast_set_flag(&p->flags[0], SIP_OUTGOING); #ifdef VOCAL_DATA_HACK ast_copy_string(p->username, "__VOCAL_DATA_SHOULD_READ_THE_SIP_SPEC__", sizeof(p->username)); xmitres = transmit_invite(p, SIP_INVITE, 0, 2, NULL); /* sinks the p refcount */ #else xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2, NULL); /* sinks the p refcount */ #endif peer->ps = ast_tvnow(); if (xmitres == XMIT_ERROR) { /* Immediately unreachable, network problems */ sip_poke_noanswer(sip_ref_peer(peer, "add ref for peerexpire (fake, for sip_poke_noanswer to remove)")); } else if (!force) { AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, peer->maxms * 2, sip_poke_noanswer, peer, sip_unref_peer(_data, "removing poke peer ref"), sip_unref_peer(peer, "removing poke peer ref"), sip_ref_peer(peer, "adding poke peer ref")); } dialog_unref(p, "unref dialog at end of sip_poke_peer, obtained from sip_alloc, just before it goes out of scope"); return 0; } /*! \brief Part of PBX channel interface \note \par Return values:--- 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 */ static int sip_devicestate(const char *data) { char *host; char *tmp; struct sip_peer *p; int res = AST_DEVICE_INVALID; /* make sure data is not null. Maybe unnecessary, but better be safe */ host = ast_strdupa(data ? data : ""); if ((tmp = strchr(host, '@'))) host = tmp + 1; ast_debug(3, "Checking device state for peer %s\n", host); /* If sip_find_peer 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 ((p = sip_find_peer(host, NULL, FALSE, FINDALLDEVICES, TRUE, 0))) { if (!(ast_sockaddr_isnull(&p->addr) && ast_sockaddr_isnull(&p->defaddr))) { /* we have an address for the peer */ /* 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 (p->onhold) /* First check for hold or ring states */ res = AST_DEVICE_ONHOLD; else if (p->ringing) { if (p->ringing == p->inuse) res = AST_DEVICE_RINGING; else res = AST_DEVICE_RINGINUSE; } else if (p->call_limit && (p->inuse == p->call_limit)) /* check call limit */ res = AST_DEVICE_BUSY; else if (p->call_limit && p->busy_level && p->inuse >= p->busy_level) /* We're forcing busy before we've reached the call limit */ res = AST_DEVICE_BUSY; else if (p->call_limit && p->inuse) /* Not busy, but we do have a call */ res = AST_DEVICE_INUSE; else if (p->maxms && ((p->lastms > p->maxms) || (p->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; } sip_unref_peer(p, "sip_unref_peer, from sip_devicestate, release ref from sip_find_peer"); } return res; } /*! \brief PBX interface function -build SIP pvt structure * SIP calls initiated by the PBX arrive here. * * \verbatim * 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. * \endverbatim */ static struct ast_channel *sip_request_call(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 *p; struct ast_channel *tmpc = NULL; char *ext = NULL, *host; char tmp[256]; struct ast_str *codec_buf = ast_str_alloca(64); struct ast_str *cap_buf = ast_str_alloca(64); char *dnid; char *secret = NULL; char *md5secret = NULL; char *authname = NULL; char *trans = NULL; char dialstring[256]; char *remote_address; enum ast_transport transport = 0; struct ast_callid *callid; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(peerorhost); AST_APP_ARG(exten); AST_APP_ARG(remote_address); ); /* mask request with some set of allowed formats. * XXX this needs to be fixed. * The original code uses AST_FORMAT_AUDIO_MASK, but it is * unclear what to use here. We have global_capabilities, which is * configured from sip.conf, and sip_tech.capabilities, which is * hardwired to all audio formats. */ if (!(ast_format_cap_has_type(cap, AST_MEDIA_TYPE_AUDIO))) { ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format %s while capability is %s\n", ast_format_cap_get_names(cap, &codec_buf), ast_format_cap_get_names(sip_cfg.caps, &cap_buf)); *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 (!(p = sip_alloc(NULL, NULL, 0, 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; if (callid) { ast_callid_unref(callid); } return NULL; } p->outgoing_call = TRUE; snprintf(dialstring, sizeof(dialstring), "%s/%s", type, dest); ast_string_field_set(p, dialstring, dialstring); if (!(p->options = ast_calloc(1, sizeof(*p->options)))) { dialog_unlink_all(p); dialog_unref(p, "unref dialog p from mem fail"); /* sip_destroy(p); */ ast_log(LOG_ERROR, "Unable to build option SIP data structure - Out of memory\n"); *cause = AST_CAUSE_SWITCH_CONGESTION; if (callid) { ast_callid_unref(callid); } return NULL; } /* Save the destination, the SIP dial string */ ast_copy_string(tmp, dest, sizeof(tmp)); /* Find DNID and take it away */ dnid = strchr(tmp, '!'); if (dnid != NULL) { *dnid++ = '\0'; ast_string_field_set(p, todnid, dnid); } /* Divvy up the items separated by slashes */ AST_NONSTANDARD_APP_ARGS(args, tmp, '/'); /* Find at sign - @ */ host = strchr(args.peerorhost, '@'); if (host) { *host++ = '\0'; ext = args.peerorhost; secret = strchr(ext, ':'); } if (secret) { *secret++ = '\0'; md5secret = strchr(secret, ':'); } if (md5secret) { *md5secret++ = '\0'; authname = strchr(md5secret, ':'); } if (authname) { *authname++ = '\0'; trans = strchr(authname, ':'); } if (trans) { *trans++ = '\0'; if (!strcasecmp(trans, "tcp")) transport = AST_TRANSPORT_TCP; else if (!strcasecmp(trans, "tls")) transport = AST_TRANSPORT_TLS; else { if (strcasecmp(trans, "udp")) ast_log(LOG_WARNING, "'%s' is not a valid transport option to Dial() for SIP calls, using udp by default.\n", trans); transport = AST_TRANSPORT_UDP; } } else { /* use default */ transport = AST_TRANSPORT_UDP; } if (!host) { ext = 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", ext, args.exten); } } if (!ast_strlen_zero(remote_address)) { p->options->outboundproxy = proxy_from_config(remote_address, 0, NULL); if (!p->options->outboundproxy) { ast_log(LOG_WARNING, "Unable to parse outboundproxy %s. We will not use this remote IP address\n", remote_address); } } set_socket_transport(&p->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 (create_addr(p, host, NULL, 1)) { *cause = AST_CAUSE_UNREGISTERED; ast_debug(3, "Cant create SIP call - target device not registered\n"); dialog_unlink_all(p); dialog_unref(p, "unref dialog p UNREGISTERED"); /* sip_destroy(p); */ if (callid) { ast_callid_unref(callid); } return NULL; } if (ast_strlen_zero(p->peername) && ext) ast_string_field_set(p, peername, ext); /* Recalculate our side, and recalculate Call ID */ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); /* 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. */ check_for_nat(&p->sa, p); /* 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 (p->relatedpeer) { if (!ast_strlen_zero(p->relatedpeer->fullcontact) && !p->natdetected && (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT) && !ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))) { /* We need to make an attempt to determine if a peer is behind NAT if the peer has the auto_force_rport flag set. */ struct ast_sockaddr tmpaddr; __set_address_from_contact(p->relatedpeer->fullcontact, &tmpaddr, 0); check_for_nat(&tmpaddr, p); } set_peer_nat(p, p->relatedpeer); } do_setnat(p); build_via(p); /* Change the dialog callid. */ change_callid_pvt(p, 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 (ext) { ast_string_field_set(p, username, ext); ast_string_field_set(p, fullcontact, NULL); } if (secret && !ast_strlen_zero(secret)) ast_string_field_set(p, peersecret, secret); if (md5secret && !ast_strlen_zero(md5secret)) ast_string_field_set(p, peermd5secret, md5secret); if (authname && !ast_strlen_zero(authname)) ast_string_field_set(p, authname, authname); #if 0 printf("Setting up to call extension '%s' at '%s'\n", ext ? ext : "", host); #endif ast_format_cap_append_from_cap(p->prefcaps, cap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_get_compatible(cap, p->caps, p->jointcaps); sip_pvt_lock(p); tmpc = sip_new(p, AST_STATE_DOWN, host, assignedids, requestor, callid); /* Place the call */ if (callid) { callid = ast_callid_unref(callid); } sip_pvt_unlock(p); if (!tmpc) { dialog_unlink_all(p); /* sip_destroy(p); */ } else { ast_channel_unlock(tmpc); } dialog_unref(p, "toss pvt ptr at end of sip_request_call"); ast_update_use_count(); restart_monitor(); return tmpc; } /*! \brief Parse insecure= setting in sip.conf and set flags according to setting */ static void set_insecure_flags (struct ast_flags *flags, const char *value, int lineno) { if (ast_strlen_zero(value)) return; if (!ast_false(value)) { char buf[64]; char *word, *next; ast_copy_string(buf, value, sizeof(buf)); next = buf; while ((word = strsep(&next, ","))) { if (!strcasecmp(word, "port")) ast_set_flag(&flags[0], SIP_INSECURE_PORT); else if (!strcasecmp(word, "invite")) ast_set_flag(&flags[0], SIP_INSECURE_INVITE); else ast_log(LOG_WARNING, "Unknown insecure mode '%s' on line %d\n", value, lineno); } } } /*! \brief Handle T.38 configuration options common to users and peers \returns non-zero if any config options were handled, zero otherwise */ static int handle_t38_options(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *v, unsigned int *maxdatagram) { int res = 1; if (!strcasecmp(v->name, "t38pt_udptl")) { char *buf = ast_strdupa(v->value); char *word, *next = buf; ast_set_flag(&mask[1], SIP_PAGE2_T38SUPPORT); while ((word = strsep(&next, ","))) { if (ast_true(word) || !strcasecmp(word, "fec")) { ast_clear_flag(&flags[1], SIP_PAGE2_T38SUPPORT); ast_set_flag(&flags[1], SIP_PAGE2_T38SUPPORT_UDPTL_FEC); } else if (!strcasecmp(word, "redundancy")) { ast_clear_flag(&flags[1], SIP_PAGE2_T38SUPPORT); ast_set_flag(&flags[1], SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY); } else if (!strcasecmp(word, "none")) { ast_clear_flag(&flags[1], SIP_PAGE2_T38SUPPORT); ast_set_flag(&flags[1], SIP_PAGE2_T38SUPPORT_UDPTL); } else if (!strncasecmp(word, "maxdatagram=", 12)) { if (sscanf(&word[12], "%30u", maxdatagram) != 1) { ast_log(LOG_WARNING, "Invalid maxdatagram '%s' at line %d of %s\n", v->value, v->lineno, config); *maxdatagram = global_t38_maxdatagram; } } } } else if (!strcasecmp(v->name, "t38pt_usertpsource")) { ast_set_flag(&mask[1], SIP_PAGE2_UDPTL_DESTINATION); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_UDPTL_DESTINATION); } else { res = 0; } return res; } /*! \brief Handle flag-type options common to configuration of devices - peers \param flags array of three struct ast_flags \param mask array of three struct ast_flags \param v linked list of config variables to process \returns non-zero if any config options were handled, zero otherwise */ static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *v) { int res = 1; if (!strcasecmp(v->name, "trustrpid")) { ast_set_flag(&mask[0], SIP_TRUSTRPID); ast_set2_flag(&flags[0], ast_true(v->value), SIP_TRUSTRPID); } else if (!strcasecmp(v->name, "supportpath")) { ast_set_flag(&mask[0], SIP_USEPATH); ast_set2_flag(&flags[0], ast_true(v->value), SIP_USEPATH); } else if (!strcasecmp(v->name, "sendrpid")) { ast_set_flag(&mask[0], SIP_SENDRPID); if (!strcasecmp(v->value, "pai")) { ast_set_flag(&flags[0], SIP_SENDRPID_PAI); } else if (!strcasecmp(v->value, "rpid")) { ast_set_flag(&flags[0], SIP_SENDRPID_RPID); } else if (ast_true(v->value)) { ast_set_flag(&flags[0], SIP_SENDRPID_RPID); } } else if (!strcasecmp(v->name, "rpid_update")) { ast_set_flag(&mask[1], SIP_PAGE2_RPID_UPDATE); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RPID_UPDATE); } else if (!strcasecmp(v->name, "rpid_immediate")) { ast_set_flag(&mask[1], SIP_PAGE2_RPID_IMMEDIATE); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RPID_IMMEDIATE); } else if (!strcasecmp(v->name, "trust_id_outbound")) { ast_set_flag(&mask[1], SIP_PAGE2_TRUST_ID_OUTBOUND); ast_clear_flag(&flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND); if (!strcasecmp(v->value, "legacy")) { ast_set_flag(&flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY); } else if (ast_true(v->value)) { ast_set_flag(&flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND_YES); } else if (ast_false(v->value)) { ast_set_flag(&flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND_NO); } else { ast_log(LOG_WARNING, "Unknown trust_id_outbound mode '%s' on line %d, using legacy\n", v->value, v->lineno); ast_set_flag(&flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY); } } else if (!strcasecmp(v->name, "g726nonstandard")) { ast_set_flag(&mask[0], SIP_G726_NONSTANDARD); ast_set2_flag(&flags[0], ast_true(v->value), SIP_G726_NONSTANDARD); } else if (!strcasecmp(v->name, "useclientcode")) { ast_set_flag(&mask[0], SIP_USECLIENTCODE); ast_set2_flag(&flags[0], ast_true(v->value), SIP_USECLIENTCODE); } else if (!strcasecmp(v->name, "dtmfmode")) { ast_set_flag(&mask[0], SIP_DTMF); ast_clear_flag(&flags[0], SIP_DTMF); if (!strcasecmp(v->value, "inband")) ast_set_flag(&flags[0], SIP_DTMF_INBAND); else if (!strcasecmp(v->value, "rfc2833")) ast_set_flag(&flags[0], SIP_DTMF_RFC2833); else if (!strcasecmp(v->value, "info")) ast_set_flag(&flags[0], SIP_DTMF_INFO); else if (!strcasecmp(v->value, "shortinfo")) ast_set_flag(&flags[0], SIP_DTMF_SHORTINFO); else if (!strcasecmp(v->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", v->value, v->lineno); ast_set_flag(&flags[0], SIP_DTMF_RFC2833); } } else if (!strcasecmp(v->name, "nat")) { sip_parse_nat_option(v->value, mask, flags); } else if (!strcasecmp(v->name, "directmedia") || !strcasecmp(v->name, "canreinvite")) { ast_set_flag(&mask[0], SIP_REINVITE); ast_clear_flag(&flags[0], SIP_REINVITE); if (ast_true(v->value)) { ast_set_flag(&flags[0], SIP_DIRECT_MEDIA | SIP_DIRECT_MEDIA_NAT); } else if (!ast_false(v->value)) { char buf[64]; char *word, *next = buf; ast_copy_string(buf, v->value, sizeof(buf)); while ((word = strsep(&next, ","))) { if (!strcasecmp(word, "update")) { ast_set_flag(&flags[0], SIP_REINVITE_UPDATE | SIP_DIRECT_MEDIA); } else if (!strcasecmp(word, "nonat")) { ast_set_flag(&flags[0], SIP_DIRECT_MEDIA); ast_clear_flag(&flags[0], SIP_DIRECT_MEDIA_NAT); } else if (!strcasecmp(word, "outgoing")) { ast_set_flag(&flags[0], SIP_DIRECT_MEDIA); ast_set_flag(&mask[2], SIP_PAGE3_DIRECT_MEDIA_OUTGOING); ast_set_flag(&flags[2], SIP_PAGE3_DIRECT_MEDIA_OUTGOING); } else { ast_log(LOG_WARNING, "Unknown directmedia mode '%s' on line %d\n", v->value, v->lineno); } } } } else if (!strcasecmp(v->name, "insecure")) { ast_set_flag(&mask[0], SIP_INSECURE); ast_clear_flag(&flags[0], SIP_INSECURE); set_insecure_flags(&flags[0], v->value, v->lineno); } else if (!strcasecmp(v->name, "progressinband")) { ast_set_flag(&mask[0], SIP_PROG_INBAND); ast_clear_flag(&flags[0], SIP_PROG_INBAND); if (ast_true(v->value)) ast_set_flag(&flags[0], SIP_PROG_INBAND_YES); else if (strcasecmp(v->value, "never")) ast_set_flag(&flags[0], SIP_PROG_INBAND_NO); } else if (!strcasecmp(v->name, "promiscredir")) { ast_set_flag(&mask[0], SIP_PROMISCREDIR); ast_set2_flag(&flags[0], ast_true(v->value), SIP_PROMISCREDIR); } else if (!strcasecmp(v->name, "videosupport")) { if (!strcasecmp(v->value, "always")) { ast_set_flag(&mask[1], SIP_PAGE2_VIDEOSUPPORT_ALWAYS); ast_set_flag(&flags[1], SIP_PAGE2_VIDEOSUPPORT_ALWAYS); } else { ast_set_flag(&mask[1], SIP_PAGE2_VIDEOSUPPORT); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_VIDEOSUPPORT); } } else if (!strcasecmp(v->name, "textsupport")) { ast_set_flag(&mask[1], SIP_PAGE2_TEXTSUPPORT); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_TEXTSUPPORT); res = 1; } else if (!strcasecmp(v->name, "allowoverlap")) { ast_set_flag(&mask[1], SIP_PAGE2_ALLOWOVERLAP); ast_clear_flag(&flags[1], SIP_PAGE2_ALLOWOVERLAP); if (ast_true(v->value)) { ast_set_flag(&flags[1], SIP_PAGE2_ALLOWOVERLAP_YES); } else if (!strcasecmp(v->value, "dtmf")){ ast_set_flag(&flags[1], SIP_PAGE2_ALLOWOVERLAP_DTMF); } } else if (!strcasecmp(v->name, "allowsubscribe")) { ast_set_flag(&mask[1], SIP_PAGE2_ALLOWSUBSCRIBE); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_ALLOWSUBSCRIBE); } else if (!strcasecmp(v->name, "ignoresdpversion")) { ast_set_flag(&mask[1], SIP_PAGE2_IGNORESDPVERSION); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_IGNORESDPVERSION); } else if (!strcasecmp(v->name, "faxdetect")) { ast_set_flag(&mask[1], SIP_PAGE2_FAX_DETECT); if (ast_true(v->value)) { ast_set_flag(&flags[1], SIP_PAGE2_FAX_DETECT_BOTH); } else if (ast_false(v->value)) { ast_clear_flag(&flags[1], SIP_PAGE2_FAX_DETECT_BOTH); } else { char *buf = ast_strdupa(v->value); char *word, *next = buf; while ((word = strsep(&next, ","))) { if (!strcasecmp(word, "cng")) { ast_set_flag(&flags[1], SIP_PAGE2_FAX_DETECT_CNG); } else if (!strcasecmp(word, "t38")) { ast_set_flag(&flags[1], SIP_PAGE2_FAX_DETECT_T38); } else { ast_log(LOG_WARNING, "Unknown faxdetect mode '%s' on line %d.\n", word, v->lineno); } } } } else if (!strcasecmp(v->name, "rfc2833compensate")) { ast_set_flag(&mask[1], SIP_PAGE2_RFC2833_COMPENSATE); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RFC2833_COMPENSATE); } else if (!strcasecmp(v->name, "buggymwi")) { ast_set_flag(&mask[1], SIP_PAGE2_BUGGY_MWI); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_BUGGY_MWI); } else res = 0; return res; } /*! \brief Add SIP domain to list of domains we are responsible for */ static int add_sip_domain(const char *domain, const enum domain_mode mode, const char *context) { struct domain *d; if (ast_strlen_zero(domain)) { ast_log(LOG_WARNING, "Zero length domain.\n"); return 1; } if (!(d = ast_calloc(1, sizeof(*d)))) return 0; ast_copy_string(d->domain, domain, sizeof(d->domain)); if (!ast_strlen_zero(context)) ast_copy_string(d->context, context, sizeof(d->context)); d->mode = mode; AST_LIST_LOCK(&domain_list); AST_LIST_INSERT_TAIL(&domain_list, d, list); AST_LIST_UNLOCK(&domain_list); if (sipdebug) ast_debug(1, "Added local SIP domain '%s'\n", domain); return 1; } /*! \brief check_sip_domain: Check if domain part of uri is local to our server */ static int check_sip_domain(const char *domain, char *context, size_t len) { struct domain *d; int result = 0; AST_LIST_LOCK(&domain_list); AST_LIST_TRAVERSE(&domain_list, d, list) { if (strcasecmp(d->domain, domain)) { continue; } if (len && !ast_strlen_zero(d->context)) ast_copy_string(context, d->context, len); result = 1; break; } AST_LIST_UNLOCK(&domain_list); return result; } /*! \brief Clear our domain list (at reload) */ static void clear_sip_domains(void) { struct domain *d; AST_LIST_LOCK(&domain_list); while ((d = AST_LIST_REMOVE_HEAD(&domain_list, list))) ast_free(d); AST_LIST_UNLOCK(&domain_list); } /*! * \internal * \brief Realm authentication container destructor. * * \param obj Container object to destroy. * * \return Nothing */ static void destroy_realm_authentication(void *obj) { struct sip_auth_container *credentials = obj; struct sip_auth *auth; while ((auth = AST_LIST_REMOVE_HEAD(&credentials->list, node))) { ast_free(auth); } } /*! * \internal * \brief Add realm authentication to credentials. * * \param credentials Realm authentication container to create/add authentication credentials. * \param configuration Credential configuration value. * \param lineno Line number in config file. * * \return Nothing */ static void add_realm_authentication(struct sip_auth_container **credentials, const char *configuration, int lineno) { char *authcopy; char *username=NULL, *realm=NULL, *secret=NULL, *md5secret=NULL; struct sip_auth *auth; if (ast_strlen_zero(configuration)) { /* Nothing to add */ return; } ast_debug(1, "Auth config :: %s\n", configuration); authcopy = ast_strdupa(configuration); username = authcopy; /* split user[:secret] and relm */ realm = strrchr(username, '@'); if (realm) *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 (!*credentials) { *credentials = ao2_t_alloc(sizeof(**credentials), destroy_realm_authentication, "Create realm auth container."); if (!*credentials) { /* Failed to create the credentials container. */ return; } } /* Create the authentication credential entry. */ auth = ast_calloc(1, sizeof(*auth)); if (!auth) { return; } ast_copy_string(auth->realm, realm, sizeof(auth->realm)); ast_copy_string(auth->username, username, sizeof(auth->username)); if (secret) ast_copy_string(auth->secret, secret, sizeof(auth->secret)); if (md5secret) ast_copy_string(auth->md5secret, md5secret, sizeof(auth->md5secret)); /* Add credential to container list. */ AST_LIST_INSERT_TAIL(&(*credentials)->list, auth, node); ast_verb(3, "Added authentication for realm %s\n", realm); } /*! * \internal * \brief Find authentication for a specific realm. * * \param credentials Realm authentication container to search. * \param realm Authentication realm to find. * * \return Found authentication credential or NULL. */ static struct sip_auth *find_realm_authentication(struct sip_auth_container *credentials, const char *realm) { struct sip_auth *auth; if (credentials) { AST_LIST_TRAVERSE(&credentials->list, auth, node) { if (!strcasecmp(auth->realm, realm)) { break; } } } else { auth = NULL; } return auth; } /*! \brief * implement the setvar config line */ static struct ast_variable *add_var(const char *buf, struct ast_variable *list) { struct ast_variable *tmpvar = NULL; char *varname = ast_strdupa(buf), *varval = NULL; if ((varval = strchr(varname, '='))) { *varval++ = '\0'; if ((tmpvar = ast_variable_new(varname, varval, ""))) { tmpvar->next = list; list = tmpvar; } } return list; } /*! \brief Set peer defaults before configuring specific configurations */ static void set_peer_defaults(struct sip_peer *peer) { if (peer->expire == 0) { /* Don't reset expire or port time during reload if we have an active registration */ peer->expire = -1; peer->pokeexpire = -1; peer->keepalivesend = -1; set_socket_transport(&peer->socket, AST_TRANSPORT_UDP); } peer->type = SIP_TYPE_PEER; ast_copy_flags(&peer->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY); ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY); ast_copy_flags(&peer->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY); ast_string_field_set(peer, context, sip_cfg.default_context); ast_string_field_set(peer, record_on_feature, sip_cfg.default_record_on_feature); ast_string_field_set(peer, record_off_feature, sip_cfg.default_record_off_feature); ast_string_field_set(peer, messagecontext, sip_cfg.messagecontext); ast_string_field_set(peer, subscribecontext, sip_cfg.default_subscribecontext); ast_string_field_set(peer, language, default_language); ast_string_field_set(peer, mohinterpret, default_mohinterpret); ast_string_field_set(peer, mohsuggest, default_mohsuggest); ast_string_field_set(peer, engine, default_engine); ast_sockaddr_setnull(&peer->addr); ast_sockaddr_setnull(&peer->defaddr); ast_format_cap_append_from_cap(peer->caps, sip_cfg.caps, AST_MEDIA_TYPE_UNKNOWN); peer->maxcallbitrate = default_maxcallbitrate; peer->rtptimeout = global_rtptimeout; peer->rtpholdtimeout = global_rtpholdtimeout; peer->rtpkeepalive = global_rtpkeepalive; peer->allowtransfer = sip_cfg.allowtransfer; peer->autoframing = global_autoframing; peer->t38_maxdatagram = global_t38_maxdatagram; peer->qualifyfreq = global_qualifyfreq; if (global_callcounter) peer->call_limit=INT_MAX; ast_string_field_set(peer, vmexten, default_vmexten); ast_string_field_set(peer, secret, ""); ast_string_field_set(peer, description, ""); 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 = default_qualify; peer->keepalive = default_keepalive; ast_string_field_set(peer, zone, default_zone); peer->stimer.st_mode_oper = global_st_mode; /* Session-Timers */ peer->stimer.st_ref = global_st_refresher; peer->stimer.st_min_se = global_min_se; peer->stimer.st_max_se = global_max_se; peer->timer_t1 = global_t1; peer->timer_b = global_timer_b; clear_peer_mailboxes(peer); peer->disallowed_methods = sip_cfg.disallowed_methods; peer->transports = default_transports; peer->default_outbound_transport = default_primary_transport; if (peer->outboundproxy) { ao2_ref(peer->outboundproxy, -1); peer->outboundproxy = NULL; } } /*! \brief Create temporary peer (used in autocreatepeer mode) */ static struct sip_peer *temp_peer(const char *name) { struct sip_peer *peer; if (!(peer = ao2_t_alloc(sizeof(*peer), sip_destroy_peer_fn, "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->cc_params = ast_cc_config_params_init())) { ao2_t_ref(peer, -1, "failed to allocate cc_params for 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_atomic_fetchadd_int(&apeerobjs, 1); set_peer_defaults(peer); ast_copy_string(peer->name, name, sizeof(peer->name)); peer->selfdestruct = TRUE; peer->host_dynamic = TRUE; reg_source_db(peer); return peer; } /*! \todo document this function */ static void add_peer_mailboxes(struct sip_peer *peer, const char *value) { char *next; char *mbox; next = ast_strdupa(value); while ((mbox = strsep(&next, ","))) { struct sip_mailbox *mailbox; int duplicate = 0; /* remove leading/trailing whitespace from mailbox string */ mbox = ast_strip(mbox); if (ast_strlen_zero(mbox)) { continue; } /* Check whether the mailbox is already in the list */ AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { if (!strcmp(mailbox->id, mbox)) { duplicate = 1; break; } } if (duplicate) { continue; } mailbox = ast_calloc(1, sizeof(*mailbox) + strlen(mbox)); if (!mailbox) { continue; } strcpy(mailbox->id, mbox); /* SAFE */ AST_LIST_INSERT_TAIL(&peer->mailboxes, mailbox, entry); } } /*! \brief Build peer from configuration (file or realtime static/dynamic) */ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only) { struct sip_peer *peer = NULL; struct ast_acl_list *oldacl = NULL; struct ast_acl_list *olddirectmediaacl = NULL; int found = 0; int firstpass = 1; uint16_t port = 0; int format = 0; /* Ama flags */ int timerb_set = 0, timert1_set = 0; time_t regseconds = 0; struct ast_flags peerflags[3] = {{(0)}}; struct ast_flags mask[3] = {{(0)}}; struct sip_peer tmp_peer; const char *srvlookup = NULL; static int deprecation_warning = 1; int alt_fullcontact = alt ? 1 : 0, headercount = 0; struct ast_str *fullcontact = ast_str_alloca(512); int acl_change_subscription_needed = 0; if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { /* Note we do NOT use sip_find_peer here, to avoid realtime recursion */ /* We also use a case-sensitive comparison (unlike sip_find_peer) so that case changes made to the peer name will be properly handled during reload */ ast_copy_string(tmp_peer.name, name, sizeof(tmp_peer.name)); peer = ao2_t_find(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 peers_by_ip container yet this leads to a wrong refcounter and the peer object is never destroyed */ if (!ast_sockaddr_isnull(&peer->addr)) { ao2_t_unlink(peers_by_ip, peer, "ao2_unlink peer from peers_by_ip table"); } if (!(peer->the_mark)) firstpass = 0; } else { if (!(peer = ao2_t_alloc(sizeof(*peer), sip_destroy_peer_fn, "allocate a peer struct"))) { return NULL; } if (!(peer->endpoint = ast_endpoint_create("SIP", name))) { 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 (!(peer->cc_params = ast_cc_config_params_init())) { ao2_t_ref(peer, -1, "failed to allocate cc_params for peer"); return NULL; } if (realtime && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { ast_atomic_fetchadd_int(&rpeerobjs, 1); ast_debug(3, "-REALTIME- peer built. Name: %s. Peer objects: %d\n", name, rpeerobjs); } else ast_atomic_fetchadd_int(&speerobjs, 1); } /* Note that our peer HAS had its reference count increased */ if (firstpass) { oldacl = peer->acl; peer->acl = NULL; olddirectmediaacl = peer->directmediaacl; peer->directmediaacl = NULL; set_peer_defaults(peer); /* Set peer defaults */ peer->type = 0; } /* in case the case of the peer name has changed, update the name */ ast_copy_string(peer->name, name, sizeof(peer->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 = 0; /* 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; AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { mailbox->delme = 1; } } /* 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 (; v || ((v = alt) && !(alt=NULL)); v = v->next) { if (!devstate_only) { if (handle_common_options(&peerflags[0], &mask[0], v)) { continue; } if (handle_t38_options(&peerflags[0], &mask[0], v, &peer->t38_maxdatagram)) { continue; } if (!strcasecmp(v->name, "transport")) { char *val = ast_strdupa(v->value); char *trans; peer->transports = peer->default_outbound_transport = 0; while ((trans = strsep(&val, ","))) { trans = ast_skip_blanks(trans); if (!strncasecmp(trans, "udp", 3)) { peer->transports |= AST_TRANSPORT_UDP; } else if (!strncasecmp(trans, "wss", 3)) { peer->transports |= AST_TRANSPORT_WSS; } else if (!strncasecmp(trans, "ws", 2)) { peer->transports |= AST_TRANSPORT_WS; } else if (sip_cfg.tcp_enabled && !strncasecmp(trans, "tcp", 3)) { peer->transports |= AST_TRANSPORT_TCP; } else if (default_tls_cfg.enabled && !strncasecmp(trans, "tls", 3)) { peer->transports |= AST_TRANSPORT_TLS; } else if (!strncasecmp(trans, "tcp", 3) || !strncasecmp(trans, "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", trans, trans); } 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", trans); } if (!peer->default_outbound_transport) { /*!< The first transport listed should be default outbound */ peer->default_outbound_transport = peer->transports; } } } else if (realtime && !strcasecmp(v->name, "regseconds")) { ast_get_time_t(v->value, ®seconds, 0, NULL); } else if (realtime && !strcasecmp(v->name, "name")) { ast_copy_string(peer->name, v->value, sizeof(peer->name)); } else if (realtime && !strcasecmp(v->name, "useragent")) { ast_string_field_set(peer, useragent, v->value); } else if (!strcasecmp(v->name, "type")) { if (!strcasecmp(v->value, "peer")) { peer->type |= SIP_TYPE_PEER; } else if (!strcasecmp(v->value, "user")) { peer->type |= SIP_TYPE_USER; } else if (!strcasecmp(v->value, "friend")) { peer->type = SIP_TYPE_USER | SIP_TYPE_PEER; } } else if (!strcasecmp(v->name, "remotesecret")) { ast_string_field_set(peer, remotesecret, v->value); } else if (!strcasecmp(v->name, "secret")) { ast_string_field_set(peer, secret, v->value); } else if (!strcasecmp(v->name, "description")) { ast_string_field_set(peer, description, v->value); } else if (!strcasecmp(v->name, "md5secret")) { ast_string_field_set(peer, md5secret, v->value); } else if (!strcasecmp(v->name, "auth")) { add_realm_authentication(&peer->auth, v->value, v->lineno); } else if (!strcasecmp(v->name, "callerid")) { char cid_name[80] = { '\0' }, cid_num[80] = { '\0' }; ast_callerid_split(v->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(v->name, "mwi_from")) { ast_string_field_set(peer, mwi_from, v->value); } else if (!strcasecmp(v->name, "fullname")) { ast_string_field_set(peer, cid_name, v->value); } else if (!strcasecmp(v->name, "trunkname")) { /* This is actually for a trunk, so we don't want to override callerid */ ast_string_field_set(peer, cid_name, ""); } else if (!strcasecmp(v->name, "cid_number")) { ast_string_field_set(peer, cid_num, v->value); } else if (!strcasecmp(v->name, "cid_tag")) { ast_string_field_set(peer, cid_tag, v->value); } else if (!strcasecmp(v->name, "context")) { ast_string_field_set(peer, context, v->value); ast_set_flag(&peer->flags[1], SIP_PAGE2_HAVEPEERCONTEXT); } else if (!strcasecmp(v->name, "recordonfeature")) { ast_string_field_set(peer, record_on_feature, v->value); } else if (!strcasecmp(v->name, "recordofffeature")) { ast_string_field_set(peer, record_off_feature, v->value); } else if (!strcasecmp(v->name, "outofcall_message_context")) { ast_string_field_set(peer, messagecontext, v->value); } else if (!strcasecmp(v->name, "subscribecontext")) { ast_string_field_set(peer, subscribecontext, v->value); } else if (!strcasecmp(v->name, "fromdomain")) { char *fromdomainport; ast_string_field_set(peer, fromdomain, v->value); if ((fromdomainport = strchr(peer->fromdomain, ':'))) { *fromdomainport++ = '\0'; if (!(peer->fromdomainport = port_str2int(fromdomainport, 0))) { ast_log(LOG_NOTICE, "'%s' is not a valid port number for fromdomain.\n",fromdomainport); } } else { peer->fromdomainport = STANDARD_SIP_PORT; } } else if (!strcasecmp(v->name, "usereqphone")) { ast_set2_flag(&peer->flags[0], ast_true(v->value), SIP_USEREQPHONE); } else if (!strcasecmp(v->name, "fromuser")) { ast_string_field_set(peer, fromuser, v->value); } else if (!strcasecmp(v->name, "outboundproxy")) { struct sip_proxy *proxy; if (ast_strlen_zero(v->value)) { ast_log(LOG_WARNING, "no value given for outbound proxy on line %d of sip.conf\n", v->lineno); continue; } proxy = proxy_from_config(v->value, v->lineno, peer->outboundproxy); if (!proxy) { ast_log(LOG_WARNING, "failure parsing the outbound proxy on line %d of sip.conf.\n", v->lineno); continue; } peer->outboundproxy = proxy; } else if (!strcasecmp(v->name, "host")) { if (!strcasecmp(v->value, "dynamic")) { /* They'll register with us */ if ((!found && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) || !peer->host_dynamic) { /* Initialize stuff if this is a new peer, or if it used to * not be dynamic before the reload. */ 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(sched, peer->expire, sip_unref_peer(peer, "removing register expire ref")); peer->host_dynamic = FALSE; srvlookup = v->value; } } else if (!strcasecmp(v->name, "defaultip")) { peer->defaddr.ss.ss_family = AST_AF_UNSPEC; if (!ast_strlen_zero(v->value) && ast_get_ip(&peer->defaddr, v->value)) { sip_unref_peer(peer, "sip_unref_peer: from build_peer defaultip"); return NULL; } } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny") || !strcasecmp(v->name, "acl")) { int ha_error = 0; if (!ast_strlen_zero(v->value)) { ast_append_acl(v->name, v->value, &peer->acl, &ha_error, &acl_change_subscription_needed); } if (ha_error) { ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value); } } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny") || !strcasecmp(v->name, "contactacl")) { int ha_error = 0; if (!ast_strlen_zero(v->value)) { ast_append_acl(v->name + 7, v->value, &peer->contactacl, &ha_error, &acl_change_subscription_needed); } if (ha_error) { ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value); } } else if (!strcasecmp(v->name, "directmediapermit") || !strcasecmp(v->name, "directmediadeny") || !strcasecmp(v->name, "directmediaacl")) { int ha_error = 0; ast_append_acl(v->name + 11, v->value, &peer->directmediaacl, &ha_error, &acl_change_subscription_needed); if (ha_error) { ast_log(LOG_ERROR, "Bad directmedia ACL entry in configuration line %d : %s\n", v->lineno, v->value); } } else if (!strcasecmp(v->name, "port")) { peer->portinuri = 1; if (!(port = port_str2int(v->value, 0))) { if (realtime) { /* If stored as integer, could be 0 for some DBs (notably MySQL) */ peer->portinuri = 0; } else { ast_log(LOG_WARNING, "Invalid peer port configuration at line %d : %s\n", v->lineno, v->value); } } } else if (!strcasecmp(v->name, "callingpres")) { peer->callingpres = ast_parse_caller_presentation(v->value); if (peer->callingpres == -1) { peer->callingpres = atoi(v->value); } } else if (!strcasecmp(v->name, "username") || !strcmp(v->name, "defaultuser")) { /* "username" is deprecated */ ast_string_field_set(peer, username, v->value); if (!strcasecmp(v->name, "username")) { if (deprecation_warning) { ast_log(LOG_NOTICE, "The 'username' field for sip peers has been deprecated in favor of the term 'defaultuser'\n"); deprecation_warning = 0; } peer->deprecated_username = 1; } } else if (!strcasecmp(v->name, "tonezone")) { struct ast_tone_zone *new_zone; if (!(new_zone = ast_get_indication_zone(v->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", v->value, peer->name, v->lineno); } else { ast_tone_zone_unref(new_zone); ast_string_field_set(peer, zone, v->value); } } else if (!strcasecmp(v->name, "language")) { ast_string_field_set(peer, language, v->value); } else if (!strcasecmp(v->name, "regexten")) { ast_string_field_set(peer, regexten, v->value); } else if (!strcasecmp(v->name, "callbackextension")) { ast_string_field_set(peer, callback, v->value); } else if (!strcasecmp(v->name, "amaflags")) { format = ast_channel_string2amaflag(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags for peer: %s at line %d\n", v->value, v->lineno); } else { peer->amaflags = format; } } else if (!strcasecmp(v->name, "maxforwards")) { if (sscanf(v->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", v->value, v->lineno); peer->maxforwards = sip_cfg.default_max_forwards; } } else if (!strcasecmp(v->name, "accountcode")) { ast_string_field_set(peer, accountcode, v->value); } else if (!strcasecmp(v->name, "mohinterpret")) { ast_string_field_set(peer, mohinterpret, v->value); } else if (!strcasecmp(v->name, "mohsuggest")) { ast_string_field_set(peer, mohsuggest, v->value); } else if (!strcasecmp(v->name, "parkinglot")) { ast_string_field_set(peer, parkinglot, v->value); } else if (!strcasecmp(v->name, "rtp_engine")) { ast_string_field_set(peer, engine, v->value); } else if (!strcasecmp(v->name, "mailbox")) { add_peer_mailboxes(peer, v->value); } else if (!strcasecmp(v->name, "hasvoicemail")) { /* People expect that if 'hasvoicemail' is set, that the mailbox will * be also set, even if not explicitly specified. */ if (ast_true(v->value) && AST_LIST_EMPTY(&peer->mailboxes)) { /* * hasvoicemail is a users.conf legacy voicemail enable method. * hasvoicemail is only going to work for app_voicemail mailboxes. */ if (strchr(name, '@')) { add_peer_mailboxes(peer, name); } else { char mailbox[AST_MAX_MAILBOX_UNIQUEID]; snprintf(mailbox, sizeof(mailbox), "%s@default", name); add_peer_mailboxes(peer, mailbox); } } } else if (!strcasecmp(v->name, "subscribemwi")) { ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_SUBSCRIBEMWIONLY); } else if (!strcasecmp(v->name, "vmexten")) { ast_string_field_set(peer, vmexten, v->value); } else if (!strcasecmp(v->name, "callgroup")) { peer->callgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "allowtransfer")) { peer->allowtransfer = ast_true(v->value) ? TRANSFER_OPENFORALL : TRANSFER_CLOSED; } else if (!strcasecmp(v->name, "pickupgroup")) { peer->pickupgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "namedcallgroup")) { peer->named_callgroups = ast_get_namedgroups(v->value); } else if (!strcasecmp(v->name, "namedpickupgroup")) { peer->named_pickupgroups = ast_get_namedgroups(v->value); } else if (!strcasecmp(v->name, "allow")) { int error = ast_format_cap_update_by_allow_disallow(peer->caps, v->value, TRUE); if (error) { ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value); } } else if (!strcasecmp(v->name, "disallow")) { int error = ast_format_cap_update_by_allow_disallow(peer->caps, v->value, FALSE); if (error) { ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value); } } else if (!strcasecmp(v->name, "preferred_codec_only")) { ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_PREFERRED_CODEC); } else if (!strcasecmp(v->name, "autoframing")) { peer->autoframing = ast_true(v->value); } else if (!strcasecmp(v->name, "rtptimeout")) { if ((sscanf(v->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", v->value, v->lineno); peer->rtptimeout = global_rtptimeout; } } else if (!strcasecmp(v->name, "rtpholdtimeout")) { if ((sscanf(v->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", v->value, v->lineno); peer->rtpholdtimeout = global_rtpholdtimeout; } } else if (!strcasecmp(v->name, "rtpkeepalive")) { if ((sscanf(v->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", v->value, v->lineno); peer->rtpkeepalive = global_rtpkeepalive; } } else if (!strcasecmp(v->name, "timert1")) { if ((sscanf(v->value, "%30d", &peer->timer_t1) != 1) || (peer->timer_t1 < 200) || (peer->timer_t1 < global_t1min)) { ast_log(LOG_WARNING, "'%s' is not a valid T1 time at line %d. Using default.\n", v->value, v->lineno); peer->timer_t1 = global_t1min; } timert1_set = 1; } else if (!strcasecmp(v->name, "timerb")) { if ((sscanf(v->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", v->value, v->lineno); peer->timer_b = global_timer_b; } timerb_set = 1; } else if (!strcasecmp(v->name, "setvar")) { peer->chanvars = add_var(v->value, peer->chanvars); } else if (!strcasecmp(v->name, "header")) { char tmp[4096]; snprintf(tmp, sizeof(tmp), "__SIPADDHEADERpre%2d=%s", ++headercount, v->value); peer->chanvars = add_var(tmp, peer->chanvars); } else if (!strcasecmp(v->name, "qualifyfreq")) { int i; if (sscanf(v->value, "%30d", &i) == 1) { peer->qualifyfreq = i * 1000; } else { ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d of %s\n", v->value, v->lineno, config); peer->qualifyfreq = global_qualifyfreq; } } else if (!strcasecmp(v->name, "maxcallbitrate")) { peer->maxcallbitrate = atoi(v->value); if (peer->maxcallbitrate < 0) { peer->maxcallbitrate = default_maxcallbitrate; } } else if (!strcasecmp(v->name, "session-timers")) { int i = (int) str2stmode(v->value); if (i < 0) { ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config); peer->stimer.st_mode_oper = global_st_mode; } else { peer->stimer.st_mode_oper = i; } } else if (!strcasecmp(v->name, "session-expires")) { if (sscanf(v->value, "%30d", &peer->stimer.st_max_se) != 1) { ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config); peer->stimer.st_max_se = global_max_se; } } else if (!strcasecmp(v->name, "session-minse")) { if (sscanf(v->value, "%30d", &peer->stimer.st_min_se) != 1) { ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config); peer->stimer.st_min_se = global_min_se; } if (peer->stimer.st_min_se < DEFAULT_MIN_SE) { ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < %d secs\n", v->value, v->lineno, config, DEFAULT_MIN_SE); peer->stimer.st_min_se = global_min_se; } } else if (!strcasecmp(v->name, "session-refresher")) { int i = (int) str2strefresherparam(v->value); if (i < 0) { ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config); peer->stimer.st_ref = global_st_refresher; } else { peer->stimer.st_ref = i; } } else if (!strcasecmp(v->name, "disallowed_methods")) { char *disallow = ast_strdupa(v->value); mark_parsed_methods(&peer->disallowed_methods, disallow); } else if (!strcasecmp(v->name, "unsolicited_mailbox")) { ast_string_field_set(peer, unsolicited_mailbox, v->value); } else if (!strcasecmp(v->name, "use_q850_reason")) { ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON); } else if (!strcasecmp(v->name, "encryption")) { ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_USE_SRTP); } else if (!strcasecmp(v->name, "encryption_taglen")) { ast_set2_flag(&peer->flags[2], !strcasecmp(v->value, "32"), SIP_PAGE3_SRTP_TAG_32); } else if (!strcasecmp(v->name, "snom_aoc_enabled")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC); } else if (!strcasecmp(v->name, "avpf")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_USE_AVPF); } else if (!strcasecmp(v->name, "icesupport")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_ICE_SUPPORT); } else if (!strcasecmp(v->name, "ignore_requested_pref")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_IGNORE_PREFCAPS); } else if (!strcasecmp(v->name, "discard_remote_hold_retrieval")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL); } else if (!strcasecmp(v->name, "force_avp")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_FORCE_AVP); } else { ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value); } } /* Apply the encryption tag length to the DTLS configuration, in case DTLS is in use */ peer->dtls_cfg.suite = (ast_test_flag(&peer->flags[2], SIP_PAGE3_SRTP_TAG_32) ? AST_AES_CM_128_HMAC_SHA1_32 : AST_AES_CM_128_HMAC_SHA1_80); /* These apply to devstate lookups */ if (realtime && !strcasecmp(v->name, "lastms")) { sscanf(v->value, "%30d", &peer->lastms); } else if (realtime && !strcasecmp(v->name, "ipaddr") && !ast_strlen_zero(v->value) ) { ast_sockaddr_parse(&peer->addr, v->value, PARSE_PORT_FORBID); } else if (realtime && !strcasecmp(v->name, "fullcontact")) { if (alt_fullcontact && !alt) { /* 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 = 0; 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", v->value); } else { ast_str_set(&fullcontact, 0, "%s", v->value); } } else if (!strcasecmp(v->name, "qualify")) { if (!strcasecmp(v->value, "no")) { peer->maxms = 0; } else if (!strcasecmp(v->value, "yes")) { peer->maxms = default_qualify ? default_qualify : DEFAULT_MAXMS; } else if (sscanf(v->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, v->lineno); peer->maxms = 0; } if (realtime && !ast_test_flag(&global_flags[1], SIP_PAGE2_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(v->name, "keepalive")) { if (!strcasecmp(v->value, "no")) { peer->keepalive = 0; } else if (!strcasecmp(v->value, "yes")) { peer->keepalive = DEFAULT_KEEPALIVE_INTERVAL; } else if (sscanf(v->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, v->lineno); peer->keepalive = 0; } } else if (!strcasecmp(v->name, "callcounter")) { peer->call_limit = ast_true(v->value) ? INT_MAX : 0; } else if (!strcasecmp(v->name, "call-limit")) { peer->call_limit = atoi(v->value); if (peer->call_limit < 0) { peer->call_limit = 0; } } else if (!strcasecmp(v->name, "busylevel")) { peer->busy_level = atoi(v->value); if (peer->busy_level < 0) { peer->busy_level = 0; } } else if (ast_cc_is_config_param(v->name)) { ast_cc_set_param(peer->cc_params, v->name, v->value); } } if (!devstate_only) { struct sip_mailbox *mailbox; AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->mailboxes, mailbox, entry) { if (mailbox->delme) { AST_LIST_REMOVE_CURRENT(entry); destroy_mailbox(mailbox); } } AST_LIST_TRAVERSE_SAFE_END; } if (!can_parse_xml && (ast_get_cc_agent_policy(peer->cc_params) == AST_CC_AGENT_NATIVE)) { ast_log(LOG_WARNING, "Peer %s has a cc_agent_policy of 'native' but required libxml2 dependency is not installed. Changing policy to 'never'\n", peer->name); ast_set_cc_agent_policy(peer->cc_params, AST_CC_AGENT_NEVER); } /* 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) < global_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 = global_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 = default_transports; /* Set default primary transport */ peer->default_outbound_transport = default_primary_transport; } /* The default transport type set during build_peer 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)) { set_socket_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. */ /* XXX May need to revisit the final argument; does the realtime DB store whether * the original contact was over TLS or not? XXX */ if ((!ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT) && !ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT)) || ast_sockaddr_isnull(&peer->addr)) { __set_address_from_contact(ast_str_buffer(fullcontact), &peer->addr, 0); } } if (srvlookup && peer->dnsmgr == NULL) { char transport[MAXHOSTNAMELEN]; char _srvlookup[MAXHOSTNAMELEN]; char *params; ast_copy_string(_srvlookup, srvlookup, sizeof(_srvlookup)); if ((params = strchr(_srvlookup, ';'))) { *params++ = '\0'; } snprintf(transport, sizeof(transport), "_%s._%s", get_srv_service(peer->socket.type), get_srv_protocol(peer->socket.type)); peer->addr.ss.ss_family = get_address_family_filter(peer->socket.type); /* Filter address family */ if (ast_dnsmgr_lookup_cb(_srvlookup, &peer->addr, &peer->dnsmgr, sip_cfg.srvlookup && !peer->portinuri ? transport : NULL, on_dns_update_peer, sip_ref_peer(peer, "Store peer on dnsmgr"))) { ast_log(LOG_ERROR, "srvlookup failed for host: %s, on peer %s, removing peer\n", _srvlookup, peer->name); sip_unref_peer(peer, "dnsmgr lookup failed, getting rid of peer dnsmgr ref"); sip_unref_peer(peer, "getting rid of a peer pointer"); return NULL; } if (!peer->dnsmgr) { /* dnsmgr refresh disabeld, release reference */ sip_unref_peer(peer, "dnsmgr disabled, unref peer"); } ast_string_field_set(peer, tohost, srvlookup); if (global_dynamic_exclude_static && !ast_sockaddr_isnull(&peer->addr)) { int ha_error = 0; ast_append_acl("deny", ast_sockaddr_stringify_addr(&peer->addr), &sip_cfg.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) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT); } if (ast_sockaddr_port(&peer->defaddr) == 0) { ast_sockaddr_set_port(&peer->defaddr, (peer->socket.type & AST_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT); } if (!peer->socket.port) { peer->socket.port = htons(((peer->socket.type & AST_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT)); } if (realtime) { int enablepoke = 1; if (!sip_cfg.ignore_regexpire && peer->host_dynamic) { time_t nowtime = time(NULL); if ((nowtime - regseconds) > 0) { destroy_association(peer); memset(&peer->addr, 0, sizeof(peer->addr)); peer->lastms = -1; enablepoke = 0; ast_debug(1, "Bah, we're expired (%d/%d/%d)!\n", (int)(nowtime - regseconds), (int)regseconds, (int)nowtime); } } /* Startup regular pokes */ if (!devstate_only && enablepoke) { sip_poke_peer(peer, 0); } } if (ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)) { sip_cfg.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_cfg.peer_rtupdate)) { reg_source_db(peer); } /* If they didn't request that MWI is sent *only* on subscribe, go ahead and * subscribe to it now. */ if (!devstate_only && !ast_test_flag(&peer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY) && !AST_LIST_EMPTY(&peer->mailboxes)) { add_peer_mwi_subs(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_send_mwi_to_peer(peer, 1); } peer->the_mark = 0; oldacl = ast_free_acl_list(oldacl); olddirectmediaacl = ast_free_acl_list(olddirectmediaacl); if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */ char *reg_string; if (ast_asprintf(®_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, peer->callback) >= 0) { sip_register(reg_string, 0); /* XXX TODO: count in registry_count */ ast_free(reg_string); } } /* If an ACL change subscription is needed and doesn't exist, we need one. */ if (acl_change_subscription_needed) { acl_change_stasis_subscribe(); } return peer; } static int peer_markall_func(void *device, void *arg, int flags) { struct sip_peer *peer = device; if (!peer->selfdestruct) { peer->the_mark = 1; } return 0; } static int peer_markall_autopeers_func(void *device, void *arg, int flags) { struct sip_peer *peer = device; if (peer->selfdestruct) { peer->the_mark = 1; } return 0; } /*! * \internal * \brief If no default formats are set in config, these are used */ static void sip_set_default_format_capabilities(struct ast_format_cap *cap) { ast_format_cap_remove_by_type(cap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(cap, ast_format_ulaw, 0); ast_format_cap_append(cap, ast_format_alaw, 0); ast_format_cap_append(cap, ast_format_gsm, 0); ast_format_cap_append(cap, ast_format_h263, 0); } static void display_nat_warning(const char *cat, int reason, struct ast_flags *flags) { int global_nat, specific_nat; if (reason == CHANNEL_MODULE_LOAD && (specific_nat = ast_test_flag(&flags[0], SIP_NAT_FORCE_RPORT)) != (global_nat = ast_test_flag(&global_flags[0], SIP_NAT_FORCE_RPORT))) { ast_log(LOG_WARNING, "!!! PLEASE NOTE: Setting 'nat' for a peer/user that differs from the global setting can make\n"); ast_log(LOG_WARNING, "!!! the name of that peer/user discoverable by an attacker. Replies for non-existent peers/users\n"); ast_log(LOG_WARNING, "!!! will be sent to a different port than replies for an existing peer/user. If at all possible,\n"); ast_log(LOG_WARNING, "!!! use the global 'nat' setting and do not set 'nat' per peer/user.\n"); ast_log(LOG_WARNING, "!!! (config category='%s' global force_rport='%s' peer/user force_rport='%s')\n", cat, AST_CLI_YESNO(global_nat), AST_CLI_YESNO(specific_nat)); } } static void cleanup_all_regs(void) { struct ao2_iterator iter; struct sip_registry *iterator; /* First, destroy all outstanding registry calls */ /* This is needed, since otherwise active registry entries will not be destroyed */ iter = ao2_iterator_init(registry_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "cleanup_all_regs iter"))) { ao2_lock(iterator); if (iterator->call) { ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", iterator->username, iterator->hostname); /* This will also remove references to the registry */ dialog_unlink_all(iterator->call); iterator->call = dialog_unref(iterator->call, "remove iterator->call from registry traversal"); } if (iterator->expire > -1) { AST_SCHED_DEL_UNREF(sched, iterator->expire, ao2_t_ref(iterator, -1, "reg ptr unref from reload config")); } if (iterator->timeout > -1) { AST_SCHED_DEL_UNREF(sched, iterator->timeout, ao2_t_ref(iterator, -1, "reg ptr unref from reload config")); } if (iterator->dnsmgr) { ast_dnsmgr_release(iterator->dnsmgr); iterator->dnsmgr = NULL; ao2_t_ref(iterator, -1, "reg ptr unref from dnsmgr"); } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "cleanup_all_regs iter"); } ao2_iterator_destroy(&iter); } /*! \brief Re-read SIP.conf config file \note 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 */ static int reload_config(enum channelreloadreason reason) { struct ast_config *cfg, *ucfg; struct ast_variable *v; struct sip_peer *peer; char *cat, *stringp, *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 config_flags = { (reason == CHANNEL_MODULE_LOAD || reason == CHANNEL_ACL_RELOAD) ? 0 : ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) ? 0 : CONFIG_FLAG_FILEUNCHANGED }; int auto_sip_domains = FALSE; struct ast_sockaddr old_bindaddr = bindaddr; int registry_count = 0, peer_count = 0, timerb_set = 0, timert1_set = 0; int subscribe_network_change = 1; time_t run_start, run_end; int bindport = 0; int acl_change_subscription_needed = 0; int min_subexpiry_set = 0, max_subexpiry_set = 0; run_start = time(0); ast_unload_realtime("sipregs"); ast_unload_realtime("sippeers"); cfg = ast_config_load(config, config_flags); /* We *must* have a config file otherwise stop immediately */ if (!cfg) { ast_log(LOG_NOTICE, "Unable to load config %s\n", config); return -1; } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { ucfg = ast_config_load("users.conf", config_flags); if (ucfg == CONFIG_STATUS_FILEUNCHANGED) { return 1; } else if (ucfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Contents of users.conf are invalid and cannot be parsed\n"); return 1; } /* Must reread both files, because one changed */ ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); if ((cfg = ast_config_load(config, config_flags)) == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Contents of %s are invalid and cannot be parsed\n", config); ast_config_destroy(ucfg); return 1; } if (!cfg) { /* should have been able to reload here */ ast_log(LOG_NOTICE, "Unable to load config %s\n", config); return -1; } } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Contents of %s are invalid and cannot be parsed\n", config); return 1; } else { ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); if ((ucfg = ast_config_load("users.conf", config_flags)) == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Contents of users.conf are invalid and cannot be parsed\n"); ast_config_destroy(cfg); return 1; } } sip_cfg.contact_acl = ast_free_acl_list(sip_cfg.contact_acl); default_tls_cfg.enabled = FALSE; /* Default: Disable TLS */ if (reason != CHANNEL_MODULE_LOAD) { ast_debug(4, "--------------- SIP reload started\n"); clear_sip_domains(); ast_mutex_lock(&authl_lock); if (authl) { ao2_t_ref(authl, -1, "Removing old global authentication"); authl = NULL; } ast_mutex_unlock(&authl_lock); /* Then, actually destroy users and registry */ cleanup_all_regs(); ast_debug(4, "--------------- Done destroying registry list\n"); ao2_t_callback(peers, OBJ_NODATA, peer_markall_func, NULL, "callback to mark all peers"); } /* Reset certificate handling for TLS sessions */ if (reason != CHANNEL_MODULE_LOAD) { ast_free(default_tls_cfg.certfile); ast_free(default_tls_cfg.pvtfile); ast_free(default_tls_cfg.cipher); ast_free(default_tls_cfg.cafile); ast_free(default_tls_cfg.capath); } default_tls_cfg.certfile = ast_strdup(AST_CERTFILE); /*XXX Not sure if this is useful */ default_tls_cfg.pvtfile = ast_strdup(""); default_tls_cfg.cipher = ast_strdup(""); default_tls_cfg.cafile = ast_strdup(""); default_tls_cfg.capath = ast_strdup(""); /* Initialize copy of current sip_cfg.regcontext for later use in removing stale contexts */ ast_copy_string(oldcontexts, sip_cfg.regcontext, sizeof(oldcontexts)); oldregcontext = oldcontexts; /* Clear all flags before setting default values */ /* Preserve debugging settings for console */ sipdebug &= sip_debug_console; ast_clear_flag(&global_flags[0], AST_FLAGS_ALL); ast_clear_flag(&global_flags[1], AST_FLAGS_ALL); ast_clear_flag(&global_flags[2], AST_FLAGS_ALL); /* Reset IP addresses */ ast_sockaddr_parse(&bindaddr, "0.0.0.0:0", 0); memset(&internip, 0, sizeof(internip)); /* Free memory for local network address mask */ ast_free_ha(localaddr); memset(&localaddr, 0, sizeof(localaddr)); memset(&externaddr, 0, sizeof(externaddr)); memset(&media_address, 0, sizeof(media_address)); memset(&sip_cfg.outboundproxy, 0, sizeof(struct sip_proxy)); sip_cfg.outboundproxy.force = FALSE; /*!< Don't force proxy usage, use route: headers */ default_transports = AST_TRANSPORT_UDP; default_primary_transport = AST_TRANSPORT_UDP; ourport_tcp = STANDARD_SIP_PORT; ourport_tls = STANDARD_TLS_PORT; externtcpport = STANDARD_SIP_PORT; externtlsport = STANDARD_TLS_PORT; sip_cfg.srvlookup = DEFAULT_SRVLOOKUP; global_tos_sip = DEFAULT_TOS_SIP; global_tos_audio = DEFAULT_TOS_AUDIO; global_tos_video = DEFAULT_TOS_VIDEO; global_tos_text = DEFAULT_TOS_TEXT; global_cos_sip = DEFAULT_COS_SIP; global_cos_audio = DEFAULT_COS_AUDIO; global_cos_video = DEFAULT_COS_VIDEO; global_cos_text = DEFAULT_COS_TEXT; externhost[0] = '\0'; /* External host name (for behind NAT DynDNS support) */ externexpire = 0; /* Expiration for DNS re-issuing */ externrefresh = 10; /* Reset channel settings to default before re-configuring */ sip_cfg.allow_external_domains = DEFAULT_ALLOW_EXT_DOM; /* Allow external invites */ sip_cfg.regcontext[0] = '\0'; sip_set_default_format_capabilities(sip_cfg.caps); sip_cfg.regextenonqualify = DEFAULT_REGEXTENONQUALIFY; sip_cfg.legacy_useroption_parsing = DEFAULT_LEGACY_USEROPTION_PARSING; sip_cfg.send_diversion = DEFAULT_SEND_DIVERSION; sip_cfg.notifyringing = DEFAULT_NOTIFYRINGING; sip_cfg.notifycid = DEFAULT_NOTIFYCID; sip_cfg.notifyhold = FALSE; /*!< Keep track of hold status for a peer */ sip_cfg.directrtpsetup = FALSE; /* Experimental feature, disabled by default */ sip_cfg.alwaysauthreject = DEFAULT_ALWAYSAUTHREJECT; sip_cfg.auth_options_requests = DEFAULT_AUTH_OPTIONS; sip_cfg.auth_message_requests = DEFAULT_AUTH_MESSAGE; sip_cfg.messagecontext[0] = '\0'; sip_cfg.accept_outofcall_message = DEFAULT_ACCEPT_OUTOFCALL_MESSAGE; sip_cfg.allowsubscribe = FALSE; sip_cfg.disallowed_methods = SIP_UNKNOWN; sip_cfg.contact_acl = NULL; /* Reset the contact ACL */ snprintf(global_useragent, sizeof(global_useragent), "%s %s", DEFAULT_USERAGENT, ast_get_version()); snprintf(global_sdpsession, sizeof(global_sdpsession), "%s %s", DEFAULT_SDPSESSION, ast_get_version()); snprintf(global_sdpowner, sizeof(global_sdpowner), "%s", DEFAULT_SDPOWNER); global_prematuremediafilter = TRUE; ast_copy_string(default_notifymime, DEFAULT_NOTIFYMIME, sizeof(default_notifymime)); ast_copy_string(sip_cfg.realm, S_OR(ast_config_AST_SYSTEM_NAME, DEFAULT_REALM), sizeof(sip_cfg.realm)); sip_cfg.domainsasrealm = DEFAULT_DOMAINSASREALM; ast_copy_string(default_callerid, DEFAULT_CALLERID, sizeof(default_callerid)); ast_copy_string(default_mwi_from, DEFAULT_MWI_FROM, sizeof(default_mwi_from)); sip_cfg.compactheaders = DEFAULT_COMPACTHEADERS; global_reg_timeout = DEFAULT_REGISTRATION_TIMEOUT; global_regattempts_max = 0; global_reg_retry_403 = 0; sip_cfg.pedanticsipchecking = DEFAULT_PEDANTIC; sip_cfg.autocreatepeer = DEFAULT_AUTOCREATEPEER; global_autoframing = 0; sip_cfg.allowguest = DEFAULT_ALLOWGUEST; global_callcounter = DEFAULT_CALLCOUNTER; global_match_auth_username = FALSE; /*!< Match auth username if available instead of From: Default off. */ global_rtptimeout = 0; global_rtpholdtimeout = 0; global_rtpkeepalive = DEFAULT_RTPKEEPALIVE; sip_cfg.allowtransfer = TRANSFER_OPENFORALL; /* Merrily accept all transfers by default */ sip_cfg.rtautoclear = 120; ast_set_flag(&global_flags[1], SIP_PAGE2_ALLOWSUBSCRIBE); /* Default for all devices: TRUE */ ast_set_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP_YES); /* Default for all devices: Yes */ sip_cfg.peer_rtupdate = TRUE; global_dynamic_exclude_static = 0; /* Exclude static peers */ sip_cfg.tcp_enabled = FALSE; /* Session-Timers */ global_st_mode = SESSION_TIMER_MODE_ACCEPT; global_st_refresher = SESSION_TIMER_REFRESHER_PARAM_UAS; global_min_se = DEFAULT_MIN_SE; global_max_se = DEFAULT_MAX_SE; /* Peer poking settings */ global_qualify_gap = DEFAULT_QUALIFY_GAP; global_qualify_peers = DEFAULT_QUALIFY_PEERS; /* Initialize some reasonable defaults at SIP reload (used both for channel and as default for devices */ ast_copy_string(sip_cfg.default_context, DEFAULT_CONTEXT, sizeof(sip_cfg.default_context)); ast_copy_string(sip_cfg.default_record_on_feature, DEFAULT_RECORD_FEATURE, sizeof(sip_cfg.default_record_on_feature)); ast_copy_string(sip_cfg.default_record_off_feature, DEFAULT_RECORD_FEATURE, sizeof(sip_cfg.default_record_off_feature)); sip_cfg.default_subscribecontext[0] = '\0'; sip_cfg.default_max_forwards = DEFAULT_MAX_FORWARDS; default_language[0] = '\0'; default_fromdomain[0] = '\0'; default_fromdomainport = 0; default_qualify = DEFAULT_QUALIFY; default_keepalive = DEFAULT_KEEPALIVE; default_zone[0] = '\0'; default_maxcallbitrate = DEFAULT_MAX_CALL_BITRATE; ast_copy_string(default_mohinterpret, DEFAULT_MOHINTERPRET, sizeof(default_mohinterpret)); ast_copy_string(default_mohsuggest, DEFAULT_MOHSUGGEST, sizeof(default_mohsuggest)); ast_copy_string(default_vmexten, DEFAULT_VMEXTEN, sizeof(default_vmexten)); ast_set_flag(&global_flags[0], SIP_DTMF_RFC2833); /*!< Default DTMF setting: RFC2833 */ ast_set_flag(&global_flags[0], SIP_DIRECT_MEDIA); /*!< Allow re-invites */ ast_set_flag(&global_flags[2], SIP_PAGE3_NAT_AUTO_RPORT); /*!< Default to nat=auto_force_rport */ ast_copy_string(default_engine, DEFAULT_ENGINE, sizeof(default_engine)); ast_copy_string(default_parkinglot, DEFAULT_PARKINGLOT, sizeof(default_parkinglot)); /* Debugging settings, always default to off */ dumphistory = FALSE; recordhistory = FALSE; sipdebug &= ~sip_debug_config; /* Misc settings for the channel */ global_relaxdtmf = FALSE; global_authfailureevents = FALSE; global_t1 = DEFAULT_TIMER_T1; global_timer_b = 64 * DEFAULT_TIMER_T1; global_t1min = DEFAULT_T1MIN; global_qualifyfreq = DEFAULT_QUALIFYFREQ; global_t38_maxdatagram = -1; global_shrinkcallerid = 1; global_refer_addheaders = TRUE; authlimit = DEFAULT_AUTHLIMIT; authtimeout = DEFAULT_AUTHTIMEOUT; global_store_sip_cause = DEFAULT_STORE_SIP_CAUSE; min_expiry = DEFAULT_MIN_EXPIRY; max_expiry = DEFAULT_MAX_EXPIRY; default_expiry = DEFAULT_DEFAULT_EXPIRY; sip_cfg.matchexternaddrlocally = DEFAULT_MATCHEXTERNADDRLOCALLY; /* Copy the default jb config over global_jbconf */ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); ast_clear_flag(&global_flags[1], SIP_PAGE2_FAX_DETECT); ast_clear_flag(&global_flags[1], SIP_PAGE2_VIDEOSUPPORT | SIP_PAGE2_VIDEOSUPPORT_ALWAYS); ast_clear_flag(&global_flags[1], SIP_PAGE2_TEXTSUPPORT); ast_clear_flag(&global_flags[1], SIP_PAGE2_IGNORESDPVERSION); /* Read the [general] config section of sip.conf (or from realtime config) */ for (v = ast_variable_browse(cfg, "general"); v; v = v->next) { if (handle_common_options(&setflags[0], &mask[0], v)) { continue; } if (handle_t38_options(&setflags[0], &mask[0], v, &global_t38_maxdatagram)) { continue; } /* handle jb conf */ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) { continue; } /* handle tls conf, don't allow setting of tlsverifyclient as it isn't supported by chan_sip */ if (!strcasecmp(v->name, "tlsverifyclient")) { ast_log(LOG_WARNING, "Ignoring unsupported option 'tlsverifyclient'\n"); continue; } else if (!ast_tls_read_conf(&default_tls_cfg, &sip_tls_desc, v->name, v->value)) { continue; } if (!strcasecmp(v->name, "context")) { ast_copy_string(sip_cfg.default_context, v->value, sizeof(sip_cfg.default_context)); } else if (!strcasecmp(v->name, "recordonfeature")) { ast_copy_string(sip_cfg.default_record_on_feature, v->value, sizeof(sip_cfg.default_record_on_feature)); } else if (!strcasecmp(v->name, "recordofffeature")) { ast_copy_string(sip_cfg.default_record_off_feature, v->value, sizeof(sip_cfg.default_record_off_feature)); } else if (!strcasecmp(v->name, "subscribecontext")) { ast_copy_string(sip_cfg.default_subscribecontext, v->value, sizeof(sip_cfg.default_subscribecontext)); } else if (!strcasecmp(v->name, "callcounter")) { global_callcounter = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "allowguest")) { sip_cfg.allowguest = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "realm")) { ast_copy_string(sip_cfg.realm, v->value, sizeof(sip_cfg.realm)); } else if (!strcasecmp(v->name, "domainsasrealm")) { sip_cfg.domainsasrealm = ast_true(v->value); } else if (!strcasecmp(v->name, "useragent")) { ast_copy_string(global_useragent, v->value, sizeof(global_useragent)); ast_debug(1, "Setting SIP channel User-Agent Name to %s\n", global_useragent); } else if (!strcasecmp(v->name, "sdpsession")) { ast_copy_string(global_sdpsession, v->value, sizeof(global_sdpsession)); } else if (!strcasecmp(v->name, "sdpowner")) { /* Field cannot contain spaces */ if (!strstr(v->value, " ")) { ast_copy_string(global_sdpowner, v->value, sizeof(global_sdpowner)); } else { ast_log(LOG_WARNING, "'%s' must not contain spaces at line %d. Using default.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "allowtransfer")) { sip_cfg.allowtransfer = ast_true(v->value) ? TRANSFER_OPENFORALL : TRANSFER_CLOSED; } else if (!strcasecmp(v->name, "rtcachefriends")) { ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_RTCACHEFRIENDS); } else if (!strcasecmp(v->name, "rtsavesysname")) { sip_cfg.rtsave_sysname = ast_true(v->value); } else if (!strcasecmp(v->name, "rtsavepath")) { sip_cfg.rtsave_path = ast_true(v->value); } else if (!strcasecmp(v->name, "rtupdate")) { sip_cfg.peer_rtupdate = ast_true(v->value); } else if (!strcasecmp(v->name, "ignoreregexpire")) { sip_cfg.ignore_regexpire = ast_true(v->value); } else if (!strcasecmp(v->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. */ global_t1 = atoi(v->value); } else if (!strcasecmp(v->name, "timerb")) { int tmp = atoi(v->value); if (tmp < 500) { global_timer_b = global_t1 * 64; ast_log(LOG_WARNING, "Invalid value for timerb ('%s'). Setting to default ('%d').\n", v->value, global_timer_b); } timerb_set = 1; } else if (!strcasecmp(v->name, "t1min")) { global_t1min = atoi(v->value); } else if (!strcasecmp(v->name, "transport")) { char *val = ast_strdupa(v->value); char *trans; default_transports = default_primary_transport = 0; while ((trans = strsep(&val, ","))) { trans = ast_skip_blanks(trans); if (!strncasecmp(trans, "udp", 3)) { default_transports |= AST_TRANSPORT_UDP; } else if (!strncasecmp(trans, "tcp", 3)) { default_transports |= AST_TRANSPORT_TCP; } else if (!strncasecmp(trans, "tls", 3)) { default_transports |= AST_TRANSPORT_TLS; } else if (!strncasecmp(trans, "wss", 3)) { default_transports |= AST_TRANSPORT_WSS; } else if (!strncasecmp(trans, "ws", 2)) { default_transports |= AST_TRANSPORT_WS; } else { ast_log(LOG_NOTICE, "'%s' is not a valid transport type. if no other is specified, udp will be used.\n", trans); } if (default_primary_transport == 0) { default_primary_transport = default_transports; } } } else if (!strcasecmp(v->name, "tcpenable")) { if (!ast_false(v->value)) { ast_debug(2, "Enabling TCP socket for listening\n"); sip_cfg.tcp_enabled = TRUE; } } else if (!strcasecmp(v->name, "tcpbindaddr")) { if (ast_parse_arg(v->value, PARSE_ADDR, &sip_tcp_desc.local_address)) { ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config); } ast_debug(2, "Setting TCP socket address to %s\n", ast_sockaddr_stringify(&sip_tcp_desc.local_address)); } else if (!strcasecmp(v->name, "dynamic_exclude_static") || !strcasecmp(v->name, "dynamic_excludes_static")) { global_dynamic_exclude_static = ast_true(v->value); } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny") || !strcasecmp(v->name, "contactacl")) { int ha_error = 0; ast_append_acl(v->name + 7, v->value, &sip_cfg.contact_acl, &ha_error, &acl_change_subscription_needed); if (ha_error) { ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value); } } else if (!strcasecmp(v->name, "rtautoclear")) { int i = atoi(v->value); if (i > 0) { sip_cfg.rtautoclear = i; } else { i = 0; } ast_set2_flag(&global_flags[1], i || ast_true(v->value), SIP_PAGE2_RTAUTOCLEAR); } else if (!strcasecmp(v->name, "usereqphone")) { ast_set2_flag(&global_flags[0], ast_true(v->value), SIP_USEREQPHONE); } else if (!strcasecmp(v->name, "prematuremedia")) { global_prematuremediafilter = ast_true(v->value); } else if (!strcasecmp(v->name, "relaxdtmf")) { global_relaxdtmf = ast_true(v->value); } else if (!strcasecmp(v->name, "vmexten")) { ast_copy_string(default_vmexten, v->value, sizeof(default_vmexten)); } else if (!strcasecmp(v->name, "rtptimeout")) { if ((sscanf(v->value, "%30d", &global_rtptimeout) != 1) || (global_rtptimeout < 0)) { ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", v->value, v->lineno); global_rtptimeout = 0; } } else if (!strcasecmp(v->name, "rtpholdtimeout")) { if ((sscanf(v->value, "%30d", &global_rtpholdtimeout) != 1) || (global_rtpholdtimeout < 0)) { ast_log(LOG_WARNING, "'%s' is not a valid RTP hold time at line %d. Using default.\n", v->value, v->lineno); global_rtpholdtimeout = 0; } } else if (!strcasecmp(v->name, "rtpkeepalive")) { if ((sscanf(v->value, "%30d", &global_rtpkeepalive) != 1) || (global_rtpkeepalive < 0)) { ast_log(LOG_WARNING, "'%s' is not a valid RTP keepalive time at line %d. Using default.\n", v->value, v->lineno); global_rtpkeepalive = DEFAULT_RTPKEEPALIVE; } } else if (!strcasecmp(v->name, "compactheaders")) { sip_cfg.compactheaders = ast_true(v->value); } else if (!strcasecmp(v->name, "notifymimetype")) { ast_copy_string(default_notifymime, v->value, sizeof(default_notifymime)); } else if (!strcasecmp(v->name, "directrtpsetup")) { sip_cfg.directrtpsetup = ast_true(v->value); } else if (!strcasecmp(v->name, "notifyringing")) { sip_cfg.notifyringing = ast_true(v->value); } else if (!strcasecmp(v->name, "notifyhold")) { sip_cfg.notifyhold = ast_true(v->value); } else if (!strcasecmp(v->name, "notifycid")) { if (!strcasecmp(v->value, "ignore-context")) { sip_cfg.notifycid = IGNORE_CONTEXT; } else { sip_cfg.notifycid = ast_true(v->value) ? ENABLED : DISABLED; } } else if (!strcasecmp(v->name, "alwaysauthreject")) { sip_cfg.alwaysauthreject = ast_true(v->value); } else if (!strcasecmp(v->name, "auth_options_requests")) { if (ast_true(v->value)) { sip_cfg.auth_options_requests = 1; } } else if (!strcasecmp(v->name, "auth_message_requests")) { sip_cfg.auth_message_requests = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "accept_outofcall_message")) { sip_cfg.accept_outofcall_message = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "outofcall_message_context")) { ast_copy_string(sip_cfg.messagecontext, v->value, sizeof(sip_cfg.messagecontext)); } else if (!strcasecmp(v->name, "mohinterpret")) { ast_copy_string(default_mohinterpret, v->value, sizeof(default_mohinterpret)); } else if (!strcasecmp(v->name, "mohsuggest")) { ast_copy_string(default_mohsuggest, v->value, sizeof(default_mohsuggest)); } else if (!strcasecmp(v->name, "tonezone")) { struct ast_tone_zone *new_zone; if (!(new_zone = ast_get_indication_zone(v->value))) { ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone in [general] at line %d. Check indications.conf for available country codes.\n", v->value, v->lineno); } else { ast_tone_zone_unref(new_zone); ast_copy_string(default_zone, v->value, sizeof(default_zone)); } } else if (!strcasecmp(v->name, "language")) { ast_copy_string(default_language, v->value, sizeof(default_language)); } else if (!strcasecmp(v->name, "regcontext")) { ast_copy_string(newcontexts, v->value, sizeof(newcontexts)); stringp = newcontexts; /* Let's remove any contexts that are no longer defined in regcontext */ cleanup_stale_contexts(stringp, oldregcontext); /* Create contexts if they don't exist already */ while ((context = strsep(&stringp, "&"))) { ast_copy_string(used_context, context, sizeof(used_context)); ast_context_find_or_create(NULL, NULL, context, "SIP"); } ast_copy_string(sip_cfg.regcontext, v->value, sizeof(sip_cfg.regcontext)); } else if (!strcasecmp(v->name, "regextenonqualify")) { sip_cfg.regextenonqualify = ast_true(v->value); } else if (!strcasecmp(v->name, "legacy_useroption_parsing")) { sip_cfg.legacy_useroption_parsing = ast_true(v->value); } else if (!strcasecmp(v->name, "send_diversion")) { sip_cfg.send_diversion = ast_true(v->value); } else if (!strcasecmp(v->name, "callerid")) { ast_copy_string(default_callerid, v->value, sizeof(default_callerid)); } else if (!strcasecmp(v->name, "mwi_from")) { ast_copy_string(default_mwi_from, v->value, sizeof(default_mwi_from)); } else if (!strcasecmp(v->name, "fromdomain")) { char *fromdomainport; ast_copy_string(default_fromdomain, v->value, sizeof(default_fromdomain)); if ((fromdomainport = strchr(default_fromdomain, ':'))) { *fromdomainport++ = '\0'; if (!(default_fromdomainport = port_str2int(fromdomainport, 0))) { ast_log(LOG_NOTICE, "'%s' is not a valid port number for fromdomain.\n",fromdomainport); } } else { default_fromdomainport = STANDARD_SIP_PORT; } } else if (!strcasecmp(v->name, "outboundproxy")) { struct sip_proxy *proxy; if (ast_strlen_zero(v->value)) { ast_log(LOG_WARNING, "no value given for outbound proxy on line %d of sip.conf\n", v->lineno); continue; } proxy = proxy_from_config(v->value, v->lineno, &sip_cfg.outboundproxy); if (!proxy) { ast_log(LOG_WARNING, "failure parsing the outbound proxy on line %d of sip.conf.\n", v->lineno); continue; } } else if (!strcasecmp(v->name, "autocreatepeer")) { if (!strcasecmp(v->value, "persist")) { sip_cfg.autocreatepeer = AUTOPEERS_PERSIST; } else { sip_cfg.autocreatepeer = ast_true(v->value) ? AUTOPEERS_VOLATILE : AUTOPEERS_DISABLED; } } else if (!strcasecmp(v->name, "match_auth_username")) { global_match_auth_username = ast_true(v->value); } else if (!strcasecmp(v->name, "srvlookup")) { sip_cfg.srvlookup = ast_true(v->value); } else if (!strcasecmp(v->name, "pedantic")) { sip_cfg.pedanticsipchecking = ast_true(v->value); } else if (!strcasecmp(v->name, "maxexpirey") || !strcasecmp(v->name, "maxexpiry")) { max_expiry = atoi(v->value); if (max_expiry < 1) { max_expiry = DEFAULT_MAX_EXPIRY; } } else if (!strcasecmp(v->name, "minexpirey") || !strcasecmp(v->name, "minexpiry")) { min_expiry = atoi(v->value); if (min_expiry < 1) { min_expiry = DEFAULT_MIN_EXPIRY; } } else if (!strcasecmp(v->name, "defaultexpiry") || !strcasecmp(v->name, "defaultexpirey")) { default_expiry = atoi(v->value); if (default_expiry < 1) { default_expiry = DEFAULT_DEFAULT_EXPIRY; } } else if (!strcasecmp(v->name, "submaxexpirey") || !strcasecmp(v->name, "submaxexpiry")) { max_subexpiry = atoi(v->value); if (max_subexpiry < 1) { max_subexpiry = DEFAULT_MAX_EXPIRY; } max_subexpiry_set = 1; } else if (!strcasecmp(v->name, "subminexpirey") || !strcasecmp(v->name, "subminexpiry")) { min_subexpiry = atoi(v->value); if (min_subexpiry < 1) { min_subexpiry = DEFAULT_MIN_EXPIRY; } min_subexpiry_set = 1; } else if (!strcasecmp(v->name, "mwiexpiry") || !strcasecmp(v->name, "mwiexpirey")) { mwi_expiry = atoi(v->value); if (mwi_expiry < 1) { mwi_expiry = DEFAULT_MWI_EXPIRY; } } else if (!strcasecmp(v->name, "tcpauthtimeout")) { if (ast_parse_arg(v->value, PARSE_INT32|PARSE_DEFAULT|PARSE_IN_RANGE, &authtimeout, DEFAULT_AUTHTIMEOUT, 1, INT_MAX)) { ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config); } } else if (!strcasecmp(v->name, "tcpauthlimit")) { if (ast_parse_arg(v->value, PARSE_INT32|PARSE_DEFAULT|PARSE_IN_RANGE, &authlimit, DEFAULT_AUTHLIMIT, 1, INT_MAX)) { ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of %s\n", v->name, v->value, v->lineno, config); } } else if (!strcasecmp(v->name, "sipdebug")) { if (ast_true(v->value)) sipdebug |= sip_debug_config; } else if (!strcasecmp(v->name, "dumphistory")) { dumphistory = ast_true(v->value); } else if (!strcasecmp(v->name, "recordhistory")) { recordhistory = ast_true(v->value); } else if (!strcasecmp(v->name, "registertimeout")) { global_reg_timeout = atoi(v->value); if (global_reg_timeout < 1) { global_reg_timeout = DEFAULT_REGISTRATION_TIMEOUT; } } else if (!strcasecmp(v->name, "registerattempts")) { global_regattempts_max = atoi(v->value); } else if (!strcasecmp(v->name, "register_retry_403")) { global_reg_retry_403 = ast_true(v->value); } else if (!strcasecmp(v->name, "bindaddr") || !strcasecmp(v->name, "udpbindaddr")) { if (ast_parse_arg(v->value, PARSE_ADDR, &bindaddr)) { ast_log(LOG_WARNING, "Invalid address: %s\n", v->value); } } else if (!strcasecmp(v->name, "localnet")) { struct ast_ha *na; int ha_error = 0; if (!(na = ast_append_ha("d", v->value, localaddr, &ha_error))) { ast_log(LOG_WARNING, "Invalid localnet value: %s\n", v->value); } else { localaddr = na; } if (ha_error) { ast_log(LOG_ERROR, "Bad localnet configuration value line %d : %s\n", v->lineno, v->value); } } else if (!strcasecmp(v->name, "media_address")) { if (ast_parse_arg(v->value, PARSE_ADDR, &media_address)) ast_log(LOG_WARNING, "Invalid address for media_address keyword: %s\n", v->value); } else if (!strcasecmp(v->name, "externaddr") || !strcasecmp(v->name, "externip")) { if (ast_parse_arg(v->value, PARSE_ADDR, &externaddr)) { ast_log(LOG_WARNING, "Invalid address for externaddr keyword: %s\n", v->value); } externexpire = 0; } else if (!strcasecmp(v->name, "externhost")) { ast_copy_string(externhost, v->value, sizeof(externhost)); if (ast_sockaddr_resolve_first(&externaddr, externhost, 0)) { ast_log(LOG_WARNING, "Invalid address for externhost keyword: %s\n", externhost); } externexpire = time(NULL); } else if (!strcasecmp(v->name, "externrefresh")) { if (sscanf(v->value, "%30d", &externrefresh) != 1) { ast_log(LOG_WARNING, "Invalid externrefresh value '%s', must be an integer >0 at line %d\n", v->value, v->lineno); externrefresh = 10; } } else if (!strcasecmp(v->name, "externtcpport")) { if (!(externtcpport = port_str2int(v->value, 0))) { ast_log(LOG_WARNING, "Invalid externtcpport value, must be a positive integer between 1 and 65535 at line %d\n", v->lineno); externtcpport = 0; } } else if (!strcasecmp(v->name, "externtlsport")) { if (!(externtlsport = port_str2int(v->value, STANDARD_TLS_PORT))) { ast_log(LOG_WARNING, "Invalid externtlsport value, must be a positive integer between 1 and 65535 at line %d\n", v->lineno); } } else if (!strcasecmp(v->name, "allow")) { int error = ast_format_cap_update_by_allow_disallow(sip_cfg.caps, v->value, TRUE); if (error) { ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value); } } else if (!strcasecmp(v->name, "disallow")) { int error = ast_format_cap_update_by_allow_disallow(sip_cfg.caps, v->value, FALSE); if (error) { ast_log(LOG_WARNING, "Codec configuration errors found in line %d : %s = %s\n", v->lineno, v->name, v->value); } } else if (!strcasecmp(v->name, "preferred_codec_only")) { ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_PREFERRED_CODEC); } else if (!strcasecmp(v->name, "autoframing")) { global_autoframing = ast_true(v->value); } else if (!strcasecmp(v->name, "allowexternaldomains")) { sip_cfg.allow_external_domains = ast_true(v->value); } else if (!strcasecmp(v->name, "autodomain")) { auto_sip_domains = ast_true(v->value); } else if (!strcasecmp(v->name, "domain")) { char *domain = ast_strdupa(v->value); char *cntx = strchr(domain, ','); if (cntx) { *cntx++ = '\0'; } if (ast_strlen_zero(cntx)) { ast_debug(1, "No context specified at line %d for domain '%s'\n", v->lineno, domain); } if (ast_strlen_zero(domain)) { ast_log(LOG_WARNING, "Empty domain specified at line %d\n", v->lineno); } else { add_sip_domain(ast_strip(domain), SIP_DOMAIN_CONFIG, cntx ? ast_strip(cntx) : ""); } } else if (!strcasecmp(v->name, "register")) { if (sip_register(v->value, v->lineno) == 0) { registry_count++; } } else if (!strcasecmp(v->name, "mwi")) { sip_subscribe_mwi(v->value, v->lineno); } else if (!strcasecmp(v->name, "tos_sip")) { if (ast_str2tos(v->value, &global_tos_sip)) { ast_log(LOG_WARNING, "Invalid tos_sip value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "tos_audio")) { if (ast_str2tos(v->value, &global_tos_audio)) { ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "tos_video")) { if (ast_str2tos(v->value, &global_tos_video)) { ast_log(LOG_WARNING, "Invalid tos_video value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "tos_text")) { if (ast_str2tos(v->value, &global_tos_text)) { ast_log(LOG_WARNING, "Invalid tos_text value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "cos_sip")) { if (ast_str2cos(v->value, &global_cos_sip)) { ast_log(LOG_WARNING, "Invalid cos_sip value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "cos_audio")) { if (ast_str2cos(v->value, &global_cos_audio)) { ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "cos_video")) { if (ast_str2cos(v->value, &global_cos_video)) { ast_log(LOG_WARNING, "Invalid cos_video value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "cos_text")) { if (ast_str2cos(v->value, &global_cos_text)) { ast_log(LOG_WARNING, "Invalid cos_text value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "bindport")) { if (sscanf(v->value, "%5d", &bindport) != 1) { ast_log(LOG_WARNING, "Invalid port number '%s' at line %d of %s\n", v->value, v->lineno, config); } } else if (!strcasecmp(v->name, "qualify")) { if (!strcasecmp(v->value, "no")) { default_qualify = 0; } else if (!strcasecmp(v->value, "yes")) { default_qualify = DEFAULT_MAXMS; } else if (sscanf(v->value, "%30d", &default_qualify) != 1) { ast_log(LOG_WARNING, "Qualification default should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", v->lineno); default_qualify = 0; } } else if (!strcasecmp(v->name, "keepalive")) { if (!strcasecmp(v->value, "no")) { default_keepalive = 0; } else if (!strcasecmp(v->value, "yes")) { default_keepalive = DEFAULT_KEEPALIVE_INTERVAL; } else if (sscanf(v->value, "%30d", &default_keepalive) != 1) { ast_log(LOG_WARNING, "Keep alive default should be 'yes', 'no', or a number of milliseconds at line %d of sip.conf\n", v->lineno); default_keepalive = 0; } } else if (!strcasecmp(v->name, "qualifyfreq")) { int i; if (sscanf(v->value, "%30d", &i) == 1) { global_qualifyfreq = i * 1000; } else { ast_log(LOG_WARNING, "Invalid qualifyfreq number '%s' at line %d of %s\n", v->value, v->lineno, config); global_qualifyfreq = DEFAULT_QUALIFYFREQ; } } else if (!strcasecmp(v->name, "authfailureevents")) { global_authfailureevents = ast_true(v->value); } else if (!strcasecmp(v->name, "maxcallbitrate")) { default_maxcallbitrate = atoi(v->value); if (default_maxcallbitrate < 0) { default_maxcallbitrate = DEFAULT_MAX_CALL_BITRATE; } } else if (!strcasecmp(v->name, "matchexternaddrlocally") || !strcasecmp(v->name, "matchexterniplocally")) { sip_cfg.matchexternaddrlocally = ast_true(v->value); } else if (!strcasecmp(v->name, "session-timers")) { int i = (int) str2stmode(v->value); if (i < 0) { ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config); global_st_mode = SESSION_TIMER_MODE_ACCEPT; } else { global_st_mode = i; } } else if (!strcasecmp(v->name, "session-expires")) { if (sscanf(v->value, "%30d", &global_max_se) != 1) { ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config); global_max_se = DEFAULT_MAX_SE; } } else if (!strcasecmp(v->name, "session-minse")) { if (sscanf(v->value, "%30d", &global_min_se) != 1) { ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config); global_min_se = DEFAULT_MIN_SE; } if (global_min_se < DEFAULT_MIN_SE) { ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < %d secs\n", v->value, v->lineno, config, DEFAULT_MIN_SE); global_min_se = DEFAULT_MIN_SE; } } else if (!strcasecmp(v->name, "session-refresher")) { int i = (int) str2strefresherparam(v->value); if (i < 0) { ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config); global_st_refresher = SESSION_TIMER_REFRESHER_PARAM_UAS; } else { global_st_refresher = i; } } else if (!strcasecmp(v->name, "storesipcause")) { global_store_sip_cause = ast_true(v->value); if (global_store_sip_cause) { ast_log(LOG_WARNING, "Usage of SIP_CAUSE is deprecated. Please use HANGUPCAUSE instead.\n"); } } else if (!strcasecmp(v->name, "qualifygap")) { if (sscanf(v->value, "%30d", &global_qualify_gap) != 1) { ast_log(LOG_WARNING, "Invalid qualifygap '%s' at line %d of %s\n", v->value, v->lineno, config); global_qualify_gap = DEFAULT_QUALIFY_GAP; } } else if (!strcasecmp(v->name, "qualifypeers")) { if (sscanf(v->value, "%30d", &global_qualify_peers) != 1) { ast_log(LOG_WARNING, "Invalid pokepeers '%s' at line %d of %s\n", v->value, v->lineno, config); global_qualify_peers = DEFAULT_QUALIFY_PEERS; } } else if (!strcasecmp(v->name, "disallowed_methods")) { char *disallow = ast_strdupa(v->value); mark_parsed_methods(&sip_cfg.disallowed_methods, disallow); } else if (!strcasecmp(v->name, "shrinkcallerid")) { if (ast_true(v->value)) { global_shrinkcallerid = 1; } else if (ast_false(v->value)) { global_shrinkcallerid = 0; } else { ast_log(LOG_WARNING, "shrinkcallerid value %s is not valid at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "use_q850_reason")) { ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON); } else if (!strcasecmp(v->name, "maxforwards")) { if (sscanf(v->value, "%30d", &sip_cfg.default_max_forwards) != 1 || sip_cfg.default_max_forwards < 1 || 255 < sip_cfg.default_max_forwards) { ast_log(LOG_WARNING, "'%s' is not a valid maxforwards value at line %d. Using default.\n", v->value, v->lineno); sip_cfg.default_max_forwards = DEFAULT_MAX_FORWARDS; } } else if (!strcasecmp(v->name, "subscribe_network_change_event")) { if (ast_true(v->value)) { subscribe_network_change = 1; } else if (ast_false(v->value)) { subscribe_network_change = 0; } else { ast_log(LOG_WARNING, "subscribe_network_change_event value %s is not valid at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "snom_aoc_enabled")) { ast_set2_flag(&global_flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC); } else if (!strcasecmp(v->name, "icesupport")) { ast_set2_flag(&global_flags[2], ast_true(v->value), SIP_PAGE3_ICE_SUPPORT); } else if (!strcasecmp(v->name, "discard_remote_hold_retrieval")) { ast_set2_flag(&global_flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL); } else if (!strcasecmp(v->name, "parkinglot")) { ast_copy_string(default_parkinglot, v->value, sizeof(default_parkinglot)); } else if (!strcasecmp(v->name, "refer_addheaders")) { global_refer_addheaders = ast_true(v->value); } else if (!strcasecmp(v->name, "websocket_write_timeout")) { if (sscanf(v->value, "%30d", &sip_cfg.websocket_write_timeout) != 1 || sip_cfg.websocket_write_timeout < 0) { ast_log(LOG_WARNING, "'%s' is not a valid websocket_write_timeout value at line %d. Using default '%d'.\n", v->value, v->lineno, AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT); sip_cfg.websocket_write_timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT; } } } /* Override global defaults if setting found in general section */ ast_copy_flags(&global_flags[0], &setflags[0], mask[0].flags); ast_copy_flags(&global_flags[1], &setflags[1], mask[1].flags); ast_copy_flags(&global_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) { min_subexpiry = min_expiry; } if (!max_subexpiry_set) { max_subexpiry = max_expiry; } if (reason != CHANNEL_MODULE_LOAD && sip_cfg.autocreatepeer != AUTOPEERS_PERSIST) { ao2_t_callback(peers, OBJ_NODATA, peer_markall_autopeers_func, NULL, "callback to mark autopeers for destruction"); } if (subscribe_network_change) { network_change_stasis_subscribe(); } else { network_change_stasis_unsubscribe(); } if (global_t1 < global_t1min) { ast_log(LOG_WARNING, "'t1min' (%d) cannot be greater than 't1timer' (%d). Resetting 't1timer' to the value of 't1min'\n", global_t1min, global_t1); global_t1 = global_t1min; } if (global_timer_b < global_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", global_timer_b, global_t1); } else if (timerb_set) { if ((global_t1 = global_timer_b / 64) < global_t1min) { ast_log(LOG_WARNING, "Timer B has been set lower than recommended (%d < 64 * timert1=%d). (RFC 3261, 17.1.1.2)\n", global_timer_b, global_t1); global_t1 = global_t1min; global_timer_b = global_t1 * 64; } } else { global_timer_b = global_t1 * 64; } } if (!sip_cfg.allow_external_domains && AST_LIST_EMPTY(&domain_list)) { ast_log(LOG_WARNING, "To disallow external domains, you need to configure local SIP domains.\n"); sip_cfg.allow_external_domains = 1; } /* If not or badly configured, set default transports */ if (!sip_cfg.tcp_enabled && (default_transports & AST_TRANSPORT_TCP)) { ast_log(LOG_WARNING, "Cannot use 'tcp' transport with tcpenable=no. Removing from available transports.\n"); default_primary_transport &= ~AST_TRANSPORT_TCP; default_transports &= ~AST_TRANSPORT_TCP; } if (!default_tls_cfg.enabled && (default_transports & AST_TRANSPORT_TLS)) { ast_log(LOG_WARNING, "Cannot use 'tls' transport with tlsenable=no. Removing from available transports.\n"); default_primary_transport &= ~AST_TRANSPORT_TLS; default_transports &= ~AST_TRANSPORT_TLS; } if (!default_transports) { ast_log(LOG_WARNING, "No valid transports available, falling back to 'udp'.\n"); default_transports = default_primary_transport = AST_TRANSPORT_UDP; } else if (!default_primary_transport) { ast_log(LOG_WARNING, "No valid default transport. Selecting 'udp' as default.\n"); default_primary_transport = AST_TRANSPORT_UDP; } /* Build list of authentication to various SIP realms, i.e. service providers */ for (v = ast_variable_browse(cfg, "authentication"); v ; v = v->next) { /* Format for authentication is auth = username:password@realm */ if (!strcasecmp(v->name, "auth")) { add_realm_authentication(&authl, v->value, v->lineno); } } if (bindport) { if (ast_sockaddr_port(&bindaddr)) { ast_log(LOG_WARNING, "bindport is also specified in bindaddr. " "Using %d.\n", bindport); } ast_sockaddr_set_port(&bindaddr, bindport); } if (!ast_sockaddr_port(&bindaddr)) { ast_sockaddr_set_port(&bindaddr, STANDARD_SIP_PORT); } /* Set UDP address and open socket */ ast_sockaddr_copy(&internip, &bindaddr); if (ast_find_ourip(&internip, &bindaddr, 0)) { ast_log(LOG_WARNING, "Unable to get own IP address, SIP disabled\n"); ast_config_destroy(cfg); return 0; } ast_mutex_lock(&netlock); if ((sipsock > -1) && (ast_sockaddr_cmp(&old_bindaddr, &bindaddr))) { close(sipsock); sipsock = -1; } if (sipsock < 0) { sipsock = socket(ast_sockaddr_is_ipv6(&bindaddr) ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); if (sipsock < 0) { ast_log(LOG_WARNING, "Unable to create SIP socket: %s\n", strerror(errno)); ast_config_destroy(cfg); ast_mutex_unlock(&netlock); return -1; } else { /* Allow SIP clients on the same host to access us: */ const int reuseFlag = 1; setsockopt(sipsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseFlag, sizeof reuseFlag); ast_enable_packet_fragmentation(sipsock); if (ast_bind(sipsock, &bindaddr) < 0) { ast_log(LOG_WARNING, "Failed to bind to %s: %s\n", ast_sockaddr_stringify(&bindaddr), strerror(errno)); close(sipsock); sipsock = -1; } else { ast_verb(2, "SIP Listening on %s\n", ast_sockaddr_stringify(&bindaddr)); ast_set_qos(sipsock, global_tos_sip, global_cos_sip, "SIP"); } } } else { ast_set_qos(sipsock, global_tos_sip, global_cos_sip, "SIP"); } ast_mutex_unlock(&netlock); /* Start TCP server */ if (sip_cfg.tcp_enabled) { if (ast_sockaddr_isnull(&sip_tcp_desc.local_address)) { ast_sockaddr_copy(&sip_tcp_desc.local_address, &bindaddr); } if (!ast_sockaddr_port(&sip_tcp_desc.local_address)) { ast_sockaddr_set_port(&sip_tcp_desc.local_address, STANDARD_SIP_PORT); } } else { ast_sockaddr_setnull(&sip_tcp_desc.local_address); } ast_tcptls_server_start(&sip_tcp_desc); if (sip_cfg.tcp_enabled && sip_tcp_desc.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"); } /* Start TLS server if needed */ memcpy(sip_tls_desc.tls_cfg, &default_tls_cfg, sizeof(default_tls_cfg)); if (ast_ssl_setup(sip_tls_desc.tls_cfg)) { if (ast_sockaddr_isnull(&sip_tls_desc.local_address)) { ast_sockaddr_copy(&sip_tls_desc.local_address, &bindaddr); ast_sockaddr_set_port(&sip_tls_desc.local_address, STANDARD_TLS_PORT); } if (!ast_sockaddr_port(&sip_tls_desc.local_address)) { ast_sockaddr_set_port(&sip_tls_desc.local_address, STANDARD_TLS_PORT); } ast_tcptls_server_start(&sip_tls_desc); if (default_tls_cfg.enabled && sip_tls_desc.accept_fd == -1) { ast_log(LOG_ERROR, "TLS Server start failed. Not listening on TLS socket.\n"); sip_tls_desc.tls_cfg = NULL; } } else if (sip_tls_desc.tls_cfg->enabled) { sip_tls_desc.tls_cfg = NULL; ast_log(LOG_WARNING, "SIP TLS server did not load because of errors.\n"); } if (ucfg) { struct ast_variable *gen; int genhassip, genregistersip; const char *hassip, *registersip; genhassip = ast_true(ast_variable_retrieve(ucfg, "general", "hassip")); genregistersip = ast_true(ast_variable_retrieve(ucfg, "general", "registersip")); gen = ast_variable_browse(ucfg, "general"); cat = ast_category_browse(ucfg, NULL); while (cat) { if (strcasecmp(cat, "general")) { hassip = ast_variable_retrieve(ucfg, cat, "hassip"); registersip = ast_variable_retrieve(ucfg, cat, "registersip"); if (ast_true(hassip) || (!hassip && genhassip)) { peer = build_peer(cat, gen, ast_variable_browse(ucfg, cat), 0, 0); if (peer) { /* user.conf entries are always of type friend */ peer->type = SIP_TYPE_USER | SIP_TYPE_PEER; ao2_t_link(peers, peer, "link peer into peer table"); if ((peer->type & SIP_TYPE_PEER) && !ast_sockaddr_isnull(&peer->addr)) { ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); } sip_unref_peer(peer, "sip_unref_peer: from reload_config"); peer_count++; } } if (ast_true(registersip) || (!registersip && genregistersip)) { char tmp[256]; const char *host = ast_variable_retrieve(ucfg, cat, "host"); const char *username = ast_variable_retrieve(ucfg, cat, "username"); const char *secret = ast_variable_retrieve(ucfg, cat, "secret"); const char *contact = ast_variable_retrieve(ucfg, cat, "contact"); const char *authuser = ast_variable_retrieve(ucfg, cat, "authuser"); if (!host) { host = ast_variable_retrieve(ucfg, "general", "host"); } if (!username) { username = ast_variable_retrieve(ucfg, "general", "username"); } if (!secret) { secret = ast_variable_retrieve(ucfg, "general", "secret"); } if (!contact) { contact = "s"; } if (!ast_strlen_zero(username) && !ast_strlen_zero(host)) { if (!ast_strlen_zero(secret)) { if (!ast_strlen_zero(authuser)) { snprintf(tmp, sizeof(tmp), "%s?%s:%s:%s@%s/%s", cat, username, secret, authuser, host, contact); } else { snprintf(tmp, sizeof(tmp), "%s?%s:%s@%s/%s", cat, username, secret, host, contact); } } else if (!ast_strlen_zero(authuser)) { snprintf(tmp, sizeof(tmp), "%s?%s::%s@%s/%s", cat, username, authuser, host, contact); } else { snprintf(tmp, sizeof(tmp), "%s?%s@%s/%s", cat, username, host, contact); } if (sip_register(tmp, 0) == 0) { registry_count++; } } } } cat = ast_category_browse(ucfg, cat); } ast_config_destroy(ucfg); } /* Load peers, users and friends */ cat = NULL; while ( (cat = ast_category_browse(cfg, cat)) ) { const char *utype; if (!strcasecmp(cat, "general") || !strcasecmp(cat, "authentication")) continue; utype = ast_variable_retrieve(cfg, cat, "type"); if (!utype) { ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat); continue; } else { if (!strcasecmp(utype, "user")) { ; } else if (!strcasecmp(utype, "friend")) { ; } else if (!strcasecmp(utype, "peer")) { ; } else { ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, "sip.conf"); continue; } peer = build_peer(cat, ast_variable_browse(cfg, cat), NULL, 0, 0); if (peer) { display_nat_warning(cat, reason, &peer->flags[0]); ao2_t_link(peers, peer, "link peer into peers table"); if ((peer->type & SIP_TYPE_PEER) && !ast_sockaddr_isnull(&peer->addr)) { ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table"); } sip_unref_peer(peer, "unref the result of the build_peer call. Now, the links from the tables are the only ones left."); peer_count++; } } } /* 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 temp[MAXHOSTNAMELEN]; /* First our default IP address */ if (!ast_sockaddr_isnull(&bindaddr) && !ast_sockaddr_is_any(&bindaddr)) { add_sip_domain(ast_sockaddr_stringify_addr(&bindaddr), SIP_DOMAIN_AUTO, NULL); } else if (!ast_sockaddr_isnull(&internip) && !ast_sockaddr_is_any(&internip)) { /* Our internal IP address, if configured */ add_sip_domain(ast_sockaddr_stringify_addr(&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_desc.local_address) && !ast_sockaddr_cmp(&bindaddr, &sip_tcp_desc.local_address)) { add_sip_domain(ast_sockaddr_stringify_addr(&sip_tcp_desc.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_desc.local_address) && !ast_sockaddr_cmp(&bindaddr, &sip_tls_desc.local_address) && !ast_sockaddr_cmp(&sip_tcp_desc.local_address, &sip_tls_desc.local_address)) { add_sip_domain(ast_sockaddr_stringify_addr(&sip_tcp_desc.local_address), SIP_DOMAIN_AUTO, NULL); } /* Our extern IP address, if configured */ if (!ast_sockaddr_isnull(&externaddr)) { add_sip_domain(ast_sockaddr_stringify_addr(&externaddr), SIP_DOMAIN_AUTO, NULL); } /* Extern host name (NAT traversal support) */ if (!ast_strlen_zero(externhost)) { add_sip_domain(externhost, SIP_DOMAIN_AUTO, NULL); } /* Our host name */ if (!gethostname(temp, sizeof(temp))) { add_sip_domain(temp, SIP_DOMAIN_AUTO, NULL); } } /* Release configuration from memory */ ast_config_destroy(cfg); register_realtime_peers_with_callbackextens(); /* Load the list of manual NOTIFY types to support */ if (notify_types) { ast_config_destroy(notify_types); } if ((notify_types = ast_config_load(notify_config, config_flags)) == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Contents of %s are invalid and cannot be parsed.\n", notify_config); notify_types = NULL; } run_end = time(0); ast_debug(4, "SIP reload_config done...Runtime= %d sec\n", (int)(run_end-run_start)); /* If an ACL change subscription is needed and doesn't exist, we need one. */ if (acl_change_subscription_needed) { acl_change_stasis_subscribe(); } return 0; } static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_rtp_instance *instance, const char *rtptype) { struct sip_pvt *p; struct ast_acl_list *acl = NULL; int res = 1; if (!(p = ast_channel_tech_pvt(chan1))) { return 0; } sip_pvt_lock(p); if (p->relatedpeer && p->relatedpeer->directmediaacl) { acl = ast_duplicate_acl_list(p->relatedpeer->directmediaacl); } sip_pvt_unlock(p); if (!acl) { return res; } if (ast_test_flag(&p->flags[0], SIP_DIRECT_MEDIA)) { struct ast_sockaddr us = { { 0, }, }, them = { { 0, }, }; ast_rtp_instance_get_remote_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; } static int sip_allow_rtp_remote(struct ast_channel *chan1, struct ast_rtp_instance *instance) { return sip_allow_anyrtp_remote(chan1, instance, "audio"); } static int sip_allow_vrtp_remote(struct ast_channel *chan1, struct ast_rtp_instance *instance) { return sip_allow_anyrtp_remote(chan1, instance, "video"); } static enum ast_rtp_glue_result sip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct sip_pvt *p = NULL; enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_LOCAL; if (!(p = ast_channel_tech_pvt(chan))) { return AST_RTP_GLUE_RESULT_FORBID; } sip_pvt_lock(p); if (!(p->rtp)) { sip_pvt_unlock(p); return AST_RTP_GLUE_RESULT_FORBID; } ao2_ref(p->rtp, +1); *instance = p->rtp; if (ast_test_flag(&p->flags[0], SIP_DIRECT_MEDIA)) { res = AST_RTP_GLUE_RESULT_REMOTE; } else if (ast_test_flag(&p->flags[0], SIP_DIRECT_MEDIA_NAT)) { res = AST_RTP_GLUE_RESULT_REMOTE; } else if (ast_test_flag(&global_jbconf, AST_JB_FORCED)) { res = AST_RTP_GLUE_RESULT_FORBID; } if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) { switch (p->t38.state) { case T38_LOCAL_REINVITE: case T38_PEER_REINVITE: case T38_ENABLED: res = AST_RTP_GLUE_RESULT_LOCAL; break; case T38_REJECTED: default: break; } } if (p->srtp) { res = AST_RTP_GLUE_RESULT_FORBID; } sip_pvt_unlock(p); return res; } static enum ast_rtp_glue_result sip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct sip_pvt *p = NULL; enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_FORBID; if (!(p = ast_channel_tech_pvt(chan))) { return AST_RTP_GLUE_RESULT_FORBID; } sip_pvt_lock(p); if (!(p->vrtp)) { sip_pvt_unlock(p); return AST_RTP_GLUE_RESULT_FORBID; } ao2_ref(p->vrtp, +1); *instance = p->vrtp; if (ast_test_flag(&p->flags[0], SIP_DIRECT_MEDIA)) { res = AST_RTP_GLUE_RESULT_REMOTE; } sip_pvt_unlock(p); return res; } static enum ast_rtp_glue_result sip_get_trtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct sip_pvt *p = NULL; enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_FORBID; if (!(p = ast_channel_tech_pvt(chan))) { return AST_RTP_GLUE_RESULT_FORBID; } sip_pvt_lock(p); if (!(p->trtp)) { sip_pvt_unlock(p); return AST_RTP_GLUE_RESULT_FORBID; } ao2_ref(p->trtp, +1); *instance = p->trtp; if (ast_test_flag(&p->flags[0], SIP_DIRECT_MEDIA)) { res = AST_RTP_GLUE_RESULT_REMOTE; } sip_pvt_unlock(p); return res; } static int sip_set_rtp_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 *p; int changed = 0; p = ast_channel_tech_pvt(chan); if (!p) { return -1; } sip_pvt_lock(p); if (p->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)); sip_pvt_unlock(p); return 0; } /* Disable early RTP bridge */ if ((instance || vinstance || tinstance) && !ast_channel_is_bridged(chan) && !sip_cfg.directrtpsetup) { sip_pvt_unlock(p); return 0; } if (p->alreadygone) { /* If we're destroyed, don't bother */ sip_pvt_unlock(p); 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(&p->flags[0], SIP_DIRECT_MEDIA_NAT)) { sip_pvt_unlock(p); return 0; } if (instance) { changed |= ast_rtp_instance_get_and_cmp_remote_address(instance, &p->redirip); if (p->rtp) { /* Prevent audio RTCP reads */ ast_channel_set_fd(chan, 1, -1); /* Silence RTCP while audio RTP is inactive */ ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 0); } } else if (!ast_sockaddr_isnull(&p->redirip)) { memset(&p->redirip, 0, sizeof(p->redirip)); changed = 1; if (p->rtp) { /* Enable RTCP since it will be inactive if we're coming back * from a reinvite */ ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 1); /* Enable audio RTCP reads */ ast_channel_set_fd(chan, 1, ast_rtp_instance_fd(p->rtp, 1)); } } if (vinstance) { changed |= ast_rtp_instance_get_and_cmp_remote_address(vinstance, &p->vredirip); if (p->vrtp) { /* Prevent video RTCP reads */ ast_channel_set_fd(chan, 3, -1); /* Silence RTCP while video RTP is inactive */ ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_RTCP, 0); } } else if (!ast_sockaddr_isnull(&p->vredirip)) { memset(&p->vredirip, 0, sizeof(p->vredirip)); changed = 1; if (p->vrtp) { /* Enable RTCP since it will be inactive if we're coming back * from a reinvite */ ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_RTCP, 1); /* Enable video RTCP reads */ ast_channel_set_fd(chan, 3, ast_rtp_instance_fd(p->vrtp, 1)); } } if (tinstance) { changed |= ast_rtp_instance_get_and_cmp_remote_address(tinstance, &p->tredirip); } else if (!ast_sockaddr_isnull(&p->tredirip)) { memset(&p->tredirip, 0, sizeof(p->tredirip)); changed = 1; } if (cap && ast_format_cap_count(cap) && !ast_format_cap_identical(cap, p->redircaps)) { ast_format_cap_remove_by_type(p->redircaps, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append_from_cap(p->redircaps, cap, AST_MEDIA_TYPE_UNKNOWN); changed = 1; } if (ast_test_flag(&p->flags[2], SIP_PAGE3_DIRECT_MEDIA_OUTGOING) && !p->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(&p->flags[2], SIP_PAGE3_DIRECT_MEDIA_OUTGOING); sip_pvt_unlock(p); return 0; } if (changed && !ast_test_flag(&p->flags[0], SIP_GOTREFER) && !ast_test_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER)) { if (ast_channel_state(chan) != AST_STATE_UP) { /* We are in early state */ if (p->do_history) append_history(p, "ExtInv", "Initial invite sent with remote bridge proposal."); ast_debug(1, "Early remote bridge setting SIP '%s' - Sending media to %s\n", p->callid, ast_sockaddr_stringify(instance ? &p->redirip : &p->ourip)); } else if (!p->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", p->callid, ast_sockaddr_stringify(instance ? &p->redirip : &p->ourip)); transmit_reinvite_with_sdp(p, FALSE, FALSE); } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) { ast_debug(3, "Deferring reinvite on SIP '%s' - It's audio will be redirected to IP %s\n", p->callid, ast_sockaddr_stringify(instance ? &p->redirip : &p->ourip)); /* We have a pending Invite. Send re-invite when we're done with the invite */ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); } } /* Reset lastrtprx timer */ p->lastrtprx = p->lastrtptx = time(NULL); sip_pvt_unlock(p); return 0; } static void sip_get_codec(struct ast_channel *chan, struct ast_format_cap *result) { struct sip_pvt *p = ast_channel_tech_pvt(chan); ast_format_cap_append_from_cap(result, !ast_format_cap_count(p->peercaps) ? p->caps : p->peercaps, AST_MEDIA_TYPE_UNKNOWN); } static struct ast_rtp_glue sip_rtp_glue = { .type = "SIP", .get_rtp_info = sip_get_rtp_peer, .allow_rtp_remote = sip_allow_rtp_remote, .get_vrtp_info = sip_get_vrtp_peer, .allow_vrtp_remote = sip_allow_vrtp_remote, .get_trtp_info = sip_get_trtp_peer, .update_peer = sip_set_rtp_peer, .get_codec = sip_get_codec, }; static char *app_dtmfmode = "SIPDtmfMode"; static char *app_sipaddheader = "SIPAddHeader"; static char *app_sipremoveheader = "SIPRemoveHeader"; #ifdef TEST_FRAMEWORK static char *app_sipsendcustominfo = "SIPSendCustomINFO"; #endif /*! \brief Set the DTMFmode for an outbound SIP call (application) */ static int sip_dtmfmode(struct ast_channel *chan, const char *data) { struct sip_pvt *p; const char *mode = data; if (!data) { ast_log(LOG_WARNING, "This application requires the argument: info, inband, rfc2833\n"); return 0; } ast_channel_lock(chan); if (!IS_SIP_TECH(ast_channel_tech(chan))) { ast_log(LOG_WARNING, "Call this application only on SIP incoming calls\n"); ast_channel_unlock(chan); return 0; } p = ast_channel_tech_pvt(chan); if (!p) { ast_channel_unlock(chan); return 0; } sip_pvt_lock(p); if (!strcasecmp(mode, "info")) { ast_clear_flag(&p->flags[0], SIP_DTMF); ast_set_flag(&p->flags[0], SIP_DTMF_INFO); p->jointnoncodeccapability &= ~AST_RTP_DTMF; } else if (!strcasecmp(mode, "shortinfo")) { ast_clear_flag(&p->flags[0], SIP_DTMF); ast_set_flag(&p->flags[0], SIP_DTMF_SHORTINFO); p->jointnoncodeccapability &= ~AST_RTP_DTMF; } else if (!strcasecmp(mode, "rfc2833")) { ast_clear_flag(&p->flags[0], SIP_DTMF); ast_set_flag(&p->flags[0], SIP_DTMF_RFC2833); p->jointnoncodeccapability |= AST_RTP_DTMF; } else if (!strcasecmp(mode, "inband")) { ast_clear_flag(&p->flags[0], SIP_DTMF); ast_set_flag(&p->flags[0], SIP_DTMF_INBAND); p->jointnoncodeccapability &= ~AST_RTP_DTMF; } else { ast_log(LOG_WARNING, "I don't know about this dtmf mode: %s\n", mode); } if (p->rtp) ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833); if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) || (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) { enable_dsp_detect(p); } else { disable_dsp_detect(p); } sip_pvt_unlock(p); ast_channel_unlock(chan); return 0; } /*! \brief Add a SIP header to an outbound INVITE */ static int sip_addheader(struct ast_channel *chan, const char *data) { int no = 0; int ok = FALSE; char varbuf[30]; const char *inbuf = data; char *subbuf; if (ast_strlen_zero(inbuf)) { ast_log(LOG_WARNING, "This application requires the argument: Header\n"); return 0; } ast_channel_lock(chan); /* Check for headers */ while (!ok && no <= 50) { no++; snprintf(varbuf, sizeof(varbuf), "__SIPADDHEADER%.2d", no); /* Compare without the leading underscores */ if ((pbx_builtin_getvar_helper(chan, (const char *) varbuf + 2) == (const char *) NULL)) { ok = TRUE; } } if (ok) { size_t len = strlen(inbuf); subbuf = ast_alloca(len + 1); ast_get_encoded_str(inbuf, subbuf, len + 1); pbx_builtin_setvar_helper(chan, varbuf, subbuf); if (sipdebug) { ast_debug(1, "SIP Header added \"%s\" as %s\n", inbuf, varbuf); } } else { ast_log(LOG_WARNING, "Too many SIP headers added, max 50\n"); } ast_channel_unlock(chan); return 0; } /*! \brief Remove SIP headers added previously with SipAddHeader application */ static int sip_removeheader(struct ast_channel *chan, const char *data) { struct ast_var_t *newvariable; struct varshead *headp; int removeall = 0; char *inbuf = (char *) data; if (ast_strlen_zero(inbuf)) { removeall = 1; } ast_channel_lock(chan); headp=ast_channel_varshead(chan); AST_LIST_TRAVERSE_SAFE_BEGIN (headp, newvariable, entries) { if (strncmp(ast_var_name(newvariable), "SIPADDHEADER", strlen("SIPADDHEADER")) == 0) { if (removeall || (!strncasecmp(ast_var_value(newvariable),inbuf,strlen(inbuf)))) { if (sipdebug) { ast_debug(1,"removing SIP Header \"%s\" as %s\n", ast_var_value(newvariable), ast_var_name(newvariable)); } AST_LIST_REMOVE_CURRENT(entries); ast_var_delete(newvariable); } } } AST_LIST_TRAVERSE_SAFE_END; ast_channel_unlock(chan); return 0; } #ifdef TEST_FRAMEWORK /*! \brief Send a custom INFO message via AST_CONTROL_CUSTOM indication */ static int sip_sendcustominfo(struct ast_channel *chan, const char *data) { char *info_data, *useragent; if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "You must provide data to be sent\n"); return 0; } useragent = ast_strdupa(data); info_data = strsep(&useragent, ","); if (ast_sipinfo_send(chan, NULL, "text/plain", info_data, useragent)) { ast_log(LOG_WARNING, "Failed to create payload for custom SIP INFO\n"); return 0; } return 0; } #endif /*! \brief Transfer call before connect with a 302 redirect \note Called by the transfer() dialplan application through the sip_transfer() pbx interface function if the call is in ringing state \todo Fix this function so that we wait for reply to the REFER and react to errors, denials or other issues the other end might have. */ static int sip_sipredirect(struct sip_pvt *p, const char *dest) { char *cdest; char *extension, *domain; cdest = ast_strdupa(dest); extension = strsep(&cdest, "@"); domain = cdest; if (ast_strlen_zero(extension)) { ast_log(LOG_ERROR, "Missing mandatory argument: extension\n"); return 0; } /* we'll issue the redirect message here */ if (!domain) { char *local_to_header; char to_header[256]; ast_copy_string(to_header, sip_get_header(&p->initreq, "To"), sizeof(to_header)); if (ast_strlen_zero(to_header)) { ast_log(LOG_ERROR, "Cannot retrieve the 'To' header from the original SIP request!\n"); return 0; } if (((local_to_header = strcasestr(to_header, "sip:")) || (local_to_header = strcasestr(to_header, "sips:"))) && (local_to_header = strchr(local_to_header, '@'))) { char ldomain[256]; memset(ldomain, 0, sizeof(ldomain)); local_to_header++; /* This is okey because lhost and lport are as big as tmp */ sscanf(local_to_header, "%256[^<>; ]", ldomain); if (ast_strlen_zero(ldomain)) { ast_log(LOG_ERROR, "Can't find the host address\n"); return 0; } domain = ast_strdupa(ldomain); } } ast_string_field_build(p, our_contact, "Transfer ", extension, domain); transmit_response_reliable(p, "302 Moved Temporarily", &p->initreq); sip_scheddestroy(p, SIP_TRANS_TIMEOUT); /* Make sure we stop send this reply. */ sip_alreadygone(p); if (p->owner) { enum ast_control_transfer message = AST_TRANSFER_SUCCESS; ast_queue_control_data(p->owner, AST_CONTROL_TRANSFER, &message, sizeof(message)); } /* hangup here */ return 0; } static int sip_is_xml_parsable(void) { #ifdef HAVE_LIBXML2 return TRUE; #else return FALSE; #endif } /*! \brief Send a poke to all known peers */ static void sip_poke_all_peers(void) { int ms = 0, num = 0; struct ao2_iterator i; struct sip_peer *peer; if (!speerobjs) { /* No peers, just give up */ return; } i = ao2_iterator_init(peers, 0); while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { ao2_lock(peer); /* Don't schedule poking on a peer without qualify */ if (peer->maxms) { if (num == global_qualify_peers) { ms += global_qualify_gap; num = 0; } else { num++; } AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, ms, sip_poke_peer_s, peer, sip_unref_peer(_data, "removing poke peer ref"), sip_unref_peer(peer, "removing poke peer ref"), sip_ref_peer(peer, "adding poke peer ref")); } ao2_unlock(peer); sip_unref_peer(peer, "toss iterator peer ptr"); } ao2_iterator_destroy(&i); } /*! \brief Send a keepalive to all known peers */ static void sip_keepalive_all_peers(void) { struct ao2_iterator i; struct sip_peer *peer; if (!speerobjs) { /* No peers, just give up */ return; } i = ao2_iterator_init(peers, 0); while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { ao2_lock(peer); AST_SCHED_REPLACE_UNREF(peer->keepalivesend, sched, 0, sip_send_keepalive, peer, sip_unref_peer(_data, "removing poke peer ref"), sip_unref_peer(peer, "removing poke peer ref"), sip_ref_peer(peer, "adding poke peer ref")); ao2_unlock(peer); sip_unref_peer(peer, "toss iterator peer ptr"); } ao2_iterator_destroy(&i); } /*! \brief Send all known registrations */ static void sip_send_all_registers(void) { int ms; int regspacing; struct ao2_iterator iter; struct sip_registry *iterator; if (!ao2_container_count(registry_list)) { return; } regspacing = default_expiry * 1000 / ao2_container_count(registry_list); if (regspacing > 100) { regspacing = 100; } ms = regspacing; iter = ao2_iterator_init(registry_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "sip_send_all_registers iter"))) { ao2_lock(iterator); ms += regspacing; AST_SCHED_REPLACE_UNREF(iterator->expire, sched, ms, sip_reregister, iterator, ao2_t_ref(_data, -1, "REPLACE sched del decs the refcount"), ao2_t_ref(iterator, -1, "REPLACE sched add failure decs the refcount"), ao2_t_ref(iterator, +1, "REPLACE sched add incs the refcount")); ao2_unlock(iterator); ao2_t_ref(iterator, -1, "sip_send_all_registers iter"); } ao2_iterator_destroy(&iter); } /*! \brief Send all MWI subscriptions */ static void sip_send_all_mwi_subscriptions(void) { struct ao2_iterator iter; struct sip_subscription_mwi *iterator; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "sip_send_all_mwi_subscriptions iter"))) { ao2_lock(iterator); AST_SCHED_DEL(sched, iterator->resub); ao2_t_ref(iterator, +1, "mwi added to schedule"); if ((iterator->resub = ast_sched_add(sched, 1, sip_subscribe_mwi_do, iterator)) < 0) { ao2_t_ref(iterator, -1, "mwi failed to schedule"); } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "sip_send_all_mwi_subscriptions iter"); } ao2_iterator_destroy(&iter); } static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a) { struct ast_rtp_engine_dtls *dtls; /* 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 += strlen("crypto:"); if (!*srtp) { if (ast_test_flag(&p->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; } if ((dtls = ast_rtp_instance_get_dtls(rtp))) { dtls->stop(rtp); p->dtls_cfg.enabled = 0; } return TRUE; } /*! \brief Reload module */ static int sip_do_reload(enum channelreloadreason reason) { time_t start_poke, end_poke; reload_config(reason); ast_sched_dump(sched); start_poke = time(0); /* Prune peers who still are supposed to be deleted */ unlink_marked_peers_from_tables(); ast_debug(4, "--------------- Done destroying pruned peers\n"); /* Send qualify (OPTIONS) to all peers */ sip_poke_all_peers(); /* Send keepalive to all peers */ sip_keepalive_all_peers(); /* Register with all services */ sip_send_all_registers(); sip_send_all_mwi_subscriptions(); end_poke = time(0); ast_debug(4, "do_reload finished. peer poke/prune reg contact time = %d sec.\n", (int)(end_poke-start_poke)); ast_debug(4, "--------------- SIP reload done\n"); return 0; } /*! \brief Force reload of module from cli */ static char *sip_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { static struct sip_peer *tmp_peer, *new_peer; switch (cmd) { case CLI_INIT: e->command = "sip reload"; e->usage = "Usage: sip reload\n" " Reloads SIP configuration from sip.conf\n"; return NULL; case CLI_GENERATE: return NULL; } ast_mutex_lock(&sip_reload_lock); if (sip_reloading) { ast_verbose("Previous SIP reload not yet done\n"); } else { sip_reloading = TRUE; sip_reloadreason = (a && a->fd) ? CHANNEL_CLI_RELOAD : CHANNEL_MODULE_RELOAD; } ast_mutex_unlock(&sip_reload_lock); restart_monitor(); tmp_peer = bogus_peer; /* Create new bogus peer possibly with new global settings. */ if ((new_peer = temp_peer("(bogus_peer)"))) { ast_string_field_set(new_peer, md5secret, BOGUS_PEER_MD5SECRET); ast_clear_flag(&new_peer->flags[0], SIP_INSECURE); bogus_peer = new_peer; ao2_t_ref(tmp_peer, -1, "unref the old bogus_peer during reload"); } else { ast_log(LOG_ERROR, "Could not update the fake authentication peer.\n"); /* You probably have bigger (memory?) issues to worry about though.. */ } return CLI_SUCCESS; } /*! \brief Part of Asterisk module interface */ static int reload(void) { if (sip_reload(0, 0, NULL)) { return 0; } return 1; } /*! \brief Return the first entry from ast_sockaddr_resolve filtered by address family * * \warning Using this function probably means you have a faulty design. */ static int ast_sockaddr_resolve_first_af(struct ast_sockaddr *addr, const char* name, int flag, int family) { struct ast_sockaddr *addrs; int addrs_cnt; addrs_cnt = ast_sockaddr_resolve(&addrs, name, flag, family); if (addrs_cnt <= 0) { return 1; } if (addrs_cnt > 1) { ast_debug(1, "Multiple addresses, using the first one only\n"); } ast_sockaddr_copy(addr, &addrs[0]); ast_free(addrs); return 0; } /*! \brief Return the first entry from ast_sockaddr_resolve filtered by family of binddaddr * * \warning Using this function probably means you have a faulty design. */ static int ast_sockaddr_resolve_first(struct ast_sockaddr *addr, const char* name, int flag) { return ast_sockaddr_resolve_first_af(addr, name, flag, get_address_family_filter(AST_TRANSPORT_UDP)); } /*! \brief Return the first entry from ast_sockaddr_resolve filtered by family of binddaddr * * \warning Using this function probably means you have a faulty design. */ static int ast_sockaddr_resolve_first_transport(struct ast_sockaddr *addr, const char* name, int flag, unsigned int transport) { return ast_sockaddr_resolve_first_af(addr, name, flag, get_address_family_filter(transport)); } /*! \brief * \note The only member of the peer used here is the name field */ static int peer_hash_cb(const void *obj, const int flags) { const struct sip_peer *peer = obj; return ast_str_case_hash(peer->name); } /*! * \note The only member of the peer used here is the name field */ static int peer_cmp_cb(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 the peer's ip address. For IPv6, we use the end * of the address. * \todo Find a better hashing function */ static int peer_iphash_cb(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. * * This function has two modes. * - If the peer arg does not have INSECURE_PORT set, then we will only return * a match for a peer that matches both the IP and port. * - If the peer arg does have the INSECURE_PORT flag set, then we will only * return a match for a peer that matches the IP and has insecure=port * in its configuration. * * This callback will be used twice when doing peer matching. There is a first * pass for full IP+port matching, and a second pass in case there is a match * that meets the insecure=port criteria. * * \note Connections coming in over TCP or TLS should never be matched by port. * * \note the peer's addr struct provides to fields combined to make a key: the sin_addr.s_addr and sin_port fields. */ static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags) { struct sip_peer *peer = obj, *peer2 = arg; char *callback = data; if (!ast_strlen_zero(callback) && strcasecmp(peer->callback, callback)) { /* We require a callback extension match, but don't have one */ return 0; } /* At this point, we match the callback extension if we need to. Carry on. */ if (ast_sockaddr_cmp_addr(&peer->addr, &peer2->addr)) { /* IP doesn't match */ return 0; } /* We matched the IP, check to see if we need to match by port as well. */ if ((peer->transports & peer2->transports) & (AST_TRANSPORT_TLS | AST_TRANSPORT_TCP)) { /* peer matching on port is not possible with TCP/TLS */ return CMP_MATCH | CMP_STOP; } else if (ast_test_flag(&peer2->flags[0], SIP_INSECURE_PORT)) { /* We are allowing match without port for peers configured that * way in this pass through the peers. */ return ast_test_flag(&peer->flags[0], SIP_INSECURE_PORT) ? (CMP_MATCH | CMP_STOP) : 0; } /* Now only return a match if the port matches, as well. */ return ast_sockaddr_port(&peer->addr) == ast_sockaddr_port(&peer2->addr) ? (CMP_MATCH | CMP_STOP) : 0; } static int peer_ipcmp_cb(void *obj, void *arg, int flags) { return peer_ipcmp_cb_full(obj, arg, NULL, flags); } static int threadt_hash_cb(const void *obj, const int flags) { const struct sip_threadinfo *th = obj; return ast_sockaddr_hash(&th->tcptls_session->remote_address); } static int threadt_cmp_cb(void *obj, void *arg, int flags) { struct sip_threadinfo *th = obj, *th2 = arg; return (th->tcptls_session == th2->tcptls_session) ? CMP_MATCH | CMP_STOP : 0; } /*! * \note The only member of the dialog used here callid string */ static int dialog_hash_cb(const void *obj, const int flags) { const struct sip_pvt *pvt = obj; return ast_str_case_hash(pvt->callid); } /*! * \note Same as dialog_cmp_cb, except without the CMP_STOP on match */ static int dialog_find_multiple(void *obj, void *arg, int flags) { struct sip_pvt *pvt = obj, *pvt2 = arg; return !strcasecmp(pvt->callid, pvt2->callid) ? CMP_MATCH : 0; } /*! * \note The only member of the dialog used here callid string */ static int dialog_cmp_cb(void *obj, void *arg, int flags) { struct sip_pvt *pvt = obj, *pvt2 = arg; return !strcasecmp(pvt->callid, pvt2->callid) ? CMP_MATCH | CMP_STOP : 0; } static int registry_hash_cb(const void *obj, const int flags) { const struct sip_registry *object; const char *key; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_KEY: key = obj; break; case OBJ_SEARCH_OBJECT: object = obj; key = object->configvalue; break; default: /* Hash can only work on something with a full key. */ ast_assert(0); return 0; } return ast_str_hash(key); } static int registry_cmp_cb(void *obj, void *arg, int flags) { const struct sip_registry *object_left = obj; const struct sip_registry *object_right = arg; const char *right_key = arg; int cmp; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_OBJECT: right_key = object_right->configvalue; /* Fall through */ case OBJ_SEARCH_KEY: cmp = strcmp(object_left->configvalue, right_key); break; default: cmp = 0; break; } if (cmp) { return 0; } return CMP_MATCH; } /*! \brief SIP Cli commands definition */ static struct ast_cli_entry cli_sip[] = { AST_CLI_DEFINE(sip_show_channels, "List active SIP channels or subscriptions"), AST_CLI_DEFINE(sip_show_channelstats, "List statistics for active SIP channels"), AST_CLI_DEFINE(sip_show_domains, "List our local SIP domains"), AST_CLI_DEFINE(sip_show_inuse, "List all inuse/limits"), AST_CLI_DEFINE(sip_show_objects, "List all SIP object allocations"), AST_CLI_DEFINE(sip_show_peers, "List defined SIP peers"), AST_CLI_DEFINE(sip_show_registry, "List SIP registration status"), AST_CLI_DEFINE(sip_unregister, "Unregister (force expiration) a SIP peer from the registry"), AST_CLI_DEFINE(sip_show_settings, "Show SIP global settings"), AST_CLI_DEFINE(sip_show_mwi, "Show MWI subscriptions"), AST_CLI_DEFINE(sip_cli_notify, "Send a notify packet to a SIP peer"), AST_CLI_DEFINE(sip_show_channel, "Show detailed SIP channel info"), AST_CLI_DEFINE(sip_show_history, "Show SIP dialog history"), AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"), AST_CLI_DEFINE(sip_show_users, "List defined SIP users"), AST_CLI_DEFINE(sip_show_user, "Show details on specific SIP user"), AST_CLI_DEFINE(sip_qualify_peer, "Send an OPTIONS packet to a peer"), AST_CLI_DEFINE(sip_show_sched, "Present a report on the status of the scheduler queue"), AST_CLI_DEFINE(sip_prune_realtime, "Prune cached Realtime users/peers"), AST_CLI_DEFINE(sip_do_debug, "Enable/Disable SIP debugging"), AST_CLI_DEFINE(sip_set_history, "Enable/Disable SIP history"), AST_CLI_DEFINE(sip_reload, "Reload SIP configuration"), AST_CLI_DEFINE(sip_show_tcp, "List TCP Connections") }; /*! \brief SIP test registration */ static void sip_register_tests(void) { sip_config_parser_register_tests(); sip_request_parser_register_tests(); sip_dialplan_function_register_tests(); } /*! \brief SIP test registration */ static void sip_unregister_tests(void) { sip_config_parser_unregister_tests(); sip_request_parser_unregister_tests(); sip_dialplan_function_unregister_tests(); } #ifdef TEST_FRAMEWORK AST_TEST_DEFINE(test_sip_mwi_subscribe_parse) { struct ao2_iterator iter; struct sip_subscription_mwi *iterator; int found = 0; enum ast_test_result_state res = AST_TEST_PASS; const char *mwi1 = "1234@mysipprovider.com/1234"; const char *mwi2 = "1234:password@mysipprovider.com/1234"; const char *mwi3 = "1234:password@mysipprovider.com:5061/1234"; const char *mwi4 = "1234:password:authuser@mysipprovider.com/1234"; const char *mwi5 = "1234:password:authuser@mysipprovider.com:5061/1234"; const char *mwi6 = "1234:password"; switch (cmd) { case TEST_INIT: info->name = "sip_mwi_subscribe_parse_test"; info->category = "/channels/chan_sip/"; info->summary = "SIP MWI subscribe line parse unit test"; info->description = "Tests the parsing of mwi subscription lines (e.g., mwi => from sip.conf)"; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } if (sip_subscribe_mwi(mwi1, 1)) { res = AST_TEST_FAIL; } else { found = 0; res = AST_TEST_FAIL; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "test_sip_mwi_subscribe_parse mwi1"))) { ao2_lock(iterator); if ( !strcmp(iterator->hostname, "mysipprovider.com") && !strcmp(iterator->username, "1234") && !strcmp(iterator->secret, "") && !strcmp(iterator->authuser, "") && !strcmp(iterator->mailbox, "1234") && iterator->portno == 0) { found = 1; res = AST_TEST_PASS; } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "test_sip_mwi_subscribe_parse mwi1"); } ao2_iterator_destroy(&iter); if (!found) { ast_test_status_update(test, "sip_subscribe_mwi test 1 failed\n"); } } if (sip_subscribe_mwi(mwi2, 1)) { res = AST_TEST_FAIL; } else { found = 0; res = AST_TEST_FAIL; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "test_sip_mwi_subscribe_parse mwi2"))) { ao2_lock(iterator); if ( !strcmp(iterator->hostname, "mysipprovider.com") && !strcmp(iterator->username, "1234") && !strcmp(iterator->secret, "password") && !strcmp(iterator->authuser, "") && !strcmp(iterator->mailbox, "1234") && iterator->portno == 0) { found = 1; res = AST_TEST_PASS; } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "test_sip_mwi_subscribe_parse mwi2"); } ao2_iterator_destroy(&iter); if (!found) { ast_test_status_update(test, "sip_subscribe_mwi test 2 failed\n"); } } if (sip_subscribe_mwi(mwi3, 1)) { res = AST_TEST_FAIL; } else { found = 0; res = AST_TEST_FAIL; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "test_sip_mwi_subscribe_parse mwi3"))) { ao2_lock(iterator); if ( !strcmp(iterator->hostname, "mysipprovider.com") && !strcmp(iterator->username, "1234") && !strcmp(iterator->secret, "password") && !strcmp(iterator->authuser, "") && !strcmp(iterator->mailbox, "1234") && iterator->portno == 5061) { found = 1; res = AST_TEST_PASS; } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "test_sip_mwi_subscribe_parse mwi3"); } ao2_iterator_destroy(&iter); if (!found) { ast_test_status_update(test, "sip_subscribe_mwi test 3 failed\n"); } } if (sip_subscribe_mwi(mwi4, 1)) { res = AST_TEST_FAIL; } else { found = 0; res = AST_TEST_FAIL; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "test_sip_mwi_subscribe_parse mwi4"))) { ao2_lock(iterator); if ( !strcmp(iterator->hostname, "mysipprovider.com") && !strcmp(iterator->username, "1234") && !strcmp(iterator->secret, "password") && !strcmp(iterator->authuser, "authuser") && !strcmp(iterator->mailbox, "1234") && iterator->portno == 0) { found = 1; res = AST_TEST_PASS; } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "test_sip_mwi_subscribe_parse mwi4"); } ao2_iterator_destroy(&iter); if (!found) { ast_test_status_update(test, "sip_subscribe_mwi test 4 failed\n"); } } if (sip_subscribe_mwi(mwi5, 1)) { res = AST_TEST_FAIL; } else { found = 0; res = AST_TEST_FAIL; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "test_sip_mwi_subscribe_parse mwi4"))) { ao2_lock(iterator); if ( !strcmp(iterator->hostname, "mysipprovider.com") && !strcmp(iterator->username, "1234") && !strcmp(iterator->secret, "password") && !strcmp(iterator->authuser, "authuser") && !strcmp(iterator->mailbox, "1234") && iterator->portno == 5061) { found = 1; res = AST_TEST_PASS; } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "test_sip_mwi_subscribe_parse mwi4"); } ao2_iterator_destroy(&iter); if (!found) { ast_test_status_update(test, "sip_subscribe_mwi test 5 failed\n"); } } if (sip_subscribe_mwi(mwi6, 1)) { res = AST_TEST_PASS; } else { res = AST_TEST_FAIL; } return res; } AST_TEST_DEFINE(test_sip_peers_get) { struct sip_peer *peer; struct ast_data *node; struct ast_data_query query = { .path = "/asterisk/channel/sip/peers", .search = "peers/peer/name=test_peer_data_provider" }; switch (cmd) { case TEST_INIT: info->name = "sip_peers_get_data_test"; info->category = "/main/data/sip/peers/"; info->summary = "SIP peers data providers unit test"; info->description = "Tests whether the SIP peers data provider implementation works as expected."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* Create the peer that we will retrieve. */ peer = build_peer("test_peer_data_provider", NULL, NULL, 0, 0); if (!peer) { return AST_TEST_FAIL; } peer->type = SIP_TYPE_USER; peer->call_limit = 10; ao2_link(peers, peer); /* retrieve the chan_sip/peers tree and check the created peer. */ node = ast_data_get(&query); if (!node) { ao2_unlink(peers, peer); ao2_ref(peer, -1); return AST_TEST_FAIL; } /* compare item. */ if (strcmp(ast_data_retrieve_string(node, "peer/name"), "test_peer_data_provider")) { ao2_unlink(peers, peer); ao2_ref(peer, -1); ast_data_free(node); return AST_TEST_FAIL; } if (strcmp(ast_data_retrieve_string(node, "peer/type"), "user")) { ao2_unlink(peers, peer); ao2_ref(peer, -1); ast_data_free(node); return AST_TEST_FAIL; } if (ast_data_retrieve_int(node, "peer/call_limit") != 10) { ao2_unlink(peers, peer); ao2_ref(peer, -1); ast_data_free(node); return AST_TEST_FAIL; } /* release resources */ ast_data_free(node); ao2_unlink(peers, peer); ao2_ref(peer, -1); return AST_TEST_PASS; } /*! * \brief Imitation TCP reception loop * * This imitates the logic used by SIP's TCP code. Its purpose * is to either * 1) Combine fragments into a single message * 2) Break up combined messages into single messages * * \param fragments The message fragments. This simulates the data received on a TCP socket. * \param num_fragments This indicates the number of fragments to receive * \param overflow This is a place to stash extra data if more than one message is received * in a single fragment * \param[out] messages The parsed messages are placed in this array * \param[out] num_messages The number of messages that were parsed * \param test Used for printing messages * \retval 0 Success * \retval -1 Failure */ static int mock_tcp_loop(char *fragments[], size_t num_fragments, struct ast_str **overflow, char **messages, int *num_messages, struct ast_test* test) { struct ast_str *req_data; int i = 0; int res = 0; req_data = ast_str_create(128); ast_str_reset(*overflow); while (i < num_fragments || ast_str_strlen(*overflow) > 0) { enum message_integrity message_integrity = MESSAGE_FRAGMENT; ast_str_reset(req_data); while (message_integrity == MESSAGE_FRAGMENT) { if (ast_str_strlen(*overflow) > 0) { ast_str_append(&req_data, 0, "%s", ast_str_buffer(*overflow)); ast_str_reset(*overflow); } else { ast_str_append(&req_data, 0, "%s", fragments[i++]); } message_integrity = check_message_integrity(&req_data, overflow); } if (strcmp(ast_str_buffer(req_data), messages[*num_messages])) { ast_test_status_update(test, "Mismatch in SIP messages.\n"); ast_test_status_update(test, "Expected message:\n%s", messages[*num_messages]); ast_test_status_update(test, "Parsed message:\n%s", ast_str_buffer(req_data)); res = -1; goto end; } else { ast_test_status_update(test, "Successfully read message:\n%s", ast_str_buffer(req_data)); } (*num_messages)++; } end: ast_free(req_data); return res; }; AST_TEST_DEFINE(test_tcp_message_fragmentation) { /* Normal single message in one fragment */ char *normal[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Single message in two fragments. * Fragments combine to make "normal" */ char *fragmented[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: ", "70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Single message in two fragments, divided precisely at the body * Fragments combine to make "normal" */ char *fragmented_body[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n", "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Single message in three fragments * Fragments combine to make "normal" */ char *multi_fragment[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n", "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4", " 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Two messages in a single fragment * Fragments split into "multi_message_divided" */ char *multi_message[] = { "SIP/2.0 100 Trying\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: \r\n" "Content-Length: 0\r\n" "\r\n" "SIP/2.0 180 Ringing\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: \r\n" "Content-Length: 0\r\n" "\r\n" }; char *multi_message_divided[] = { "SIP/2.0 100 Trying\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: \r\n" "Content-Length: 0\r\n" "\r\n", "SIP/2.0 180 Ringing\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: \r\n" "Content-Length: 0\r\n" "\r\n" }; /* Two messages with bodies combined into one fragment * Fragments split into "multi_message_body_divided" */ char *multi_message_body[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 2 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; char *multi_message_body_divided[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n", "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 2 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: 130\r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Two messages that appear in two fragments. Fragment * boundaries do not align with message boundaries. * Fragments combine to make "multi_message_divided" */ char *multi_message_in_fragments[] = { "SIP/2.0 100 Trying\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVI", "TE\r\n" "Contact: \r\n" "Content-Length: 0\r\n" "\r\n" "SIP/2.0 180 Ringing\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: \r\n" "Content-Length: 0\r\n" "\r\n" }; /* Message with compact content-length header * Same as "normal" but with compact content-length header */ char *compact[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "l:130\r\n" /* intentionally no space */ "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Message with faux content-length headers * Same as "normal" but with extra fake content-length headers */ char *faux[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "DisContent-Length: 0\r\n" "MalContent-Length: 60\r\n" "Content-Length:130\r\n" /* intentionally no space */ "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Message with folded Content-Length header * Message is "normal" with Content-Length spread across three lines * * This is the test that requires pedantic=yes in order to pass */ char *folded[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "Content-Length: \t\r\n" "\t \r\n" " 130\t \r\n" "\r\n" "v=0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; /* Message with compact Content-length header in message and * full Content-Length header in the body. Ensure that the header * in the message is read and that the one in the body is ignored */ char *cl_in_body[] = { "INVITE sip:bob@example.org SIP/2.0\r\n" "Via: SIP/2.0/TCP 127.0.0.1:5060;branch=[branch]\r\n" "From: sipp ;tag=12345\r\n" "To: \r\n" "Call-ID: 12345\r\n" "CSeq: 1 INVITE\r\n" "Contact: sip:127.0.0.1:5061\r\n" "Max-Forwards: 70\r\n" "Content-Type: application/sdp\r\n" "l: 149\r\n" "\r\n" "v=0\r\n" "Content-Length: 0\r\n" "o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" "s=-\r\n" "c=IN IP4 127.0.0.1\r\n" "t=0 0\r\n" "m=audio 10000 RTP/AVP 0\r\n" "a=rtpmap:0 PCMU/8000\r\n" }; struct ast_str *overflow; struct { char **fragments; char **expected; int num_expected; const char *description; } tests[] = { { normal, normal, 1, "normal" }, { fragmented, normal, 1, "fragmented" }, { fragmented_body, normal, 1, "fragmented_body" }, { multi_fragment, normal, 1, "multi_fragment" }, { multi_message, multi_message_divided, 2, "multi_message" }, { multi_message_body, multi_message_body_divided, 2, "multi_message_body" }, { multi_message_in_fragments, multi_message_divided, 2, "multi_message_in_fragments" }, { compact, compact, 1, "compact" }, { faux, faux, 1, "faux" }, { folded, folded, 1, "folded" }, { cl_in_body, cl_in_body, 1, "cl_in_body" }, }; int i; enum ast_test_result_state res = AST_TEST_PASS; switch (cmd) { case TEST_INIT: info->name = "sip_tcp_message_fragmentation"; info->category = "/main/sip/transport/"; info->summary = "SIP TCP message fragmentation test"; info->description = "Tests reception of different TCP messages that have been fragmented or" "run together. This test mimicks the code that TCP reception uses."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } if (!sip_cfg.pedanticsipchecking) { ast_log(LOG_WARNING, "Not running test. Pedantic SIP checking is not enabled, so it is guaranteed to fail\n"); return AST_TEST_NOT_RUN; } overflow = ast_str_create(128); if (!overflow) { return AST_TEST_FAIL; } for (i = 0; i < ARRAY_LEN(tests); ++i) { int num_messages = 0; if (mock_tcp_loop(tests[i].fragments, ARRAY_LEN(tests[i].fragments), &overflow, tests[i].expected, &num_messages, test)) { ast_test_status_update(test, "Failed to parse message '%s'\n", tests[i].description); res = AST_TEST_FAIL; break; } if (num_messages != tests[i].num_expected) { ast_test_status_update(test, "Did not receive the expected number of messages. " "Expected %d but received %d\n", tests[i].num_expected, num_messages); res = AST_TEST_FAIL; break; } } ast_free(overflow); return res; } AST_TEST_DEFINE(get_in_brackets_const_test) { const char *input; const char *start = NULL; int len = 0; int res; #define CHECK_RESULTS(in, expected_res, expected_start, expected_len) do { \ input = (in); \ res = get_in_brackets_const(input, &start, &len); \ if ((expected_res) != res) { \ ast_test_status_update(test, "Unexpected result: %d != %d\n", expected_res, res); \ return AST_TEST_FAIL; \ } \ if ((expected_start) != start) { \ const char *e = expected_start ? expected_start : "(null)"; \ const char *a = start ? start : "(null)"; \ ast_test_status_update(test, "Unexpected start: %s != %s\n", e, a); \ return AST_TEST_FAIL; \ } \ if ((expected_len) != len) { \ ast_test_status_update(test, "Unexpected len: %d != %d\n", expected_len, len); \ return AST_TEST_FAIL; \ } \ } while(0) switch (cmd) { case TEST_INIT: info->name = __func__; info->category = "/channels/chan_sip/"; info->summary = "get_in_brackets_const test"; info->description = "Tests the get_in_brackets_const function"; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } CHECK_RESULTS("", 1, NULL, -1); CHECK_RESULTS("normal ", 0, input + 8, 4); CHECK_RESULTS("\"normal\" ", 0, input + 10, 4); CHECK_RESULTS("not normal ", 0, input + 16, 4); CHECK_RESULTS("\"even > this\" ", 0, input + 15, 4); CHECK_RESULTS("", 0, input + 1, 22); CHECK_RESULTS(", ", 0, input + 1, 22); CHECK_RESULTS("", 0, input + 1, 26); CHECK_RESULTS("", 0, input + 1, 36); CHECK_RESULTS("\"quoted text\" ", 0, input + 15, 23); return AST_TEST_PASS; } #endif #define DATA_EXPORT_SIP_PEER(MEMBER) \ MEMBER(sip_peer, name, AST_DATA_STRING) \ MEMBER(sip_peer, secret, AST_DATA_PASSWORD) \ MEMBER(sip_peer, md5secret, AST_DATA_PASSWORD) \ MEMBER(sip_peer, remotesecret, AST_DATA_PASSWORD) \ MEMBER(sip_peer, context, AST_DATA_STRING) \ MEMBER(sip_peer, subscribecontext, AST_DATA_STRING) \ MEMBER(sip_peer, username, AST_DATA_STRING) \ MEMBER(sip_peer, accountcode, AST_DATA_STRING) \ MEMBER(sip_peer, tohost, AST_DATA_STRING) \ MEMBER(sip_peer, regexten, AST_DATA_STRING) \ MEMBER(sip_peer, fromuser, AST_DATA_STRING) \ MEMBER(sip_peer, fromdomain, AST_DATA_STRING) \ MEMBER(sip_peer, fullcontact, AST_DATA_STRING) \ MEMBER(sip_peer, cid_num, AST_DATA_STRING) \ MEMBER(sip_peer, cid_name, AST_DATA_STRING) \ MEMBER(sip_peer, vmexten, AST_DATA_STRING) \ MEMBER(sip_peer, language, AST_DATA_STRING) \ MEMBER(sip_peer, mohinterpret, AST_DATA_STRING) \ MEMBER(sip_peer, mohsuggest, AST_DATA_STRING) \ MEMBER(sip_peer, parkinglot, AST_DATA_STRING) \ MEMBER(sip_peer, useragent, AST_DATA_STRING) \ MEMBER(sip_peer, mwi_from, AST_DATA_STRING) \ MEMBER(sip_peer, engine, AST_DATA_STRING) \ MEMBER(sip_peer, unsolicited_mailbox, AST_DATA_STRING) \ MEMBER(sip_peer, is_realtime, AST_DATA_BOOLEAN) \ MEMBER(sip_peer, host_dynamic, AST_DATA_BOOLEAN) \ MEMBER(sip_peer, autoframing, AST_DATA_BOOLEAN) \ MEMBER(sip_peer, inuse, AST_DATA_INTEGER) \ MEMBER(sip_peer, ringing, AST_DATA_INTEGER) \ MEMBER(sip_peer, onhold, AST_DATA_INTEGER) \ MEMBER(sip_peer, call_limit, AST_DATA_INTEGER) \ MEMBER(sip_peer, t38_maxdatagram, AST_DATA_INTEGER) \ MEMBER(sip_peer, maxcallbitrate, AST_DATA_INTEGER) \ MEMBER(sip_peer, rtptimeout, AST_DATA_SECONDS) \ MEMBER(sip_peer, rtpholdtimeout, AST_DATA_SECONDS) \ MEMBER(sip_peer, rtpkeepalive, AST_DATA_SECONDS) \ MEMBER(sip_peer, lastms, AST_DATA_MILLISECONDS) \ MEMBER(sip_peer, maxms, AST_DATA_MILLISECONDS) \ MEMBER(sip_peer, qualifyfreq, AST_DATA_MILLISECONDS) \ MEMBER(sip_peer, timer_t1, AST_DATA_MILLISECONDS) \ MEMBER(sip_peer, timer_b, AST_DATA_MILLISECONDS) \ MEMBER(sip_peer, description, AST_DATA_STRING) AST_DATA_STRUCTURE(sip_peer, DATA_EXPORT_SIP_PEER); static int peers_data_provider_get(const struct ast_data_search *search, struct ast_data *data_root) { struct sip_peer *peer; struct ao2_iterator i; struct ast_data *data_peer, *data_peer_mailboxes = NULL, *data_peer_mailbox, *enum_node; struct ast_data *data_sip_options; int total_mailboxes, x; struct sip_mailbox *mailbox; i = ao2_iterator_init(peers, 0); while ((peer = ao2_iterator_next(&i))) { ao2_lock(peer); data_peer = ast_data_add_node(data_root, "peer"); if (!data_peer) { ao2_unlock(peer); ao2_ref(peer, -1); continue; } ast_data_add_structure(sip_peer, data_peer, peer); /* transfer mode */ enum_node = ast_data_add_node(data_peer, "allowtransfer"); if (!enum_node) { ao2_unlock(peer); ao2_ref(peer, -1); continue; } ast_data_add_str(enum_node, "text", transfermode2str(peer->allowtransfer)); ast_data_add_int(enum_node, "value", peer->allowtransfer); /* transports */ ast_data_add_str(data_peer, "transports", get_transport_list(peer->transports)); /* peer type */ if ((peer->type & SIP_TYPE_USER) && (peer->type & SIP_TYPE_PEER)) { ast_data_add_str(data_peer, "type", "friend"); } else if (peer->type & SIP_TYPE_PEER) { ast_data_add_str(data_peer, "type", "peer"); } else if (peer->type & SIP_TYPE_USER) { ast_data_add_str(data_peer, "type", "user"); } /* mailboxes */ total_mailboxes = 0; AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { if (!total_mailboxes) { data_peer_mailboxes = ast_data_add_node(data_peer, "mailboxes"); if (!data_peer_mailboxes) { break; } total_mailboxes++; } data_peer_mailbox = ast_data_add_node(data_peer_mailboxes, "mailbox"); if (!data_peer_mailbox) { continue; } ast_data_add_str(data_peer_mailbox, "id", mailbox->id); } /* amaflags */ enum_node = ast_data_add_node(data_peer, "amaflags"); if (!enum_node) { ao2_unlock(peer); ao2_ref(peer, -1); continue; } ast_data_add_int(enum_node, "value", peer->amaflags); ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(peer->amaflags)); /* sip options */ data_sip_options = ast_data_add_node(data_peer, "sipoptions"); if (!data_sip_options) { ao2_unlock(peer); ao2_ref(peer, -1); continue; } for (x = 0 ; x < ARRAY_LEN(sip_options); x++) { ast_data_add_bool(data_sip_options, sip_options[x].text, peer->sipoptions & sip_options[x].id); } /* callingpres */ enum_node = ast_data_add_node(data_peer, "callingpres"); if (!enum_node) { ao2_unlock(peer); ao2_ref(peer, -1); continue; } ast_data_add_int(enum_node, "value", peer->callingpres); ast_data_add_str(enum_node, "text", ast_describe_caller_presentation(peer->callingpres)); /* codecs */ ast_data_add_codecs(data_peer, "codecs", peer->caps); if (!ast_data_search_match(search, data_peer)) { ast_data_remove_node(data_root, data_peer); } ao2_unlock(peer); ao2_ref(peer, -1); } ao2_iterator_destroy(&i); return 0; } static const struct ast_data_handler peers_data_provider = { .version = AST_DATA_HANDLER_VERSION, .get = peers_data_provider_get }; static const struct ast_data_entry sip_data_providers[] = { AST_DATA_ENTRY("asterisk/channel/sip/peers", &peers_data_provider), }; static const struct ast_sip_api_tech chan_sip_api_provider = { .version = AST_SIP_API_VERSION, .name = "chan_sip", .sipinfo_send = sipinfo_send, }; static int unload_module(void); /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { ast_verbose("SIP channel loading...\n"); if (STASIS_MESSAGE_TYPE_INIT(session_timeout_type)) { unload_module(); return AST_MODULE_LOAD_FAILURE; } if (!(sip_tech.capabilities = ast_format_cap_alloc(0))) { unload_module(); return AST_MODULE_LOAD_FAILURE; } if (ast_sip_api_provider_register(&chan_sip_api_provider)) { unload_module(); return AST_MODULE_LOAD_FAILURE; } /* 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 */ peers = ao2_t_container_alloc(HASH_PEER_SIZE, peer_hash_cb, peer_cmp_cb, "allocate peers"); peers_by_ip = ao2_t_container_alloc(HASH_PEER_SIZE, peer_iphash_cb, peer_ipcmp_cb, "allocate peers_by_ip"); dialogs = ao2_t_container_alloc(HASH_DIALOG_SIZE, dialog_hash_cb, dialog_cmp_cb, "allocate dialogs"); dialogs_needdestroy = ao2_t_container_alloc(1, NULL, NULL, "allocate dialogs_needdestroy"); dialogs_rtpcheck = ao2_t_container_alloc(HASH_DIALOG_SIZE, dialog_hash_cb, dialog_cmp_cb, "allocate dialogs for rtpchecks"); threadt = ao2_t_container_alloc(HASH_DIALOG_SIZE, threadt_hash_cb, threadt_cmp_cb, "allocate threadt table"); if (!peers || !peers_by_ip || !dialogs || !dialogs_needdestroy || !dialogs_rtpcheck || !threadt) { ast_log(LOG_ERROR, "Unable to create primary SIP container(s)\n"); unload_module(); return AST_MODULE_LOAD_FAILURE; } if (!(sip_cfg.caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { unload_module(); return AST_MODULE_LOAD_FAILURE; } ast_format_cap_append_by_type(sip_tech.capabilities, AST_MEDIA_TYPE_AUDIO); registry_list = ao2_t_container_alloc(HASH_REGISTRY_SIZE, registry_hash_cb, registry_cmp_cb, "allocate registry_list"); subscription_mwi_list = ao2_t_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_INSERT_BEGIN, NULL, NULL, "allocate subscription_mwi_list"); if (!(sched = ast_sched_context_create())) { ast_log(LOG_ERROR, "Unable to create scheduler context\n"); unload_module(); return AST_MODULE_LOAD_FAILURE; } if (!(io = io_context_create())) { ast_log(LOG_ERROR, "Unable to create I/O context\n"); unload_module(); return AST_MODULE_LOAD_FAILURE; } sip_reloadreason = CHANNEL_MODULE_LOAD; can_parse_xml = sip_is_xml_parsable(); if (reload_config(sip_reloadreason)) { /* Load the configuration from sip.conf */ unload_module(); return AST_MODULE_LOAD_DECLINE; } /* Initialize bogus peer. Can be done first after reload_config() */ if (!(bogus_peer = temp_peer("(bogus_peer)"))) { ast_log(LOG_ERROR, "Unable to create bogus_peer for authentication\n"); unload_module(); return AST_MODULE_LOAD_FAILURE; } /* Make sure the auth will always fail. */ ast_string_field_set(bogus_peer, md5secret, BOGUS_PEER_MD5SECRET); ast_clear_flag(&bogus_peer->flags[0], SIP_INSECURE); /* Prepare the version that does not require DTMF BEGIN frames. * We need to use tricks such as memcpy and casts because the variable * has const fields. */ memcpy(&sip_tech_info, &sip_tech, sizeof(sip_tech)); memset((void *) &sip_tech_info.send_digit_begin, 0, sizeof(sip_tech_info.send_digit_begin)); if (ast_msg_tech_register(&sip_msg_tech)) { unload_module(); return AST_MODULE_LOAD_FAILURE; } /* 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_FAILURE; } #ifdef TEST_FRAMEWORK AST_TEST_REGISTER(test_sip_peers_get); AST_TEST_REGISTER(test_sip_mwi_subscribe_parse); AST_TEST_REGISTER(test_tcp_message_fragmentation); AST_TEST_REGISTER(get_in_brackets_const_test); #endif /* Register AstData providers */ ast_data_register_multiple(sip_data_providers, ARRAY_LEN(sip_data_providers)); /* Register all CLI functions for SIP */ ast_cli_register_multiple(cli_sip, ARRAY_LEN(cli_sip)); /* Tell the RTP engine about our RTP glue */ ast_rtp_glue_register(&sip_rtp_glue); /* Register dialplan applications */ ast_register_application_xml(app_dtmfmode, sip_dtmfmode); ast_register_application_xml(app_sipaddheader, sip_addheader); ast_register_application_xml(app_sipremoveheader, sip_removeheader); #ifdef TEST_FRAMEWORK ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo); #endif /* Register dialplan functions */ ast_custom_function_register(&sip_header_function); ast_custom_function_register(&sippeer_function); ast_custom_function_register(&checksipdomain_function); /* Register manager commands */ ast_manager_register_xml("SIPpeers", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_show_peers); ast_manager_register_xml("SIPshowpeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_show_peer); ast_manager_register_xml("SIPqualifypeer", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_sip_qualify_peer); ast_manager_register_xml("SIPshowregistry", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_show_registry); ast_manager_register_xml("SIPnotify", EVENT_FLAG_SYSTEM, manager_sipnotify); ast_manager_register_xml("SIPpeerstatus", EVENT_FLAG_SYSTEM, manager_sip_peer_status); sip_poke_all_peers(); sip_keepalive_all_peers(); sip_send_all_registers(); sip_send_all_mwi_subscriptions(); initialize_escs(); if (sip_epa_register(&cc_epa_static_data)) { unload_module(); return AST_MODULE_LOAD_DECLINE; } if (sip_reqresp_parser_init() == -1) { ast_log(LOG_ERROR, "Unable to initialize the SIP request and response parser\n"); unload_module(); return AST_MODULE_LOAD_DECLINE; } if (can_parse_xml) { /* SIP CC agents require the ability to parse XML PIDF bodies * in incoming PUBLISH requests */ if (ast_cc_agent_register(&sip_cc_agent_callbacks)) { unload_module(); return AST_MODULE_LOAD_DECLINE; } } if (ast_cc_monitor_register(&sip_cc_monitor_callbacks)) { unload_module(); return AST_MODULE_LOAD_DECLINE; } if (!(sip_monitor_instances = ao2_container_alloc(37, sip_monitor_instance_hash_fn, sip_monitor_instance_cmp_fn))) { unload_module(); return AST_MODULE_LOAD_DECLINE; } /* And start the monitor for the first time */ restart_monitor(); 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_register_tests(); network_change_stasis_subscribe(); ast_websocket_add_protocol("sip", sip_websocket_callback); return AST_MODULE_LOAD_SUCCESS; } /*! \brief PBX unload module API */ static int unload_module(void) { struct sip_pvt *p; struct sip_threadinfo *th; struct ast_context *con; struct ao2_iterator i; int wait_count; ast_sip_api_provider_unregister(); ast_websocket_remove_protocol("sip", sip_websocket_callback); network_change_stasis_unsubscribe(); acl_change_event_stasis_unsubscribe(); ast_sched_dump(sched); /* First, take us out of the channel type list */ ast_channel_unregister(&sip_tech); ast_msg_tech_unregister(&sip_msg_tech); /* Unregister dial plan functions */ ast_custom_function_unregister(&sippeer_function); ast_custom_function_unregister(&sip_header_function); ast_custom_function_unregister(&checksipdomain_function); /* Unregister dial plan applications */ ast_unregister_application(app_dtmfmode); ast_unregister_application(app_sipaddheader); ast_unregister_application(app_sipremoveheader); #ifdef TEST_FRAMEWORK ast_unregister_application(app_sipsendcustominfo); AST_TEST_UNREGISTER(test_sip_peers_get); AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse); AST_TEST_UNREGISTER(test_tcp_message_fragmentation); AST_TEST_UNREGISTER(get_in_brackets_const_test); #endif /* Unregister all the AstData providers */ ast_data_unregister(NULL); /* Unregister CLI commands */ ast_cli_unregister_multiple(cli_sip, ARRAY_LEN(cli_sip)); /* Disconnect from RTP engine */ ast_rtp_glue_unregister(&sip_rtp_glue); /* 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"); /* Kill TCP/TLS server threads */ if (sip_tcp_desc.master) { ast_tcptls_server_stop(&sip_tcp_desc); } if (sip_tls_desc.master) { ast_tcptls_server_stop(&sip_tls_desc); } ast_ssl_teardown(sip_tls_desc.tls_cfg); /* Kill all existing TCP/TLS threads */ i = ao2_iterator_init(threadt, 0); while ((th = ao2_t_iterator_next(&i, "iterate through tcp threads for 'sip show tcp'"))) { pthread_t thread = th->threadid; th->stop = 1; pthread_kill(thread, SIGURG); ao2_t_ref(th, -1, "decrement ref from iterator"); } ao2_iterator_destroy(&i); /* Hangup all dialogs if they have an owner */ i = ao2_iterator_init(dialogs, 0); while ((p = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { if (p->owner) ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD); ao2_t_ref(p, -1, "toss dialog ptr from iterator_next"); } ao2_iterator_destroy(&i); unlink_all_peers_from_tables(); ast_mutex_lock(&monlock); if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { pthread_t th = monitor_thread; monitor_thread = AST_PTHREADT_STOP; pthread_cancel(th); pthread_kill(th, SIGURG); ast_mutex_unlock(&monlock); pthread_join(th, NULL); } else { monitor_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&monlock); } /* Destroy all the dialogs and free their memory */ i = ao2_iterator_init(dialogs, 0); while ((p = ao2_t_iterator_next(&i, "iterate thru dialogs"))) { dialog_unlink_all(p); ao2_t_ref(p, -1, "throw away iterator result"); } ao2_iterator_destroy(&i); /* Free memory for local network address mask */ ast_free_ha(localaddr); ast_mutex_lock(&authl_lock); if (authl) { ao2_t_cleanup(authl, "Removing global authentication"); authl = NULL; } ast_mutex_unlock(&authl_lock); sip_epa_unregister_all(); destroy_escs(); ast_free(default_tls_cfg.certfile); ast_free(default_tls_cfg.pvtfile); ast_free(default_tls_cfg.cipher); ast_free(default_tls_cfg.cafile); ast_free(default_tls_cfg.capath); cleanup_all_regs(); ao2_cleanup(registry_list); { struct ao2_iterator iter; struct sip_subscription_mwi *iterator; iter = ao2_iterator_init(subscription_mwi_list, 0); while ((iterator = ao2_t_iterator_next(&iter, "unload_module iter"))) { ao2_lock(iterator); if (iterator->dnsmgr) { ast_dnsmgr_release(iterator->dnsmgr); iterator->dnsmgr = NULL; ao2_t_ref(iterator, -1, "dnsmgr release"); } ao2_unlock(iterator); ao2_t_ref(iterator, -1, "unload_module iter"); } ao2_iterator_destroy(&iter); } ao2_cleanup(subscription_mwi_list); /* * 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. */ wait_count = 1000; while (ao2_container_count(threadt) && --wait_count) { sched_yield(); } if (!wait_count) { ast_debug(2, "TCP/TLS thread container did not become empty :(\n"); } ao2_t_cleanup(bogus_peer, "unref the bogus_peer"); ao2_t_cleanup(peers, "unref the peers table"); ao2_t_cleanup(peers_by_ip, "unref the peers_by_ip table"); ao2_t_cleanup(dialogs, "unref the dialogs table"); ao2_t_cleanup(dialogs_needdestroy, "unref dialogs_needdestroy"); ao2_t_cleanup(dialogs_rtpcheck, "unref dialogs_rtpcheck"); ao2_t_cleanup(threadt, "unref the thread table"); ao2_t_cleanup(sip_monitor_instances, "unref the sip_monitor_instances table"); clear_sip_domains(); sip_cfg.contact_acl = ast_free_acl_list(sip_cfg.contact_acl); if (sipsock_read_id) { ast_io_remove(io, sipsock_read_id); sipsock_read_id = NULL; } close(sipsock); io_context_destroy(io); ast_sched_context_destroy(sched); con = ast_context_find(used_context); if (con) { ast_context_destroy(con, "SIP"); } ast_unload_realtime("sipregs"); ast_unload_realtime("sippeers"); ast_cc_monitor_unregister(&sip_cc_monitor_callbacks); ast_cc_agent_unregister(&sip_cc_agent_callbacks); sip_reqresp_parser_exit(); sip_unregister_tests(); if (notify_types) { ast_config_destroy(notify_types); notify_types = NULL; } ao2_cleanup(sip_tech.capabilities); sip_tech.capabilities = NULL; ao2_cleanup(sip_cfg.caps); sip_cfg.caps = NULL; STASIS_MESSAGE_TYPE_CLEANUP(session_timeout_type); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Protocol (SIP)", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, .nonoptreq = "res_crypto,res_http_websocket", ); asterisk-13.1.0/channels/misdn/0000755000000000000000000000000012443600112015015 5ustar rootrootasterisk-13.1.0/channels/misdn/chan_misdn_config.h0000644000000000000000000001404112010207054020614 0ustar rootroot/* * Chan_Misdn -- Channel Driver for Asterisk * * Interface to mISDN * * Copyright (C) 2004, Christian Richter * * Christian Richter * * This program is free software, distributed under the terms of * the GNU General Public License */ /*! \file * \brief Interface to mISDN - Config * \author Christian Richter */ #ifndef CHAN_MISDN_CONFIG_H #define CHAN_MISDN_CONFIG_H #define BUFFERSIZE 512 enum misdn_cfg_elements { /* port config items */ MISDN_CFG_FIRST = 0, MISDN_CFG_GROUPNAME, /* char[] */ MISDN_CFG_ALLOWED_BEARERS, /* char[] */ MISDN_CFG_FAR_ALERTING, /* int (bool) */ MISDN_CFG_RXGAIN, /* int */ MISDN_CFG_TXGAIN, /* int */ MISDN_CFG_TE_CHOOSE_CHANNEL, /* int (bool) */ MISDN_CFG_PMP_L1_CHECK, /* int (bool) */ MISDN_CFG_REJECT_CAUSE, /* int */ MISDN_CFG_ALARM_BLOCK, /* int (bool) */ MISDN_CFG_HDLC, /* int (bool) */ MISDN_CFG_CONTEXT, /* char[] */ MISDN_CFG_LANGUAGE, /* char[] */ MISDN_CFG_MUSICCLASS, /* char[] */ MISDN_CFG_CALLERID, /* char[] */ MISDN_CFG_INCOMING_CALLERID_TAG, /* char[] */ MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, /* int (bool) */ MISDN_CFG_METHOD, /* char[] */ MISDN_CFG_DIALPLAN, /* int */ MISDN_CFG_LOCALDIALPLAN, /* int */ MISDN_CFG_CPNDIALPLAN, /* int */ MISDN_CFG_TON_PREFIX_UNKNOWN, /* char[] */ MISDN_CFG_TON_PREFIX_INTERNATIONAL, /* char[] */ MISDN_CFG_TON_PREFIX_NATIONAL, /* char[] */ MISDN_CFG_TON_PREFIX_NETWORK_SPECIFIC,/* char[] */ MISDN_CFG_TON_PREFIX_SUBSCRIBER, /* char[] */ MISDN_CFG_TON_PREFIX_ABBREVIATED, /* char[] */ MISDN_CFG_PRES, /* int */ MISDN_CFG_SCREEN, /* int */ MISDN_CFG_DISPLAY_CONNECTED, /* int */ MISDN_CFG_DISPLAY_SETUP, /* int */ MISDN_CFG_ALWAYS_IMMEDIATE, /* int (bool) */ MISDN_CFG_NODIALTONE, /* int (bool) */ MISDN_CFG_IMMEDIATE, /* int (bool) */ MISDN_CFG_SENDDTMF, /* int (bool) */ MISDN_CFG_ASTDTMF, /* int (bool) */ MISDN_CFG_HOLD_ALLOWED, /* int (bool) */ MISDN_CFG_EARLY_BCONNECT, /* int (bool) */ MISDN_CFG_INCOMING_EARLY_AUDIO, /* int (bool) */ MISDN_CFG_ECHOCANCEL, /* int */ MISDN_CFG_CC_REQUEST_RETENTION,/* bool */ MISDN_CFG_OUTGOING_COLP, /* int */ #ifdef MISDN_1_2 MISDN_CFG_PIPELINE, /* char[] */ #endif #ifdef WITH_BEROEC MISDN_CFG_BNECHOCANCEL, MISDN_CFG_BNEC_ANTIHOWL, MISDN_CFG_BNEC_NLP, MISDN_CFG_BNEC_ZEROCOEFF, MISDN_CFG_BNEC_TD, MISDN_CFG_BNEC_ADAPT, #endif MISDN_CFG_NEED_MORE_INFOS, /* bool */ MISDN_CFG_NOAUTORESPOND_ON_SETUP, /* bool */ MISDN_CFG_NTTIMEOUT, /* bool */ MISDN_CFG_BRIDGING, /* bool */ MISDN_CFG_JITTERBUFFER, /* int */ MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, /* int */ MISDN_CFG_CALLGROUP, /* ast_group_t */ MISDN_CFG_PICKUPGROUP, /* ast_group_t */ MISDN_CFG_NAMEDCALLGROUP, /* ast_namedgroups * */ MISDN_CFG_NAMEDPICKUPGROUP, /* ast_namedgroups * */ MISDN_CFG_MAX_IN, /* int */ MISDN_CFG_MAX_OUT, /* int */ MISDN_CFG_L1_TIMEOUT, /* int */ MISDN_CFG_OVERLAP_DIAL, /* int (bool)*/ MISDN_CFG_MSNS, /* char[] */ MISDN_CFG_FAXDETECT, /* char[] */ MISDN_CFG_FAXDETECT_CONTEXT, /* char[] */ MISDN_CFG_FAXDETECT_TIMEOUT, /* int */ MISDN_CFG_PTP, /* int (bool) */ MISDN_CFG_LAST, /* general config items */ MISDN_GEN_FIRST, #ifndef MISDN_1_2 MISDN_GEN_MISDN_INIT, /* char[] */ #endif MISDN_GEN_DEBUG, /* int */ MISDN_GEN_TRACEFILE, /* char[] */ MISDN_GEN_BRIDGING, /* int (bool) */ MISDN_GEN_STOP_TONE, /* int (bool) */ MISDN_GEN_APPEND_DIGITS2EXTEN, /* int (bool) */ MISDN_GEN_DYNAMIC_CRYPT, /* int (bool) */ MISDN_GEN_CRYPT_PREFIX, /* char[] */ MISDN_GEN_CRYPT_KEYS, /* char[] */ MISDN_GEN_NTKEEPCALLS, /* int (bool) */ MISDN_GEN_NTDEBUGFLAGS, /* int */ MISDN_GEN_NTDEBUGFILE, /* char[] */ MISDN_GEN_LAST }; enum misdn_cfg_method { METHOD_STANDARD = 0, METHOD_ROUND_ROBIN, METHOD_STANDARD_DEC }; /* you must call misdn_cfg_init before any other function of this header file */ int misdn_cfg_init(int max_ports, int reload); void misdn_cfg_reload(void); void misdn_cfg_destroy(void); void misdn_cfg_update_ptp( void ); /* if you requst a general config element, the port value is ignored. if the requested * value is not available, or the buffer is too small, the buffer will be nulled (in * case of a char* only its first byte will be nulled). */ void misdn_cfg_get(int port, enum misdn_cfg_elements elem, void* buf, int bufsize); /* returns the enum element for the given name, returns MISDN_CFG_FIRST if none was found */ enum misdn_cfg_elements misdn_cfg_get_elem (const char *name); /* fills the buffer with the name of the given config element */ void misdn_cfg_get_name (enum misdn_cfg_elements elem, void *buf, int bufsize); /* fills the buffer with the description of the given config element */ void misdn_cfg_get_desc (enum misdn_cfg_elements elem, void *buf, int bufsize, void *buf_default, int bufsize_default); /* fills the buffer with a ',' separated list of all active ports */ void misdn_cfg_get_ports_string(char *ports); /* fills the buffer with a nice printable string representation of the config element */ void misdn_cfg_get_config_string(int port, enum misdn_cfg_elements elem, char* buf, int bufsize); /* returns the next available port number. returns -1 if the last one was reached. */ int misdn_cfg_get_next_port(int port); int misdn_cfg_get_next_port_spin(int port); int misdn_cfg_is_msn_valid(int port, char* msn); int misdn_cfg_is_port_valid(int port); int misdn_cfg_is_group_method(char *group, enum misdn_cfg_method meth); #if 0 char *misdn_cfg_get_next_group(char *group); int misdn_cfg_get_next_port_in_group(int port, char *group); #endif struct ast_jb_conf *misdn_get_global_jbconf(void); #endif asterisk-13.1.0/channels/misdn/isdn_lib.c0000644000000000000000000033117512056223471016767 0ustar rootroot/* * Chan_Misdn -- Channel Driver for Asterisk * * Interface to mISDN * * Copyright (C) 2004, Christian Richter * * Christian Richter * * This program is free software, distributed under the terms of * the GNU General Public License */ /*! \file * \brief Interface to mISDN * \author Christian Richter */ /*** MODULEINFO extended ***/ #include #include #include #include "isdn_lib_intern.h" #include "isdn_lib.h" enum event_response_e (*cb_event)(enum event_e event, struct misdn_bchannel *bc, void *user_data); void (*cb_log)(int level, int port, char *tmpl, ...) __attribute__ ((format (printf, 3, 4))); int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len); /* * Define ARRAY_LEN() because I cannot * #include "asterisk/utils.h" */ #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #include "asterisk/causes.h" void misdn_join_conf(struct misdn_bchannel *bc, int conf_id); void misdn_split_conf(struct misdn_bchannel *bc, int conf_id); int misdn_lib_get_l2_up(struct misdn_stack *stack); struct misdn_stack *get_misdn_stack(void); int misdn_lib_port_is_pri(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) { return stack->pri; } } return -1; } int misdn_lib_port_is_nt(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) { return stack->nt; } } return -1; } void misdn_make_dummy(struct misdn_bchannel *dummybc, int port, int l3id, int nt, int channel) { memset (dummybc,0,sizeof(struct misdn_bchannel)); dummybc->port=port; if (l3id==0) dummybc->l3_id = MISDN_ID_DUMMY; else dummybc->l3_id=l3id; dummybc->nt=nt; dummybc->dummy=1; dummybc->channel=channel; } int misdn_lib_port_block(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) { stack->blocked=1; return 0; } } return -1; } int misdn_lib_port_unblock(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) { stack->blocked=0; return 0; } } return -1; } int misdn_lib_is_port_blocked(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) { return stack->blocked; } } return -1; } int misdn_lib_is_ptp(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) return stack->ptp; } return -1; } int misdn_lib_get_maxchans(int port) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { if (stack->port == port) { if (stack->pri) return 30; else return 2; } } return -1; } struct misdn_stack *get_stack_by_bc(struct misdn_bchannel *bc) { struct misdn_stack *stack = get_misdn_stack(); if (!bc) return NULL; for ( ; stack; stack = stack->next) { if (bc->port == stack->port) return stack; } return NULL; } void get_show_stack_details(int port, char *buf) { struct misdn_stack *stack = get_misdn_stack(); for (; stack; stack = stack->next) { if (stack->port == port) { break; } } if (stack) { sprintf(buf, "* Port %2d Type %s Prot. %s L2Link %s L1Link:%s Blocked:%d", stack->port, stack->nt ? "NT" : "TE", stack->ptp ? "PTP" : "PMP", (stack->nt && !stack->ptp) ? "UNKN" : stack->l2link ? "UP " : "DOWN", stack->l1link ? "UP " : "DOWN", stack->blocked); } else { buf[0] = 0; } } static int nt_err_cnt =0 ; enum global_states { MISDN_INITIALIZING, MISDN_INITIALIZED } ; static enum global_states global_state=MISDN_INITIALIZING; #include #include #include #include #include #include #include "isdn_lib.h" struct misdn_lib { /*! \brief mISDN device handle returned by mISDN_open() */ int midev; pthread_t event_thread; pthread_t event_handler_thread; void *user_data; msg_queue_t activatequeue; sem_t new_msg; struct misdn_stack *stack_list; } ; #ifndef ECHOCAN_ON #define ECHOCAN_ON 123 #define ECHOCAN_OFF 124 #endif #define MISDN_DEBUG 0 void misdn_tx_jitter(struct misdn_bchannel *bc, int len); struct misdn_bchannel *find_bc_by_l3id(struct misdn_stack *stack, unsigned long l3id); int manager_isdn_handler(iframe_t *frm ,msg_t *msg); int misdn_lib_port_restart(int port); int misdn_lib_pid_restart(int pid); extern struct isdn_msg msgs_g[]; #define ISDN_PID_L3_B_USER 0x430000ff #define ISDN_PID_L4_B_USER 0x440000ff /* #define MISDN_IBUF_SIZE 1024 */ #define MISDN_IBUF_SIZE 512 /* Fine Tuning of Inband Signalling time */ #define TONE_ALERT_CNT 41 /* 1 Sec */ #define TONE_ALERT_SILENCE_CNT 200 /* 4 Sec */ #define TONE_BUSY_CNT 20 /* ? */ #define TONE_BUSY_SILENCE_CNT 48 /* ? */ static int entity; static struct misdn_lib *glob_mgr; static char tone_425_flip[TONE_425_SIZE]; static char tone_silence_flip[TONE_SILENCE_SIZE]; static void misdn_lib_isdn_event_catcher(void *arg); static int handle_event_nt(void *dat, void *arg); void stack_holder_add(struct misdn_stack *stack, struct misdn_bchannel *holder); void stack_holder_remove(struct misdn_stack *stack, struct misdn_bchannel *holder); struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id); /* from isdn_lib.h */ /* user iface */ void te_lib_destroy(int midev) ; struct misdn_bchannel *manager_find_bc_by_pid(int pid); void manager_ph_control_block(struct misdn_bchannel *bc, int c1, void *c2, int c2_len); void manager_clean_bc(struct misdn_bchannel *bc ); void manager_bchannel_setup (struct misdn_bchannel *bc); void manager_bchannel_cleanup (struct misdn_bchannel *bc); void ec_chunk( struct misdn_bchannel *bc, unsigned char *rxchunk, unsigned char *txchunk, int chunk_size); /* end */ int bchdev_echocancel_activate(struct misdn_bchannel* dev); void bchdev_echocancel_deactivate(struct misdn_bchannel* dev); /* end */ static char *bearer2str(int cap) { static char *bearers[]={ "Speech", "Audio 3.1k", "Unres Digital", "Res Digital", "Unknown Bearer" }; switch (cap) { case INFO_CAPABILITY_SPEECH: return bearers[0]; break; case INFO_CAPABILITY_AUDIO_3_1K: return bearers[1]; break; case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: return bearers[2]; break; case INFO_CAPABILITY_DIGITAL_RESTRICTED: return bearers[3]; break; default: return bearers[4]; break; } } static char flip_table[256]; static void init_flip_bits(void) { int i,k; for (i = 0 ; i < 256 ; i++) { unsigned char sample = 0 ; for (k = 0; k<8; k++) { if ( i & 1 << k ) sample |= 0x80 >> k; } flip_table[i] = sample; } } static char * flip_buf_bits ( char * buf , int len) { int i; char * start = buf; for (i = 0 ; i < len; i++) { buf[i] = flip_table[(unsigned char)buf[i]]; } return start; } static msg_t *create_l2msg(int prim, int dinfo, int size) /* NT only */ { int i = 0; msg_t *dmsg; while(i < 10) { dmsg = prep_l3data_msg(prim, dinfo, size, 256, NULL); if (dmsg) return(dmsg); if (!i) printf("cannot allocate memory, trying again...\n"); i++; usleep(300000); } printf("cannot allocate memory, system overloaded.\n"); exit(-1); } msg_t *create_l3msg(int prim, int mt, int dinfo, int size, int ntmode) { int i = 0; msg_t *dmsg; Q931_info_t *qi; iframe_t *frm; if (!ntmode) size = sizeof(Q931_info_t)+2; while(i < 10) { if (ntmode) { dmsg = prep_l3data_msg(prim, dinfo, size, 256, NULL); if (dmsg) { return(dmsg); } } else { dmsg = alloc_msg(size+256+mISDN_HEADER_LEN+DEFAULT_HEADROOM); if (dmsg) { memset(msg_put(dmsg,size+mISDN_HEADER_LEN), 0, size+mISDN_HEADER_LEN); frm = (iframe_t *)dmsg->data; frm->prim = prim; frm->dinfo = dinfo; qi = (Q931_info_t *)(dmsg->data + mISDN_HEADER_LEN); qi->type = mt; return(dmsg); } } if (!i) printf("cannot allocate memory, trying again...\n"); i++; usleep(300000); } printf("cannot allocate memory, system overloaded.\n"); exit(-1); } static int send_msg (int midev, struct misdn_bchannel *bc, msg_t *dmsg) { iframe_t *frm = (iframe_t *)dmsg->data; struct misdn_stack *stack=get_stack_by_bc(bc); if (!stack) { cb_log(0,bc->port,"send_msg: IEK!! no stack\n "); return -1; } frm->addr = (stack->upper_id | FLG_MSG_DOWN); frm->dinfo = bc->l3_id; frm->len = (dmsg->len) - mISDN_HEADER_LEN; cb_log(4,stack->port,"Sending msg, prim:%x addr:%x dinfo:%x\n",frm->prim,frm->addr,frm->dinfo); mISDN_write(midev, dmsg->data, dmsg->len, TIMEOUT_1SEC); free_msg(dmsg); return 0; } static int mypid=1; int misdn_cap_is_speech(int cap) /** Poor mans version **/ { if ( (cap != INFO_CAPABILITY_DIGITAL_UNRESTRICTED) && (cap != INFO_CAPABILITY_DIGITAL_RESTRICTED) ) return 1; return 0; } int misdn_inband_avail(struct misdn_bchannel *bc) { if (!bc->early_bconnect) { /* We have opted to never receive any available inband recorded messages */ return 0; } switch (bc->progress_indicator) { case INFO_PI_INBAND_AVAILABLE: case INFO_PI_CALL_NOT_E2E_ISDN: case INFO_PI_CALLED_NOT_ISDN: return 1; default: return 0; } return 0; } static void dump_chan_list(struct misdn_stack *stack) { int i; for (i = 0; i <= stack->b_num; ++i) { cb_log(6, stack->port, "Idx:%d stack->cchan:%d in_use:%d Chan:%d\n", i, stack->channels[i], stack->bc[i].in_use, i + 1); } #if defined(AST_MISDN_ENHANCEMENTS) for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) { if (stack->bc[i].in_use) { cb_log(6, stack->port, "Idx:%d stack->cchan:%d REGISTER Chan:%d in_use\n", i, stack->channels[i], i + 1); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } void misdn_dump_chanlist(void) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { dump_chan_list(stack); } } static int set_chan_in_stack(struct misdn_stack *stack, int channel) { cb_log(4,stack->port,"set_chan_in_stack: %d\n",channel); dump_chan_list(stack); if (1 <= channel && channel <= ARRAY_LEN(stack->channels)) { if (!stack->channels[channel-1]) stack->channels[channel-1] = 1; else { cb_log(4,stack->port,"channel already in use:%d\n", channel ); return -1; } } else { cb_log(0,stack->port,"couldn't set channel %d in\n", channel ); return -1; } return 0; } static int find_free_chan_in_stack(struct misdn_stack *stack, struct misdn_bchannel *bc, int channel, int dec) { int i; int chan = 0; int bnums; if (bc->channel_found) { return 0; } bc->channel_found = 1; #if defined(AST_MISDN_ENHANCEMENTS) if (bc->is_register_pool) { pthread_mutex_lock(&stack->st_lock); for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->channels); ++i) { if (!stack->channels[i]) { chan = i + 1; cb_log(3, stack->port, " --> found REGISTER chan: %d\n", chan); break; } } } else #endif /* defined(AST_MISDN_ENHANCEMENTS) */ { cb_log(5, stack->port, "find_free_chan: req_chan:%d\n", channel); if (channel < 0 || channel > MAX_BCHANS) { cb_log(0, stack->port, " !! out of bound call to find_free_chan_in_stack! (ch:%d)\n", channel); return 0; } --channel; pthread_mutex_lock(&stack->st_lock); bnums = stack->pri ? stack->b_num : stack->b_num - 1; if (dec) { for (i = bnums; i >= 0; --i) { if (i != 15 && (channel < 0 || i == channel)) { /* skip E1 D channel ;) and work with chan preselection */ if (!stack->channels[i]) { chan = i + 1; cb_log(3, stack->port, " --> found chan%s: %d\n", channel >= 0 ? " (preselected)" : "", chan); break; } } } } else { for (i = 0; i <= bnums; ++i) { if (i != 15 && (channel < 0 || i == channel)) { /* skip E1 D channel ;) and work with chan preselection */ if (!stack->channels[i]) { chan = i + 1; cb_log(3, stack->port, " --> found chan%s: %d\n", channel >= 0 ? " (preselected)" : "", chan); break; } } } } } if (!chan) { pthread_mutex_unlock(&stack->st_lock); cb_log(1, stack->port, " !! NO FREE CHAN IN STACK\n"); dump_chan_list(stack); bc->out_cause = AST_CAUSE_NORMAL_CIRCUIT_CONGESTION; return -1; } if (set_chan_in_stack(stack, chan) < 0) { pthread_mutex_unlock(&stack->st_lock); cb_log(0, stack->port, "Channel Already in use:%d\n", chan); bc->out_cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL; return -1; } pthread_mutex_unlock(&stack->st_lock); bc->channel = chan; return 0; } /*! * \internal * \brief Release a B channel to the allocation pool. * * \param stack Which port stack B channel belongs. * \param channel B channel to release. (Range 1-MAX_BCHANS representing B1-Bn) * * \return Nothing * * \note * Must be called after clean_up_bc() to make sure that the media stream is * no longer connected. */ static void empty_chan_in_stack(struct misdn_stack *stack, int channel) { if (channel < 1 || ARRAY_LEN(stack->channels) < channel) { cb_log(0, stack->port, "empty_chan_in_stack: cannot empty channel %d\n", channel); return; } cb_log(4, stack->port, "empty_chan_in_stack: %d\n", channel); stack->channels[channel - 1] = 0; dump_chan_list(stack); } char *bc_state2str(enum bchannel_state state) { int i; struct bchan_state_s { char *n; enum bchannel_state s; } states[] = { {"BCHAN_CLEANED", BCHAN_CLEANED }, {"BCHAN_EMPTY", BCHAN_EMPTY}, {"BCHAN_ACTIVATED", BCHAN_ACTIVATED}, {"BCHAN_BRIDGED", BCHAN_BRIDGED}, {"BCHAN_RELEASE", BCHAN_RELEASE}, {"BCHAN_ERROR", BCHAN_ERROR} }; for (i=0; i< sizeof(states)/sizeof(struct bchan_state_s); i++) if ( states[i].s == state) return states[i].n; return "UNKNOWN"; } void bc_state_change(struct misdn_bchannel *bc, enum bchannel_state state) { cb_log(5,bc->port,"BC_STATE_CHANGE: l3id:%x from:%s to:%s\n", bc->l3_id, bc_state2str(bc->bc_state), bc_state2str(state) ); switch (state) { case BCHAN_ACTIVATED: if (bc->next_bc_state == BCHAN_BRIDGED) { misdn_join_conf(bc, bc->conf_id); bc->next_bc_state = BCHAN_EMPTY; return; } default: bc->bc_state=state; break; } } static void bc_next_state_change(struct misdn_bchannel *bc, enum bchannel_state state) { cb_log(5,bc->port,"BC_NEXT_STATE_CHANGE: from:%s to:%s\n", bc_state2str(bc->next_bc_state), bc_state2str(state) ); bc->next_bc_state=state; } /*! * \internal * \brief Empty the B channel record of most call data. * * \param bc B channel record to empty of most call data. * * \return Nothing * * \note * Sets the last_used time and must be called before clearing bc->in_use. */ static void empty_bc(struct misdn_bchannel *bc) { bc->caller.presentation = 0; /* allowed */ bc->caller.number_plan = NUMPLAN_ISDN; bc->caller.number_type = NUMTYPE_UNKNOWN; bc->caller.name[0] = 0; bc->caller.number[0] = 0; bc->caller.subaddress[0] = 0; bc->connected.presentation = 0; /* allowed */ bc->connected.number_plan = NUMPLAN_ISDN; bc->connected.number_type = NUMTYPE_UNKNOWN; bc->connected.name[0] = 0; bc->connected.number[0] = 0; bc->connected.subaddress[0] = 0; bc->redirecting.from.presentation = 0; /* allowed */ bc->redirecting.from.number_plan = NUMPLAN_ISDN; bc->redirecting.from.number_type = NUMTYPE_UNKNOWN; bc->redirecting.from.name[0] = 0; bc->redirecting.from.number[0] = 0; bc->redirecting.from.subaddress[0] = 0; bc->redirecting.to.presentation = 0; /* allowed */ bc->redirecting.to.number_plan = NUMPLAN_ISDN; bc->redirecting.to.number_type = NUMTYPE_UNKNOWN; bc->redirecting.to.name[0] = 0; bc->redirecting.to.number[0] = 0; bc->redirecting.to.subaddress[0] = 0; bc->redirecting.reason = mISDN_REDIRECTING_REASON_UNKNOWN; bc->redirecting.count = 0; bc->redirecting.to_changed = 0; bc->dummy=0; bc->bframe_len=0; bc->cw= 0; bc->dec=0; bc->channel = 0; bc->sending_complete = 0; bc->restart_channel=0; bc->conf_id = 0; bc->need_more_infos = 0; bc->send_dtmf=0; bc->nodsp=0; bc->nojitter=0; bc->time_usec=0; bc->rxgain=0; bc->txgain=0; bc->crypt=0; bc->curptx=0; bc->curprx=0; bc->crypt_key[0] = 0; bc->generate_tone=0; bc->tone_cnt=0; bc->active = 0; bc->early_bconnect = 1; #ifdef MISDN_1_2 *bc->pipeline = 0; #else bc->ec_enable = 0; bc->ec_deftaps = 128; #endif bc->AOCD_need_export = 0; bc->orig=0; bc->cause = AST_CAUSE_NORMAL_CLEARING; bc->out_cause = AST_CAUSE_NORMAL_CLEARING; bc->display_connected = 0; /* none */ bc->display_setup = 0; /* none */ bc->outgoing_colp = 0;/* pass */ bc->presentation = 0; /* allowed */ bc->set_presentation = 0; bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; bc->progress_coding=0; bc->progress_location=0; bc->progress_indicator=0; #if defined(AST_MISDN_ENHANCEMENTS) bc->div_leg_3_rx_wanted = 0; bc->div_leg_3_tx_pending = 0; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /** Set Default Bearer Caps **/ bc->capability=INFO_CAPABILITY_SPEECH; bc->law=INFO_CODEC_ALAW; bc->mode=0; bc->rate=0x10; bc->user1=0; bc->urate=0; bc->hdlc=0; bc->dialed.number_plan = NUMPLAN_ISDN; bc->dialed.number_type = NUMTYPE_UNKNOWN; bc->dialed.number[0] = 0; bc->dialed.subaddress[0] = 0; bc->info_dad[0] = 0; bc->display[0] = 0; bc->infos_pending[0] = 0; bc->uu[0]=0; bc->uulen=0; bc->fac_in.Function = Fac_None; bc->fac_out.Function = Fac_None; bc->te_choose_channel = 0; bc->channel_found= 0; gettimeofday(&bc->last_used, NULL); } static int clean_up_bc(struct misdn_bchannel *bc) { int ret=0; unsigned char buff[32]; struct misdn_stack * stack; cb_log(3, bc->port, "$$$ CLEANUP CALLED pid:%d\n", bc->pid); stack=get_stack_by_bc(bc); if (!stack) return -1; switch (bc->bc_state ) { case BCHAN_CLEANED: cb_log(5, stack->port, "$$$ Already cleaned up bc with stid :%x\n", bc->b_stid); return -1; default: break; } cb_log(2, stack->port, "$$$ Cleaning up bc with stid :%x pid:%d\n", bc->b_stid, bc->pid); manager_ec_disable(bc); manager_bchannel_deactivate(bc); mISDN_write_frame(stack->midev, buff, bc->layer_id|FLG_MSG_TARGET|FLG_MSG_DOWN, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); bc->b_stid = 0; bc_state_change(bc, BCHAN_CLEANED); return ret; } static void clear_l3(struct misdn_stack *stack) { int i; if (global_state == MISDN_INITIALIZED) { for (i = 0; i <= stack->b_num; ++i) { cb_event(EVENT_CLEANUP, &stack->bc[i], NULL); empty_bc(&stack->bc[i]); clean_up_bc(&stack->bc[i]); empty_chan_in_stack(stack, i + 1); stack->bc[i].in_use = 0; } #if defined(AST_MISDN_ENHANCEMENTS) for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) { empty_bc(&stack->bc[i]); empty_chan_in_stack(stack, i + 1); stack->bc[i].in_use = 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } } static int new_te_id = 0; static int misdn_lib_get_l1_down(struct misdn_stack *stack) { /* Pull Up L1 */ iframe_t act; act.prim = PH_DEACTIVATE | REQUEST; act.addr = stack->upper_id | FLG_MSG_DOWN; act.dinfo = 0; act.len = 0; cb_log(1, stack->port, "SENDING PH_DEACTIVATE | REQ\n"); return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC); } static int misdn_lib_get_l2_down(struct misdn_stack *stack) { if (stack->ptp && stack->nt) { msg_t *dmsg; /* L2 */ dmsg = create_l2msg(DL_RELEASE| REQUEST, 0, 0); pthread_mutex_lock(&stack->nstlock); if (stack->nst.manager_l3(&stack->nst, dmsg)) free_msg(dmsg); pthread_mutex_unlock(&stack->nstlock); } else if (!stack->nt) { iframe_t act; act.prim = DL_RELEASE| REQUEST; act.addr = (stack->upper_id |FLG_MSG_DOWN) ; act.dinfo = 0; act.len = 0; return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC); } /* cannot deestablish L2 for NT PTMP to unknown TE TEIs */ return 0; } static int misdn_lib_get_l1_up(struct misdn_stack *stack) { /* Pull Up L1 */ iframe_t act; act.prim = PH_ACTIVATE | REQUEST; act.addr = stack->upper_id | FLG_MSG_DOWN; act.dinfo = 0; act.len = 0; cb_log(1, stack->port, "SENDING PH_ACTIVATE | REQ\n"); return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC); } int misdn_lib_get_l2_up(struct misdn_stack *stack) { if (stack->ptp && stack->nt) { msg_t *dmsg; /* L2 */ dmsg = create_l2msg(DL_ESTABLISH | REQUEST, 0, 0); pthread_mutex_lock(&stack->nstlock); if (stack->nst.manager_l3(&stack->nst, dmsg)) free_msg(dmsg); pthread_mutex_unlock(&stack->nstlock); } else if (!stack->nt) { iframe_t act; act.prim = DL_ESTABLISH | REQUEST; act.addr = (stack->upper_id |FLG_MSG_DOWN) ; act.dinfo = 0; act.len = 0; return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC); } /* cannot establish L2 for NT PTMP to unknown TE TEIs */ return 0; } static int misdn_lib_get_short_status(struct misdn_stack *stack) { iframe_t act; act.prim = MGR_SHORTSTATUS | REQUEST; act.addr = (stack->upper_id | MSG_BROADCAST) ; act.dinfo = SSTATUS_BROADCAST_BIT | SSTATUS_ALL; act.len = 0; return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC); } static int create_process(int midev, struct misdn_bchannel *bc) { iframe_t ncr; int l3_id; int proc_id; struct misdn_stack *stack; stack = get_stack_by_bc(bc); if (stack->nt) { if (find_free_chan_in_stack(stack, bc, bc->channel_preselected ? bc->channel : 0, 0) < 0) { return -1; } cb_log(4, stack->port, " --> found channel: %d\n", bc->channel); for (proc_id = 0; proc_id < MAXPROCS; ++proc_id) { if (stack->procids[proc_id] == 0) { break; } } if (proc_id == MAXPROCS) { cb_log(0, stack->port, "Couldn't Create New ProcId.\n"); return -1; } stack->procids[proc_id] = 1; l3_id = 0xff00 | proc_id; bc->l3_id = l3_id; cb_log(3, stack->port, " --> new_l3id %x\n", l3_id); } else { if ((stack->pri && stack->ptp) || bc->te_choose_channel) { /* we know exactly which channels are in use */ if (find_free_chan_in_stack(stack, bc, bc->channel_preselected ? bc->channel : 0, bc->dec) < 0) { return -1; } cb_log(2, stack->port, " --> found channel: %d\n", bc->channel); } else { /* other phones could have made a call also on this port (ptmp) */ bc->channel = 0xff; } /* if we are in te-mode, we need to create a process first */ if (++new_te_id > 0xffff) { new_te_id = 0x0001; } l3_id = (entity << 16) | new_te_id; bc->l3_id = l3_id; cb_log(3, stack->port, "--> new_l3id %x\n", l3_id); /* send message */ ncr.prim = CC_NEW_CR | REQUEST; ncr.addr = (stack->upper_id | FLG_MSG_DOWN); ncr.dinfo = l3_id; ncr.len = 0; mISDN_write(midev, &ncr, mISDN_HEADER_LEN + ncr.len, TIMEOUT_1SEC); } return l3_id; } static int setup_bc(struct misdn_bchannel *bc) { unsigned char buff[1025]; int midev; int channel; int b_stid; int i; mISDN_pid_t pid; int ret; struct misdn_stack *stack=get_stack_by_bc(bc); if (!stack) { cb_log(0, bc->port, "setup_bc: NO STACK FOUND!!\n"); return -1; } midev = stack->midev; channel = bc->channel - 1 - (bc->channel > 16); b_stid = stack->b_stids[channel >= 0 ? channel : 0]; switch (bc->bc_state) { case BCHAN_CLEANED: break; default: cb_log(4, stack->port, "$$$ bc already setup stid :%x (state:%s)\n", b_stid, bc_state2str(bc->bc_state) ); return -1; } cb_log(5, stack->port, "$$$ Setting up bc with stid :%x\n", b_stid); /*check if the b_stid is already initialized*/ for (i=0; i <= stack->b_num; i++) { if (stack->bc[i].b_stid == b_stid) { cb_log(0, bc->port, "setup_bc: b_stid:%x already in use !!!\n", b_stid); return -1; } } if (b_stid <= 0) { cb_log(0, stack->port," -- Stid <=0 at the moment in channel:%d\n",channel); bc_state_change(bc,BCHAN_ERROR); return 1; } bc->b_stid = b_stid; { layer_info_t li; memset(&li, 0, sizeof(li)); li.object_id = -1; li.extentions = 0; li.st = bc->b_stid; /* given idx */ #define MISDN_DSP #ifndef MISDN_DSP bc->nodsp=1; #endif if ( bc->hdlc || bc->nodsp) { cb_log(4, stack->port,"setup_bc: without dsp\n"); { int l = sizeof(li.name); strncpy(li.name, "B L3", l); li.name[l-1] = 0; } li.pid.layermask = ISDN_LAYER((3)); li.pid.protocol[3] = ISDN_PID_L3_B_USER; bc->layer=3; } else { cb_log(4, stack->port,"setup_bc: with dsp\n"); { int l = sizeof(li.name); strncpy(li.name, "B L4", l); li.name[l-1] = 0; } li.pid.layermask = ISDN_LAYER((4)); li.pid.protocol[4] = ISDN_PID_L4_B_USER; bc->layer=4; } ret = mISDN_new_layer(midev, &li); if (ret ) { cb_log(0, stack->port,"New Layer Err: %d %s\n",ret,strerror(errno)); bc_state_change(bc,BCHAN_ERROR); return(-EINVAL); } bc->layer_id = li.id; } memset(&pid, 0, sizeof(pid)); cb_log(4, stack->port," --> Channel is %d\n", bc->channel); if (bc->nodsp && !bc->hdlc) { cb_log(2, stack->port," --> TRANSPARENT Mode (no DSP, no HDLC)\n"); pid.protocol[1] = ISDN_PID_L1_B_64TRANS; pid.protocol[2] = ISDN_PID_L2_B_TRANS; pid.protocol[3] = ISDN_PID_L3_B_USER; pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)); } else if ( bc->hdlc ) { cb_log(2, stack->port," --> HDLC Mode\n"); pid.protocol[1] = ISDN_PID_L1_B_64HDLC ; pid.protocol[2] = ISDN_PID_L2_B_TRANS ; pid.protocol[3] = ISDN_PID_L3_B_USER; pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)) ; } else { cb_log(2, stack->port," --> TRANSPARENT Mode\n"); pid.protocol[1] = ISDN_PID_L1_B_64TRANS; pid.protocol[2] = ISDN_PID_L2_B_TRANS; pid.protocol[3] = ISDN_PID_L3_B_DSP; pid.protocol[4] = ISDN_PID_L4_B_USER; pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)) | ISDN_LAYER((4)); } ret = mISDN_set_stack(midev, bc->b_stid, &pid); if (ret){ cb_log(0, stack->port,"$$$ Set Stack Err: %d %s\n",ret,strerror(errno)); mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); bc_state_change(bc,BCHAN_ERROR); cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data); return(-EINVAL); } ret = mISDN_get_setstack_ind(midev, bc->layer_id); if (ret) { cb_log(0, stack->port,"$$$ Set StackIND Err: %d %s\n",ret,strerror(errno)); mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); bc_state_change(bc,BCHAN_ERROR); cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data); return(-EINVAL); } ret = mISDN_get_layerid(midev, bc->b_stid, bc->layer) ; bc->addr = ret>0? ret : 0; if (!bc->addr) { cb_log(0, stack->port,"$$$ Get Layerid Err: %d %s\n",ret,strerror(errno)); mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); bc_state_change(bc,BCHAN_ERROR); cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data); return (-EINVAL); } manager_bchannel_activate(bc); bc_state_change(bc,BCHAN_ACTIVATED); return 0; } /** IFACE **/ static int init_bc(struct misdn_stack *stack, struct misdn_bchannel *bc, int midev, int port, int bidx) { if (!bc) { return -1; } cb_log(8, port, "Init.BC %d.\n",bidx); bc->send_lock = malloc(sizeof(struct send_lock)); /* XXX BUG! memory leak never freed */ if (!bc->send_lock) { return -1; } pthread_mutex_init(&bc->send_lock->lock, NULL); empty_bc(bc); bc->port=stack->port; bc_state_change(bc, BCHAN_CLEANED); bc->nt=stack->nt?1:0; bc->pri=stack->pri; { ibuffer_t* ibuf= init_ibuffer(MISDN_IBUF_SIZE); if (!ibuf) return -1; clear_ibuffer( ibuf); ibuf->rsem=malloc(sizeof(sem_t)); if (!ibuf->rsem) { return -1; } bc->astbuf=ibuf; if (sem_init(ibuf->rsem,1,0)<0) sem_init(ibuf->rsem,0,0); } #if 0 /* This code does not seem to do anything useful */ if (bidx <= stack->b_num) { unsigned char buff[1025]; iframe_t *frm = (iframe_t *) buff; stack_info_t *stinf; int ret; ret = mISDN_get_stack_info(midev, stack->port, buff, sizeof(buff)); if (ret < 0) { cb_log(0, port, "%s: Cannot get stack info for this port. (ret=%d)\n", __FUNCTION__, ret); return -1; } stinf = (stack_info_t *)&frm->data.p; cb_log(8, port, " --> Child %x\n",stinf->child[bidx]); } #endif return 0; } static struct misdn_stack *stack_init(int midev, int port, int ptp) { int ret; unsigned char buff[1025]; iframe_t *frm = (iframe_t *)buff; stack_info_t *stinf; struct misdn_stack *stack; int i; layer_info_t li; stack = calloc(1, sizeof(struct misdn_stack)); if (!stack) { return NULL; } cb_log(8, port, "Init. Stack.\n"); stack->port=port; stack->midev=midev; stack->ptp=ptp; stack->holding=NULL; stack->pri=0; msg_queue_init(&stack->downqueue); pthread_mutex_init(&stack->st_lock, NULL); /* query port's requirements */ ret = mISDN_get_stack_info(midev, port, buff, sizeof(buff)); if (ret < 0) { cb_log(0, port, "%s: Cannot get stack info for this port. (ret=%d)\n", __FUNCTION__, ret); free(stack); return(NULL); } stinf = (stack_info_t *)&frm->data.p; stack->d_stid = stinf->id; stack->b_num = stinf->childcnt; for (i=0; i<=stinf->childcnt; i++) stack->b_stids[i] = stinf->child[i]; switch(stinf->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK) { case ISDN_PID_L0_TE_S0: cb_log(8, port, "TE Stack\n"); stack->nt=0; break; case ISDN_PID_L0_NT_S0: cb_log(8, port, "NT Stack\n"); stack->nt=1; break; case ISDN_PID_L0_TE_E1: cb_log(8, port, "TE S2M Stack\n"); stack->nt=0; stack->pri=1; break; case ISDN_PID_L0_NT_E1: cb_log(8, port, "NT S2M Stack\n"); stack->nt=1; stack->pri=1; break; default: cb_log(0, port, "this is a unknown port type 0x%08x\n", stinf->pid.protocol[0]); } if (!stack->nt) { if (stinf->pid.protocol[2] & ISDN_PID_L2_DF_PTP ) { stack->ptp = 1; } else { stack->ptp = 0; } } { int ret; int nt=stack->nt; memset(&li, 0, sizeof(li)); { int l = sizeof(li.name); strncpy(li.name,nt?"net l2":"user l4", l); li.name[l-1] = 0; } li.object_id = -1; li.extentions = 0; li.pid.protocol[nt?2:4] = nt?ISDN_PID_L2_LAPD_NET:ISDN_PID_L4_CAPI20; li.pid.layermask = ISDN_LAYER((nt?2:4)); li.st = stack->d_stid; ret = mISDN_new_layer(midev, &li); if (ret) { cb_log(0, port, "%s: Cannot add layer %d to this port.\n", __FUNCTION__, nt?2:4); free(stack); return(NULL); } ret = mISDN_register_layer(midev, stack->d_stid, li.id); if (ret) { cb_log(0,port,"Cannot register layer %d of this port.\n", nt?2:4); free(stack); return(NULL); } stack->lower_id = mISDN_get_layerid(midev, stack->d_stid, nt?1:3); if (stack->lower_id < 0) { cb_log(0, port, "%s: Cannot get layer(%d) id of this port.\n", __FUNCTION__, nt?1:3); free(stack); return(NULL); } stack->upper_id = mISDN_get_layerid(midev, stack->d_stid, nt?2:4); if (stack->upper_id < 0) { cb_log(0, port, "%s: Cannot get layer(%d) id of this port.\n", __FUNCTION__, nt?2:4); free(stack); return(NULL); } /* create nst (nt-mode only) */ if (nt) { memset(&stack->nst, 0, sizeof(net_stack_t)); memset(&stack->mgr, 0, sizeof(manager_t)); stack->mgr.nst = &stack->nst; stack->nst.manager = &stack->mgr; stack->nst.l3_manager = handle_event_nt; stack->nst.device = midev; stack->nst.cardnr = port; stack->nst.d_stid = stack->d_stid; stack->nst.feature = FEATURE_NET_HOLD; if (stack->ptp) stack->nst.feature |= FEATURE_NET_PTP; if (stack->pri) stack->nst.feature |= FEATURE_NET_CRLEN2 | FEATURE_NET_EXTCID; stack->nst.l1_id = stack->lower_id; /* never used */ stack->nst.l2_id = stack->upper_id; msg_queue_init(&stack->nst.down_queue); pthread_mutex_init(&stack->nstlock, NULL); Isdnl2Init(&stack->nst); Isdnl3Init(&stack->nst); } stack->l1link=0; stack->l2link=0; #if 0 if (!stack->nt) { misdn_lib_get_short_status(stack); } else { misdn_lib_get_l1_up(stack); if (!stack->ptp) misdn_lib_get_l1_up(stack); misdn_lib_get_l2_up(stack); } #endif misdn_lib_get_short_status(stack); misdn_lib_get_l1_up(stack); /* handle_l1 will start L2 for NT. */ if (!stack->nt) { misdn_lib_get_l2_up(stack); } } cb_log(8, port, "stack_init: lowerId:%x upperId:%x\n", stack->lower_id, stack->upper_id); return stack; } static void stack_destroy(struct misdn_stack *stack) { char buf[1024]; if (!stack) return; if (stack->nt) { pthread_mutex_destroy(&stack->nstlock); cleanup_Isdnl2(&stack->nst); cleanup_Isdnl3(&stack->nst); } if (stack->upper_id) mISDN_write_frame(stack->midev, buf, stack->upper_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); pthread_mutex_destroy(&stack->st_lock); } static struct misdn_stack * find_stack_by_addr(int addr) { struct misdn_stack *stack; for (stack = glob_mgr->stack_list; stack; stack = stack->next) { if ((stack->upper_id & STACK_ID_MASK) == (addr & STACK_ID_MASK)) { /* Found the stack */ break; } } return stack; } static struct misdn_stack *find_stack_by_port(int port) { struct misdn_stack *stack; for (stack = glob_mgr->stack_list; stack; stack = stack->next) { if (stack->port == port) { /* Found the stack */ break; } } return stack; } static struct misdn_stack *find_stack_by_mgr(manager_t *mgr_nt) { struct misdn_stack *stack; for (stack = glob_mgr->stack_list; stack; stack = stack->next) { if (&stack->mgr == mgr_nt) { /* Found the stack */ break; } } return stack; } static struct misdn_bchannel *find_bc_by_masked_l3id(struct misdn_stack *stack, unsigned long l3id, unsigned long mask) { int i; for (i = 0; i <= stack->b_num; ++i) { if (stack->bc[i].in_use && (stack->bc[i].l3_id & mask) == (l3id & mask)) { return &stack->bc[i]; } } #if defined(AST_MISDN_ENHANCEMENTS) /* Search the B channel records for a REGISTER signaling link. */ for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) { if (stack->bc[i].in_use && (stack->bc[i].l3_id & mask) == (l3id & mask)) { return &stack->bc[i]; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ return stack_holder_find(stack, l3id); } struct misdn_bchannel *find_bc_by_l3id(struct misdn_stack *stack, unsigned long l3id) { int i; for (i = 0; i <= stack->b_num; ++i) { if (stack->bc[i].in_use && stack->bc[i].l3_id == l3id) { return &stack->bc[i]; } } #if defined(AST_MISDN_ENHANCEMENTS) /* Search the B channel records for a REGISTER signaling link. */ for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) { if (stack->bc[i].in_use && stack->bc[i].l3_id == l3id) { return &stack->bc[i]; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ return stack_holder_find(stack, l3id); } static struct misdn_bchannel *find_bc_by_addr(unsigned long addr) { struct misdn_stack *stack; int i; for (stack = glob_mgr->stack_list; stack; stack = stack->next) { for (i = 0; i <= stack->b_num; i++) { if (stack->bc[i].in_use && ((stack->bc[i].addr & STACK_ID_MASK) == (addr & STACK_ID_MASK) || stack->bc[i].layer_id == addr)) { return &stack->bc[i]; } } } return NULL; } static struct misdn_bchannel *find_bc_by_confid(unsigned long confid) { struct misdn_stack *stack; int i; for (stack = glob_mgr->stack_list; stack; stack = stack->next) { for (i = 0; i <= stack->b_num; i++) { if (stack->bc[i].in_use && stack->bc[i].conf_id == confid) { return &stack->bc[i]; } } } return NULL; } static int handle_event_te(struct misdn_bchannel *bc, enum event_e event, iframe_t *frm) { struct misdn_stack *stack=get_stack_by_bc(bc); switch (event) { case EVENT_CONNECT_ACKNOWLEDGE: setup_bc(bc); if ( *bc->crypt_key ) { cb_log(4, stack->port, "ENABLING BLOWFISH channel:%d caller%d:\"%s\" <%s> dialed%d:%s\n", bc->channel, bc->caller.number_type, bc->caller.name, bc->caller.number, bc->dialed.number_type, bc->dialed.number); manager_ph_control_block(bc, BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) ); } if (misdn_cap_is_speech(bc->capability)) { if ( !bc->nodsp) manager_ph_control(bc, DTMF_TONE_START, 0); manager_ec_enable(bc); if ( bc->txgain != 0 ) { cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain); manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain); } if ( bc->rxgain != 0 ) { cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain); manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain); } } break; case EVENT_CONNECT: if ( *bc->crypt_key ) { cb_log(4, stack->port, "ENABLING BLOWFISH channel:%d caller%d:\"%s\" <%s> dialed%d:%s\n", bc->channel, bc->caller.number_type, bc->caller.name, bc->caller.number, bc->dialed.number_type, bc->dialed.number); manager_ph_control_block(bc, BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) ); } case EVENT_ALERTING: case EVENT_PROGRESS: case EVENT_PROCEEDING: case EVENT_SETUP_ACKNOWLEDGE: case EVENT_SETUP: { if (bc->channel == 0xff || bc->channel<=0) bc->channel=0; if (find_free_chan_in_stack(stack, bc, bc->channel, 0)<0){ if (!stack->pri && !stack->ptp) { bc->cw=1; break; } if (!bc->channel) { cb_log(0, stack->port, "Any Channel Requested, but we have no more!!\n"); } else { cb_log(0, stack->port, "Requested Channel Already in Use releasing this call with cause %d!!!!\n", bc->out_cause); } /* when the channel is already in use, we can't * simply clear it, we need to make sure that * it will still be marked as in_use in the * available channels list.*/ bc->channel=0; misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE); return -1; } if (event != EVENT_SETUP) { setup_bc(bc); } break; } case EVENT_RELEASE_COMPLETE: case EVENT_RELEASE: break; default: break; } return 0; } static int handle_cr ( struct misdn_stack *stack, iframe_t *frm) { struct misdn_bchannel dummybc; struct misdn_bchannel *bc; int channel; if (!stack) return -1; switch (frm->prim) { case CC_NEW_CR|INDICATION: cb_log(7, stack->port, " --> lib: NEW_CR Ind with l3id:%x on this port.\n",frm->dinfo); bc = misdn_lib_get_free_bc(stack->port, 0, 1, 0); if (!bc) { cb_log(0, stack->port, " --> !! lib: No free channel!\n"); return -1; } cb_log(7, stack->port, " --> new_process: New L3Id: %x\n",frm->dinfo); bc->l3_id=frm->dinfo; return 1; case CC_NEW_CR|CONFIRM: return 1; case CC_NEW_CR|REQUEST: return 1; case CC_RELEASE_CR|REQUEST: return 1; case CC_RELEASE_CR|CONFIRM: break; case CC_RELEASE_CR|INDICATION: cb_log(4, stack->port, " --> lib: RELEASE_CR Ind with l3id:%x\n", frm->dinfo); bc = find_bc_by_l3id(stack, frm->dinfo); if (!bc) { cb_log(4, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", frm->dinfo); misdn_make_dummy(&dummybc, stack->port, frm->dinfo, stack->nt, 0); bc = &dummybc; } channel = bc->channel; cb_log(4, stack->port, " --> lib: CLEANING UP l3id: %x\n", frm->dinfo); /* bc->pid = 0; */ bc->need_disconnect = 0; bc->need_release = 0; bc->need_release_complete = 0; cb_event(EVENT_CLEANUP, bc, glob_mgr->user_data); empty_bc(bc); clean_up_bc(bc); if (channel > 0) empty_chan_in_stack(stack, channel); bc->in_use = 0; dump_chan_list(stack); if (bc->stack_holder) { cb_log(4, stack->port, "REMOVING Holder\n"); stack_holder_remove(stack, bc); free(bc); } return 1; default: break; } return 0; } /* Empties bc if it's reserved (no SETUP out yet) */ void misdn_lib_release(struct misdn_bchannel *bc) { int channel; struct misdn_stack *stack=get_stack_by_bc(bc); if (!stack) { cb_log(1,0,"misdn_release: No Stack found\n"); return; } channel = bc->channel; empty_bc(bc); clean_up_bc(bc); if (channel > 0) { empty_chan_in_stack(stack, channel); } bc->in_use=0; } int misdn_lib_get_port_up (int port) { /* Pull Up L1 */ struct misdn_stack *stack; for (stack=glob_mgr->stack_list; stack; stack=stack->next) { if (stack->port == port) { if (!stack->l1link) misdn_lib_get_l1_up(stack); /* handle_l1 will start L2 for NT. */ if (!stack->l2link && !stack->nt) { misdn_lib_get_l2_up(stack); } return 0; } } return 0; } int misdn_lib_get_port_down (int port) { /* Pull Down L1 */ struct misdn_stack *stack; for (stack=glob_mgr->stack_list; stack; stack=stack->next) { if (stack->port == port) { if (stack->l2link) misdn_lib_get_l2_down(stack); misdn_lib_get_l1_down(stack); return 0; } } return 0; } int misdn_lib_port_up(int port, int check) { struct misdn_stack *stack; for (stack=glob_mgr->stack_list; stack; stack=stack->next) { if (stack->port == port) { if (stack->blocked) { cb_log(0,port, "Port Blocked:%d L2:%d L1:%d\n", stack->blocked, stack->l2link, stack->l1link); return -1; } if (stack->ptp ) { if (stack->l1link && stack->l2link) { return 1; } else { cb_log(1,port, "Port Down L2:%d L1:%d\n", stack->l2link, stack->l1link); return 0; } } else { if ( !check || stack->l1link ) return 1; else { cb_log(1,port, "Port down PMP\n"); return 0; } } } } return -1; } static int release_cr(struct misdn_stack *stack, mISDNuser_head_t *hh) { struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo); struct misdn_bchannel dummybc; iframe_t frm; /* fake te frm to remove callref from global callreflist */ frm.dinfo = hh->dinfo; frm.addr=stack->upper_id | FLG_MSG_DOWN; frm.prim = CC_RELEASE_CR|INDICATION; cb_log(4, stack->port, " --> CC_RELEASE_CR: Faking Release_cr for %x l3id:%x\n",frm.addr, frm.dinfo); /** removing procid **/ if (!bc) { cb_log(4, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", hh->dinfo); misdn_make_dummy(&dummybc, stack->port, hh->dinfo, stack->nt, 0); bc=&dummybc; } if ((bc->l3_id & 0xff00) == 0xff00) { cb_log(4, stack->port, " --> Removing Process Id:%x on this port.\n", bc->l3_id & 0xff); stack->procids[bc->l3_id & 0xff] = 0; } if (handle_cr(stack, &frm)<0) { } return 0 ; } static int handle_event_nt(void *dat, void *arg) { struct misdn_bchannel dummybc; struct misdn_bchannel *bc; manager_t *mgr = (manager_t *)dat; msg_t *msg = (msg_t *)arg; msg_t *dmsg; mISDNuser_head_t *hh; struct misdn_stack *stack; enum event_e event; int reject=0; int l3id; int channel; int tmpcause; if (!msg || !mgr) return(-EINVAL); stack = find_stack_by_mgr(mgr); hh=(mISDNuser_head_t*)msg->data; /* * When we are called from the mISDNuser lib, the nstlock is held and it * must be held when we return. We unlock here because the lib may be * entered again recursively. */ pthread_mutex_unlock(&stack->nstlock); cb_log(5, stack->port, " --> lib: prim %x dinfo %x\n",hh->prim, hh->dinfo); switch(hh->prim) { case CC_RETRIEVE|INDICATION: { struct misdn_bchannel *hold_bc; iframe_t frm; /* fake te frm to add callref to global callreflist */ frm.dinfo = hh->dinfo; frm.addr=stack->upper_id | FLG_MSG_DOWN; frm.prim = CC_NEW_CR|INDICATION; if (handle_cr( stack, &frm)< 0) { goto ERR_NO_CHANNEL; } bc = find_bc_by_l3id(stack, hh->dinfo); hold_bc = stack_holder_find(stack, bc->l3_id); cb_log(4, stack->port, "bc_l3id:%x holded_bc_l3id:%x\n",bc->l3_id, hold_bc->l3_id); if (hold_bc) { cb_log(4, stack->port, "REMOVING Holder\n"); /* swap the backup to our new channel back */ stack_holder_remove(stack, hold_bc); memcpy(bc, hold_bc, sizeof(*bc)); free(hold_bc); bc->holded=0; bc->b_stid=0; } break; } case CC_SETUP | CONFIRM: l3id = *((int *) (msg->data + mISDNUSER_HEAD_SIZE)); cb_log(4, stack->port, " --> lib: Event_ind:SETUP CONFIRM [NT] : new L3ID is %x\n", l3id); bc = find_bc_by_l3id(stack, hh->dinfo); if (bc) { cb_log (2, bc->port, "I IND :CC_SETUP|CONFIRM: old l3id:%x new l3id:%x\n", bc->l3_id, l3id); bc->l3_id = l3id; cb_event(EVENT_NEW_L3ID, bc, glob_mgr->user_data); } else { cb_log(4, stack->port, "Bc Not found (after SETUP CONFIRM)\n"); } free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; case CC_SETUP | INDICATION: bc = misdn_lib_get_free_bc(stack->port, 0, 1, 0); if (!bc) { goto ERR_NO_CHANNEL; } cb_log(4, stack->port, " --> new_process: New L3Id: %x\n",hh->dinfo); bc->l3_id=hh->dinfo; break; #if defined(AST_MISDN_ENHANCEMENTS) case CC_REGISTER | CONFIRM: l3id = *((int *) (msg->data + mISDNUSER_HEAD_SIZE)); cb_log(4, stack->port, " --> lib: Event_ind:REGISTER CONFIRM [NT] : new L3ID is %x\n", l3id); bc = find_bc_by_l3id(stack, hh->dinfo); if (bc) { cb_log (2, bc->port, "I IND :CC_REGISTER|CONFIRM: old l3id:%x new l3id:%x\n", bc->l3_id, l3id); bc->l3_id = l3id; } else { cb_log(4, stack->port, "Bc Not found (after REGISTER CONFIRM)\n"); } free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) case CC_REGISTER | INDICATION: bc = misdn_lib_get_register_bc(stack->port); if (!bc) { goto ERR_NO_CHANNEL; } cb_log(4, stack->port, " --> new_process: New L3Id: %x\n",hh->dinfo); bc->l3_id=hh->dinfo; break; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ case CC_CONNECT_ACKNOWLEDGE|INDICATION: break; case CC_ALERTING|INDICATION: case CC_PROCEEDING|INDICATION: case CC_SETUP_ACKNOWLEDGE|INDICATION: case CC_CONNECT|INDICATION: break; case CC_DISCONNECT|INDICATION: bc = find_bc_by_l3id(stack, hh->dinfo); if (!bc) { bc=find_bc_by_masked_l3id(stack, hh->dinfo, 0xffff0000); if (bc) { int myprocid=bc->l3_id&0x0000ffff; hh->dinfo=(hh->dinfo&0xffff0000)|myprocid; cb_log(3,stack->port,"Reject dinfo: %x cause:%d\n",hh->dinfo,bc->cause); reject=1; } } break; case CC_FACILITY|INDICATION: bc = find_bc_by_l3id(stack, hh->dinfo); if (!bc) { bc=find_bc_by_masked_l3id(stack, hh->dinfo, 0xffff0000); if (bc) { int myprocid=bc->l3_id&0x0000ffff; hh->dinfo=(hh->dinfo&0xffff0000)|myprocid; cb_log(4,bc->port,"Repaired reject Bug, new dinfo: %x\n",hh->dinfo); } } break; case CC_RELEASE_COMPLETE|INDICATION: break; case CC_SUSPEND|INDICATION: cb_log(4, stack->port, " --> Got Suspend, sending Reject for now\n"); dmsg = create_l3msg(CC_SUSPEND_REJECT | REQUEST,MT_SUSPEND_REJECT, hh->dinfo,sizeof(RELEASE_COMPLETE_t), 1); pthread_mutex_lock(&stack->nstlock); stack->nst.manager_l3(&stack->nst, dmsg); free_msg(msg); return 0; case CC_RESUME|INDICATION: break; case CC_RELEASE|CONFIRM: bc = find_bc_by_l3id(stack, hh->dinfo); if (bc) { cb_log(1, stack->port, "CC_RELEASE|CONFIRM (l3id:%x), sending RELEASE_COMPLETE\n", hh->dinfo); misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); } break; case CC_RELEASE|INDICATION: break; case CC_RELEASE_CR|INDICATION: release_cr(stack, hh); free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; case CC_NEW_CR|INDICATION: /* Got New CR for bchan, for now I handle this one in */ /* connect_ack, Need to be changed */ l3id = *((int *) (msg->data + mISDNUSER_HEAD_SIZE)); bc = find_bc_by_l3id(stack, hh->dinfo); if (!bc) { cb_log(0, stack->port, " --> In NEW_CR: didn't found bc ??\n"); pthread_mutex_lock(&stack->nstlock); return -1; } if (((l3id&0xff00)!=0xff00) && ((bc->l3_id&0xff00)==0xff00)) { cb_log(4, stack->port, " --> Removing Process Id:%x on this port.\n", 0xff&bc->l3_id); stack->procids[bc->l3_id&0xff] = 0 ; } cb_log(4, stack->port, "lib: Event_ind:CC_NEW_CR : very new L3ID is %x\n",l3id ); bc->l3_id =l3id; if (!bc->is_register_pool) { cb_event(EVENT_NEW_L3ID, bc, glob_mgr->user_data); } free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; case DL_ESTABLISH | INDICATION: case DL_ESTABLISH | CONFIRM: cb_log(3, stack->port, "%% GOT L2 Activate Info.\n"); if (stack->ptp && stack->l2link) { cb_log(0, stack->port, "%% GOT L2 Activate Info. but we're activated already.. this l2 is faulty, blocking port\n"); cb_event(EVENT_PORT_ALARM, &stack->bc[0], glob_mgr->user_data); } if (stack->ptp && !stack->restart_sent) { /* make sure we restart the interface of the * other side */ stack->restart_sent=1; misdn_lib_send_restart(stack->port, -1); } /* when we get the L2 UP, the L1 is UP definitely too*/ stack->l2link = 1; stack->l2upcnt=0; free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; case DL_RELEASE | INDICATION: case DL_RELEASE | CONFIRM: cb_log(3, stack->port, "%% GOT L2 DeActivate Info.\n"); if (stack->ptp) { if (stack->l2upcnt>3) { cb_log(0 , stack->port, "!!! Could not Get the L2 up after 3 Attempts!!!\n"); } else { if (stack->l1link) { misdn_lib_get_l2_up(stack); stack->l2upcnt++; } } } stack->l2link = 0; free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; default: break; } /* Parse Events and fire_up to App. */ event = isdn_msg_get_event(msgs_g, msg, 1); bc = find_bc_by_l3id(stack, hh->dinfo); if (!bc) { cb_log(4, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x).\n", hh->dinfo); misdn_make_dummy(&dummybc, stack->port, hh->dinfo, stack->nt, 0); bc = &dummybc; } isdn_msg_parse_event(msgs_g, msg, bc, 1); switch (event) { case EVENT_SETUP: if (bc->channel <= 0 || bc->channel == 0xff) { bc->channel = 0; } if (find_free_chan_in_stack(stack, bc, bc->channel, 0) < 0) { goto ERR_NO_CHANNEL; } break; case EVENT_RELEASE: case EVENT_RELEASE_COMPLETE: channel = bc->channel; tmpcause = bc->cause; empty_bc(bc); bc->cause = tmpcause; clean_up_bc(bc); if (channel > 0) empty_chan_in_stack(stack, channel); bc->in_use = 0; break; default: break; } if(!isdn_get_info(msgs_g, event, 1)) { cb_log(4, stack->port, "Unknown Event Ind: prim %x dinfo %x\n", hh->prim, hh->dinfo); } else { if (reject) { switch(bc->cause) { case AST_CAUSE_USER_BUSY: cb_log(1, stack->port, "Siemens Busy reject..\n"); break; default: break; } } cb_event(event, bc, glob_mgr->user_data); } free_msg(msg); pthread_mutex_lock(&stack->nstlock); return 0; ERR_NO_CHANNEL: cb_log(4, stack->port, "Patch from MEIDANIS:Sending RELEASE_COMPLETE %x (No free Chan for you..)\n", hh->dinfo); dmsg = create_l3msg(CC_RELEASE_COMPLETE | REQUEST, MT_RELEASE_COMPLETE, hh->dinfo, sizeof(RELEASE_COMPLETE_t), 1); pthread_mutex_lock(&stack->nstlock); stack->nst.manager_l3(&stack->nst, dmsg); free_msg(msg); return 0; } static int handle_timers(msg_t* msg) { iframe_t *frm= (iframe_t*)msg->data; struct misdn_stack *stack; /* Timer Stuff */ switch (frm->prim) { case MGR_INITTIMER | CONFIRM: case MGR_ADDTIMER | CONFIRM: case MGR_DELTIMER | CONFIRM: case MGR_REMOVETIMER | CONFIRM: free_msg(msg); return(1); } if (frm->prim==(MGR_TIMER | INDICATION) ) { for (stack = glob_mgr->stack_list; stack; stack = stack->next) { itimer_t *it; if (!stack->nt) continue; it = stack->nst.tlist; /* find timer */ for(it=stack->nst.tlist; it; it=it->next) { if (it->id == (int)frm->addr) break; } if (it) { mISDN_write_frame(stack->midev, msg->data, frm->addr, MGR_TIMER | RESPONSE, 0, 0, NULL, TIMEOUT_1SEC); test_and_clear_bit(FLG_TIMER_RUNING, (long unsigned int *)&it->Flags); pthread_mutex_lock(&stack->nstlock); it->function(it->data); pthread_mutex_unlock(&stack->nstlock); free_msg(msg); return 1; } } cb_log(0, 0, "Timer Msg without Timer ??\n"); free_msg(msg); return 1; } return 0; } void misdn_lib_tone_generator_start(struct misdn_bchannel *bc) { bc->generate_tone=1; } void misdn_lib_tone_generator_stop(struct misdn_bchannel *bc) { bc->generate_tone=0; } static int do_tone(struct misdn_bchannel *bc, int len) { bc->tone_cnt=len; if (bc->generate_tone) { cb_event(EVENT_TONE_GENERATE, bc, glob_mgr->user_data); if ( !bc->nojitter ) { misdn_tx_jitter(bc,len); } return 1; } return 0; } #ifdef MISDN_SAVE_DATA static void misdn_save_data(int id, char *p1, int l1, char *p2, int l2) { char n1[32],n2[32]; FILE *rx, *tx; sprintf(n1,"/tmp/misdn-rx-%d.raw",id); sprintf(n2,"/tmp/misdn-tx-%d.raw",id); rx = fopen(n1,"a+"); tx = fopen(n2,"a+"); if (!rx || !tx) { cb_log(0,0,"Couldn't open files: %s\n",strerror(errno)); if (rx) fclose(rx); if (tx) fclose(tx); return ; } fwrite(p1,1,l1,rx); fwrite(p2,1,l2,tx); fclose(rx); fclose(tx); } #endif void misdn_tx_jitter(struct misdn_bchannel *bc, int len) { char buf[4096 + mISDN_HEADER_LEN]; char *data=&buf[mISDN_HEADER_LEN]; iframe_t *txfrm= (iframe_t*)buf; int jlen, r; jlen=cb_jb_empty(bc,data,len); if (jlen) { #ifdef MISDN_SAVE_DATA misdn_save_data((bc->port*100+bc->channel), data, jlen, bc->bframe, bc->bframe_len); #endif flip_buf_bits( data, jlen); if (jlen < len) { cb_log(1, bc->port, "Jitterbuffer Underrun. Got %d of expected %d\n", jlen, len); } txfrm->prim = DL_DATA|REQUEST; txfrm->dinfo = 0; txfrm->addr = bc->addr|FLG_MSG_DOWN; /* | IF_DOWN; */ txfrm->len =jlen; cb_log(9, bc->port, "Transmitting %d samples 2 misdn\n", txfrm->len); r=mISDN_write( glob_mgr->midev, buf, txfrm->len + mISDN_HEADER_LEN, 8000 ); } else { #define MISDN_GEN_SILENCE #ifdef MISDN_GEN_SILENCE int cnt=len/TONE_SILENCE_SIZE; int rest=len%TONE_SILENCE_SIZE; int i; for (i=0; iprim = DL_DATA|REQUEST; txfrm->dinfo = 0; txfrm->addr = bc->addr|FLG_MSG_DOWN; /* | IF_DOWN; */ txfrm->len =len; cb_log(5, bc->port, "Transmitting %d samples of silence to misdn\n", len); r=mISDN_write( glob_mgr->midev, buf, txfrm->len + mISDN_HEADER_LEN, 8000 ); #else r = 0; #endif } if (r < 0) { cb_log(1, bc->port, "Error in mISDN_write (%s)\n", strerror(errno)); } } static int handle_bchan(msg_t *msg) { iframe_t *frm= (iframe_t*)msg->data; struct misdn_bchannel *bc=find_bc_by_addr(frm->addr); struct misdn_stack *stack; if (!bc) { cb_log(1,0,"handle_bchan: BC not found for prim:%x with addr:%x dinfo:%x\n", frm->prim, frm->addr, frm->dinfo); return 0 ; } stack = get_stack_by_bc(bc); if (!stack) { cb_log(0, bc->port,"handle_bchan: STACK not found for prim:%x with addr:%x dinfo:%x\n", frm->prim, frm->addr, frm->dinfo); return 0; } switch (frm->prim) { case MGR_SETSTACK| CONFIRM: cb_log(3, stack->port, "BCHAN: MGR_SETSTACK|CONFIRM pid:%d\n",bc->pid); break; case MGR_SETSTACK| INDICATION: cb_log(3, stack->port, "BCHAN: MGR_SETSTACK|IND pid:%d\n",bc->pid); break; case MGR_DELLAYER| INDICATION: cb_log(3, stack->port, "BCHAN: MGR_DELLAYER|IND pid:%d\n",bc->pid); break; case MGR_DELLAYER| CONFIRM: cb_log(3, stack->port, "BCHAN: MGR_DELLAYER|CNF pid:%d\n",bc->pid); bc->pid=0; bc->addr=0; free_msg(msg); return 1; case PH_ACTIVATE | INDICATION: case DL_ESTABLISH | INDICATION: cb_log(3, stack->port, "BCHAN: ACT Ind pid:%d\n", bc->pid); free_msg(msg); return 1; case PH_ACTIVATE | CONFIRM: case DL_ESTABLISH | CONFIRM: cb_log(3, stack->port, "BCHAN: bchan ACT Confirm pid:%d\n",bc->pid); free_msg(msg); return 1; case DL_ESTABLISH | REQUEST: { char buf[128]; mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_TARGET | FLG_MSG_DOWN, DL_ESTABLISH | CONFIRM, 0,0, NULL, TIMEOUT_1SEC); } free_msg(msg); return 1; case DL_RELEASE|REQUEST: { char buf[128]; mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_TARGET | FLG_MSG_DOWN, DL_RELEASE| CONFIRM, 0,0, NULL, TIMEOUT_1SEC); } free_msg(msg); return 1; case PH_DEACTIVATE | INDICATION: case DL_RELEASE | INDICATION: cb_log (3, stack->port, "BCHAN: DeACT Ind pid:%d\n",bc->pid); free_msg(msg); return 1; case PH_DEACTIVATE | CONFIRM: case DL_RELEASE | CONFIRM: cb_log(3, stack->port, "BCHAN: DeACT Conf pid:%d\n",bc->pid); free_msg(msg); return 1; case PH_CONTROL|INDICATION: { unsigned int *cont = (unsigned int *) &frm->data.p; cb_log(4, stack->port, "PH_CONTROL: channel:%d caller%d:\"%s\" <%s> dialed%d:%s \n", bc->channel, bc->caller.number_type, bc->caller.name, bc->caller.number, bc->dialed.number_type, bc->dialed.number); if ((*cont & ~DTMF_TONE_MASK) == DTMF_TONE_VAL) { int dtmf = *cont & DTMF_TONE_MASK; cb_log(4, stack->port, " --> DTMF TONE: %c\n",dtmf); bc->dtmf=dtmf; cb_event(EVENT_DTMF_TONE, bc, glob_mgr->user_data); free_msg(msg); return 1; } if (*cont == BF_REJECT) { cb_log(4, stack->port, " --> BF REJECT\n"); free_msg(msg); return 1; } if (*cont == BF_ACCEPT) { cb_log(4, stack->port, " --> BF ACCEPT\n"); free_msg(msg); return 1; } } break; case PH_DATA|REQUEST: case DL_DATA|REQUEST: cb_log(0, stack->port, "DL_DATA REQUEST \n"); do_tone(bc, 64); free_msg(msg); return 1; case PH_DATA|INDICATION: case DL_DATA|INDICATION: { bc->bframe = (void*)&frm->data.i; bc->bframe_len = frm->len; /** Anyway flip the bufbits **/ if ( misdn_cap_is_speech(bc->capability) ) flip_buf_bits(bc->bframe, bc->bframe_len); if (!bc->bframe_len) { cb_log(2, stack->port, "DL_DATA INDICATION bc->addr:%x frm->addr:%x\n", bc->addr, frm->addr); free_msg(msg); return 1; } if ( (bc->addr&STACK_ID_MASK) != (frm->addr&STACK_ID_MASK) ) { cb_log(2, stack->port, "DL_DATA INDICATION bc->addr:%x frm->addr:%x\n", bc->addr, frm->addr); free_msg(msg); return 1; } #if MISDN_DEBUG cb_log(0, stack->port, "DL_DATA INDICATION Len %d\n", frm->len); #endif if ( (bc->bc_state == BCHAN_ACTIVATED) && frm->len > 0) { int t; #ifdef MISDN_B_DEBUG cb_log(0,bc->port,"do_tone START\n"); #endif t=do_tone(bc,frm->len); #ifdef MISDN_B_DEBUG cb_log(0,bc->port,"do_tone STOP (%d)\n",t); #endif if ( !t ) { int i; if ( misdn_cap_is_speech(bc->capability)) { if ( !bc->nojitter ) { #ifdef MISDN_B_DEBUG cb_log(0,bc->port,"tx_jitter START\n"); #endif misdn_tx_jitter(bc,frm->len); #ifdef MISDN_B_DEBUG cb_log(0,bc->port,"tx_jitter STOP\n"); #endif } } #ifdef MISDN_B_DEBUG cb_log(0,bc->port,"EVENT_B_DATA START\n"); #endif i = cb_event(EVENT_BCHAN_DATA, bc, glob_mgr->user_data); #ifdef MISDN_B_DEBUG cb_log(0,bc->port,"EVENT_B_DATA STOP\n"); #endif if (i<0) { cb_log(10,stack->port,"cb_event returned <0\n"); /*clean_up_bc(bc);*/ } } } free_msg(msg); return 1; } case PH_CONTROL | CONFIRM: cb_log(4, stack->port, "PH_CONTROL|CNF bc->addr:%x\n", frm->addr); free_msg(msg); return 1; case PH_DATA | CONFIRM: case DL_DATA|CONFIRM: #if MISDN_DEBUG cb_log(0, stack->port, "Data confirmed\n"); #endif free_msg(msg); return 1; case DL_DATA|RESPONSE: #if MISDN_DEBUG cb_log(0, stack->port, "Data response\n"); #endif break; } return 0; } static int handle_frm_nt(msg_t *msg) { iframe_t *frm= (iframe_t*)msg->data; struct misdn_stack *stack; int err=0; stack=find_stack_by_addr( frm->addr ); if (!stack || !stack->nt) { return 0; } pthread_mutex_lock(&stack->nstlock); if ((err=stack->nst.l1_l2(&stack->nst,msg))) { pthread_mutex_unlock(&stack->nstlock); if (nt_err_cnt > 0 ) { if (nt_err_cnt < 100) { nt_err_cnt++; cb_log(0, stack->port, "NT Stack sends us error: %d \n", err); } else if (nt_err_cnt < 105){ cb_log(0, stack->port, "NT Stack sends us error: %d over 100 times, so I'll stop this message\n", err); nt_err_cnt = - 1; } } free_msg(msg); return 1; } pthread_mutex_unlock(&stack->nstlock); return 1; } static int handle_frm_te(msg_t *msg) { struct misdn_bchannel dummybc; struct misdn_bchannel *bc; iframe_t *frm; struct misdn_stack *stack; enum event_e event; enum event_response_e response; int ret; int channel; int tmpcause; int tmp_out_cause; frm = (iframe_t*) msg->data; stack = find_stack_by_addr(frm->addr); if (!stack || stack->nt) { return 0; } cb_log(4, stack->port, "handle_frm_te: frm->addr:%x frm->prim:%x\n", frm->addr, frm->prim); ret = handle_cr(stack, frm); if (ret < 0) { cb_log(3, stack->port, "handle_frm_te: handle_cr <0 prim:%x addr:%x\n", frm->prim, frm->addr); } if (ret) { free_msg(msg); return 1; } bc = find_bc_by_l3id(stack, frm->dinfo); if (!bc) { misdn_make_dummy(&dummybc, stack->port, 0, stack->nt, 0); switch (frm->prim) { case CC_RESTART | CONFIRM: dummybc.l3_id = MISDN_ID_GLOBAL; bc = &dummybc; break; case CC_SETUP | INDICATION: dummybc.l3_id = frm->dinfo; bc = &dummybc; /* set a reasonable cause */ bc->out_cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL; /* if we want to send something the flag must be set! */ bc->need_release_complete = 1; misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); free_msg(msg); return 1; default: if (frm->prim == (CC_FACILITY | INDICATION)) { cb_log(5, stack->port, " --> Using Dummy BC for FACILITY\n"); } else { cb_log(0, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", frm->dinfo); dummybc.l3_id = frm->dinfo; } bc = &dummybc; break; } } event = isdn_msg_get_event(msgs_g, msg, 0); isdn_msg_parse_event(msgs_g, msg, bc, 0); /* Preprocess some Events */ ret = handle_event_te(bc, event, frm); if (ret < 0) { cb_log(0, stack->port, "couldn't handle event\n"); free_msg(msg); return 1; } /* shoot up event to App: */ cb_log(5, stack->port, "lib Got Prim: Addr %x prim %x dinfo %x\n", frm->addr, frm->prim, frm->dinfo); if (!isdn_get_info(msgs_g, event, 0)) { cb_log(0, stack->port, "Unknown Event Ind: Addr:%x prim %x dinfo %x\n", frm->addr, frm->prim, frm->dinfo); response = RESPONSE_OK; } else { response = cb_event(event, bc, glob_mgr->user_data); } switch (event) { case EVENT_SETUP: switch (response) { case RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE: cb_log(0, stack->port, "TOTALLY IGNORING SETUP\n"); break; case RESPONSE_IGNORE_SETUP: cb_log(0, stack->port, "GOT IGNORE SETUP\n"); /* I think we should send CC_RELEASE_CR, but am not sure*/ bc->out_cause = AST_CAUSE_NORMAL_CLEARING; /* fall through */ case RESPONSE_RELEASE_SETUP: if (response == RESPONSE_RELEASE_SETUP) { cb_log(0, stack->port, "GOT RELEASE SETUP\n"); } misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); break; case RESPONSE_OK: cb_log(4, stack->port, "GOT SETUP OK\n"); break; default: cb_log(0, stack->port, "GOT UNKNOWN SETUP RESPONSE\n"); break; } break; case EVENT_RELEASE_COMPLETE: /* release bchannel only after we've announced the RELEASE_COMPLETE */ channel = bc->channel; tmpcause = bc->cause; tmp_out_cause = bc->out_cause; empty_bc(bc); bc->cause = tmpcause; bc->out_cause = tmp_out_cause; clean_up_bc(bc); bc->in_use = 0; if (tmpcause == AST_CAUSE_REQUESTED_CHAN_UNAVAIL) { cb_log(0, stack->port, "**** Received CAUSE:%d, restarting channel %d\n", AST_CAUSE_REQUESTED_CHAN_UNAVAIL, channel); misdn_lib_send_restart(stack->port, channel); } if (channel > 0) { empty_chan_in_stack(stack, channel); } break; case EVENT_RESTART: cb_log(0, stack->port, "**** Received RESTART channel:%d\n", bc->restart_channel); empty_chan_in_stack(stack, bc->restart_channel); break; default: break; } cb_log(5, stack->port, "Freeing Msg on prim:%x \n", frm->prim); free_msg(msg); return 1; } static int handle_l1(msg_t *msg) { iframe_t *frm = (iframe_t*) msg->data; struct misdn_stack *stack = find_stack_by_addr(frm->addr); if (!stack) return 0 ; switch (frm->prim) { case PH_ACTIVATE | CONFIRM: /* we have to check for errors! */ if (frm->len) { cb_log (3, stack->port, "L1: PH_ACTIVATE|REQUEST returned error!\n"); free_msg(msg); return 1; } /* fall through */ case PH_ACTIVATE | INDICATION: cb_log (3, stack->port, "L1: PH L1Link Up!\n"); stack->l1link=1; if (stack->nt) { pthread_mutex_lock(&stack->nstlock); if (stack->nst.l1_l2(&stack->nst, msg)) free_msg(msg); pthread_mutex_unlock(&stack->nstlock); if (stack->ptp) misdn_lib_get_l2_up(stack); } else { free_msg(msg); } return 1; case PH_ACTIVATE | REQUEST: free_msg(msg); cb_log(3,stack->port,"L1: PH_ACTIVATE|REQUEST \n"); return 1; case PH_DEACTIVATE | REQUEST: free_msg(msg); cb_log(3,stack->port,"L1: PH_DEACTIVATE|REQUEST \n"); return 1; case PH_DEACTIVATE | CONFIRM: /* we have to check for errors! */ if (frm->len) { cb_log (3, stack->port, "L1: PH_DEACTIVATE|REQUEST returned error!\n"); free_msg(msg); return 1; } /* fall through */ case PH_DEACTIVATE | INDICATION: cb_log (3, stack->port, "L1: PH L1Link Down! \n"); #if 0 for (i=0; i<=stack->b_num; i++) { if (global_state == MISDN_INITIALIZED) { cb_event(EVENT_CLEANUP, &stack->bc[i], glob_mgr->user_data); } } #endif if (stack->nt) { pthread_mutex_lock(&stack->nstlock); if (stack->nst.l1_l2(&stack->nst, msg)) free_msg(msg); pthread_mutex_unlock(&stack->nstlock); } else { free_msg(msg); } stack->l1link=0; stack->l2link=0; return 1; } return 0; } static int handle_l2(msg_t *msg) { iframe_t *frm = (iframe_t*) msg->data; struct misdn_stack *stack = find_stack_by_addr(frm->addr); if (!stack) { return 0 ; } switch(frm->prim) { case DL_ESTABLISH | REQUEST: cb_log(1,stack->port,"DL_ESTABLISH|REQUEST \n"); free_msg(msg); return 1; case DL_RELEASE | REQUEST: cb_log(1,stack->port,"DL_RELEASE|REQUEST \n"); free_msg(msg); return 1; case DL_ESTABLISH | INDICATION: case DL_ESTABLISH | CONFIRM: { cb_log (3, stack->port, "L2: L2Link Up! \n"); if (stack->ptp && stack->l2link) { cb_log (-1, stack->port, "L2: L2Link Up! but it's already UP.. must be faulty, blocking port\n"); cb_event(EVENT_PORT_ALARM, &stack->bc[0], glob_mgr->user_data); } stack->l2link=1; free_msg(msg); return 1; } break; case DL_RELEASE | INDICATION: case DL_RELEASE | CONFIRM: { cb_log (3, stack->port, "L2: L2Link Down! \n"); stack->l2link=0; free_msg(msg); return 1; } break; } return 0; } static int handle_mgmt(msg_t *msg) { iframe_t *frm = (iframe_t*) msg->data; struct misdn_stack *stack; if ( (frm->addr == 0) && (frm->prim == (MGR_DELLAYER|CONFIRM)) ) { cb_log(2, 0, "MGMT: DELLAYER|CONFIRM Addr: 0 !\n") ; free_msg(msg); return 1; } stack = find_stack_by_addr(frm->addr); if (!stack) { if (frm->prim == (MGR_DELLAYER|CONFIRM)) { cb_log(2, 0, "MGMT: DELLAYER|CONFIRM Addr: %x !\n", frm->addr) ; free_msg(msg); return 1; } return 0; } switch(frm->prim) { case MGR_SHORTSTATUS | INDICATION: case MGR_SHORTSTATUS | CONFIRM: cb_log(5, stack->port, "MGMT: Short status dinfo %x\n",frm->dinfo); switch (frm->dinfo) { case SSTATUS_L1_ACTIVATED: cb_log(3, stack->port, "MGMT: SSTATUS: L1_ACTIVATED \n"); stack->l1link=1; break; case SSTATUS_L1_DEACTIVATED: cb_log(3, stack->port, "MGMT: SSTATUS: L1_DEACTIVATED \n"); stack->l1link=0; #if 0 clear_l3(stack); #endif break; case SSTATUS_L2_ESTABLISHED: cb_log(3, stack->port, "MGMT: SSTATUS: L2_ESTABLISH \n"); stack->l2link=1; break; case SSTATUS_L2_RELEASED: cb_log(3, stack->port, "MGMT: SSTATUS: L2_RELEASED \n"); stack->l2link=0; break; } free_msg(msg); return 1; case MGR_SETSTACK | INDICATION: cb_log(4, stack->port, "MGMT: SETSTACK|IND dinfo %x\n",frm->dinfo); free_msg(msg); return 1; case MGR_DELLAYER | CONFIRM: cb_log(4, stack->port, "MGMT: DELLAYER|CNF dinfo %x\n",frm->dinfo) ; free_msg(msg); return 1; } /* if ( (frm->prim & 0x0f0000) == 0x0f0000) { cb_log(5, 0, "$$$ MGMT FRAME: prim %x addr %x dinfo %x\n",frm->prim, frm->addr, frm->dinfo) ; free_msg(msg); return 1; } */ return 0; } static msg_t *fetch_msg(int midev) { msg_t *msg=alloc_msg(MAX_MSG_SIZE); int r; if (!msg) { cb_log(0, 0, "fetch_msg: alloc msg failed !!"); return NULL; } AGAIN: r=mISDN_read(midev,msg->data,MAX_MSG_SIZE, TIMEOUT_10SEC); msg->len=r; if (r==0) { free_msg(msg); /* danger, cause usually freeing in main_loop */ cb_log(6,0,"Got empty Msg..\n"); return NULL; } if (r<0) { if (errno == EAGAIN) { /*we wait for mISDN here*/ cb_log(4,0,"mISDN_read wants us to wait\n"); usleep(5000); goto AGAIN; } cb_log(0,0,"mISDN_read returned :%d error:%s (%d)\n",r,strerror(errno),errno); } #if 0 if (!(frm->prim == (DL_DATA|INDICATION) )|| (frm->prim == (PH_DATA|INDICATION))) cb_log(0,0,"prim: %x dinfo:%x addr:%x msglen:%d frm->len:%d\n",frm->prim, frm->dinfo, frm->addr, msg->len,frm->len ); #endif return msg; } void misdn_lib_isdn_l1watcher(int port) { struct misdn_stack *stack; for (stack = glob_mgr->stack_list; stack && (stack->port != port); stack = stack->next) ; if (stack) { cb_log(4, port, "Checking L1 State\n"); if (!stack->l1link) { cb_log(4, port, "L1 State Down, trying to get it up again\n"); misdn_lib_get_short_status(stack); misdn_lib_get_l1_up(stack); misdn_lib_get_l2_up(stack); } } } /* This is a thread */ static void misdn_lib_isdn_event_catcher(void *arg) { struct misdn_lib *mgr = arg; int zero_frm=0 , fff_frm=0 ; int midev= mgr->midev; int port=0; while (1) { msg_t *msg = fetch_msg(midev); iframe_t *frm; if (!msg) continue; frm = (iframe_t*) msg->data; /** When we make a call from NT2Ast we get these frames **/ if (frm->len == 0 && frm->addr == 0 && frm->dinfo == 0 && frm->prim == 0 ) { zero_frm++; free_msg(msg); continue; } else { if (zero_frm) { cb_log(0, port, "*** Alert: %d zero_frms caught\n", zero_frm); zero_frm = 0 ; } } /** I get this sometimes after setup_bc **/ if (frm->len == 0 && frm->dinfo == 0 && frm->prim == 0xffffffff ) { fff_frm++; free_msg(msg); continue; } else { if (fff_frm) { cb_log(0, port, "*** Alert: %d fff_frms caught\n", fff_frm); fff_frm = 0 ; } } manager_isdn_handler(frm, msg); } } /** App Interface **/ static int te_lib_init(void) { char buff[1025] = ""; iframe_t *frm = (iframe_t *) buff; int midev; int ret; midev = mISDN_open(); if (midev <= 0) { return midev; } /* create entity for layer 3 TE-mode */ mISDN_write_frame(midev, buff, 0, MGR_NEWENTITY | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); ret = mISDN_read_frame(midev, frm, sizeof(iframe_t), 0, MGR_NEWENTITY | CONFIRM, TIMEOUT_1SEC); entity = frm->dinfo & 0xffff; if (ret < mISDN_HEADER_LEN || !entity) { fprintf(stderr, "cannot request MGR_NEWENTITY from mISDN: %s\n", strerror(errno)); mISDN_close(midev); return -1; } return midev; } void te_lib_destroy(int midev) { char buf[1024]; mISDN_write_frame(midev, buf, 0, MGR_DELENTITY | REQUEST, entity, 0, NULL, TIMEOUT_1SEC); cb_log(4, 0, "Entity deleted\n"); mISDN_close(midev); cb_log(4, 0, "midev closed\n"); } struct misdn_bchannel *manager_find_bc_by_pid(int pid) { struct misdn_stack *stack; int i; for (stack = glob_mgr->stack_list; stack; stack = stack->next) { for (i = 0; i <= stack->b_num; i++) { if (stack->bc[i].in_use && stack->bc[i].pid == pid) { return &stack->bc[i]; } } } return NULL; } static int test_inuse(struct misdn_bchannel *bc) { struct timeval now; if (!bc->in_use) { gettimeofday(&now, NULL); if (bc->last_used.tv_sec == now.tv_sec && misdn_lib_port_is_pri(bc->port)) { cb_log(2, bc->port, "channel with stid:%x for one second still in use! (n:%d lu:%d)\n", bc->b_stid, (int) now.tv_sec, (int) bc->last_used.tv_sec); return 1; } cb_log(3,bc->port, "channel with stid:%x not in use!\n", bc->b_stid); return 0; } cb_log(2,bc->port, "channel with stid:%x in use!\n", bc->b_stid); return 1; } static void prepare_bc(struct misdn_bchannel*bc, int channel) { bc->channel = channel; bc->channel_preselected = channel?1:0; bc->need_disconnect=1; bc->need_release=1; bc->need_release_complete=1; bc->cause = AST_CAUSE_NORMAL_CLEARING; if (++mypid>5000) mypid=1; bc->pid=mypid; bc->in_use = 1; } struct misdn_bchannel *misdn_lib_get_free_bc(int port, int channel, int inout, int dec) { struct misdn_stack *stack; int i; int maxnum; if (channel < 0 || channel > MAX_BCHANS) { cb_log(0, port, "Requested channel out of bounds (%d)\n", channel); return NULL; } /* Find the port stack structure */ stack = find_stack_by_port(port); if (!stack) { cb_log(0, port, "Port is not configured (%d)\n", port); return NULL; } if (stack->blocked) { cb_log(0, port, "Port is blocked\n"); return NULL; } pthread_mutex_lock(&stack->st_lock); if (channel > 0) { if (channel <= stack->b_num) { for (i = 0; i < stack->b_num; i++) { if (stack->bc[i].channel == channel) { if (test_inuse(&stack->bc[i])) { pthread_mutex_unlock(&stack->st_lock); cb_log(0, port, "Requested channel:%d on port:%d is already in use\n", channel, port); return NULL; } else { prepare_bc(&stack->bc[i], channel); pthread_mutex_unlock(&stack->st_lock); return &stack->bc[i]; } } } } else { pthread_mutex_unlock(&stack->st_lock); cb_log(0, port, "Requested channel:%d is out of bounds on port:%d\n", channel, port); return NULL; } } /* Note: channel == 0 here */ maxnum = (inout && !stack->pri && !stack->ptp) ? stack->b_num + 1 : stack->b_num; if (dec) { for (i = maxnum - 1; i >= 0; --i) { if (!test_inuse(&stack->bc[i])) { /* 3. channel on bri means CW*/ if (!stack->pri && i == stack->b_num) { stack->bc[i].cw = 1; } prepare_bc(&stack->bc[i], channel); stack->bc[i].dec = 1; pthread_mutex_unlock(&stack->st_lock); return &stack->bc[i]; } } } else { for (i = 0; i < maxnum; ++i) { if (!test_inuse(&stack->bc[i])) { /* 3. channel on bri means CW */ if (!stack->pri && i == stack->b_num) { stack->bc[i].cw = 1; } prepare_bc(&stack->bc[i], channel); pthread_mutex_unlock(&stack->st_lock); return &stack->bc[i]; } } } pthread_mutex_unlock(&stack->st_lock); cb_log(1, port, "There is no free channel on port (%d)\n", port); return NULL; } #if defined(AST_MISDN_ENHANCEMENTS) /*! * \brief Allocate a B channel struct from the REGISTER pool * * \param port Logical port number * * \retval B channel struct on success. * \retval NULL on error. */ struct misdn_bchannel *misdn_lib_get_register_bc(int port) { struct misdn_stack *stack; struct misdn_bchannel *bc; unsigned index; /* Find the port stack structure */ stack = find_stack_by_port(port); if (!stack) { cb_log(0, port, "Port is not configured (%d)\n", port); return NULL; } if (stack->blocked) { cb_log(0, port, "Port is blocked\n"); return NULL; } pthread_mutex_lock(&stack->st_lock); for (index = MAX_BCHANS + 1; index < ARRAY_LEN(stack->bc); ++index) { bc = &stack->bc[index]; if (!test_inuse(bc)) { prepare_bc(bc, 0); bc->need_disconnect = 0; bc->need_release = 0; pthread_mutex_unlock(&stack->st_lock); return bc; } } pthread_mutex_unlock(&stack->st_lock); cb_log(1, port, "There is no free REGISTER link on port (%d)\n", port); return NULL; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /*! * \internal * \brief Convert the facility function enum value into a string. * * \return String version of the enum value */ static const char *fac2str(enum FacFunction facility) { static const struct { enum FacFunction facility; char *name; } arr[] = { /* *INDENT-OFF* */ { Fac_None, "Fac_None" }, #if defined(AST_MISDN_ENHANCEMENTS) { Fac_ERROR, "Fac_ERROR" }, { Fac_RESULT, "Fac_RESULT" }, { Fac_REJECT, "Fac_REJECT" }, { Fac_ActivationDiversion, "Fac_ActivationDiversion" }, { Fac_DeactivationDiversion, "Fac_DeactivationDiversion" }, { Fac_ActivationStatusNotificationDiv, "Fac_ActivationStatusNotificationDiv" }, { Fac_DeactivationStatusNotificationDiv, "Fac_DeactivationStatusNotificationDiv" }, { Fac_InterrogationDiversion, "Fac_InterrogationDiversion" }, { Fac_DiversionInformation, "Fac_DiversionInformation" }, { Fac_CallDeflection, "Fac_CallDeflection" }, { Fac_CallRerouteing, "Fac_CallRerouteing" }, { Fac_DivertingLegInformation2, "Fac_DivertingLegInformation2" }, { Fac_InterrogateServedUserNumbers, "Fac_InterrogateServedUserNumbers" }, { Fac_DivertingLegInformation1, "Fac_DivertingLegInformation1" }, { Fac_DivertingLegInformation3, "Fac_DivertingLegInformation3" }, { Fac_EctExecute, "Fac_EctExecute" }, { Fac_ExplicitEctExecute, "Fac_ExplicitEctExecute" }, { Fac_RequestSubaddress, "Fac_RequestSubaddress" }, { Fac_SubaddressTransfer, "Fac_SubaddressTransfer" }, { Fac_EctLinkIdRequest, "Fac_EctLinkIdRequest" }, { Fac_EctInform, "Fac_EctInform" }, { Fac_EctLoopTest, "Fac_EctLoopTest" }, { Fac_ChargingRequest, "Fac_ChargingRequest" }, { Fac_AOCSCurrency, "Fac_AOCSCurrency" }, { Fac_AOCSSpecialArr, "Fac_AOCSSpecialArr" }, { Fac_AOCDCurrency, "Fac_AOCDCurrency" }, { Fac_AOCDChargingUnit, "Fac_AOCDChargingUnit" }, { Fac_AOCECurrency, "Fac_AOCECurrency" }, { Fac_AOCEChargingUnit, "Fac_AOCEChargingUnit" }, { Fac_StatusRequest, "Fac_StatusRequest" }, { Fac_CallInfoRetain, "Fac_CallInfoRetain" }, { Fac_EraseCallLinkageID, "Fac_EraseCallLinkageID" }, { Fac_CCBSDeactivate, "Fac_CCBSDeactivate" }, { Fac_CCBSErase, "Fac_CCBSErase" }, { Fac_CCBSRemoteUserFree, "Fac_CCBSRemoteUserFree" }, { Fac_CCBSCall, "Fac_CCBSCall" }, { Fac_CCBSStatusRequest, "Fac_CCBSStatusRequest" }, { Fac_CCBSBFree, "Fac_CCBSBFree" }, { Fac_CCBSStopAlerting, "Fac_CCBSStopAlerting" }, { Fac_CCBSRequest, "Fac_CCBSRequest" }, { Fac_CCBSInterrogate, "Fac_CCBSInterrogate" }, { Fac_CCNRRequest, "Fac_CCNRRequest" }, { Fac_CCNRInterrogate, "Fac_CCNRInterrogate" }, { Fac_CCBS_T_Call, "Fac_CCBS_T_Call" }, { Fac_CCBS_T_Suspend, "Fac_CCBS_T_Suspend" }, { Fac_CCBS_T_Resume, "Fac_CCBS_T_Resume" }, { Fac_CCBS_T_RemoteUserFree, "Fac_CCBS_T_RemoteUserFree" }, { Fac_CCBS_T_Available, "Fac_CCBS_T_Available" }, { Fac_CCBS_T_Request, "Fac_CCBS_T_Request" }, { Fac_CCNR_T_Request, "Fac_CCNR_T_Request" }, #else { Fac_CFActivate, "Fac_CFActivate" }, { Fac_CFDeactivate, "Fac_CFDeactivate" }, { Fac_CD, "Fac_CD" }, { Fac_AOCDCurrency, "Fac_AOCDCurrency" }, { Fac_AOCDChargingUnit, "Fac_AOCDChargingUnit" }, #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /* *INDENT-ON* */ }; unsigned index; for (index = 0; index < ARRAY_LEN(arr); ++index) { if (arr[index].facility == facility) { return arr[index].name; } } return "unknown"; } void misdn_lib_log_ies(struct misdn_bchannel *bc) { struct misdn_stack *stack; if (!bc) return; stack = get_stack_by_bc(bc); if (!stack) return; cb_log(2, stack->port, " --> channel:%d mode:%s cause:%d ocause:%d\n", bc->channel, stack->nt ? "NT" : "TE", bc->cause, bc->out_cause); cb_log(2, stack->port, " --> info_dad:%s dialed numtype:%d plan:%d\n", bc->info_dad, bc->dialed.number_type, bc->dialed.number_plan); cb_log(2, stack->port, " --> caller:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n", bc->caller.name, bc->caller.number, bc->caller.number_type, bc->caller.number_plan, bc->caller.presentation, bc->caller.screening); cb_log(2, stack->port, " --> redirecting-from:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n", bc->redirecting.from.name, bc->redirecting.from.number, bc->redirecting.from.number_type, bc->redirecting.from.number_plan, bc->redirecting.from.presentation, bc->redirecting.from.screening); cb_log(2, stack->port, " --> redirecting-to:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n", bc->redirecting.to.name, bc->redirecting.to.number, bc->redirecting.to.number_type, bc->redirecting.to.number_plan, bc->redirecting.to.presentation, bc->redirecting.to.screening); cb_log(2, stack->port, " --> redirecting reason:%d count:%d\n", bc->redirecting.reason, bc->redirecting.count); cb_log(2, stack->port, " --> connected:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n", bc->connected.name, bc->connected.number, bc->connected.number_type, bc->connected.number_plan, bc->connected.presentation, bc->connected.screening); cb_log(3, stack->port, " --> caps:%s pi:%x keypad:%s sending_complete:%d\n", bearer2str(bc->capability),bc->progress_indicator, bc->keypad, bc->sending_complete); cb_log(4, stack->port, " --> set_pres:%d pres:%d\n", bc->set_presentation, bc->presentation); cb_log(4, stack->port, " --> addr:%x l3id:%x b_stid:%x layer_id:%x\n", bc->addr, bc->l3_id, bc->b_stid, bc->layer_id); cb_log(4, stack->port, " --> facility in:%s out:%s\n", fac2str(bc->fac_in.Function), fac2str(bc->fac_out.Function)); cb_log(5, stack->port, " --> urate:%d rate:%d mode:%d user1:%d\n", bc->urate, bc->rate, bc->mode,bc->user1); cb_log(5, stack->port, " --> bc:%p h:%d sh:%d\n", bc, bc->holded, bc->stack_holder); } #define RETURN(a,b) {retval=a; goto b;} static void misdn_send_lock(struct misdn_bchannel *bc) { //cb_log(0,bc->port,"Locking bc->pid:%d\n", bc->pid); if (bc->send_lock) pthread_mutex_lock(&bc->send_lock->lock); } static void misdn_send_unlock(struct misdn_bchannel *bc) { //cb_log(0,bc->port,"UnLocking bc->pid:%d\n", bc->pid); if (bc->send_lock) pthread_mutex_unlock(&bc->send_lock->lock); } int misdn_lib_send_event(struct misdn_bchannel *bc, enum event_e event ) { msg_t *msg; struct misdn_bchannel *bc2; struct misdn_bchannel *held_bc; struct misdn_stack *stack; int retval = 0; int channel; int tmpcause; int tmp_out_cause; if (!bc) RETURN(-1,OUT_POST_UNLOCK); stack = get_stack_by_bc(bc); if (!stack) { cb_log(0,bc->port, "SENDEVENT: no Stack for event:%s caller:\"%s\" <%s> dialed:%s \n", isdn_get_info(msgs_g, event, 0), bc->caller.name, bc->caller.number, bc->dialed.number); RETURN(-1,OUT); } misdn_send_lock(bc); cb_log(6,stack->port,"SENDEVENT: stack->nt:%d stack->upperid:%x\n",stack->nt, stack->upper_id); if ( stack->nt && !stack->l1link) { misdn_lib_get_l1_up(stack); } cb_log(1, stack->port, "I SEND:%s caller:\"%s\" <%s> dialed:%s pid:%d\n", isdn_get_info(msgs_g, event, 0), bc->caller.name, bc->caller.number, bc->dialed.number, bc->pid); cb_log(4, stack->port, " --> bc_state:%s\n",bc_state2str(bc->bc_state)); misdn_lib_log_ies(bc); switch (event) { case EVENT_REGISTER: case EVENT_SETUP: if (create_process(glob_mgr->midev, bc) < 0) { cb_log(0, stack->port, " No free channel at the moment @ send_event\n"); RETURN(-ENOCHAN,OUT); } break; case EVENT_PROGRESS: case EVENT_ALERTING: case EVENT_PROCEEDING: case EVENT_SETUP_ACKNOWLEDGE: case EVENT_CONNECT: if (!stack->nt) { if (stack->ptp) { setup_bc(bc); } break; } /* fall through */ case EVENT_RETRIEVE_ACKNOWLEDGE: if (stack->nt) { if (bc->channel <=0 ) { /* else we have the channel already */ if (find_free_chan_in_stack(stack, bc, 0, 0)<0) { cb_log(0, stack->port, " No free channel at the moment\n"); /*FIXME: add disconnect*/ RETURN(-ENOCHAN,OUT); } } /* Its that i generate channels */ } retval=setup_bc(bc); if (retval == -EINVAL) { cb_log(0,bc->port,"send_event: setup_bc failed\n"); } if (misdn_cap_is_speech(bc->capability)) { if ((event==EVENT_CONNECT)||(event==EVENT_RETRIEVE_ACKNOWLEDGE)) { if ( *bc->crypt_key ) { cb_log(4, stack->port, " --> ENABLING BLOWFISH channel:%d caller%d:\"%s\" <%s> dialed%d:%s\n", bc->channel, bc->caller.number_type, bc->caller.name, bc->caller.number, bc->dialed.number_type, bc->dialed.number); manager_ph_control_block(bc, BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) ); } if (!bc->nodsp) manager_ph_control(bc, DTMF_TONE_START, 0); manager_ec_enable(bc); if (bc->txgain != 0) { cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain); manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain); } if ( bc->rxgain != 0 ) { cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain); manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain); } } } break; case EVENT_HOLD_ACKNOWLEDGE: held_bc = malloc(sizeof(struct misdn_bchannel)); if (!held_bc) { cb_log(0, bc->port, "Could not allocate held_bc!!!\n"); RETURN(-1,OUT); } /* backup the bc and put it in storage */ *held_bc = *bc; held_bc->holded = 1; held_bc->channel = 0;/* A held call does not have a channel anymore. */ held_bc->channel_preselected = 0; held_bc->channel_found = 0; bc_state_change(held_bc, BCHAN_CLEANED); stack_holder_add(stack, held_bc); /* kill the bridge and clean the real b-channel record */ if (stack->nt) { if (bc->bc_state == BCHAN_BRIDGED) { misdn_split_conf(bc,bc->conf_id); bc2 = find_bc_by_confid(bc->conf_id); if (!bc2) { cb_log(0,bc->port,"We have no second bc in bridge???\n"); } else { misdn_split_conf(bc2,bc->conf_id); } } channel = bc->channel; empty_bc(bc); clean_up_bc(bc); if (channel>0) empty_chan_in_stack(stack,channel); bc->in_use=0; } break; /* finishing the channel eh ? */ case EVENT_DISCONNECT: if (!bc->need_disconnect) { cb_log(0, bc->port, " --> we have already sent DISCONNECT\n"); RETURN(-1,OUT); } /* IE cause is mandatory for DISCONNECT, but optional for the answers to DISCONNECT. * We must initialize cause, so it is later correctly indicated to ast_channel * in case the answer does not include one! */ bc->cause = bc->out_cause; bc->need_disconnect=0; break; case EVENT_RELEASE: if (!bc->need_release) { cb_log(0, bc->port, " --> we have already sent RELEASE\n"); RETURN(-1,OUT); } bc->need_disconnect=0; bc->need_release=0; break; case EVENT_RELEASE_COMPLETE: if (!bc->need_release_complete) { cb_log(0, bc->port, " --> we have already sent RELEASE_COMPLETE\n"); RETURN(-1,OUT); } bc->need_disconnect=0; bc->need_release=0; bc->need_release_complete=0; if (!stack->nt) { /* create cleanup in TE */ channel = bc->channel; tmpcause = bc->cause; tmp_out_cause = bc->out_cause; empty_bc(bc); bc->cause=tmpcause; bc->out_cause=tmp_out_cause; clean_up_bc(bc); if (channel>0) empty_chan_in_stack(stack,channel); bc->in_use=0; } break; case EVENT_CONNECT_ACKNOWLEDGE: if ( bc->nt || misdn_cap_is_speech(bc->capability)) { int retval=setup_bc(bc); if (retval == -EINVAL){ cb_log(0,bc->port,"send_event: setup_bc failed\n"); } } if (misdn_cap_is_speech(bc->capability)) { if ( !bc->nodsp) manager_ph_control(bc, DTMF_TONE_START, 0); manager_ec_enable(bc); if ( bc->txgain != 0 ) { cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain); manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain); } if ( bc->rxgain != 0 ) { cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain); manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain); } } break; default: break; } /* Later we should think about sending bchannel data directly to misdn. */ msg = isdn_msg_build_event(msgs_g, bc, event, stack->nt); if (!msg) { /* * The message was not built. * * NOTE: The only time that the message will fail to build * is because the requested FACILITY message is not supported. * A failed malloc() results in exit() being called. */ RETURN(-1, OUT); } else { msg_queue_tail(&stack->downqueue, msg); sem_post(&glob_mgr->new_msg); } OUT: misdn_send_unlock(bc); OUT_POST_UNLOCK: return retval; } static int handle_err(msg_t *msg) { iframe_t *frm = (iframe_t*) msg->data; if (!frm->addr) { static int cnt=0; if (!cnt) cb_log(0,0,"mISDN Msg without Address pr:%x dinfo:%x\n",frm->prim,frm->dinfo); cnt++; if (cnt>100) { cb_log(0,0,"mISDN Msg without Address pr:%x dinfo:%x (already more than 100 of them)\n",frm->prim,frm->dinfo); cnt=0; } free_msg(msg); return 1; } switch (frm->prim) { case MGR_SETSTACK|INDICATION: return handle_bchan(msg); break; case MGR_SETSTACK|CONFIRM: case MGR_CLEARSTACK|CONFIRM: free_msg(msg) ; return 1; break; case DL_DATA|CONFIRM: cb_log(4,0,"DL_DATA|CONFIRM\n"); free_msg(msg); return 1; case PH_CONTROL|CONFIRM: cb_log(4,0,"PH_CONTROL|CONFIRM\n"); free_msg(msg); return 1; case DL_DATA|INDICATION: { int port=(frm->addr&MASTER_ID_MASK) >> 8; int channel=(frm->addr&CHILD_ID_MASK) >> 16; /*we flush the read buffer here*/ cb_log(9,0,"BCHAN DATA without BC: addr:%x port:%d channel:%d\n",frm->addr, port,channel); free_msg(msg); return 1; } } return 0; } int manager_isdn_handler(iframe_t *frm ,msg_t *msg) { if (frm->dinfo==0xffffffff && frm->prim==(PH_DATA|CONFIRM)) { cb_log(0,0,"SERIOUS BUG, dinfo == 0xffffffff, prim == PH_DATA | CONFIRM !!!!\n"); } /* Timer primitives must be handled first, because the frm->addr is a different * "address space" than the stack/instance address of other Lx primitives. */ if (handle_timers(msg)) { return 0; } if ( ((frm->addr | ISDN_PID_BCHANNEL_BIT )>> 28 ) == 0x5) { static int unhandled_bmsg_count=1000; if (handle_bchan(msg)) { return 0 ; } if (unhandled_bmsg_count==1000) { cb_log(0, 0, "received 1k Unhandled Bchannel Messages: prim %x len %d from addr %x, dinfo %x on this port.\n",frm->prim, frm->len, frm->addr, frm->dinfo); unhandled_bmsg_count=0; } unhandled_bmsg_count++; free_msg(msg); return 0; } #ifdef RECV_FRM_SYSLOG_DEBUG syslog(LOG_NOTICE,"mISDN recv: ADDR:%x PRIM:%x DINFO:%x\n", frm->addr, frm->prim, frm->dinfo); #endif if (handle_mgmt(msg)) return 0 ; if (handle_l2(msg)) return 0 ; /* Its important to handle l1 AFTER l2 */ if (handle_l1(msg)) return 0 ; if (handle_frm_nt(msg)) { return 0; } if (handle_frm_te(msg)) { return 0; } if (handle_err(msg)) { return 0 ; } cb_log(0, 0, "Unhandled Message: prim %x len %d from addr %x, dinfo %x on this port.\n",frm->prim, frm->len, frm->addr, frm->dinfo); free_msg(msg); return 0; } int misdn_lib_get_port_info(int port) { msg_t *msg=alloc_msg(MAX_MSG_SIZE); iframe_t *frm; struct misdn_stack *stack=find_stack_by_port(port); if (!msg) { cb_log(0, port, "misdn_lib_get_port_info: alloc_msg failed!\n"); return -1; } frm=(iframe_t*)msg->data; if (!stack ) { cb_log(0, port, "There is no Stack for this port.\n"); return -1; } /* activate bchannel */ frm->prim = CC_STATUS_ENQUIRY | REQUEST; frm->addr = stack->upper_id| FLG_MSG_DOWN; frm->dinfo = 0; frm->len = 0; msg_queue_tail(&glob_mgr->activatequeue, msg); sem_post(&glob_mgr->new_msg); return 0; } int misdn_lib_pid_restart(int pid) { struct misdn_bchannel *bc=manager_find_bc_by_pid(pid); if (bc) { manager_clean_bc(bc); } return 0; } /*Sends Restart message for every bchannel*/ int misdn_lib_send_restart(int port, int channel) { struct misdn_stack *stack=find_stack_by_port(port); struct misdn_bchannel dummybc; /*default is all channels*/ cb_log(0, port, "Sending Restarts on this port.\n"); misdn_make_dummy(&dummybc, stack->port, MISDN_ID_GLOBAL, stack->nt, 0); /*default is all channels*/ if (channel <0) { dummybc.channel=-1; cb_log(0, port, "Restarting and all Interfaces\n"); misdn_lib_send_event(&dummybc, EVENT_RESTART); return 0; } /*if a channel is specified we restart only this one*/ if (channel >0) { int cnt; dummybc.channel=channel; cb_log(0, port, "Restarting and cleaning channel %d\n",channel); misdn_lib_send_event(&dummybc, EVENT_RESTART); /* clean up chan in stack, to be sure we don't think it's * in use anymore */ for (cnt=0; cnt<=stack->b_num; cnt++) { if (stack->bc[cnt].in_use && stack->bc[cnt].channel == channel) { empty_bc(&stack->bc[cnt]); clean_up_bc(&stack->bc[cnt]); stack->bc[cnt].in_use=0; } } } return 0; } /*reinitializes the L2/L3*/ int misdn_lib_port_restart(int port) { struct misdn_stack *stack=find_stack_by_port(port); cb_log(0, port, "Restarting this port.\n"); if (stack) { cb_log(0, port, "Stack:%p\n",stack); clear_l3(stack); { msg_t *msg=alloc_msg(MAX_MSG_SIZE); iframe_t *frm; if (!msg) { cb_log(0, port, "port_restart: alloc_msg failed\n"); return -1; } frm=(iframe_t*)msg->data; /* we must activate if we are deactivated */ /* activate bchannel */ frm->prim = DL_RELEASE | REQUEST; frm->addr = stack->upper_id | FLG_MSG_DOWN; frm->dinfo = 0; frm->len = 0; msg_queue_tail(&glob_mgr->activatequeue, msg); sem_post(&glob_mgr->new_msg); } if (stack->nt) misdn_lib_reinit_nt_stack(stack->port); } return 0; } static sem_t handler_started; /* This is a thread */ static void manager_event_handler(void *arg) { sem_post(&handler_started); while (1) { struct misdn_stack *stack; msg_t *msg; /** wait for events **/ sem_wait(&glob_mgr->new_msg); for (msg=msg_dequeue(&glob_mgr->activatequeue); msg; msg=msg_dequeue(&glob_mgr->activatequeue) ) { iframe_t *frm = (iframe_t*) msg->data ; switch ( frm->prim) { case MGR_SETSTACK | REQUEST : free_msg(msg); break; default: mISDN_write(glob_mgr->midev, frm, mISDN_HEADER_LEN+frm->len, TIMEOUT_1SEC); free_msg(msg); } } for (stack=glob_mgr->stack_list; stack; stack=stack->next ) { /* Here we should check if we really want to send all the messages we've queued, lets assume we've queued a Disconnect, but received it already from the other side!*/ while ( (msg=msg_dequeue(&stack->downqueue)) ) { if (stack->nt ) { pthread_mutex_lock(&stack->nstlock); if (stack->nst.manager_l3(&stack->nst, msg)) cb_log(0, stack->port, "Error@ Sending Message in NT-Stack.\n"); pthread_mutex_unlock(&stack->nstlock); } else { iframe_t *frm = (iframe_t *)msg->data; struct misdn_bchannel *bc = find_bc_by_l3id(stack, frm->dinfo); if (bc) send_msg(glob_mgr->midev, bc, msg); else { struct misdn_bchannel dummybc; misdn_make_dummy(&dummybc, stack->port, frm->dinfo, stack->nt, 0); if (frm->dinfo == MISDN_ID_GLOBAL || frm->dinfo == MISDN_ID_DUMMY ) { cb_log(5,0," --> GLOBAL/DUMMY\n"); } else { /* * We need to be able to at least answer with RELEASE_COMPLETE * on SETUP|INDICATION errors so use a dummy bc. */ cb_log(0,0,"No bc for Message. Using dummy_bc\n"); } send_msg(glob_mgr->midev, &dummybc, msg); } } } } } } int misdn_lib_maxports_get(void) { /* BE AWARE WE HAVE NO cb_log() HERE! */ int i = mISDN_open(); int max=0; if (i<0) return -1; max = mISDN_get_stack_count(i); mISDN_close(i); return max; } void misdn_lib_nt_keepcalls( int kc) { #ifdef FEATURE_NET_KEEPCALLS if (kc) { struct misdn_stack *stack=get_misdn_stack(); for ( ; stack; stack=stack->next) { stack->nst.feature |= FEATURE_NET_KEEPCALLS; } } #endif } void misdn_lib_nt_debug_init( int flags, char *file ) { static int init=0; char *f; if (!flags) f=NULL; else f=file; if (!init) { debug_init( flags , f, f, f); init=1; } else { debug_close(); debug_init( flags , f, f, f); } } int misdn_lib_init(char *portlist, struct misdn_lib_iface *iface, void *user_data) { struct misdn_lib *mgr; char *tok, *tokb; char plist[1024]; int midev; int port_count=0; cb_log = iface->cb_log; cb_event = iface->cb_event; cb_jb_empty = iface->cb_jb_empty; if (!portlist || (*portlist == 0)) { return 1; } mgr = calloc(1, sizeof(*mgr)); if (!mgr) { return 1; } glob_mgr = mgr; msg_init(); misdn_lib_nt_debug_init(0,NULL); init_flip_bits(); strncpy(plist, portlist, 1024); plist[1023] = 0; memcpy(tone_425_flip,tone_425,TONE_425_SIZE); flip_buf_bits(tone_425_flip,TONE_425_SIZE); memcpy(tone_silence_flip,tone_SILENCE,TONE_SILENCE_SIZE); flip_buf_bits(tone_silence_flip,TONE_SILENCE_SIZE); midev=te_lib_init(); if (midev <= 0) { free(glob_mgr); glob_mgr = NULL; return 1; } mgr->midev=midev; port_count=mISDN_get_stack_count(midev); msg_queue_init(&mgr->activatequeue); if (sem_init(&mgr->new_msg, 1, 0)<0) sem_init(&mgr->new_msg, 0, 0); for (tok=strtok_r(plist," ,",&tokb ); tok; tok=strtok_r(NULL," ,",&tokb)) { int port = atoi(tok); struct misdn_stack *stack; struct misdn_stack *help; int ptp=0; int i; int r; if (strstr(tok, "ptp")) ptp=1; if (port > port_count) { cb_log(0, port, "Couldn't Initialize this port since we have only %d ports\n", port_count); continue; } stack = stack_init(midev, port, ptp); if (!stack) { cb_log(0, port, "stack_init() failed for this port\n"); continue; } /* Initialize the B channel records for real B channels. */ for (i = 0; i <= stack->b_num; i++) { r = init_bc(stack, &stack->bc[i], stack->midev, port, i); if (r < 0) { cb_log(0, port, "Got Err @ init_bc :%d\n", r); break; } } if (i <= stack->b_num) { stack_destroy(stack); free(stack); continue; } #if defined(AST_MISDN_ENHANCEMENTS) /* Initialize the B channel records for REGISTER signaling links. */ for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) { r = init_bc(stack, &stack->bc[i], stack->midev, port, i); if (r < 0) { cb_log(0, port, "Got Err @ init_bc :%d\n", r); break; } stack->bc[i].is_register_pool = 1; } if (i < ARRAY_LEN(stack->bc)) { stack_destroy(stack); free(stack); continue; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /* Add the new stack to the end of the list */ help = mgr->stack_list; if (!help) { mgr->stack_list = stack; } else { while (help->next) { help = help->next; } help->next = stack; } } if (!mgr->stack_list) { /* no stacks were successfully initialized !? */ te_lib_destroy(midev); free(glob_mgr); glob_mgr = NULL; return 1; } if (sem_init(&handler_started, 1, 0)<0) sem_init(&handler_started, 0, 0); cb_log(8, 0, "Starting Event Handler\n"); pthread_create( &mgr->event_handler_thread, NULL,(void*)manager_event_handler, mgr); sem_wait(&handler_started) ; cb_log(8, 0, "Starting Event Catcher\n"); pthread_create( &mgr->event_thread, NULL, (void*)misdn_lib_isdn_event_catcher, mgr); cb_log(8, 0, "Event Catcher started\n"); global_state= MISDN_INITIALIZED; return (mgr == NULL); } void misdn_lib_destroy(void) { struct misdn_stack *help; int i; for ( help=glob_mgr->stack_list; help; help=help->next ) { for(i=0;i<=help->b_num; i++) { char buf[1024]; mISDN_write_frame(help->midev, buf, help->bc[i].addr, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC); help->bc[i].addr = 0; } cb_log (1, help->port, "Destroying this port.\n"); stack_destroy(help); } if (global_state == MISDN_INITIALIZED) { cb_log(4, 0, "Killing Handler Thread\n"); if ( pthread_cancel(glob_mgr->event_handler_thread) == 0 ) { cb_log(4, 0, "Joining Handler Thread\n"); pthread_join(glob_mgr->event_handler_thread, NULL); } cb_log(4, 0, "Killing Main Thread\n"); if ( pthread_cancel(glob_mgr->event_thread) == 0 ) { cb_log(4, 0, "Joining Main Thread\n"); pthread_join(glob_mgr->event_thread, NULL); } } cb_log(1, 0, "Closing mISDN device\n"); te_lib_destroy(glob_mgr->midev); while ((help = glob_mgr->stack_list)) { glob_mgr->stack_list = help->next; free(help); } free(glob_mgr); glob_mgr = NULL; } char *manager_isdn_get_info(enum event_e event) { return isdn_get_info(msgs_g , event, 0); } void manager_bchannel_activate(struct misdn_bchannel *bc) { char buf[128]; struct misdn_stack *stack=get_stack_by_bc(bc); if (!stack) { cb_log(0, bc->port, "bchannel_activate: Stack not found !"); return ; } /* we must activate if we are deactivated */ clear_ibuffer(bc->astbuf); cb_log(5, stack->port, "$$$ Bchan Activated addr %x\n", bc->addr); mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_DOWN, DL_ESTABLISH | REQUEST, 0,0, NULL, TIMEOUT_1SEC); return ; } void manager_bchannel_deactivate(struct misdn_bchannel * bc) { struct misdn_stack *stack=get_stack_by_bc(bc); char buf[128]; switch (bc->bc_state) { case BCHAN_ACTIVATED: break; case BCHAN_BRIDGED: misdn_split_conf(bc,bc->conf_id); break; default: cb_log( 4, bc->port,"bchan_deactivate: called but not activated\n"); return ; } cb_log(5, stack->port, "$$$ Bchan deActivated addr %x\n", bc->addr); bc->generate_tone=0; mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_DOWN, DL_RELEASE|REQUEST,0,0,NULL, TIMEOUT_1SEC); clear_ibuffer(bc->astbuf); bc_state_change(bc,BCHAN_RELEASE); return; } int misdn_lib_tx2misdn_frm(struct misdn_bchannel *bc, void *data, int len) { struct misdn_stack *stack=get_stack_by_bc(bc); char buf[4096 + mISDN_HEADER_LEN]; iframe_t *frm = (iframe_t*)buf; switch (bc->bc_state) { case BCHAN_ACTIVATED: case BCHAN_BRIDGED: break; default: cb_log(3, bc->port, "BC not yet activated (state:%s)\n",bc_state2str(bc->bc_state)); return -1; } frm->prim = DL_DATA|REQUEST; frm->dinfo = 0; frm->addr = bc->addr | FLG_MSG_DOWN ; frm->len = len; memcpy(&buf[mISDN_HEADER_LEN], data,len); if ( misdn_cap_is_speech(bc->capability) ) flip_buf_bits( &buf[mISDN_HEADER_LEN], len); else cb_log(6, stack->port, "Writing %d data bytes\n",len); cb_log(9, stack->port, "Writing %d bytes 2 mISDN\n",len); mISDN_write(stack->midev, buf, frm->len + mISDN_HEADER_LEN, TIMEOUT_INFINIT); return 0; } /* * send control information to the channel (dsp-module) */ void manager_ph_control(struct misdn_bchannel *bc, int c1, int c2) { unsigned char buffer[mISDN_HEADER_LEN+2*sizeof(int)]; iframe_t *ctrl = (iframe_t *)buffer; /* preload data */ unsigned int *d = (unsigned int*)&ctrl->data.p; /*struct misdn_stack *stack=get_stack_by_bc(bc);*/ cb_log(4,bc->port,"ph_control: c1:%x c2:%x\n",c1,c2); ctrl->prim = PH_CONTROL | REQUEST; ctrl->addr = bc->addr | FLG_MSG_DOWN; ctrl->dinfo = 0; ctrl->len = sizeof(unsigned int)*2; *d++ = c1; *d++ = c2; mISDN_write(glob_mgr->midev, ctrl, mISDN_HEADER_LEN+ctrl->len, TIMEOUT_1SEC); } /* * allow live control of channel parameters */ void isdn_lib_update_rxgain (struct misdn_bchannel *bc) { manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain); } void isdn_lib_update_txgain (struct misdn_bchannel *bc) { manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain); } void isdn_lib_update_ec (struct misdn_bchannel *bc) { #ifdef MISDN_1_2 if (*bc->pipeline) #else if (bc->ec_enable) #endif manager_ec_enable(bc); else manager_ec_disable(bc); } void isdn_lib_stop_dtmf (struct misdn_bchannel *bc) { manager_ph_control(bc, DTMF_TONE_STOP, 0); } /* * send control information to the channel (dsp-module) */ void manager_ph_control_block(struct misdn_bchannel *bc, int c1, void *c2, int c2_len) { unsigned char buffer[mISDN_HEADER_LEN+sizeof(int)+c2_len]; iframe_t *ctrl = (iframe_t *)buffer; unsigned int *d = (unsigned int *)&ctrl->data.p; /*struct misdn_stack *stack=get_stack_by_bc(bc);*/ ctrl->prim = PH_CONTROL | REQUEST; ctrl->addr = bc->addr | FLG_MSG_DOWN; ctrl->dinfo = 0; ctrl->len = sizeof(unsigned int) + c2_len; *d++ = c1; memcpy(d, c2, c2_len); mISDN_write(glob_mgr->midev, ctrl, mISDN_HEADER_LEN+ctrl->len, TIMEOUT_1SEC); } void manager_clean_bc(struct misdn_bchannel *bc ) { struct misdn_stack *stack=get_stack_by_bc(bc); if (stack && bc->channel > 0) { empty_chan_in_stack(stack, bc->channel); } empty_bc(bc); bc->in_use=0; cb_event(EVENT_CLEANUP, bc, NULL); } void stack_holder_add(struct misdn_stack *stack, struct misdn_bchannel *holder) { struct misdn_bchannel *help; cb_log(4,stack->port, "*HOLDER: add %x\n",holder->l3_id); holder->stack_holder=1; holder->next=NULL; if (!stack->holding) { stack->holding = holder; return; } for (help=stack->holding; help; help=help->next) { if (!help->next) { help->next=holder; break; } } } void stack_holder_remove(struct misdn_stack *stack, struct misdn_bchannel *holder) { struct misdn_bchannel *h1; if (!holder->stack_holder) return; holder->stack_holder=0; cb_log(4,stack->port, "*HOLDER: remove %x\n",holder->l3_id); if (!stack || ! stack->holding) return; if (holder == stack->holding) { stack->holding = stack->holding->next; return; } for (h1=stack->holding; h1; h1=h1->next) { if (h1->next == holder) { h1->next=h1->next->next; return ; } } } struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id) { struct misdn_bchannel *help; cb_log(4, stack->port, "*HOLDER: find %lx\n",l3id); for (help=stack->holding; help; help=help->next) { if (help->l3_id == l3id) { cb_log(4,stack->port, "*HOLDER: found bc\n"); return help; } } cb_log(4,stack->port, "*HOLDER: find nothing\n"); return NULL; } /*! * \brief Find a held call's B channel record. * * \param port Port the call is on. * \param l3_id mISDN Layer 3 ID of held call. * * \return Found bc-record or NULL. */ struct misdn_bchannel *misdn_lib_find_held_bc(int port, int l3_id) { struct misdn_bchannel *bc; struct misdn_stack *stack; bc = NULL; for (stack = get_misdn_stack(); stack; stack = stack->next) { if (stack->port == port) { bc = stack_holder_find(stack, l3_id); break; } } return bc; } void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone) { char buf[mISDN_HEADER_LEN + 128] = ""; iframe_t *frm = (iframe_t*)buf; switch(tone) { case TONE_DIAL: manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_DIALTONE); break; case TONE_ALERTING: manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_RINGING); break; case TONE_HANGUP: manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_HANGUP); break; case TONE_NONE: default: manager_ph_control(bc, TONE_PATT_OFF, TONE_GERMAN_HANGUP); } frm->prim=DL_DATA|REQUEST; frm->addr=bc->addr|FLG_MSG_DOWN; frm->dinfo=0; frm->len=128; mISDN_write(glob_mgr->midev, frm, mISDN_HEADER_LEN+frm->len, TIMEOUT_1SEC); } void manager_ec_enable(struct misdn_bchannel *bc) { struct misdn_stack *stack=get_stack_by_bc(bc); cb_log(4, stack?stack->port:0,"ec_enable\n"); if (!misdn_cap_is_speech(bc->capability)) { cb_log(1, stack?stack->port:0, " --> no speech? cannot enable EC\n"); } else { #ifdef MISDN_1_2 if (*bc->pipeline) { cb_log(3, stack?stack->port:0,"Sending Control PIPELINE_CFG %s\n",bc->pipeline); manager_ph_control_block(bc, PIPELINE_CFG, bc->pipeline, strlen(bc->pipeline) + 1); } #else int ec_arr[2]; if (bc->ec_enable) { cb_log(3, stack?stack->port:0,"Sending Control ECHOCAN_ON taps:%d\n",bc->ec_deftaps); switch (bc->ec_deftaps) { case 4: case 8: case 16: case 32: case 64: case 128: case 256: case 512: case 1024: cb_log(4, stack->port, "Taps is %d\n",bc->ec_deftaps); break; default: cb_log(0, stack->port, "Taps should be power of 2\n"); bc->ec_deftaps=128; } ec_arr[0]=bc->ec_deftaps; ec_arr[1]=0; manager_ph_control_block(bc, ECHOCAN_ON, ec_arr, sizeof(ec_arr)); } #endif } } void manager_ec_disable(struct misdn_bchannel *bc) { struct misdn_stack *stack=get_stack_by_bc(bc); cb_log(4, stack?stack->port:0," --> ec_disable\n"); if (!misdn_cap_is_speech(bc->capability)) { cb_log(1, stack?stack->port:0, " --> no speech? cannot disable EC\n"); return; } #ifdef MISDN_1_2 manager_ph_control_block(bc, PIPELINE_CFG, "", 0); #else if ( ! bc->ec_enable) { cb_log(3, stack?stack->port:0, "Sending Control ECHOCAN_OFF\n"); manager_ph_control(bc, ECHOCAN_OFF, 0); } #endif } struct misdn_stack *get_misdn_stack(void) { return glob_mgr->stack_list; } void misdn_join_conf(struct misdn_bchannel *bc, int conf_id) { char data[16] = ""; bc_state_change(bc,BCHAN_BRIDGED); manager_ph_control(bc, CMX_RECEIVE_OFF, 0); manager_ph_control(bc, CMX_CONF_JOIN, conf_id); cb_log(3,bc->port, "Joining bc:%x in conf:%d\n",bc->addr,conf_id); misdn_lib_tx2misdn_frm(bc, data, sizeof(data) - 1); } void misdn_split_conf(struct misdn_bchannel *bc, int conf_id) { bc_state_change(bc,BCHAN_ACTIVATED); manager_ph_control(bc, CMX_RECEIVE_ON, 0); manager_ph_control(bc, CMX_CONF_SPLIT, conf_id); cb_log(4,bc->port, "Splitting bc:%x in conf:%d\n",bc->addr,conf_id); } void misdn_lib_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2) { int conf_id = bc1->pid + 1; struct misdn_bchannel *bc_list[] = { bc1, bc2, NULL }; struct misdn_bchannel **bc; cb_log(4, bc1->port, "I Send: BRIDGE from:%d to:%d\n",bc1->port,bc2->port); for (bc=bc_list; *bc; bc++) { (*bc)->conf_id=conf_id; cb_log(4, (*bc)->port, " --> bc_addr:%x\n",(*bc)->addr); switch((*bc)->bc_state) { case BCHAN_ACTIVATED: misdn_join_conf(*bc,conf_id); break; default: bc_next_state_change(*bc,BCHAN_BRIDGED); break; } } } void misdn_lib_split_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2) { struct misdn_bchannel *bc_list[]={ bc1,bc2,NULL }; struct misdn_bchannel **bc; for (bc=bc_list; *bc; bc++) { if ( (*bc)->bc_state == BCHAN_BRIDGED){ misdn_split_conf( *bc, (*bc)->conf_id); } else { cb_log( 2, (*bc)->port, "BC not bridged (state:%s) so not splitting it\n",bc_state2str((*bc)->bc_state)); } } } void misdn_lib_echo(struct misdn_bchannel *bc, int onoff) { cb_log(3,bc->port, " --> ECHO %s\n", onoff?"ON":"OFF"); manager_ph_control(bc, onoff?CMX_ECHO_ON:CMX_ECHO_OFF, 0); } void misdn_lib_reinit_nt_stack(int port) { struct misdn_stack *stack=find_stack_by_port(port); if (stack) { stack->l2link=0; stack->blocked=0; cleanup_Isdnl3(&stack->nst); cleanup_Isdnl2(&stack->nst); memset(&stack->nst, 0, sizeof(net_stack_t)); memset(&stack->mgr, 0, sizeof(manager_t)); stack->mgr.nst = &stack->nst; stack->nst.manager = &stack->mgr; stack->nst.l3_manager = handle_event_nt; stack->nst.device = glob_mgr->midev; stack->nst.cardnr = port; stack->nst.d_stid = stack->d_stid; stack->nst.feature = FEATURE_NET_HOLD; if (stack->ptp) stack->nst.feature |= FEATURE_NET_PTP; if (stack->pri) stack->nst.feature |= FEATURE_NET_CRLEN2 | FEATURE_NET_EXTCID; stack->nst.l1_id = stack->lower_id; /* never used */ stack->nst.l2_id = stack->upper_id; msg_queue_init(&stack->nst.down_queue); Isdnl2Init(&stack->nst); Isdnl3Init(&stack->nst); if (!stack->ptp) misdn_lib_get_l1_up(stack); } } asterisk-13.1.0/channels/misdn/Makefile0000644000000000000000000000041411162007226016460 0ustar rootroot# # Makefile for chan_misdn support # ifneq ($(wildcard /usr/include/linux/mISDNdsp.h),) CFLAGS+=-DMISDN_1_2 endif all: %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< portinfo: portinfo.o $(CC) -o $@ $^ -lisdnnet -lmISDN -lpthread clean: rm -rf *.a *.o *.so portinfo *.i asterisk-13.1.0/channels/misdn/isdn_msg_parser.c0000644000000000000000000014564212143270344020363 0ustar rootroot/* * Chan_Misdn -- Channel Driver for Asterisk * * Interface to mISDN * * Copyright (C) 2004, Christian Richter * * Christian Richter * * This program is free software, distributed under the terms of * the GNU General Public License */ /*! \file * \brief Interface to mISDN - message parser * \author Christian Richter */ /*** MODULEINFO extended ***/ #include "isdn_lib_intern.h" #include "isdn_lib.h" #include "ie.c" /*! * \internal * \brief Build the name, number, name/number display message string * * \param display Display buffer to fill in * \param display_length Length of the display buffer to fill in * \param display_format Display format enumeration * \param name Name string to use * \param number Number string to use * * \return Nothing */ static void build_display_str(char *display, size_t display_length, int display_format, const char *name, const char *number) { display[0] = 0; switch (display_format) { default: case 0: /* none */ break; case 1: /* name */ snprintf(display, display_length, "%s", name); break; case 2: /* number */ snprintf(display, display_length, "%s", number); break; case 3: /* both */ if (name[0] || number[0]) { snprintf(display, display_length, "\"%s\" <%s>", name, number); } break; } } /*! * \internal * \brief Encode the Facility IE and put it into the message structure. * * \param ntmode Where the encoded facility was put when in NT mode. * \param msg General message structure * \param fac Data to encode into the facility ie. * \param nt TRUE if in NT mode. * * \return Nothing */ static void enc_ie_facility(unsigned char **ntmode, msg_t *msg, struct FacParm *fac, int nt) { int len; Q931_info_t *qi; unsigned char *p; unsigned char buf[256]; len = encodeFac(buf, fac); if (len <= 0) { /* * mISDN does not know how to build the requested facility structure * Clear facility information */ fac->Function = Fac_None; return; } p = msg_put(msg, len); if (nt) { *ntmode = p + 1; } else { qi = (Q931_info_t *) (msg->data + mISDN_HEADER_LEN); qi->QI_ELEMENT(facility) = p - (unsigned char *) qi - sizeof(Q931_info_t); } memcpy(p, buf, len); /* Clear facility information */ fac->Function = Fac_None; } /*! * \internal * \brief Decode the Facility IE. * * \param p Encoded facility ie data to decode. (NT mode) * \param qi Encoded facility ie data to decode. (TE mode) * \param fac Where to put the decoded facility ie data if it is available. * \param nt TRUE if in NT mode. * \param bc Associated B channel * * \return Nothing */ static void dec_ie_facility(unsigned char *p, Q931_info_t *qi, struct FacParm *fac, int nt, struct misdn_bchannel *bc) { fac->Function = Fac_None; if (!nt) { p = NULL; if (qi->QI_ELEMENT(facility)) { p = (unsigned char *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(facility) + 1; } } if (!p) { return; } if (decodeFac(p, fac)) { cb_log(3, bc->port, "Decoding facility ie failed! Unrecognized facility message?\n"); } } static void set_channel(struct misdn_bchannel *bc, int channel) { cb_log(3,bc->port,"set_channel: bc->channel:%d channel:%d\n", bc->channel, channel); if (channel==0xff) { /* any channel */ channel=-1; } /* ALERT: is that everytime true ? */ if (channel > 0 && bc->nt ) { if (bc->channel && ( bc->channel != 0xff) ) { cb_log(0,bc->port,"We already have a channel (%d)\n", bc->channel); } else { bc->channel = channel; cb_event(EVENT_NEW_CHANNEL,bc,NULL); } } if (channel > 0 && !bc->nt ) { bc->channel = channel; cb_event(EVENT_NEW_CHANNEL,bc,NULL); } } static void parse_proceeding (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; CALL_PROCEEDING_t *proceeding = (CALL_PROCEEDING_t *) (msg->data + HEADER_LEN); //struct misdn_stack *stack=get_stack_by_bc(bc); { int exclusive, channel; dec_ie_channel_id(proceeding->CHANNEL_ID, (Q931_info_t *)proceeding, &exclusive, &channel, nt,bc); set_channel(bc,channel); } dec_ie_progress(proceeding->PROGRESS, (Q931_info_t *)proceeding, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); dec_ie_facility(proceeding->FACILITY, (Q931_info_t *) proceeding, &bc->fac_in, nt, bc); /* dec_ie_redir_dn */ #ifdef DEBUG printf("Parsing PROCEEDING Msg\n"); #endif } static msg_t *build_proceeding (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; CALL_PROCEEDING_t *proceeding; msg_t *msg =(msg_t*)create_l3msg(CC_PROCEEDING | REQUEST, MT_CALL_PROCEEDING, bc?bc->l3_id:-1, sizeof(CALL_PROCEEDING_t) ,nt); proceeding=(CALL_PROCEEDING_t*)((msg->data+HEADER_LEN)); enc_ie_channel_id(&proceeding->CHANNEL_ID, msg, 1,bc->channel, nt,bc); if (nt) enc_ie_progress(&proceeding->PROGRESS, msg, 0, nt?1:5, 8, nt,bc); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&proceeding->FACILITY, msg, &bc->fac_out, nt); } /* enc_ie_redir_dn */ #ifdef DEBUG printf("Building PROCEEDING Msg\n"); #endif return msg; } static void parse_alerting (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; ALERTING_t *alerting = (ALERTING_t *) (msg->data + HEADER_LEN); //Q931_info_t *qi=(Q931_info_t*)(msg->data+HEADER_LEN); dec_ie_facility(alerting->FACILITY, (Q931_info_t *) alerting, &bc->fac_in, nt, bc); /* dec_ie_redir_dn */ dec_ie_progress(alerting->PROGRESS, (Q931_info_t *)alerting, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); #ifdef DEBUG printf("Parsing ALERTING Msg\n"); #endif } static msg_t *build_alerting (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; ALERTING_t *alerting; msg_t *msg =(msg_t*)create_l3msg(CC_ALERTING | REQUEST, MT_ALERTING, bc?bc->l3_id:-1, sizeof(ALERTING_t) ,nt); alerting=(ALERTING_t*)((msg->data+HEADER_LEN)); enc_ie_channel_id(&alerting->CHANNEL_ID, msg, 1,bc->channel, nt,bc); if (nt) enc_ie_progress(&alerting->PROGRESS, msg, 0, nt?1:5, 8, nt,bc); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&alerting->FACILITY, msg, &bc->fac_out, nt); } /* enc_ie_redir_dn */ #ifdef DEBUG printf("Building ALERTING Msg\n"); #endif return msg; } static void parse_progress (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; PROGRESS_t *progress = (PROGRESS_t *) (msg->data + HEADER_LEN); //Q931_info_t *qi=(Q931_info_t*)(msg->data+HEADER_LEN); dec_ie_progress(progress->PROGRESS, (Q931_info_t *)progress, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); dec_ie_facility(progress->FACILITY, (Q931_info_t *) progress, &bc->fac_in, nt, bc); #ifdef DEBUG printf("Parsing PROGRESS Msg\n"); #endif } static msg_t *build_progress (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; PROGRESS_t *progress; msg_t *msg =(msg_t*)create_l3msg(CC_PROGRESS | REQUEST, MT_PROGRESS, bc?bc->l3_id:-1, sizeof(PROGRESS_t) ,nt); progress=(PROGRESS_t*)((msg->data+HEADER_LEN)); enc_ie_progress(&progress->PROGRESS, msg, 0, nt ? 1 : 5, 8, nt, bc); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&progress->FACILITY, msg, &bc->fac_out, nt); } #ifdef DEBUG printf("Building PROGRESS Msg\n"); #endif return msg; } #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Extract the SETUP message's BC, HLC, and LLC encoded ie contents. * * \param setup Indexed setup message contents * \param nt TRUE if in NT mode. * \param bc Associated B channel * * \return Nothing */ static void extract_setup_Bc_Hlc_Llc(SETUP_t *setup, int nt, struct misdn_bchannel *bc) { __u8 *p; Q931_info_t *qi; qi = (Q931_info_t *) setup; /* Extract Bearer Capability */ if (nt) { p = (__u8 *) setup->BEARER; } else { if (qi->QI_ELEMENT(bearer_capability)) { p = (__u8 *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(bearer_capability) + 1; } else { p = NULL; } } if (!p || *p == 0 || sizeof(bc->setup_bc_hlc_llc.Bc.Contents) < *p) { bc->setup_bc_hlc_llc.Bc.Length = 0; } else { bc->setup_bc_hlc_llc.Bc.Length = *p; memcpy(bc->setup_bc_hlc_llc.Bc.Contents, p + 1, *p); } /* Extract Low Layer Compatibility */ if (nt) { p = (__u8 *) setup->LLC; } else { if (qi->QI_ELEMENT(llc)) { p = (__u8 *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(llc) + 1; } else { p = NULL; } } if (!p || *p == 0 || sizeof(bc->setup_bc_hlc_llc.Llc.Contents) < *p) { bc->setup_bc_hlc_llc.Llc.Length = 0; } else { bc->setup_bc_hlc_llc.Llc.Length = *p; memcpy(bc->setup_bc_hlc_llc.Llc.Contents, p + 1, *p); } /* Extract High Layer Compatibility */ if (nt) { p = (__u8 *) setup->HLC; } else { if (qi->QI_ELEMENT(hlc)) { p = (__u8 *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(hlc) + 1; } else { p = NULL; } } if (!p || *p == 0 || sizeof(bc->setup_bc_hlc_llc.Hlc.Contents) < *p) { bc->setup_bc_hlc_llc.Hlc.Length = 0; } else { bc->setup_bc_hlc_llc.Hlc.Length = *p; memcpy(bc->setup_bc_hlc_llc.Hlc.Contents, p + 1, *p); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ static void parse_setup (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; SETUP_t *setup = (SETUP_t *) (msg->data + HEADER_LEN); Q931_info_t *qi = (Q931_info_t *) (msg->data + HEADER_LEN); int type; int plan; int present; int screen; int reason; #ifdef DEBUG printf("Parsing SETUP Msg\n"); #endif dec_ie_calling_pn(setup->CALLING_PN, qi, &type, &plan, &present, &screen, bc->caller.number, sizeof(bc->caller.number), nt, bc); bc->caller.number_type = type; bc->caller.number_plan = plan; switch (present) { default: case 0: bc->caller.presentation = 0; /* presentation allowed */ break; case 1: bc->caller.presentation = 1; /* presentation restricted */ break; case 2: bc->caller.presentation = 2; /* Number not available */ break; } if (0 <= screen) { bc->caller.screening = screen; } else { bc->caller.screening = 0; /* Unscreened */ } dec_ie_facility(setup->FACILITY, (Q931_info_t *) setup, &bc->fac_in, nt, bc); dec_ie_called_pn(setup->CALLED_PN, (Q931_info_t *) setup, &type, &plan, bc->dialed.number, sizeof(bc->dialed.number), nt, bc); bc->dialed.number_type = type; bc->dialed.number_plan = plan; dec_ie_keypad(setup->KEYPAD, (Q931_info_t *) setup, bc->keypad, sizeof(bc->keypad), nt, bc); dec_ie_complete(setup->COMPLETE, (Q931_info_t *) setup, &bc->sending_complete, nt, bc); dec_ie_redir_nr(setup->REDIR_NR, (Q931_info_t *) setup, &type, &plan, &present, &screen, &reason, bc->redirecting.from.number, sizeof(bc->redirecting.from.number), nt, bc); bc->redirecting.from.number_type = type; bc->redirecting.from.number_plan = plan; switch (present) { default: case 0: bc->redirecting.from.presentation = 0; /* presentation allowed */ break; case 1: bc->redirecting.from.presentation = 1; /* presentation restricted */ break; case 2: bc->redirecting.from.presentation = 2; /* Number not available */ break; } if (0 <= screen) { bc->redirecting.from.screening = screen; } else { bc->redirecting.from.screening = 0; /* Unscreened */ } if (0 <= reason) { bc->redirecting.reason = reason; } else { bc->redirecting.reason = mISDN_REDIRECTING_REASON_UNKNOWN; } { int coding, capability, mode, rate, multi, user, async, urate, stopbits, dbits, parity; dec_ie_bearer(setup->BEARER, (Q931_info_t *)setup, &coding, &capability, &mode, &rate, &multi, &user, &async, &urate, &stopbits, &dbits, &parity, nt,bc); switch (capability) { case -1: bc->capability=INFO_CAPABILITY_DIGITAL_UNRESTRICTED; break; case 0: bc->capability=INFO_CAPABILITY_SPEECH; break; case 18: bc->capability=INFO_CAPABILITY_VIDEO; break; case 8: bc->capability=INFO_CAPABILITY_DIGITAL_UNRESTRICTED; bc->user1 = user; bc->urate = urate; bc->rate = rate; bc->mode = mode; break; case 9: bc->capability=INFO_CAPABILITY_DIGITAL_RESTRICTED; break; default: break; } switch(user) { case 2: bc->law=INFO_CODEC_ULAW; break; case 3: bc->law=INFO_CODEC_ALAW; break; default: bc->law=INFO_CODEC_ALAW; } bc->capability=capability; } { int exclusive, channel; dec_ie_channel_id(setup->CHANNEL_ID, (Q931_info_t *)setup, &exclusive, &channel, nt,bc); set_channel(bc,channel); } { int protocol ; dec_ie_useruser(setup->USER_USER, (Q931_info_t *)setup, &protocol, bc->uu, &bc->uulen, nt,bc); if (bc->uulen) cb_log(1, bc->port, "USERUSERINFO:%s\n", bc->uu); else cb_log(1, bc->port, "NO USERUSERINFO\n"); } dec_ie_progress(setup->PROGRESS, (Q931_info_t *)setup, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); #if defined(AST_MISDN_ENHANCEMENTS) extract_setup_Bc_Hlc_Llc(setup, nt, bc); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } #define ANY_CHANNEL 0xff /* IE attribute for 'any channel' */ static msg_t *build_setup (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; SETUP_t *setup; msg_t *msg =(msg_t*)create_l3msg(CC_SETUP | REQUEST, MT_SETUP, bc?bc->l3_id:-1, sizeof(SETUP_t) ,nt); int is_ptp; enum FacFunction fac_type; setup=(SETUP_t*)((msg->data+HEADER_LEN)); if (bc->channel == 0 || bc->channel == ANY_CHANNEL || bc->channel==-1) enc_ie_channel_id(&setup->CHANNEL_ID, msg, 0, bc->channel, nt,bc); else enc_ie_channel_id(&setup->CHANNEL_ID, msg, 1, bc->channel, nt,bc); fac_type = bc->fac_out.Function; if (fac_type != Fac_None) { enc_ie_facility(&setup->FACILITY, msg, &bc->fac_out, nt); } enc_ie_calling_pn(&setup->CALLING_PN, msg, bc->caller.number_type, bc->caller.number_plan, bc->caller.presentation, bc->caller.screening, bc->caller.number, nt, bc); if (bc->dialed.number[0]) { enc_ie_called_pn(&setup->CALLED_PN, msg, bc->dialed.number_type, bc->dialed.number_plan, bc->dialed.number, nt, bc); } switch (bc->outgoing_colp) { case 0:/* pass */ case 1:/* restricted */ is_ptp = misdn_lib_is_ptp(bc->port); if (bc->redirecting.from.number[0] && ((!is_ptp && nt) || (is_ptp #if defined(AST_MISDN_ENHANCEMENTS) /* * There is no need to send out this ie when we are also sending * a Fac_DivertingLegInformation2 as well. The * Fac_DivertingLegInformation2 supercedes the information in * this ie. */ && fac_type != Fac_DivertingLegInformation2 #endif /* defined(AST_MISDN_ENHANCEMENTS) */ ))) { #if 1 /* ETSI and Q.952 do not define the screening field */ enc_ie_redir_nr(&setup->REDIR_NR, msg, bc->redirecting.from.number_type, bc->redirecting.from.number_plan, bc->redirecting.from.presentation, 0, bc->redirecting.reason, bc->redirecting.from.number, nt, bc); #else /* Q.931 defines the screening field */ enc_ie_redir_nr(&setup->REDIR_NR, msg, bc->redirecting.from.number_type, bc->redirecting.from.number_plan, bc->redirecting.from.presentation, bc->redirecting.from.screening, bc->redirecting.reason, bc->redirecting.from.number, nt, bc); #endif } break; default: break; } if (bc->keypad[0]) { enc_ie_keypad(&setup->KEYPAD, msg, bc->keypad, nt,bc); } if (*bc->display) { enc_ie_display(&setup->DISPLAY, msg, bc->display, nt, bc); } else if (nt && bc->caller.presentation == 0) { char display[sizeof(bc->display)]; /* Presentation is allowed */ build_display_str(display, sizeof(display), bc->display_setup, bc->caller.name, bc->caller.number); if (display[0]) { enc_ie_display(&setup->DISPLAY, msg, display, nt, bc); } } { int coding = 0; int capability; int mode = 0; /* 2 for packet! */ int user; int rate = 0x10; switch (bc->law) { case INFO_CODEC_ULAW: user=2; break; case INFO_CODEC_ALAW: user=3; break; default: user=3; } switch (bc->capability) { case INFO_CAPABILITY_SPEECH: capability = 0; break; case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: capability = 8; user=-1; mode=bc->mode; rate=bc->rate; break; case INFO_CAPABILITY_DIGITAL_RESTRICTED: capability = 9; user=-1; break; default: capability=bc->capability; } enc_ie_bearer(&setup->BEARER, msg, coding, capability, mode, rate, -1, user, nt,bc); } if (bc->sending_complete) { enc_ie_complete(&setup->COMPLETE,msg, bc->sending_complete, nt, bc); } if (bc->uulen) { int protocol=4; enc_ie_useruser(&setup->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc); cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu); } #if defined(AST_MISDN_ENHANCEMENTS) extract_setup_Bc_Hlc_Llc(setup, nt, bc); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #ifdef DEBUG printf("Building SETUP Msg\n"); #endif return msg; } static void parse_connect (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; CONNECT_t *connect = (CONNECT_t *) (msg->data + HEADER_LEN); int type; int plan; int pres; int screen; bc->ces = connect->ces; dec_ie_progress(connect->PROGRESS, (Q931_info_t *)connect, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); dec_ie_connected_pn(connect->CONNECT_PN, (Q931_info_t *) connect, &type, &plan, &pres, &screen, bc->connected.number, sizeof(bc->connected.number), nt, bc); bc->connected.number_type = type; bc->connected.number_plan = plan; switch (pres) { default: case 0: bc->connected.presentation = 0; /* presentation allowed */ break; case 1: bc->connected.presentation = 1; /* presentation restricted */ break; case 2: bc->connected.presentation = 2; /* Number not available */ break; } if (0 <= screen) { bc->connected.screening = screen; } else { bc->connected.screening = 0; /* Unscreened */ } dec_ie_facility(connect->FACILITY, (Q931_info_t *) connect, &bc->fac_in, nt, bc); /* cb_log(1,bc->port,"CONNETED PN: %s cpn_dialplan:%d\n", connected_pn, type); */ #ifdef DEBUG printf("Parsing CONNECT Msg\n"); #endif } static msg_t *build_connect (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; CONNECT_t *connect; msg_t *msg =(msg_t*)create_l3msg(CC_CONNECT | REQUEST, MT_CONNECT, bc?bc->l3_id:-1, sizeof(CONNECT_t) ,nt); cb_log(6,bc->port,"BUILD_CONNECT: bc:%p bc->l3id:%d, nt:%d\n",bc,bc->l3_id,nt); connect=(CONNECT_t*)((msg->data+HEADER_LEN)); if (nt) { time_t now; time(&now); enc_ie_date(&connect->DATE, msg, now, nt,bc); } switch (bc->outgoing_colp) { case 0:/* pass */ case 1:/* restricted */ enc_ie_connected_pn(&connect->CONNECT_PN, msg, bc->connected.number_type, bc->connected.number_plan, bc->connected.presentation, bc->connected.screening, bc->connected.number, nt, bc); break; default: break; } if (nt && bc->connected.presentation == 0) { char display[sizeof(bc->display)]; /* Presentation is allowed */ build_display_str(display, sizeof(display), bc->display_connected, bc->connected.name, bc->connected.number); if (display[0]) { enc_ie_display(&connect->DISPLAY, msg, display, nt, bc); } } if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&connect->FACILITY, msg, &bc->fac_out, nt); } #ifdef DEBUG printf("Building CONNECT Msg\n"); #endif return msg; } static void parse_setup_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; SETUP_ACKNOWLEDGE_t *setup_acknowledge = (SETUP_ACKNOWLEDGE_t *) (msg->data + HEADER_LEN); { int exclusive, channel; dec_ie_channel_id(setup_acknowledge->CHANNEL_ID, (Q931_info_t *)setup_acknowledge, &exclusive, &channel, nt,bc); set_channel(bc, channel); } dec_ie_progress(setup_acknowledge->PROGRESS, (Q931_info_t *)setup_acknowledge, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); dec_ie_facility(setup_acknowledge->FACILITY, (Q931_info_t *) setup_acknowledge, &bc->fac_in, nt, bc); #ifdef DEBUG printf("Parsing SETUP_ACKNOWLEDGE Msg\n"); #endif } static msg_t *build_setup_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; SETUP_ACKNOWLEDGE_t *setup_acknowledge; msg_t *msg =(msg_t*)create_l3msg(CC_SETUP_ACKNOWLEDGE | REQUEST, MT_SETUP_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(SETUP_ACKNOWLEDGE_t) ,nt); setup_acknowledge=(SETUP_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN)); enc_ie_channel_id(&setup_acknowledge->CHANNEL_ID, msg, 1,bc->channel, nt,bc); if (nt) enc_ie_progress(&setup_acknowledge->PROGRESS, msg, 0, nt?1:5, 8, nt,bc); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&setup_acknowledge->FACILITY, msg, &bc->fac_out, nt); } #ifdef DEBUG printf("Building SETUP_ACKNOWLEDGE Msg\n"); #endif return msg; } static void parse_connect_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing CONNECT_ACKNOWLEDGE Msg\n"); #endif } static msg_t *build_connect_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; CONNECT_ACKNOWLEDGE_t *connect_acknowledge; msg_t *msg =(msg_t*)create_l3msg(CC_CONNECT | RESPONSE, MT_CONNECT, bc?bc->l3_id:-1, sizeof(CONNECT_ACKNOWLEDGE_t) ,nt); connect_acknowledge=(CONNECT_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN)); enc_ie_channel_id(&connect_acknowledge->CHANNEL_ID, msg, 1, bc->channel, nt,bc); #ifdef DEBUG printf("Building CONNECT_ACKNOWLEDGE Msg\n"); #endif return msg; } static void parse_user_information (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing USER_INFORMATION Msg\n"); #endif } static msg_t *build_user_information (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_USER_INFORMATION | REQUEST, MT_USER_INFORMATION, bc?bc->l3_id:-1, sizeof(USER_INFORMATION_t) ,nt); #ifdef DEBUG printf("Building USER_INFORMATION Msg\n"); #endif return msg; } static void parse_suspend_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing SUSPEND_REJECT Msg\n"); #endif } static msg_t *build_suspend_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND_REJECT | REQUEST, MT_SUSPEND_REJECT, bc?bc->l3_id:-1, sizeof(SUSPEND_REJECT_t) ,nt); #ifdef DEBUG printf("Building SUSPEND_REJECT Msg\n"); #endif return msg; } static void parse_resume_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing RESUME_REJECT Msg\n"); #endif } static msg_t *build_resume_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_RESUME_REJECT | REQUEST, MT_RESUME_REJECT, bc?bc->l3_id:-1, sizeof(RESUME_REJECT_t) ,nt); #ifdef DEBUG printf("Building RESUME_REJECT Msg\n"); #endif return msg; } static void parse_hold (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing HOLD Msg\n"); #endif } static msg_t *build_hold (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_HOLD | REQUEST, MT_HOLD, bc?bc->l3_id:-1, sizeof(HOLD_t) ,nt); #ifdef DEBUG printf("Building HOLD Msg\n"); #endif return msg; } static void parse_suspend (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing SUSPEND Msg\n"); #endif } static msg_t *build_suspend (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND | REQUEST, MT_SUSPEND, bc?bc->l3_id:-1, sizeof(SUSPEND_t) ,nt); #ifdef DEBUG printf("Building SUSPEND Msg\n"); #endif return msg; } static void parse_resume (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing RESUME Msg\n"); #endif } static msg_t *build_resume (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_RESUME | REQUEST, MT_RESUME, bc?bc->l3_id:-1, sizeof(RESUME_t) ,nt); #ifdef DEBUG printf("Building RESUME Msg\n"); #endif return msg; } static void parse_hold_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing HOLD_ACKNOWLEDGE Msg\n"); #endif } static msg_t *build_hold_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_HOLD_ACKNOWLEDGE | REQUEST, MT_HOLD_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(HOLD_ACKNOWLEDGE_t) ,nt); #ifdef DEBUG printf("Building HOLD_ACKNOWLEDGE Msg\n"); #endif return msg; } static void parse_suspend_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing SUSPEND_ACKNOWLEDGE Msg\n"); #endif } static msg_t *build_suspend_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND_ACKNOWLEDGE | REQUEST, MT_SUSPEND_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(SUSPEND_ACKNOWLEDGE_t) ,nt); #ifdef DEBUG printf("Building SUSPEND_ACKNOWLEDGE Msg\n"); #endif return msg; } static void parse_resume_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing RESUME_ACKNOWLEDGE Msg\n"); #endif } static msg_t *build_resume_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_RESUME_ACKNOWLEDGE | REQUEST, MT_RESUME_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(RESUME_ACKNOWLEDGE_t) ,nt); #ifdef DEBUG printf("Building RESUME_ACKNOWLEDGE Msg\n"); #endif return msg; } static void parse_hold_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing HOLD_REJECT Msg\n"); #endif } static msg_t *build_hold_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_HOLD_REJECT | REQUEST, MT_HOLD_REJECT, bc?bc->l3_id:-1, sizeof(HOLD_REJECT_t) ,nt); #ifdef DEBUG printf("Building HOLD_REJECT Msg\n"); #endif return msg; } static void parse_retrieve (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing RETRIEVE Msg\n"); #endif } static msg_t *build_retrieve (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE | REQUEST, MT_RETRIEVE, bc?bc->l3_id:-1, sizeof(RETRIEVE_t) ,nt); #ifdef DEBUG printf("Building RETRIEVE Msg\n"); #endif return msg; } static void parse_retrieve_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing RETRIEVE_ACKNOWLEDGE Msg\n"); #endif } static msg_t *build_retrieve_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RETRIEVE_ACKNOWLEDGE_t *retrieve_acknowledge; msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE_ACKNOWLEDGE | REQUEST, MT_RETRIEVE_ACKNOWLEDGE, bc?bc->l3_id:-1, sizeof(RETRIEVE_ACKNOWLEDGE_t) ,nt); retrieve_acknowledge=(RETRIEVE_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN)); enc_ie_channel_id(&retrieve_acknowledge->CHANNEL_ID, msg, 1, bc->channel, nt,bc); #ifdef DEBUG printf("Building RETRIEVE_ACKNOWLEDGE Msg\n"); #endif return msg; } static void parse_retrieve_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing RETRIEVE_REJECT Msg\n"); #endif } static msg_t *build_retrieve_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE_REJECT | REQUEST, MT_RETRIEVE_REJECT, bc?bc->l3_id:-1, sizeof(RETRIEVE_REJECT_t) ,nt); #ifdef DEBUG printf("Building RETRIEVE_REJECT Msg\n"); #endif return msg; } static void parse_disconnect (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; DISCONNECT_t *disconnect = (DISCONNECT_t *) (msg->data + HEADER_LEN); int location; int cause; dec_ie_cause(disconnect->CAUSE, (Q931_info_t *)(disconnect), &location, &cause, nt,bc); if (cause>0) bc->cause=cause; dec_ie_facility(disconnect->FACILITY, (Q931_info_t *) disconnect, &bc->fac_in, nt, bc); dec_ie_progress(disconnect->PROGRESS, (Q931_info_t *)disconnect, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc); #ifdef DEBUG printf("Parsing DISCONNECT Msg\n"); #endif } static msg_t *build_disconnect (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; DISCONNECT_t *disconnect; msg_t *msg =(msg_t*)create_l3msg(CC_DISCONNECT | REQUEST, MT_DISCONNECT, bc?bc->l3_id:-1, sizeof(DISCONNECT_t) ,nt); disconnect=(DISCONNECT_t*)((msg->data+HEADER_LEN)); enc_ie_cause(&disconnect->CAUSE, msg, (nt)?1:0, bc->out_cause,nt,bc); if (nt) { enc_ie_progress(&disconnect->PROGRESS, msg, 0, nt ? 1 : 5, 8, nt, bc); } if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&disconnect->FACILITY, msg, &bc->fac_out, nt); } if (bc->uulen) { int protocol=4; enc_ie_useruser(&disconnect->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc); cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu); } #ifdef DEBUG printf("Building DISCONNECT Msg\n"); #endif return msg; } static void parse_restart (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RESTART_t *restart = (RESTART_t *) (msg->data + HEADER_LEN); struct misdn_stack *stack=get_stack_by_bc(bc); #ifdef DEBUG printf("Parsing RESTART Msg\n"); #endif { int exclusive; dec_ie_channel_id(restart->CHANNEL_ID, (Q931_info_t *)restart, &exclusive, &bc->restart_channel, nt,bc); cb_log(3, stack->port, "CC_RESTART Request on channel:%d on this port.\n", bc->restart_channel); } } static msg_t *build_restart (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RESTART_t *restart; msg_t *msg =(msg_t*)create_l3msg(CC_RESTART | REQUEST, MT_RESTART, bc?bc->l3_id:-1, sizeof(RESTART_t) ,nt); restart=(RESTART_t*)((msg->data+HEADER_LEN)); #ifdef DEBUG printf("Building RESTART Msg\n"); #endif if (bc->channel > 0) { enc_ie_channel_id(&restart->CHANNEL_ID, msg, 1,bc->channel, nt,bc); enc_ie_restart_ind(&restart->RESTART_IND, msg, 0x80, nt, bc); } else { enc_ie_restart_ind(&restart->RESTART_IND, msg, 0x87, nt, bc); } cb_log(0,bc->port, "Restarting channel %d\n", bc->channel); return msg; } static void parse_release (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RELEASE_t *release = (RELEASE_t *) (msg->data + HEADER_LEN); int location; int cause; dec_ie_cause(release->CAUSE, (Q931_info_t *)(release), &location, &cause, nt,bc); if (cause>0) bc->cause=cause; dec_ie_facility(release->FACILITY, (Q931_info_t *) release, &bc->fac_in, nt, bc); #ifdef DEBUG printf("Parsing RELEASE Msg\n"); #endif } static msg_t *build_release (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RELEASE_t *release; msg_t *msg =(msg_t*)create_l3msg(CC_RELEASE | REQUEST, MT_RELEASE, bc?bc->l3_id:-1, sizeof(RELEASE_t) ,nt); release=(RELEASE_t*)((msg->data+HEADER_LEN)); if (bc->out_cause>= 0) enc_ie_cause(&release->CAUSE, msg, nt?1:0, bc->out_cause, nt,bc); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&release->FACILITY, msg, &bc->fac_out, nt); } if (bc->uulen) { int protocol=4; enc_ie_useruser(&release->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc); cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu); } #ifdef DEBUG printf("Building RELEASE Msg\n"); #endif return msg; } static void parse_release_complete (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RELEASE_COMPLETE_t *release_complete = (RELEASE_COMPLETE_t *) (msg->data + HEADER_LEN); int location; int cause; iframe_t *frm = (iframe_t*) msg->data; struct misdn_stack *stack=get_stack_by_bc(bc); mISDNuser_head_t *hh; hh=(mISDNuser_head_t*)msg->data; /*hh=(mISDN_head_t*)msg->data; mISDN_head_t *hh;*/ if (nt) { if (hh->prim == (CC_RELEASE_COMPLETE|CONFIRM)) { cb_log(0, stack->port, "CC_RELEASE_COMPLETE|CONFIRM [NT] \n"); return; } } else { if (frm->prim == (CC_RELEASE_COMPLETE|CONFIRM)) { cb_log(0, stack->port, "CC_RELEASE_COMPLETE|CONFIRM [TE] \n"); return; } } dec_ie_cause(release_complete->CAUSE, (Q931_info_t *)(release_complete), &location, &cause, nt,bc); if (cause>0) bc->cause=cause; dec_ie_facility(release_complete->FACILITY, (Q931_info_t *) release_complete, &bc->fac_in, nt, bc); #ifdef DEBUG printf("Parsing RELEASE_COMPLETE Msg\n"); #endif } static msg_t *build_release_complete (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; RELEASE_COMPLETE_t *release_complete; msg_t *msg =(msg_t*)create_l3msg(CC_RELEASE_COMPLETE | REQUEST, MT_RELEASE_COMPLETE, bc?bc->l3_id:-1, sizeof(RELEASE_COMPLETE_t) ,nt); release_complete=(RELEASE_COMPLETE_t*)((msg->data+HEADER_LEN)); enc_ie_cause(&release_complete->CAUSE, msg, nt?1:0, bc->out_cause, nt,bc); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(&release_complete->FACILITY, msg, &bc->fac_out, nt); } if (bc->uulen) { int protocol=4; enc_ie_useruser(&release_complete->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc); cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu); } #ifdef DEBUG printf("Building RELEASE_COMPLETE Msg\n"); #endif return msg; } static void parse_facility (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN; FACILITY_t *facility = (FACILITY_t*)(msg->data+HEADER_LEN); Q931_info_t *qi = (Q931_info_t*)(msg->data+HEADER_LEN); unsigned char *p = NULL; #if defined(AST_MISDN_ENHANCEMENTS) int description_code; int type; int plan; int present; char number[sizeof(bc->redirecting.to.number)]; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #ifdef DEBUG printf("Parsing FACILITY Msg\n"); #endif bc->fac_in.Function = Fac_None; if (!bc->nt) { if (qi->QI_ELEMENT(facility)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(facility) + 1; } else { p = facility->FACILITY; } if (!p) return; if (decodeFac(p, &bc->fac_in)) { cb_log(3, bc->port, "Decoding facility ie failed! Unrecognized facility message?\n"); } #if defined(AST_MISDN_ENHANCEMENTS) dec_ie_notify(facility->NOTIFY, qi, &description_code, nt, bc); if (description_code < 0) { bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; } else { bc->notify_description_code = description_code; } dec_ie_redir_dn(facility->REDIR_DN, qi, &type, &plan, &present, number, sizeof(number), nt, bc); if (0 <= type) { bc->redirecting.to_changed = 1; bc->redirecting.to.number_type = type; bc->redirecting.to.number_plan = plan; switch (present) { default: case 0: bc->redirecting.to.presentation = 0; /* presentation allowed */ break; case 1: bc->redirecting.to.presentation = 1; /* presentation restricted */ break; case 2: bc->redirecting.to.presentation = 2; /* Number not available */ break; } bc->redirecting.to.screening = 0; /* Unscreened */ strcpy(bc->redirecting.to.number, number); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } static msg_t *build_facility (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int len; int HEADER_LEN; unsigned char *ie_fac; unsigned char fac_tmp[256]; msg_t *msg; FACILITY_t *facility; Q931_info_t *qi; #ifdef DEBUG printf("Building FACILITY Msg\n"); #endif len = encodeFac(fac_tmp, &(bc->fac_out)); if (len <= 0) { /* * mISDN does not know how to build the requested facility structure * Clear facility information */ bc->fac_out.Function = Fac_None; #if defined(AST_MISDN_ENHANCEMENTS) /* Clear other one shot information. */ bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; bc->redirecting.to_changed = 0; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ return NULL; } msg = (msg_t *) create_l3msg(CC_FACILITY | REQUEST, MT_FACILITY, bc ? bc->l3_id : -1, sizeof(FACILITY_t), nt); HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN; facility = (FACILITY_t *) (msg->data + HEADER_LEN); ie_fac = msg_put(msg, len); if (bc->nt) { facility->FACILITY = ie_fac + 1; } else { qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); qi->QI_ELEMENT(facility) = ie_fac - (unsigned char *)qi - sizeof(Q931_info_t); } memcpy(ie_fac, fac_tmp, len); /* Clear facility information */ bc->fac_out.Function = Fac_None; if (*bc->display) { #ifdef DEBUG printf("Sending %s as Display\n", bc->display); #endif enc_ie_display(&facility->DISPLAY, msg, bc->display, nt,bc); } #if defined(AST_MISDN_ENHANCEMENTS) if (bc->notify_description_code != mISDN_NOTIFY_CODE_INVALID) { enc_ie_notify(&facility->NOTIFY, msg, bc->notify_description_code, nt, bc); bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; } if (bc->redirecting.to_changed) { bc->redirecting.to_changed = 0; switch (bc->outgoing_colp) { case 0:/* pass */ case 1:/* restricted */ enc_ie_redir_dn(&facility->REDIR_DN, msg, bc->redirecting.to.number_type, bc->redirecting.to.number_plan, bc->redirecting.to.presentation, bc->redirecting.to.number, nt, bc); break; default: break; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ return msg; } #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Parse a received REGISTER message * * \param msgs Search table entry that called us. * \param msg Received message contents * \param bc Associated B channel * \param nt TRUE if in NT mode. * * \return Nothing */ static void parse_register(struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN; REGISTER_t *reg; HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN; reg = (REGISTER_t *) (msg->data + HEADER_LEN); /* * A facility ie is optional. * The peer may just be establishing a connection to send * messages later. */ dec_ie_facility(reg->FACILITY, (Q931_info_t *) reg, &bc->fac_in, nt, bc); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Construct a REGISTER message * * \param msgs Search table entry that called us. * \param bc Associated B channel * \param nt TRUE if in NT mode. * * \return Allocated built message */ static msg_t *build_register(struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN; REGISTER_t *reg; msg_t *msg; msg = (msg_t *) create_l3msg(CC_REGISTER | REQUEST, MT_REGISTER, bc ? bc->l3_id : -1, sizeof(REGISTER_t), nt); HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN; reg = (REGISTER_t *) (msg->data + HEADER_LEN); if (bc->fac_out.Function != Fac_None) { enc_ie_facility(®->FACILITY, msg, &bc->fac_out, nt); } return msg; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ static void parse_notify (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN; NOTIFY_t *notify = (NOTIFY_t *) (msg->data + HEADER_LEN); int description_code; int type; int plan; int present; char number[sizeof(bc->redirecting.to.number)]; #ifdef DEBUG printf("Parsing NOTIFY Msg\n"); #endif dec_ie_notify(notify->NOTIFY, (Q931_info_t *) notify, &description_code, nt, bc); if (description_code < 0) { bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; } else { bc->notify_description_code = description_code; } dec_ie_redir_dn(notify->REDIR_DN, (Q931_info_t *) notify, &type, &plan, &present, number, sizeof(number), nt, bc); if (0 <= type) { bc->redirecting.to_changed = 1; bc->redirecting.to.number_type = type; bc->redirecting.to.number_plan = plan; switch (present) { default: case 0: bc->redirecting.to.presentation = 0; /* presentation allowed */ break; case 1: bc->redirecting.to.presentation = 1; /* presentation restricted */ break; case 2: bc->redirecting.to.presentation = 2; /* Number not available */ break; } bc->redirecting.to.screening = 0; /* Unscreened */ strcpy(bc->redirecting.to.number, number); } } static msg_t *build_notify (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; NOTIFY_t *notify; msg_t *msg =(msg_t*)create_l3msg(CC_NOTIFY | REQUEST, MT_NOTIFY, bc?bc->l3_id:-1, sizeof(NOTIFY_t) ,nt); #ifdef DEBUG printf("Building NOTIFY Msg\n"); #endif notify = (NOTIFY_t *) (msg->data + HEADER_LEN); enc_ie_notify(¬ify->NOTIFY, msg, bc->notify_description_code, nt, bc); bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; if (bc->redirecting.to_changed) { bc->redirecting.to_changed = 0; switch (bc->outgoing_colp) { case 0:/* pass */ case 1:/* restricted */ enc_ie_redir_dn(¬ify->REDIR_DN, msg, bc->redirecting.to.number_type, bc->redirecting.to.number_plan, bc->redirecting.to.presentation, bc->redirecting.to.number, nt, bc); break; default: break; } } return msg; } static void parse_status_enquiry (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing STATUS_ENQUIRY Msg\n"); #endif } static msg_t *build_status_enquiry (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_STATUS_ENQUIRY | REQUEST, MT_STATUS_ENQUIRY, bc?bc->l3_id:-1, sizeof(STATUS_ENQUIRY_t) ,nt); #ifdef DEBUG printf("Building STATUS_ENQUIRY Msg\n"); #endif return msg; } static void parse_information (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; INFORMATION_t *information = (INFORMATION_t *) (msg->data + HEADER_LEN); int type, plan; dec_ie_called_pn(information->CALLED_PN, (Q931_info_t *) information, &type, &plan, bc->info_dad, sizeof(bc->info_dad), nt, bc); dec_ie_keypad(information->KEYPAD, (Q931_info_t *) information, bc->keypad, sizeof(bc->keypad), nt, bc); #ifdef DEBUG printf("Parsing INFORMATION Msg\n"); #endif } static msg_t *build_information (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; INFORMATION_t *information; msg_t *msg =(msg_t*)create_l3msg(CC_INFORMATION | REQUEST, MT_INFORMATION, bc?bc->l3_id:-1, sizeof(INFORMATION_t) ,nt); information=(INFORMATION_t*)((msg->data+HEADER_LEN)); enc_ie_called_pn(&information->CALLED_PN, msg, 0, 1, bc->info_dad, nt,bc); { if (*bc->display) { #ifdef DEBUG printf("Sending %s as Display\n", bc->display); #endif enc_ie_display(&information->DISPLAY, msg, bc->display, nt,bc); } } #ifdef DEBUG printf("Building INFORMATION Msg\n"); #endif return msg; } static void parse_status (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN; STATUS_t *status = (STATUS_t *) (msg->data + HEADER_LEN); int location; int cause; dec_ie_cause(status->CAUSE, (Q931_info_t *)(status), &location, &cause, nt,bc); if (cause>0) bc->cause=cause; #ifdef DEBUG printf("Parsing STATUS Msg\n"); #endif } static msg_t *build_status (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_STATUS | REQUEST, MT_STATUS, bc?bc->l3_id:-1, sizeof(STATUS_t) ,nt); #ifdef DEBUG printf("Building STATUS Msg\n"); #endif return msg; } static void parse_timeout (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { #ifdef DEBUG printf("Parsing STATUS Msg\n"); #endif } static msg_t *build_timeout (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt) { msg_t *msg =(msg_t*)create_l3msg(CC_STATUS | REQUEST, MT_STATUS, bc?bc->l3_id:-1, sizeof(STATUS_t) ,nt); #ifdef DEBUG printf("Building STATUS Msg\n"); #endif return msg; } /************************************/ /** Msg Array **/ struct isdn_msg msgs_g[] = { /* *INDENT-OFF* */ /* misdn_msg, event, msg_parser, msg_builder, info */ { CC_PROCEEDING, EVENT_PROCEEDING, parse_proceeding, build_proceeding, "PROCEEDING" }, { CC_ALERTING, EVENT_ALERTING, parse_alerting, build_alerting, "ALERTING" }, { CC_PROGRESS, EVENT_PROGRESS, parse_progress, build_progress, "PROGRESS" }, { CC_SETUP, EVENT_SETUP, parse_setup, build_setup, "SETUP" }, #if defined(AST_MISDN_ENHANCEMENTS) { CC_REGISTER, EVENT_REGISTER, parse_register, build_register, "REGISTER" }, #endif /* defined(AST_MISDN_ENHANCEMENTS) */ { CC_CONNECT, EVENT_CONNECT, parse_connect, build_connect, "CONNECT" }, { CC_SETUP_ACKNOWLEDGE, EVENT_SETUP_ACKNOWLEDGE, parse_setup_acknowledge, build_setup_acknowledge, "SETUP_ACKNOWLEDGE" }, { CC_CONNECT_ACKNOWLEDGE, EVENT_CONNECT_ACKNOWLEDGE, parse_connect_acknowledge, build_connect_acknowledge, "CONNECT_ACKNOWLEDGE " }, { CC_USER_INFORMATION, EVENT_USER_INFORMATION, parse_user_information, build_user_information, "USER_INFORMATION" }, { CC_SUSPEND_REJECT, EVENT_SUSPEND_REJECT, parse_suspend_reject, build_suspend_reject, "SUSPEND_REJECT" }, { CC_RESUME_REJECT, EVENT_RESUME_REJECT, parse_resume_reject, build_resume_reject, "RESUME_REJECT" }, { CC_HOLD, EVENT_HOLD, parse_hold, build_hold, "HOLD" }, { CC_SUSPEND, EVENT_SUSPEND, parse_suspend, build_suspend, "SUSPEND" }, { CC_RESUME, EVENT_RESUME, parse_resume, build_resume, "RESUME" }, { CC_HOLD_ACKNOWLEDGE, EVENT_HOLD_ACKNOWLEDGE, parse_hold_acknowledge, build_hold_acknowledge, "HOLD_ACKNOWLEDGE" }, { CC_SUSPEND_ACKNOWLEDGE, EVENT_SUSPEND_ACKNOWLEDGE, parse_suspend_acknowledge, build_suspend_acknowledge, "SUSPEND_ACKNOWLEDGE" }, { CC_RESUME_ACKNOWLEDGE, EVENT_RESUME_ACKNOWLEDGE, parse_resume_acknowledge, build_resume_acknowledge, "RESUME_ACKNOWLEDGE" }, { CC_HOLD_REJECT, EVENT_HOLD_REJECT, parse_hold_reject, build_hold_reject, "HOLD_REJECT" }, { CC_RETRIEVE, EVENT_RETRIEVE, parse_retrieve, build_retrieve, "RETRIEVE" }, { CC_RETRIEVE_ACKNOWLEDGE, EVENT_RETRIEVE_ACKNOWLEDGE, parse_retrieve_acknowledge, build_retrieve_acknowledge, "RETRIEVE_ACKNOWLEDGE" }, { CC_RETRIEVE_REJECT, EVENT_RETRIEVE_REJECT, parse_retrieve_reject, build_retrieve_reject, "RETRIEVE_REJECT" }, { CC_DISCONNECT, EVENT_DISCONNECT, parse_disconnect, build_disconnect, "DISCONNECT" }, { CC_RESTART, EVENT_RESTART, parse_restart, build_restart, "RESTART" }, { CC_RELEASE, EVENT_RELEASE, parse_release, build_release, "RELEASE" }, { CC_RELEASE_COMPLETE, EVENT_RELEASE_COMPLETE, parse_release_complete, build_release_complete, "RELEASE_COMPLETE" }, { CC_FACILITY, EVENT_FACILITY, parse_facility, build_facility, "FACILITY" }, { CC_NOTIFY, EVENT_NOTIFY, parse_notify, build_notify, "NOTIFY" }, { CC_STATUS_ENQUIRY, EVENT_STATUS_ENQUIRY, parse_status_enquiry, build_status_enquiry, "STATUS_ENQUIRY" }, { CC_INFORMATION, EVENT_INFORMATION, parse_information, build_information, "INFORMATION" }, { CC_STATUS, EVENT_STATUS, parse_status, build_status, "STATUS" }, { CC_TIMEOUT, EVENT_TIMEOUT, parse_timeout, build_timeout, "TIMEOUT" }, { 0, 0, NULL, NULL, NULL } /* *INDENT-ON* */ }; #define msgs_max (sizeof(msgs_g)/sizeof(struct isdn_msg)) /** INTERFACE FCTS ***/ int isdn_msg_get_index(struct isdn_msg msgs[], msg_t *msg, int nt) { int i; if (nt){ mISDNuser_head_t *hh = (mISDNuser_head_t*)msg->data; for (i=0; i< msgs_max -1; i++) { if ( (hh->prim&COMMAND_MASK)==(msgs[i].misdn_msg&COMMAND_MASK)) return i; } } else { iframe_t *frm = (iframe_t*)msg->data; for (i=0; i< msgs_max -1; i++) if ( (frm->prim&COMMAND_MASK)==(msgs[i].misdn_msg&COMMAND_MASK)) return i; } return -1; } int isdn_msg_get_index_by_event(struct isdn_msg msgs[], enum event_e event, int nt) { int i; for (i=0; i< msgs_max; i++) if ( event == msgs[i].event) return i; cb_log(10,0, "get_index: event not found!\n"); return -1; } enum event_e isdn_msg_get_event(struct isdn_msg msgs[], msg_t *msg, int nt) { int i=isdn_msg_get_index(msgs, msg, nt); if(i>=0) return msgs[i].event; return EVENT_UNKNOWN; } char * isdn_msg_get_info(struct isdn_msg msgs[], msg_t *msg, int nt) { int i=isdn_msg_get_index(msgs, msg, nt); if(i>=0) return msgs[i].info; return NULL; } char EVENT_CLEAN_INFO[] = "CLEAN_UP"; char EVENT_DTMF_TONE_INFO[] = "DTMF_TONE"; char EVENT_NEW_L3ID_INFO[] = "NEW_L3ID"; char EVENT_NEW_BC_INFO[] = "NEW_BC"; char EVENT_PORT_ALARM_INFO[] = "ALARM"; char EVENT_NEW_CHANNEL_INFO[] = "NEW_CHANNEL"; char EVENT_BCHAN_DATA_INFO[] = "BCHAN_DATA"; char EVENT_BCHAN_ACTIVATED_INFO[] = "BCHAN_ACTIVATED"; char EVENT_TONE_GENERATE_INFO[] = "TONE_GENERATE"; char EVENT_BCHAN_ERROR_INFO[] = "BCHAN_ERROR"; char * isdn_get_info(struct isdn_msg msgs[], enum event_e event, int nt) { int i=isdn_msg_get_index_by_event(msgs, event, nt); if(i>=0) return msgs[i].info; if (event == EVENT_CLEANUP) return EVENT_CLEAN_INFO; if (event == EVENT_DTMF_TONE) return EVENT_DTMF_TONE_INFO; if (event == EVENT_NEW_L3ID) return EVENT_NEW_L3ID_INFO; if (event == EVENT_NEW_BC) return EVENT_NEW_BC_INFO; if (event == EVENT_NEW_CHANNEL) return EVENT_NEW_CHANNEL_INFO; if (event == EVENT_BCHAN_DATA) return EVENT_BCHAN_DATA_INFO; if (event == EVENT_BCHAN_ACTIVATED) return EVENT_BCHAN_ACTIVATED_INFO; if (event == EVENT_TONE_GENERATE) return EVENT_TONE_GENERATE_INFO; if (event == EVENT_PORT_ALARM) return EVENT_PORT_ALARM_INFO; if (event == EVENT_BCHAN_ERROR) return EVENT_BCHAN_ERROR_INFO; return NULL; } int isdn_msg_parse_event(struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt) { int i=isdn_msg_get_index(msgs, msg, nt); if(i<0) return -1; msgs[i].msg_parser(msgs, msg, bc, nt); return 0; } msg_t * isdn_msg_build_event(struct isdn_msg msgs[], struct misdn_bchannel *bc, enum event_e event, int nt) { int i=isdn_msg_get_index_by_event(msgs, event, nt); if(i<0) return NULL; return msgs[i].msg_builder(msgs, bc, nt); } asterisk-13.1.0/channels/misdn/isdn_lib.h0000644000000000000000000005612512045012442016763 0ustar rootroot/* * Chan_Misdn -- Channel Driver for Asterisk * * Interface to mISDN * * Copyright (C) 2004, Christian Richter * * Christian Richter * * This program is free software, distributed under the terms of * the GNU General Public License */ /*! \file * \brief Interface to mISDN * * \author Christian Richter */ #ifndef TE_LIB #define TE_LIB #include /** For initialization usage **/ /* typedef int ie_nothing_t ;*/ /** end of init usage **/ /* * uncomment the following to make chan_misdn create * record files in /tmp/misdn-{rx|tx}-PortChannel format * */ /*#define MISDN_SAVE_DATA*/ #ifdef WITH_BEROEC typedef int beroec_t; enum beroec_type { BEROEC_FULLBAND=0, BEROEC_SUBBAND, BEROEC_FASTSUBBAND }; void beroec_init(void); void beroec_exit(void); beroec_t *beroec_new(int tail, enum beroec_type type, int anti_howl, int tonedisable, int zerocoeff, int adapt, int nlp); void beroec_destroy(beroec_t *ec); int beroec_cancel_alaw_chunk(beroec_t *ec, char *send, char *receive, int len); int beroec_version(void); #endif enum tone_e { TONE_NONE=0, TONE_DIAL, TONE_ALERTING, TONE_FAR_ALERTING, TONE_BUSY, TONE_HANGUP, TONE_CUSTOM, TONE_FILE }; #define MAX_BCHANS 31 enum bchannel_state { BCHAN_CLEANED=0, BCHAN_EMPTY, BCHAN_ACTIVATED, BCHAN_BRIDGED, BCHAN_RELEASE, BCHAN_ERROR }; enum misdn_err_e { ENOCHAN=1 }; enum mISDN_NUMBER_PLAN { NUMPLAN_UNKNOWN = 0x0, NUMPLAN_ISDN = 0x1, /* ISDN/Telephony numbering plan E.164 */ NUMPLAN_DATA = 0x3, /* Data numbering plan X.121 */ NUMPLAN_TELEX = 0x4, /* Telex numbering plan F.69 */ NUMPLAN_NATIONAL = 0x8, NUMPLAN_PRIVATE = 0x9 }; enum mISDN_NUMBER_TYPE { NUMTYPE_UNKNOWN = 0x0, NUMTYPE_INTERNATIONAL = 0x1, NUMTYPE_NATIONAL = 0x2, NUMTYPE_NETWORK_SPECIFIC = 0x3, NUMTYPE_SUBSCRIBER = 0x4, NUMTYPE_ABBREVIATED = 0x5 }; enum event_response_e { RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE, RESPONSE_IGNORE_SETUP, RESPONSE_RELEASE_SETUP, RESPONSE_ERR, RESPONSE_OK }; enum event_e { EVENT_NOTHING, EVENT_TONE_GENERATE, EVENT_BCHAN_DATA, EVENT_BCHAN_ACTIVATED, EVENT_BCHAN_ERROR, EVENT_CLEANUP, EVENT_PROCEEDING, EVENT_PROGRESS, EVENT_SETUP, EVENT_REGISTER, EVENT_ALERTING, EVENT_CONNECT, EVENT_SETUP_ACKNOWLEDGE, EVENT_CONNECT_ACKNOWLEDGE , EVENT_USER_INFORMATION, EVENT_SUSPEND_REJECT, EVENT_RESUME_REJECT, EVENT_HOLD, EVENT_SUSPEND, EVENT_RESUME, EVENT_HOLD_ACKNOWLEDGE, EVENT_SUSPEND_ACKNOWLEDGE, EVENT_RESUME_ACKNOWLEDGE, EVENT_HOLD_REJECT, EVENT_RETRIEVE, EVENT_RETRIEVE_ACKNOWLEDGE, EVENT_RETRIEVE_REJECT, EVENT_DISCONNECT, EVENT_RESTART, EVENT_RELEASE, EVENT_RELEASE_COMPLETE, EVENT_FACILITY, EVENT_NOTIFY, EVENT_STATUS_ENQUIRY, EVENT_INFORMATION, EVENT_STATUS, EVENT_TIMEOUT, EVENT_DTMF_TONE, EVENT_NEW_L3ID, EVENT_NEW_BC, EVENT_PORT_ALARM, EVENT_NEW_CHANNEL, EVENT_UNKNOWN }; enum ie_name_e { IE_DUMMY, IE_LAST }; enum { /* bearer capability */ INFO_CAPABILITY_SPEECH=0, INFO_CAPABILITY_AUDIO_3_1K=0x10 , INFO_CAPABILITY_AUDIO_7K=0x11 , INFO_CAPABILITY_VIDEO =0x18, INFO_CAPABILITY_DIGITAL_UNRESTRICTED =0x8, INFO_CAPABILITY_DIGITAL_RESTRICTED =0x09, INFO_CAPABILITY_DIGITAL_UNRESTRICTED_TONES }; enum { /* progress indicators */ INFO_PI_CALL_NOT_E2E_ISDN =0x01, INFO_PI_CALLED_NOT_ISDN =0x02, INFO_PI_CALLER_NOT_ISDN =0x03, INFO_PI_CALLER_RETURNED_TO_ISDN =0x04, INFO_PI_INBAND_AVAILABLE =0x08, INFO_PI_DELAY_AT_INTERF =0x0a, INFO_PI_INTERWORKING_WITH_PUBLIC =0x10, INFO_PI_INTERWORKING_NO_RELEASE =0x11, INFO_PI_INTERWORKING_NO_RELEASE_PRE_ANSWER =0x12, INFO_PI_INTERWORKING_NO_RELEASE_POST_ANSWER =0x13 }; /*! * \brief Q.931 encoded redirecting reason */ enum mISDN_REDIRECTING_REASON { mISDN_REDIRECTING_REASON_UNKNOWN = 0x0, /*! Call forwarding busy or called DTE busy */ mISDN_REDIRECTING_REASON_CALL_FWD_BUSY = 0x1, /*! Call forwarding no reply */ mISDN_REDIRECTING_REASON_NO_REPLY = 0x2, /*! Call deflection */ mISDN_REDIRECTING_REASON_DEFLECTION = 0x4, /*! Called DTE out of order */ mISDN_REDIRECTING_REASON_OUT_OF_ORDER = 0x9, /*! Call forwarding by the called DTE */ mISDN_REDIRECTING_REASON_CALL_FWD_DTE = 0xA, /*! Call forwarding unconditional or systematic call redirection */ mISDN_REDIRECTING_REASON_CALL_FWD = 0xF }; /*! * \brief Notification description code enumeration */ enum mISDN_NOTIFY_CODE { mISDN_NOTIFY_CODE_INVALID = -1, /*! Call is placed on hold (Q.931) */ mISDN_NOTIFY_CODE_USER_SUSPEND = 0x00, /*! Call is taken off of hold (Q.931) */ mISDN_NOTIFY_CODE_USER_RESUME = 0x01, /*! Call is diverting (EN 300 207-1 Section 7.2.1) */ mISDN_NOTIFY_CODE_CALL_IS_DIVERTING = 0x7B, /*! Call diversion is enabled (cfu, cfb, cfnr) (EN 300 207-1 Section 7.2.1) */ mISDN_NOTIFY_CODE_DIVERSION_ACTIVATED = 0x68, /*! Call transfer, alerting (EN 300 369-1 Section 7.2) */ mISDN_NOTIFY_CODE_CALL_TRANSFER_ALERTING = 0x69, /*! Call transfer, active(answered) (EN 300 369-1 Section 7.2) */ mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE = 0x6A, }; enum { /*CODECS*/ INFO_CODEC_ULAW=2, INFO_CODEC_ALAW=3 }; enum layer_e { L3, L2, L1, UNKNOWN }; /*! Maximum phone number (address) length plus null terminator */ #define MISDN_MAX_NUMBER_LEN (31 + 1) /*! Maximum name length plus null terminator (From ECMA-164) */ #define MISDN_MAX_NAME_LEN (50 + 1) /*! Maximum subaddress length plus null terminator */ #define MISDN_MAX_SUBADDRESS_LEN (23 + 1) /*! Maximum keypad facility content length plus null terminator */ #define MISDN_MAX_KEYPAD_LEN (31 + 1) /*! \brief Dialed/Called information struct */ struct misdn_party_dialing { /*! \brief Type-of-number in ISDN terms for the dialed/called number */ enum mISDN_NUMBER_TYPE number_type; /*! \brief Type-of-number numbering plan. */ enum mISDN_NUMBER_PLAN number_plan; /*! \brief Dialed/Called Phone Number (Address) */ char number[MISDN_MAX_NUMBER_LEN]; /*! \brief Dialed/Called Subaddress number */ char subaddress[MISDN_MAX_SUBADDRESS_LEN]; }; /*! \brief Connected-Line/Calling/Redirecting ID info struct */ struct misdn_party_id { /*! \brief Number presentation restriction code * 0=Allowed, 1=Restricted, 2=Unavailable */ int presentation; /*! \brief Number screening code * 0=Unscreened, 1=Passed Screen, 2=Failed Screen, 3=Network Number */ int screening; /*! \brief Type-of-number in ISDN terms for the number */ enum mISDN_NUMBER_TYPE number_type; /*! \brief Type-of-number numbering plan. */ enum mISDN_NUMBER_PLAN number_plan; /*! \brief Subscriber Name * \note The name is currently obtained from Asterisk for * potential use in display ie's since basic ISDN does * not support names directly. */ char name[MISDN_MAX_NAME_LEN]; /*! \brief Phone number (Address) */ char number[MISDN_MAX_NUMBER_LEN]; /*! \brief Subaddress number */ char subaddress[MISDN_MAX_SUBADDRESS_LEN]; }; /*! \brief Redirecting information struct */ struct misdn_party_redirecting { /*! \brief Who is redirecting the call (Sent to the party the call is redirected toward) */ struct misdn_party_id from; /*! \brief Where the call is being redirected toward (Sent to the calling party) */ struct misdn_party_id to; /*! \brief Reason a call is being redirected (Q.931 field value) */ enum mISDN_REDIRECTING_REASON reason; /*! \brief Number of times the call has been redirected */ int count; /*! \brief TRUE if the redirecting.to information has changed */ int to_changed; }; /*! \brief B channel control structure */ struct misdn_bchannel { /*! \brief B channel send locking structure */ struct send_lock *send_lock; #if defined(AST_MISDN_ENHANCEMENTS) /*! \brief The BC, HLC (optional) and LLC (optional) contents from the SETUP message. */ struct Q931_Bc_Hlc_Llc setup_bc_hlc_llc; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /*! * \brief Dialed/Called information struct * \note The number_type element is set to "dialplan" in /etc/asterisk/misdn.conf for outgoing calls */ struct misdn_party_dialing dialed; /*! \brief Originating/Caller ID information struct * \note The number_type element can be set to "localdialplan" in /etc/asterisk/misdn.conf for outgoing calls * \note The number element can be set to "callerid" in /etc/asterisk/misdn.conf for outgoing calls */ struct misdn_party_id caller; /*! \brief Incoming Caller ID string tag for special purpose * \note The element can be set to "incoming_cid_tag" in /etc/asterisk/misdn.conf for incoming calls */ char incoming_cid_tag[MISDN_MAX_NAME_LEN]; /*! \brief Connected-Party/Connected-Line ID information struct * \note The number_type element can be set to "cpndialplan" in /etc/asterisk/misdn.conf for outgoing calls */ struct misdn_party_id connected; /*! \brief Redirecting information struct (Where a call diversion or transfer was invoked) * \note The redirecting subaddress is not defined in Q.931 so it is not used. */ struct misdn_party_redirecting redirecting; /*! \brief TRUE if this is a dummy BC record */ int dummy; /*! \brief TRUE if NT side of protocol (TE otherwise) */ int nt; /*! \brief TRUE if ISDN-PRI (ISDN-BRI otherwise) */ int pri; /*! \brief Logical Layer 1 port associated with this B channel */ int port; /** init stuff **/ /*! \brief B Channel mISDN driver stack ID */ int b_stid; /* int b_addr; */ /*! \brief B Channel mISDN driver layer ID from mISDN_new_layer() */ int layer_id; /*! \brief B channel layer; set to 3 or 4 */ int layer; /* state stuff */ /*! \brief TRUE if DISCONNECT needs to be sent to clear a call */ int need_disconnect; /*! \brief TRUE if RELEASE needs to be sent to clear a call */ int need_release; /*! \brief TRUE if RELEASE_COMPLETE needs to be sent to clear a call */ int need_release_complete; /*! \brief TRUE if allocate higher B channels first */ int dec; /* var stuff */ /*! \brief Layer 3 process ID */ int l3_id; /*! \brief B channel process ID (1-5000) */ int pid; /*! \brief Not used. Saved mISDN stack CONNECT_t ces value */ int ces; /*! \brief B channel to restart if received a RESTART message */ int restart_channel; /*! \brief Assigned B channel number B1, B2... 0 if not assigned */ int channel; /*! \brief TRUE if the B channel number is preselected */ int channel_preselected; /*! \brief TRUE if the B channel is allocated from the REGISTER pool */ int is_register_pool; /*! \brief TRUE if B channel record is in use */ int in_use; /*! \brief Time when empty_bc() last called on this record */ struct timeval last_used; /*! \brief TRUE if call waiting */ int cw; /*! \brief B Channel mISDN driver layer ID from mISDN_get_layerid() */ int addr; /*! \brief B channel speech sample data buffer */ char *bframe; /*! \brief B channel speech sample data buffer size */ int bframe_len; int time_usec; /* Not used */ /*! \brief Not used. Contents are setup but not used. */ void *astbuf; /*! \brief TRUE if the TE side should choose the B channel to use * \note This value is user configurable in /etc/asterisk/misdn.conf */ int te_choose_channel; /*! \brief TRUE if the call progress indicators can indicate an inband audio message for the user to listen to * \note This value is user configurable in /etc/asterisk/misdn.conf */ int early_bconnect; /*! \brief Last decoded DTMF digit from mISDN driver */ int dtmf; /*! \brief TRUE if we should produce DTMF tones ourselves * \note This value is user configurable in /etc/asterisk/misdn.conf */ int send_dtmf; /*! \brief TRUE if we send SETUP_ACKNOWLEDGE on incoming calls anyway (instead of PROCEEDING). * * This requests additional INFORMATION messages, so we can * wait for digits without issues. * \note This value is user configurable in /etc/asterisk/misdn.conf */ int need_more_infos; /*! \brief TRUE if all digits necessary to complete the call are available. * No more INFORMATION messages are needed. */ int sending_complete; /*! \brief TRUE if we will not use jollys dsp */ int nodsp; /*! \brief TRUE if we will not use the jitter buffer system */ int nojitter; /*! \brief Progress Indicator IE coding standard field. * \note Collected from the incoming messages but not used. */ int progress_coding; /*! \brief Progress Indicator IE location field. * \note Collected from the incoming messages but not used. */ int progress_location; /*! \brief Progress Indicator IE progress description field. * Used to determine if there is an inband audio message present. */ int progress_indicator; #if defined(AST_MISDN_ENHANCEMENTS) /*! * \brief TRUE if waiting for DivertingLegInformation3 to queue redirecting update. */ int div_leg_3_rx_wanted; /*! * \brief TRUE if a DivertingLegInformation3 needs to be sent with CONNECT. */ int div_leg_3_tx_pending; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /*! \brief Inbound FACILITY message function type and contents */ struct FacParm fac_in; /*! \brief Outbound FACILITY message function type and contents. * \note Filled in by misdn facility commands before FACILITY message sent. */ struct FacParm fac_out; /* storing the current AOCD info here */ enum FacFunction AOCDtype; union { struct FacAOCDCurrency currency; struct FacAOCDChargingUnit chargingUnit; } AOCD; /*! \brief TRUE if AOCDtype and AOCD data are ready to export to Asterisk */ int AOCD_need_export; /*** CRYPTING STUFF ***/ int crypt; /* Initialized, Not used */ int curprx; /* Initialized, Not used */ int curptx; /* Initialized, Not used */ /*! \brief Blowfish encryption key string (secret) */ char crypt_key[255]; int crypt_state; /* Not used */ /*** CRYPTING STUFF END***/ /*! \brief Seems to have been intended for something to do with the jitter buffer. * \note Used as a boolean. Only initialized to 0 and referenced in a couple places */ int active; int upset; /* Not used */ /*! \brief TRUE if tone generator allowed to start */ int generate_tone; /*! \brief Number of tone samples to generate */ int tone_cnt; /*! \brief Current B Channel state */ enum bchannel_state bc_state; /*! \brief This is used as a pending bridge join request for when bc_state becomes BCHAN_ACTIVATED */ enum bchannel_state next_bc_state; /*! \brief Bridging conference ID */ int conf_id; /*! \brief TRUE if this channel is on hold */ int holded; /*! \brief TRUE if this channel is on the misdn_stack->holding list * \note If TRUE this implies that the structure is also malloced. */ int stack_holder; /*! * \brief Put a display ie in the CONNECT message * \details * Put a display ie in the CONNECT message containing the following * information if it is available (nt port only): * 0 - Do not put the connected line information in the display ie. * 1 - Put the available connected line name in the display ie. * 2 - Put the available connected line number in the display ie. * 3 - Put the available connected line name and number in the display ie. */ int display_connected; /*! * \brief Put a display ie in the SETUP message * \details * Put a display ie in the SETUP message containing the following * information if it is available (nt port only): * 0 - Do not put the caller information in the display ie. * 1 - Put the available caller name in the display ie. * 2 - Put the available caller number in the display ie. * 3 - Put the available caller name and number in the display ie. */ int display_setup; /*! * \brief Select what to do with outgoing COLP information. * \details * 0 - pass (Send out COLP information unaltered.) * 1 - restricted (Force COLP to restricted on all outgoing COLP information.) * 2 - block (Do not send COLP information.) */ int outgoing_colp; /*! \brief User set presentation restriction code * 0=Allowed, 1=Restricted, 2=Unavailable * \note It is settable by the misdn_set_opt() application. */ int presentation; /*! \brief TRUE if the user set the presentation restriction code */ int set_presentation; /*! \brief Notification indicator ie description code */ enum mISDN_NOTIFY_CODE notify_description_code; /*! \brief SETUP message bearer capability field code value */ int capability; /*! \brief Companding ALaw/uLaw encoding (INFO_CODEC_ALAW / INFO_CODEC_ULAW) */ int law; /* V110 Stuff */ /*! \brief Q.931 Bearer Capability IE Information Transfer Rate field. Initialized to 0x10 (64kbit). Altered by incoming SETUP messages. */ int rate; /*! \brief Q.931 Bearer Capability IE Transfer Mode field. Initialized to 0 (Circuit). Altered by incoming SETUP messages. */ int mode; /*! \brief Q.931 Bearer Capability IE User Information Layer 1 Protocol field code. * \note Collected from the incoming SETUP message but not used. */ int user1; /*! \brief Q.931 Bearer Capability IE Layer 1 User Rate field. * \note Collected from the incoming SETUP message and exported to Asterisk variable MISDN_URATE. */ int urate; /*! \brief TRUE if call made in digital HDLC mode * \note This value is user configurable in /etc/asterisk/misdn.conf. * It is also settable by the misdn_set_opt() application. */ int hdlc; /* V110 */ /*! \brief Display message that can be displayed by the user phone. * \note Maximum displayable length is 34 or 82 octets. * It is also settable by the misdn_set_opt() application. */ char display[84]; /*! \brief Q.931 Keypad Facility IE contents * \note Contents exported and imported to Asterisk variable MISDN_KEYPAD */ char keypad[MISDN_MAX_KEYPAD_LEN]; /*! \brief Current overlap dialing digits to/from INFORMATION messages */ char info_dad[MISDN_MAX_NUMBER_LEN]; /*! \brief Collected digits to go into info_dad[] while waiting for a SETUP_ACKNOWLEDGE to come in. */ char infos_pending[MISDN_MAX_NUMBER_LEN]; /* unsigned char info_keypad[MISDN_MAX_KEYPAD_LEN]; */ /* unsigned char clisub[24]; */ /* unsigned char cldsub[24]; */ /*! \brief User-User information string. * \note Contents exported and imported to Asterisk variable MISDN_USERUSER * \note We only support ASCII strings (IA5 characters). */ char uu[256]; /*! \brief User-User information string length in uu[] */ int uulen; /*! \brief Q.931 Cause for disconnection code (received) * \note Need to use the AST_CAUSE_xxx code definitions in causes.h */ int cause; /*! \brief Q.931 Cause for disconnection code (sent) * \note Need to use the AST_CAUSE_xxx code definitions in causes.h * \note -1 is used to suppress including the cause code in the RELEASE message. */ int out_cause; /* struct misdn_bchannel hold_bc; */ /** list stuf **/ #ifdef MISDN_1_2 /*! \brief The configuration string for the mISDN dsp pipeline in /etc/asterisk/misdn.conf. */ char pipeline[128]; #else /*! \brief TRUE if the echo cancellor is enabled */ int ec_enable; /*! \brief Number of taps in the echo cancellor when enabled. * \note This value is user configurable in /etc/asterisk/misdn.conf (echocancel) */ int ec_deftaps; #endif /*! \brief TRUE if the channel was allocated from the available B channels */ int channel_found; /*! \brief Who originated the call (ORG_AST, ORG_MISDN) * \note Set but not used when the misdn_set_opt() application enables echo cancellation. */ int orig; /*! \brief Tx gain setting (range -8 to 8) * \note This value is user configurable in /etc/asterisk/misdn.conf. * It is also settable by the misdn_set_opt() application. */ int txgain; /*! \brief Rx gain setting (range -8 to 8) * \note This value is user configurable in /etc/asterisk/misdn.conf. * It is also settable by the misdn_set_opt() application. */ int rxgain; /*! \brief Next node in the misdn_stack.holding list */ struct misdn_bchannel *next; }; extern enum event_response_e (*cb_event) (enum event_e event, struct misdn_bchannel *bc, void *user_data); extern void (*cb_log) (int level, int port, char *tmpl, ...) __attribute__ ((format (printf, 3, 4))); extern int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len); struct misdn_lib_iface { enum event_response_e (*cb_event)(enum event_e event, struct misdn_bchannel *bc, void *user_data); void (*cb_log)(int level, int port, char *tmpl, ...) __attribute__ ((format (printf, 3, 4))); int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len); }; /***** USER IFACE **********/ void misdn_lib_nt_keepcalls(int kc); void misdn_lib_nt_debug_init( int flags, char *file ); int misdn_lib_init(char *portlist, struct misdn_lib_iface* iface, void *user_data); int misdn_lib_send_event(struct misdn_bchannel *bc, enum event_e event ); void misdn_lib_destroy(void); void misdn_lib_isdn_l1watcher(int port); void misdn_lib_log_ies(struct misdn_bchannel *bc); char *manager_isdn_get_info(enum event_e event); struct misdn_bchannel* misdn_lib_get_free_bc(int port, int channel, int inout, int dec); #if defined(AST_MISDN_ENHANCEMENTS) struct misdn_bchannel *misdn_lib_get_register_bc(int port); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ void manager_bchannel_activate(struct misdn_bchannel *bc); void manager_bchannel_deactivate(struct misdn_bchannel * bc); int misdn_lib_tx2misdn_frm(struct misdn_bchannel *bc, void *data, int len); void manager_ph_control(struct misdn_bchannel *bc, int c1, int c2); void isdn_lib_update_rxgain (struct misdn_bchannel *bc); void isdn_lib_update_txgain (struct misdn_bchannel *bc); void isdn_lib_update_ec (struct misdn_bchannel *bc); void isdn_lib_stop_dtmf (struct misdn_bchannel *bc); int misdn_lib_port_restart(int port); int misdn_lib_pid_restart(int pid); int misdn_lib_send_restart(int port, int channel); int misdn_lib_get_port_info(int port); int misdn_lib_is_port_blocked(int port); int misdn_lib_port_block(int port); int misdn_lib_port_unblock(int port); int misdn_lib_port_is_pri(int port); int misdn_lib_port_is_nt(int port); int misdn_lib_port_up(int port, int notcheck); int misdn_lib_get_port_down(int port); int misdn_lib_get_port_up (int port) ; int misdn_lib_maxports_get(void) ; struct misdn_bchannel *misdn_lib_find_held_bc(int port, int l3_id); void misdn_lib_release(struct misdn_bchannel *bc); int misdn_cap_is_speech(int cap); int misdn_inband_avail(struct misdn_bchannel *bc); void manager_ec_enable(struct misdn_bchannel *bc); void manager_ec_disable(struct misdn_bchannel *bc); void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone); void get_show_stack_details(int port, char *buf); void misdn_lib_tone_generator_start(struct misdn_bchannel *bc); void misdn_lib_tone_generator_stop(struct misdn_bchannel *bc); void misdn_lib_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2); void misdn_lib_split_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2); void misdn_lib_echo(struct misdn_bchannel *bc, int onoff); int misdn_lib_is_ptp(int port); int misdn_lib_get_maxchans(int port); void misdn_lib_reinit_nt_stack(int port); #define PRI_TRANS_CAP_SPEECH 0x0 #define PRI_TRANS_CAP_DIGITAL 0x08 #define PRI_TRANS_CAP_RESTRICTED_DIGITAL 0x09 #define PRI_TRANS_CAP_3_1K_AUDIO 0x10 #define PRI_TRANS_CAP_7K_AUDIO 0x11 char *bc_state2str(enum bchannel_state state); void bc_state_change(struct misdn_bchannel *bc, enum bchannel_state state); void misdn_dump_chanlist(void); void misdn_make_dummy(struct misdn_bchannel *dummybc, int port, int l3id, int nt, int channel); #endif /* TE_LIB */ asterisk-13.1.0/channels/misdn/ie.c0000644000000000000000000010023111766660300015567 0ustar rootroot/* * Chan_Misdn -- Channel Driver for Asterisk * * Interface to mISDN * * Copyright (C) 2005, Christian Richter * * Christian Richter * * heaviliy patched from jollys ie.cpp, jolly gave me ALL * rights for this code, i can even have my own copyright on it. * * This program is free software, distributed under the terms of * the GNU General Public License */ /*! \file * \brief Interface to mISDN * \author Christian Richter */ /* the pointer of enc_ie_* always points to the IE itself if qi is not NULL (TE-mode), offset is set */ /*** MODULEINFO extended ***/ #include "asterisk.h" #include #include #include #include #include #include "asterisk/localtime.h" #define MISDN_IE_DEBG 0 /* support stuff */ static void strnncpy(char *dest, const char *src, size_t len, size_t dst_len) { if (len > dst_len-1) len = dst_len-1; strncpy(dest, src, len); dest[len] = '\0'; } /* IE_COMPLETE */ static void enc_ie_complete(unsigned char **ntmode, msg_t *msg, int complete, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); if (complete<0 || complete>1) { printf("%s: ERROR: complete(%d) is out of range.\n", __FUNCTION__, complete); return; } if (complete) if (MISDN_IE_DEBG) printf(" complete=%d\n", complete); if (complete) { p = msg_put(msg, 1); if (nt) { *ntmode = p; } else qi->QI_ELEMENT(sending_complete) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_COMPLETE; } } static void dec_ie_complete(unsigned char *p, Q931_info_t *qi, int *complete, int nt, struct misdn_bchannel *bc) { *complete = 0; if (!nt) { if (qi->QI_ELEMENT(sending_complete)) *complete = 1; } else if (p) *complete = 1; if (*complete) if (MISDN_IE_DEBG) printf(" complete=%d\n", *complete); } /* IE_BEARER */ static void enc_ie_bearer(unsigned char **ntmode, msg_t *msg, int coding, int capability, int mode, int rate, int multi, int user, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (coding<0 || coding>3) { printf("%s: ERROR: coding(%d) is out of range.\n", __FUNCTION__, coding); return; } if (capability<0 || capability>31) { printf("%s: ERROR: capability(%d) is out of range.\n", __FUNCTION__, capability); return; } if (mode<0 || mode>3) { printf("%s: ERROR: mode(%d) is out of range.\n", __FUNCTION__, mode); return; } if (rate<0 || rate>31) { printf("%s: ERROR: rate(%d) is out of range.\n", __FUNCTION__, rate); return; } if (multi>127) { printf("%s: ERROR: multi(%d) is out of range.\n", __FUNCTION__, multi); return; } if (user>31) { printf("%s: ERROR: user L1(%d) is out of range.\n", __FUNCTION__, rate); return; } if (rate!=24 && multi>=0) { printf("%s: WARNING: multi(%d) is only possible if rate(%d) would be 24.\n", __FUNCTION__, multi, rate); multi = -1; } if (MISDN_IE_DEBG) printf(" coding=%d capability=%d mode=%d rate=%d multi=%d user=%d\n", coding, capability, mode, rate, multi, user); l = 2 + (multi>=0) + (user>=0); p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(bearer_capability) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_BEARER; p[1] = l; p[2] = 0x80 + (coding<<5) + capability; p[3] = 0x80 + (mode<<5) + rate; if (multi >= 0) p[4] = 0x80 + multi; if (user >= 0) p[4+(multi>=0)] = 0xa0 + user; } static void dec_ie_bearer(unsigned char *p, Q931_info_t *qi, int *coding, int *capability, int *mode, int *rate, int *multi, int *user, int *async, int *urate, int *stopbits, int *dbits, int *parity, int nt, struct misdn_bchannel *bc) { int octet; *coding = -1; *capability = -1; *mode = -1; *rate = -1; *multi = -1; *user = -1; *async = -1; *urate = -1; *stopbits = -1; *dbits = -1; *parity = -1; if (!nt) { p = NULL; #ifdef LLC_SUPPORT if (qi->QI_ELEMENT(llc)) { p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(llc) + 1; } #endif if (qi->QI_ELEMENT(bearer_capability)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(bearer_capability) + 1; } if (!p) return; if (p[0] < 2) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *coding = (p[1]&0x60) >> 5; *capability = p[1] & 0x1f; octet = 2; if (!(p[1] & 0x80)) octet++; if (p[0] < octet) goto done; *mode = (p[octet]&0x60) >> 5; *rate = p[octet] & 0x1f; octet++; if (p[0] < octet) goto done; if (*rate == 0x18) { /* Rate multiplier only present if 64Kb/s base rate */ *multi = p[octet++] & 0x7f; } if (p[0] < octet) goto done; /* Start L1 info */ if ((p[octet] & 0x60) == 0x20) { *user = p[octet] & 0x1f; if (p[0] <= octet) goto done; if (p[octet++] & 0x80) goto l2; *async = !!(p[octet] & 0x40); /* 0x20 is inband negotiation */ *urate = p[octet] & 0x1f; if (p[0] <= octet) goto done; if (p[octet++] & 0x80) goto l2; /* Ignore next byte for now: Intermediate rate, NIC, flow control */ if (p[0] <= octet) goto done; if (p[octet++] & 0x80) goto l2; /* And the next one. Header, multiframe, mode, assignor/ee, negotiation */ if (p[0] <= octet) goto done; if (~p[octet++] & 0x80) goto l2; /* Wheee. V.110 speed information */ *stopbits = (p[octet] & 0x60) >> 5; *dbits = (p[octet] & 0x18) >> 3; *parity = p[octet] & 7; octet++; } l2: /* Nobody seems to want the rest so we don't bother (yet) */ done: if (MISDN_IE_DEBG) printf(" coding=%d capability=%d mode=%d rate=%d multi=%d user=%d async=%d urate=%d stopbits=%d dbits=%d parity=%d\n", *coding, *capability, *mode, *rate, *multi, *user, *async, *urate, *stopbits, *dbits, *parity); } /* IE_CALL_ID */ #if 0 static void enc_ie_call_id(unsigned char **ntmode, msg_t *msg, char *callid, int callid_len, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; char debug[25]; int i; if (!callid || callid_len<=0) { return; } if (callid_len>8) { printf("%s: ERROR: callid_len(%d) is out of range.\n", __FUNCTION__, callid_len); return; } i = 0; while(i < callid_len) { if (MISDN_IE_DEBG) printf(debug+(i*3), " %02x", callid[i]); i++; } if (MISDN_IE_DEBG) printf(" callid%s\n", debug); l = callid_len; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(call_id) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CALL_ID; p[1] = l; memcpy(p+2, callid, callid_len); } #endif #if 0 static void dec_ie_call_id(unsigned char *p, Q931_info_t *qi, char *callid, int *callid_len, int nt, struct misdn_bchannel *bc) { char debug[25]; int i; *callid_len = -1; if (!nt) { p = NULL; if (qi->QI_ELEMENT(call_id)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(call_id) + 1; } if (!p) return; if (p[0] > 8) { printf("%s: ERROR: IE too long (%d).\n", __FUNCTION__, p[0]); return; } *callid_len = p[0]; memcpy(callid, p+1, *callid_len); i = 0; while(i < *callid_len) { if (MISDN_IE_DEBG) printf(debug+(i*3), " %02x", callid[i]); i++; } if (MISDN_IE_DEBG) printf(" callid%s\n", debug); } #endif /* IE_CALLED_PN */ static void enc_ie_called_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, char *number, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (type<0 || type>7) { printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type); return; } if (plan<0 || plan>15) { printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan); return; } if (!number[0]) { printf("%s: ERROR: number is not given.\n", __FUNCTION__); return; } if (MISDN_IE_DEBG) printf(" type=%d plan=%d number='%s'\n", type, plan, number); l = 1+strlen((char *)number); p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(called_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CALLED_PN; p[1] = l; p[2] = 0x80 + (type<<4) + plan; strncpy((char *)p+3, (char *)number, strlen((char *)number)); } static void dec_ie_called_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, char *number, size_t number_len, int nt, struct misdn_bchannel *bc) { *type = -1; *plan = -1; *number = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(called_nr)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(called_nr) + 1; } if (!p) return; if (p[0] < 2) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *type = (p[1]&0x70) >> 4; *plan = p[1] & 0xf; strnncpy(number, (char *)p+2, p[0]-1, number_len); if (MISDN_IE_DEBG) printf(" type=%d plan=%d number='%s'\n", *type, *plan, number); } /* IE_CALLING_PN */ static void enc_ie_calling_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, char *number, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (type<0 || type>7) { printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type); return; } if (plan<0 || plan>15) { printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan); return; } if (present>3) { printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present); return; } if (present >= 0) if (screen<0 || screen>3) { printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen); return; } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", type, plan, present, screen, number); l = 1; if (number) if (number[0]) l += strlen((char *)number); if (present >= 0) l += 1; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(calling_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CALLING_PN; p[1] = l; if (present >= 0) { p[2] = 0x00 + (type<<4) + plan; p[3] = 0x80 + (present<<5) + screen; if (number) if (number[0]) strncpy((char *)p+4, (char *)number, strlen((char *)number)); } else { p[2] = 0x80 + (type<<4) + plan; if (number) if (number[0]) strncpy((char *)p+3, (char *)number, strlen((char *)number)); } } static void dec_ie_calling_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, char *number, size_t number_len, int nt, struct misdn_bchannel *bc) { *type = -1; *plan = -1; *present = -1; *screen = -1; *number = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(calling_nr)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(calling_nr) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *type = (p[1]&0x70) >> 4; *plan = p[1] & 0xf; if (!(p[1] & 0x80)) { if (p[0] < 2) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *present = (p[2]&0x60) >> 5; *screen = p[2] & 0x3; strnncpy(number, (char *)p+3, p[0]-2, number_len); } else { strnncpy(number, (char *)p+2, p[0]-1, number_len); /* SPECIAL workarround for IBT software bug */ /* if (number[0]==0x80) */ /* strcpy((char *)number, (char *)number+1); */ } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", *type, *plan, *present, *screen, number); } /* IE_CONNECTED_PN */ static void enc_ie_connected_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, char *number, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (type<0 || type>7) { printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type); return; } if (plan<0 || plan>15) { printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan); return; } if (present>3) { printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present); return; } if (present >= 0) if (screen<0 || screen>3) { printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen); return; } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", type, plan, present, screen, number); l = 1; if (number) if (number[0]) l += strlen((char *)number); if (present >= 0) l += 1; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(connected_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CONNECT_PN; p[1] = l; if (present >= 0) { p[2] = 0x00 + (type<<4) + plan; p[3] = 0x80 + (present<<5) + screen; if (number) if (number[0]) strncpy((char *)p+4, (char *)number, strlen((char *)number)); } else { p[2] = 0x80 + (type<<4) + plan; if (number) if (number[0]) strncpy((char *)p+3, (char *)number, strlen((char *)number)); } } static void dec_ie_connected_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, char *number, size_t number_len, int nt, struct misdn_bchannel *bc) { *type = -1; *plan = -1; *present = -1; *screen = -1; *number = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(connected_nr)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(connected_nr) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *type = (p[1]&0x70) >> 4; *plan = p[1] & 0xf; if (!(p[1] & 0x80)) { if (p[0] < 2) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *present = (p[2]&0x60) >> 5; *screen = p[2] & 0x3; strnncpy(number, (char *)p+3, p[0]-2, number_len); } else { strnncpy(number, (char *)p+2, p[0]-1, number_len); } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d number='%s'\n", *type, *plan, *present, *screen, number); } /* IE_CAUSE */ static void enc_ie_cause(unsigned char **ntmode, msg_t *msg, int location, int cause, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (location<0 || location>7) { printf("%s: ERROR: location(%d) is out of range.\n", __FUNCTION__, location); return; } if (cause<0 || cause>127) { printf("%s: ERROR: cause(%d) is out of range.\n", __FUNCTION__, cause); return; } if (MISDN_IE_DEBG) printf(" location=%d cause=%d\n", location, cause); l = 2; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(cause) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CAUSE; p[1] = l; p[2] = 0x80 + location; p[3] = 0x80 + cause; } #if 0 static void enc_ie_cause_standalone(unsigned char **ntmode, msg_t *msg, int location, int cause, int nt, struct misdn_bchannel *bc) { unsigned char *p = msg_put(msg, 4); Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); if (ntmode) *ntmode = p+1; else qi->QI_ELEMENT(cause) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CAUSE; p[1] = 2; p[2] = 0x80 + location; p[3] = 0x80 + cause; } #endif static void dec_ie_cause(unsigned char *p, Q931_info_t *qi, int *location, int *cause, int nt, struct misdn_bchannel *bc) { *location = -1; *cause = -1; if (!nt) { p = NULL; if (qi->QI_ELEMENT(cause)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(cause) + 1; } if (!p) return; if (p[0] < 2) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *location = p[1] & 0x0f; *cause = p[2] & 0x7f; if (MISDN_IE_DEBG) printf(" location=%d cause=%d\n", *location, *cause); } /* IE_CHANNEL_ID */ static void enc_ie_channel_id(unsigned char **ntmode, msg_t *msg, int exclusive, int channel, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; struct misdn_stack *stack=get_stack_by_bc(bc); int pri = stack->pri; if (exclusive<0 || exclusive>1) { printf("%s: ERROR: exclusive(%d) is out of range.\n", __FUNCTION__, exclusive); return; } if ((channel<0 || channel>0xff) || (!pri && (channel>2 && channel<0xff)) || (pri && (channel>31 && channel<0xff)) || (pri && channel==16)) { printf("%s: ERROR: channel(%d) is out of range.\n", __FUNCTION__, channel); return; } /* if (MISDN_IE_DEBG) printf(" exclusive=%d channel=%d\n", exclusive, channel); */ if (!pri) { /* BRI */ l = 1; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CHANNEL_ID; p[1] = l; if (channel == 0xff) channel = 3; p[2] = 0x80 + (exclusive<<3) + channel; /* printf(" exclusive=%d channel=%d\n", exclusive, channel); */ } else { /* PRI */ if (channel == 0) /* no channel */ return; /* IE not present */ /* if (MISDN_IE_DEBG) printf("channel = %d\n", channel); */ if (channel == 0xff) /* any channel */ { l = 1; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CHANNEL_ID; p[1] = l; p[2] = 0x80 + 0x20 + 0x03; /* if (MISDN_IE_DEBG) printf("%02x\n", p[2]); */ return; /* end */ } l = 3; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_CHANNEL_ID; p[1] = l; p[2] = 0x80 + 0x20 + (exclusive<<3) + 0x01; p[3] = 0x80 + 3; /* CCITT, Number, B-type */ p[4] = 0x80 + channel; /* if (MISDN_IE_DEBG) printf("%02x %02x %02x\n", p[2], p[3], p[4]); */ } } static void dec_ie_channel_id(unsigned char *p, Q931_info_t *qi, int *exclusive, int *channel, int nt, struct misdn_bchannel *bc) { struct misdn_stack *stack=get_stack_by_bc(bc); int pri =stack->pri; *exclusive = -1; *channel = -1; if (!nt) { p = NULL; if (qi->QI_ELEMENT(channel_id)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(channel_id) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } if (p[1] & 0x40) { printf("%s: ERROR: refering to channels of other interfaces is not supported.\n", __FUNCTION__); return; } if (p[1] & 0x04) { printf("%s: ERROR: using d-channel is not supported.\n", __FUNCTION__); return; } *exclusive = (p[1]&0x08) >> 3; if (!pri) { /* BRI */ if (p[1] & 0x20) { printf("%s: ERROR: extended channel ID with non PRI interface.\n", __FUNCTION__); return; } *channel = p[1] & 0x03; if (*channel == 3) *channel = 0xff; } else { /* PRI */ if (p[0] < 1) { printf("%s: ERROR: IE too short for PRI (%d).\n", __FUNCTION__, p[0]); return; } if (!(p[1] & 0x20)) { printf("%s: ERROR: basic channel ID with PRI interface.\n", __FUNCTION__); return; } if ((p[1]&0x03) == 0x00) { /* no channel */ *channel = 0; return; } if ((p[1]&0x03) == 0x03) { /* any channel */ *channel = 0xff; return; } if (p[0] < 3) { printf("%s: ERROR: IE too short for PRI with channel(%d).\n", __FUNCTION__, p[0]); return; } if (p[2] & 0x10) { printf("%s: ERROR: channel map not supported.\n", __FUNCTION__); return; } *channel = p[3] & 0x7f; if ( (*channel<1) | (*channel==16) | (*channel>31)) { printf("%s: ERROR: PRI interface channel out of range (%d).\n", __FUNCTION__, *channel); return; } /* if (MISDN_IE_DEBG) printf("%02x %02x %02x\n", p[1], p[2], p[3]); */ } if (MISDN_IE_DEBG) printf(" exclusive=%d channel=%d\n", *exclusive, *channel); } /* IE_DATE */ static void enc_ie_date(unsigned char **ntmode, msg_t *msg, time_t ti, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; struct timeval tv = { ti, 0 }; struct ast_tm tm; ast_localtime(&tv, &tm, NULL); if (MISDN_IE_DEBG) printf(" year=%d month=%d day=%d hour=%d minute=%d\n", tm.tm_year%100, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min); l = 5; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(date) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_DATE; p[1] = l; p[2] = tm.tm_year % 100; p[3] = tm.tm_mon + 1; p[4] = tm.tm_mday; p[5] = tm.tm_hour; p[6] = tm.tm_min; } /* IE_DISPLAY */ static void enc_ie_display(unsigned char **ntmode, msg_t *msg, char *display, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *) (msg->data + mISDN_HEADER_LEN); int l; if (!display[0]) { printf("%s: ERROR: display text not given.\n", __FUNCTION__); return; } l = strlen(display); if (80 < l) { l = 80; printf("%s: WARNING: display text too long (max %d chars), cutting.\n", __FUNCTION__, l); display[l] = '\0'; } /* if (MISDN_IE_DEBG) printf(" display='%s' (len=%d)\n", display, l); */ p = msg_put(msg, l + 2); if (nt) *ntmode = p + 1; else qi->QI_ELEMENT(display) = p - (unsigned char *) qi - sizeof(Q931_info_t); p[0] = IE_DISPLAY; p[1] = l; strncpy((char *) p + 2, display, l); } #if 0 static void dec_ie_display(unsigned char *p, Q931_info_t *qi, char *display, size_t display_len, int nt, struct misdn_bchannel *bc) { *display = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(display)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(display) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } strnncpy(display, (char *)p+1, p[0], display_len); if (MISDN_IE_DEBG) printf(" display='%s'\n", display); } #endif /* IE_KEYPAD */ #if 1 static void enc_ie_keypad(unsigned char **ntmode, msg_t *msg, char *keypad, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (!keypad[0]) { printf("%s: ERROR: keypad info not given.\n", __FUNCTION__); return; } if (MISDN_IE_DEBG) printf(" keypad='%s'\n", keypad); l = strlen(keypad); p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(keypad) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_KEYPAD; p[1] = l; strncpy((char *)p+2, keypad, strlen(keypad)); } #endif static void dec_ie_keypad(unsigned char *p, Q931_info_t *qi, char *keypad, size_t keypad_len, int nt, struct misdn_bchannel *bc) { *keypad = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(keypad)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(keypad) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } strnncpy(keypad, (char *)p+1, p[0], keypad_len); if (MISDN_IE_DEBG) printf(" keypad='%s'\n", keypad); } /* IE_NOTIFY */ static void enc_ie_notify(unsigned char **ntmode, msg_t *msg, int notify, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (notify<0 || notify>0x7f) { printf("%s: ERROR: notify(%d) is out of range.\n", __FUNCTION__, notify); return; } if (MISDN_IE_DEBG) printf(" notify=%d\n", notify); l = 1; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(notify) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_NOTIFY; p[1] = l; p[2] = 0x80 + notify; } static void dec_ie_notify(unsigned char *p, Q931_info_t *qi, int *notify, int nt, struct misdn_bchannel *bc) { *notify = -1; if (!nt) { p = NULL; if (qi->QI_ELEMENT(notify)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(notify) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *notify = p[1] & 0x7f; if (MISDN_IE_DEBG) printf(" notify=%d\n", *notify); } /* IE_PROGRESS */ static void enc_ie_progress(unsigned char **ntmode, msg_t *msg, int coding, int location, int progress, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (coding<0 || coding>0x03) { printf("%s: ERROR: coding(%d) is out of range.\n", __FUNCTION__, coding); return; } if (location<0 || location>0x0f) { printf("%s: ERROR: location(%d) is out of range.\n", __FUNCTION__, location); return; } if (progress<0 || progress>0x7f) { printf("%s: ERROR: progress(%d) is out of range.\n", __FUNCTION__, progress); return; } if (MISDN_IE_DEBG) printf(" coding=%d location=%d progress=%d\n", coding, location, progress); l = 2; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(progress) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_PROGRESS; p[1] = l; p[2] = 0x80 + (coding<<5) + location; p[3] = 0x80 + progress; } static void dec_ie_progress(unsigned char *p, Q931_info_t *qi, int *coding, int *location, int *progress, int nt, struct misdn_bchannel *bc) { *coding = -1; *location = -1; //*progress = -1; *progress = 0; if (!nt) { p = NULL; if (qi->QI_ELEMENT(progress)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(progress) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *coding = (p[1]&0x60) >> 5; *location = p[1] & 0x0f; *progress = p[2] & 0x7f; if (MISDN_IE_DEBG) printf(" coding=%d location=%d progress=%d\n", *coding, *location, *progress); } /* IE_REDIR_NR (redirecting = during MT_SETUP) */ static void enc_ie_redir_nr(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, int reason, char *number, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (type<0 || type>7) { printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type); return; } if (plan<0 || plan>15) { printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan); return; } if (present > 3) { printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present); return; } if (present >= 0) if (screen<0 || screen>3) { printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen); return; } if (reason > 0x0f) { printf("%s: ERROR: reason(%d) is out of range.\n", __FUNCTION__, reason); return; } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d readon=%d number='%s'\n", type, plan, present, screen, reason, number); l = 1; if (number) l += strlen((char *)number); if (present >= 0) { l += 1; if (reason >= 0) l += 1; } p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(redirect_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_REDIR_NR; p[1] = l; if (present >= 0) { if (reason >= 0) { p[2] = 0x00 + (type<<4) + plan; p[3] = 0x00 + (present<<5) + screen; p[4] = 0x80 + reason; if (number) strncpy((char *)p+5, (char *)number, strlen((char *)number)); } else { p[2] = 0x00 + (type<<4) + plan; p[3] = 0x80 + (present<<5) + screen; if (number) strncpy((char *)p+4, (char *)number, strlen((char *)number)); } } else { p[2] = 0x80 + (type<<4) + plan; if (number) if (number[0]) strncpy((char *)p+3, (char *)number, strlen((char *)number)); } } static void dec_ie_redir_nr(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, int *reason, char *number, size_t number_len, int nt, struct misdn_bchannel *bc) { *type = -1; *plan = -1; *present = -1; *screen = -1; *reason = -1; *number = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(redirect_nr)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(redirect_nr) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *type = (p[1]&0x70) >> 4; *plan = p[1] & 0xf; if (!(p[1] & 0x80)) { *present = (p[2]&0x60) >> 5; *screen = p[2] & 0x3; if (!(p[2] & 0x80)) { *reason = p[3] & 0x0f; strnncpy(number, (char *)p+4, p[0]-3, number_len); } else { strnncpy(number, (char *)p+3, p[0]-2, number_len); } } else { strnncpy(number, (char *)p+2, p[0]-1, number_len); } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d screen=%d reason=%d number='%s'\n", *type, *plan, *present, *screen, *reason, number); } /* IE_REDIR_DN (redirection = during MT_NOTIFY) */ static void enc_ie_redir_dn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, char *number, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (type<0 || type>7) { printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type); return; } if (plan<0 || plan>15) { printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan); return; } if (present > 3) { printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present); return; } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d number='%s'\n", type, plan, present, number); l = 1; if (number) l += strlen((char *)number); if (present >= 0) l += 1; p = msg_put(msg, l+2); if (nt) *ntmode = p+1; else { qi->QI_ELEMENT(redirect_dn) = p - (unsigned char *)qi - sizeof(Q931_info_t); } p[0] = IE_REDIR_DN; p[1] = l; if (present >= 0) { p[2] = 0x00 + (type<<4) + plan; p[3] = 0x80 + (present<<5); if (number) strncpy((char *)p+4, (char *)number, strlen((char *)number)); } else { p[2] = 0x80 + (type<<4) + plan; if (number) strncpy((char *)p+3, (char *)number, strlen((char *)number)); } } static void dec_ie_redir_dn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, char *number, size_t number_len, int nt, struct misdn_bchannel *bc) { *type = -1; *plan = -1; *present = -1; *number = '\0'; if (!nt) { p = NULL; if (qi->QI_ELEMENT(redirect_dn)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(redirect_dn) + 1; } if (!p) return; if (p[0] < 1) { printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]); return; } *type = (p[1]&0x70) >> 4; *plan = p[1] & 0xf; if (!(p[1] & 0x80)) { *present = (p[2]&0x60) >> 5; strnncpy(number, (char *)p+3, p[0]-2, number_len); } else { strnncpy(number, (char *)p+2, p[0]-1, number_len); } if (MISDN_IE_DEBG) printf(" type=%d plan=%d present=%d number='%s'\n", *type, *plan, *present, number); } /* IE_USERUSER */ #if 1 static void enc_ie_useruser(unsigned char **ntmode, msg_t *msg, int protocol, char *user, int user_len, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); int l; if (protocol<0 || protocol>127) { printf("%s: ERROR: protocol(%d) is out of range.\n", __FUNCTION__, protocol); return; } if (!user || user_len<=0) { return; } if (MISDN_IE_DEBG) { size_t i; char debug[768]; for (i = 0; i < user_len; ++i) { sprintf(debug + (i * 3), " %02x", user[i]); } debug[i * 3] = 0; printf(" protocol=%d user-user%s\n", protocol, debug); } l = user_len+1; p = msg_put(msg, l+3); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(useruser) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_USER_USER; p[1] = l; p[2] = protocol; memcpy(p+3, user, user_len); } #endif #if 1 static void dec_ie_useruser(unsigned char *p, Q931_info_t *qi, int *protocol, char *user, int *user_len, int nt, struct misdn_bchannel *bc) { *user_len = 0; *protocol = -1; if (!nt) { p = NULL; if (qi->QI_ELEMENT(useruser)) p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(useruser) + 1; } if (!p) return; *user_len = p[0]-1; if (p[0] < 1) return; *protocol = p[1]; memcpy(user, p+2, (*user_len<=128)?*(user_len):128); /* clip to 128 maximum */ if (MISDN_IE_DEBG) { int i; char debug[768]; for (i = 0; i < *user_len; ++i) { sprintf(debug + (i * 3), " %02x", user[i]); } debug[i * 3] = 0; printf(" protocol=%d user-user%s\n", *protocol, debug); } } #endif /* IE_DISPLAY */ static void enc_ie_restart_ind(unsigned char **ntmode, msg_t *msg, unsigned char rind, int nt, struct misdn_bchannel *bc) { unsigned char *p; Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN); /* if (MISDN_IE_DEBG) printf(" display='%s' (len=%d)\n", display, strlen((char *)display)); */ p = msg_put(msg, 3); if (nt) *ntmode = p+1; else qi->QI_ELEMENT(restart_ind) = p - (unsigned char *)qi - sizeof(Q931_info_t); p[0] = IE_RESTART_IND; p[1] = 1; p[2] = rind; } asterisk-13.1.0/channels/misdn/isdn_lib_intern.h0000644000000000000000000001020511261251313020330 0ustar rootroot#ifndef ISDN_LIB_INTERN #define ISDN_LIB_INTERN #include #include #include #include #include #include "isdn_lib.h" #ifndef MISDNUSER_VERSION_CODE #error "You need a newer version of mISDNuser ..." #elif MISDNUSER_VERSION_CODE < MISDNUSER_VERSION(1, 0, 3) #error "You need a newer version of mISDNuser ..." #endif #define QI_ELEMENT(a) a.off #ifndef mISDNUSER_HEAD_SIZE #define mISDNUSER_HEAD_SIZE (sizeof(mISDNuser_head_t)) /*#define mISDNUSER_HEAD_SIZE (sizeof(mISDN_head_t))*/ #endif #if 0 ibuffer_t *astbuf; /* Not used */ ibuffer_t *misdnbuf; /* Not used */ #endif struct send_lock { pthread_mutex_t lock; }; struct isdn_msg { unsigned long misdn_msg; enum event_e event; void (*msg_parser)(struct isdn_msg *msgs, msg_t *msg, struct misdn_bchannel *bc, int nt); msg_t *(*msg_builder)(struct isdn_msg *msgs, struct misdn_bchannel *bc, int nt); char *info; } ; /* for isdn_msg_parser.c */ msg_t *create_l3msg(int prim, int mt, int dinfo , int size, int nt); #if defined(AST_MISDN_ENHANCEMENTS) /* Max call-completion REGISTER signaling links per stack/port */ #define MISDN_MAX_REGISTER_LINKS MAX_BCHANS #else /* Max call-completion REGISTER signaling links per stack/port */ #define MISDN_MAX_REGISTER_LINKS 0 #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #define MAXPROCS 0x100 struct misdn_stack { /** is first element because &nst equals &mISDNlist **/ net_stack_t nst; manager_t mgr; pthread_mutex_t nstlock; /*! \brief Stack struct critical section lock. */ pthread_mutex_t st_lock; /*! \brief D Channel mISDN driver stack ID (Parent stack ID) */ int d_stid; /*! /brief Number of B channels supported by this port */ int b_num; /*! \brief B Channel mISDN driver stack IDs (Child stack IDs) */ int b_stids[MAX_BCHANS + 1]; /*! \brief TRUE if Point-To-Point(PTP) (Point-To-Multipoint(PTMP) otherwise) */ int ptp; /*! \brief Number of consecutive times PTP Layer 2 declared down */ int l2upcnt; int l2_id; /* Not used */ /*! \brief Lower layer mISDN ID (addr) (Layer 1/3) */ int lower_id; /*! \brief Upper layer mISDN ID (addr) (Layer 2/4) */ int upper_id; /*! \brief TRUE if port is blocked */ int blocked; /*! \brief TRUE if Layer 2 is UP */ int l2link; /*! \brief TRUE if Layer 1 is UP */ int l1link; /*! \brief TRUE if restart has been sent to the other side after stack startup */ int restart_sent; /*! \brief mISDN device handle returned by mISDN_open() */ int midev; /*! \brief TRUE if NT side of protocol (TE otherwise) */ int nt; /*! \brief TRUE if ISDN-PRI (ISDN-BRI otherwise) */ int pri; /*! \brief CR Process ID allocation table. TRUE if ID allocated */ int procids[MAXPROCS]; /*! \brief Queue of Event messages to send to mISDN */ msg_queue_t downqueue; msg_queue_t upqueue; /* No code puts anything on this queue */ int busy; /* Not used */ /*! \brief Logical Layer 1 port associated with this stack */ int port; /*! * \brief B Channel record pool array * (Must be dimensioned the same as struct misdn_stack.channels[]) */ struct misdn_bchannel bc[MAX_BCHANS + 1 + MISDN_MAX_REGISTER_LINKS]; /*! * \brief Array of B channels in use (a[0] = B1). TRUE if B channel in use. * (Must be dimensioned the same as struct misdn_stack.bc[]) */ char channels[MAX_BCHANS + 1 + MISDN_MAX_REGISTER_LINKS]; /*! \brief List of held channels */ struct misdn_bchannel *holding; /*! \brief Next stack in the list of stacks */ struct misdn_stack *next; }; struct misdn_stack* get_stack_by_bc(struct misdn_bchannel *bc); int isdn_msg_get_index(struct isdn_msg msgs[], msg_t *frm, int nt); enum event_e isdn_msg_get_event(struct isdn_msg msgs[], msg_t *frm, int nt); int isdn_msg_parse_event(struct isdn_msg msgs[], msg_t *frm, struct misdn_bchannel *bc, int nt); char * isdn_get_info(struct isdn_msg msgs[], enum event_e event, int nt); msg_t * isdn_msg_build_event(struct isdn_msg msgs[], struct misdn_bchannel *bc, enum event_e event, int nt); int isdn_msg_get_index_by_event(struct isdn_msg msgs[], enum event_e event, int nt); char * isdn_msg_get_info(struct isdn_msg msgs[], msg_t *msg, int nt); #endif asterisk-13.1.0/channels/misdn/portinfo.c0000644000000000000000000001070511766660300017040 0ustar rootroot/*! \file * \brief Interface to mISDN - port info * \author Christian Richter */ /*** MODULEINFO extended ***/ #include "isdn_lib.h" #include "isdn_lib_intern.h" /* * global function to show all available isdn ports */ void isdn_port_info(void) { int err; int i, ii, p; int useable, nt, pri; unsigned char buff[1025]; iframe_t *frm = (iframe_t *)buff; stack_info_t *stinf; int device; /* open mISDN */ if ((device = mISDN_open()) < 0) { fprintf(stderr, "mISDN_open() failed: ret=%d errno=%d (%s) Check for mISDN modules and device.\n", device, errno, strerror(errno)); exit(-1); } /* get number of stacks */ i = 1; ii = mISDN_get_stack_count(device); printf("\n"); if (ii <= 0) { printf("Found no card. Please be sure to load card drivers.\n"); } /* loop the number of cards and get their info */ while(i <= ii) { err = mISDN_get_stack_info(device, i, buff, sizeof(buff)); if (err <= 0) { fprintf(stderr, "mISDN_get_stack_info() failed: port=%d err=%d\n", i, err); break; } stinf = (stack_info_t *)&frm->data.p; nt = pri = 0; useable = 1; /* output the port info */ printf("Port %2d: ", i); switch(stinf->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK) { case ISDN_PID_L0_TE_S0: printf("TE-mode BRI S/T interface line (for phone lines)"); #if 0 if (stinf->pid.protocol[0] & ISDN_PID_L0_TE_S0_HFC & ISDN_PID_FEATURE_MASK) printf(" HFC multiport card"); #endif break; case ISDN_PID_L0_NT_S0: nt = 1; printf("NT-mode BRI S/T interface port (for phones)"); #if 0 if (stinf->pid.protocol[0] & ISDN_PID_L0_NT_S0_HFC & ISDN_PID_FEATURE_MASK) printf(" HFC multiport card"); #endif break; case ISDN_PID_L0_TE_U: printf("TE-mode BRI U interface line"); break; case ISDN_PID_L0_NT_U: nt = 1; printf("NT-mode BRI U interface port"); break; case ISDN_PID_L0_TE_UP2: printf("TE-mode BRI Up2 interface line"); break; case ISDN_PID_L0_NT_UP2: nt = 1; printf("NT-mode BRI Up2 interface port"); break; case ISDN_PID_L0_TE_E1: pri = 1; printf("TE-mode PRI E1 interface line (for phone lines)"); #if 0 if (stinf->pid.protocol[0] & ISDN_PID_L0_TE_E1_HFC & ISDN_PID_FEATURE_MASK) printf(" HFC-E1 card"); #endif break; case ISDN_PID_L0_NT_E1: nt = 1; pri = 1; printf("NT-mode PRI E1 interface port (for phones)"); #if 0 if (stinf->pid.protocol[0] & ISDN_PID_L0_NT_E1_HFC & ISDN_PID_FEATURE_MASK) printf(" HFC-E1 card"); #endif break; default: useable = 0; printf("unknown type 0x%08x",stinf->pid.protocol[0]); } printf("\n"); if (nt) { if (stinf->pid.protocol[1] == 0) { useable = 0; printf(" -> Missing layer 1 NT-mode protocol.\n"); } p = 2; while(p <= MAX_LAYER_NR) { if (stinf->pid.protocol[p]) { useable = 0; printf(" -> Layer %d protocol 0x%08x is detected, but not allowed for NT lib.\n", p, stinf->pid.protocol[p]); } p++; } if (useable) { if (pri) printf(" -> Interface is Point-To-Point (PRI).\n"); else printf(" -> Interface can be Poin-To-Point/Multipoint.\n"); } } else { if (stinf->pid.protocol[1] == 0) { useable = 0; printf(" -> Missing layer 1 protocol.\n"); } if (stinf->pid.protocol[2] == 0) { useable = 0; printf(" -> Missing layer 2 protocol.\n"); } if (stinf->pid.protocol[2] & ISDN_PID_L2_DF_PTP) { printf(" -> Interface is Poin-To-Point.\n"); } if (stinf->pid.protocol[3] == 0) { useable = 0; printf(" -> Missing layer 3 protocol.\n"); } else { printf(" -> Protocol: "); switch(stinf->pid.protocol[3] & ~ISDN_PID_FEATURE_MASK) { case ISDN_PID_L3_DSS1USER: printf("DSS1 (Euro ISDN)"); break; default: useable = 0; printf("unknown protocol 0x%08x",stinf->pid.protocol[3]); } printf("\n"); } p = 4; while(p <= MAX_LAYER_NR) { if (stinf->pid.protocol[p]) { useable = 0; printf(" -> Layer %d protocol 0x%08x is detected, but not allowed for TE lib.\n", p, stinf->pid.protocol[p]); } p++; } printf(" -> childcnt: %d\n",stinf->childcnt); } if (!useable) printf(" * Port NOT useable for PBX\n"); printf("--------\n"); i++; } printf("\n"); /* close mISDN */ if ((err = mISDN_close(device))) { fprintf(stderr, "mISDN_close() failed: err=%d '%s'\n", err, strerror(err)); exit(-1); } } int main() { isdn_port_info(); return 0; } asterisk-13.1.0/channels/chan_motif.c0000644000000000000000000031242312431721330016166 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2012, Digium, Inc. * * Joshua Colp * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \author Joshua Colp * * \brief Motif Jingle Channel Driver * * Iksemel http://iksemel.jabberstudio.org/ * * \ingroup channel_drivers */ /*! \li \ref chan_motif.c uses the configuration file \ref motif.conf * \addtogroup configuration_file */ /*! \page motif.conf motif.conf * \verbinclude motif.conf.sample */ /*** MODULEINFO iksemel res_xmpp openssl core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 427982 $") #include #include #include #include #include #include #include #include #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config_options.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/sched.h" #include "asterisk/io.h" #include "asterisk/rtp_engine.h" #include "asterisk/acl.h" #include "asterisk/callerid.h" #include "asterisk/file.h" #include "asterisk/cli.h" #include "asterisk/app.h" #include "asterisk/musiconhold.h" #include "asterisk/manager.h" #include "asterisk/stringfields.h" #include "asterisk/utils.h" #include "asterisk/causes.h" #include "asterisk/abstract_jb.h" #include "asterisk/xmpp.h" #include "asterisk/endpoints.h" #include "asterisk/stasis_channels.h" #include "asterisk/format_cache.h" /*** DOCUMENTATION Jingle Channel Driver Transports There are three different transports and protocol derivatives supported by chan_motif. They are in order of preference: Jingle using ICE-UDP, Google Jingle, and Google-V1. Jingle as defined in XEP-0166 supports the widest range of features. It is referred to as ice-udp. This is the specification that Jingle clients implement. Google Jingle follows the Jingle specification for signaling but uses a custom transport for media. It is supported by the Google Talk Plug-in in Gmail and by some other Jingle clients. It is referred to as google in this file. Google-V1 is the original Google Talk signaling protocol which uses an initial preliminary version of Jingle. It also uses the same custom transport as Google Jingle for media. It is supported by Google Voice, some other Jingle clients, and the Windows Google Talk client. It is referred to as google-v1 in this file. Incoming sessions will automatically switch to the correct transport once it has been determined. Outgoing sessions are capable of determining if the target is capable of Jingle or a Google transport if the target is in the roster. Unfortunately it is not possible to differentiate between a Google Jingle or Google-V1 capable resource until a session initiate attempt occurs. If a resource is determined to use a Google transport it will initially use Google Jingle but will fall back to Google-V1 if required. If an outgoing session attempt fails due to failure to support the given transport chan_motif will fall back in preference order listed previously until all transports have been exhausted. Dialing and Resource Selection Strategy Placing a call through an endpoint can be accomplished using the following dial string: Motif/[endpoint name]/[target] When placing an outgoing call through an endpoint the requested target is searched for in the roster list. If present the first Jingle or Google Jingle capable resource is specifically targeted. Since the capabilities of the resource are known the outgoing session initiation will disregard the configured transport and use the determined one. If the target is not found in the roster the target will be used as-is and a session will be initiated using the transport specified in this configuration file. If no transport has been specified the endpoint defaults to ice-udp. Video Support Support for video does not need to be explicitly enabled. Configuring any video codec on your endpoint will automatically enable it. DTMF The only supported method for DTMF is RFC2833. This is always enabled on audio streams and negotiated if possible. Incoming Calls Incoming calls will first look for the extension matching the name of the endpoint in the configured context. If no such extension exists the call will automatically fall back to the s extension. CallerID The incoming caller id number is populated with the username of the caller and the name is populated with the full identity of the caller. If you would like to perform authentication or filtering of incoming calls it is recommended that you use these fields to do so. Outgoing caller id can not be set. Multiple endpoints using the same connection is NOT supported. Doing so may result in broken calls. The configuration for an endpoint. Default dialplan context that incoming sessions will be routed to A callgroup to assign to this endpoint. A pickup group to assign to this endpoint. The default language for this endpoint. Default music on hold class for this endpoint. Default parking lot for this endpoint. Accout code for CDR purposes Codecs to allow Codecs to disallow Connection to accept traffic on and on which to send traffic out The transport to use for the endpoint. The default outbound transport for this endpoint. Inbound messages are inferred. Allowed transports are ice-udp, google, or google-v1. Note that chan_motif will fall back to transport preference order if the transport value chosen here fails. The Jingle protocol, as defined in XEP 0166. The Google Jingle protocol, which follows the Jingle specification for signaling but uses a custom transport for media. Google-V1 is the original Google Talk signaling protocol which uses an initial preliminary version of Jingle. It also uses the same custom transport as google for media. Maximum number of ICE candidates to offer Maximum number of pyaloads to offer ***/ /*! \brief Default maximum number of ICE candidates we will offer */ #define DEFAULT_MAX_ICE_CANDIDATES "10" /*! \brief Default maximum number of payloads we will offer */ #define DEFAULT_MAX_PAYLOADS "30" /*! \brief Number of buckets for endpoints */ #define ENDPOINT_BUCKETS 37 /*! \brief Number of buckets for sessions, on a per-endpoint basis */ #define SESSION_BUCKETS 37 /*! \brief Namespace for Jingle itself */ #define JINGLE_NS "urn:xmpp:jingle:1" /*! \brief Namespace for Jingle RTP sessions */ #define JINGLE_RTP_NS "urn:xmpp:jingle:apps:rtp:1" /*! \brief Namespace for Jingle RTP info */ #define JINGLE_RTP_INFO_NS "urn:xmpp:jingle:apps:rtp:info:1" /*! \brief Namespace for Jingle ICE-UDP */ #define JINGLE_ICE_UDP_NS "urn:xmpp:jingle:transports:ice-udp:1" /*! \brief Namespace for Google Talk ICE-UDP */ #define GOOGLE_TRANSPORT_NS "http://www.google.com/transport/p2p" /*! \brief Namespace for Google Talk Raw UDP */ #define GOOGLE_TRANSPORT_RAW_NS "http://www.google.com/transport/raw-udp" /*! \brief Namespace for Google Session */ #define GOOGLE_SESSION_NS "http://www.google.com/session" /*! \brief Namespace for Google Phone description */ #define GOOGLE_PHONE_NS "http://www.google.com/session/phone" /*! \brief Namespace for Google Video description */ #define GOOGLE_VIDEO_NS "http://www.google.com/session/video" /*! \brief Namespace for XMPP stanzas */ #define XMPP_STANZAS_NS "urn:ietf:params:xml:ns:xmpp-stanzas" /*! \brief The various transport methods supported, from highest priority to lowest priority when doing fallback */ enum jingle_transport { JINGLE_TRANSPORT_ICE_UDP = 3, /*!< XEP-0176 */ JINGLE_TRANSPORT_GOOGLE_V2 = 2, /*!< https://developers.google.com/talk/call_signaling */ JINGLE_TRANSPORT_GOOGLE_V1 = 1, /*!< Undocumented initial Google specification */ JINGLE_TRANSPORT_NONE = 0, /*!< No transport specified */ }; /*! \brief Endpoint state information */ struct jingle_endpoint_state { struct ao2_container *sessions; /*!< Active sessions to or from the endpoint */ }; /*! \brief Endpoint which contains configuration information and active sessions */ struct jingle_endpoint { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(name); /*!< Name of the endpoint */ AST_STRING_FIELD(context); /*!< Context to place incoming calls into */ AST_STRING_FIELD(accountcode); /*!< Account code */ AST_STRING_FIELD(language); /*!< Default language for prompts */ AST_STRING_FIELD(musicclass); /*!< Configured music on hold class */ AST_STRING_FIELD(parkinglot); /*!< Configured parking lot */ ); struct ast_xmpp_client *connection; /*!< Connection to use for traffic */ iksrule *rule; /*!< Active matching rule */ unsigned int maxicecandidates; /*!< Maximum number of ICE candidates we will offer */ unsigned int maxpayloads; /*!< Maximum number of payloads we will offer */ struct ast_format_cap *cap; /*!< Formats to use */ ast_group_t callgroup; /*!< Call group */ ast_group_t pickupgroup; /*!< Pickup group */ enum jingle_transport transport; /*!< Default transport to use on outgoing sessions */ struct jingle_endpoint_state *state; /*!< Endpoint state information */ }; /*! \brief Session which contains information about an active session */ struct jingle_session { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(sid); /*!< Session identifier */ AST_STRING_FIELD(audio_name); /*!< Name of the audio content */ AST_STRING_FIELD(video_name); /*!< Name of the video content */ ); struct jingle_endpoint_state *state; /*!< Endpoint we are associated with */ struct ast_xmpp_client *connection; /*!< Connection to use for traffic */ enum jingle_transport transport; /*!< Transport type to use for this session */ unsigned int maxicecandidates; /*!< Maximum number of ICE candidates we will offer */ unsigned int maxpayloads; /*!< Maximum number of payloads we will offer */ char remote_original[XMPP_MAX_JIDLEN];/*!< Identifier of the original remote party (remote may have changed due to redirect) */ char remote[XMPP_MAX_JIDLEN]; /*!< Identifier of the remote party */ iksrule *rule; /*!< Session matching rule */ struct ast_channel *owner; /*!< Master Channel */ struct ast_rtp_instance *rtp; /*!< RTP audio session */ struct ast_rtp_instance *vrtp; /*!< RTP video session */ struct ast_format_cap *cap; /*!< Local codec capabilities */ struct ast_format_cap *jointcap; /*!< Joint codec capabilities */ struct ast_format_cap *peercap; /*!< Peer codec capabilities */ unsigned int outgoing:1; /*!< Whether this is an outgoing leg or not */ unsigned int gone:1; /*!< In the eyes of Jingle this session is already gone */ struct ast_callid *callid; /*!< Bound session call-id */ }; static const char desc[] = "Motif Jingle Channel"; static const char channel_type[] = "Motif"; struct jingle_config { struct ao2_container *endpoints; /*!< Configured endpoints */ }; static AO2_GLOBAL_OBJ_STATIC(globals); static struct ast_sched_context *sched; /*!< Scheduling context for RTCP */ /* \brief Asterisk core interaction functions */ static struct ast_channel *jingle_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int jingle_sendtext(struct ast_channel *ast, const char *text); static int jingle_digit_begin(struct ast_channel *ast, char digit); static int jingle_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int jingle_call(struct ast_channel *ast, const char *dest, int timeout); static int jingle_hangup(struct ast_channel *ast); static int jingle_answer(struct ast_channel *ast); static struct ast_frame *jingle_read(struct ast_channel *ast); static int jingle_write(struct ast_channel *ast, struct ast_frame *f); static int jingle_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); static int jingle_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static struct jingle_session *jingle_alloc(struct jingle_endpoint *endpoint, const char *from, const char *sid); /*! \brief Action handlers */ static void jingle_action_session_initiate(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak); static void jingle_action_transport_info(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak); static void jingle_action_session_accept(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak); static void jingle_action_session_info(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak); static void jingle_action_session_terminate(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak); /*! \brief PBX interface structure for channel registration */ static struct ast_channel_tech jingle_tech = { .type = "Motif", .description = "Motif Jingle Channel Driver", .requester = jingle_request, .send_text = jingle_sendtext, .send_digit_begin = jingle_digit_begin, .send_digit_end = jingle_digit_end, .call = jingle_call, .hangup = jingle_hangup, .answer = jingle_answer, .read = jingle_read, .write = jingle_write, .write_video = jingle_write, .exception = jingle_read, .indicate = jingle_indicate, .fixup = jingle_fixup, .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER }; /*! \brief Defined handlers for different Jingle actions */ static const struct jingle_action_handler { const char *action; void (*handler)(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak); } jingle_action_handlers[] = { /* Jingle actions */ { "session-initiate", jingle_action_session_initiate, }, { "transport-info", jingle_action_transport_info, }, { "session-accept", jingle_action_session_accept, }, { "session-info", jingle_action_session_info, }, { "session-terminate", jingle_action_session_terminate, }, /* Google-V1 actions */ { "initiate", jingle_action_session_initiate, }, { "candidates", jingle_action_transport_info, }, { "accept", jingle_action_session_accept, }, { "terminate", jingle_action_session_terminate, }, { "reject", jingle_action_session_terminate, }, }; /*! \brief Reason text <-> cause code mapping */ static const struct jingle_reason_mapping { const char *reason; int cause; } jingle_reason_mappings[] = { { "busy", AST_CAUSE_BUSY, }, { "cancel", AST_CAUSE_CALL_REJECTED, }, { "connectivity-error", AST_CAUSE_INTERWORKING, }, { "decline", AST_CAUSE_CALL_REJECTED, }, { "expired", AST_CAUSE_NO_USER_RESPONSE, }, { "failed-transport", AST_CAUSE_PROTOCOL_ERROR, }, { "failed-application", AST_CAUSE_SWITCH_CONGESTION, }, { "general-error", AST_CAUSE_CONGESTION, }, { "gone", AST_CAUSE_NORMAL_CLEARING, }, { "incompatible-parameters", AST_CAUSE_BEARERCAPABILITY_NOTAVAIL, }, { "media-error", AST_CAUSE_BEARERCAPABILITY_NOTAVAIL, }, { "security-error", AST_CAUSE_PROTOCOL_ERROR, }, { "success", AST_CAUSE_NORMAL_CLEARING, }, { "timeout", AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE, }, { "unsupported-applications", AST_CAUSE_BEARERCAPABILITY_NOTAVAIL, }, { "unsupported-transports", AST_CAUSE_FACILITY_NOT_IMPLEMENTED, }, }; /*! \brief Hashing function for Jingle sessions */ static int jingle_session_hash(const void *obj, const int flags) { const struct jingle_session *session = obj; const char *sid = obj; return ast_str_hash(flags & OBJ_KEY ? sid : session->sid); } /*! \brief Comparator function for Jingle sessions */ static int jingle_session_cmp(void *obj, void *arg, int flags) { struct jingle_session *session1 = obj, *session2 = arg; const char *sid = arg; return !strcmp(session1->sid, flags & OBJ_KEY ? sid : session2->sid) ? CMP_MATCH | CMP_STOP : 0; } /*! \brief Destructor for Jingle endpoint state */ static void jingle_endpoint_state_destructor(void *obj) { struct jingle_endpoint_state *state = obj; ao2_ref(state->sessions, -1); } /*! \brief Destructor for Jingle endpoints */ static void jingle_endpoint_destructor(void *obj) { struct jingle_endpoint *endpoint = obj; if (endpoint->rule) { iks_filter_remove_rule(endpoint->connection->filter, endpoint->rule); } if (endpoint->connection) { ast_xmpp_client_unref(endpoint->connection); } ao2_cleanup(endpoint->cap); ao2_ref(endpoint->state, -1); ast_string_field_free_memory(endpoint); } /*! \brief Find function for Jingle endpoints */ static void *jingle_endpoint_find(struct ao2_container *tmp_container, const char *category) { return ao2_find(tmp_container, category, OBJ_KEY); } /*! \brief Allocator function for Jingle endpoint state */ static struct jingle_endpoint_state *jingle_endpoint_state_create(void) { struct jingle_endpoint_state *state; if (!(state = ao2_alloc(sizeof(*state), jingle_endpoint_state_destructor))) { return NULL; } if (!(state->sessions = ao2_container_alloc(SESSION_BUCKETS, jingle_session_hash, jingle_session_cmp))) { ao2_ref(state, -1); return NULL; } return state; } /*! \brief State find/create function */ static struct jingle_endpoint_state *jingle_endpoint_state_find_or_create(const char *category) { RAII_VAR(struct jingle_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); RAII_VAR(struct jingle_endpoint *, endpoint, NULL, ao2_cleanup); if (!cfg || !cfg->endpoints || !(endpoint = jingle_endpoint_find(cfg->endpoints, category))) { return jingle_endpoint_state_create(); } ao2_ref(endpoint->state, +1); return endpoint->state; } /*! \brief Allocator function for Jingle endpoints */ static void *jingle_endpoint_alloc(const char *cat) { struct jingle_endpoint *endpoint; if (!(endpoint = ao2_alloc(sizeof(*endpoint), jingle_endpoint_destructor))) { return NULL; } if (ast_string_field_init(endpoint, 512)) { ao2_ref(endpoint, -1); return NULL; } if (!(endpoint->state = jingle_endpoint_state_find_or_create(cat))) { ao2_ref(endpoint, -1); return NULL; } ast_string_field_set(endpoint, name, cat); endpoint->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); endpoint->transport = JINGLE_TRANSPORT_ICE_UDP; return endpoint; } /*! \brief Hashing function for Jingle endpoints */ static int jingle_endpoint_hash(const void *obj, const int flags) { const struct jingle_endpoint *endpoint = obj; const char *name = obj; return ast_str_hash(flags & OBJ_KEY ? name : endpoint->name); } /*! \brief Comparator function for Jingle endpoints */ static int jingle_endpoint_cmp(void *obj, void *arg, int flags) { struct jingle_endpoint *endpoint1 = obj, *endpoint2 = arg; const char *name = arg; return !strcmp(endpoint1->name, flags & OBJ_KEY ? name : endpoint2->name) ? CMP_MATCH | CMP_STOP : 0; } static struct aco_type endpoint_option = { .type = ACO_ITEM, .name = "endpoint", .category_match = ACO_BLACKLIST, .category = "^general$", .item_alloc = jingle_endpoint_alloc, .item_find = jingle_endpoint_find, .item_offset = offsetof(struct jingle_config, endpoints), }; struct aco_type *endpoint_options[] = ACO_TYPES(&endpoint_option); struct aco_file jingle_conf = { .filename = "motif.conf", .types = ACO_TYPES(&endpoint_option), }; /*! \brief Destructor for Jingle sessions */ static void jingle_session_destructor(void *obj) { struct jingle_session *session = obj; if (session->rule) { iks_filter_remove_rule(session->connection->filter, session->rule); } if (session->connection) { ast_xmpp_client_unref(session->connection); } if (session->rtp) { ast_rtp_instance_stop(session->rtp); ast_rtp_instance_destroy(session->rtp); } if (session->vrtp) { ast_rtp_instance_stop(session->vrtp); ast_rtp_instance_destroy(session->vrtp); } ao2_cleanup(session->cap); ao2_cleanup(session->jointcap); ao2_cleanup(session->peercap); if (session->callid) { ast_callid_unref(session->callid); } ast_string_field_free_memory(session); } /*! \brief Destructor called when module configuration goes away */ static void jingle_config_destructor(void *obj) { struct jingle_config *cfg = obj; ao2_cleanup(cfg->endpoints); } /*! \brief Allocator called when module configuration should appear */ static void *jingle_config_alloc(void) { struct jingle_config *cfg; if (!(cfg = ao2_alloc(sizeof(*cfg), jingle_config_destructor))) { return NULL; } if (!(cfg->endpoints = ao2_container_alloc(ENDPOINT_BUCKETS, jingle_endpoint_hash, jingle_endpoint_cmp))) { ao2_ref(cfg, -1); return NULL; } return cfg; } CONFIG_INFO_STANDARD(cfg_info, globals, jingle_config_alloc, .files = ACO_FILES(&jingle_conf), ); /*! \brief Function called by RTP engine to get local RTP peer */ static enum ast_rtp_glue_result jingle_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct jingle_session *session = ast_channel_tech_pvt(chan); enum ast_rtp_glue_result res = AST_RTP_GLUE_RESULT_LOCAL; if (!session->rtp) { return AST_RTP_GLUE_RESULT_FORBID; } ao2_ref(session->rtp, +1); *instance = session->rtp; return res; } /*! \brief Function called by RTP engine to get peer capabilities */ static void jingle_get_codec(struct ast_channel *chan, struct ast_format_cap *result) { } /*! \brief Function called by RTP engine to change where the remote party should send media */ static int jingle_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp, struct ast_rtp_instance *tpeer, const struct ast_format_cap *cap, int nat_active) { return -1; } /*! \brief Local glue for interacting with the RTP engine core */ static struct ast_rtp_glue jingle_rtp_glue = { .type = "Motif", .get_rtp_info = jingle_get_rtp_peer, .get_codec = jingle_get_codec, .update_peer = jingle_set_rtp_peer, }; /*! \brief Set the channel owner on the \ref jingle_session object and related objects */ static void jingle_set_owner(struct jingle_session *session, struct ast_channel *chan) { session->owner = chan; if (session->rtp) { ast_rtp_instance_set_channel_id(session->rtp, session->owner ? ast_channel_uniqueid(session->owner) : ""); } if (session->vrtp) { ast_rtp_instance_set_channel_id(session->vrtp, session->owner ? ast_channel_uniqueid(session->owner) : ""); } } /*! \brief Internal helper function which enables video support on a sesson if possible */ static void jingle_enable_video(struct jingle_session *session) { struct ast_sockaddr tmp; struct ast_rtp_engine_ice *ice; /* If video is already present don't do anything */ if (session->vrtp) { return; } /* If there are no configured video codecs do not turn video support on, it just won't work */ if (!ast_format_cap_has_type(session->cap, AST_MEDIA_TYPE_VIDEO)) { return; } ast_sockaddr_parse(&tmp, "0.0.0.0", 0); if (!(session->vrtp = ast_rtp_instance_new("asterisk", sched, &tmp, NULL))) { return; } ast_rtp_instance_set_prop(session->vrtp, AST_RTP_PROPERTY_RTCP, 1); ast_rtp_instance_set_channel_id(session->vrtp, ast_channel_uniqueid(session->owner)); ast_channel_set_fd(session->owner, 2, ast_rtp_instance_fd(session->vrtp, 0)); ast_channel_set_fd(session->owner, 3, ast_rtp_instance_fd(session->vrtp, 1)); ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(session->vrtp), ast_format_cap_get_framing(session->cap)); if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2 && (ice = ast_rtp_instance_get_ice(session->vrtp))) { ice->stop(session->vrtp); } } /*! \brief Internal helper function used to allocate Jingle session on an endpoint */ static struct jingle_session *jingle_alloc(struct jingle_endpoint *endpoint, const char *from, const char *sid) { struct jingle_session *session; struct ast_callid *callid; struct ast_sockaddr tmp; if (!(session = ao2_alloc(sizeof(*session), jingle_session_destructor))) { return NULL; } callid = ast_read_threadstorage_callid(); session->callid = (callid ? callid : ast_create_callid()); if (ast_string_field_init(session, 512)) { ao2_ref(session, -1); return NULL; } if (!ast_strlen_zero(from)) { ast_copy_string(session->remote_original, from, sizeof(session->remote_original)); ast_copy_string(session->remote, from, sizeof(session->remote)); } if (ast_strlen_zero(sid)) { ast_string_field_build(session, sid, "%08lx%08lx", (unsigned long)ast_random(), (unsigned long)ast_random()); session->outgoing = 1; ast_string_field_set(session, audio_name, "audio"); ast_string_field_set(session, video_name, "video"); } else { ast_string_field_set(session, sid, sid); } ao2_ref(endpoint->state, +1); session->state = endpoint->state; ao2_ref(endpoint->connection, +1); session->connection = endpoint->connection; session->transport = endpoint->transport; if (!(session->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) || !(session->jointcap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) || !(session->peercap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) || !session->callid) { ao2_ref(session, -1); return NULL; } ast_format_cap_append_from_cap(session->cap, endpoint->cap, AST_MEDIA_TYPE_UNKNOWN); /* While we rely on res_xmpp for communication we still need a temporary ast_sockaddr to tell the RTP engine * that we want IPv4 */ ast_sockaddr_parse(&tmp, "0.0.0.0", 0); /* Sessions always carry audio, but video is optional so don't enable it here */ if (!(session->rtp = ast_rtp_instance_new("asterisk", sched, &tmp, NULL))) { ao2_ref(session, -1); return NULL; } ast_rtp_instance_set_prop(session->rtp, AST_RTP_PROPERTY_RTCP, 1); ast_rtp_instance_set_prop(session->rtp, AST_RTP_PROPERTY_DTMF, 1); session->maxicecandidates = endpoint->maxicecandidates; session->maxpayloads = endpoint->maxpayloads; return session; } /*! \brief Function called to create a new Jingle Asterisk channel */ static struct ast_channel *jingle_new(struct jingle_endpoint *endpoint, struct jingle_session *session, int state, const char *title, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *cid_name) { struct ast_channel *chan; const char *str = S_OR(title, session->remote); struct ast_format_cap *caps; struct ast_format *tmpfmt; if (!ast_format_cap_count(session->cap)) { return NULL; } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { return NULL; } if (!(chan = ast_channel_alloc_with_endpoint(1, state, S_OR(title, ""), S_OR(cid_name, ""), "", "", "", assignedids, requestor, 0, endpoint->connection->endpoint, "Motif/%s-%04lx", str, (unsigned long)(ast_random() & 0xffff)))) { ao2_ref(caps, -1); return NULL; } ast_channel_stage_snapshot(chan); ast_channel_tech_set(chan, &jingle_tech); ast_channel_tech_pvt_set(chan, session); jingle_set_owner(session, chan); ast_channel_callid_set(chan, session->callid); ast_format_cap_append_from_cap(caps, session->cap, AST_MEDIA_TYPE_UNKNOWN); ast_channel_nativeformats_set(chan, caps); ao2_ref(caps, -1); if (session->rtp) { struct ast_rtp_engine_ice *ice; ast_channel_set_fd(chan, 0, ast_rtp_instance_fd(session->rtp, 0)); ast_channel_set_fd(chan, 1, ast_rtp_instance_fd(session->rtp, 1)); ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(session->rtp), ast_format_cap_get_framing(session->cap)); if (((session->transport == JINGLE_TRANSPORT_GOOGLE_V2) || (session->transport == JINGLE_TRANSPORT_GOOGLE_V1)) && (ice = ast_rtp_instance_get_ice(session->rtp))) { /* We stop built in ICE support because we need to fall back to old old old STUN support */ ice->stop(session->rtp); } } if (state == AST_STATE_RING) { ast_channel_rings_set(chan, 1); } ast_channel_adsicpe_set(chan, AST_ADSI_UNAVAILABLE); tmpfmt = ast_format_cap_get_format(session->cap, 0); ast_channel_set_writeformat(chan, tmpfmt); ast_channel_set_rawwriteformat(chan, tmpfmt); ast_channel_set_readformat(chan, tmpfmt); ast_channel_set_rawreadformat(chan, tmpfmt); ao2_ref(tmpfmt, -1); ao2_lock(endpoint); ast_channel_callgroup_set(chan, endpoint->callgroup); ast_channel_pickupgroup_set(chan, endpoint->pickupgroup); if (!ast_strlen_zero(endpoint->accountcode)) { ast_channel_accountcode_set(chan, endpoint->accountcode); } if (!ast_strlen_zero(endpoint->language)) { ast_channel_language_set(chan, endpoint->language); } if (!ast_strlen_zero(endpoint->musicclass)) { ast_channel_musicclass_set(chan, endpoint->musicclass); } ast_channel_context_set(chan, endpoint->context); if (ast_exists_extension(NULL, endpoint->context, endpoint->name, 1, NULL)) { ast_channel_exten_set(chan, endpoint->name); } else { ast_channel_exten_set(chan, "s"); } ast_channel_priority_set(chan, 1); ao2_unlock(endpoint); ast_channel_stage_snapshot_done(chan); ast_channel_unlock(chan); return chan; } /*! \brief Internal helper function which sends a response */ static void jingle_send_response(struct ast_xmpp_client *connection, ikspak *pak) { iks *response; if (!(response = iks_new("iq"))) { ast_log(LOG_ERROR, "Unable to allocate an IKS response stanza\n"); return; } iks_insert_attrib(response, "type", "result"); iks_insert_attrib(response, "from", connection->jid->full); iks_insert_attrib(response, "to", iks_find_attrib(pak->x, "from")); iks_insert_attrib(response, "id", iks_find_attrib(pak->x, "id")); ast_xmpp_client_send(connection, response); iks_delete(response); } /*! \brief Internal helper function which sends an error response */ static void jingle_send_error_response(struct ast_xmpp_client *connection, ikspak *pak, const char *type, const char *reasonstr, const char *reasonstr2) { iks *response, *error = NULL, *reason = NULL, *reason2 = NULL; if (!(response = iks_new("iq")) || !(error = iks_new("error")) || !(reason = iks_new(reasonstr))) { ast_log(LOG_ERROR, "Unable to allocate IKS error response stanzas\n"); goto end; } iks_insert_attrib(response, "type", "error"); iks_insert_attrib(response, "from", connection->jid->full); iks_insert_attrib(response, "to", iks_find_attrib(pak->x, "from")); iks_insert_attrib(response, "id", iks_find_attrib(pak->x, "id")); iks_insert_attrib(error, "type", type); iks_insert_node(error, reason); if (!ast_strlen_zero(reasonstr2) && (reason2 = iks_new(reasonstr2))) { iks_insert_node(error, reason2); } iks_insert_node(response, error); ast_xmpp_client_send(connection, response); end: iks_delete(reason2); iks_delete(reason); iks_delete(error); iks_delete(response); } /*! \brief Internal helper function which adds ICE-UDP candidates to a transport node */ static int jingle_add_ice_udp_candidates_to_transport(struct ast_rtp_instance *rtp, iks *transport, iks **candidates, unsigned int maximum) { struct ast_rtp_engine_ice *ice; struct ao2_container *local_candidates; struct ao2_iterator it; struct ast_rtp_engine_ice_candidate *candidate; int i = 0, res = 0; if (!(ice = ast_rtp_instance_get_ice(rtp)) || !(local_candidates = ice->get_local_candidates(rtp))) { ast_log(LOG_ERROR, "Unable to add ICE-UDP candidates as ICE support not available or no candidates available\n"); return -1; } iks_insert_attrib(transport, "xmlns", JINGLE_ICE_UDP_NS); iks_insert_attrib(transport, "pwd", ice->get_password(rtp)); iks_insert_attrib(transport, "ufrag", ice->get_ufrag(rtp)); it = ao2_iterator_init(local_candidates, 0); while ((candidate = ao2_iterator_next(&it)) && (i < maximum)) { iks *local_candidate; char tmp[30]; if (!(local_candidate = iks_new("candidate"))) { res = -1; ast_log(LOG_ERROR, "Unable to allocate IKS candidate stanza for ICE-UDP transport\n"); break; } snprintf(tmp, sizeof(tmp), "%u", candidate->id); iks_insert_attrib(local_candidate, "component", tmp); snprintf(tmp, sizeof(tmp), "%d", ast_str_hash(candidate->foundation)); iks_insert_attrib(local_candidate, "foundation", tmp); iks_insert_attrib(local_candidate, "generation", "0"); iks_insert_attrib(local_candidate, "network", "0"); snprintf(tmp, sizeof(tmp), "%04lx", (unsigned long)(ast_random() & 0xffff)); iks_insert_attrib(local_candidate, "id", tmp); iks_insert_attrib(local_candidate, "ip", ast_sockaddr_stringify_host(&candidate->address)); iks_insert_attrib(local_candidate, "port", ast_sockaddr_stringify_port(&candidate->address)); snprintf(tmp, sizeof(tmp), "%d", candidate->priority); iks_insert_attrib(local_candidate, "priority", tmp); iks_insert_attrib(local_candidate, "protocol", "udp"); if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_HOST) { iks_insert_attrib(local_candidate, "type", "host"); } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_SRFLX) { iks_insert_attrib(local_candidate, "type", "srflx"); } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_RELAYED) { iks_insert_attrib(local_candidate, "type", "relay"); } iks_insert_node(transport, local_candidate); candidates[i++] = local_candidate; } ao2_iterator_destroy(&it); ao2_ref(local_candidates, -1); return res; } /*! \brief Internal helper function which adds Google candidates to a transport node */ static int jingle_add_google_candidates_to_transport(struct ast_rtp_instance *rtp, iks *transport, iks **candidates, unsigned int video, enum jingle_transport transport_type, unsigned int maximum) { struct ast_rtp_engine_ice *ice; struct ao2_container *local_candidates; struct ao2_iterator it; struct ast_rtp_engine_ice_candidate *candidate; int i = 0, res = 0; if (!(ice = ast_rtp_instance_get_ice(rtp)) || !(local_candidates = ice->get_local_candidates(rtp))) { ast_log(LOG_ERROR, "Unable to add Google ICE candidates as ICE support not available or no candidates available\n"); return -1; } if (transport_type != JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(transport, "xmlns", GOOGLE_TRANSPORT_NS); } it = ao2_iterator_init(local_candidates, 0); while ((candidate = ao2_iterator_next(&it)) && (i < maximum)) { iks *local_candidate; /* In Google land a username is 16 bytes, explicitly */ char ufrag[17] = ""; if (!(local_candidate = iks_new("candidate"))) { res = -1; ast_log(LOG_ERROR, "Unable to allocate IKS candidate stanza for Google ICE transport\n"); break; } if (candidate->id == 1) { iks_insert_attrib(local_candidate, "name", !video ? "rtp" : "video_rtp"); } else if (candidate->id == 2) { iks_insert_attrib(local_candidate, "name", !video ? "rtcp" : "video_rtcp"); } else { iks_delete(local_candidate); continue; } iks_insert_attrib(local_candidate, "address", ast_sockaddr_stringify_host(&candidate->address)); iks_insert_attrib(local_candidate, "port", ast_sockaddr_stringify_port(&candidate->address)); if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_HOST) { iks_insert_attrib(local_candidate, "preference", "0.95"); iks_insert_attrib(local_candidate, "type", "local"); } else if (candidate->type == AST_RTP_ICE_CANDIDATE_TYPE_SRFLX) { iks_insert_attrib(local_candidate, "preference", "0.9"); iks_insert_attrib(local_candidate, "type", "stun"); } iks_insert_attrib(local_candidate, "protocol", "udp"); iks_insert_attrib(local_candidate, "network", "0"); snprintf(ufrag, sizeof(ufrag), "%s", ice->get_ufrag(rtp)); iks_insert_attrib(local_candidate, "username", ufrag); iks_insert_attrib(local_candidate, "generation", "0"); if (transport_type == JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(local_candidate, "password", ""); iks_insert_attrib(local_candidate, "foundation", "0"); iks_insert_attrib(local_candidate, "component", "1"); } else { iks_insert_attrib(local_candidate, "password", ice->get_password(rtp)); } /* You may notice a lack of relay support up above - this is because we don't support it for use with * the Google talk transport due to their arcane support. */ iks_insert_node(transport, local_candidate); candidates[i++] = local_candidate; } ao2_iterator_destroy(&it); ao2_ref(local_candidates, -1); return res; } /*! \brief Internal function which sends a session-terminate message */ static void jingle_send_session_terminate(struct jingle_session *session, const char *reasontext) { iks *iq = NULL, *jingle = NULL, *reason = NULL, *text = NULL; if (!(iq = iks_new("iq")) || !(jingle = iks_new(session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "session" : "jingle")) || !(reason = iks_new("reason")) || !(text = iks_new(reasontext))) { ast_log(LOG_ERROR, "Failed to allocate stanzas for session-terminate message on session '%s'\n", session->sid); goto end; } iks_insert_attrib(iq, "to", session->remote); iks_insert_attrib(iq, "type", "set"); iks_insert_attrib(iq, "id", session->connection->mid); ast_xmpp_increment_mid(session->connection->mid); if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(jingle, "type", "terminate"); iks_insert_attrib(jingle, "id", session->sid); iks_insert_attrib(jingle, "xmlns", GOOGLE_SESSION_NS); iks_insert_attrib(jingle, "initiator", session->outgoing ? session->connection->jid->full : session->remote); } else { iks_insert_attrib(jingle, "action", "session-terminate"); iks_insert_attrib(jingle, "sid", session->sid); iks_insert_attrib(jingle, "xmlns", JINGLE_NS); } iks_insert_node(iq, jingle); iks_insert_node(jingle, reason); iks_insert_node(reason, text); ast_xmpp_client_send(session->connection, iq); end: iks_delete(text); iks_delete(reason); iks_delete(jingle); iks_delete(iq); } /*! \brief Internal function which sends a session-info message */ static void jingle_send_session_info(struct jingle_session *session, const char *info) { iks *iq = NULL, *jingle = NULL, *text = NULL; /* Google-V1 has no way to send informational messages so don't even bother trying */ if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { return; } if (!(iq = iks_new("iq")) || !(jingle = iks_new("jingle")) || !(text = iks_new(info))) { ast_log(LOG_ERROR, "Failed to allocate stanzas for session-info message on session '%s'\n", session->sid); goto end; } iks_insert_attrib(iq, "to", session->remote); iks_insert_attrib(iq, "type", "set"); iks_insert_attrib(iq, "id", session->connection->mid); ast_xmpp_increment_mid(session->connection->mid); iks_insert_attrib(jingle, "action", "session-info"); iks_insert_attrib(jingle, "sid", session->sid); iks_insert_attrib(jingle, "xmlns", JINGLE_NS); iks_insert_node(iq, jingle); iks_insert_node(jingle, text); ast_xmpp_client_send(session->connection, iq); end: iks_delete(text); iks_delete(jingle); iks_delete(iq); } /*! \internal * * \brief Locks both pvt and pvt owner if owner is present. * * \note This function gives a ref to pvt->owner if it is present and locked. * This reference must be decremented after pvt->owner is unlocked. * * \note This function will never give you up, * \note This function will never let you down. * \note This function will run around and desert you. * * \pre pvt is not locked * \post pvt is locked * \post pvt->owner is locked and its reference count is increased (if pvt->owner is not NULL) * * \returns a pointer to the locked and reffed pvt->owner channel if it exists. */ static struct ast_channel *jingle_session_lock_full(struct jingle_session *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); chan = pvt->owner; if (chan) { /* 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; } /*! \brief Helper function which queues a hangup frame with cause code */ static void jingle_queue_hangup_with_cause(struct jingle_session *session, int cause) { struct ast_channel *chan; if ((chan = jingle_session_lock_full(session))) { ast_debug(3, "Hanging up channel '%s' with cause '%d'\n", ast_channel_name(chan), cause); ast_queue_hangup_with_cause(chan, cause); ast_channel_unlock(chan); ast_channel_unref(chan); } ao2_unlock(session); } /*! \brief Internal function which sends a transport-info message */ static void jingle_send_transport_info(struct jingle_session *session, const char *from) { iks *iq, *jingle = NULL, *audio = NULL, *audio_transport = NULL, *video = NULL, *video_transport = NULL; iks *audio_candidates[session->maxicecandidates], *video_candidates[session->maxicecandidates]; int i, res = 0; if (!(iq = iks_new("iq")) || !(jingle = iks_new(session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "session" : "jingle"))) { iks_delete(iq); jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); ast_log(LOG_ERROR, "Failed to allocate stanzas for transport-info message, hanging up session '%s'\n", session->sid); return; } memset(audio_candidates, 0, sizeof(audio_candidates)); memset(video_candidates, 0, sizeof(video_candidates)); iks_insert_attrib(iq, "from", session->connection->jid->full); iks_insert_attrib(iq, "to", from); iks_insert_attrib(iq, "type", "set"); iks_insert_attrib(iq, "id", session->connection->mid); ast_xmpp_increment_mid(session->connection->mid); if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(jingle, "type", "candidates"); iks_insert_attrib(jingle, "id", session->sid); iks_insert_attrib(jingle, "xmlns", GOOGLE_SESSION_NS); iks_insert_attrib(jingle, "initiator", session->outgoing ? session->connection->jid->full : from); } else { iks_insert_attrib(jingle, "action", "transport-info"); iks_insert_attrib(jingle, "sid", session->sid); iks_insert_attrib(jingle, "xmlns", JINGLE_NS); } iks_insert_node(iq, jingle); if (session->rtp) { if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { /* V1 protocol has the candidates directly in the session */ res = jingle_add_google_candidates_to_transport(session->rtp, jingle, audio_candidates, 0, session->transport, session->maxicecandidates); } else if ((audio = iks_new("content")) && (audio_transport = iks_new("transport"))) { iks_insert_attrib(audio, "creator", session->outgoing ? "initiator" : "responder"); iks_insert_attrib(audio, "name", session->audio_name); iks_insert_node(jingle, audio); iks_insert_node(audio, audio_transport); if (session->transport == JINGLE_TRANSPORT_ICE_UDP) { res = jingle_add_ice_udp_candidates_to_transport(session->rtp, audio_transport, audio_candidates, session->maxicecandidates); } else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) { res = jingle_add_google_candidates_to_transport(session->rtp, audio_transport, audio_candidates, 0, session->transport, session->maxicecandidates); } } else { res = -1; } } if ((session->transport != JINGLE_TRANSPORT_GOOGLE_V1) && !res && session->vrtp) { if ((video = iks_new("content")) && (video_transport = iks_new("transport"))) { iks_insert_attrib(video, "creator", session->outgoing ? "initiator" : "responder"); iks_insert_attrib(video, "name", session->video_name); iks_insert_node(jingle, video); iks_insert_node(video, video_transport); if (session->transport == JINGLE_TRANSPORT_ICE_UDP) { res = jingle_add_ice_udp_candidates_to_transport(session->vrtp, video_transport, video_candidates, session->maxicecandidates); } else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) { res = jingle_add_google_candidates_to_transport(session->vrtp, video_transport, video_candidates, 1, session->transport, session->maxicecandidates); } } else { res = -1; } } if (!res) { ast_xmpp_client_send(session->connection, iq); } else { jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); } /* Clean up after ourselves */ for (i = 0; i < session->maxicecandidates; i++) { iks_delete(video_candidates[i]); iks_delete(audio_candidates[i]); } iks_delete(video_transport); iks_delete(video); iks_delete(audio_transport); iks_delete(audio); iks_delete(jingle); iks_delete(iq); } /*! \brief Internal helper function which adds payloads to a description */ static int jingle_add_payloads_to_description(struct jingle_session *session, struct ast_rtp_instance *rtp, iks *description, iks **payloads, enum ast_media_type type) { int x = 0, i = 0, res = 0; for (x = 0; (x < ast_format_cap_count(session->jointcap)) && (i < (session->maxpayloads - 2)); x++) { struct ast_format *format = ast_format_cap_get_format(session->jointcap, x); int rtp_code; iks *payload; char tmp[32]; if (ast_format_get_type(format) != type) { ao2_ref(format, -1); continue; } if (((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1, format, 0)) == -1) || (!(payload = iks_new("payload-type")))) { ao2_ref(format, -1); return -1; } if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(payload, "xmlns", GOOGLE_PHONE_NS); } snprintf(tmp, sizeof(tmp), "%d", rtp_code); iks_insert_attrib(payload, "id", tmp); iks_insert_attrib(payload, "name", ast_rtp_lookup_mime_subtype2(1, format, 0, 0)); iks_insert_attrib(payload, "channels", "1"); if ((ast_format_cmp(format, ast_format_g722) == AST_FORMAT_CMP_EQUAL) && ((session->transport == JINGLE_TRANSPORT_GOOGLE_V1) || (session->transport == JINGLE_TRANSPORT_GOOGLE_V2))) { iks_insert_attrib(payload, "clockrate", "16000"); } else { snprintf(tmp, sizeof(tmp), "%u", ast_rtp_lookup_sample_rate2(1, format, 0)); iks_insert_attrib(payload, "clockrate", tmp); } if ((type == AST_MEDIA_TYPE_VIDEO) && (session->transport == JINGLE_TRANSPORT_GOOGLE_V2)) { iks *parameter; /* Google requires these parameters to be set, but alas we can not give accurate values so use some safe defaults */ if ((parameter = iks_new("parameter"))) { iks_insert_attrib(parameter, "name", "width"); iks_insert_attrib(parameter, "value", "640"); iks_insert_node(payload, parameter); } if ((parameter = iks_new("parameter"))) { iks_insert_attrib(parameter, "name", "height"); iks_insert_attrib(parameter, "value", "480"); iks_insert_node(payload, parameter); } if ((parameter = iks_new("parameter"))) { iks_insert_attrib(parameter, "name", "framerate"); iks_insert_attrib(parameter, "value", "30"); iks_insert_node(payload, parameter); } } iks_insert_node(description, payload); payloads[i++] = payload; ao2_ref(format, -1); } /* If this is for audio and there is room for RFC2833 add it in */ if ((type == AST_MEDIA_TYPE_AUDIO) && (i < session->maxpayloads)) { iks *payload; if ((payload = iks_new("payload-type"))) { if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(payload, "xmlns", GOOGLE_PHONE_NS); } iks_insert_attrib(payload, "id", "101"); iks_insert_attrib(payload, "name", "telephone-event"); iks_insert_attrib(payload, "channels", "1"); iks_insert_attrib(payload, "clockrate", "8000"); iks_insert_node(description, payload); payloads[i++] = payload; } } return res; } /*! \brief Helper function which adds content to a description */ static int jingle_add_content(struct jingle_session *session, iks *jingle, iks *content, iks *description, iks *transport, const char *name, enum ast_media_type type, struct ast_rtp_instance *rtp, iks **payloads) { int res = 0; if (session->transport != JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(content, "creator", session->outgoing ? "initiator" : "responder"); iks_insert_attrib(content, "name", name); iks_insert_node(jingle, content); iks_insert_attrib(description, "xmlns", JINGLE_RTP_NS); if (type == AST_MEDIA_TYPE_AUDIO) { iks_insert_attrib(description, "media", "audio"); } else if (type == AST_MEDIA_TYPE_VIDEO) { iks_insert_attrib(description, "media", "video"); } else { return -1; } iks_insert_node(content, description); } else { iks_insert_attrib(description, "xmlns", GOOGLE_PHONE_NS); iks_insert_node(jingle, description); } if (!(res = jingle_add_payloads_to_description(session, rtp, description, payloads, type))) { if (session->transport == JINGLE_TRANSPORT_ICE_UDP) { iks_insert_attrib(transport, "xmlns", JINGLE_ICE_UDP_NS); iks_insert_node(content, transport); } else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) { iks_insert_attrib(transport, "xmlns", GOOGLE_TRANSPORT_NS); iks_insert_node(content, transport); } } return res; } /*! \brief Internal function which sends a complete session message */ static void jingle_send_session_action(struct jingle_session *session, const char *action) { iks *iq, *jingle, *audio = NULL, *audio_description = NULL, *video = NULL, *video_description = NULL; iks *audio_payloads[session->maxpayloads], *video_payloads[session->maxpayloads]; iks *audio_transport = NULL, *video_transport = NULL; int i, res = 0; if (!(iq = iks_new("iq")) || !(jingle = iks_new(session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "session" : "jingle"))) { jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); iks_delete(iq); return; } memset(audio_payloads, 0, sizeof(audio_payloads)); memset(video_payloads, 0, sizeof(video_payloads)); iks_insert_attrib(iq, "from", session->connection->jid->full); iks_insert_attrib(iq, "to", session->remote); iks_insert_attrib(iq, "type", "set"); iks_insert_attrib(iq, "id", session->connection->mid); ast_xmpp_increment_mid(session->connection->mid); if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { iks_insert_attrib(jingle, "type", action); iks_insert_attrib(jingle, "id", session->sid); iks_insert_attrib(jingle, "xmlns", GOOGLE_SESSION_NS); } else { iks_insert_attrib(jingle, "action", action); iks_insert_attrib(jingle, "sid", session->sid); iks_insert_attrib(jingle, "xmlns", JINGLE_NS); } if (!strcasecmp(action, "session-initiate") || !strcasecmp(action, "initiate") || !strcasecmp(action, "accept")) { iks_insert_attrib(jingle, "initiator", session->outgoing ? session->connection->jid->full : session->remote); } iks_insert_node(iq, jingle); if (session->rtp && (audio = iks_new("content")) && (audio_description = iks_new("description")) && (audio_transport = iks_new("transport"))) { res = jingle_add_content(session, jingle, audio, audio_description, audio_transport, session->audio_name, AST_MEDIA_TYPE_AUDIO, session->rtp, audio_payloads); } else { ast_log(LOG_ERROR, "Failed to allocate audio content stanzas for session '%s', hanging up\n", session->sid); res = -1; } if ((session->transport != JINGLE_TRANSPORT_GOOGLE_V1) && !res && session->vrtp) { if ((video = iks_new("content")) && (video_description = iks_new("description")) && (video_transport = iks_new("transport"))) { res = jingle_add_content(session, jingle, video, video_description, video_transport, session->video_name, AST_MEDIA_TYPE_VIDEO, session->vrtp, video_payloads); } else { ast_log(LOG_ERROR, "Failed to allocate video content stanzas for session '%s', hanging up\n", session->sid); res = -1; } } if (!res) { ast_xmpp_client_send(session->connection, iq); } else { jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); } iks_delete(video_transport); iks_delete(audio_transport); for (i = 0; i < session->maxpayloads; i++) { iks_delete(video_payloads[i]); iks_delete(audio_payloads[i]); } iks_delete(video_description); iks_delete(video); iks_delete(audio_description); iks_delete(audio); iks_delete(jingle); iks_delete(iq); } /*! \brief Internal function which sends a session-inititate message */ static void jingle_send_session_initiate(struct jingle_session *session) { jingle_send_session_action(session, session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "initiate" : "session-initiate"); } /*! \brief Internal function which sends a session-accept message */ static void jingle_send_session_accept(struct jingle_session *session) { jingle_send_session_action(session, session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "accept" : "session-accept"); } /*! \brief Callback for when a response is received for an outgoing session-initiate message */ static int jingle_outgoing_hook(void *data, ikspak *pak) { struct jingle_session *session = data; iks *error = iks_find(pak->x, "error"), *redirect; /* In all cases this hook is done with */ iks_filter_remove_rule(session->connection->filter, session->rule); session->rule = NULL; ast_callid_threadassoc_add(session->callid); /* If no error occurred they accepted our session-initiate message happily */ if (!error) { struct ast_channel *chan; if ((chan = jingle_session_lock_full(session))) { ast_queue_control(chan, AST_CONTROL_PROCEEDING); ast_channel_unlock(chan); ast_channel_unref(chan); } ao2_unlock(session); jingle_send_transport_info(session, iks_find_attrib(pak->x, "from")); goto end; } /* Assume that because this is an error the session is gone, there is only one case where this is incorrect - a redirect */ session->gone = 1; /* Map the error we received to an appropriate cause code and hang up the channel */ if ((redirect = iks_find_with_attrib(error, "redirect", "xmlns", XMPP_STANZAS_NS))) { iks *to = iks_child(redirect); char *target; if (to && (target = iks_name(to)) && !ast_strlen_zero(target)) { /* Make the xmpp: go away if it is present */ if (!strncmp(target, "xmpp:", 5)) { target += 5; } /* This is actually a fairly simple operation - we update the remote and send another session-initiate */ ast_copy_string(session->remote, target, sizeof(session->remote)); /* Add a new hook so we can get the status of redirected session */ session->rule = iks_filter_add_rule(session->connection->filter, jingle_outgoing_hook, session, IKS_RULE_ID, session->connection->mid, IKS_RULE_DONE); jingle_send_session_initiate(session); session->gone = 0; } else { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); } } else if (iks_find_with_attrib(error, "service-unavailable", "xmlns", XMPP_STANZAS_NS)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_CONGESTION); } else if (iks_find_with_attrib(error, "resource-constraint", "xmlns", XMPP_STANZAS_NS)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_REQUESTED_CHAN_UNAVAIL); } else if (iks_find_with_attrib(error, "bad-request", "xmlns", XMPP_STANZAS_NS)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); } else if (iks_find_with_attrib(error, "remote-server-not-found", "xmlns", XMPP_STANZAS_NS)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_NO_ROUTE_DESTINATION); } else if (iks_find_with_attrib(error, "feature-not-implemented", "xmlns", XMPP_STANZAS_NS)) { /* Assume that this occurred because the remote side does not support our transport, so drop it down one and try again */ session->transport--; /* If we still have a viable transport mechanism re-send the session-initiate */ if (session->transport != JINGLE_TRANSPORT_NONE) { struct ast_rtp_engine_ice *ice; if (((session->transport == JINGLE_TRANSPORT_GOOGLE_V2) || (session->transport == JINGLE_TRANSPORT_GOOGLE_V1)) && (ice = ast_rtp_instance_get_ice(session->rtp))) { /* We stop built in ICE support because we need to fall back to old old old STUN support */ ice->stop(session->rtp); } /* Re-send the message to the *original* target and not a redirected one */ ast_copy_string(session->remote, session->remote_original, sizeof(session->remote)); session->rule = iks_filter_add_rule(session->connection->filter, jingle_outgoing_hook, session, IKS_RULE_ID, session->connection->mid, IKS_RULE_DONE); jingle_send_session_initiate(session); session->gone = 0; } else { /* Otherwise we have exhausted all transports */ jingle_queue_hangup_with_cause(session, AST_CAUSE_FACILITY_NOT_IMPLEMENTED); } } else { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); } end: ast_callid_threadassoc_remove(); return IKS_FILTER_EAT; } /*! \brief Function called by core when we should answer a Jingle session */ static int jingle_answer(struct ast_channel *ast) { struct jingle_session *session = ast_channel_tech_pvt(ast); /* The channel has already been answered so we don't need to do anything */ if (ast_channel_state(ast) == AST_STATE_UP) { return 0; } jingle_send_session_accept(session); return 0; } /*! \brief Function called by core to read any waiting frames */ static struct ast_frame *jingle_read(struct ast_channel *ast) { struct jingle_session *session = ast_channel_tech_pvt(ast); struct ast_frame *frame = &ast_null_frame; switch (ast_channel_fdno(ast)) { case 0: if (session->rtp) { frame = ast_rtp_instance_read(session->rtp, 0); } break; case 1: if (session->rtp) { frame = ast_rtp_instance_read(session->rtp, 1); } break; case 2: if (session->vrtp) { frame = ast_rtp_instance_read(session->vrtp, 0); } break; case 3: if (session->vrtp) { frame = ast_rtp_instance_read(session->vrtp, 1); } break; default: break; } if (frame && frame->frametype == AST_FRAME_VOICE && ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { if (ast_format_cap_iscompatible_format(session->jointcap, 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(ast)); ast_frfree(frame); frame = &ast_null_frame; } else { struct ast_format_cap *caps; ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(frame->subclass.format)); caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append(caps, frame->subclass.format, 0); ast_channel_nativeformats_set(ast, caps); ao2_ref(caps, -1); } ast_set_read_format(ast, ast_channel_readformat(ast)); ast_set_write_format(ast, ast_channel_writeformat(ast)); } } return frame; } /*! \brief Function called by core to write frames */ static int jingle_write(struct ast_channel *ast, struct ast_frame *frame) { struct jingle_session *session = ast_channel_tech_pvt(ast); int res = 0; switch (frame->frametype) { case AST_FRAME_VOICE: if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *codec_buf = ast_str_alloca(64); 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(ast), &codec_buf), ast_format_get_name(ast_channel_readformat(ast)), ast_format_get_name(ast_channel_writeformat(ast))); return 0; } if (session && session->rtp) { res = ast_rtp_instance_write(session->rtp, frame); } break; case AST_FRAME_VIDEO: if (session && session->vrtp) { res = ast_rtp_instance_write(session->vrtp, frame); } break; default: ast_log(LOG_WARNING, "Can't send %u type frames with Jingle write\n", frame->frametype); return 0; } return res; } /*! \brief Function called by core to change the underlying owner channel */ static int jingle_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct jingle_session *session = ast_channel_tech_pvt(newchan); ao2_lock(session); jingle_set_owner(session, newchan); ao2_unlock(session); return 0; } /*! \brief Function called by core to ask the channel to indicate some sort of condition */ static int jingle_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) { struct jingle_session *session = ast_channel_tech_pvt(ast); int res = 0; switch (condition) { case AST_CONTROL_RINGING: if (ast_channel_state(ast) == AST_STATE_RING) { jingle_send_session_info(session, "ringing xmlns='urn:xmpp:jingle:apps:rtp:info:1'"); } else { res = -1; } break; case AST_CONTROL_BUSY: if (ast_channel_state(ast) != AST_STATE_UP) { ast_channel_hangupcause_set(ast, AST_CAUSE_BUSY); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); } else { res = -1; } break; case AST_CONTROL_CONGESTION: if (ast_channel_state(ast) != AST_STATE_UP) { ast_channel_hangupcause_set(ast, AST_CAUSE_CONGESTION); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); } else { res = -1; } break; case AST_CONTROL_INCOMPLETE: if (ast_channel_state(ast) != AST_STATE_UP) { ast_channel_hangupcause_set(ast, AST_CAUSE_CONGESTION); ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); } break; case AST_CONTROL_HOLD: ast_moh_start(ast, data, NULL); break; case AST_CONTROL_UNHOLD: ast_moh_stop(ast); break; case AST_CONTROL_SRCUPDATE: if (session->rtp) { ast_rtp_instance_update_source(session->rtp); } break; case AST_CONTROL_SRCCHANGE: if (session->rtp) { ast_rtp_instance_change_source(session->rtp); } break; case AST_CONTROL_VIDUPDATE: case AST_CONTROL_UPDATE_RTP_PEER: case AST_CONTROL_CONNECTED_LINE: break; case AST_CONTROL_PVT_CAUSE_CODE: case AST_CONTROL_MASQUERADE_NOTIFY: case -1: res = -1; break; default: ast_log(LOG_NOTICE, "Don't know how to indicate condition '%d'\n", condition); res = -1; } return res; } /*! \brief Function called by core to send text to the remote party of the Jingle session */ static int jingle_sendtext(struct ast_channel *chan, const char *text) { struct jingle_session *session = ast_channel_tech_pvt(chan); return ast_xmpp_client_send_message(session->connection, session->remote, text); } /*! \brief Function called by core to start a DTMF digit */ static int jingle_digit_begin(struct ast_channel *chan, char digit) { struct jingle_session *session = ast_channel_tech_pvt(chan); if (session->rtp) { ast_rtp_instance_dtmf_begin(session->rtp, digit); } return 0; } /*! \brief Function called by core to stop a DTMF digit */ static int jingle_digit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct jingle_session *session = ast_channel_tech_pvt(ast); if (session->rtp) { ast_rtp_instance_dtmf_end_with_duration(session->rtp, digit, duration); } return 0; } /*! \brief Function called by core to actually start calling a remote party */ static int jingle_call(struct ast_channel *ast, const char *dest, int timeout) { struct jingle_session *session = ast_channel_tech_pvt(ast); ast_setstate(ast, AST_STATE_RING); /* Since we have no idea of the remote capabilities use ours for now */ ast_format_cap_append_from_cap(session->jointcap, session->cap, AST_MEDIA_TYPE_UNKNOWN); /* We set up a hook so we can know when our session-initiate message was accepted or rejected */ session->rule = iks_filter_add_rule(session->connection->filter, jingle_outgoing_hook, session, IKS_RULE_ID, session->connection->mid, IKS_RULE_DONE); jingle_send_session_initiate(session); return 0; } /*! \brief Function called by core to hang up a Jingle session */ static int jingle_hangup(struct ast_channel *ast) { struct jingle_session *session = ast_channel_tech_pvt(ast); ao2_lock(session); if ((ast_channel_state(ast) != AST_STATE_DOWN) && !session->gone) { int cause = (session->owner ? ast_channel_hangupcause(session->owner) : AST_CAUSE_CONGESTION); const char *reason = "success"; int i; /* Get the appropriate reason and send a session-terminate */ for (i = 0; i < ARRAY_LEN(jingle_reason_mappings); i++) { if (jingle_reason_mappings[i].cause == cause) { reason = jingle_reason_mappings[i].reason; break; } } jingle_send_session_terminate(session, reason); } ast_channel_tech_pvt_set(ast, NULL); jingle_set_owner(session, NULL); ao2_unlink(session->state->sessions, session); ao2_ref(session->state, -1); ao2_unlock(session); ao2_ref(session, -1); return 0; } /*! \brief Function called by core to create a new outgoing Jingle session */ static struct ast_channel *jingle_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { RAII_VAR(struct jingle_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); RAII_VAR(struct jingle_endpoint *, endpoint, NULL, ao2_cleanup); char *dialed, target[200] = ""; struct ast_xmpp_buddy *buddy; struct jingle_session *session; struct ast_channel *chan; enum jingle_transport transport = JINGLE_TRANSPORT_NONE; struct ast_rtp_engine_ice *ice; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(name); AST_APP_ARG(target); ); /* We require at a minimum one audio format to be requested */ if (!ast_format_cap_has_type(cap, AST_MEDIA_TYPE_AUDIO)) { ast_log(LOG_ERROR, "Motif channel driver requires an audio format when dialing a destination\n"); *cause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL; return NULL; } if (ast_strlen_zero(data) || !(dialed = ast_strdupa(data))) { ast_log(LOG_ERROR, "Unable to create channel with empty destination.\n"); *cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; return NULL; } /* Parse the given dial string and validate the results */ AST_NONSTANDARD_APP_ARGS(args, dialed, '/'); if (ast_strlen_zero(args.name) || ast_strlen_zero(args.target)) { ast_log(LOG_ERROR, "Unable to determine endpoint name and target.\n"); *cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; return NULL; } if (!(endpoint = jingle_endpoint_find(cfg->endpoints, args.name))) { ast_log(LOG_ERROR, "Endpoint '%s' does not exist.\n", args.name); *cause = AST_CAUSE_CHANNEL_UNACCEPTABLE; return NULL; } ao2_lock(endpoint->state); /* If we don't have a connection for the endpoint we can't exactly start a session on it */ if (!endpoint->connection) { ast_log(LOG_ERROR, "Unable to create Jingle session on endpoint '%s' as no valid connection exists\n", args.name); *cause = AST_CAUSE_SWITCH_CONGESTION; ao2_unlock(endpoint->state); return NULL; } /* Find the target in the roster so we can choose a resource */ if ((buddy = ao2_find(endpoint->connection->buddies, args.target, OBJ_KEY))) { struct ao2_iterator res; struct ast_xmpp_resource *resource; /* Iterate through finding the first viable Jingle capable resource */ res = ao2_iterator_init(buddy->resources, 0); while ((resource = ao2_iterator_next(&res))) { if (resource->caps.jingle) { snprintf(target, sizeof(target), "%s/%s", args.target, resource->resource); transport = JINGLE_TRANSPORT_ICE_UDP; break; } else if (resource->caps.google) { snprintf(target, sizeof(target), "%s/%s", args.target, resource->resource); transport = JINGLE_TRANSPORT_GOOGLE_V2; break; } ao2_ref(resource, -1); } ao2_iterator_destroy(&res); ao2_ref(buddy, -1); } else { /* If the target is NOT in the roster use the provided target as-is */ ast_copy_string(target, args.target, sizeof(target)); } ao2_unlock(endpoint->state); /* If no target was found we can't set up a session */ if (ast_strlen_zero(target)) { ast_log(LOG_ERROR, "Unable to create Jingle session on endpoint '%s' as no capable resource for target '%s' was found\n", args.name, args.target); *cause = AST_CAUSE_SWITCH_CONGESTION; return NULL; } if (!(session = jingle_alloc(endpoint, target, NULL))) { ast_log(LOG_ERROR, "Unable to create Jingle session on endpoint '%s'\n", args.name); *cause = AST_CAUSE_SWITCH_CONGESTION; return NULL; } /* Update the transport if we learned what we should actually use */ if (transport != JINGLE_TRANSPORT_NONE) { session->transport = transport; /* Note that for Google-V1 and Google-V2 we don't stop built-in ICE support, this will happen in jingle_new */ } if (!(chan = jingle_new(endpoint, session, AST_STATE_DOWN, target, assignedids, requestor, NULL))) { ast_log(LOG_ERROR, "Unable to create Jingle channel on endpoint '%s'\n", args.name); *cause = AST_CAUSE_SWITCH_CONGESTION; ao2_ref(session, -1); return NULL; } /* If video was requested try to enable it on the session */ if (ast_format_cap_has_type(cap, AST_MEDIA_TYPE_VIDEO)) { jingle_enable_video(session); } /* As this is outgoing set ourselves as controlling */ if (session->rtp && (ice = ast_rtp_instance_get_ice(session->rtp))) { ice->ice_lite(session->rtp); } if (session->vrtp && (ice = ast_rtp_instance_get_ice(session->vrtp))) { ice->ice_lite(session->vrtp); } /* We purposely don't decrement the session here as there is a reference on the channel */ ao2_link(endpoint->state->sessions, session); return chan; } /*! \brief Helper function which handles content descriptions */ static int jingle_interpret_description(struct jingle_session *session, iks *description, const char *name, struct ast_rtp_instance **rtp) { char *media = iks_find_attrib(description, "media"); struct ast_rtp_codecs codecs; iks *codec; int othercapability = 0; /* Google-V1 is always carrying audio, but just doesn't tell us so */ if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) { media = "audio"; } else if (ast_strlen_zero(media)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_log(LOG_ERROR, "Received a content description on session '%s' without a name\n", session->sid); return -1; } /* Determine the type of media that is being carried and update the RTP instance, as well as the name */ if (!strcasecmp(media, "audio")) { if (!ast_strlen_zero(name)) { ast_string_field_set(session, audio_name, name); } *rtp = session->rtp; ast_format_cap_remove_by_type(session->peercap, AST_MEDIA_TYPE_AUDIO); ast_format_cap_remove_by_type(session->jointcap, AST_MEDIA_TYPE_AUDIO); } else if (!strcasecmp(media, "video")) { if (!ast_strlen_zero(name)) { ast_string_field_set(session, video_name, name); } jingle_enable_video(session); *rtp = session->vrtp; /* If video is not present cancel this session */ if (!session->vrtp) { jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_log(LOG_ERROR, "Received a video content description on session '%s' but could not enable video\n", session->sid); return -1; } ast_format_cap_remove_by_type(session->peercap, AST_MEDIA_TYPE_VIDEO); ast_format_cap_remove_by_type(session->jointcap, AST_MEDIA_TYPE_VIDEO); } else { /* Unknown media type */ jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_log(LOG_ERROR, "Unsupported media type '%s' received in content description on session '%s'\n", media, session->sid); return -1; } if (ast_rtp_codecs_payloads_initialize(&codecs)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_log(LOG_ERROR, "Could not initialize codecs for negotiation on session '%s'\n", session->sid); return -1; } /* Iterate the codecs updating the relevant RTP instance as we go */ for (codec = iks_child(description); codec; codec = iks_next(codec)) { char *id = iks_find_attrib(codec, "id"), *name = iks_find_attrib(codec, "name"); char *clockrate = iks_find_attrib(codec, "clockrate"); int rtp_id, rtp_clockrate; if (!ast_strlen_zero(id) && !ast_strlen_zero(name) && (sscanf(id, "%30d", &rtp_id) == 1)) { if (!ast_strlen_zero(clockrate) && (sscanf(clockrate, "%30d", &rtp_clockrate) == 1)) { ast_rtp_codecs_payloads_set_rtpmap_type_rate(&codecs, NULL, rtp_id, media, name, 0, rtp_clockrate); } else { ast_rtp_codecs_payloads_set_rtpmap_type(&codecs, NULL, rtp_id, media, name, 0); } } } ast_rtp_codecs_payload_formats(&codecs, session->peercap, &othercapability); ast_format_cap_get_compatible(session->cap, session->peercap, session->jointcap); if (!ast_format_cap_count(session->jointcap)) { /* We have no compatible codecs, so terminate the session appropriately */ jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_rtp_codecs_payloads_destroy(&codecs); return -1; } ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(*rtp), *rtp); ast_rtp_codecs_payloads_destroy(&codecs); return 0; } /*! \brief Helper function which handles ICE-UDP transport information */ static int jingle_interpret_ice_udp_transport(struct jingle_session *session, iks *transport, struct ast_rtp_instance *rtp) { struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(rtp); char *ufrag = iks_find_attrib(transport, "ufrag"), *pwd = iks_find_attrib(transport, "pwd"); iks *candidate; if (!ice) { jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); ast_log(LOG_ERROR, "Received ICE-UDP transport information on session '%s' but ICE support not available\n", session->sid); return -1; } if (!ast_strlen_zero(ufrag) && !ast_strlen_zero(pwd)) { ice->set_authentication(rtp, ufrag, pwd); } for (candidate = iks_child(transport); candidate; candidate = iks_next(candidate)) { char *component = iks_find_attrib(candidate, "component"), *foundation = iks_find_attrib(candidate, "foundation"); char *generation = iks_find_attrib(candidate, "generation"), *id = iks_find_attrib(candidate, "id"); char *ip = iks_find_attrib(candidate, "ip"), *port = iks_find_attrib(candidate, "port"); char *priority = iks_find_attrib(candidate, "priority"), *protocol = iks_find_attrib(candidate, "protocol"); char *type = iks_find_attrib(candidate, "type"); struct ast_rtp_engine_ice_candidate local_candidate = { 0, }; int real_port; struct ast_sockaddr remote_address = { { 0, } }; /* If this candidate is incomplete skip it */ if (ast_strlen_zero(component) || ast_strlen_zero(foundation) || ast_strlen_zero(generation) || ast_strlen_zero(id) || ast_strlen_zero(ip) || ast_strlen_zero(port) || ast_strlen_zero(priority) || ast_strlen_zero(protocol) || ast_strlen_zero(type)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); ast_log(LOG_ERROR, "Incomplete ICE-UDP candidate received on session '%s'\n", session->sid); return -1; } if ((sscanf(component, "%30u", &local_candidate.id) != 1) || (sscanf(priority, "%30u", (unsigned *)&local_candidate.priority) != 1) || (sscanf(port, "%30d", &real_port) != 1)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); ast_log(LOG_ERROR, "Invalid ICE-UDP candidate information received on session '%s'\n", session->sid); return -1; } local_candidate.foundation = foundation; local_candidate.transport = protocol; ast_sockaddr_parse(&local_candidate.address, ip, PARSE_PORT_FORBID); /* We only support IPv4 right now */ if (!ast_sockaddr_is_ipv4(&local_candidate.address)) { continue; } ast_sockaddr_set_port(&local_candidate.address, real_port); if (!strcasecmp(type, "host")) { local_candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST; } else if (!strcasecmp(type, "srflx")) { local_candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX; } else if (!strcasecmp(type, "relay")) { local_candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED; } else { continue; } /* Worst case use the first viable address */ ast_rtp_instance_get_remote_address(rtp, &remote_address); if (ast_sockaddr_is_ipv4(&local_candidate.address) && ast_sockaddr_isnull(&remote_address)) { ast_rtp_instance_set_remote_address(rtp, &local_candidate.address); } ice->add_remote_candidate(rtp, &local_candidate); } ice->start(rtp); return 0; } /*! \brief Helper function which handles Google transport information */ static int jingle_interpret_google_transport(struct jingle_session *session, iks *transport, struct ast_rtp_instance *rtp) { struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(rtp); iks *candidate; if (!ice) { jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); ast_log(LOG_ERROR, "Received Google transport information on session '%s' but ICE support not available\n", session->sid); return -1; } /* If this session has not transitioned to the Google transport do so now */ if ((session->transport != JINGLE_TRANSPORT_GOOGLE_V2) && (session->transport != JINGLE_TRANSPORT_GOOGLE_V1)) { /* Stop built-in ICE support... we need to fall back to the old old old STUN */ ice->stop(rtp); session->transport = JINGLE_TRANSPORT_GOOGLE_V2; } for (candidate = iks_child(transport); candidate; candidate = iks_next(candidate)) { char *address = iks_find_attrib(candidate, "address"), *port = iks_find_attrib(candidate, "port"); char *username = iks_find_attrib(candidate, "username"), *name = iks_find_attrib(candidate, "name"); char *protocol = iks_find_attrib(candidate, "protocol"); int real_port; struct ast_sockaddr target = { { 0, } }; /* In Google land the combined value is 32 bytes */ char combined[33] = ""; /* If this is NOT actually a candidate just skip it */ if (strcasecmp(iks_name(candidate), "candidate") && strcasecmp(iks_name(candidate), "p:candidate") && strcasecmp(iks_name(candidate), "ses:candidate")) { continue; } /* If this candidate is incomplete skip it */ if (ast_strlen_zero(address) || ast_strlen_zero(port) || ast_strlen_zero(username) || ast_strlen_zero(name)) { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); ast_log(LOG_ERROR, "Incomplete Google candidate received on session '%s'\n", session->sid); return -1; } /* We only support UDP so skip any other protocols */ if (!ast_strlen_zero(protocol) && strcasecmp(protocol, "udp")) { continue; } /* We only permit audio and video, not RTCP */ if (strcasecmp(name, "rtp") && strcasecmp(name, "video_rtp")) { continue; } /* Parse the target information so we can send a STUN request to the candidate */ if (sscanf(port, "%30d", &real_port) != 1) { jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR); ast_log(LOG_ERROR, "Invalid Google candidate port '%s' received on session '%s'\n", port, session->sid); return -1; } ast_sockaddr_parse(&target, address, PARSE_PORT_FORBID); ast_sockaddr_set_port(&target, real_port); /* Per the STUN support Google talk uses combine the two usernames */ snprintf(combined, sizeof(combined), "%s%s", username, ice->get_ufrag(rtp)); /* This should appease the masses... we will actually change the remote address when we get their STUN packet */ ast_rtp_instance_stun_request(rtp, &target, combined); } return 0; } /*! * \brief Helper function which locates content stanzas and interprets them * * \note The session *must not* be locked before calling this */ static int jingle_interpret_content(struct jingle_session *session, ikspak *pak) { iks *content; unsigned int changed = 0; struct ast_channel *chan; /* Look at the content in the session initiation */ for (content = iks_child(iks_child(pak->x)); content; content = iks_next(content)) { char *name; struct ast_rtp_instance *rtp = NULL; iks *description, *transport; /* Ignore specific parts if they are known not to be useful */ if (!strcmp(iks_name(content), "conference-info")) { continue; } name = iks_find_attrib(content, "name"); if (session->transport != JINGLE_TRANSPORT_GOOGLE_V1) { /* If this content stanza has no name consider it invalid and move on */ if (ast_strlen_zero(name) && !(name = iks_find_attrib(content, "jin:name"))) { jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_log(LOG_ERROR, "Received content without a name on session '%s'\n", session->sid); return -1; } /* Try to pre-populate which RTP instance this content is relevant to */ if (!strcmp(session->audio_name, name)) { rtp = session->rtp; } else if (!strcmp(session->video_name, name)) { rtp = session->vrtp; } } else { /* Google-V1 has no concept of assocating things like the above does, so since we only support audio over it assume they want audio */ rtp = session->rtp; } /* If description information is available use it */ if ((description = iks_find_with_attrib(content, "description", "xmlns", JINGLE_RTP_NS)) || (description = iks_find_with_attrib(content, "rtp:description", "xmlns:rtp", JINGLE_RTP_NS)) || (description = iks_find_with_attrib(content, "pho:description", "xmlns:pho", GOOGLE_PHONE_NS)) || (description = iks_find_with_attrib(pak->query, "description", "xmlns", GOOGLE_PHONE_NS)) || (description = iks_find_with_attrib(pak->query, "pho:description", "xmlns:pho", GOOGLE_PHONE_NS)) || (description = iks_find_with_attrib(pak->query, "vid:description", "xmlns", GOOGLE_VIDEO_NS))) { /* If we failed to do something with the content description abort immediately */ if (jingle_interpret_description(session, description, name, &rtp)) { return -1; } /* If we successfully interpret the description then the codecs need updating */ changed = 1; } /* If we get past the description handling and we still don't know what RTP instance this is for... it is unknown content */ if (!rtp) { ast_log(LOG_ERROR, "Received a content stanza but have no RTP instance for it on session '%s'\n", session->sid); jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION); return -1; } /* If ICE UDP transport information is available use it */ if ((transport = iks_find_with_attrib(content, "transport", "xmlns", JINGLE_ICE_UDP_NS))) { if (jingle_interpret_ice_udp_transport(session, transport, rtp)) { return -1; } } else if ((transport = iks_find_with_attrib(content, "transport", "xmlns", GOOGLE_TRANSPORT_NS)) || (transport = iks_find_with_attrib(content, "p:transport", "xmlns:p", GOOGLE_TRANSPORT_NS)) || (transport = iks_find_with_attrib(pak->x, "session", "xmlns", GOOGLE_SESSION_NS)) || (transport = iks_find_with_attrib(pak->x, "ses:session", "xmlns:ses", GOOGLE_SESSION_NS))) { /* If Google transport support is available use it */ if (jingle_interpret_google_transport(session, transport, rtp)) { return -1; } } else if (iks_find(content, "transport")) { /* If this is a transport we do not support terminate the session as it probably won't work out in the end */ jingle_queue_hangup_with_cause(session, AST_CAUSE_FACILITY_NOT_IMPLEMENTED); ast_log(LOG_ERROR, "Unsupported transport type received on session '%s'\n", session->sid); return -1; } } if (!changed) { return 0; } if ((chan = jingle_session_lock_full(session))) { struct ast_format_cap *caps; struct ast_format *fmt; caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append_from_cap(caps, session->jointcap, AST_MEDIA_TYPE_UNKNOWN); ast_channel_nativeformats_set(chan, caps); ao2_ref(caps, -1); } fmt = ast_format_cap_get_format(session->jointcap, 0); ast_set_read_format(chan, fmt); ast_set_write_format(chan, fmt); ao2_ref(fmt, -1); ast_channel_unlock(chan); ast_channel_unref(chan); } ao2_unlock(session); return 0; } /*! \brief Handler function for the 'session-initiate' action */ static void jingle_action_session_initiate(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak) { char *sid; enum jingle_transport transport = JINGLE_TRANSPORT_NONE; struct ast_channel *chan; int res; if (session) { /* This is a duplicate session setup, so respond accordingly */ jingle_send_error_response(endpoint->connection, pak, "result", "out-of-order", NULL); return; } /* Retrieve the session identifier from the message, note that this may alter the transport */ if ((sid = iks_find_attrib(pak->query, "id"))) { /* The presence of the session identifier in the 'id' attribute tells us that this is Google-V1 as everything else uses 'sid' */ transport = JINGLE_TRANSPORT_GOOGLE_V1; } else if (!(sid = iks_find_attrib(pak->query, "sid"))) { jingle_send_error_response(endpoint->connection, pak, "bad-request", NULL, NULL); return; } /* Create a new local session */ if (!(session = jingle_alloc(endpoint, pak->from->full, sid))) { jingle_send_error_response(endpoint->connection, pak, "cancel", "service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", NULL); return; } /* If we determined that the transport should change as a result of how we got the SID change it */ if (transport != JINGLE_TRANSPORT_NONE) { session->transport = transport; } /* Create a new Asterisk channel using the above local session */ if (!(chan = jingle_new(endpoint, session, AST_STATE_DOWN, pak->from->user, NULL, NULL, pak->from->full))) { ao2_ref(session, -1); jingle_send_error_response(endpoint->connection, pak, "cancel", "service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", NULL); return; } ao2_link(endpoint->state->sessions, session); ast_channel_lock(chan); ast_setstate(chan, AST_STATE_RING); ast_channel_unlock(chan); res = ast_pbx_start(chan); switch (res) { case AST_PBX_FAILED: ast_log(LOG_WARNING, "Failed to start PBX :(\n"); jingle_send_error_response(endpoint->connection, pak, "cancel", "service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", NULL); session->gone = 1; ast_hangup(chan); break; case AST_PBX_CALL_LIMIT: ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n"); jingle_send_error_response(endpoint->connection, pak, "wait", "resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", NULL); ast_hangup(chan); break; case AST_PBX_SUCCESS: jingle_send_response(endpoint->connection, pak); /* Only send a transport-info message if we successfully interpreted the available content */ if (!jingle_interpret_content(session, pak)) { jingle_send_transport_info(session, iks_find_attrib(pak->x, "from")); } break; } } /*! \brief Handler function for the 'transport-info' action */ static void jingle_action_transport_info(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak) { if (!session) { jingle_send_error_response(endpoint->connection, pak, "cancel", "item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", "unknown-session xmlns='urn:xmpp:jingle:errors:1'"); return; } jingle_interpret_content(session, pak); jingle_send_response(endpoint->connection, pak); } /*! \brief Handler function for the 'session-accept' action */ static void jingle_action_session_accept(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak) { struct ast_channel *chan; if (!session) { jingle_send_error_response(endpoint->connection, pak, "cancel", "item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", "unknown-session xmlns='urn:xmpp:jingle:errors:1'"); return; } jingle_interpret_content(session, pak); if ((chan = jingle_session_lock_full(session))) { ast_queue_control(chan, AST_CONTROL_ANSWER); ast_channel_unlock(chan); ast_channel_unref(chan); } ao2_unlock(session); jingle_send_response(endpoint->connection, pak); } /*! \brief Handler function for the 'session-info' action */ static void jingle_action_session_info(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak) { struct ast_channel *chan; if (!session) { jingle_send_error_response(endpoint->connection, pak, "cancel", "item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", "unknown-session xmlns='urn:xmpp:jingle:errors:1'"); return; } if (!(chan = jingle_session_lock_full(session))) { ao2_unlock(session); jingle_send_response(endpoint->connection, pak); return; } if (iks_find_with_attrib(pak->query, "ringing", "xmlns", JINGLE_RTP_INFO_NS)) { ast_queue_control(chan, AST_CONTROL_RINGING); if (ast_channel_state(chan) != AST_STATE_UP) { ast_setstate(chan, AST_STATE_RINGING); } } else if (iks_find_with_attrib(pak->query, "hold", "xmlns", JINGLE_RTP_INFO_NS)) { ast_queue_hold(chan, NULL); } else if (iks_find_with_attrib(pak->query, "unhold", "xmlns", JINGLE_RTP_INFO_NS)) { ast_queue_unhold(chan); } ast_channel_unlock(chan); ast_channel_unref(chan); ao2_unlock(session); jingle_send_response(endpoint->connection, pak); } /*! \brief Handler function for the 'session-terminate' action */ static void jingle_action_session_terminate(struct jingle_endpoint *endpoint, struct jingle_session *session, ikspak *pak) { struct ast_channel *chan; iks *reason, *text; int cause = AST_CAUSE_NORMAL; struct ast_control_pvt_cause_code *cause_code; int data_size = sizeof(*cause_code); if (!session) { jingle_send_error_response(endpoint->connection, pak, "cancel", "item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'", "unknown-session xmlns='urn:xmpp:jingle:errors:1'"); return; } if (!(chan = jingle_session_lock_full(session))) { ao2_unlock(session); jingle_send_response(endpoint->connection, pak); return; } /* Pull the reason text from the session-terminate message and translate it into a cause code */ if ((reason = iks_find(pak->query, "reason")) && (text = iks_child(reason))) { int i; /* Size of the string making up the cause code is "Motif " + text */ data_size += 6 + strlen(iks_name(text)); cause_code = ast_alloca(data_size); memset(cause_code, 0, data_size); /* Get the appropriate cause code mapping for this reason */ for (i = 0; i < ARRAY_LEN(jingle_reason_mappings); i++) { if (!strcasecmp(jingle_reason_mappings[i].reason, iks_name(text))) { cause = jingle_reason_mappings[i].cause; break; } } /* Store the technology specific information */ snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "Motif %s", iks_name(text)); } else { /* No technology specific information is available */ cause_code = ast_alloca(data_size); memset(cause_code, 0, data_size); } ast_copy_string(cause_code->chan_name, ast_channel_name(chan), AST_CHANNEL_NAME); cause_code->ast_cause = cause; ast_queue_control_data(chan, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size); ast_channel_hangupcause_hash_set(chan, cause_code, data_size); ast_debug(3, "Hanging up channel '%s' due to session terminate message with cause '%d'\n", ast_channel_name(chan), cause); ast_queue_hangup_with_cause(chan, cause); session->gone = 1; ast_channel_unlock(chan); ast_channel_unref(chan); ao2_unlock(session); jingle_send_response(endpoint->connection, pak); } /*! \brief Callback for when a Jingle action is received from an endpoint */ static int jingle_action_hook(void *data, ikspak *pak) { char *action; const char *sid = NULL; struct jingle_session *session = NULL; struct jingle_endpoint *endpoint = data; int i, handled = 0; /* We accept both Jingle and Google-V1 */ if (!(action = iks_find_attrib(pak->query, "action")) && !(action = iks_find_attrib(pak->query, "type"))) { /* This occurs if either receive a packet masquerading as Jingle or Google-V1 that is actually not OR we receive a response * to a message that has no response hook. */ return IKS_FILTER_EAT; } /* Bump the endpoint reference count up in case a reload occurs. Unfortunately the available synchronization between iksemel and us * does not permit us to make this completely safe. */ ao2_ref(endpoint, +1); /* If a Jingle session identifier is present use it */ if (!(sid = iks_find_attrib(pak->query, "sid"))) { /* If a Google-V1 session identifier is present use it */ sid = iks_find_attrib(pak->query, "id"); } /* If a session identifier was present in the message attempt to find the session, it is up to the action handler whether * this is required or not */ if (!ast_strlen_zero(sid)) { session = ao2_find(endpoint->state->sessions, sid, OBJ_KEY); } /* If a session is present associate the callid with this thread */ if (session) { ast_callid_threadassoc_add(session->callid); } /* Iterate through supported action handlers looking for one that is able to handle this */ for (i = 0; i < ARRAY_LEN(jingle_action_handlers); i++) { if (!strcasecmp(jingle_action_handlers[i].action, action)) { jingle_action_handlers[i].handler(endpoint, session, pak); handled = 1; break; } } /* If no action handler is present for the action they sent us make it evident */ if (!handled) { ast_log(LOG_NOTICE, "Received action '%s' for session '%s' that has no handler\n", action, sid); } /* If a session was successfully found for this message deref it now since the handler is done */ if (session) { ast_callid_threadassoc_remove(); ao2_ref(session, -1); } ao2_ref(endpoint, -1); return IKS_FILTER_EAT; } /*! \brief Custom handler for groups */ static int custom_group_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct jingle_endpoint *endpoint = obj; if (!strcasecmp(var->name, "callgroup")) { endpoint->callgroup = ast_get_group(var->value); } else if (!strcasecmp(var->name, "pickupgroup")) { endpoint->pickupgroup = ast_get_group(var->value); } else { return -1; } return 0; } /*! \brief Custom handler for connection */ static int custom_connection_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct jingle_endpoint *endpoint = obj; /* You might think... but Josh, shouldn't you do this in a prelink callback? Well I *could* but until the original is destroyed * this will not actually get called, so even if the config turns out to be bogus this is harmless. */ if (!(endpoint->connection = ast_xmpp_client_find(var->value))) { ast_log(LOG_ERROR, "Connection '%s' configured on endpoint '%s' could not be found\n", var->value, endpoint->name); return -1; } if (!(endpoint->rule = iks_filter_add_rule(endpoint->connection->filter, jingle_action_hook, endpoint, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_NS, JINGLE_NS, IKS_RULE_NS, GOOGLE_SESSION_NS, IKS_RULE_DONE))) { ast_log(LOG_ERROR, "Action hook could not be added to connection '%s' on endpoint '%s'\n", var->value, endpoint->name); return -1; } return 0; } /*! \brief Custom handler for transport */ static int custom_transport_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct jingle_endpoint *endpoint = obj; if (!strcasecmp(var->value, "ice-udp")) { endpoint->transport = JINGLE_TRANSPORT_ICE_UDP; } else if (!strcasecmp(var->value, "google")) { endpoint->transport = JINGLE_TRANSPORT_GOOGLE_V2; } else if (!strcasecmp(var->value, "google-v1")) { endpoint->transport = JINGLE_TRANSPORT_GOOGLE_V1; } else { ast_log(LOG_WARNING, "Unknown transport type '%s' on endpoint '%s', defaulting to 'ice-udp'\n", var->value, endpoint->name); endpoint->transport = JINGLE_TRANSPORT_ICE_UDP; } return 0; } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { if (!(jingle_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } if (aco_info_init(&cfg_info)) { ast_log(LOG_ERROR, "Unable to intialize configuration for chan_motif.\n"); goto end; } aco_option_register(&cfg_info, "context", ACO_EXACT, endpoint_options, "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct jingle_endpoint, context)); aco_option_register_custom(&cfg_info, "callgroup", ACO_EXACT, endpoint_options, NULL, custom_group_handler, 0); aco_option_register_custom(&cfg_info, "pickupgroup", ACO_EXACT, endpoint_options, NULL, custom_group_handler, 0); aco_option_register(&cfg_info, "language", ACO_EXACT, endpoint_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct jingle_endpoint, language)); aco_option_register(&cfg_info, "musicclass", ACO_EXACT, endpoint_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct jingle_endpoint, musicclass)); aco_option_register(&cfg_info, "parkinglot", ACO_EXACT, endpoint_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct jingle_endpoint, parkinglot)); aco_option_register(&cfg_info, "accountcode", ACO_EXACT, endpoint_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct jingle_endpoint, accountcode)); aco_option_register(&cfg_info, "allow", ACO_EXACT, endpoint_options, "ulaw,alaw", OPT_CODEC_T, 1, FLDSET(struct jingle_endpoint, cap)); aco_option_register(&cfg_info, "disallow", ACO_EXACT, endpoint_options, "all", OPT_CODEC_T, 0, FLDSET(struct jingle_endpoint, cap)); aco_option_register_custom(&cfg_info, "connection", ACO_EXACT, endpoint_options, NULL, custom_connection_handler, 0); aco_option_register_custom(&cfg_info, "transport", ACO_EXACT, endpoint_options, NULL, custom_transport_handler, 0); aco_option_register(&cfg_info, "maxicecandidates", ACO_EXACT, endpoint_options, DEFAULT_MAX_ICE_CANDIDATES, OPT_UINT_T, PARSE_DEFAULT, FLDSET(struct jingle_endpoint, maxicecandidates), DEFAULT_MAX_ICE_CANDIDATES); aco_option_register(&cfg_info, "maxpayloads", ACO_EXACT, endpoint_options, DEFAULT_MAX_PAYLOADS, OPT_UINT_T, PARSE_DEFAULT, FLDSET(struct jingle_endpoint, maxpayloads), DEFAULT_MAX_PAYLOADS); ast_format_cap_append_by_type(jingle_tech.capabilities, AST_MEDIA_TYPE_AUDIO); if (aco_process_config(&cfg_info, 0)) { ast_log(LOG_ERROR, "Unable to read config file motif.conf. Module loaded but not running.\n"); aco_info_destroy(&cfg_info); ao2_cleanup(jingle_tech.capabilities); jingle_tech.capabilities = NULL; return AST_MODULE_LOAD_DECLINE; } if (!(sched = ast_sched_context_create())) { ast_log(LOG_ERROR, "Unable to create scheduler context.\n"); goto end; } if (ast_sched_start_thread(sched)) { ast_log(LOG_ERROR, "Unable to create scheduler context thread.\n"); goto end; } ast_rtp_glue_register(&jingle_rtp_glue); if (ast_channel_register(&jingle_tech)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", channel_type); goto end; } return 0; end: ast_rtp_glue_unregister(&jingle_rtp_glue); if (sched) { ast_sched_context_destroy(sched); } aco_info_destroy(&cfg_info); ao2_global_obj_release(globals); ao2_cleanup(jingle_tech.capabilities); jingle_tech.capabilities = NULL; return AST_MODULE_LOAD_FAILURE; } /*! \brief Reload module */ static int reload(void) { if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) { return -1; } return 0; } /*! \brief Unload the jingle channel from Asterisk */ static int unload_module(void) { ast_channel_unregister(&jingle_tech); ao2_cleanup(jingle_tech.capabilities); jingle_tech.capabilities = NULL; ast_rtp_glue_unregister(&jingle_rtp_glue); ast_sched_context_destroy(sched); aco_info_destroy(&cfg_info); ao2_global_obj_release(globals); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Motif Jingle Channel Driver", .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, ); asterisk-13.1.0/channels/console_video.h0000644000000000000000000001017611032177613016721 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2007 Luigi Rizzo * * 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. */ /* * Common header for console video support * * $Revision: 126572 $ */ #ifndef CONSOLE_VIDEO_H #define CONSOLE_VIDEO_H #if !defined(HAVE_VIDEO_CONSOLE) || !defined(HAVE_FFMPEG) #define CONSOLE_VIDEO_CMDS \ "console {device}" #else #include #ifndef OLD_FFMPEG #include /* requires a recent ffmpeg */ #endif #define CONSOLE_VIDEO_CMDS \ "console {videodevice|videocodec" \ "|video_size|bitrate|fps|qmin" \ "|sendvideo|keypad" \ "|sdl_videodriver" \ "|device|startgui|stopgui" \ "}" #endif /* HAVE_VIDEO_CONSOLE and others */ #define SRC_WIN_W 80 /* width of video thumbnails */ #define SRC_WIN_H 60 /* height of video thumbnails */ /* we only support a limited number of video sources in the GUI, * because we need screen estate to switch between them. */ #define MAX_VIDEO_SOURCES 9 /* * In many places we use buffers to store the raw frames (but not only), * so here is a structure to keep all the info. data = NULL means the * structure is not initialized, so the other fields are invalid. * size = 0 means the buffer is not malloc'ed so we don't have to free it. */ struct fbuf_t { /* frame buffers, dynamically allocated */ uint8_t *data; /* memory, malloced if size > 0, just reference * otherwise */ int size; /* total size in bytes */ int used; /* space used so far */ int ebit; /* bits to ignore at the end */ int x; /* origin, if necessary */ int y; int w; /* size */ int h; int pix_fmt; /* offsets and size of the copy in Picture-in-Picture mode */ int win_x; int win_y; int win_w; int win_h; }; void fbuf_free(struct fbuf_t *); /* descriptor for a grabber */ struct grab_desc { const char *name; void *(*open)(const char *name, struct fbuf_t *geom, int fps); struct fbuf_t *(*read)(void *d); void (*move)(void *d, int dx, int dy); void *(*close)(void *d); }; extern struct grab_desc *console_grabbers[]; struct video_desc; /* opaque type for video support */ struct video_desc *get_video_desc(struct ast_channel *c); /* linked by console_video.o */ int console_write_video(struct ast_channel *chan, struct ast_frame *f); extern int console_video_formats; int console_video_cli(struct video_desc *env, const char *var, int fd); int console_video_config(struct video_desc **penv, const char *var, const char *val); void console_video_uninit(struct video_desc *env); void console_video_start(struct video_desc *env, struct ast_channel *owner); int get_gui_startup(struct video_desc* env); /* console_board.c */ /* Where do we send the keyboard/keypad output */ enum kb_output { KO_NONE, KO_INPUT, /* the local input window */ KO_DIALED, /* the 'dialed number' window */ KO_MESSAGE, /* the 'message' window */ }; enum drag_window { /* which window are we dragging */ DRAG_NONE, DRAG_LOCAL, /* local video */ DRAG_REMOTE, /* remote video */ DRAG_DIALED, /* dialed number */ DRAG_INPUT, /* input window */ DRAG_MESSAGE, /* message window */ DRAG_PIP, /* picture in picture */ }; /*! \brief support for drag actions */ struct drag_info { int x_start; /* last known mouse position */ int y_start; enum drag_window drag_window; }; /*! \brief info related to the gui: button status, mouse coords, etc. */ struct board; /* !\brief print a message on a board */ void move_message_board(struct board *b, int dy); int print_message(struct board *b, const char *s); /*! \brief return the whole text from a board */ const char *read_message(const struct board *b); /*! \brief reset the board to blank */ int reset_board(struct board *b); /*! \brief deallocates memory space for a board */ void delete_board(struct board *b); #endif /* CONSOLE_VIDEO_H */ /* end of file */ asterisk-13.1.0/channels/Makefile0000644000000000000000000000432012355525615015362 0ustar rootroot# # Asterisk -- An open source telephony toolkit. # # Makefile for channel drivers # # Copyright (C) 1999-2006, Digium, Inc. # # This program is free software, distributed under the terms of # the GNU General Public License # -include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps MODULE_PREFIX=chan MENUSELECT_CATEGORY=CHANNELS MENUSELECT_DESCRIPTION=Channel Drivers all: _all include $(ASTTOPDIR)/Makefile.moddir_rules ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) LIBS+= -lres_monitor.so -lres_features.so endif clean:: $(MAKE) -C misdn clean rm -f dahdi/*.o dahdi/*.i rm -f sip/*.o sip/*.i rm -f iax2/*.o iax2/*.i rm -f pjsip/*.o pjsip/*.i $(if $(filter chan_iax2,$(EMBEDDED_MODS)),modules.link,chan_iax2.so): $(subst .c,.o,$(wildcard iax2/*.c)) $(subst .c,.o,$(wildcard iax2/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_iax2) $(if $(filter chan_sip,$(EMBEDDED_MODS)),modules.link,chan_sip.so): $(subst .c,.o,$(wildcard sip/*.c)) $(subst .c,.o,$(wildcard sip/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_sip) $(if $(filter chan_pjsip,$(EMBEDDED_MODS)),modules.link,chan_pjsip.so): $(subst .c,.o,$(wildcard pjsip/*.c)) $(subst .c,.o,$(wildcard pjsip/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_pjsip) # Additional objects to combine with chan_dahdi.so CHAN_DAHDI_OBJS= \ $(subst .c,.o,$(wildcard dahdi/*.c)) \ sig_analog.o \ sig_pri.o \ sig_ss7.o \ $(if $(filter chan_dahdi,$(EMBEDDED_MODS)),modules.link,chan_dahdi.so): $(CHAN_DAHDI_OBJS) $(CHAN_DAHDI_OBJS): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_dahdi) chan_misdn.o: _ASTCFLAGS+=-Imisdn misdn_config.o: _ASTCFLAGS+=-Imisdn misdn/isdn_lib.o: _ASTCFLAGS+=-Wno-strict-aliasing $(if $(filter chan_misdn,$(EMBEDDED_MODS)),modules.link,chan_misdn.so): misdn_config.o misdn/isdn_lib.o misdn/isdn_msg_parser.o misdn_config.o misdn/isdn_lib.o misdn/isdn_msg_parser.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_misdn) $(if $(filter chan_oss,$(EMBEDDED_MODS)),modules.link,chan_oss.so): console_video.o vgrabbers.o console_board.o console_video.o vgrabbers.o console_board.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_oss) chan_usbradio.o: ./xpmr/xpmr.c ./xpmr/xpmr.h ./xpmr/xpmr_coef.h chan_usbradio.so: LIBS+=-lusb -lasound chan_usbradio.so: _ASTCFLAGS+=-DNDEBUG asterisk-13.1.0/channels/sig_pri.c0000644000000000000000000112550012437125770015526 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2009, 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. */ /*! \file * * \brief PRI signaling module * * \author Matthew Fredrickson */ /*** MODULEINFO core ***/ /*** DOCUMENTATION Published when a malicious call ID request arrives. ***/ #include "asterisk.h" #ifdef HAVE_PRI #include #include #include #include "asterisk/utils.h" #include "asterisk/options.h" #include "asterisk/pbx.h" #include "asterisk/app.h" #include "asterisk/file.h" #include "asterisk/callerid.h" #include "asterisk/say.h" #include "asterisk/manager.h" #include "asterisk/astdb.h" #include "asterisk/causes.h" #include "asterisk/musiconhold.h" #include "asterisk/cli.h" #include "asterisk/transcap.h" #include "asterisk/features.h" #include "asterisk/aoc.h" #include "asterisk/bridge.h" #include "asterisk/stasis_channels.h" #include "sig_pri.h" #ifndef PRI_EVENT_FACILITY #error "Upgrade your libpri" #endif /*** DOCUMENTATION ***/ /* define this to send PRI user-user information elements */ #undef SUPPORT_USERUSER /*! * Define to make always pick a channel if allowed. Useful for * testing channel shifting. */ //#define ALWAYS_PICK_CHANNEL 1 /*! * Define to force a RESTART on a channel that returns a cause * code of PRI_CAUSE_REQUESTED_CHAN_UNAVAIL(44). If the cause * is because of a stuck channel on the peer and the channel is * always the next channel we pick for an outgoing call then * this can help. */ #define FORCE_RESTART_UNAVAIL_CHANS 1 #if defined(HAVE_PRI_CCSS) struct sig_pri_cc_agent_prv { /*! Asterisk span D channel control structure. */ struct sig_pri_span *pri; /*! CC id value to use with libpri. -1 if invalid. */ long cc_id; /*! TRUE if CC has been requested and we are waiting for the response. */ unsigned char cc_request_response_pending; }; struct sig_pri_cc_monitor_instance { /*! \brief Asterisk span D channel control structure. */ struct sig_pri_span *pri; /*! CC id value to use with libpri. (-1 if already canceled). */ long cc_id; /*! CC core id value. */ int core_id; /*! Device name(Channel name less sequence number) */ char name[1]; }; /*! Upper level agent/monitor type name. */ static const char *sig_pri_cc_type_name; /*! Container of sig_pri monitor instances. */ static struct ao2_container *sig_pri_cc_monitors; #endif /* defined(HAVE_PRI_CCSS) */ static int pri_matchdigittimeout = 3000; static int pri_gendigittimeout = 8000; #define DCHAN_NOTINALARM (1 << 0) #define DCHAN_UP (1 << 1) /* Defines to help decode the encoded event channel id. */ #define PRI_CHANNEL(p) ((p) & 0xff) #define PRI_SPAN(p) (((p) >> 8) & 0xff) #define PRI_EXPLICIT (1 << 16) #define PRI_CIS_CALL (1 << 17) /* Call is using the D channel only. */ #define PRI_HELD_CALL (1 << 18) #define DCHAN_AVAILABLE (DCHAN_NOTINALARM | DCHAN_UP) static int pri_active_dchan_index(struct sig_pri_span *pri); static const char *sig_pri_call_level2str(enum sig_pri_call_level level) { switch (level) { case SIG_PRI_CALL_LEVEL_IDLE: return "Idle"; case SIG_PRI_CALL_LEVEL_SETUP: return "Setup"; case SIG_PRI_CALL_LEVEL_OVERLAP: return "Overlap"; case SIG_PRI_CALL_LEVEL_PROCEEDING: return "Proceeding"; case SIG_PRI_CALL_LEVEL_ALERTING: return "Alerting"; case SIG_PRI_CALL_LEVEL_DEFER_DIAL: return "DeferDial"; case SIG_PRI_CALL_LEVEL_CONNECT: return "Connect"; } return "Unknown"; } static inline void pri_rel(struct sig_pri_span *pri) { ast_mutex_unlock(&pri->lock); } static unsigned int PVT_TO_CHANNEL(struct sig_pri_chan *p) { int res = (((p)->prioffset) | ((p)->logicalspan << 8) | (p->mastertrunkgroup ? PRI_EXPLICIT : 0)); ast_debug(5, "prioffset: %d mastertrunkgroup: %d logicalspan: %d result: %d\n", p->prioffset, p->mastertrunkgroup, p->logicalspan, res); return res; } static void sig_pri_handle_dchan_exception(struct sig_pri_span *pri, int index) { if (sig_pri_callbacks.handle_dchan_exception) { sig_pri_callbacks.handle_dchan_exception(pri, index); } } static void sig_pri_set_dialing(struct sig_pri_chan *p, int is_dialing) { if (sig_pri_callbacks.set_dialing) { sig_pri_callbacks.set_dialing(p->chan_pvt, is_dialing); } } static void sig_pri_set_digital(struct sig_pri_chan *p, int is_digital) { p->digital = is_digital; if (sig_pri_callbacks.set_digital) { sig_pri_callbacks.set_digital(p->chan_pvt, is_digital); } } static void sig_pri_set_outgoing(struct sig_pri_chan *p, int is_outgoing) { p->outgoing = is_outgoing; if (sig_pri_callbacks.set_outgoing) { sig_pri_callbacks.set_outgoing(p->chan_pvt, is_outgoing); } } void sig_pri_set_alarm(struct sig_pri_chan *p, int in_alarm) { if (sig_pri_is_alarm_ignored(p->pri)) { /* Always set not in alarm */ in_alarm = 0; } /* * Clear the channel restart state when the channel alarm * changes to prevent the state from getting stuck when the link * goes down. */ p->resetting = SIG_PRI_RESET_IDLE; p->inalarm = in_alarm; if (sig_pri_callbacks.set_alarm) { sig_pri_callbacks.set_alarm(p->chan_pvt, in_alarm); } } static const char *sig_pri_get_orig_dialstring(struct sig_pri_chan *p) { if (sig_pri_callbacks.get_orig_dialstring) { return sig_pri_callbacks.get_orig_dialstring(p->chan_pvt); } ast_log(LOG_ERROR, "get_orig_dialstring callback not defined\n"); return ""; } #if defined(HAVE_PRI_CCSS) static void sig_pri_make_cc_dialstring(struct sig_pri_chan *p, char *buf, size_t buf_size) { if (sig_pri_callbacks.make_cc_dialstring) { sig_pri_callbacks.make_cc_dialstring(p->chan_pvt, buf, buf_size); } else { ast_log(LOG_ERROR, "make_cc_dialstring callback not defined\n"); buf[0] = '\0'; } } #endif /* defined(HAVE_PRI_CCSS) */ static void sig_pri_dial_digits(struct sig_pri_chan *p, const char *dial_string) { if (sig_pri_callbacks.dial_digits) { sig_pri_callbacks.dial_digits(p->chan_pvt, dial_string); } } /*! * \internal * \brief Reevaluate the PRI span device state. * \since 1.8 * * \param pri PRI span control structure. * * \return Nothing * * \note Assumes the pri->lock is already obtained. */ static void sig_pri_span_devstate_changed(struct sig_pri_span *pri) { if (sig_pri_callbacks.update_span_devstate) { sig_pri_callbacks.update_span_devstate(pri); } } /*! * \internal * \brief Set the caller id information in the parent module. * \since 1.8 * * \param p sig_pri channel structure. * * \return Nothing */ static void sig_pri_set_caller_id(struct sig_pri_chan *p) { struct ast_party_caller caller; if (sig_pri_callbacks.set_callerid) { ast_party_caller_init(&caller); caller.id.name.str = p->cid_name; caller.id.name.presentation = p->callingpres; caller.id.name.valid = 1; caller.id.number.str = p->cid_num; caller.id.number.plan = p->cid_ton; caller.id.number.presentation = p->callingpres; caller.id.number.valid = 1; if (!ast_strlen_zero(p->cid_subaddr)) { caller.id.subaddress.valid = 1; //caller.id.subaddress.type = 0;/* nsap */ //caller.id.subaddress.odd_even_indicator = 0; caller.id.subaddress.str = p->cid_subaddr; } caller.id.tag = p->user_tag; caller.ani.number.str = p->cid_ani; //caller.ani.number.plan = p->xxx; //caller.ani.number.presentation = p->xxx; caller.ani.number.valid = 1; caller.ani2 = p->cid_ani2; sig_pri_callbacks.set_callerid(p->chan_pvt, &caller); } } /*! * \internal * \brief Set the Dialed Number Identifier. * \since 1.8 * * \param p sig_pri channel structure. * \param dnid Dialed Number Identifier string. * * \return Nothing */ static void sig_pri_set_dnid(struct sig_pri_chan *p, const char *dnid) { if (sig_pri_callbacks.set_dnid) { sig_pri_callbacks.set_dnid(p->chan_pvt, dnid); } } /*! * \internal * \brief Set the Redirecting Directory Number Information Service (RDNIS). * \since 1.8 * * \param p sig_pri channel structure. * \param rdnis Redirecting Directory Number Information Service (RDNIS) string. * * \return Nothing */ static void sig_pri_set_rdnis(struct sig_pri_chan *p, const char *rdnis) { if (sig_pri_callbacks.set_rdnis) { sig_pri_callbacks.set_rdnis(p->chan_pvt, rdnis); } } static void sig_pri_unlock_private(struct sig_pri_chan *p) { if (sig_pri_callbacks.unlock_private) { sig_pri_callbacks.unlock_private(p->chan_pvt); } } static void sig_pri_lock_private(struct sig_pri_chan *p) { if (sig_pri_callbacks.lock_private) { sig_pri_callbacks.lock_private(p->chan_pvt); } } static void sig_pri_deadlock_avoidance_private(struct sig_pri_chan *p) { if (sig_pri_callbacks.deadlock_avoidance_private) { sig_pri_callbacks.deadlock_avoidance_private(p->chan_pvt); } else { /* Fallback to the old way if callback not present. */ sig_pri_unlock_private(p); sched_yield(); sig_pri_lock_private(p); } } static void pri_grab(struct sig_pri_chan *p, struct sig_pri_span *pri) { /* Grab the lock first */ while (ast_mutex_trylock(&pri->lock)) { /* Avoid deadlock */ sig_pri_deadlock_avoidance_private(p); } /* Then break the poll */ if (pri->master != AST_PTHREADT_NULL) { pthread_kill(pri->master, SIGURG); } } /*! * \internal * \brief Convert PRI redirecting reason to asterisk version. * \since 1.8 * * \param pri_reason PRI redirecting reason. * * \return Equivalent asterisk redirecting reason value. */ static enum AST_REDIRECTING_REASON pri_to_ast_reason(int pri_reason) { enum AST_REDIRECTING_REASON ast_reason; switch (pri_reason) { case PRI_REDIR_FORWARD_ON_BUSY: ast_reason = AST_REDIRECTING_REASON_USER_BUSY; break; case PRI_REDIR_FORWARD_ON_NO_REPLY: ast_reason = AST_REDIRECTING_REASON_NO_ANSWER; break; case PRI_REDIR_DEFLECTION: ast_reason = AST_REDIRECTING_REASON_DEFLECTION; break; case PRI_REDIR_UNCONDITIONAL: ast_reason = AST_REDIRECTING_REASON_UNCONDITIONAL; break; case PRI_REDIR_UNKNOWN: default: ast_reason = AST_REDIRECTING_REASON_UNKNOWN; break; } return ast_reason; } /*! * \internal * \brief Convert asterisk redirecting reason to PRI version. * \since 1.8 * * \param ast_reason Asterisk redirecting reason. * * \return Equivalent PRI redirecting reason value. */ static int ast_to_pri_reason(enum AST_REDIRECTING_REASON ast_reason) { int pri_reason; switch (ast_reason) { case AST_REDIRECTING_REASON_USER_BUSY: pri_reason = PRI_REDIR_FORWARD_ON_BUSY; break; case AST_REDIRECTING_REASON_NO_ANSWER: pri_reason = PRI_REDIR_FORWARD_ON_NO_REPLY; break; case AST_REDIRECTING_REASON_UNCONDITIONAL: pri_reason = PRI_REDIR_UNCONDITIONAL; break; case AST_REDIRECTING_REASON_DEFLECTION: pri_reason = PRI_REDIR_DEFLECTION; break; case AST_REDIRECTING_REASON_UNKNOWN: default: pri_reason = PRI_REDIR_UNKNOWN; break; } return pri_reason; } /*! * \internal * \brief Convert PRI number presentation to asterisk version. * \since 1.8 * * \param pri_presentation PRI number presentation. * * \return Equivalent asterisk number presentation value. */ static int pri_to_ast_presentation(int pri_presentation) { int ast_presentation; switch (pri_presentation) { case PRI_PRES_ALLOWED | PRI_PRES_USER_NUMBER_UNSCREENED: ast_presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_UNSCREENED; break; case PRI_PRES_ALLOWED | PRI_PRES_USER_NUMBER_PASSED_SCREEN: ast_presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; break; case PRI_PRES_ALLOWED | PRI_PRES_USER_NUMBER_FAILED_SCREEN: ast_presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_FAILED_SCREEN; break; case PRI_PRES_ALLOWED | PRI_PRES_NETWORK_NUMBER: ast_presentation = AST_PRES_ALLOWED | AST_PRES_NETWORK_NUMBER; break; case PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED: ast_presentation = AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED; break; case PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_PASSED_SCREEN: ast_presentation = AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_PASSED_SCREEN; break; case PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_FAILED_SCREEN: ast_presentation = AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_FAILED_SCREEN; break; case PRI_PRES_RESTRICTED | PRI_PRES_NETWORK_NUMBER: ast_presentation = AST_PRES_RESTRICTED | AST_PRES_NETWORK_NUMBER; break; case PRI_PRES_UNAVAILABLE | PRI_PRES_USER_NUMBER_UNSCREENED: case PRI_PRES_UNAVAILABLE | PRI_PRES_USER_NUMBER_PASSED_SCREEN: case PRI_PRES_UNAVAILABLE | PRI_PRES_USER_NUMBER_FAILED_SCREEN: case PRI_PRES_UNAVAILABLE | PRI_PRES_NETWORK_NUMBER: ast_presentation = AST_PRES_NUMBER_NOT_AVAILABLE; break; default: ast_presentation = AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED; break; } return ast_presentation; } /*! * \internal * \brief Convert asterisk number presentation to PRI version. * \since 1.8 * * \param ast_presentation Asterisk number presentation. * * \return Equivalent PRI number presentation value. */ static int ast_to_pri_presentation(int ast_presentation) { int pri_presentation; switch (ast_presentation) { case AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_UNSCREENED: pri_presentation = PRI_PRES_ALLOWED | PRI_PRES_USER_NUMBER_UNSCREENED; break; case AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN: pri_presentation = PRI_PRES_ALLOWED | PRI_PRES_USER_NUMBER_PASSED_SCREEN; break; case AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_FAILED_SCREEN: pri_presentation = PRI_PRES_ALLOWED | PRI_PRES_USER_NUMBER_FAILED_SCREEN; break; case AST_PRES_ALLOWED | AST_PRES_NETWORK_NUMBER: pri_presentation = PRI_PRES_ALLOWED | PRI_PRES_NETWORK_NUMBER; break; case AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED: pri_presentation = PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED; break; case AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_PASSED_SCREEN: pri_presentation = PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_PASSED_SCREEN; break; case AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_FAILED_SCREEN: pri_presentation = PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_FAILED_SCREEN; break; case AST_PRES_RESTRICTED | AST_PRES_NETWORK_NUMBER: pri_presentation = PRI_PRES_RESTRICTED | PRI_PRES_NETWORK_NUMBER; break; case AST_PRES_UNAVAILABLE | AST_PRES_USER_NUMBER_UNSCREENED: case AST_PRES_UNAVAILABLE | AST_PRES_USER_NUMBER_PASSED_SCREEN: case AST_PRES_UNAVAILABLE | AST_PRES_USER_NUMBER_FAILED_SCREEN: case AST_PRES_UNAVAILABLE | AST_PRES_NETWORK_NUMBER: pri_presentation = PRES_NUMBER_NOT_AVAILABLE; break; default: pri_presentation = PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED; break; } return pri_presentation; } /*! * \internal * \brief Convert PRI name char_set to asterisk version. * \since 1.8 * * \param pri_char_set PRI name char_set. * * \return Equivalent asterisk name char_set value. */ static enum AST_PARTY_CHAR_SET pri_to_ast_char_set(int pri_char_set) { enum AST_PARTY_CHAR_SET ast_char_set; switch (pri_char_set) { default: case PRI_CHAR_SET_UNKNOWN: ast_char_set = AST_PARTY_CHAR_SET_UNKNOWN; break; case PRI_CHAR_SET_ISO8859_1: ast_char_set = AST_PARTY_CHAR_SET_ISO8859_1; break; case PRI_CHAR_SET_WITHDRAWN: ast_char_set = AST_PARTY_CHAR_SET_WITHDRAWN; break; case PRI_CHAR_SET_ISO8859_2: ast_char_set = AST_PARTY_CHAR_SET_ISO8859_2; break; case PRI_CHAR_SET_ISO8859_3: ast_char_set = AST_PARTY_CHAR_SET_ISO8859_3; break; case PRI_CHAR_SET_ISO8859_4: ast_char_set = AST_PARTY_CHAR_SET_ISO8859_4; break; case PRI_CHAR_SET_ISO8859_5: ast_char_set = AST_PARTY_CHAR_SET_ISO8859_5; break; case PRI_CHAR_SET_ISO8859_7: ast_char_set = AST_PARTY_CHAR_SET_ISO8859_7; break; case PRI_CHAR_SET_ISO10646_BMPSTRING: ast_char_set = AST_PARTY_CHAR_SET_ISO10646_BMPSTRING; break; case PRI_CHAR_SET_ISO10646_UTF_8STRING: ast_char_set = AST_PARTY_CHAR_SET_ISO10646_UTF_8STRING; break; } return ast_char_set; } /*! * \internal * \brief Convert asterisk name char_set to PRI version. * \since 1.8 * * \param ast_char_set Asterisk name char_set. * * \return Equivalent PRI name char_set value. */ static int ast_to_pri_char_set(enum AST_PARTY_CHAR_SET ast_char_set) { int pri_char_set; switch (ast_char_set) { default: case AST_PARTY_CHAR_SET_UNKNOWN: pri_char_set = PRI_CHAR_SET_UNKNOWN; break; case AST_PARTY_CHAR_SET_ISO8859_1: pri_char_set = PRI_CHAR_SET_ISO8859_1; break; case AST_PARTY_CHAR_SET_WITHDRAWN: pri_char_set = PRI_CHAR_SET_WITHDRAWN; break; case AST_PARTY_CHAR_SET_ISO8859_2: pri_char_set = PRI_CHAR_SET_ISO8859_2; break; case AST_PARTY_CHAR_SET_ISO8859_3: pri_char_set = PRI_CHAR_SET_ISO8859_3; break; case AST_PARTY_CHAR_SET_ISO8859_4: pri_char_set = PRI_CHAR_SET_ISO8859_4; break; case AST_PARTY_CHAR_SET_ISO8859_5: pri_char_set = PRI_CHAR_SET_ISO8859_5; break; case AST_PARTY_CHAR_SET_ISO8859_7: pri_char_set = PRI_CHAR_SET_ISO8859_7; break; case AST_PARTY_CHAR_SET_ISO10646_BMPSTRING: pri_char_set = PRI_CHAR_SET_ISO10646_BMPSTRING; break; case AST_PARTY_CHAR_SET_ISO10646_UTF_8STRING: pri_char_set = PRI_CHAR_SET_ISO10646_UTF_8STRING; break; } return pri_char_set; } #if defined(HAVE_PRI_SUBADDR) /*! * \internal * \brief Fill in the asterisk party subaddress from the given PRI party subaddress. * \since 1.8 * * \param ast_subaddress Asterisk party subaddress structure. * \param pri_subaddress PRI party subaddress structure. * * \return Nothing * */ static void sig_pri_set_subaddress(struct ast_party_subaddress *ast_subaddress, const struct pri_party_subaddress *pri_subaddress) { ast_free(ast_subaddress->str); if (pri_subaddress->length <= 0) { ast_party_subaddress_init(ast_subaddress); return; } if (!pri_subaddress->type) { /* NSAP */ ast_subaddress->str = ast_strdup((char *) pri_subaddress->data); } else { char *cnum; char *ptr; int x; int len; /* User Specified */ cnum = ast_malloc(2 * pri_subaddress->length + 1); if (!cnum) { ast_party_subaddress_init(ast_subaddress); return; } ptr = cnum; len = pri_subaddress->length - 1; /* -1 account for zero based indexing */ for (x = 0; x < len; ++x) { ptr += sprintf(ptr, "%02x", (unsigned)pri_subaddress->data[x]); } if (pri_subaddress->odd_even_indicator) { /* ODD */ sprintf(ptr, "%01x", (unsigned)((pri_subaddress->data[len]) >> 4)); } else { /* EVEN */ sprintf(ptr, "%02x", (unsigned)pri_subaddress->data[len]); } ast_subaddress->str = cnum; } ast_subaddress->type = pri_subaddress->type; ast_subaddress->odd_even_indicator = pri_subaddress->odd_even_indicator; ast_subaddress->valid = 1; } #endif /* defined(HAVE_PRI_SUBADDR) */ #if defined(HAVE_PRI_SUBADDR) static unsigned char ast_pri_pack_hex_char(char c) { unsigned char res; if (c < '0') { res = 0; } else if (c < ('9' + 1)) { res = c - '0'; } else if (c < 'A') { res = 0; } else if (c < ('F' + 1)) { res = c - 'A' + 10; } else if (c < 'a') { res = 0; } else if (c < ('f' + 1)) { res = c - 'a' + 10; } else { res = 0; } return res; } #endif /* defined(HAVE_PRI_SUBADDR) */ #if defined(HAVE_PRI_SUBADDR) /*! * \internal * \brief Convert a null terminated hexadecimal string to a packed hex byte array. * \details left justified, with 0 padding if odd length. * \since 1.8 * * \param dst pointer to packed byte array. * \param src pointer to null terminated hexadecimal string. * \param maxlen destination array size. * * \return Length of byte array * * \note The dst is not an ASCIIz string. * \note The src is an ASCIIz hex string. */ static int ast_pri_pack_hex_string(unsigned char *dst, char *src, int maxlen) { int res = 0; int len = strlen(src); if (len > (2 * maxlen)) { len = 2 * maxlen; } res = len / 2 + len % 2; while (len > 1) { *dst = ast_pri_pack_hex_char(*src) << 4; src++; *dst |= ast_pri_pack_hex_char(*src); dst++, src++; len -= 2; } if (len) { /* 1 left */ *dst = ast_pri_pack_hex_char(*src) << 4; } return res; } #endif /* defined(HAVE_PRI_SUBADDR) */ #if defined(HAVE_PRI_SUBADDR) /*! * \internal * \brief Fill in the PRI party subaddress from the given asterisk party subaddress. * \since 1.8 * * \param pri_subaddress PRI party subaddress structure. * \param ast_subaddress Asterisk party subaddress structure. * * \return Nothing * * \note Assumes that pri_subaddress has been previously memset to zero. */ static void sig_pri_party_subaddress_from_ast(struct pri_party_subaddress *pri_subaddress, const struct ast_party_subaddress *ast_subaddress) { if (ast_subaddress->valid && !ast_strlen_zero(ast_subaddress->str)) { pri_subaddress->type = ast_subaddress->type; if (!ast_subaddress->type) { /* 0 = NSAP */ ast_copy_string((char *) pri_subaddress->data, ast_subaddress->str, sizeof(pri_subaddress->data)); pri_subaddress->length = strlen((char *) pri_subaddress->data); pri_subaddress->odd_even_indicator = 0; pri_subaddress->valid = 1; } else { /* 2 = User Specified */ /* * Copy HexString to packed HexData, * if odd length then right pad trailing byte with 0 */ int length = ast_pri_pack_hex_string(pri_subaddress->data, ast_subaddress->str, sizeof(pri_subaddress->data)); pri_subaddress->length = length; /* packed data length */ length = strlen(ast_subaddress->str); if (length > 2 * sizeof(pri_subaddress->data)) { pri_subaddress->odd_even_indicator = 0; } else { pri_subaddress->odd_even_indicator = (length & 1); } pri_subaddress->valid = 1; } } } #endif /* defined(HAVE_PRI_SUBADDR) */ /*! * \internal * \brief Fill in the PRI party name from the given asterisk party name. * \since 1.8 * * \param pri_name PRI party name structure. * \param ast_name Asterisk party name structure. * * \return Nothing * * \note Assumes that pri_name has been previously memset to zero. */ static void sig_pri_party_name_from_ast(struct pri_party_name *pri_name, const struct ast_party_name *ast_name) { if (!ast_name->valid) { return; } pri_name->valid = 1; pri_name->presentation = ast_to_pri_presentation(ast_name->presentation); pri_name->char_set = ast_to_pri_char_set(ast_name->char_set); if (!ast_strlen_zero(ast_name->str)) { ast_copy_string(pri_name->str, ast_name->str, sizeof(pri_name->str)); } } /*! * \internal * \brief Fill in the PRI party number from the given asterisk party number. * \since 1.8 * * \param pri_number PRI party number structure. * \param ast_number Asterisk party number structure. * * \return Nothing * * \note Assumes that pri_number has been previously memset to zero. */ static void sig_pri_party_number_from_ast(struct pri_party_number *pri_number, const struct ast_party_number *ast_number) { if (!ast_number->valid) { return; } pri_number->valid = 1; pri_number->presentation = ast_to_pri_presentation(ast_number->presentation); pri_number->plan = ast_number->plan; if (!ast_strlen_zero(ast_number->str)) { ast_copy_string(pri_number->str, ast_number->str, sizeof(pri_number->str)); } } /*! * \internal * \brief Fill in the PRI party id from the given asterisk party id. * \since 1.8 * * \param pri_id PRI party id structure. * \param ast_id Asterisk party id structure. * * \return Nothing * * \note Assumes that pri_id has been previously memset to zero. */ static void sig_pri_party_id_from_ast(struct pri_party_id *pri_id, const struct ast_party_id *ast_id) { sig_pri_party_name_from_ast(&pri_id->name, &ast_id->name); sig_pri_party_number_from_ast(&pri_id->number, &ast_id->number); #if defined(HAVE_PRI_SUBADDR) sig_pri_party_subaddress_from_ast(&pri_id->subaddress, &ast_id->subaddress); #endif /* defined(HAVE_PRI_SUBADDR) */ } /*! * \internal * \brief Update the PRI redirecting information for the current call. * \since 1.8 * * \param pvt sig_pri private channel structure. * \param ast Asterisk channel * * \return Nothing * * \note Assumes that the PRI lock is already obtained. */ static void sig_pri_redirecting_update(struct sig_pri_chan *pvt, struct ast_channel *ast) { struct pri_party_redirecting pri_redirecting; const struct ast_party_redirecting *ast_redirecting; struct ast_party_id redirecting_from = ast_channel_redirecting_effective_from(ast); struct ast_party_id redirecting_to = ast_channel_redirecting_effective_to(ast); struct ast_party_id redirecting_orig = ast_channel_redirecting_effective_orig(ast); memset(&pri_redirecting, 0, sizeof(pri_redirecting)); ast_redirecting = ast_channel_redirecting(ast); sig_pri_party_id_from_ast(&pri_redirecting.from, &redirecting_from); sig_pri_party_id_from_ast(&pri_redirecting.to, &redirecting_to); sig_pri_party_id_from_ast(&pri_redirecting.orig_called, &redirecting_orig); pri_redirecting.count = ast_redirecting->count; pri_redirecting.orig_reason = ast_to_pri_reason(ast_redirecting->orig_reason.code); pri_redirecting.reason = ast_to_pri_reason(ast_redirecting->reason.code); pri_redirecting_update(pvt->pri->pri, pvt->call, &pri_redirecting); } /*! * \internal * \brief Reset DTMF detector. * \since 1.8 * * \param p sig_pri channel structure. * * \return Nothing */ static void sig_pri_dsp_reset_and_flush_digits(struct sig_pri_chan *p) { if (sig_pri_callbacks.dsp_reset_and_flush_digits) { sig_pri_callbacks.dsp_reset_and_flush_digits(p->chan_pvt); } } static int sig_pri_set_echocanceller(struct sig_pri_chan *p, int enable) { if (sig_pri_callbacks.set_echocanceller) { return sig_pri_callbacks.set_echocanceller(p->chan_pvt, enable); } else { return -1; } } static void sig_pri_fixup_chans(struct sig_pri_chan *old_chan, struct sig_pri_chan *new_chan) { if (sig_pri_callbacks.fixup_chans) { sig_pri_callbacks.fixup_chans(old_chan->chan_pvt, new_chan->chan_pvt); } } static int sig_pri_play_tone(struct sig_pri_chan *p, enum sig_pri_tone tone) { if (sig_pri_callbacks.play_tone) { return sig_pri_callbacks.play_tone(p->chan_pvt, tone); } else { return -1; } } static struct ast_channel *sig_pri_new_ast_channel(struct sig_pri_chan *p, int state, enum sig_pri_law law, int transfercapability, char *exten, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_channel *c; if (sig_pri_callbacks.new_ast_channel) { c = sig_pri_callbacks.new_ast_channel(p->chan_pvt, state, law, exten, assignedids, requestor); } else { return NULL; } if (!c) { return NULL; } ast_assert(p->owner == NULL || p->owner == c); p->owner = c; p->isidlecall = 0; p->alreadyhungup = 0; ast_channel_transfercapability_set(c, transfercapability); pbx_builtin_setvar_helper(c, "TRANSFERCAPABILITY", ast_transfercapability2str(transfercapability)); if (transfercapability & AST_TRANS_CAP_DIGITAL) { sig_pri_set_digital(p, 1); } if (p->pri) { ast_mutex_lock(&p->pri->lock); sig_pri_span_devstate_changed(p->pri); ast_mutex_unlock(&p->pri->lock); } return c; } /*! * \internal * \brief Open the PRI channel media path. * \since 1.8 * * \param p Channel private control structure. * * \return Nothing */ static void sig_pri_open_media(struct sig_pri_chan *p) { if (p->no_b_channel) { return; } if (sig_pri_callbacks.open_media) { sig_pri_callbacks.open_media(p->chan_pvt); } } /*! * \internal * \brief Post an AMI B channel association event. * \since 1.8 * * \param p Channel private control structure. * * \note Assumes the private and owner are locked. * * \return Nothing */ static void sig_pri_ami_channel_event(struct sig_pri_chan *p) { if (sig_pri_callbacks.ami_channel_event) { sig_pri_callbacks.ami_channel_event(p->chan_pvt, p->owner); } } struct ast_channel *sig_pri_request(struct sig_pri_chan *p, enum sig_pri_law law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int transfercapability) { struct ast_channel *ast; ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); sig_pri_set_outgoing(p, 1); ast = sig_pri_new_ast_channel(p, AST_STATE_RESERVED, law, transfercapability, p->exten, assignedids, requestor); if (!ast) { sig_pri_set_outgoing(p, 0); } return ast; } int pri_is_up(struct sig_pri_span *pri) { int x; for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if (pri->dchanavail[x] == DCHAN_AVAILABLE) return 1; } return 0; } static const char *pri_order(int level) { switch (level) { case 0: return "Primary"; case 1: return "Secondary"; case 2: return "Tertiary"; case 3: return "Quaternary"; default: return ""; } } /* Returns index of the active dchan */ static int pri_active_dchan_index(struct sig_pri_span *pri) { int x; for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if ((pri->dchans[x] == pri->pri)) return x; } ast_log(LOG_WARNING, "No active dchan found!\n"); return -1; } static void pri_find_dchan(struct sig_pri_span *pri) { struct pri *old; int oldslot = -1; int newslot = -1; int idx; old = pri->pri; for (idx = 0; idx < SIG_PRI_NUM_DCHANS; ++idx) { if (!pri->dchans[idx]) { /* No more D channels defined on the span. */ break; } if (pri->dchans[idx] == old) { oldslot = idx; } if (newslot < 0 && pri->dchanavail[idx] == DCHAN_AVAILABLE) { newslot = idx; } } /* At this point, idx is a count of how many D-channels are defined on the span. */ if (1 < idx) { /* We have several D-channels defined on the span. (NFAS PRI setup) */ if (newslot < 0) { /* No D-channels available. Default to the primary D-channel. */ newslot = 0; if (!pri->no_d_channels) { pri->no_d_channels = 1; if (old && oldslot != newslot) { ast_log(LOG_WARNING, "Span %d: No D-channels up! Switching selected D-channel from %s to %s.\n", pri->span, pri_order(oldslot), pri_order(newslot)); } else { ast_log(LOG_WARNING, "Span %d: No D-channels up!\n", pri->span); } } } else { pri->no_d_channels = 0; } if (old && oldslot != newslot) { ast_log(LOG_NOTICE, "Switching selected D-channel from %s (fd %d) to %s (fd %d)!\n", pri_order(oldslot), pri->fds[oldslot], pri_order(newslot), pri->fds[newslot]); } } else { if (newslot < 0) { /* The only D-channel is not up. */ newslot = 0; if (!pri->no_d_channels) { pri->no_d_channels = 1; /* * This is annoying to see on non-persistent layer 2 * connections. Let's not complain in that case. */ if (pri->sig != SIG_BRI_PTMP) { ast_log(LOG_WARNING, "Span %d: D-channel is down!\n", pri->span); } } } else { pri->no_d_channels = 0; } } pri->pri = pri->dchans[newslot]; } /*! * \internal * \brief Determine if a private channel structure is in use. * \since 1.8 * * \param pvt Channel to determine if in use. * * \return TRUE if the channel is in use. */ static int sig_pri_is_chan_in_use(struct sig_pri_chan *pvt) { return pvt->owner || pvt->call || pvt->allocated || pvt->inalarm || pvt->resetting != SIG_PRI_RESET_IDLE; } /*! * \brief Determine if a private channel structure is available. * \since 1.8 * * \param pvt Channel to determine if available. * * \return TRUE if the channel is available. */ int sig_pri_is_chan_available(struct sig_pri_chan *pvt) { return !sig_pri_is_chan_in_use(pvt) #if defined(HAVE_PRI_SERVICE_MESSAGES) /* And not out-of-service */ && !pvt->service_status #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ ; } /*! * \internal * \brief Obtain the sig_pri owner channel lock if the owner exists. * \since 1.8 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_pri_lock_owner(struct sig_pri_span *pri, int chanpos) { for (;;) { if (!pri->pvts[chanpos]->owner) { /* There is no owner lock to get. */ break; } if (!ast_channel_trylock(pri->pvts[chanpos]->owner)) { /* We got the lock */ break; } /* Avoid deadlock */ sig_pri_unlock_private(pri->pvts[chanpos]); DEADLOCK_AVOIDANCE(&pri->lock); sig_pri_lock_private(pri->pvts[chanpos]); } } /*! * \internal * \brief Queue the given frame onto the owner channel. * \since 1.8 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param frame Frame to queue onto the owner channel. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void pri_queue_frame(struct sig_pri_span *pri, int chanpos, struct ast_frame *frame) { sig_pri_lock_owner(pri, chanpos); if (pri->pvts[chanpos]->owner) { ast_queue_frame(pri->pvts[chanpos]->owner, frame); ast_channel_unlock(pri->pvts[chanpos]->owner); } } /*! * \internal * \brief Queue a hold frame onto the owner channel. * \since 12 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_pri_queue_hold(struct sig_pri_span *pri, int chanpos) { sig_pri_lock_owner(pri, chanpos); if (pri->pvts[chanpos]->owner) { ast_queue_hold(pri->pvts[chanpos]->owner, NULL); ast_channel_unlock(pri->pvts[chanpos]->owner); } } /*! * \internal * \brief Queue an unhold frame onto the owner channel. * \since 12 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_pri_queue_unhold(struct sig_pri_span *pri, int chanpos) { sig_pri_lock_owner(pri, chanpos); if (pri->pvts[chanpos]->owner) { ast_queue_unhold(pri->pvts[chanpos]->owner); ast_channel_unlock(pri->pvts[chanpos]->owner); } } /*! * \internal * \brief Queue a control frame of the specified subclass onto the owner channel. * \since 1.8 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param subclass Control frame subclass to queue onto the owner channel. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void pri_queue_control(struct sig_pri_span *pri, int chanpos, int subclass) { struct ast_frame f = {AST_FRAME_CONTROL, }; struct sig_pri_chan *p = pri->pvts[chanpos]; if (sig_pri_callbacks.queue_control) { sig_pri_callbacks.queue_control(p->chan_pvt, subclass); } f.subclass.integer = subclass; pri_queue_frame(pri, chanpos, &f); } /*! * \internal * \brief Queue a PVT_CAUSE_CODE frame onto the owner channel. * \since 11 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param cause String describing the cause to be placed into the frame. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void pri_queue_pvt_cause_data(struct sig_pri_span *pri, int chanpos, const char *cause, int ast_cause) { struct ast_channel *chan; struct ast_control_pvt_cause_code *cause_code; sig_pri_lock_owner(pri, chanpos); chan = pri->pvts[chanpos]->owner; if (chan) { int datalen = sizeof(*cause_code) + strlen(cause); cause_code = ast_alloca(datalen); memset(cause_code, 0, datalen); cause_code->ast_cause = ast_cause; ast_copy_string(cause_code->chan_name, ast_channel_name(chan), AST_CHANNEL_NAME); ast_copy_string(cause_code->code, cause, datalen + 1 - sizeof(*cause_code)); ast_queue_control_data(chan, AST_CONTROL_PVT_CAUSE_CODE, cause_code, datalen); ast_channel_hangupcause_hash_set(chan, cause_code, datalen); ast_channel_unlock(chan); } } /*! * \internal * \brief Find the channel associated with the libpri call. * \since 10.0 * * \param pri PRI span control structure. * \param call LibPRI opaque call pointer to find. * * \note Assumes the pri->lock is already obtained. * * \retval array-index into private pointer array on success. * \retval -1 on error. */ static int pri_find_principle_by_call(struct sig_pri_span *pri, q931_call *call) { int idx; if (!call) { /* Cannot find a call without a call. */ return -1; } for (idx = 0; idx < pri->numchans; ++idx) { if (pri->pvts[idx] && pri->pvts[idx]->call == call) { /* Found the principle */ return idx; } } return -1; } /*! * \internal * \brief Queue the span for destruction * \since 13.0 * * \param pri PRI span control structure. * * Asks the channel driver to queue the span for destruction at a * possibly later time, if (e.g.) locking considerations don't allow * destroying it right now. * * \return Nothing */ static void pri_destroy_later(struct sig_pri_span *pri) { if (!sig_pri_callbacks.destroy_later) { return; } sig_pri_callbacks.destroy_later(pri); } /*! * \internal * \brief Kill the call. * \since 10.0 * * \param pri PRI span control structure. * \param call LibPRI opaque call pointer to find. * \param cause Reason call was killed. * * \note Assumes the pvt->pri->lock is already obtained. * * \return Nothing */ static void sig_pri_kill_call(struct sig_pri_span *pri, q931_call *call, int cause) { int chanpos; chanpos = pri_find_principle_by_call(pri, call); if (chanpos < 0) { pri_hangup(pri->pri, call, cause); return; } sig_pri_lock_private(pri->pvts[chanpos]); if (!pri->pvts[chanpos]->owner) { pri_hangup(pri->pri, call, cause); pri->pvts[chanpos]->call = NULL; sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); return; } ast_channel_hangupcause_set(pri->pvts[chanpos]->owner, cause); pri_queue_control(pri, chanpos, AST_CONTROL_HANGUP); sig_pri_unlock_private(pri->pvts[chanpos]); } /*! * \internal * \brief Find the private structure for the libpri call. * * \param pri PRI span control structure. * \param channel LibPRI encoded channel ID. * \param call LibPRI opaque call pointer. * * \note Assumes the pri->lock is already obtained. * * \retval array-index into private pointer array on success. * \retval -1 on error. */ static int pri_find_principle(struct sig_pri_span *pri, int channel, q931_call *call) { int x; int span; int principle; int prioffset; if (channel < 0) { /* Channel is not picked yet. */ return -1; } prioffset = PRI_CHANNEL(channel); if (!prioffset || (channel & PRI_HELD_CALL)) { /* Find the call waiting call or held call. */ return pri_find_principle_by_call(pri, call); } span = PRI_SPAN(channel); if (!(channel & PRI_EXPLICIT)) { int index; index = pri_active_dchan_index(pri); if (index == -1) { return -1; } span = pri->dchan_logical_span[index]; } principle = -1; for (x = 0; x < pri->numchans; x++) { if (pri->pvts[x] && pri->pvts[x]->prioffset == prioffset && pri->pvts[x]->logicalspan == span && !pri->pvts[x]->no_b_channel) { principle = x; break; } } return principle; } /*! * \internal * \brief Fixup the private structure associated with the libpri call. * * \param pri PRI span control structure. * \param principle Array-index into private array to move call to if not already there. * \param call LibPRI opaque call pointer to find if need to move call. * * \note Assumes the pri->lock is already obtained. * * \retval principle on success. * \retval -1 on error. */ static int pri_fixup_principle(struct sig_pri_span *pri, int principle, q931_call *call) { int x; if (principle < 0 || pri->numchans <= principle) { /* Out of rannge */ return -1; } if (!call) { /* No call */ return principle; } if (pri->pvts[principle] && pri->pvts[principle]->call == call) { /* Call is already on the specified principle. */ return principle; } /* Find the old principle location. */ for (x = 0; x < pri->numchans; x++) { struct sig_pri_chan *new_chan; struct sig_pri_chan *old_chan; if (!pri->pvts[x] || pri->pvts[x]->call != call) { continue; } /* Found our call */ new_chan = pri->pvts[principle]; old_chan = pri->pvts[x]; /* Get locks to safely move to the new private structure. */ sig_pri_lock_private(old_chan); sig_pri_lock_owner(pri, x); sig_pri_lock_private(new_chan); ast_verb(3, "Moving call (%s) from channel %d to %d.\n", old_chan->owner ? ast_channel_name(old_chan->owner) : "", old_chan->channel, new_chan->channel); if (!sig_pri_is_chan_available(new_chan)) { ast_log(LOG_WARNING, "Can't move call (%s) from channel %d to %d. It is already in use.\n", old_chan->owner ? ast_channel_name(old_chan->owner) : "", old_chan->channel, new_chan->channel); sig_pri_unlock_private(new_chan); if (old_chan->owner) { ast_channel_unlock(old_chan->owner); } sig_pri_unlock_private(old_chan); return -1; } sig_pri_fixup_chans(old_chan, new_chan); /* Fix it all up now */ new_chan->owner = old_chan->owner; old_chan->owner = NULL; new_chan->call = old_chan->call; old_chan->call = NULL; /* Transfer flags from the old channel. */ #if defined(HAVE_PRI_AOC_EVENTS) new_chan->aoc_s_request_invoke_id_valid = old_chan->aoc_s_request_invoke_id_valid; new_chan->waiting_for_aoce = old_chan->waiting_for_aoce; new_chan->holding_aoce = old_chan->holding_aoce; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ new_chan->alreadyhungup = old_chan->alreadyhungup; new_chan->isidlecall = old_chan->isidlecall; new_chan->progress = old_chan->progress; new_chan->allocated = old_chan->allocated; new_chan->outgoing = old_chan->outgoing; new_chan->digital = old_chan->digital; #if defined(HAVE_PRI_CALL_WAITING) new_chan->is_call_waiting = old_chan->is_call_waiting; #endif /* defined(HAVE_PRI_CALL_WAITING) */ #if defined(HAVE_PRI_SETUP_ACK_INBAND) new_chan->no_dialed_digits = old_chan->no_dialed_digits; #endif /* defined(HAVE_PRI_SETUP_ACK_INBAND) */ #if defined(HAVE_PRI_AOC_EVENTS) old_chan->aoc_s_request_invoke_id_valid = 0; old_chan->waiting_for_aoce = 0; old_chan->holding_aoce = 0; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ old_chan->alreadyhungup = 0; old_chan->isidlecall = 0; old_chan->progress = 0; old_chan->allocated = 0; old_chan->outgoing = 0; old_chan->digital = 0; #if defined(HAVE_PRI_CALL_WAITING) old_chan->is_call_waiting = 0; #endif /* defined(HAVE_PRI_CALL_WAITING) */ #if defined(HAVE_PRI_SETUP_ACK_INBAND) old_chan->no_dialed_digits = 0; #endif /* defined(HAVE_PRI_SETUP_ACK_INBAND) */ /* More stuff to transfer to the new channel. */ new_chan->call_level = old_chan->call_level; old_chan->call_level = SIG_PRI_CALL_LEVEL_IDLE; #if defined(HAVE_PRI_REVERSE_CHARGE) new_chan->reverse_charging_indication = old_chan->reverse_charging_indication; #endif /* defined(HAVE_PRI_REVERSE_CHARGE) */ #if defined(HAVE_PRI_SETUP_KEYPAD) strcpy(new_chan->keypad_digits, old_chan->keypad_digits); #endif /* defined(HAVE_PRI_SETUP_KEYPAD) */ strcpy(new_chan->deferred_digits, old_chan->deferred_digits); strcpy(new_chan->moh_suggested, old_chan->moh_suggested); new_chan->moh_state = old_chan->moh_state; old_chan->moh_state = SIG_PRI_MOH_STATE_IDLE; #if defined(HAVE_PRI_TRANSFER) new_chan->xfer_data = old_chan->xfer_data; old_chan->xfer_data = NULL; #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_AOC_EVENTS) new_chan->aoc_s_request_invoke_id = old_chan->aoc_s_request_invoke_id; new_chan->aoc_e = old_chan->aoc_e; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ strcpy(new_chan->user_tag, old_chan->user_tag); if (new_chan->no_b_channel) { /* Copy the real channel configuration to the no B channel interface. */ new_chan->hidecallerid = old_chan->hidecallerid; new_chan->hidecalleridname = old_chan->hidecalleridname; new_chan->immediate = old_chan->immediate; new_chan->priexclusive = old_chan->priexclusive; new_chan->priindication_oob = old_chan->priindication_oob; new_chan->use_callerid = old_chan->use_callerid; new_chan->use_callingpres = old_chan->use_callingpres; new_chan->stripmsd = old_chan->stripmsd; strcpy(new_chan->context, old_chan->context); strcpy(new_chan->mohinterpret, old_chan->mohinterpret); /* Become a member of the old channel span/trunk-group. */ new_chan->logicalspan = old_chan->logicalspan; new_chan->mastertrunkgroup = old_chan->mastertrunkgroup; } else if (old_chan->no_b_channel) { /* * We are transitioning from a held/call-waiting channel to a * real channel so we need to make sure that the media path is * open. (Needed especially if the channel is natively * bridged.) */ sig_pri_open_media(new_chan); } if (new_chan->owner) { sig_pri_ami_channel_event(new_chan); } sig_pri_unlock_private(old_chan); if (new_chan->owner) { ast_channel_unlock(new_chan->owner); } sig_pri_unlock_private(new_chan); return principle; } ast_verb(3, "Call specified, but not found.\n"); return -1; } /*! * \internal * \brief Find and fixup the private structure associated with the libpri call. * * \param pri PRI span control structure. * \param channel LibPRI encoded channel ID. * \param call LibPRI opaque call pointer. * * \details * This is a combination of pri_find_principle() and pri_fixup_principle() * to reduce code redundancy and to make handling several PRI_EVENT_xxx's * consistent for the current architecture. * * \note Assumes the pri->lock is already obtained. * * \retval array-index into private pointer array on success. * \retval -1 on error. */ static int pri_find_fixup_principle(struct sig_pri_span *pri, int channel, q931_call *call) { int chanpos; chanpos = pri_find_principle(pri, channel, call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: PRI requested channel %d/%d is unconfigured.\n", pri->span, PRI_SPAN(channel), PRI_CHANNEL(channel)); sig_pri_kill_call(pri, call, PRI_CAUSE_IDENTIFIED_CHANNEL_NOTEXIST); return -1; } chanpos = pri_fixup_principle(pri, chanpos, call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: PRI requested channel %d/%d is not available.\n", pri->span, PRI_SPAN(channel), PRI_CHANNEL(channel)); /* * Using Q.931 section 5.2.3.1 b) as the reason for picking * PRI_CAUSE_CHANNEL_UNACCEPTABLE. Receiving a * PRI_CAUSE_REQUESTED_CHAN_UNAVAIL would cause us to restart * that channel (which is not specified by Q.931) and kill some * other call which would be bad. */ sig_pri_kill_call(pri, call, PRI_CAUSE_CHANNEL_UNACCEPTABLE); return -1; } return chanpos; } static char * redirectingreason2str(int redirectingreason) { switch (redirectingreason) { case 0: return "UNKNOWN"; case 1: return "BUSY"; case 2: return "NO_REPLY"; case 0xF: return "UNCONDITIONAL"; default: return "NOREDIRECT"; } } static char *dialplan2str(int dialplan) { if (dialplan == -1) { return("Dynamically set dialplan in ISDN"); } return (pri_plan2str(dialplan)); } /*! * \internal * \brief Apply numbering plan prefix to the given number. * * \param buf Buffer to put number into. * \param size Size of given buffer. * \param pri PRI span control structure. * \param number Number to apply numbering plan. * \param plan Numbering plan to apply. * * \return Nothing */ static void apply_plan_to_number(char *buf, size_t size, const struct sig_pri_span *pri, const char *number, int plan) { switch (plan) { case PRI_INTERNATIONAL_ISDN: /* Q.931 dialplan == 0x11 international dialplan => prepend international prefix digits */ snprintf(buf, size, "%s%s", pri->internationalprefix, number); break; case PRI_NATIONAL_ISDN: /* Q.931 dialplan == 0x21 national dialplan => prepend national prefix digits */ snprintf(buf, size, "%s%s", pri->nationalprefix, number); break; case PRI_LOCAL_ISDN: /* Q.931 dialplan == 0x41 local dialplan => prepend local prefix digits */ snprintf(buf, size, "%s%s", pri->localprefix, number); break; case PRI_PRIVATE: /* Q.931 dialplan == 0x49 private dialplan => prepend private prefix digits */ snprintf(buf, size, "%s%s", pri->privateprefix, number); break; case PRI_UNKNOWN: /* Q.931 dialplan == 0x00 unknown dialplan => prepend unknown prefix digits */ snprintf(buf, size, "%s%s", pri->unknownprefix, number); break; default: /* other Q.931 dialplan => don't twiddle with callingnum */ snprintf(buf, size, "%s", number); break; } } /*! * \internal * \brief Apply numbering plan prefix to the given number if the number exists. * * \param buf Buffer to put number into. * \param size Size of given buffer. * \param pri PRI span control structure. * \param number Number to apply numbering plan. * \param plan Numbering plan to apply. * * \return Nothing */ static void apply_plan_to_existing_number(char *buf, size_t size, const struct sig_pri_span *pri, const char *number, int plan) { /* Make sure a number exists so the prefix isn't placed on an empty string. */ if (ast_strlen_zero(number)) { if (size) { *buf = '\0'; } return; } apply_plan_to_number(buf, size, pri, number, plan); } /*! * \internal * \brief Restart the next channel we think is idle on the span. * * \param pri PRI span control structure. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void pri_check_restart(struct sig_pri_span *pri) { #if defined(HAVE_PRI_SERVICE_MESSAGES) unsigned why; #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ for (++pri->resetpos; pri->resetpos < pri->numchans; ++pri->resetpos) { if (!pri->pvts[pri->resetpos] || pri->pvts[pri->resetpos]->no_b_channel || sig_pri_is_chan_in_use(pri->pvts[pri->resetpos])) { continue; } #if defined(HAVE_PRI_SERVICE_MESSAGES) why = pri->pvts[pri->resetpos]->service_status; if (why) { ast_log(LOG_NOTICE, "Span %d: channel %d out-of-service (reason: %s), not sending RESTART\n", pri->span, pri->pvts[pri->resetpos]->channel, (why & SRVST_FAREND) ? (why & SRVST_NEAREND) ? "both ends" : "far end" : "near end"); continue; } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ break; } if (pri->resetpos < pri->numchans) { /* Mark the channel as resetting and restart it */ pri->pvts[pri->resetpos]->resetting = SIG_PRI_RESET_ACTIVE; pri_reset(pri->pri, PVT_TO_CHANNEL(pri->pvts[pri->resetpos])); } else { pri->resetting = 0; time(&pri->lastreset); sig_pri_span_devstate_changed(pri); } } #if defined(HAVE_PRI_CALL_WAITING) /*! * \internal * \brief Init the private channel configuration using the span controller. * \since 1.8 * * \param pvt Channel to init the configuration. * \param pri PRI span control structure. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_init_config(struct sig_pri_chan *pvt, struct sig_pri_span *pri) { pvt->stripmsd = pri->ch_cfg.stripmsd; pvt->hidecallerid = pri->ch_cfg.hidecallerid; pvt->hidecalleridname = pri->ch_cfg.hidecalleridname; pvt->immediate = pri->ch_cfg.immediate; pvt->priexclusive = pri->ch_cfg.priexclusive; pvt->priindication_oob = pri->ch_cfg.priindication_oob; pvt->use_callerid = pri->ch_cfg.use_callerid; pvt->use_callingpres = pri->ch_cfg.use_callingpres; ast_copy_string(pvt->context, pri->ch_cfg.context, sizeof(pvt->context)); ast_copy_string(pvt->mohinterpret, pri->ch_cfg.mohinterpret, sizeof(pvt->mohinterpret)); if (sig_pri_callbacks.init_config) { sig_pri_callbacks.init_config(pvt->chan_pvt, pri); } } #endif /* defined(HAVE_PRI_CALL_WAITING) */ /*! * \internal * \brief Find an empty B-channel interface to use. * * \param pri PRI span control structure. * \param backwards TRUE if the search starts from higher channels. * * \note Assumes the pri->lock is already obtained. * * \retval array-index into private pointer array on success. * \retval -1 on error. */ static int pri_find_empty_chan(struct sig_pri_span *pri, int backwards) { int x; if (backwards) x = pri->numchans; else x = 0; for (;;) { if (backwards && (x < 0)) break; if (!backwards && (x >= pri->numchans)) break; if (pri->pvts[x] && !pri->pvts[x]->no_b_channel && sig_pri_is_chan_available(pri->pvts[x])) { ast_debug(1, "Found empty available channel %d/%d\n", pri->pvts[x]->logicalspan, pri->pvts[x]->prioffset); return x; } if (backwards) x--; else x++; } return -1; } #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Find or create an empty no-B-channel interface to use. * \since 1.8 * * \param pri PRI span control structure. * * \note Assumes the pri->lock is already obtained. * * \retval array-index into private pointer array on success. * \retval -1 on error. */ static int pri_find_empty_nobch(struct sig_pri_span *pri) { int idx; for (idx = 0; idx < pri->numchans; ++idx) { if (pri->pvts[idx] && pri->pvts[idx]->no_b_channel && sig_pri_is_chan_available(pri->pvts[idx])) { ast_debug(1, "Found empty available no B channel interface\n"); return idx; } } /* Need to create a new interface. */ if (sig_pri_callbacks.new_nobch_intf) { idx = sig_pri_callbacks.new_nobch_intf(pri); } else { idx = -1; } return idx; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ static void *do_idle_thread(void *v_pvt) { struct sig_pri_chan *pvt = v_pvt; struct ast_channel *chan = pvt->owner; struct ast_frame *f; char ex[80]; /* Wait up to 30 seconds for an answer */ int timeout_ms = 30000; int ms; struct timeval start; struct ast_callid *callid; if ((callid = ast_channel_callid(chan))) { ast_callid_threadassoc_add(callid); callid = ast_callid_unref(callid); } ast_verb(3, "Initiating idle call on channel %s\n", ast_channel_name(chan)); snprintf(ex, sizeof(ex), "%d/%s", pvt->channel, pvt->pri->idledial); if (ast_call(chan, ex, 0)) { ast_log(LOG_WARNING, "Idle dial failed on '%s' to '%s'\n", ast_channel_name(chan), ex); ast_hangup(chan); return NULL; } start = ast_tvnow(); while ((ms = ast_remaining_ms(start, timeout_ms))) { if (ast_waitfor(chan, ms) <= 0) { break; } f = ast_read(chan); if (!f) { /* Got hangup */ break; } if (f->frametype == AST_FRAME_CONTROL) { switch (f->subclass.integer) { case AST_CONTROL_ANSWER: /* Launch the PBX */ ast_channel_exten_set(chan, pvt->pri->idleext); ast_channel_context_set(chan, pvt->pri->idlecontext); ast_channel_priority_set(chan, 1); ast_verb(4, "Idle channel '%s' answered, sending to %s@%s\n", ast_channel_name(chan), ast_channel_exten(chan), ast_channel_context(chan)); ast_pbx_run(chan); /* It's already hungup, return immediately */ return NULL; case AST_CONTROL_BUSY: ast_verb(4, "Idle channel '%s' busy, waiting...\n", ast_channel_name(chan)); break; case AST_CONTROL_CONGESTION: ast_verb(4, "Idle channel '%s' congested, waiting...\n", ast_channel_name(chan)); break; }; } ast_frfree(f); } /* Hangup the channel since nothing happend */ ast_hangup(chan); return NULL; } static void *pri_ss_thread(void *data) { struct sig_pri_chan *p = data; struct ast_channel *chan = p->owner; char exten[AST_MAX_EXTENSION]; int res; int len; int timeout; struct ast_callid *callid; if (!chan) { /* We lost the owner before we could get started. */ return NULL; } if ((callid = ast_channel_callid(chan))) { ast_callid_threadassoc_add(callid); ast_callid_unref(callid); } /* * In the bizarre case where the channel has become a zombie before we * even get started here, abort safely. */ if (!ast_channel_tech_pvt(chan)) { ast_log(LOG_WARNING, "Channel became a zombie before simple switch could be started (%s)\n", ast_channel_name(chan)); ast_hangup(chan); return NULL; } ast_verb(3, "Starting simple switch on '%s'\n", ast_channel_name(chan)); sig_pri_dsp_reset_and_flush_digits(p); /* Now loop looking for an extension */ ast_copy_string(exten, p->exten, sizeof(exten)); len = strlen(exten); res = 0; while ((len < AST_MAX_EXTENSION-1) && ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { if (len && !ast_ignore_pattern(ast_channel_context(chan), exten)) sig_pri_play_tone(p, -1); else sig_pri_play_tone(p, SIG_PRI_TONE_DIALTONE); if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) timeout = pri_matchdigittimeout; else timeout = pri_gendigittimeout; res = ast_waitfordigit(chan, timeout); if (res < 0) { ast_debug(1, "waitfordigit returned < 0...\n"); ast_hangup(chan); return NULL; } else if (res) { exten[len++] = res; exten[len] = '\0'; } else break; } /* if no extension was received ('unspecified') on overlap call, use the 's' extension */ if (ast_strlen_zero(exten)) { ast_verb(3, "Going to extension s|1 because of empty extension received on overlap call\n"); exten[0] = 's'; exten[1] = '\0'; } else { ast_free(ast_channel_dialed(chan)->number.str); ast_channel_dialed(chan)->number.str = ast_strdup(exten); if (p->pri->append_msn_to_user_tag && p->pri->nodetype != PRI_NETWORK) { /* * Update the user tag for party id's from this device for this call * now that we have a complete MSN from the network. */ snprintf(p->user_tag, sizeof(p->user_tag), "%s_%s", p->pri->initial_user_tag, exten); ast_free(ast_channel_caller(chan)->id.tag); ast_channel_caller(chan)->id.tag = ast_strdup(p->user_tag); } } sig_pri_play_tone(p, -1); if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { /* Start the real PBX */ ast_channel_exten_set(chan, exten); sig_pri_dsp_reset_and_flush_digits(p); #if defined(JIRA_ASTERISK_15594) /* * Conditionaled out this code to effectively revert the JIRA * ASTERISK-15594 change. It breaks overlap dialing through * Asterisk. There is not enough information available at this * point to know if dialing is complete. The * ast_exists_extension(), ast_matchmore_extension(), and * ast_canmatch_extension() calls are not adequate to detect a * dial through extension pattern of "_9!". * * Workaround is to use the dialplan Proceeding() application * early on non-dial through extensions. */ if ((p->pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING) && !ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { sig_pri_lock_private(p); if (p->pri->pri) { pri_grab(p, p->pri); if (p->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING) { p->call_level = SIG_PRI_CALL_LEVEL_PROCEEDING; } pri_proceeding(p->pri->pri, p->call, PVT_TO_CHANNEL(p), 0); pri_rel(p->pri); } sig_pri_unlock_private(p); } #endif /* defined(JIRA_ASTERISK_15594) */ sig_pri_set_echocanceller(p, 1); ast_channel_lock(chan); ast_setstate(chan, AST_STATE_RING); ast_channel_unlock(chan); res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero!\n"); } } else { ast_debug(1, "No such possible extension '%s' in context '%s'\n", exten, ast_channel_context(chan)); ast_channel_hangupcause_set(chan, AST_CAUSE_UNALLOCATED); ast_hangup(chan); p->exten[0] = '\0'; /* Since we send release complete here, we won't get one */ p->call = NULL; ast_mutex_lock(&p->pri->lock); sig_pri_span_devstate_changed(p->pri); ast_mutex_unlock(&p->pri->lock); } return NULL; } void pri_event_alarm(struct sig_pri_span *pri, int index, int before_start_pri) { pri->dchanavail[index] &= ~(DCHAN_NOTINALARM | DCHAN_UP); if (!before_start_pri) { pri_find_dchan(pri); } } void pri_event_noalarm(struct sig_pri_span *pri, int index, int before_start_pri) { pri->dchanavail[index] |= DCHAN_NOTINALARM; if (!before_start_pri) pri_restart(pri->dchans[index]); } /*! * \internal * \brief Convert libpri party name into asterisk party name. * \since 1.8 * * \param ast_name Asterisk party name structure to fill. Must already be set initialized. * \param pri_name libpri party name structure containing source information. * * \note The filled in ast_name structure needs to be destroyed by * ast_party_name_free() when it is no longer needed. * * \return Nothing */ static void sig_pri_party_name_convert(struct ast_party_name *ast_name, const struct pri_party_name *pri_name) { ast_name->str = ast_strdup(pri_name->str); ast_name->char_set = pri_to_ast_char_set(pri_name->char_set); ast_name->presentation = pri_to_ast_presentation(pri_name->presentation); ast_name->valid = 1; } /*! * \internal * \brief Convert libpri party number into asterisk party number. * \since 1.8 * * \param ast_number Asterisk party number structure to fill. Must already be set initialized. * \param pri_number libpri party number structure containing source information. * \param pri PRI span control structure. * * \note The filled in ast_number structure needs to be destroyed by * ast_party_number_free() when it is no longer needed. * * \return Nothing */ static void sig_pri_party_number_convert(struct ast_party_number *ast_number, const struct pri_party_number *pri_number, struct sig_pri_span *pri) { char number[AST_MAX_EXTENSION]; apply_plan_to_existing_number(number, sizeof(number), pri, pri_number->str, pri_number->plan); ast_number->str = ast_strdup(number); ast_number->plan = pri_number->plan; ast_number->presentation = pri_to_ast_presentation(pri_number->presentation); ast_number->valid = 1; } /*! * \internal * \brief Convert libpri party id into asterisk party id. * \since 1.8 * * \param ast_id Asterisk party id structure to fill. Must already be set initialized. * \param pri_id libpri party id structure containing source information. * \param pri PRI span control structure. * * \note The filled in ast_id structure needs to be destroyed by * ast_party_id_free() when it is no longer needed. * * \return Nothing */ static void sig_pri_party_id_convert(struct ast_party_id *ast_id, const struct pri_party_id *pri_id, struct sig_pri_span *pri) { if (pri_id->name.valid) { sig_pri_party_name_convert(&ast_id->name, &pri_id->name); } if (pri_id->number.valid) { sig_pri_party_number_convert(&ast_id->number, &pri_id->number, pri); } #if defined(HAVE_PRI_SUBADDR) if (pri_id->subaddress.valid) { sig_pri_set_subaddress(&ast_id->subaddress, &pri_id->subaddress); } #endif /* defined(HAVE_PRI_SUBADDR) */ } /*! * \internal * \brief Convert libpri redirecting information into asterisk redirecting information. * \since 1.8 * * \param ast_redirecting Asterisk redirecting structure to fill. * \param pri_redirecting libpri redirecting structure containing source information. * \param ast_guide Asterisk redirecting structure to use as an initialization guide. * \param pri PRI span control structure. * * \note The filled in ast_redirecting structure needs to be destroyed by * ast_party_redirecting_free() when it is no longer needed. * * \return Nothing */ static void sig_pri_redirecting_convert(struct ast_party_redirecting *ast_redirecting, const struct pri_party_redirecting *pri_redirecting, const struct ast_party_redirecting *ast_guide, struct sig_pri_span *pri) { ast_party_redirecting_set_init(ast_redirecting, ast_guide); sig_pri_party_id_convert(&ast_redirecting->orig, &pri_redirecting->orig_called, pri); sig_pri_party_id_convert(&ast_redirecting->from, &pri_redirecting->from, pri); sig_pri_party_id_convert(&ast_redirecting->to, &pri_redirecting->to, pri); ast_redirecting->count = pri_redirecting->count; ast_redirecting->reason.code = pri_to_ast_reason(pri_redirecting->reason); ast_redirecting->orig_reason.code = pri_to_ast_reason(pri_redirecting->orig_reason); } /*! * \internal * \brief Determine if the given extension matches one of the MSNs in the pattern list. * \since 1.8 * * \param msn_patterns Comma separated list of MSN patterns to match. * \param exten Extension to match in the MSN list. * * \retval 1 if matches. * \retval 0 if no match. */ static int sig_pri_msn_match(const char *msn_patterns, const char *exten) { char *pattern; char *msn_list; char *list_tail; msn_list = ast_strdupa(msn_patterns); list_tail = NULL; pattern = strtok_r(msn_list, ",", &list_tail); while (pattern) { pattern = ast_strip(pattern); if (!ast_strlen_zero(pattern) && ast_extension_match(pattern, exten)) { /* Extension matched the pattern. */ return 1; } pattern = strtok_r(NULL, ",", &list_tail); } /* Did not match any pattern in the list. */ return 0; } #if defined(HAVE_PRI_MCID) static void party_number_json_to_ami(struct ast_str **msg, const char *prefix, struct ast_json *number) { const char *num_txt, *pres_txt; int plan, pres; if (!number) { ast_str_append(msg, 0, "%sNumValid: 0\r\n" "%sNum: \r\n" "%ston: 0\r\n", prefix, prefix, prefix); return; } num_txt = ast_json_string_get(ast_json_object_get(number, "number")); plan = ast_json_integer_get(ast_json_object_get(number, "plan")); pres = ast_json_integer_get(ast_json_object_get(number, "presentation")); pres_txt = ast_json_string_get(ast_json_object_get(number, "presentation_txt")); ast_str_append(msg, 0, "%sNumValid: 1\r\n", prefix); ast_str_append(msg, 0, "%sNum: %s\r\n", prefix, num_txt); ast_str_append(msg, 0, "%ston: %d\r\n", prefix, plan); ast_str_append(msg, 0, "%sNumPlan: %d\r\n", prefix, plan); ast_str_append(msg, 0, "%sNumPres: %d (%s)\r\n", prefix, pres, pres_txt); } static void party_name_json_to_ami(struct ast_str **msg, const char *prefix, struct ast_json *name) { const char *name_txt, *pres_txt, *charset; int pres; if (!name) { ast_str_append(msg, 0, "%sNameValid: 0\r\n" "%sName: \r\n", prefix, prefix); return; } name_txt = ast_json_string_get(ast_json_object_get(name, "name")); charset = ast_json_string_get(ast_json_object_get(name, "character_set")); pres = ast_json_integer_get(ast_json_object_get(name, "presentation")); pres_txt = ast_json_string_get(ast_json_object_get(name, "presentation_txt")); ast_str_append(msg, 0, "%sNameValid: 1\r\n", prefix); ast_str_append(msg, 0, "%sName: %s\r\n", prefix, name_txt); ast_str_append(msg, 0, "%sNameCharSet: %s\r\n", prefix, charset); ast_str_append(msg, 0, "%sNamePres: %d (%s)\r\n", prefix, pres, pres_txt); } static void party_subaddress_json_to_ami(struct ast_str **msg, const char *prefix, struct ast_json *subaddress) { const char *subaddress_txt, *type_txt; int odd; if (!subaddress) { return; } subaddress_txt = ast_json_string_get(ast_json_object_get(subaddress, "subaddress")); type_txt = ast_json_string_get(ast_json_object_get(subaddress, "type")); odd = ast_json_is_true(ast_json_object_get(subaddress, "odd")) ? 1 : 0; ast_str_append(msg, 0, "%sSubaddr: %s\r\n", prefix, subaddress_txt); ast_str_append(msg, 0, "%sSubaddrType: %s\r\n", prefix, type_txt); ast_str_append(msg, 0, "%sSubaddrOdd: %d\r\n", prefix, odd); } /*! * \internal * \brief Append the given JSON party id to the event string. * \since 1.8 * * \param msg Event message string being built. * \param prefix Prefix to add to the party id lines. * \param party Party information to encode. * * \return Nothing */ static void party_json_to_ami(struct ast_str **msg, const char *prefix, struct ast_json *party) { struct ast_json *presentation = ast_json_object_get(party, "presentation"); struct ast_json *presentation_txt = ast_json_object_get(party, "presentation_txt"); struct ast_json *name = ast_json_object_get(party, "name"); struct ast_json *number = ast_json_object_get(party, "number"); struct ast_json *subaddress = ast_json_object_get(party, "subaddress"); /* Combined party presentation */ ast_str_append(msg, 0, "%sPres: %jd (%s)\r\n", prefix, ast_json_integer_get(presentation), ast_json_string_get(presentation_txt)); /* Party number */ party_number_json_to_ami(msg, prefix, number); /* Party name */ party_name_json_to_ami(msg, prefix, name); /* Party subaddress */ party_subaddress_json_to_ami(msg, prefix, subaddress); } static struct ast_manager_event_blob *mcid_to_ami(struct stasis_message *msg) { RAII_VAR(struct ast_str *, channel_string, NULL, ast_free); RAII_VAR(struct ast_str *, party_string, ast_str_create(256), ast_free); struct ast_channel_blob *obj = stasis_message_data(msg); if (obj->snapshot) { channel_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_string) { return NULL; } } party_json_to_ami(&party_string, "MCallerID", ast_json_object_get(obj->blob, "caller")); party_json_to_ami(&party_string, "MConnectedID", ast_json_object_get(obj->blob, "connected")); return ast_manager_event_blob_create(EVENT_FLAG_CALL, "MCID", "%s" "%s", S_COR(obj->snapshot, ast_str_buffer(channel_string), ""), ast_str_buffer(party_string)); } STASIS_MESSAGE_TYPE_DEFN_LOCAL(mcid_type, .to_ami = mcid_to_ami, ); static void send_mcid(struct ast_channel *chan, struct ast_party_id *caller, struct ast_party_id *connected) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ast_assert(caller != NULL); ast_assert(connected != NULL); blob = ast_json_pack("{s: o, s: o}", "caller", ast_json_party_id(caller), "connected", ast_json_party_id(connected)); if (!blob) { return; } ast_channel_publish_blob(chan, mcid_type(), blob); } /*! * \internal * \brief Handle the MCID event. * \since 1.8 * * \param pri PRI span control structure. * \param mcid MCID event parameters. * \param owner Asterisk channel associated with the call. * NULL if Asterisk no longer has the ast_channel struct. * * \note Assumes the pri->lock is already obtained. * \note Assumes the owner channel lock is already obtained if still present. * * \return Nothing */ static void sig_pri_mcid_event(struct sig_pri_span *pri, const struct pri_subcmd_mcid_req *mcid, struct ast_channel *owner) { struct ast_party_id caller_party; struct ast_party_id connected_party; /* Always use libpri's called party information. */ ast_party_id_init(&connected_party); sig_pri_party_id_convert(&connected_party, &mcid->answerer, pri); if (owner) { /* * The owner channel is present. * Pass the event to the peer as well. */ ast_queue_control(owner, AST_CONTROL_MCID); send_mcid(owner, &ast_channel_connected(owner)->id, &connected_party); } else { /* * Since we no longer have an owner channel, * we have to use the caller information supplied by libpri. */ ast_party_id_init(&caller_party); sig_pri_party_id_convert(&caller_party, &mcid->originator, pri); send_mcid(owner, &caller_party, &connected_party); ast_party_id_free(&caller_party); } ast_party_id_free(&connected_party); } #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_TRANSFER) struct xfer_rsp_data { struct sig_pri_span *pri; /*! Call to send transfer success/fail response over. */ q931_call *call; /*! Invocation ID to use when sending a reply to the transfer request. */ int invoke_id; /*! TRUE if the transfer response has been made. */ int responded; }; #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_TRANSFER) /*! * \internal * \brief Send the transfer success/fail response message. * \since 1.8 * * \param rsp Transfer response data. * \param is_successful TRUE if the transfer was successful. * * \note Assumes the rsp->pri->lock is already obtained. * * \return Nothing */ static void sig_pri_transfer_rsp(struct xfer_rsp_data *rsp, int is_successful) { if (rsp->responded) { return; } rsp->responded = 1; pri_transfer_rsp(rsp->pri->pri, rsp->call, rsp->invoke_id, is_successful); } #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_CALL_HOLD) || defined(HAVE_PRI_TRANSFER) /*! * \internal * \brief Attempt to transfer the two calls to each other. * \since 1.8 * * \param pri PRI span control structure. * \param call_1_pri First call involved in the transfer. (transferee; usually on hold) * \param call_1_held TRUE if call_1_pri is on hold. * \param call_2_pri Second call involved in the transfer. (target; usually active/ringing) * \param call_2_held TRUE if call_2_pri is on hold. * \param xfer_data Transfer response data if non-NULL. * * \note Assumes the pri->lock is already obtained. * * \retval 0 on success. * \retval -1 on error. */ static int sig_pri_attempt_transfer(struct sig_pri_span *pri, q931_call *call_1_pri, int call_1_held, q931_call *call_2_pri, int call_2_held, struct xfer_rsp_data *xfer_data) { struct attempt_xfer_call { q931_call *pri; struct ast_channel *ast; int held; int chanpos; }; int retval; enum ast_transfer_result xfer_res; struct attempt_xfer_call *call_1; struct attempt_xfer_call *call_2; struct attempt_xfer_call c1; struct attempt_xfer_call c2; c1.pri = call_1_pri; c1.held = call_1_held; call_1 = &c1; c2.pri = call_2_pri; c2.held = call_2_held; call_2 = &c2; call_1->chanpos = pri_find_principle_by_call(pri, call_1->pri); call_2->chanpos = pri_find_principle_by_call(pri, call_2->pri); if (call_1->chanpos < 0 || call_2->chanpos < 0) { /* Calls not found in span control. */ #if defined(HAVE_PRI_TRANSFER) if (xfer_data) { /* Transfer failed. */ sig_pri_transfer_rsp(xfer_data, 0); } #endif /* defined(HAVE_PRI_TRANSFER) */ return -1; } /* Get call_1 owner. */ sig_pri_lock_private(pri->pvts[call_1->chanpos]); sig_pri_lock_owner(pri, call_1->chanpos); call_1->ast = pri->pvts[call_1->chanpos]->owner; if (call_1->ast) { ast_channel_ref(call_1->ast); ast_channel_unlock(call_1->ast); } sig_pri_unlock_private(pri->pvts[call_1->chanpos]); /* Get call_2 owner. */ sig_pri_lock_private(pri->pvts[call_2->chanpos]); sig_pri_lock_owner(pri, call_2->chanpos); call_2->ast = pri->pvts[call_2->chanpos]->owner; if (call_2->ast) { ast_channel_ref(call_2->ast); ast_channel_unlock(call_2->ast); } sig_pri_unlock_private(pri->pvts[call_2->chanpos]); if (!call_1->ast || !call_2->ast) { /* At least one owner is not present. */ if (call_1->ast) { ast_channel_unref(call_1->ast); } if (call_2->ast) { ast_channel_unref(call_2->ast); } #if defined(HAVE_PRI_TRANSFER) if (xfer_data) { /* Transfer failed. */ sig_pri_transfer_rsp(xfer_data, 0); } #endif /* defined(HAVE_PRI_TRANSFER) */ return -1; } ast_verb(3, "TRANSFERRING %s to %s\n", ast_channel_name(call_1->ast), ast_channel_name(call_2->ast)); #if defined(HAVE_PRI_TRANSFER) if (xfer_data) { /* * Add traps on the transferer channels in case threading causes * them to hangup before ast_bridge_transfer_attended() returns * and we can get the pri->lock back. */ sig_pri_lock_private(pri->pvts[call_1->chanpos]); pri->pvts[call_1->chanpos]->xfer_data = xfer_data; sig_pri_unlock_private(pri->pvts[call_1->chanpos]); sig_pri_lock_private(pri->pvts[call_2->chanpos]); pri->pvts[call_2->chanpos]->xfer_data = xfer_data; sig_pri_unlock_private(pri->pvts[call_2->chanpos]); } #endif /* defined(HAVE_PRI_TRANSFER) */ ast_mutex_unlock(&pri->lock); xfer_res = ast_bridge_transfer_attended(call_1->ast, call_2->ast); ast_mutex_lock(&pri->lock); retval = (xfer_res != AST_BRIDGE_TRANSFER_SUCCESS) ? -1 : 0; #if defined(HAVE_PRI_TRANSFER) if (xfer_data) { int rsp_chanpos; /* * Remove the transferrer channel traps. * * We must refind chanpos because we released pri->lock. */ rsp_chanpos = pri_find_principle_by_call(pri, call_1->pri); if (0 <= rsp_chanpos) { sig_pri_lock_private(pri->pvts[rsp_chanpos]); pri->pvts[rsp_chanpos]->xfer_data = NULL; sig_pri_unlock_private(pri->pvts[rsp_chanpos]); } rsp_chanpos = pri_find_principle_by_call(pri, call_2->pri); if (0 <= rsp_chanpos) { sig_pri_lock_private(pri->pvts[rsp_chanpos]); pri->pvts[rsp_chanpos]->xfer_data = NULL; sig_pri_unlock_private(pri->pvts[rsp_chanpos]); } /* Report transfer status. */ sig_pri_transfer_rsp(xfer_data, retval ? 0 : 1); } #endif /* defined(HAVE_PRI_TRANSFER) */ ast_channel_unref(call_1->ast); ast_channel_unref(call_2->ast); return retval; } #endif /* defined(HAVE_PRI_CALL_HOLD) || defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Compare the CC agent private data by libpri cc_id. * \since 1.8 * * \param obj pointer to the (user-defined part) of an object. * \param arg callback argument from ao2_callback() * \param flags flags from ao2_callback() * * \return values are a combination of enum _cb_results. */ static int sig_pri_cc_agent_cmp_cc_id(void *obj, void *arg, int flags) { struct ast_cc_agent *agent_1 = obj; struct sig_pri_cc_agent_prv *agent_prv_1 = agent_1->private_data; struct sig_pri_cc_agent_prv *agent_prv_2 = arg; return (agent_prv_1 && agent_prv_1->pri == agent_prv_2->pri && agent_prv_1->cc_id == agent_prv_2->cc_id) ? CMP_MATCH | CMP_STOP : 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Find the CC agent by libpri cc_id. * \since 1.8 * * \param pri PRI span control structure. * \param cc_id CC record ID to find. * * \note * Since agents are refcounted, and this function returns * a reference to the agent, it is imperative that you decrement * the refcount of the agent once you have finished using it. * * \retval agent on success. * \retval NULL not found. */ static struct ast_cc_agent *sig_pri_find_cc_agent_by_cc_id(struct sig_pri_span *pri, long cc_id) { struct sig_pri_cc_agent_prv finder = { .pri = pri, .cc_id = cc_id, }; return ast_cc_agent_callback(0, sig_pri_cc_agent_cmp_cc_id, &finder, sig_pri_cc_type_name); } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Compare the CC monitor instance by libpri cc_id. * \since 1.8 * * \param obj pointer to the (user-defined part) of an object. * \param arg callback argument from ao2_callback() * \param flags flags from ao2_callback() * * \return values are a combination of enum _cb_results. */ static int sig_pri_cc_monitor_cmp_cc_id(void *obj, void *arg, int flags) { struct sig_pri_cc_monitor_instance *monitor_1 = obj; struct sig_pri_cc_monitor_instance *monitor_2 = arg; return (monitor_1->pri == monitor_2->pri && monitor_1->cc_id == monitor_2->cc_id) ? CMP_MATCH | CMP_STOP : 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Find the CC monitor instance by libpri cc_id. * \since 1.8 * * \param pri PRI span control structure. * \param cc_id CC record ID to find. * * \note * Since monitor_instances are refcounted, and this function returns * a reference to the instance, it is imperative that you decrement * the refcount of the instance once you have finished using it. * * \retval monitor_instance on success. * \retval NULL not found. */ static struct sig_pri_cc_monitor_instance *sig_pri_find_cc_monitor_by_cc_id(struct sig_pri_span *pri, long cc_id) { struct sig_pri_cc_monitor_instance finder = { .pri = pri, .cc_id = cc_id, }; return ao2_callback(sig_pri_cc_monitors, 0, sig_pri_cc_monitor_cmp_cc_id, &finder); } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Destroy the given monitor instance. * \since 1.8 * * \param data Monitor instance to destroy. * * \return Nothing */ static void sig_pri_cc_monitor_instance_destroy(void *data) { struct sig_pri_cc_monitor_instance *monitor_instance = data; if (monitor_instance->cc_id != -1) { ast_mutex_lock(&monitor_instance->pri->lock); pri_cc_cancel(monitor_instance->pri->pri, monitor_instance->cc_id); ast_mutex_unlock(&monitor_instance->pri->lock); } sig_pri_callbacks.module_unref(); } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Construct a new monitor instance. * \since 1.8 * * \param core_id CC core ID. * \param pri PRI span control structure. * \param cc_id CC record ID. * \param device_name Name of device (Asterisk channel name less sequence number). * * \note * Since monitor_instances are refcounted, and this function returns * a reference to the instance, it is imperative that you decrement * the refcount of the instance once you have finished using it. * * \retval monitor_instance on success. * \retval NULL on error. */ static struct sig_pri_cc_monitor_instance *sig_pri_cc_monitor_instance_init(int core_id, struct sig_pri_span *pri, long cc_id, const char *device_name) { struct sig_pri_cc_monitor_instance *monitor_instance; if (!sig_pri_callbacks.module_ref || !sig_pri_callbacks.module_unref) { return NULL; } monitor_instance = ao2_alloc(sizeof(*monitor_instance) + strlen(device_name), sig_pri_cc_monitor_instance_destroy); if (!monitor_instance) { return NULL; } monitor_instance->cc_id = cc_id; monitor_instance->pri = pri; monitor_instance->core_id = core_id; strcpy(monitor_instance->name, device_name); sig_pri_callbacks.module_ref(); ao2_link(sig_pri_cc_monitors, monitor_instance); return monitor_instance; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Announce to the CC core that protocol CC monitor is available for this call. * \since 1.8 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param cc_id CC record ID. * \param service CCBS/CCNR indication. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * \note Assumes the sig_pri_lock_owner(pri, chanpos) is already obtained. * * \retval 0 on success. * \retval -1 on error. */ static int sig_pri_cc_available(struct sig_pri_span *pri, int chanpos, long cc_id, enum ast_cc_service_type service) { struct sig_pri_chan *pvt; struct ast_cc_config_params *cc_params; struct sig_pri_cc_monitor_instance *monitor; enum ast_cc_monitor_policies monitor_policy; int core_id; int res; char device_name[AST_CHANNEL_NAME]; char dialstring[AST_CHANNEL_NAME]; pvt = pri->pvts[chanpos]; core_id = ast_cc_get_current_core_id(pvt->owner); if (core_id == -1) { return -1; } cc_params = ast_channel_get_cc_config_params(pvt->owner); if (!cc_params) { return -1; } res = -1; monitor_policy = ast_get_cc_monitor_policy(cc_params); switch (monitor_policy) { case AST_CC_MONITOR_NEVER: /* CCSS is not enabled. */ break; case AST_CC_MONITOR_NATIVE: case AST_CC_MONITOR_ALWAYS: /* * If it is AST_CC_MONITOR_ALWAYS and native fails we will attempt the fallback * later in the call to sig_pri_cc_generic_check(). */ ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name)); sig_pri_make_cc_dialstring(pvt, dialstring, sizeof(dialstring)); monitor = sig_pri_cc_monitor_instance_init(core_id, pri, cc_id, device_name); if (!monitor) { break; } res = ast_queue_cc_frame(pvt->owner, sig_pri_cc_type_name, dialstring, service, monitor); if (res) { monitor->cc_id = -1; ao2_unlink(sig_pri_cc_monitors, monitor); ao2_ref(monitor, -1); } break; case AST_CC_MONITOR_GENERIC: ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, sig_pri_get_orig_dialstring(pvt), service, NULL); /* Say it failed to force caller to cancel native CC. */ break; } return res; } #endif /* defined(HAVE_PRI_CCSS) */ /*! * \internal * \brief Check if generic CC monitor is needed and request it. * \since 1.8 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param service CCBS/CCNR indication. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_pri_cc_generic_check(struct sig_pri_span *pri, int chanpos, enum ast_cc_service_type service) { struct ast_channel *owner; struct ast_cc_config_params *cc_params; #if defined(HAVE_PRI_CCSS) struct ast_cc_monitor *monitor; char device_name[AST_CHANNEL_NAME]; #endif /* defined(HAVE_PRI_CCSS) */ enum ast_cc_monitor_policies monitor_policy; int core_id; if (!pri->pvts[chanpos]->outgoing) { /* This is not an outgoing call so it cannot be CC monitor. */ return; } sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (!owner) { return; } core_id = ast_cc_get_current_core_id(owner); if (core_id == -1) { /* No CC core setup */ goto done; } cc_params = ast_channel_get_cc_config_params(owner); if (!cc_params) { /* Could not get CC config parameters. */ goto done; } #if defined(HAVE_PRI_CCSS) ast_channel_get_device_name(owner, device_name, sizeof(device_name)); monitor = ast_cc_get_monitor_by_recall_core_id(core_id, device_name); if (monitor) { /* CC monitor is already present so no need for generic CC. */ ao2_ref(monitor, -1); goto done; } #endif /* defined(HAVE_PRI_CCSS) */ monitor_policy = ast_get_cc_monitor_policy(cc_params); switch (monitor_policy) { case AST_CC_MONITOR_NEVER: /* CCSS is not enabled. */ break; case AST_CC_MONITOR_NATIVE: if (pri->sig == SIG_BRI_PTMP && pri->nodetype == PRI_NETWORK) { /* Request generic CC monitor. */ ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE, sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL); } break; case AST_CC_MONITOR_ALWAYS: if (pri->sig == SIG_BRI_PTMP && pri->nodetype != PRI_NETWORK) { /* * Cannot monitor PTMP TE side since this is not defined. * We are playing the roll of a phone in this case and * a phone cannot monitor a party over the network without * protocol help. */ break; } /* * We are either falling back or this is a PTMP NT span. * Request generic CC monitor. */ ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE, sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL); break; case AST_CC_MONITOR_GENERIC: if (pri->sig == SIG_BRI_PTMP && pri->nodetype == PRI_NETWORK) { /* Request generic CC monitor. */ ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE, sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL); } break; } done: ast_channel_unlock(owner); } #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief The CC link canceled the CC instance. * \since 1.8 * * \param pri PRI span control structure. * \param cc_id CC record ID. * \param is_agent TRUE if the cc_id is for an agent. * * \return Nothing */ static void sig_pri_cc_link_canceled(struct sig_pri_span *pri, long cc_id, int is_agent) { if (is_agent) { struct ast_cc_agent *agent; agent = sig_pri_find_cc_agent_by_cc_id(pri, cc_id); if (!agent) { return; } ast_cc_failed(agent->core_id, "%s agent got canceled by link", sig_pri_cc_type_name); ao2_ref(agent, -1); } else { struct sig_pri_cc_monitor_instance *monitor; monitor = sig_pri_find_cc_monitor_by_cc_id(pri, cc_id); if (!monitor) { return; } monitor->cc_id = -1; ast_cc_monitor_failed(monitor->core_id, monitor->name, "%s monitor got canceled by link", sig_pri_cc_type_name); ao2_ref(monitor, -1); } } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Convert ast_aoc_charged_item to PRI_AOC_CHARGED_ITEM . * \since 1.8 * * \param value Value to convert to string. * * \return PRI_AOC_CHARGED_ITEM */ static enum PRI_AOC_CHARGED_ITEM sig_pri_aoc_charged_item_to_pri(enum PRI_AOC_CHARGED_ITEM value) { switch (value) { case AST_AOC_CHARGED_ITEM_NA: return PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE; case AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT: return PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT; case AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION: return PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION; case AST_AOC_CHARGED_ITEM_CALL_ATTEMPT: return PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT; case AST_AOC_CHARGED_ITEM_CALL_SETUP: return PRI_AOC_CHARGED_ITEM_CALL_SETUP; case AST_AOC_CHARGED_ITEM_USER_USER_INFO: return PRI_AOC_CHARGED_ITEM_USER_USER_INFO; case AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE: return PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE; } return PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE; } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Convert PRI_AOC_CHARGED_ITEM to ast_aoc_charged_item. * \since 1.8 * * \param value Value to convert to string. * * \return ast_aoc_charged_item */ static enum ast_aoc_s_charged_item sig_pri_aoc_charged_item_to_ast(enum PRI_AOC_CHARGED_ITEM value) { switch (value) { case PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE: return AST_AOC_CHARGED_ITEM_NA; case PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT: return AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT; case PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION: return AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION; case PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT: return AST_AOC_CHARGED_ITEM_CALL_ATTEMPT; case PRI_AOC_CHARGED_ITEM_CALL_SETUP: return AST_AOC_CHARGED_ITEM_CALL_SETUP; case PRI_AOC_CHARGED_ITEM_USER_USER_INFO: return AST_AOC_CHARGED_ITEM_USER_USER_INFO; case PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE: return AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE; } return AST_AOC_CHARGED_ITEM_NA; } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Convert AST_AOC_MULTIPLER to PRI_AOC_MULTIPLIER. * \since 1.8 * * \return pri enum equivalent. */ static int sig_pri_aoc_multiplier_from_ast(enum ast_aoc_currency_multiplier mult) { switch (mult) { case AST_AOC_MULT_ONETHOUSANDTH: return PRI_AOC_MULTIPLIER_THOUSANDTH; case AST_AOC_MULT_ONEHUNDREDTH: return PRI_AOC_MULTIPLIER_HUNDREDTH; case AST_AOC_MULT_ONETENTH: return PRI_AOC_MULTIPLIER_TENTH; case AST_AOC_MULT_ONE: return PRI_AOC_MULTIPLIER_ONE; case AST_AOC_MULT_TEN: return PRI_AOC_MULTIPLIER_TEN; case AST_AOC_MULT_HUNDRED: return PRI_AOC_MULTIPLIER_HUNDRED; case AST_AOC_MULT_THOUSAND: return PRI_AOC_MULTIPLIER_THOUSAND; default: return PRI_AOC_MULTIPLIER_ONE; } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Convert PRI_AOC_MULTIPLIER to AST_AOC_MULTIPLIER * \since 1.8 * * \return ast enum equivalent. */ static int sig_pri_aoc_multiplier_from_pri(const int mult) { switch (mult) { case PRI_AOC_MULTIPLIER_THOUSANDTH: return AST_AOC_MULT_ONETHOUSANDTH; case PRI_AOC_MULTIPLIER_HUNDREDTH: return AST_AOC_MULT_ONEHUNDREDTH; case PRI_AOC_MULTIPLIER_TENTH: return AST_AOC_MULT_ONETENTH; case PRI_AOC_MULTIPLIER_ONE: return AST_AOC_MULT_ONE; case PRI_AOC_MULTIPLIER_TEN: return AST_AOC_MULT_TEN; case PRI_AOC_MULTIPLIER_HUNDRED: return AST_AOC_MULT_HUNDRED; case PRI_AOC_MULTIPLIER_THOUSAND: return AST_AOC_MULT_THOUSAND; default: return AST_AOC_MULT_ONE; } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Convert ast_aoc_time_scale representation to PRI_AOC_TIME_SCALE * \since 1.8 * * \param value Value to convert to ast representation * * \return PRI_AOC_TIME_SCALE */ static enum PRI_AOC_TIME_SCALE sig_pri_aoc_scale_to_pri(enum ast_aoc_time_scale value) { switch (value) { default: case AST_AOC_TIME_SCALE_HUNDREDTH_SECOND: return PRI_AOC_TIME_SCALE_HUNDREDTH_SECOND; case AST_AOC_TIME_SCALE_TENTH_SECOND: return PRI_AOC_TIME_SCALE_TENTH_SECOND; case AST_AOC_TIME_SCALE_SECOND: return PRI_AOC_TIME_SCALE_SECOND; case AST_AOC_TIME_SCALE_TEN_SECOND: return PRI_AOC_TIME_SCALE_TEN_SECOND; case AST_AOC_TIME_SCALE_MINUTE: return PRI_AOC_TIME_SCALE_MINUTE; case AST_AOC_TIME_SCALE_HOUR: return PRI_AOC_TIME_SCALE_HOUR; case AST_AOC_TIME_SCALE_DAY: return PRI_AOC_TIME_SCALE_DAY; } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Convert PRI_AOC_TIME_SCALE to ast aoc representation * \since 1.8 * * \param value Value to convert to ast representation * * \return ast aoc time scale */ static enum ast_aoc_time_scale sig_pri_aoc_scale_to_ast(enum PRI_AOC_TIME_SCALE value) { switch (value) { default: case PRI_AOC_TIME_SCALE_HUNDREDTH_SECOND: return AST_AOC_TIME_SCALE_HUNDREDTH_SECOND; case PRI_AOC_TIME_SCALE_TENTH_SECOND: return AST_AOC_TIME_SCALE_TENTH_SECOND; case PRI_AOC_TIME_SCALE_SECOND: return AST_AOC_TIME_SCALE_SECOND; case PRI_AOC_TIME_SCALE_TEN_SECOND: return AST_AOC_TIME_SCALE_TEN_SECOND; case PRI_AOC_TIME_SCALE_MINUTE: return AST_AOC_TIME_SCALE_MINUTE; case PRI_AOC_TIME_SCALE_HOUR: return AST_AOC_TIME_SCALE_HOUR; case PRI_AOC_TIME_SCALE_DAY: return AST_AOC_TIME_SCALE_DAY; } return AST_AOC_TIME_SCALE_HUNDREDTH_SECOND; } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Handle AOC-S control frame * \since 1.8 * * \param aoc_s AOC-S event parameters. * \param owner Asterisk channel associated with the call. * \param passthrough indicating if this message should be queued on the ast channel * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri private is locked * \note Assumes the owner channel lock is already obtained. * * \return Nothing */ static void sig_pri_aoc_s_from_pri(const struct pri_subcmd_aoc_s *aoc_s, struct ast_channel *owner, int passthrough) { struct ast_aoc_decoded *decoded = NULL; struct ast_aoc_encoded *encoded = NULL; size_t encoded_size = 0; int idx; if (!owner || !aoc_s) { return; } if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) { return; } for (idx = 0; idx < aoc_s->num_items; ++idx) { enum ast_aoc_s_charged_item charged_item; charged_item = sig_pri_aoc_charged_item_to_ast(aoc_s->item[idx].chargeable); if (charged_item == AST_AOC_CHARGED_ITEM_NA) { /* Delete the unknown charged item from the list. */ continue; } switch (aoc_s->item[idx].rate_type) { case PRI_AOC_RATE_TYPE_DURATION: ast_aoc_s_add_rate_duration(decoded, charged_item, aoc_s->item[idx].rate.duration.amount.cost, sig_pri_aoc_multiplier_from_pri(aoc_s->item[idx].rate.duration.amount.multiplier), aoc_s->item[idx].rate.duration.currency, aoc_s->item[idx].rate.duration.time.length, sig_pri_aoc_scale_to_ast(aoc_s->item[idx].rate.duration.time.scale), aoc_s->item[idx].rate.duration.granularity.length, sig_pri_aoc_scale_to_ast(aoc_s->item[idx].rate.duration.granularity.scale), aoc_s->item[idx].rate.duration.charging_type); break; case PRI_AOC_RATE_TYPE_FLAT: ast_aoc_s_add_rate_flat(decoded, charged_item, aoc_s->item[idx].rate.flat.amount.cost, sig_pri_aoc_multiplier_from_pri(aoc_s->item[idx].rate.flat.amount.multiplier), aoc_s->item[idx].rate.flat.currency); break; case PRI_AOC_RATE_TYPE_VOLUME: ast_aoc_s_add_rate_volume(decoded, charged_item, aoc_s->item[idx].rate.volume.unit, aoc_s->item[idx].rate.volume.amount.cost, sig_pri_aoc_multiplier_from_pri(aoc_s->item[idx].rate.volume.amount.multiplier), aoc_s->item[idx].rate.volume.currency); break; case PRI_AOC_RATE_TYPE_SPECIAL_CODE: ast_aoc_s_add_rate_special_charge_code(decoded, charged_item, aoc_s->item[idx].rate.special); break; case PRI_AOC_RATE_TYPE_FREE: ast_aoc_s_add_rate_free(decoded, charged_item, 0); break; case PRI_AOC_RATE_TYPE_FREE_FROM_BEGINNING: ast_aoc_s_add_rate_free(decoded, charged_item, 1); break; default: ast_aoc_s_add_rate_na(decoded, charged_item); break; } } if (passthrough && (encoded = ast_aoc_encode(decoded, &encoded_size, owner))) { ast_queue_control_data(owner, AST_CONTROL_AOC, encoded, encoded_size); } ast_aoc_manager_event(decoded, owner); ast_aoc_destroy_decoded(decoded); ast_aoc_destroy_encoded(encoded); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Generate AOC Request Response * \since 1.8 * * \param aoc_request * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri private is locked * \note Assumes the owner channel lock is already obtained. * * \return Nothing */ static void sig_pri_aoc_request_from_pri(const struct pri_subcmd_aoc_request *aoc_request, struct sig_pri_chan *pvt, q931_call *call) { int request; if (!aoc_request) { return; } request = aoc_request->charging_request; if (request & PRI_AOC_REQUEST_S) { if (pvt->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S) { /* An AOC-S response must come from the other side, so save off this invoke_id * and see if an AOC-S message comes in before the call is answered. */ pvt->aoc_s_request_invoke_id = aoc_request->invoke_id; pvt->aoc_s_request_invoke_id_valid = 1; } else { pri_aoc_s_request_response_send(pvt->pri->pri, call, aoc_request->invoke_id, NULL); } } if (request & PRI_AOC_REQUEST_D) { if (pvt->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D) { pri_aoc_de_request_response_send(pvt->pri->pri, call, PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS, aoc_request->invoke_id); } else { pri_aoc_de_request_response_send(pvt->pri->pri, call, PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE, aoc_request->invoke_id); } } if (request & PRI_AOC_REQUEST_E) { if (pvt->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E) { pri_aoc_de_request_response_send(pvt->pri->pri, call, PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS, aoc_request->invoke_id); } else { pri_aoc_de_request_response_send(pvt->pri->pri, call, PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE, aoc_request->invoke_id); } } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Generate AOC-D AST_CONTROL_AOC frame * \since 1.8 * * \param aoc_e AOC-D event parameters. * \param owner Asterisk channel associated with the call. * \param passthrough indicating if this message should be queued on the ast channel * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri private is locked * \note Assumes the owner channel lock is already obtained. * * \return Nothing */ static void sig_pri_aoc_d_from_pri(const struct pri_subcmd_aoc_d *aoc_d, struct ast_channel *owner, int passthrough) { struct ast_aoc_decoded *decoded = NULL; struct ast_aoc_encoded *encoded = NULL; size_t encoded_size = 0; enum ast_aoc_charge_type type; if (!owner || !aoc_d) { return; } switch (aoc_d->charge) { case PRI_AOC_DE_CHARGE_CURRENCY: type = AST_AOC_CHARGE_CURRENCY; break; case PRI_AOC_DE_CHARGE_UNITS: type = AST_AOC_CHARGE_UNIT; break; case PRI_AOC_DE_CHARGE_FREE: type = AST_AOC_CHARGE_FREE; break; default: type = AST_AOC_CHARGE_NA; break; } if (!(decoded = ast_aoc_create(AST_AOC_D, type, 0))) { return; } switch (aoc_d->billing_accumulation) { default: ast_debug(1, "AOC-D billing accumulation has unknown value: %d\n", aoc_d->billing_accumulation); /* Fall through */ case 0:/* subTotal */ ast_aoc_set_total_type(decoded, AST_AOC_SUBTOTAL); break; case 1:/* total */ ast_aoc_set_total_type(decoded, AST_AOC_TOTAL); break; } switch (aoc_d->billing_id) { case PRI_AOC_D_BILLING_ID_NORMAL: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NORMAL); break; case PRI_AOC_D_BILLING_ID_REVERSE: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_REVERSE_CHARGE); break; case PRI_AOC_D_BILLING_ID_CREDIT_CARD: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CREDIT_CARD); break; case PRI_AOC_D_BILLING_ID_NOT_AVAILABLE: default: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NA); break; } switch (aoc_d->charge) { case PRI_AOC_DE_CHARGE_CURRENCY: ast_aoc_set_currency_info(decoded, aoc_d->recorded.money.amount.cost, sig_pri_aoc_multiplier_from_pri(aoc_d->recorded.money.amount.multiplier), aoc_d->recorded.money.currency); break; case PRI_AOC_DE_CHARGE_UNITS: { int i; for (i = 0; i < aoc_d->recorded.unit.num_items; ++i) { /* if type or number are negative, then they are not present */ ast_aoc_add_unit_entry(decoded, (aoc_d->recorded.unit.item[i].number >= 0 ? 1 : 0), aoc_d->recorded.unit.item[i].number, (aoc_d->recorded.unit.item[i].type >= 0 ? 1 : 0), aoc_d->recorded.unit.item[i].type); } } break; } if (passthrough && (encoded = ast_aoc_encode(decoded, &encoded_size, owner))) { ast_queue_control_data(owner, AST_CONTROL_AOC, encoded, encoded_size); } ast_aoc_manager_event(decoded, owner); ast_aoc_destroy_decoded(decoded); ast_aoc_destroy_encoded(encoded); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief Generate AOC-E AST_CONTROL_AOC frame * \since 1.8 * * \param aoc_e AOC-E event parameters. * \param owner Asterisk channel associated with the call. * \param passthrough indicating if this message should be queued on the ast channel * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri private is locked * \note Assumes the owner channel lock is already obtained. * \note owner channel may be NULL. In that case, generate event only * * \return Nothing */ static void sig_pri_aoc_e_from_pri(const struct pri_subcmd_aoc_e *aoc_e, struct ast_channel *owner, int passthrough) { struct ast_aoc_decoded *decoded = NULL; struct ast_aoc_encoded *encoded = NULL; size_t encoded_size = 0; enum ast_aoc_charge_type type; if (!aoc_e) { return; } switch (aoc_e->charge) { case PRI_AOC_DE_CHARGE_CURRENCY: type = AST_AOC_CHARGE_CURRENCY; break; case PRI_AOC_DE_CHARGE_UNITS: type = AST_AOC_CHARGE_UNIT; break; case PRI_AOC_DE_CHARGE_FREE: type = AST_AOC_CHARGE_FREE; break; default: type = AST_AOC_CHARGE_NA; break; } if (!(decoded = ast_aoc_create(AST_AOC_E, type, 0))) { return; } switch (aoc_e->associated.charging_type) { case PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER: if (!aoc_e->associated.charge.number.valid) { break; } ast_aoc_set_association_number(decoded, aoc_e->associated.charge.number.str, aoc_e->associated.charge.number.plan); break; case PRI_AOC_E_CHARGING_ASSOCIATION_ID: ast_aoc_set_association_id(decoded, aoc_e->associated.charge.id); break; default: break; } switch (aoc_e->billing_id) { case PRI_AOC_E_BILLING_ID_NORMAL: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NORMAL); break; case PRI_AOC_E_BILLING_ID_REVERSE: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_REVERSE_CHARGE); break; case PRI_AOC_E_BILLING_ID_CREDIT_CARD: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CREDIT_CARD); break; case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL); break; case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_FWD_BUSY); break; case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_FWD_NO_REPLY); break; case PRI_AOC_E_BILLING_ID_CALL_DEFLECTION: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_DEFLECTION); break; case PRI_AOC_E_BILLING_ID_CALL_TRANSFER: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_TRANSFER); break; case PRI_AOC_E_BILLING_ID_NOT_AVAILABLE: default: ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NA); break; } switch (aoc_e->charge) { case PRI_AOC_DE_CHARGE_CURRENCY: ast_aoc_set_currency_info(decoded, aoc_e->recorded.money.amount.cost, sig_pri_aoc_multiplier_from_pri(aoc_e->recorded.money.amount.multiplier), aoc_e->recorded.money.currency); break; case PRI_AOC_DE_CHARGE_UNITS: { int i; for (i = 0; i < aoc_e->recorded.unit.num_items; ++i) { /* if type or number are negative, then they are not present */ ast_aoc_add_unit_entry(decoded, (aoc_e->recorded.unit.item[i].number >= 0 ? 1 : 0), aoc_e->recorded.unit.item[i].number, (aoc_e->recorded.unit.item[i].type >= 0 ? 1 : 0), aoc_e->recorded.unit.item[i].type); } } } if (passthrough && owner && (encoded = ast_aoc_encode(decoded, &encoded_size, owner))) { ast_queue_control_data(owner, AST_CONTROL_AOC, encoded, encoded_size); } ast_aoc_manager_event(decoded, owner); ast_aoc_destroy_decoded(decoded); ast_aoc_destroy_encoded(encoded); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief send an AOC-S message on the current call * * \param pvt sig_pri private channel structure. * \param generic decoded ast AOC message * * \return Nothing * * \note Assumes that the PRI lock is already obtained. */ static void sig_pri_aoc_s_from_ast(struct sig_pri_chan *pvt, struct ast_aoc_decoded *decoded) { struct pri_subcmd_aoc_s aoc_s = { 0, }; const struct ast_aoc_s_entry *entry; int idx; for (idx = 0; idx < ast_aoc_s_get_count(decoded); idx++) { if (!(entry = ast_aoc_s_get_rate_info(decoded, idx))) { break; } aoc_s.item[idx].chargeable = sig_pri_aoc_charged_item_to_pri(entry->charged_item); switch (entry->rate_type) { case AST_AOC_RATE_TYPE_DURATION: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_DURATION; aoc_s.item[idx].rate.duration.amount.cost = entry->rate.duration.amount; aoc_s.item[idx].rate.duration.amount.multiplier = sig_pri_aoc_multiplier_from_ast(entry->rate.duration.multiplier); aoc_s.item[idx].rate.duration.time.length = entry->rate.duration.time; aoc_s.item[idx].rate.duration.time.scale = sig_pri_aoc_scale_to_pri(entry->rate.duration.time_scale); aoc_s.item[idx].rate.duration.granularity.length = entry->rate.duration.granularity_time; aoc_s.item[idx].rate.duration.granularity.scale = sig_pri_aoc_scale_to_pri(entry->rate.duration.granularity_time_scale); aoc_s.item[idx].rate.duration.charging_type = entry->rate.duration.charging_type; if (!ast_strlen_zero(entry->rate.duration.currency_name)) { ast_copy_string(aoc_s.item[idx].rate.duration.currency, entry->rate.duration.currency_name, sizeof(aoc_s.item[idx].rate.duration.currency)); } break; case AST_AOC_RATE_TYPE_FLAT: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_FLAT; aoc_s.item[idx].rate.flat.amount.cost = entry->rate.flat.amount; aoc_s.item[idx].rate.flat.amount.multiplier = sig_pri_aoc_multiplier_from_ast(entry->rate.flat.multiplier); if (!ast_strlen_zero(entry->rate.flat.currency_name)) { ast_copy_string(aoc_s.item[idx].rate.flat.currency, entry->rate.flat.currency_name, sizeof(aoc_s.item[idx].rate.flat.currency)); } break; case AST_AOC_RATE_TYPE_VOLUME: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_VOLUME; aoc_s.item[idx].rate.volume.unit = entry->rate.volume.volume_unit; aoc_s.item[idx].rate.volume.amount.cost = entry->rate.volume.amount; aoc_s.item[idx].rate.volume.amount.multiplier = sig_pri_aoc_multiplier_from_ast(entry->rate.volume.multiplier); if (!ast_strlen_zero(entry->rate.volume.currency_name)) { ast_copy_string(aoc_s.item[idx].rate.volume.currency, entry->rate.volume.currency_name, sizeof(aoc_s.item[idx].rate.volume.currency)); } break; case AST_AOC_RATE_TYPE_SPECIAL_CODE: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_SPECIAL_CODE; aoc_s.item[idx].rate.special = entry->rate.special_code; break; case AST_AOC_RATE_TYPE_FREE: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_FREE; break; case AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_FREE_FROM_BEGINNING; break; default: case AST_AOC_RATE_TYPE_NA: aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_NOT_AVAILABLE; break; } } aoc_s.num_items = idx; /* if this rate should be sent as a response to an AOC-S request we will * have an aoc_s_request_invoke_id associated with this pvt */ if (pvt->aoc_s_request_invoke_id_valid) { pri_aoc_s_request_response_send(pvt->pri->pri, pvt->call, pvt->aoc_s_request_invoke_id, &aoc_s); pvt->aoc_s_request_invoke_id_valid = 0; } else { pri_aoc_s_send(pvt->pri->pri, pvt->call, &aoc_s); } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief send an AOC-D message on the current call * * \param pvt sig_pri private channel structure. * \param generic decoded ast AOC message * * \return Nothing * * \note Assumes that the PRI lock is already obtained. */ static void sig_pri_aoc_d_from_ast(struct sig_pri_chan *pvt, struct ast_aoc_decoded *decoded) { struct pri_subcmd_aoc_d aoc_d = { 0, }; aoc_d.billing_accumulation = (ast_aoc_get_total_type(decoded) == AST_AOC_TOTAL) ? 1 : 0; switch (ast_aoc_get_billing_id(decoded)) { case AST_AOC_BILLING_NORMAL: aoc_d.billing_id = PRI_AOC_D_BILLING_ID_NORMAL; break; case AST_AOC_BILLING_REVERSE_CHARGE: aoc_d.billing_id = PRI_AOC_D_BILLING_ID_REVERSE; break; case AST_AOC_BILLING_CREDIT_CARD: aoc_d.billing_id = PRI_AOC_D_BILLING_ID_CREDIT_CARD; break; case AST_AOC_BILLING_NA: default: aoc_d.billing_id = PRI_AOC_D_BILLING_ID_NOT_AVAILABLE; break; } switch (ast_aoc_get_charge_type(decoded)) { case AST_AOC_CHARGE_FREE: aoc_d.charge = PRI_AOC_DE_CHARGE_FREE; break; case AST_AOC_CHARGE_CURRENCY: { const char *currency_name = ast_aoc_get_currency_name(decoded); aoc_d.charge = PRI_AOC_DE_CHARGE_CURRENCY; aoc_d.recorded.money.amount.cost = ast_aoc_get_currency_amount(decoded); aoc_d.recorded.money.amount.multiplier = sig_pri_aoc_multiplier_from_ast(ast_aoc_get_currency_multiplier(decoded)); if (!ast_strlen_zero(currency_name)) { ast_copy_string(aoc_d.recorded.money.currency, currency_name, sizeof(aoc_d.recorded.money.currency)); } } break; case AST_AOC_CHARGE_UNIT: { const struct ast_aoc_unit_entry *entry; int i; aoc_d.charge = PRI_AOC_DE_CHARGE_UNITS; for (i = 0; i < ast_aoc_get_unit_count(decoded); i++) { if ((entry = ast_aoc_get_unit_info(decoded, i)) && i < ARRAY_LEN(aoc_d.recorded.unit.item)) { if (entry->valid_amount) { aoc_d.recorded.unit.item[i].number = entry->amount; } else { aoc_d.recorded.unit.item[i].number = -1; } if (entry->valid_type) { aoc_d.recorded.unit.item[i].type = entry->type; } else { aoc_d.recorded.unit.item[i].type = -1; } aoc_d.recorded.unit.num_items++; } else { break; } } } break; case AST_AOC_CHARGE_NA: default: aoc_d.charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE; break; } pri_aoc_d_send(pvt->pri->pri, pvt->call, &aoc_d); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief send an AOC-E message on the current call * * \param pvt sig_pri private channel structure. * \param generic decoded ast AOC message * * \return Nothing * * \note Assumes that the PRI lock is already obtained. */ static void sig_pri_aoc_e_from_ast(struct sig_pri_chan *pvt, struct ast_aoc_decoded *decoded) { struct pri_subcmd_aoc_e *aoc_e = &pvt->aoc_e; const struct ast_aoc_charging_association *ca = ast_aoc_get_association_info(decoded); memset(aoc_e, 0, sizeof(*aoc_e)); pvt->holding_aoce = 1; switch (ca->charging_type) { case AST_AOC_CHARGING_ASSOCIATION_NUMBER: aoc_e->associated.charge.number.valid = 1; ast_copy_string(aoc_e->associated.charge.number.str, ca->charge.number.number, sizeof(aoc_e->associated.charge.number.str)); aoc_e->associated.charge.number.plan = ca->charge.number.plan; aoc_e->associated.charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER; break; case AST_AOC_CHARGING_ASSOCIATION_ID: aoc_e->associated.charge.id = ca->charge.id; aoc_e->associated.charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_ID; break; case AST_AOC_CHARGING_ASSOCIATION_NA: default: break; } switch (ast_aoc_get_billing_id(decoded)) { case AST_AOC_BILLING_NORMAL: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_NORMAL; break; case AST_AOC_BILLING_REVERSE_CHARGE: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_REVERSE; break; case AST_AOC_BILLING_CREDIT_CARD: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CREDIT_CARD; break; case AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL; break; case AST_AOC_BILLING_CALL_FWD_BUSY: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY; break; case AST_AOC_BILLING_CALL_FWD_NO_REPLY: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY; break; case AST_AOC_BILLING_CALL_DEFLECTION: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_DEFLECTION; break; case AST_AOC_BILLING_CALL_TRANSFER: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_TRANSFER; break; case AST_AOC_BILLING_NA: default: aoc_e->billing_id = PRI_AOC_E_BILLING_ID_NOT_AVAILABLE; break; } switch (ast_aoc_get_charge_type(decoded)) { case AST_AOC_CHARGE_FREE: aoc_e->charge = PRI_AOC_DE_CHARGE_FREE; break; case AST_AOC_CHARGE_CURRENCY: { const char *currency_name = ast_aoc_get_currency_name(decoded); aoc_e->charge = PRI_AOC_DE_CHARGE_CURRENCY; aoc_e->recorded.money.amount.cost = ast_aoc_get_currency_amount(decoded); aoc_e->recorded.money.amount.multiplier = sig_pri_aoc_multiplier_from_ast(ast_aoc_get_currency_multiplier(decoded)); if (!ast_strlen_zero(currency_name)) { ast_copy_string(aoc_e->recorded.money.currency, currency_name, sizeof(aoc_e->recorded.money.currency)); } } break; case AST_AOC_CHARGE_UNIT: { const struct ast_aoc_unit_entry *entry; int i; aoc_e->charge = PRI_AOC_DE_CHARGE_UNITS; for (i = 0; i < ast_aoc_get_unit_count(decoded); i++) { if ((entry = ast_aoc_get_unit_info(decoded, i)) && i < ARRAY_LEN(aoc_e->recorded.unit.item)) { if (entry->valid_amount) { aoc_e->recorded.unit.item[i].number = entry->amount; } else { aoc_e->recorded.unit.item[i].number = -1; } if (entry->valid_type) { aoc_e->recorded.unit.item[i].type = entry->type; } else { aoc_e->recorded.unit.item[i].type = -1; } aoc_e->recorded.unit.num_items++; } } } break; case AST_AOC_CHARGE_NA: default: aoc_e->charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE; break; } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief send an AOC-E termination request on ast_channel and set * hangup delay. * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param ms to delay hangup * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_pri_send_aoce_termination_request(struct sig_pri_span *pri, int chanpos, unsigned int ms) { struct sig_pri_chan *pvt; struct ast_aoc_decoded *decoded = NULL; struct ast_aoc_encoded *encoded = NULL; size_t encoded_size; struct timeval whentohangup = { 0, }; sig_pri_lock_owner(pri, chanpos); pvt = pri->pvts[chanpos]; if (!pvt->owner) { return; } if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, AST_AOC_REQUEST_E))) { ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV); goto cleanup_termination_request; } ast_aoc_set_termination_request(decoded); if (!(encoded = ast_aoc_encode(decoded, &encoded_size, pvt->owner))) { ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV); goto cleanup_termination_request; } /* convert ms to timeval */ whentohangup.tv_usec = (ms % 1000) * 1000; whentohangup.tv_sec = ms / 1000; if (ast_queue_control_data(pvt->owner, AST_CONTROL_AOC, encoded, encoded_size)) { ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV); goto cleanup_termination_request; } pvt->waiting_for_aoce = 1; ast_channel_setwhentohangup_tv(pvt->owner, whentohangup); ast_debug(1, "Delaying hangup on %s for aoc-e msg\n", ast_channel_name(pvt->owner)); cleanup_termination_request: ast_channel_unlock(pvt->owner); ast_aoc_destroy_decoded(decoded); ast_aoc_destroy_encoded(encoded); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ /*! * \internal * \brief TRUE if PRI event came in on a CIS call. * \since 1.8 * * \param channel PRI encoded span/channel * * \retval non-zero if CIS call. */ static int sig_pri_is_cis_call(int channel) { return channel != -1 && (channel & PRI_CIS_CALL); } /*! * \internal * \brief Handle the CIS associated PRI subcommand events. * \since 1.8 * * \param pri PRI span control structure. * \param event_id PRI event id * \param subcmds Subcommands to process if any. (Could be NULL). * \param call_rsp libpri opaque call structure to send any responses toward. * Could be NULL either because it is not available or the call is for the * dummy call reference. However, this should not be NULL in the cases that * need to use the pointer to send a response message back. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_cis_subcmds(struct sig_pri_span *pri, int event_id, const struct pri_subcommands *subcmds, q931_call *call_rsp) { int index; #if defined(HAVE_PRI_CCSS) struct ast_cc_agent *agent; struct sig_pri_cc_agent_prv *agent_prv; struct sig_pri_cc_monitor_instance *monitor; #endif /* defined(HAVE_PRI_CCSS) */ if (!subcmds) { return; } for (index = 0; index < subcmds->counter_subcmd; ++index) { const struct pri_subcommand *subcmd = &subcmds->subcmd[index]; switch (subcmd->cmd) { #if defined(STATUS_REQUEST_PLACE_HOLDER) case PRI_SUBCMD_STATUS_REQ: case PRI_SUBCMD_STATUS_REQ_RSP: /* Ignore for now. */ break; #endif /* defined(STATUS_REQUEST_PLACE_HOLDER) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_REQ: agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_request.cc_id); if (!agent) { pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id); break; } if (!ast_cc_request_is_within_limits()) { if (pri_cc_req_rsp(pri->pri, subcmd->u.cc_request.cc_id, 5/* queue_full */)) { pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id); } ast_cc_failed(agent->core_id, "%s agent system CC queue full", sig_pri_cc_type_name); ao2_ref(agent, -1); break; } agent_prv = agent->private_data; agent_prv->cc_request_response_pending = 1; if (ast_cc_agent_accept_request(agent->core_id, "%s caller accepted CC offer.", sig_pri_cc_type_name)) { agent_prv->cc_request_response_pending = 0; if (pri_cc_req_rsp(pri->pri, subcmd->u.cc_request.cc_id, 2/* short_term_denial */)) { pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id); } ast_cc_failed(agent->core_id, "%s agent CC core request accept failed", sig_pri_cc_type_name); } ao2_ref(agent, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_REQ_RSP: monitor = sig_pri_find_cc_monitor_by_cc_id(pri, subcmd->u.cc_request_rsp.cc_id); if (!monitor) { pri_cc_cancel(pri->pri, subcmd->u.cc_request_rsp.cc_id); break; } switch (subcmd->u.cc_request_rsp.status) { case 0:/* success */ ast_cc_monitor_request_acked(monitor->core_id, "%s far end accepted CC request", sig_pri_cc_type_name); break; case 1:/* timeout */ ast_verb(2, "core_id:%d %s CC request timeout\n", monitor->core_id, sig_pri_cc_type_name); ast_cc_monitor_failed(monitor->core_id, monitor->name, "%s CC request timeout", sig_pri_cc_type_name); break; case 2:/* error */ ast_verb(2, "core_id:%d %s CC request error: %s\n", monitor->core_id, sig_pri_cc_type_name, pri_facility_error2str(subcmd->u.cc_request_rsp.fail_code)); ast_cc_monitor_failed(monitor->core_id, monitor->name, "%s CC request error", sig_pri_cc_type_name); break; case 3:/* reject */ ast_verb(2, "core_id:%d %s CC request reject: %s\n", monitor->core_id, sig_pri_cc_type_name, pri_facility_reject2str(subcmd->u.cc_request_rsp.fail_code)); ast_cc_monitor_failed(monitor->core_id, monitor->name, "%s CC request reject", sig_pri_cc_type_name); break; default: ast_verb(2, "core_id:%d %s CC request unknown status %d\n", monitor->core_id, sig_pri_cc_type_name, subcmd->u.cc_request_rsp.status); ast_cc_monitor_failed(monitor->core_id, monitor->name, "%s CC request unknown status", sig_pri_cc_type_name); break; } ao2_ref(monitor, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_REMOTE_USER_FREE: monitor = sig_pri_find_cc_monitor_by_cc_id(pri, subcmd->u.cc_remote_user_free.cc_id); if (!monitor) { pri_cc_cancel(pri->pri, subcmd->u.cc_remote_user_free.cc_id); break; } ast_cc_monitor_callee_available(monitor->core_id, "%s callee has become available", sig_pri_cc_type_name); ao2_ref(monitor, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_B_FREE: monitor = sig_pri_find_cc_monitor_by_cc_id(pri, subcmd->u.cc_b_free.cc_id); if (!monitor) { pri_cc_cancel(pri->pri, subcmd->u.cc_b_free.cc_id); break; } ast_cc_monitor_party_b_free(monitor->core_id); ao2_ref(monitor, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_STATUS_REQ: monitor = sig_pri_find_cc_monitor_by_cc_id(pri, subcmd->u.cc_status_req.cc_id); if (!monitor) { pri_cc_cancel(pri->pri, subcmd->u.cc_status_req.cc_id); break; } ast_cc_monitor_status_request(monitor->core_id); ao2_ref(monitor, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_STATUS_REQ_RSP: agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status_req_rsp.cc_id); if (!agent) { pri_cc_cancel(pri->pri, subcmd->u.cc_status_req_rsp.cc_id); break; } ast_cc_agent_status_response(agent->core_id, subcmd->u.cc_status_req_rsp.status ? AST_DEVICE_INUSE : AST_DEVICE_NOT_INUSE); ao2_ref(agent, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_STATUS: agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status.cc_id); if (!agent) { pri_cc_cancel(pri->pri, subcmd->u.cc_status.cc_id); break; } if (subcmd->u.cc_status.status) { ast_cc_agent_caller_busy(agent->core_id, "%s agent caller is busy", sig_pri_cc_type_name); } else { ast_cc_agent_caller_available(agent->core_id, "%s agent caller is available", sig_pri_cc_type_name); } ao2_ref(agent, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_CANCEL: sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id, subcmd->u.cc_cancel.is_agent); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_STOP_ALERTING: monitor = sig_pri_find_cc_monitor_by_cc_id(pri, subcmd->u.cc_stop_alerting.cc_id); if (!monitor) { pri_cc_cancel(pri->pri, subcmd->u.cc_stop_alerting.cc_id); break; } ast_cc_monitor_stop_ringing(monitor->core_id); ao2_ref(monitor, -1); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_AOC_EVENTS) case PRI_SUBCMD_AOC_E: /* Queue AST_CONTROL_AOC frame */ sig_pri_aoc_e_from_pri(&subcmd->u.aoc_e, NULL, 0); break; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ default: ast_debug(2, "Span %d: Unknown CIS subcommand(%d) in %s event.\n", pri->span, subcmd->cmd, pri_event2str(event_id)); break; } } } #if defined(HAVE_PRI_AOC_EVENTS) /*! * \internal * \brief detect if AOC-S subcmd is present. * \since 1.8 * * \param subcmds Subcommands to process if any. (Could be NULL). * * \note Knowing whether or not an AOC-E subcmd is present on certain * PRI hangup events is necessary to determine what method to use to hangup * the ast_channel. If an AOC-E subcmd just came in, then a new AOC-E was queued * on the ast_channel. If a soft hangup is used, the AOC-E msg will never make it * across the bridge, but if a AST_CONTROL_HANGUP frame is queued behind it * we can ensure the AOC-E frame makes it to it's destination before the hangup * frame is read. * * * \retval 0 AOC-E is not present in subcmd list * \retval 1 AOC-E is present in subcmd list */ static int detect_aoc_e_subcmd(const struct pri_subcommands *subcmds) { int i; if (!subcmds) { return 0; } for (i = 0; i < subcmds->counter_subcmd; ++i) { const struct pri_subcommand *subcmd = &subcmds->subcmd[i]; if (subcmd->cmd == PRI_SUBCMD_AOC_E) { return 1; } } return 0; } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ /*! * \internal * \brief Handle the call associated PRI subcommand events. * \since 1.8 * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param event_id PRI event id * \param subcmds Subcommands to process if any. (Could be NULL). * \param call_rsp libpri opaque call structure to send any responses toward. * Could be NULL either because it is not available or the call is for the * dummy call reference. However, this should not be NULL in the cases that * need to use the pointer to send a response message back. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void sig_pri_handle_subcmds(struct sig_pri_span *pri, int chanpos, int event_id, const struct pri_subcommands *subcmds, q931_call *call_rsp) { int index; struct ast_channel *owner; struct ast_party_redirecting ast_redirecting; #if defined(HAVE_PRI_TRANSFER) struct xfer_rsp_data xfer_rsp; #endif /* defined(HAVE_PRI_TRANSFER) */ if (!subcmds) { return; } for (index = 0; index < subcmds->counter_subcmd; ++index) { const struct pri_subcommand *subcmd = &subcmds->subcmd[index]; switch (subcmd->cmd) { case PRI_SUBCMD_CONNECTED_LINE: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { struct ast_party_connected_line ast_connected; int caller_id_update; /* Extract the connected line information */ ast_party_connected_line_init(&ast_connected); sig_pri_party_id_convert(&ast_connected.id, &subcmd->u.connected_line.id, pri); ast_connected.id.tag = ast_strdup(pri->pvts[chanpos]->user_tag); caller_id_update = 0; if (ast_connected.id.name.str) { /* Save name for Caller-ID update */ ast_copy_string(pri->pvts[chanpos]->cid_name, ast_connected.id.name.str, sizeof(pri->pvts[chanpos]->cid_name)); caller_id_update = 1; } if (ast_connected.id.number.str) { /* Save number for Caller-ID update */ ast_copy_string(pri->pvts[chanpos]->cid_num, ast_connected.id.number.str, sizeof(pri->pvts[chanpos]->cid_num)); pri->pvts[chanpos]->cid_ton = ast_connected.id.number.plan; caller_id_update = 1; } ast_connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; pri->pvts[chanpos]->cid_subaddr[0] = '\0'; #if defined(HAVE_PRI_SUBADDR) if (ast_connected.id.subaddress.str) { ast_copy_string(pri->pvts[chanpos]->cid_subaddr, ast_connected.id.subaddress.str, sizeof(pri->pvts[chanpos]->cid_subaddr)); caller_id_update = 1; } #endif /* defined(HAVE_PRI_SUBADDR) */ if (caller_id_update) { struct ast_party_caller ast_caller; pri->pvts[chanpos]->callingpres = ast_party_id_presentation(&ast_connected.id); sig_pri_set_caller_id(pri->pvts[chanpos]); ast_party_caller_set_init(&ast_caller, ast_channel_caller(owner)); ast_caller.id = ast_connected.id; ast_caller.ani = ast_connected.id; ast_channel_set_caller_event(owner, &ast_caller, NULL); /* Update the connected line information on the other channel */ if (event_id != PRI_EVENT_RING) { /* This connected_line update was not from a SETUP message. */ ast_channel_queue_connected_line_update(owner, &ast_connected, NULL); } } ast_party_connected_line_free(&ast_connected); ast_channel_unlock(owner); } break; case PRI_SUBCMD_REDIRECTING: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { sig_pri_redirecting_convert(&ast_redirecting, &subcmd->u.redirecting, ast_channel_redirecting(owner), pri); ast_redirecting.orig.tag = ast_strdup(pri->pvts[chanpos]->user_tag); ast_redirecting.from.tag = ast_strdup(pri->pvts[chanpos]->user_tag); ast_redirecting.to.tag = ast_strdup(pri->pvts[chanpos]->user_tag); ast_channel_set_redirecting(owner, &ast_redirecting, NULL); if (event_id != PRI_EVENT_RING) { /* This redirection was not from a SETUP message. */ /* Invalidate any earlier private redirecting id representations */ ast_party_id_invalidate(&ast_redirecting.priv_orig); ast_party_id_invalidate(&ast_redirecting.priv_from); ast_party_id_invalidate(&ast_redirecting.priv_to); ast_channel_queue_redirecting_update(owner, &ast_redirecting, NULL); } ast_party_redirecting_free(&ast_redirecting); ast_channel_unlock(owner); } break; #if defined(HAVE_PRI_CALL_REROUTING) case PRI_SUBCMD_REROUTING: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { struct pri_party_redirecting pri_deflection; if (!call_rsp) { ast_log(LOG_WARNING, "Span %d: %s tried CallRerouting/CallDeflection to '%s' without call!\n", pri->span, ast_channel_name(owner), subcmd->u.rerouting.deflection.to.number.str); ast_channel_unlock(owner); break; } if (ast_strlen_zero(subcmd->u.rerouting.deflection.to.number.str)) { ast_log(LOG_WARNING, "Span %d: %s tried CallRerouting/CallDeflection to empty number!\n", pri->span, ast_channel_name(owner)); pri_rerouting_rsp(pri->pri, call_rsp, subcmd->u.rerouting.invoke_id, PRI_REROUTING_RSP_INVALID_NUMBER); ast_channel_unlock(owner); break; } ast_verb(3, "Span %d: %s is CallRerouting/CallDeflection to '%s'.\n", pri->span, ast_channel_name(owner), subcmd->u.rerouting.deflection.to.number.str); /* * Send back positive ACK to CallRerouting/CallDeflection. * * Note: This call will be hungup by the core when it processes * the call_forward string. */ pri_rerouting_rsp(pri->pri, call_rsp, subcmd->u.rerouting.invoke_id, PRI_REROUTING_RSP_OK_CLEAR); pri_deflection = subcmd->u.rerouting.deflection; /* Adjust the deflecting to number based upon the subscription option. */ switch (subcmd->u.rerouting.subscription_option) { case 0: /* noNotification */ case 1: /* notificationWithoutDivertedToNr */ /* Delete the number because the far end is not supposed to see it. */ pri_deflection.to.number.presentation = PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED; pri_deflection.to.number.plan = (PRI_TON_UNKNOWN << 4) | PRI_NPI_E163_E164; pri_deflection.to.number.str[0] = '\0'; break; case 2: /* notificationWithDivertedToNr */ break; case 3: /* notApplicable */ default: break; } sig_pri_redirecting_convert(&ast_redirecting, &pri_deflection, ast_channel_redirecting(owner), pri); ast_redirecting.orig.tag = ast_strdup(pri->pvts[chanpos]->user_tag); ast_redirecting.from.tag = ast_strdup(pri->pvts[chanpos]->user_tag); ast_redirecting.to.tag = ast_strdup(pri->pvts[chanpos]->user_tag); ast_channel_set_redirecting(owner, &ast_redirecting, NULL); ast_party_redirecting_free(&ast_redirecting); /* Request the core to forward to the new number. */ ast_channel_call_forward_set(owner, subcmd->u.rerouting.deflection.to.number.str); /* Wake up the channel. */ ast_queue_frame(owner, &ast_null_frame); ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_CALL_REROUTING) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_AVAILABLE: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { enum ast_cc_service_type service; switch (event_id) { case PRI_EVENT_RINGING: service = AST_CC_CCNR; break; case PRI_EVENT_HANGUP_REQ: /* We will assume that the cause was busy/congestion. */ service = AST_CC_CCBS; break; default: service = AST_CC_NONE; break; } if (service == AST_CC_NONE || sig_pri_cc_available(pri, chanpos, subcmd->u.cc_available.cc_id, service)) { pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id); } ast_channel_unlock(owner); } else { /* No asterisk channel. */ pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id); } break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_CALL: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { struct ast_cc_agent *agent; agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_call.cc_id); if (agent) { ast_setup_cc_recall_datastore(owner, agent->core_id); ast_cc_agent_set_interfaces_chanvar(owner); ast_cc_agent_recalling(agent->core_id, "%s caller is attempting recall", sig_pri_cc_type_name); ao2_ref(agent, -1); } ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) case PRI_SUBCMD_CC_CANCEL: sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id, subcmd->u.cc_cancel.is_agent); break; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_TRANSFER) case PRI_SUBCMD_TRANSFER_CALL: if (!call_rsp) { /* Should never happen. */ ast_log(LOG_ERROR, "Call transfer subcommand without call to send response!\n"); break; } sig_pri_unlock_private(pri->pvts[chanpos]); xfer_rsp.pri = pri; xfer_rsp.call = call_rsp; xfer_rsp.invoke_id = subcmd->u.transfer.invoke_id; xfer_rsp.responded = 0; sig_pri_attempt_transfer(pri, subcmd->u.transfer.call_1, subcmd->u.transfer.is_call_1_held, subcmd->u.transfer.call_2, subcmd->u.transfer.is_call_2_held, &xfer_rsp); sig_pri_lock_private(pri->pvts[chanpos]); break; #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_AOC_EVENTS) case PRI_SUBCMD_AOC_S: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { sig_pri_aoc_s_from_pri(&subcmd->u.aoc_s, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S)); ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) case PRI_SUBCMD_AOC_D: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { /* Queue AST_CONTROL_AOC frame on channel */ sig_pri_aoc_d_from_pri(&subcmd->u.aoc_d, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D)); ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) case PRI_SUBCMD_AOC_E: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; /* Queue AST_CONTROL_AOC frame */ sig_pri_aoc_e_from_pri(&subcmd->u.aoc_e, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E)); if (owner) { ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) case PRI_SUBCMD_AOC_CHARGING_REQ: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { sig_pri_aoc_request_from_pri(&subcmd->u.aoc_request, pri->pvts[chanpos], call_rsp); ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_AOC_EVENTS) case PRI_SUBCMD_AOC_CHARGING_REQ_RSP: /* * An AOC request response may contain an AOC-S rate list. * If this is the case handle this just like we * would an incoming AOC-S msg. */ if (subcmd->u.aoc_request_response.valid_aoc_s) { sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { sig_pri_aoc_s_from_pri(&subcmd->u.aoc_request_response.aoc_s, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S)); ast_channel_unlock(owner); } } break; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_MCID) case PRI_SUBCMD_MCID_REQ: sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; sig_pri_mcid_event(pri, &subcmd->u.mcid_req, owner); if (owner) { ast_channel_unlock(owner); } break; #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_MCID) case PRI_SUBCMD_MCID_RSP: /* Ignore for now. */ break; #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_DISPLAY_TEXT) case PRI_SUBCMD_DISPLAY_TEXT: if (event_id != PRI_EVENT_RING) { /* * This display text was not from a SETUP message. We can do * something with this display text string. */ sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { struct ast_frame f; /* Pass the display text to the peer channel. */ memset(&f, 0, sizeof(f)); f.frametype = AST_FRAME_TEXT; f.subclass.integer = 0; f.offset = 0; f.data.ptr = &subcmd->u.display.text; f.datalen = subcmd->u.display.length + 1; ast_queue_frame(owner, &f); ast_channel_unlock(owner); } } break; #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ default: ast_debug(2, "Span %d: Unknown call subcommand(%d) in %s event.\n", pri->span, subcmd->cmd, pri_event2str(event_id)); break; } } } /*! * \internal * \brief Convert the MOH state to string. * \since 10.0 * * \param state MOH state to process. * * \return String version of MOH state. */ static const char *sig_pri_moh_state_str(enum sig_pri_moh_state state) { const char *str; str = "Unknown"; switch (state) { case SIG_PRI_MOH_STATE_IDLE: str = "SIG_PRI_MOH_STATE_IDLE"; break; case SIG_PRI_MOH_STATE_NOTIFY: str = "SIG_PRI_MOH_STATE_NOTIFY"; break; case SIG_PRI_MOH_STATE_MOH: str = "SIG_PRI_MOH_STATE_MOH"; break; #if defined(HAVE_PRI_CALL_HOLD) case SIG_PRI_MOH_STATE_HOLD_REQ: str = "SIG_PRI_MOH_STATE_HOLD_REQ"; break; case SIG_PRI_MOH_STATE_PEND_UNHOLD: str = "SIG_PRI_MOH_STATE_PEND_UNHOLD"; break; case SIG_PRI_MOH_STATE_HOLD: str = "SIG_PRI_MOH_STATE_HOLD"; break; case SIG_PRI_MOH_STATE_RETRIEVE_REQ: str = "SIG_PRI_MOH_STATE_RETRIEVE_REQ"; break; case SIG_PRI_MOH_STATE_PEND_HOLD: str = "SIG_PRI_MOH_STATE_PEND_HOLD"; break; case SIG_PRI_MOH_STATE_RETRIEVE_FAIL: str = "SIG_PRI_MOH_STATE_RETRIEVE_FAIL"; break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ case SIG_PRI_MOH_STATE_NUM: /* Not a real state. */ break; } return str; } /*! * \internal * \brief Convert the MOH event to string. * \since 10.0 * * \param event MOH event to process. * * \return String version of MOH event. */ static const char *sig_pri_moh_event_str(enum sig_pri_moh_event event) { const char *str; str = "Unknown"; switch (event) { case SIG_PRI_MOH_EVENT_RESET: str = "SIG_PRI_MOH_EVENT_RESET"; break; case SIG_PRI_MOH_EVENT_HOLD: str = "SIG_PRI_MOH_EVENT_HOLD"; break; case SIG_PRI_MOH_EVENT_UNHOLD: str = "SIG_PRI_MOH_EVENT_UNHOLD"; break; #if defined(HAVE_PRI_CALL_HOLD) case SIG_PRI_MOH_EVENT_HOLD_ACK: str = "SIG_PRI_MOH_EVENT_HOLD_ACK"; break; case SIG_PRI_MOH_EVENT_HOLD_REJ: str = "SIG_PRI_MOH_EVENT_HOLD_REJ"; break; case SIG_PRI_MOH_EVENT_RETRIEVE_ACK: str = "SIG_PRI_MOH_EVENT_RETRIEVE_ACK"; break; case SIG_PRI_MOH_EVENT_RETRIEVE_REJ: str = "SIG_PRI_MOH_EVENT_RETRIEVE_REJ"; break; case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK: str = "SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK"; break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ case SIG_PRI_MOH_EVENT_NUM: /* Not a real event. */ break; } return str; } #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Retrieve a call that was placed on hold by the HOLD message. * \since 10.0 * * \param pvt Channel private control structure. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_retrieve_call(struct sig_pri_chan *pvt) { int chanpos; int channel; if (pvt->pri->nodetype == PRI_NETWORK) { /* Find an available channel to propose */ chanpos = pri_find_empty_chan(pvt->pri, 1); if (chanpos < 0) { /* No channels available. */ return SIG_PRI_MOH_STATE_RETRIEVE_FAIL; } channel = PVT_TO_CHANNEL(pvt->pri->pvts[chanpos]); /* * We cannot occupy or reserve this channel at this time because * the retrieve may fail or we could have a RETRIEVE collision. */ } else { /* Let the network pick the channel. */ channel = 0; } if (pri_retrieve(pvt->pri->pri, pvt->call, channel)) { return SIG_PRI_MOH_STATE_RETRIEVE_FAIL; } return SIG_PRI_MOH_STATE_RETRIEVE_REQ; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ /*! * \internal * \brief MOH FSM state idle. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_idle(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_HOLD: if (!strcasecmp(pvt->mohinterpret, "passthrough")) { /* * This config setting is deprecated. * The old way did not send MOH just in case the notification was ignored. */ pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_HOLD); next_state = SIG_PRI_MOH_STATE_NOTIFY; break; } switch (pvt->pri->moh_signaling) { default: case SIG_PRI_MOH_SIGNALING_MOH: ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); next_state = SIG_PRI_MOH_STATE_MOH; break; case SIG_PRI_MOH_SIGNALING_NOTIFY: /* Send MOH anyway in case the far end does not interpret the notification. */ ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_HOLD); next_state = SIG_PRI_MOH_STATE_NOTIFY; break; #if defined(HAVE_PRI_CALL_HOLD) case SIG_PRI_MOH_SIGNALING_HOLD: if (pri_hold(pvt->pri->pri, pvt->call)) { /* Fall back to MOH instead */ ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); next_state = SIG_PRI_MOH_STATE_MOH; } else { next_state = SIG_PRI_MOH_STATE_HOLD_REQ; } break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ } break; default: break; } pvt->moh_state = next_state; return next_state; } /*! * \internal * \brief MOH FSM state notify remote party. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_notify(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_HOLD: if (strcasecmp(pvt->mohinterpret, "passthrough")) { /* Restart MOH in case it was stopped by other means. */ ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); } break; case SIG_PRI_MOH_EVENT_UNHOLD: pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_RETRIEVAL); /* Fall through */ case SIG_PRI_MOH_EVENT_RESET: ast_moh_stop(chan); next_state = SIG_PRI_MOH_STATE_IDLE; break; default: break; } pvt->moh_state = next_state; return next_state; } /*! * \internal * \brief MOH FSM state generate moh. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_moh(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_HOLD: /* Restart MOH in case it was stopped by other means. */ ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); break; case SIG_PRI_MOH_EVENT_RESET: case SIG_PRI_MOH_EVENT_UNHOLD: ast_moh_stop(chan); next_state = SIG_PRI_MOH_STATE_IDLE; break; default: break; } pvt->moh_state = next_state; return next_state; } #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief MOH FSM state hold requested. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_hold_req(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_RESET: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_UNHOLD: next_state = SIG_PRI_MOH_STATE_PEND_UNHOLD; break; case SIG_PRI_MOH_EVENT_HOLD_REJ: /* Fall back to MOH */ if (chan) { ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); } next_state = SIG_PRI_MOH_STATE_MOH; break; case SIG_PRI_MOH_EVENT_HOLD_ACK: next_state = SIG_PRI_MOH_STATE_HOLD; break; default: break; } pvt->moh_state = next_state; return next_state; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief MOH FSM state hold requested with pending unhold. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_pend_unhold(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_RESET: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_HOLD: next_state = SIG_PRI_MOH_STATE_HOLD_REQ; break; case SIG_PRI_MOH_EVENT_HOLD_REJ: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_HOLD_ACK: next_state = sig_pri_moh_retrieve_call(pvt); break; default: break; } pvt->moh_state = next_state; return next_state; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief MOH FSM state hold. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_hold(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_RESET: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_UNHOLD: next_state = sig_pri_moh_retrieve_call(pvt); break; case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK: /* Fall back to MOH */ if (chan) { ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); } next_state = SIG_PRI_MOH_STATE_MOH; break; default: break; } pvt->moh_state = next_state; return next_state; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief MOH FSM state retrieve requested. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_retrieve_req(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_RESET: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_HOLD: next_state = SIG_PRI_MOH_STATE_PEND_HOLD; break; case SIG_PRI_MOH_EVENT_RETRIEVE_ACK: case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_RETRIEVE_REJ: next_state = SIG_PRI_MOH_STATE_RETRIEVE_FAIL; break; default: break; } pvt->moh_state = next_state; return next_state; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief MOH FSM state retrieve requested with pending hold. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_pend_hold(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_RESET: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_UNHOLD: next_state = SIG_PRI_MOH_STATE_RETRIEVE_REQ; break; case SIG_PRI_MOH_EVENT_RETRIEVE_ACK: case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK: /* * Successfully came off of hold. Now we can reinterpret the * MOH signaling option to handle the pending HOLD request. */ switch (pvt->pri->moh_signaling) { default: case SIG_PRI_MOH_SIGNALING_MOH: if (chan) { ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); } next_state = SIG_PRI_MOH_STATE_MOH; break; case SIG_PRI_MOH_SIGNALING_NOTIFY: /* Send MOH anyway in case the far end does not interpret the notification. */ if (chan) { ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); } pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_HOLD); next_state = SIG_PRI_MOH_STATE_NOTIFY; break; case SIG_PRI_MOH_SIGNALING_HOLD: if (pri_hold(pvt->pri->pri, pvt->call)) { /* Fall back to MOH instead */ if (chan) { ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret); } next_state = SIG_PRI_MOH_STATE_MOH; } else { next_state = SIG_PRI_MOH_STATE_HOLD_REQ; } break; } break; case SIG_PRI_MOH_EVENT_RETRIEVE_REJ: /* * We cannot reinterpret the MOH signaling option because we * failed to come off of hold. */ next_state = SIG_PRI_MOH_STATE_HOLD; break; default: break; } pvt->moh_state = next_state; return next_state; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief MOH FSM state retrieve failed. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ static enum sig_pri_moh_state sig_pri_moh_fsm_retrieve_fail(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state next_state; next_state = pvt->moh_state; switch (event) { case SIG_PRI_MOH_EVENT_RESET: next_state = SIG_PRI_MOH_STATE_IDLE; break; case SIG_PRI_MOH_EVENT_HOLD: next_state = SIG_PRI_MOH_STATE_HOLD; break; case SIG_PRI_MOH_EVENT_UNHOLD: next_state = sig_pri_moh_retrieve_call(pvt); break; case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK: next_state = SIG_PRI_MOH_STATE_IDLE; break; default: break; } pvt->moh_state = next_state; return next_state; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ /*! * \internal * \brief MOH FSM state function type. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Next MOH state */ typedef enum sig_pri_moh_state (*sig_pri_moh_fsm_state)(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event); /*! MOH FSM state table. */ static const sig_pri_moh_fsm_state sig_pri_moh_fsm[SIG_PRI_MOH_STATE_NUM] = { /* *INDENT-OFF* */ [SIG_PRI_MOH_STATE_IDLE] = sig_pri_moh_fsm_idle, [SIG_PRI_MOH_STATE_NOTIFY] = sig_pri_moh_fsm_notify, [SIG_PRI_MOH_STATE_MOH] = sig_pri_moh_fsm_moh, #if defined(HAVE_PRI_CALL_HOLD) [SIG_PRI_MOH_STATE_HOLD_REQ] = sig_pri_moh_fsm_hold_req, [SIG_PRI_MOH_STATE_PEND_UNHOLD] = sig_pri_moh_fsm_pend_unhold, [SIG_PRI_MOH_STATE_HOLD] = sig_pri_moh_fsm_hold, [SIG_PRI_MOH_STATE_RETRIEVE_REQ] = sig_pri_moh_fsm_retrieve_req, [SIG_PRI_MOH_STATE_PEND_HOLD] = sig_pri_moh_fsm_pend_hold, [SIG_PRI_MOH_STATE_RETRIEVE_FAIL] = sig_pri_moh_fsm_retrieve_fail, #endif /* defined(HAVE_PRI_CALL_HOLD) */ /* *INDENT-ON* */ }; /*! * \internal * \brief Send an event to the MOH FSM. * \since 10.0 * * \param chan Channel to post event to (Usually pvt->owner) * \param pvt Channel private control structure. * \param event MOH event to process. * * \note Assumes the pvt->pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pvt) is already obtained. * * \return Nothing */ static void sig_pri_moh_fsm_event(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event) { enum sig_pri_moh_state orig_state; enum sig_pri_moh_state next_state; const char *chan_name; if (chan) { chan_name = ast_strdupa(ast_channel_name(chan)); } else { chan_name = "Unknown"; } orig_state = pvt->moh_state; ast_debug(2, "Channel '%s' MOH-Event: %s in state %s\n", chan_name, sig_pri_moh_event_str(event), sig_pri_moh_state_str(orig_state)); if (orig_state < SIG_PRI_MOH_STATE_IDLE || SIG_PRI_MOH_STATE_NUM <= orig_state || !sig_pri_moh_fsm[orig_state]) { /* Programming error: State not implemented. */ ast_log(LOG_ERROR, "MOH state not implemented: %s(%u)\n", sig_pri_moh_state_str(orig_state), orig_state); return; } /* Execute the state. */ next_state = sig_pri_moh_fsm[orig_state](chan, pvt, event); ast_debug(2, "Channel '%s' MOH-Next-State: %s\n", chan_name, (orig_state == next_state) ? "$" : sig_pri_moh_state_str(next_state)); } /*! * \internal * \brief Set callid threadstorage for the pri_dchannel thread when a new call is created * * \return A new callid which has been bound to threadstorage. The return must be * unreffed and the threadstorage should be unbound when the pri_dchannel * primary loop wraps. */ static struct ast_callid *func_pri_dchannel_new_callid(void) { struct ast_callid *callid = ast_create_callid(); if (callid) { ast_callid_threadassoc_add(callid); } return callid; } /*! * \internal * \brief Set callid threadstorage for the pri_dchannel thread to that of an existing channel * * \param pri PRI span control structure. * \param chanpos channel position in the span * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return a reference to the callid bound to the channel which has also * been bound to threadstorage if it exists. If this returns non-NULL, * the callid must be unreffed and the threadstorage should be unbound * when the pri_dchannel primary loop wraps. */ static struct ast_callid *func_pri_dchannel_chanpos_callid(struct sig_pri_span *pri, int chanpos) { if (chanpos < 0) { return NULL; } sig_pri_lock_owner(pri, chanpos); if (pri->pvts[chanpos]->owner) { struct ast_callid *callid; callid = ast_channel_callid(pri->pvts[chanpos]->owner); ast_channel_unlock(pri->pvts[chanpos]->owner); if (callid) { ast_callid_threadassoc_add(callid); return callid; } } return NULL; } #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Handle the hold event from libpri. * \since 1.8 * * \param pri PRI span control structure. * \param ev Hold event received. * * \note Assumes the pri->lock is already obtained. * * \retval 0 on success. * \retval -1 on error. */ static int sig_pri_handle_hold(struct sig_pri_span *pri, pri_event *ev) { int retval; int chanpos_old; int chanpos_new; struct ast_channel *owner; struct ast_callid *callid = NULL; chanpos_old = pri_find_principle_by_call(pri, ev->hold.call); if (chanpos_old < 0) { ast_log(LOG_WARNING, "Span %d: Received HOLD for unknown call.\n", pri->span); return -1; } if (pri->pvts[chanpos_old]->no_b_channel) { /* Call is already on hold or is call waiting call. */ return -1; } chanpos_new = -1; sig_pri_lock_private(pri->pvts[chanpos_old]); sig_pri_lock_owner(pri, chanpos_old); owner = pri->pvts[chanpos_old]->owner; if (!owner) { goto done_with_private; } callid = ast_channel_callid(owner); if (callid) { ast_callid_threadassoc_add(callid); } if (pri->pvts[chanpos_old]->call_level != SIG_PRI_CALL_LEVEL_CONNECT) { /* * Make things simple. Don't allow placing a call on hold that * is not connected. */ goto done_with_owner; } chanpos_new = pri_find_empty_nobch(pri); if (chanpos_new < 0) { /* No hold channel available. */ goto done_with_owner; } sig_pri_handle_subcmds(pri, chanpos_old, ev->e, ev->hold.subcmds, ev->hold.call); sig_pri_queue_hold(pri, chanpos_old); chanpos_new = pri_fixup_principle(pri, chanpos_new, ev->hold.call); if (chanpos_new < 0) { /* Should never happen. */ sig_pri_queue_unhold(pri, chanpos_old); } done_with_owner:; ast_channel_unlock(owner); done_with_private:; sig_pri_unlock_private(pri->pvts[chanpos_old]); if (chanpos_new < 0) { retval = -1; } else { sig_pri_span_devstate_changed(pri); retval = 0; } if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } return retval; } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Handle the hold acknowledge event from libpri. * \since 10.0 * * \param pri PRI span control structure. * \param ev Hold acknowledge event received. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_hold_ack(struct sig_pri_span *pri, pri_event *ev) { int chanpos; struct ast_callid *callid; /* * We were successfully put on hold by the remote party * so we just need to switch to a no_b_channel channel. */ chanpos = pri_find_empty_nobch(pri); if (chanpos < 0) { /* Very bad news. No hold channel available. */ ast_log(LOG_ERROR, "Span %d: No hold channel available for held call that is on %d/%d\n", pri->span, PRI_SPAN(ev->hold_ack.channel), PRI_CHANNEL(ev->hold_ack.channel)); sig_pri_kill_call(pri, ev->hold_ack.call, PRI_CAUSE_RESOURCE_UNAVAIL_UNSPECIFIED); return; } chanpos = pri_fixup_principle(pri, chanpos, ev->hold_ack.call); if (chanpos < 0) { /* Should never happen. */ sig_pri_kill_call(pri, ev->hold_ack.call, PRI_CAUSE_NORMAL_TEMPORARY_FAILURE); return; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->hold_ack.subcmds, ev->hold_ack.call); sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos], SIG_PRI_MOH_EVENT_HOLD_ACK); sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Handle the hold reject event from libpri. * \since 10.0 * * \param pri PRI span control structure. * \param ev Hold reject event received. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_hold_rej(struct sig_pri_span *pri, pri_event *ev) { int chanpos; struct ast_callid *callid; chanpos = pri_find_principle(pri, ev->hold_rej.channel, ev->hold_rej.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Could not find principle for HOLD_REJECT\n", pri->span); sig_pri_kill_call(pri, ev->hold_rej.call, PRI_CAUSE_NORMAL_TEMPORARY_FAILURE); return; } chanpos = pri_fixup_principle(pri, chanpos, ev->hold_rej.call); if (chanpos < 0) { /* Should never happen. */ sig_pri_kill_call(pri, ev->hold_rej.call, PRI_CAUSE_NORMAL_TEMPORARY_FAILURE); return; } ast_debug(1, "Span %d: HOLD_REJECT cause: %d(%s)\n", pri->span, ev->hold_rej.cause, pri_cause2str(ev->hold_rej.cause)); sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->hold_rej.subcmds, ev->hold_rej.call); sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos], SIG_PRI_MOH_EVENT_HOLD_REJ); sig_pri_unlock_private(pri->pvts[chanpos]); if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Handle the retrieve event from libpri. * \since 1.8 * * \param pri PRI span control structure. * \param ev Retrieve event received. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_retrieve(struct sig_pri_span *pri, pri_event *ev) { int chanpos; struct ast_callid *callid; if (!(ev->retrieve.channel & PRI_HELD_CALL)) { /* The call is not currently held. */ pri_retrieve_rej(pri->pri, ev->retrieve.call, PRI_CAUSE_RESOURCE_UNAVAIL_UNSPECIFIED); return; } if (pri_find_principle_by_call(pri, ev->retrieve.call) < 0) { ast_log(LOG_WARNING, "Span %d: Received RETRIEVE for unknown call.\n", pri->span); pri_retrieve_rej(pri->pri, ev->retrieve.call, PRI_CAUSE_RESOURCE_UNAVAIL_UNSPECIFIED); return; } if (PRI_CHANNEL(ev->retrieve.channel) == 0xFF) { chanpos = pri_find_empty_chan(pri, 1); } else { chanpos = pri_find_principle(pri, ev->retrieve.channel & ~PRI_HELD_CALL, ev->retrieve.call); if (ev->retrieve.flexible && (chanpos < 0 || !sig_pri_is_chan_available(pri->pvts[chanpos]))) { /* * Channel selection is flexible and the requested channel * is bad or not available. Pick another channel. */ chanpos = pri_find_empty_chan(pri, 1); } } if (chanpos < 0) { pri_retrieve_rej(pri->pri, ev->retrieve.call, ev->retrieve.flexible ? PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION : PRI_CAUSE_REQUESTED_CHAN_UNAVAIL); return; } chanpos = pri_fixup_principle(pri, chanpos, ev->retrieve.call); if (chanpos < 0) { /* Channel is already in use. */ pri_retrieve_rej(pri->pri, ev->retrieve.call, PRI_CAUSE_REQUESTED_CHAN_UNAVAIL); return; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->retrieve.subcmds, ev->retrieve.call); sig_pri_queue_unhold(pri, chanpos); pri_retrieve_ack(pri->pri, ev->retrieve.call, PVT_TO_CHANNEL(pri->pvts[chanpos])); sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos], SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK); sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Handle the retrieve acknowledge event from libpri. * \since 10.0 * * \param pri PRI span control structure. * \param ev Retrieve acknowledge event received. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_retrieve_ack(struct sig_pri_span *pri, pri_event *ev) { int chanpos; struct ast_callid *callid; chanpos = pri_find_fixup_principle(pri, ev->retrieve_ack.channel, ev->retrieve_ack.call); if (chanpos < 0) { return; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->retrieve_ack.subcmds, ev->retrieve_ack.call); sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos], SIG_PRI_MOH_EVENT_RETRIEVE_ACK); sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) /*! * \internal * \brief Handle the retrieve reject event from libpri. * \since 10.0 * * \param pri PRI span control structure. * \param ev Retrieve reject event received. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_retrieve_rej(struct sig_pri_span *pri, pri_event *ev) { int chanpos; struct ast_callid *callid; chanpos = pri_find_principle(pri, ev->retrieve_rej.channel, ev->retrieve_rej.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Could not find principle for RETRIEVE_REJECT\n", pri->span); sig_pri_kill_call(pri, ev->retrieve_rej.call, PRI_CAUSE_NORMAL_TEMPORARY_FAILURE); return; } chanpos = pri_fixup_principle(pri, chanpos, ev->retrieve_rej.call); if (chanpos < 0) { /* Should never happen. */ sig_pri_kill_call(pri, ev->retrieve_rej.call, PRI_CAUSE_NORMAL_TEMPORARY_FAILURE); return; } ast_debug(1, "Span %d: RETRIEVE_REJECT cause: %d(%s)\n", pri->span, ev->retrieve_rej.cause, pri_cause2str(ev->retrieve_rej.cause)); sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->retrieve_rej.subcmds, ev->retrieve_rej.call); sig_pri_moh_fsm_event(pri->pvts[chanpos]->owner, pri->pvts[chanpos], SIG_PRI_MOH_EVENT_RETRIEVE_REJ); sig_pri_unlock_private(pri->pvts[chanpos]); if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } #endif /* defined(HAVE_PRI_CALL_HOLD) */ /*! * \internal * \brief Setup channel variables on the owner. * * \param pri PRI span control structure. * \param chanpos Channel position in the span. * \param ev SETUP event received. * * \note Assumes the pri->lock is already obtained. * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. * * \return Nothing */ static void setup_incoming_channel(struct sig_pri_span *pri, int chanpos, pri_event *ev) { struct ast_channel *owner; char ani2str[6]; char calledtonstr[10]; sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (!owner) { return; } ast_channel_stage_snapshot(owner); #if defined(HAVE_PRI_SUBADDR) if (ev->ring.calling.subaddress.valid) { /* Set Calling Subaddress */ sig_pri_set_subaddress(&ast_channel_caller(owner)->id.subaddress, &ev->ring.calling.subaddress); if (!ev->ring.calling.subaddress.type && !ast_strlen_zero((char *) ev->ring.calling.subaddress.data)) { /* NSAP */ pbx_builtin_setvar_helper(owner, "CALLINGSUBADDR", (char *) ev->ring.calling.subaddress.data); } } if (ev->ring.called_subaddress.valid) { /* Set Called Subaddress */ sig_pri_set_subaddress(&ast_channel_dialed(owner)->subaddress, &ev->ring.called_subaddress); if (!ev->ring.called_subaddress.type && !ast_strlen_zero((char *) ev->ring.called_subaddress.data)) { /* NSAP */ pbx_builtin_setvar_helper(owner, "CALLEDSUBADDR", (char *) ev->ring.called_subaddress.data); } } #else if (!ast_strlen_zero(ev->ring.callingsubaddr)) { pbx_builtin_setvar_helper(owner, "CALLINGSUBADDR", ev->ring.callingsubaddr); } #endif /* !defined(HAVE_PRI_SUBADDR) */ if (ev->ring.ani2 >= 0) { ast_channel_caller(owner)->ani2 = ev->ring.ani2; snprintf(ani2str, sizeof(ani2str), "%d", ev->ring.ani2); pbx_builtin_setvar_helper(owner, "ANI2", ani2str); } #ifdef SUPPORT_USERUSER if (!ast_strlen_zero(ev->ring.useruserinfo)) { pbx_builtin_setvar_helper(owner, "USERUSERINFO", ev->ring.useruserinfo); } #endif snprintf(calledtonstr, sizeof(calledtonstr), "%d", ev->ring.calledplan); pbx_builtin_setvar_helper(owner, "CALLEDTON", calledtonstr); ast_channel_dialed(owner)->number.plan = ev->ring.calledplan; if (ev->ring.redirectingreason >= 0) { /* This is now just a status variable. Use REDIRECTING() dialplan function. */ pbx_builtin_setvar_helper(owner, "PRIREDIRECTREASON", redirectingreason2str(ev->ring.redirectingreason)); } #if defined(HAVE_PRI_REVERSE_CHARGE) pri->pvts[chanpos]->reverse_charging_indication = ev->ring.reversecharge; #endif #if defined(HAVE_PRI_SETUP_KEYPAD) ast_copy_string(pri->pvts[chanpos]->keypad_digits, ev->ring.keypad_digits, sizeof(pri->pvts[chanpos]->keypad_digits)); #endif /* defined(HAVE_PRI_SETUP_KEYPAD) */ /* * It's ok to call this with the owner already locked here * since it will want to do this anyway if there are any * subcmds. */ sig_pri_handle_subcmds(pri, chanpos, ev->e, ev->ring.subcmds, ev->ring.call); ast_channel_stage_snapshot_done(owner); ast_channel_unlock(owner); } /*! * \internal * \brief Handle the incoming SETUP event from libpri. * * \param pri PRI span control structure. * \param e SETUP event received. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void sig_pri_handle_setup(struct sig_pri_span *pri, pri_event *e) { int exten_exists_or_can_exist; int could_match_more; int need_dialtone; enum sig_pri_law law; int chanpos = -1; struct ast_callid *callid = NULL; struct ast_channel *c; char plancallingnum[AST_MAX_EXTENSION]; char plancallingani[AST_MAX_EXTENSION]; pthread_t threadid; if (!ast_strlen_zero(pri->msn_list) && !sig_pri_msn_match(pri->msn_list, e->ring.callednum)) { /* The call is not for us so ignore it. */ ast_verb(3, "Ignoring call to '%s' on span %d. Its not in the MSN list: %s\n", e->ring.callednum, pri->span, pri->msn_list); pri_destroycall(pri->pri, e->ring.call); goto setup_exit; } if (sig_pri_is_cis_call(e->ring.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->ring.subcmds, e->ring.call); goto setup_exit; } chanpos = pri_find_principle_by_call(pri, e->ring.call); if (-1 < chanpos) { /* Libpri has already filtered out duplicate SETUPs. */ ast_log(LOG_WARNING, "Span %d: Got SETUP with duplicate call ptr (%p). Dropping call.\n", pri->span, e->ring.call); pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_NORMAL_TEMPORARY_FAILURE); goto setup_exit; } if (e->ring.channel == -1 || PRI_CHANNEL(e->ring.channel) == 0xFF) { /* Any channel requested. */ chanpos = pri_find_empty_chan(pri, 1); if (-1 < chanpos) { callid = func_pri_dchannel_new_callid(); } } else if (PRI_CHANNEL(e->ring.channel) == 0x00) { /* No channel specified. */ #if defined(HAVE_PRI_CALL_WAITING) if (!pri->allow_call_waiting_calls) #endif /* defined(HAVE_PRI_CALL_WAITING) */ { /* We will not accept incoming call waiting calls. */ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION); goto setup_exit; } #if defined(HAVE_PRI_CALL_WAITING) chanpos = pri_find_empty_nobch(pri); if (chanpos < 0) { /* We could not find/create a call interface. */ pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION); goto setup_exit; } callid = func_pri_dchannel_new_callid(); /* Setup the call interface to use. */ sig_pri_init_config(pri->pvts[chanpos], pri); #endif /* defined(HAVE_PRI_CALL_WAITING) */ } else { /* A channel is specified. */ callid = func_pri_dchannel_new_callid(); chanpos = pri_find_principle(pri, e->ring.channel, e->ring.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: SETUP on unconfigured channel %d/%d\n", pri->span, PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel)); } else { switch (pri->pvts[chanpos]->resetting) { case SIG_PRI_RESET_IDLE: break; case SIG_PRI_RESET_ACTIVE: /* * The peer may have lost the expected ack or not received the * RESTART yet. */ pri->pvts[chanpos]->resetting = SIG_PRI_RESET_NO_ACK; break; case SIG_PRI_RESET_NO_ACK: /* The peer likely is not going to ack the RESTART. */ ast_debug(1, "Span %d: Second SETUP while waiting for RESTART ACKNOWLEDGE on channel %d/%d\n", pri->span, PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel)); /* Assume we got the ack. */ pri->pvts[chanpos]->resetting = SIG_PRI_RESET_IDLE; if (pri->resetting) { /* Go on to the next idle channel to RESTART. */ pri_check_restart(pri); } break; } if (!sig_pri_is_chan_available(pri->pvts[chanpos])) { /* This is where we handle initial glare */ ast_debug(1, "Span %d: SETUP requested unavailable channel %d/%d. Attempting to renegotiate.\n", pri->span, PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel)); chanpos = -1; } } #if defined(ALWAYS_PICK_CHANNEL) if (e->ring.flexible) { chanpos = -1; } #endif /* defined(ALWAYS_PICK_CHANNEL) */ if (chanpos < 0 && e->ring.flexible) { /* We can try to pick another channel. */ chanpos = pri_find_empty_chan(pri, 1); } } if (chanpos < 0) { if (e->ring.flexible) { pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION); } else { pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_REQUESTED_CHAN_UNAVAIL); } goto setup_exit; } sig_pri_lock_private(pri->pvts[chanpos]); /* Mark channel as in use so noone else will steal it. */ pri->pvts[chanpos]->call = e->ring.call; /* Use plancallingnum as a scratch buffer since it is initialized next. */ apply_plan_to_existing_number(plancallingnum, sizeof(plancallingnum), pri, e->ring.redirectingnum, e->ring.callingplanrdnis); sig_pri_set_rdnis(pri->pvts[chanpos], plancallingnum); /* Setup caller-id info */ apply_plan_to_existing_number(plancallingnum, sizeof(plancallingnum), pri, e->ring.callingnum, e->ring.callingplan); pri->pvts[chanpos]->cid_ani2 = 0; if (pri->pvts[chanpos]->use_callerid) { ast_shrink_phone_number(plancallingnum); ast_copy_string(pri->pvts[chanpos]->cid_num, plancallingnum, sizeof(pri->pvts[chanpos]->cid_num)); #ifdef PRI_ANI apply_plan_to_existing_number(plancallingani, sizeof(plancallingani), pri, e->ring.callingani, e->ring.callingplanani); ast_shrink_phone_number(plancallingani); ast_copy_string(pri->pvts[chanpos]->cid_ani, plancallingani, sizeof(pri->pvts[chanpos]->cid_ani)); #endif pri->pvts[chanpos]->cid_subaddr[0] = '\0'; #if defined(HAVE_PRI_SUBADDR) if (e->ring.calling.subaddress.valid) { struct ast_party_subaddress calling_subaddress; ast_party_subaddress_init(&calling_subaddress); sig_pri_set_subaddress(&calling_subaddress, &e->ring.calling.subaddress); if (calling_subaddress.str) { ast_copy_string(pri->pvts[chanpos]->cid_subaddr, calling_subaddress.str, sizeof(pri->pvts[chanpos]->cid_subaddr)); } ast_party_subaddress_free(&calling_subaddress); } #endif /* defined(HAVE_PRI_SUBADDR) */ ast_copy_string(pri->pvts[chanpos]->cid_name, e->ring.callingname, sizeof(pri->pvts[chanpos]->cid_name)); /* this is the callingplan (TON/NPI), e->ring.callingplan>>4 would be the TON */ pri->pvts[chanpos]->cid_ton = e->ring.callingplan; pri->pvts[chanpos]->callingpres = e->ring.callingpres; if (e->ring.ani2 >= 0) { pri->pvts[chanpos]->cid_ani2 = e->ring.ani2; } } else { pri->pvts[chanpos]->cid_num[0] = '\0'; pri->pvts[chanpos]->cid_subaddr[0] = '\0'; pri->pvts[chanpos]->cid_ani[0] = '\0'; pri->pvts[chanpos]->cid_name[0] = '\0'; pri->pvts[chanpos]->cid_ton = 0; pri->pvts[chanpos]->callingpres = 0; } /* Setup the user tag for party id's from this device for this call. */ if (pri->append_msn_to_user_tag) { snprintf(pri->pvts[chanpos]->user_tag, sizeof(pri->pvts[chanpos]->user_tag), "%s_%s", pri->initial_user_tag, pri->nodetype == PRI_NETWORK ? plancallingnum : e->ring.callednum); } else { ast_copy_string(pri->pvts[chanpos]->user_tag, pri->initial_user_tag, sizeof(pri->pvts[chanpos]->user_tag)); } sig_pri_set_caller_id(pri->pvts[chanpos]); /* Set DNID on all incoming calls -- even immediate */ sig_pri_set_dnid(pri->pvts[chanpos], e->ring.callednum); if (pri->pvts[chanpos]->immediate) { /* immediate=yes go to s|1 */ ast_verb(3, "Going to extension s|1 because of immediate=yes\n"); pri->pvts[chanpos]->exten[0] = 's'; pri->pvts[chanpos]->exten[1] = '\0'; } else if (!ast_strlen_zero(e->ring.callednum)) { /* Get called number */ ast_copy_string(pri->pvts[chanpos]->exten, e->ring.callednum, sizeof(pri->pvts[chanpos]->exten)); } else if (pri->overlapdial) { pri->pvts[chanpos]->exten[0] = '\0'; } else { /* Some PRI circuits are set up to send _no_ digits. Handle them as 's'. */ pri->pvts[chanpos]->exten[0] = 's'; pri->pvts[chanpos]->exten[1] = '\0'; } /* No number yet, but received "sending complete"? */ if (e->ring.complete && (ast_strlen_zero(e->ring.callednum))) { ast_verb(3, "Going to extension s|1 because of Complete received\n"); pri->pvts[chanpos]->exten[0] = 's'; pri->pvts[chanpos]->exten[1] = '\0'; } /* Make sure extension exists (or in overlap dial mode, can exist) */ exten_exists_or_can_exist = ((pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING) && ast_canmatch_extension(NULL, pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten, 1, pri->pvts[chanpos]->cid_num)) || ast_exists_extension(NULL, pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten, 1, pri->pvts[chanpos]->cid_num); if (!exten_exists_or_can_exist) { ast_verb(3, "Span %d: Extension %s@%s does not exist. Rejecting call from '%s'.\n", pri->span, pri->pvts[chanpos]->exten, pri->pvts[chanpos]->context, pri->pvts[chanpos]->cid_num); pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_UNALLOCATED); pri->pvts[chanpos]->call = NULL; pri->pvts[chanpos]->exten[0] = '\0'; sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); goto setup_exit; } /* Select audio companding mode. */ switch (e->ring.layer1) { case PRI_LAYER_1_ALAW: law = SIG_PRI_ALAW; break; case PRI_LAYER_1_ULAW: law = SIG_PRI_ULAW; break; default: /* This is a data call to us. */ law = SIG_PRI_DEFLAW; break; } could_match_more = !e->ring.complete && (pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING) && ast_matchmore_extension(NULL, pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten, 1, pri->pvts[chanpos]->cid_num); need_dialtone = could_match_more /* * Must explicitly check the digital capability this * way instead of checking the pvt->digital flag * because the flag hasn't been set yet. */ && !(e->ring.ctype & AST_TRANS_CAP_DIGITAL) && !pri->pvts[chanpos]->no_b_channel && (!strlen(pri->pvts[chanpos]->exten) || ast_ignore_pattern(pri->pvts[chanpos]->context, pri->pvts[chanpos]->exten)); if (e->ring.complete || !(pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)) { /* Just announce proceeding */ pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_PROCEEDING; pri_proceeding(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 0); } else if (pri->switchtype == PRI_SWITCH_GR303_TMC) { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_CONNECT; pri_answer(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1); } else { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_OVERLAP; #if defined(HAVE_PRI_SETUP_ACK_INBAND) pri_setup_ack(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1, need_dialtone); #else /* !defined(HAVE_PRI_SETUP_ACK_INBAND) */ pri_need_more_info(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1); #endif /* !defined(HAVE_PRI_SETUP_ACK_INBAND) */ } /* * Release the PRI lock while we create the channel so other * threads can send D channel messages. We must also release * the private lock to prevent deadlock while creating the * channel. */ sig_pri_unlock_private(pri->pvts[chanpos]); ast_mutex_unlock(&pri->lock); c = sig_pri_new_ast_channel(pri->pvts[chanpos], could_match_more ? AST_STATE_RESERVED : AST_STATE_RING, law, e->ring.ctype, pri->pvts[chanpos]->exten, NULL, NULL); ast_mutex_lock(&pri->lock); sig_pri_lock_private(pri->pvts[chanpos]); if (c) { setup_incoming_channel(pri, chanpos, e); /* Start PBX */ if (could_match_more) { #if !defined(HAVE_PRI_SETUP_ACK_INBAND) if (need_dialtone) { /* Indicate that we are providing dialtone. */ pri->pvts[chanpos]->progress = 1;/* No need to send plain PROGRESS again. */ #ifdef HAVE_PRI_PROG_W_CAUSE pri_progress_with_cause(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1, -1);/* no cause at all */ #else pri_progress(pri->pri, e->ring.call, PVT_TO_CHANNEL(pri->pvts[chanpos]), 1); #endif } #endif /* !defined(HAVE_PRI_SETUP_ACK_INBAND) */ if (!ast_pthread_create_detached(&threadid, NULL, pri_ss_thread, pri->pvts[chanpos])) { ast_verb(3, "Accepting overlap call from '%s' to '%s' on channel %d/%d, span %d\n", plancallingnum, S_OR(pri->pvts[chanpos]->exten, ""), pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span); sig_pri_unlock_private(pri->pvts[chanpos]); goto setup_exit; } } else { if (!ast_pbx_start(c)) { ast_verb(3, "Accepting call from '%s' to '%s' on channel %d/%d, span %d\n", plancallingnum, pri->pvts[chanpos]->exten, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span); sig_pri_set_echocanceller(pri->pvts[chanpos], 1); sig_pri_unlock_private(pri->pvts[chanpos]); goto setup_exit; } } } ast_log(LOG_WARNING, "Unable to start PBX on channel %d/%d, span %d\n", pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span); if (c) { /* Avoid deadlock while destroying channel */ sig_pri_unlock_private(pri->pvts[chanpos]); ast_mutex_unlock(&pri->lock); ast_hangup(c); ast_mutex_lock(&pri->lock); } else { pri_hangup(pri->pri, e->ring.call, PRI_CAUSE_SWITCH_CONGESTION); pri->pvts[chanpos]->call = NULL; sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); } setup_exit:; if (callid) { ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } static void *pri_dchannel(void *vpri) { struct sig_pri_span *pri = vpri; pri_event *e; struct pollfd fds[SIG_PRI_NUM_DCHANS]; int res; int x; struct timeval tv, lowest, *next; int doidling=0; char *cc; time_t t; int i, which=-1; int numdchans; struct timeval lastidle = { 0, 0 }; pthread_t p; struct ast_channel *idle; char idlen[80]; int nextidle = -1; int haveidles; int activeidles; unsigned int len; gettimeofday(&lastidle, NULL); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); if (!ast_strlen_zero(pri->idledial) && !ast_strlen_zero(pri->idleext)) { /* Need to do idle dialing, check to be sure though */ cc = strchr(pri->idleext, '@'); if (cc) { *cc = '\0'; cc++; ast_copy_string(pri->idlecontext, cc, sizeof(pri->idlecontext)); #if 0 /* Extensions may not be loaded yet */ if (!ast_exists_extension(NULL, pri->idlecontext, pri->idleext, 1, NULL)) ast_log(LOG_WARNING, "Extension '%s @ %s' does not exist\n", pri->idleext, pri->idlecontext); else #endif doidling = 1; } else ast_log(LOG_WARNING, "Idle dial string '%s' lacks '@context'\n", pri->idleext); } for (;;) { struct ast_callid *callid = NULL; for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { if (!pri->dchans[i]) break; fds[i].fd = pri->fds[i]; fds[i].events = POLLIN | POLLPRI; fds[i].revents = 0; } numdchans = i; time(&t); ast_mutex_lock(&pri->lock); if (pri->switchtype != PRI_SWITCH_GR303_TMC && (pri->sig != SIG_BRI_PTMP) && (pri->resetinterval > 0)) { if (pri->resetting && pri_is_up(pri)) { if (pri->resetpos < 0) { pri_check_restart(pri); if (pri->resetting) { sig_pri_span_devstate_changed(pri); } } } else { if (!pri->resetting && (t - pri->lastreset) >= pri->resetinterval) { pri->resetting = 1; pri->resetpos = -1; } } } /* Look for any idle channels if appropriate */ if (doidling && pri_is_up(pri)) { nextidle = -1; haveidles = 0; activeidles = 0; for (x = pri->numchans; x >= 0; x--) { if (pri->pvts[x] && !pri->pvts[x]->no_b_channel) { if (sig_pri_is_chan_available(pri->pvts[x])) { if (haveidles < pri->minunused) { haveidles++; } else { nextidle = x; break; } } else if (pri->pvts[x]->owner && pri->pvts[x]->isidlecall) { activeidles++; } } } if (nextidle > -1) { if (ast_tvdiff_ms(ast_tvnow(), lastidle) > 1000) { /* Don't create a new idle call more than once per second */ snprintf(idlen, sizeof(idlen), "%d/%s", pri->pvts[nextidle]->channel, pri->idledial); pri->pvts[nextidle]->allocated = 1; /* * Release the PRI lock while we create the channel so other * threads can send D channel messages. */ ast_mutex_unlock(&pri->lock); /* * We already have the B channel reserved for this call. We * just need to make sure that sig_pri_hangup() has completed * cleaning up before continuing. */ sig_pri_lock_private(pri->pvts[nextidle]); sig_pri_unlock_private(pri->pvts[nextidle]); idle = sig_pri_request(pri->pvts[nextidle], SIG_PRI_ULAW, NULL, NULL, 0); ast_mutex_lock(&pri->lock); if (idle) { pri->pvts[nextidle]->isidlecall = 1; if (ast_pthread_create_background(&p, NULL, do_idle_thread, pri->pvts[nextidle])) { ast_log(LOG_WARNING, "Unable to start new thread for idle channel '%s'\n", ast_channel_name(idle)); ast_mutex_unlock(&pri->lock); ast_hangup(idle); ast_mutex_lock(&pri->lock); } } else { pri->pvts[nextidle]->allocated = 0; ast_log(LOG_WARNING, "Unable to request channel 'DAHDI/%s' for idle call\n", idlen); } gettimeofday(&lastidle, NULL); } } else if ((haveidles < pri->minunused) && (activeidles > pri->minidle)) { /* Mark something for hangup if there is something that can be hungup */ for (x = pri->numchans; x >= 0; x--) { /* find a candidate channel */ if (pri->pvts[x] && pri->pvts[x]->owner && pri->pvts[x]->isidlecall) { ast_channel_softhangup_internal_flag_add(pri->pvts[x]->owner, AST_SOFTHANGUP_DEV); haveidles++; /* Stop if we have enough idle channels or can't spare any more active idle ones */ if ((haveidles >= pri->minunused) || (activeidles <= pri->minidle)) break; } } } } /* Start with reasonable max */ if (doidling || pri->resetting) { /* * Make sure we stop at least once per second if we're * monitoring idle channels */ lowest = ast_tv(1, 0); } else { /* Don't poll for more than 60 seconds */ lowest = ast_tv(60, 0); } for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { if (!pri->dchans[i]) { /* We scanned all D channels on this span. */ break; } next = pri_schedule_next(pri->dchans[i]); if (next) { /* We need relative time here */ tv = ast_tvsub(*next, ast_tvnow()); if (tv.tv_sec < 0) { /* * A timer has already expired. * By definition zero time is the lowest so we can quit early. */ lowest = ast_tv(0, 0); break; } if (ast_tvcmp(tv, lowest) < 0) { lowest = tv; } } } ast_mutex_unlock(&pri->lock); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); e = NULL; res = poll(fds, numdchans, lowest.tv_sec * 1000 + lowest.tv_usec / 1000); pthread_testcancel(); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); ast_mutex_lock(&pri->lock); if (!res) { for (which = 0; which < SIG_PRI_NUM_DCHANS; which++) { if (!pri->dchans[which]) break; /* Just a timeout, run the scheduler */ e = pri_schedule_run(pri->dchans[which]); if (e) break; } } else if (res > -1) { for (which = 0; which < SIG_PRI_NUM_DCHANS; which++) { if (!pri->dchans[which]) break; if (fds[which].revents & POLLPRI) { sig_pri_handle_dchan_exception(pri, which); } else if (fds[which].revents & POLLIN) { e = pri_check_event(pri->dchans[which]); } if (e) break; if ((errno != 0) && (errno != EINTR)) { ast_log(LOG_NOTICE, "pri_check_event returned error %d (%s)\n", errno, strerror(errno)); } if (errno == ENODEV) { pri_destroy_later(pri); } } } else if (errno != EINTR) ast_log(LOG_WARNING, "pri_event returned error %d (%s)\n", errno, strerror(errno)); if (e) { int chanpos = -1; char cause_str[35]; if (pri->debug) { ast_verbose("Span %d: Processing event %s(%d)\n", pri->span, pri_event2str(e->e), e->e); } if (e->e != PRI_EVENT_DCHAN_DOWN) { if (!(pri->dchanavail[which] & DCHAN_UP)) { ast_verb(2, "%s D-Channel on span %d up\n", pri_order(which), pri->span); } pri->dchanavail[which] |= DCHAN_UP; } else { if (pri->dchanavail[which] & DCHAN_UP) { ast_verb(2, "%s D-Channel on span %d down\n", pri_order(which), pri->span); } pri->dchanavail[which] &= ~DCHAN_UP; } if ((e->e != PRI_EVENT_DCHAN_UP) && (e->e != PRI_EVENT_DCHAN_DOWN) && (pri->pri != pri->dchans[which])) /* Must be an NFAS group that has the secondary dchan active */ pri->pri = pri->dchans[which]; switch (e->e) { case PRI_EVENT_DCHAN_UP: pri->no_d_channels = 0; if (!pri->pri) { pri_find_dchan(pri); } /* Note presense of D-channel */ time(&pri->lastreset); /* Restart in 5 seconds */ if (pri->resetinterval > -1) { pri->lastreset -= pri->resetinterval; pri->lastreset += 5; } /* Take the channels from inalarm condition */ pri->resetting = 0; for (i = 0; i < pri->numchans; i++) { if (pri->pvts[i]) { sig_pri_set_alarm(pri->pvts[i], 0); } } sig_pri_span_devstate_changed(pri); break; case PRI_EVENT_DCHAN_DOWN: pri_find_dchan(pri); if (!pri_is_up(pri)) { if (pri->sig == SIG_BRI_PTMP) { /* * For PTMP connections with non-persistent layer 2 we want to * *not* declare inalarm unless there actually is an alarm. */ break; } /* Hangup active channels and put them in alarm mode */ pri->resetting = 0; for (i = 0; i < pri->numchans; i++) { struct sig_pri_chan *p = pri->pvts[i]; if (p) { if (pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0) { /* T309 is not enabled : destroy calls when alarm occurs */ if (p->call) { pri_destroycall(p->pri->pri, p->call); p->call = NULL; } if (p->owner) ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); } sig_pri_set_alarm(p, 1); } } sig_pri_span_devstate_changed(pri); } break; case PRI_EVENT_RESTART: if (e->restart.channel > -1 && PRI_CHANNEL(e->restart.channel) != 0xFF) { chanpos = pri_find_principle(pri, e->restart.channel, NULL); if (chanpos < 0) ast_log(LOG_WARNING, "Span %d: Restart requested on odd/unavailable channel number %d/%d\n", pri->span, PRI_SPAN(e->restart.channel), PRI_CHANNEL(e->restart.channel)); else { int skipit = 0; #if defined(HAVE_PRI_SERVICE_MESSAGES) unsigned why; why = pri->pvts[chanpos]->service_status; if (why) { ast_log(LOG_NOTICE, "Span %d: Channel %d/%d out-of-service (reason: %s), ignoring RESTART\n", pri->span, PRI_SPAN(e->restart.channel), PRI_CHANNEL(e->restart.channel), (why & SRVST_FAREND) ? (why & SRVST_NEAREND) ? "both ends" : "far end" : "near end"); skipit = 1; } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ sig_pri_lock_private(pri->pvts[chanpos]); if (!skipit) { ast_verb(3, "Span %d: Channel %d/%d restarted\n", pri->span, PRI_SPAN(e->restart.channel), PRI_CHANNEL(e->restart.channel)); if (pri->pvts[chanpos]->call) { pri_destroycall(pri->pri, pri->pvts[chanpos]->call); pri->pvts[chanpos]->call = NULL; } } /* Force soft hangup if appropriate */ if (pri->pvts[chanpos]->owner) ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); sig_pri_unlock_private(pri->pvts[chanpos]); } } else { ast_verb(3, "Restart requested on entire span %d\n", pri->span); for (x = 0; x < pri->numchans; x++) if (pri->pvts[x]) { sig_pri_lock_private(pri->pvts[x]); if (pri->pvts[x]->call) { pri_destroycall(pri->pri, pri->pvts[x]->call); pri->pvts[x]->call = NULL; } if (pri->pvts[x]->owner) ast_channel_softhangup_internal_flag_add(pri->pvts[x]->owner, AST_SOFTHANGUP_DEV); sig_pri_unlock_private(pri->pvts[x]); } } sig_pri_span_devstate_changed(pri); break; case PRI_EVENT_KEYPAD_DIGIT: if (sig_pri_is_cis_call(e->digit.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->digit.subcmds, e->digit.call); break; } chanpos = pri_find_principle_by_call(pri, e->digit.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Received keypad digits for unknown call.\n", pri->span); break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->digit.subcmds, e->digit.call); /* queue DTMF frame if the PBX for this call was already started (we're forwarding KEYPAD_DIGITs further on */ if ((pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING) && pri->pvts[chanpos]->owner) { /* how to do that */ int digitlen = strlen(e->digit.digits); int i; for (i = 0; i < digitlen; i++) { struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = e->digit.digits[i], }; pri_queue_frame(pri, chanpos, &f); } } sig_pri_unlock_private(pri->pvts[chanpos]); break; case PRI_EVENT_INFO_RECEIVED: if (sig_pri_is_cis_call(e->ring.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->ring.subcmds, e->ring.call); break; } chanpos = pri_find_principle_by_call(pri, e->ring.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Received INFORMATION for unknown call.\n", pri->span); break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->ring.subcmds, e->ring.call); /* queue DTMF frame if the PBX for this call was already started (we're forwarding INFORMATION further on */ if ((pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING) && pri->pvts[chanpos]->owner) { /* how to do that */ int digitlen = strlen(e->ring.callednum); int i; for (i = 0; i < digitlen; i++) { struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = e->ring.callednum[i], }; pri_queue_frame(pri, chanpos, &f); } } sig_pri_unlock_private(pri->pvts[chanpos]); break; #if defined(HAVE_PRI_SERVICE_MESSAGES) case PRI_EVENT_SERVICE: chanpos = pri_find_principle(pri, e->service.channel, NULL); if (chanpos < 0) { ast_log(LOG_WARNING, "Received service change status %d on unconfigured channel %d/%d span %d\n", e->service_ack.changestatus, PRI_SPAN(e->service_ack.channel), PRI_CHANNEL(e->service_ack.channel), pri->span); } else { char db_chan_name[20]; char db_answer[5]; int ch; unsigned *why; ch = pri->pvts[chanpos]->channel; snprintf(db_chan_name, sizeof(db_chan_name), "%s/%d:%d", dahdi_db, pri->span, ch); why = &pri->pvts[chanpos]->service_status; switch (e->service.changestatus) { case 0: /* in-service */ /* Far end wants to be in service now. */ ast_db_del(db_chan_name, SRVST_DBKEY); *why &= ~SRVST_FAREND; if (*why) { snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); } else { sig_pri_span_devstate_changed(pri); } break; case 2: /* out-of-service */ /* Far end wants to be out-of-service now. */ ast_db_del(db_chan_name, SRVST_DBKEY); *why |= SRVST_FAREND; snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); sig_pri_span_devstate_changed(pri); break; default: ast_log(LOG_ERROR, "Huh? changestatus is: %d\n", e->service.changestatus); break; } ast_log(LOG_NOTICE, "Channel %d/%d span %d (logical: %d) received a change of service message, status '%d'\n", PRI_SPAN(e->service.channel), PRI_CHANNEL(e->service.channel), pri->span, ch, e->service.changestatus); } break; case PRI_EVENT_SERVICE_ACK: chanpos = pri_find_principle(pri, e->service_ack.channel, NULL); if (chanpos < 0) { ast_log(LOG_WARNING, "Received service acknowledge change status '%d' on unconfigured channel %d/%d span %d\n", e->service_ack.changestatus, PRI_SPAN(e->service_ack.channel), PRI_CHANNEL(e->service_ack.channel), pri->span); } else { ast_debug(2, "Channel %d/%d span %d received a change os service acknowledgement message, status '%d'\n", PRI_SPAN(e->service_ack.channel), PRI_CHANNEL(e->service_ack.channel), pri->span, e->service_ack.changestatus); } break; #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ case PRI_EVENT_RING: sig_pri_handle_setup(pri, e); break; case PRI_EVENT_RINGING: if (sig_pri_is_cis_call(e->ringing.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->ringing.subcmds, e->ringing.call); break; } chanpos = pri_find_fixup_principle(pri, e->ringing.channel, e->ringing.call); if (chanpos < 0) { break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->ringing.subcmds, e->ringing.call); sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCNR); sig_pri_set_echocanceller(pri->pvts[chanpos], 1); sig_pri_lock_owner(pri, chanpos); if (pri->pvts[chanpos]->owner) { ast_setstate(pri->pvts[chanpos]->owner, AST_STATE_RINGING); ast_channel_unlock(pri->pvts[chanpos]->owner); } pri_queue_control(pri, chanpos, AST_CONTROL_RINGING); if (pri->pvts[chanpos]->call_level < SIG_PRI_CALL_LEVEL_ALERTING) { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_ALERTING; } if (!pri->pvts[chanpos]->progress && !pri->pvts[chanpos]->no_b_channel #ifdef PRI_PROGRESS_MASK && (e->ringing.progressmask & (PRI_PROG_CALL_NOT_E2E_ISDN | PRI_PROG_INBAND_AVAILABLE)) #else && e->ringing.progress == 8 #endif ) { /* Bring voice path up */ pri_queue_control(pri, chanpos, AST_CONTROL_PROGRESS); pri->pvts[chanpos]->progress = 1; sig_pri_set_dialing(pri->pvts[chanpos], 0); sig_pri_open_media(pri->pvts[chanpos]); } #ifdef SUPPORT_USERUSER if (!ast_strlen_zero(e->ringing.useruserinfo)) { struct ast_channel *owner; sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->ringing.useruserinfo); ast_channel_unlock(owner); } } #endif sig_pri_unlock_private(pri->pvts[chanpos]); break; case PRI_EVENT_PROGRESS: if (sig_pri_is_cis_call(e->proceeding.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->proceeding.subcmds, e->proceeding.call); break; } chanpos = pri_find_fixup_principle(pri, e->proceeding.channel, e->proceeding.call); if (chanpos < 0) { break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->proceeding.subcmds, e->proceeding.call); if (e->proceeding.cause > -1) { if (pri->pvts[chanpos]->owner) { snprintf(cause_str, sizeof(cause_str), "PRI PRI_EVENT_PROGRESS (%d)", e->proceeding.cause); pri_queue_pvt_cause_data(pri, chanpos, cause_str, e->proceeding.cause); } ast_verb(3, "PROGRESS with cause code %d received\n", e->proceeding.cause); /* Work around broken, out of spec USER_BUSY cause in a progress message */ if (e->proceeding.cause == AST_CAUSE_USER_BUSY) { if (pri->pvts[chanpos]->owner) { ast_verb(3, "PROGRESS with 'user busy' received, signaling AST_CONTROL_BUSY instead of AST_CONTROL_PROGRESS\n"); ast_channel_hangupcause_set(pri->pvts[chanpos]->owner, e->proceeding.cause); pri_queue_control(pri, chanpos, AST_CONTROL_BUSY); } } } if (!pri->pvts[chanpos]->progress && !pri->pvts[chanpos]->no_b_channel #ifdef PRI_PROGRESS_MASK && (e->proceeding.progressmask & (PRI_PROG_CALL_NOT_E2E_ISDN | PRI_PROG_INBAND_AVAILABLE)) #else && e->proceeding.progress == 8 #endif ) { /* Bring voice path up */ ast_debug(1, "Queuing frame from PRI_EVENT_PROGRESS on channel %d/%d span %d\n", pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span); pri_queue_control(pri, chanpos, AST_CONTROL_PROGRESS); pri->pvts[chanpos]->progress = 1; sig_pri_set_dialing(pri->pvts[chanpos], 0); sig_pri_open_media(pri->pvts[chanpos]); } sig_pri_unlock_private(pri->pvts[chanpos]); break; case PRI_EVENT_PROCEEDING: if (sig_pri_is_cis_call(e->proceeding.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->proceeding.subcmds, e->proceeding.call); break; } chanpos = pri_find_fixup_principle(pri, e->proceeding.channel, e->proceeding.call); if (chanpos < 0) { break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->proceeding.subcmds, e->proceeding.call); if (pri->pvts[chanpos]->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING) { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_PROCEEDING; ast_debug(1, "Queuing frame from PRI_EVENT_PROCEEDING on channel %d/%d span %d\n", pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span); pri_queue_control(pri, chanpos, AST_CONTROL_PROCEEDING); } if (!pri->pvts[chanpos]->progress && !pri->pvts[chanpos]->no_b_channel #ifdef PRI_PROGRESS_MASK /* * We only care about PRI_PROG_INBAND_AVAILABLE to open the * voice path. * * We explicitly DO NOT want to check PRI_PROG_CALL_NOT_E2E_ISDN * because it will mess up ISDN to SIP interoperability for * the ALERTING message. */ && (e->proceeding.progressmask & PRI_PROG_INBAND_AVAILABLE) #else && e->proceeding.progress == 8 #endif ) { /* Bring voice path up */ pri_queue_control(pri, chanpos, AST_CONTROL_PROGRESS); pri->pvts[chanpos]->progress = 1; sig_pri_set_dialing(pri->pvts[chanpos], 0); sig_pri_open_media(pri->pvts[chanpos]); } else if (pri->inband_on_proceeding) { /* * XXX This is to accomodate a broken switch that sends a * PROCEEDING without any progress indication ie for * inband audio. This should be part of the conditional * test above to bring the voice path up. */ sig_pri_set_dialing(pri->pvts[chanpos], 0); } sig_pri_unlock_private(pri->pvts[chanpos]); break; case PRI_EVENT_FACILITY: if (!e->facility.call || sig_pri_is_cis_call(e->facility.channel)) { /* Event came in on the dummy channel or a CIS call. */ #if defined(HAVE_PRI_CALL_REROUTING) sig_pri_handle_cis_subcmds(pri, e->e, e->facility.subcmds, e->facility.subcall); #else sig_pri_handle_cis_subcmds(pri, e->e, e->facility.subcmds, e->facility.call); #endif /* !defined(HAVE_PRI_CALL_REROUTING) */ break; } chanpos = pri_find_principle_by_call(pri, e->facility.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Received facility for unknown call.\n", pri->span); break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); #if defined(HAVE_PRI_CALL_REROUTING) sig_pri_handle_subcmds(pri, chanpos, e->e, e->facility.subcmds, e->facility.subcall); #else sig_pri_handle_subcmds(pri, chanpos, e->e, e->facility.subcmds, e->facility.call); #endif /* !defined(HAVE_PRI_CALL_REROUTING) */ sig_pri_unlock_private(pri->pvts[chanpos]); break; case PRI_EVENT_ANSWER: if (sig_pri_is_cis_call(e->answer.channel)) { #if defined(HAVE_PRI_CALL_WAITING) /* Call is CIS so do normal CONNECT_ACKNOWLEDGE. */ pri_connect_ack(pri->pri, e->answer.call, 0); #endif /* defined(HAVE_PRI_CALL_WAITING) */ sig_pri_handle_cis_subcmds(pri, e->e, e->answer.subcmds, e->answer.call); break; } chanpos = pri_find_fixup_principle(pri, e->answer.channel, e->answer.call); if (chanpos < 0) { break; } #if defined(HAVE_PRI_CALL_WAITING) if (pri->pvts[chanpos]->is_call_waiting) { if (pri->pvts[chanpos]->no_b_channel) { int new_chanpos; /* * Need to find a free channel now or * kill the call with PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION. */ new_chanpos = pri_find_empty_chan(pri, 1); if (0 <= new_chanpos) { new_chanpos = pri_fixup_principle(pri, new_chanpos, e->answer.call); } if (new_chanpos < 0) { /* * Either no channel was available or someone stole * the channel! */ ast_verb(3, "Span %d: Channel not available for call waiting call.\n", pri->span); sig_pri_lock_private(pri->pvts[chanpos]); sig_pri_handle_subcmds(pri, chanpos, e->e, e->answer.subcmds, e->answer.call); sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS); sig_pri_lock_owner(pri, chanpos); if (pri->pvts[chanpos]->owner) { ast_channel_hangupcause_set(pri->pvts[chanpos]->owner, PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION); switch (ast_channel_state(pri->pvts[chanpos]->owner)) { case AST_STATE_BUSY: case AST_STATE_UP: ast_softhangup_nolock(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); break; default: pri_queue_control(pri, chanpos, AST_CONTROL_CONGESTION); break; } ast_channel_unlock(pri->pvts[chanpos]->owner); } else { pri->pvts[chanpos]->is_call_waiting = 0; ast_atomic_fetchadd_int(&pri->num_call_waiting_calls, -1); pri_hangup(pri->pri, e->answer.call, PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION); pri->pvts[chanpos]->call = NULL; } sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); break; } chanpos = new_chanpos; } pri_connect_ack(pri->pri, e->answer.call, PVT_TO_CHANNEL(pri->pvts[chanpos])); sig_pri_span_devstate_changed(pri); } else { /* Call is normal so do normal CONNECT_ACKNOWLEDGE. */ pri_connect_ack(pri->pri, e->answer.call, 0); } #endif /* defined(HAVE_PRI_CALL_WAITING) */ sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); #if defined(HAVE_PRI_CALL_WAITING) if (pri->pvts[chanpos]->is_call_waiting) { pri->pvts[chanpos]->is_call_waiting = 0; ast_atomic_fetchadd_int(&pri->num_call_waiting_calls, -1); } #endif /* defined(HAVE_PRI_CALL_WAITING) */ sig_pri_handle_subcmds(pri, chanpos, e->e, e->answer.subcmds, e->answer.call); if (!ast_strlen_zero(pri->pvts[chanpos]->deferred_digits)) { /* We have some 'w' deferred digits to dial now. */ ast_verb(3, "Span %d: Channel %d/%d dialing deferred digit string: %s\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->pvts[chanpos]->deferred_digits); if (pri->pvts[chanpos]->call_level < SIG_PRI_CALL_LEVEL_DEFER_DIAL) { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_DEFER_DIAL; } sig_pri_dial_digits(pri->pvts[chanpos], pri->pvts[chanpos]->deferred_digits); } else { if (pri->pvts[chanpos]->call_level < SIG_PRI_CALL_LEVEL_CONNECT) { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_CONNECT; } sig_pri_open_media(pri->pvts[chanpos]); pri_queue_control(pri, chanpos, AST_CONTROL_ANSWER); sig_pri_set_dialing(pri->pvts[chanpos], 0); /* Enable echo cancellation if it's not on already */ sig_pri_set_echocanceller(pri->pvts[chanpos], 1); } #ifdef SUPPORT_USERUSER if (!ast_strlen_zero(e->answer.useruserinfo)) { struct ast_channel *owner; sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->answer.useruserinfo); ast_channel_unlock(owner); } } #endif sig_pri_unlock_private(pri->pvts[chanpos]); break; #if defined(HAVE_PRI_CALL_WAITING) case PRI_EVENT_CONNECT_ACK: if (sig_pri_is_cis_call(e->connect_ack.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->connect_ack.subcmds, e->connect_ack.call); break; } chanpos = pri_find_fixup_principle(pri, e->connect_ack.channel, e->connect_ack.call); if (chanpos < 0) { break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->connect_ack.subcmds, e->connect_ack.call); sig_pri_open_media(pri->pvts[chanpos]); sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); break; #endif /* defined(HAVE_PRI_CALL_WAITING) */ case PRI_EVENT_HANGUP: if (sig_pri_is_cis_call(e->hangup.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds, e->hangup.call); pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); break; } chanpos = pri_find_principle_by_call(pri, e->hangup.call); if (chanpos < 0) { /* * Continue hanging up the call even though * we do not remember it (if we ever did). */ pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->hangup.subcmds, e->hangup.call); switch (e->hangup.cause) { case PRI_CAUSE_INVALID_CALL_REFERENCE: /* * The peer denies the existence of this call so we must * continue hanging it up and forget about it. */ pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); pri->pvts[chanpos]->call = NULL; break; default: break; } if (!pri->pvts[chanpos]->alreadyhungup) { /* we're calling here dahdi_hangup so once we get there we need to clear p->call after calling pri_hangup */ pri->pvts[chanpos]->alreadyhungup = 1; switch (e->hangup.cause) { case PRI_CAUSE_USER_BUSY: case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION: sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS); break; default: break; } if (pri->pvts[chanpos]->owner) { int do_hangup = 0; snprintf(cause_str, sizeof(cause_str), "PRI PRI_EVENT_HANGUP (%d)", e->hangup.cause); pri_queue_pvt_cause_data(pri, chanpos, cause_str, e->hangup.cause); /* Queue a BUSY instead of a hangup if our cause is appropriate */ ast_channel_hangupcause_set(pri->pvts[chanpos]->owner, e->hangup.cause); switch (ast_channel_state(pri->pvts[chanpos]->owner)) { case AST_STATE_BUSY: case AST_STATE_UP: do_hangup = 1; break; default: if (!pri->pvts[chanpos]->outgoing) { /* * The incoming call leg hung up before getting * connected so just hangup the call. */ do_hangup = 1; break; } switch (e->hangup.cause) { case PRI_CAUSE_USER_BUSY: pri_queue_control(pri, chanpos, AST_CONTROL_BUSY); break; case PRI_CAUSE_CALL_REJECTED: case PRI_CAUSE_NETWORK_OUT_OF_ORDER: case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION: case PRI_CAUSE_SWITCH_CONGESTION: case PRI_CAUSE_DESTINATION_OUT_OF_ORDER: case PRI_CAUSE_NORMAL_TEMPORARY_FAILURE: pri_queue_control(pri, chanpos, AST_CONTROL_CONGESTION); break; default: do_hangup = 1; break; } break; } if (do_hangup) { #if defined(HAVE_PRI_AOC_EVENTS) if (detect_aoc_e_subcmd(e->hangup.subcmds)) { /* If a AOC-E msg was sent during the release, we must use a * AST_CONTROL_HANGUP frame to guarantee that frame gets read before hangup */ pri_queue_control(pri, chanpos, AST_CONTROL_HANGUP); } else { ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); } #else ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); #endif /* defined(HAVE_PRI_AOC_EVENTS) */ } } else { /* * Continue hanging up the call even though * we do not have an owner. */ pri_hangup(pri->pri, pri->pvts[chanpos]->call, e->hangup.cause); pri->pvts[chanpos]->call = NULL; } ast_verb(3, "Span %d: Channel %d/%d got hangup, cause %d\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, e->hangup.cause); } else { /* Continue hanging up the call. */ pri_hangup(pri->pri, pri->pvts[chanpos]->call, e->hangup.cause); pri->pvts[chanpos]->call = NULL; } #if defined(FORCE_RESTART_UNAVAIL_CHANS) if (e->hangup.cause == PRI_CAUSE_REQUESTED_CHAN_UNAVAIL && pri->sig != SIG_BRI_PTMP && !pri->resetting && pri->pvts[chanpos]->resetting == SIG_PRI_RESET_IDLE) { ast_verb(3, "Span %d: Forcing restart of channel %d/%d since channel reported in use\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); pri->pvts[chanpos]->resetting = SIG_PRI_RESET_ACTIVE; pri_reset(pri->pri, PVT_TO_CHANNEL(pri->pvts[chanpos])); } #endif /* defined(FORCE_RESTART_UNAVAIL_CHANS) */ if (e->hangup.aoc_units > -1) ast_verb(3, "Channel %d/%d, span %d received AOC-E charging %d unit%s\n", pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span, (int)e->hangup.aoc_units, (e->hangup.aoc_units == 1) ? "" : "s"); #ifdef SUPPORT_USERUSER if (!ast_strlen_zero(e->hangup.useruserinfo)) { struct ast_channel *owner; sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->hangup.useruserinfo); ast_channel_unlock(owner); } } #endif sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); break; case PRI_EVENT_HANGUP_REQ: if (sig_pri_is_cis_call(e->hangup.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds, e->hangup.call); pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); break; } chanpos = pri_find_principle_by_call(pri, e->hangup.call); if (chanpos < 0) { /* * Continue hanging up the call even though * we do not remember it (if we ever did). */ pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->hangup.subcmds, e->hangup.call); #if defined(HAVE_PRI_CALL_HOLD) if (e->hangup.call_active && e->hangup.call_held && pri->hold_disconnect_transfer) { /* We are to transfer the call instead of simply hanging up. */ sig_pri_unlock_private(pri->pvts[chanpos]); if (!sig_pri_attempt_transfer(pri, e->hangup.call_held, 1, e->hangup.call_active, 0, NULL)) { break; } sig_pri_lock_private(pri->pvts[chanpos]); } #endif /* defined(HAVE_PRI_CALL_HOLD) */ switch (e->hangup.cause) { case PRI_CAUSE_USER_BUSY: case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION: sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS); break; case PRI_CAUSE_INVALID_CALL_REFERENCE: /* * The peer denies the existence of this call so we must * continue hanging it up and forget about it. We should not * get this cause here, but for completeness we will handle it * anyway. */ pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); pri->pvts[chanpos]->call = NULL; break; default: break; } if (pri->pvts[chanpos]->owner) { int do_hangup = 0; snprintf(cause_str, sizeof(cause_str), "PRI PRI_EVENT_HANGUP_REQ (%d)", e->hangup.cause); pri_queue_pvt_cause_data(pri, chanpos, cause_str, e->hangup.cause); ast_channel_hangupcause_set(pri->pvts[chanpos]->owner, e->hangup.cause); switch (ast_channel_state(pri->pvts[chanpos]->owner)) { case AST_STATE_BUSY: case AST_STATE_UP: do_hangup = 1; break; default: if (!pri->pvts[chanpos]->outgoing) { /* * The incoming call leg hung up before getting * connected so just hangup the call. */ do_hangup = 1; break; } switch (e->hangup.cause) { case PRI_CAUSE_USER_BUSY: pri_queue_control(pri, chanpos, AST_CONTROL_BUSY); break; case PRI_CAUSE_CALL_REJECTED: case PRI_CAUSE_NETWORK_OUT_OF_ORDER: case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION: case PRI_CAUSE_SWITCH_CONGESTION: case PRI_CAUSE_DESTINATION_OUT_OF_ORDER: case PRI_CAUSE_NORMAL_TEMPORARY_FAILURE: pri_queue_control(pri, chanpos, AST_CONTROL_CONGESTION); break; default: do_hangup = 1; break; } break; } if (do_hangup) { #if defined(HAVE_PRI_AOC_EVENTS) if (!pri->pvts[chanpos]->holding_aoce && pri->aoce_delayhangup && ast_channel_is_bridged(pri->pvts[chanpos]->owner)) { sig_pri_send_aoce_termination_request(pri, chanpos, pri_get_timer(pri->pri, PRI_TIMER_T305) / 2); } else if (detect_aoc_e_subcmd(e->hangup.subcmds)) { /* If a AOC-E msg was sent during the Disconnect, we must use a AST_CONTROL_HANGUP frame * to guarantee that frame gets read before hangup */ pri_queue_control(pri, chanpos, AST_CONTROL_HANGUP); } else { ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); } #else ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); #endif /* defined(HAVE_PRI_AOC_EVENTS) */ } ast_verb(3, "Span %d: Channel %d/%d got hangup request, cause %d\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, e->hangup.cause); } else { /* * Continue hanging up the call even though * we do not have an owner. */ pri_hangup(pri->pri, pri->pvts[chanpos]->call, e->hangup.cause); pri->pvts[chanpos]->call = NULL; } #if defined(FORCE_RESTART_UNAVAIL_CHANS) if (e->hangup.cause == PRI_CAUSE_REQUESTED_CHAN_UNAVAIL && pri->sig != SIG_BRI_PTMP && !pri->resetting && pri->pvts[chanpos]->resetting == SIG_PRI_RESET_IDLE) { ast_verb(3, "Span %d: Forcing restart of channel %d/%d since channel reported in use\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); pri->pvts[chanpos]->resetting = SIG_PRI_RESET_ACTIVE; pri_reset(pri->pri, PVT_TO_CHANNEL(pri->pvts[chanpos])); } #endif /* defined(FORCE_RESTART_UNAVAIL_CHANS) */ #ifdef SUPPORT_USERUSER if (!ast_strlen_zero(e->hangup.useruserinfo)) { struct ast_channel *owner; sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->hangup.useruserinfo); ast_channel_unlock(owner); } } #endif sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); break; case PRI_EVENT_HANGUP_ACK: if (sig_pri_is_cis_call(e->hangup.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds, e->hangup.call); break; } chanpos = pri_find_principle_by_call(pri, e->hangup.call); if (chanpos < 0) { break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); pri->pvts[chanpos]->call = NULL; if (pri->pvts[chanpos]->owner) { ast_verb(3, "Span %d: Channel %d/%d got hangup ACK\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); } #ifdef SUPPORT_USERUSER if (!ast_strlen_zero(e->hangup.useruserinfo)) { struct ast_channel *owner; sig_pri_lock_owner(pri, chanpos); owner = pri->pvts[chanpos]->owner; if (owner) { pbx_builtin_setvar_helper(owner, "USERUSERINFO", e->hangup.useruserinfo); ast_channel_unlock(owner); } } #endif sig_pri_unlock_private(pri->pvts[chanpos]); sig_pri_span_devstate_changed(pri); break; case PRI_EVENT_CONFIG_ERR: ast_log(LOG_WARNING, "PRI Error on span %d: %s\n", pri->span, e->err.err); break; case PRI_EVENT_RESTART_ACK: chanpos = pri_find_principle(pri, e->restartack.channel, NULL); if (chanpos < 0) { /* Sometime switches (e.g. I421 / British Telecom) don't give us the channel number, so we have to figure it out... This must be why everybody resets exactly a channel at a time. */ for (x = 0; x < pri->numchans; x++) { if (pri->pvts[x] && pri->pvts[x]->resetting != SIG_PRI_RESET_IDLE) { chanpos = x; sig_pri_lock_private(pri->pvts[chanpos]); ast_debug(1, "Span %d: Assuming restart ack is for channel %d/%d\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); if (pri->pvts[chanpos]->owner) { ast_log(LOG_WARNING, "Span %d: Got restart ack on channel %d/%d with owner\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); } pri->pvts[chanpos]->resetting = SIG_PRI_RESET_IDLE; ast_verb(3, "Span %d: Channel %d/%d successfully restarted\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); sig_pri_unlock_private(pri->pvts[chanpos]); if (pri->resetting) pri_check_restart(pri); break; } } if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Restart ACK on strange channel %d/%d\n", pri->span, PRI_SPAN(e->restartack.channel), PRI_CHANNEL(e->restartack.channel)); } } else { sig_pri_lock_private(pri->pvts[chanpos]); if (pri->pvts[chanpos]->resetting == SIG_PRI_RESET_IDLE) { /* The channel is not in the resetting state. */ ast_debug(1, "Span %d: Unexpected or late restart ack on channel %d/%d (Ignoring)\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); sig_pri_unlock_private(pri->pvts[chanpos]); break; } if (pri->pvts[chanpos]->owner) { ast_log(LOG_WARNING, "Span %d: Got restart ack on channel %d/%d with owner\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV); } pri->pvts[chanpos]->resetting = SIG_PRI_RESET_IDLE; ast_verb(3, "Span %d: Channel %d/%d successfully restarted\n", pri->span, pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset); sig_pri_unlock_private(pri->pvts[chanpos]); if (pri->resetting) pri_check_restart(pri); } break; case PRI_EVENT_SETUP_ACK: if (sig_pri_is_cis_call(e->setup_ack.channel)) { sig_pri_handle_cis_subcmds(pri, e->e, e->setup_ack.subcmds, e->setup_ack.call); break; } chanpos = pri_find_fixup_principle(pri, e->setup_ack.channel, e->setup_ack.call); if (chanpos < 0) { break; } sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); sig_pri_handle_subcmds(pri, chanpos, e->e, e->setup_ack.subcmds, e->setup_ack.call); if (pri->pvts[chanpos]->call_level < SIG_PRI_CALL_LEVEL_OVERLAP) { pri->pvts[chanpos]->call_level = SIG_PRI_CALL_LEVEL_OVERLAP; } /* Send any queued digits */ len = strlen(pri->pvts[chanpos]->dialdest); for (x = 0; x < len; ++x) { ast_debug(1, "Sending pending digit '%c'\n", pri->pvts[chanpos]->dialdest[x]); pri_information(pri->pri, pri->pvts[chanpos]->call, pri->pvts[chanpos]->dialdest[x]); } if (!pri->pvts[chanpos]->progress && (pri->overlapdial & DAHDI_OVERLAPDIAL_OUTGOING) && !pri->pvts[chanpos]->digital && !pri->pvts[chanpos]->no_b_channel #if defined(HAVE_PRI_SETUP_ACK_INBAND) /* * We only care about PRI_PROG_INBAND_AVAILABLE to open the * voice path. * * We explicitly DO NOT want to check PRI_PROG_CALL_NOT_E2E_ISDN * because it will mess up ISDN to SIP interoperability for * the ALERTING message. * * Q.931 Section 5.1.3 says that in scenarios with overlap * dialing where no called digits are received and the tone * option requires dialtone, the switch MAY send an inband * progress indication ie to indicate dialtone presence in * the SETUP ACKNOWLEDGE. Therefore, if we did not send any * digits with the SETUP then we must assume that dialtone * is present and open the voice path. Fortunately when * interoperating with SIP, we should be sending digits. */ && ((e->setup_ack.progressmask & PRI_PROG_INBAND_AVAILABLE) || pri->inband_on_setup_ack || pri->pvts[chanpos]->no_dialed_digits) #endif /* defined(HAVE_PRI_SETUP_ACK_INBAND) */ ) { /* * Call has a channel. * Indicate for overlap dialing that dialtone may be present. */ pri_queue_control(pri, chanpos, AST_CONTROL_PROGRESS); pri->pvts[chanpos]->progress = 1;/* Claim to have seen inband-information */ sig_pri_set_dialing(pri->pvts[chanpos], 0); sig_pri_open_media(pri->pvts[chanpos]); } sig_pri_unlock_private(pri->pvts[chanpos]); break; case PRI_EVENT_NOTIFY: if (sig_pri_is_cis_call(e->notify.channel)) { #if defined(HAVE_PRI_CALL_HOLD) sig_pri_handle_cis_subcmds(pri, e->e, e->notify.subcmds, e->notify.call); #else sig_pri_handle_cis_subcmds(pri, e->e, e->notify.subcmds, NULL); #endif /* !defined(HAVE_PRI_CALL_HOLD) */ break; } #if defined(HAVE_PRI_CALL_HOLD) chanpos = pri_find_principle_by_call(pri, e->notify.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Span %d: Received NOTIFY for unknown call.\n", pri->span); break; } #else /* * This version of libpri does not supply a call pointer for * this message. We are just going to have to trust that the * correct principle is found. */ chanpos = pri_find_principle(pri, e->notify.channel, NULL); if (chanpos < 0) { ast_log(LOG_WARNING, "Received NOTIFY on unconfigured channel %d/%d span %d\n", PRI_SPAN(e->notify.channel), PRI_CHANNEL(e->notify.channel), pri->span); break; } #endif /* !defined(HAVE_PRI_CALL_HOLD) */ sig_pri_lock_private(pri->pvts[chanpos]); callid = func_pri_dchannel_chanpos_callid(pri, chanpos); #if defined(HAVE_PRI_CALL_HOLD) sig_pri_handle_subcmds(pri, chanpos, e->e, e->notify.subcmds, e->notify.call); #else sig_pri_handle_subcmds(pri, chanpos, e->e, e->notify.subcmds, NULL); #endif /* !defined(HAVE_PRI_CALL_HOLD) */ switch (e->notify.info) { case PRI_NOTIFY_REMOTE_HOLD: if (!pri->discardremoteholdretrieval) { sig_pri_queue_hold(pri, chanpos); } break; case PRI_NOTIFY_REMOTE_RETRIEVAL: if (!pri->discardremoteholdretrieval) { sig_pri_queue_unhold(pri, chanpos); } break; } sig_pri_unlock_private(pri->pvts[chanpos]); break; #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_HOLD: /* We should not be getting any CIS calls with this message type. */ if (sig_pri_handle_hold(pri, e)) { pri_hold_rej(pri->pri, e->hold.call, PRI_CAUSE_RESOURCE_UNAVAIL_UNSPECIFIED); } else { pri_hold_ack(pri->pri, e->hold.call); } break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_HOLD_ACK: /* We should not be getting any CIS calls with this message type. */ sig_pri_handle_hold_ack(pri, e); break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_HOLD_REJ: /* We should not be getting any CIS calls with this message type. */ sig_pri_handle_hold_rej(pri, e); break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_RETRIEVE: /* We should not be getting any CIS calls with this message type. */ sig_pri_handle_retrieve(pri, e); break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_RETRIEVE_ACK: /* We should not be getting any CIS calls with this message type. */ sig_pri_handle_retrieve_ack(pri, e); break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_RETRIEVE_REJ: /* We should not be getting any CIS calls with this message type. */ sig_pri_handle_retrieve_rej(pri, e); break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ default: ast_debug(1, "Span: %d Unhandled event: %s(%d)\n", pri->span, pri_event2str(e->e), e->e); break; } /* If a callid was set, we need to deref it and remove it from thread storage. */ if (callid) { callid = ast_callid_unref(callid); ast_callid_threadassoc_remove(); } } ast_mutex_unlock(&pri->lock); } /* Never reached */ return NULL; } /*! * \brief Output AMI show spans response events for the given PRI span. * \since 10.0 * * \param show_cmd AMI command name * \param s AMI session to output span information. * \param pri PRI span control structure. * \param dchannels Array of D channel channel numbers. * \param action_id Action ID line to use. * * \return Number of D channels on this span. */ int sig_pri_ami_show_spans(struct mansession *s, const char *show_cmd, struct sig_pri_span *pri, const int *dchannels, const char *action_id) { int count; int x; count = 0; for (x = 0; x < ARRAY_LEN(pri->dchans); ++x) { if (pri->dchans[x]) { ++count; astman_append(s, "Event: %s\r\n" "Span: %d\r\n" "DChannel: %d\r\n" "Order: %s\r\n" "Active: %s\r\n" "Alarm: %s\r\n" "Up: %s\r\n" "%s" "\r\n", show_cmd, pri->span, dchannels[x], pri_order(x), (pri->dchans[x] == pri->pri) ? "Yes" : "No", (pri->dchanavail[x] & DCHAN_NOTINALARM) ? "No" : "Yes", (pri->dchanavail[x] & DCHAN_UP) ? "Yes" : "No", action_id ); } } return count; } void sig_pri_init_pri(struct sig_pri_span *pri) { int i; memset(pri, 0, sizeof(*pri)); ast_mutex_init(&pri->lock); pri->master = AST_PTHREADT_NULL; for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) pri->fds[i] = -1; } int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast) { ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } sig_pri_set_outgoing(p, 0); sig_pri_set_digital(p, 0); /* push up to parent for EC*/ #if defined(HAVE_PRI_CALL_WAITING) if (p->is_call_waiting) { p->is_call_waiting = 0; ast_atomic_fetchadd_int(&p->pri->num_call_waiting_calls, -1); } #endif /* defined(HAVE_PRI_CALL_WAITING) */ p->call_level = SIG_PRI_CALL_LEVEL_IDLE; p->progress = 0; p->cid_num[0] = '\0'; p->cid_subaddr[0] = '\0'; p->cid_name[0] = '\0'; p->user_tag[0] = '\0'; p->exten[0] = '\0'; sig_pri_set_dialing(p, 0); /* Make sure we really have a call */ pri_grab(p, p->pri); sig_pri_moh_fsm_event(ast, p, SIG_PRI_MOH_EVENT_RESET); if (p->call) { #if defined(SUPPORT_USERUSER) const char *useruser = pbx_builtin_getvar_helper(ast, "USERUSERINFO"); if (!ast_strlen_zero(useruser)) { pri_call_set_useruser(p->call, useruser); } #endif /* defined(SUPPORT_USERUSER) */ #if defined(HAVE_PRI_TRANSFER) if (p->xfer_data) { /* * The transferrer call leg is disconnecting. It must mean that * the transfer was successful and the core is disconnecting the * call legs involved. * * The transfer protocol response message must go out before the * call leg is disconnected. */ sig_pri_transfer_rsp(p->xfer_data, 1); } #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_AOC_EVENTS) if (p->holding_aoce) { pri_aoc_e_send(p->pri->pri, p->call, &p->aoc_e); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ if (p->alreadyhungup) { ast_debug(1, "Already hungup... Calling hangup once, and clearing call\n"); pri_hangup(p->pri->pri, p->call, -1); p->call = NULL; } else { const char *cause = pbx_builtin_getvar_helper(ast,"PRI_CAUSE"); int icause = ast_channel_hangupcause(ast) ? ast_channel_hangupcause(ast) : -1; p->alreadyhungup = 1; if (!ast_strlen_zero(cause)) { if (atoi(cause)) { icause = atoi(cause); } } ast_debug(1, "Not yet hungup... Calling hangup with cause %d, and clearing call\n", icause); pri_hangup(p->pri->pri, p->call, icause); } } #if defined(HAVE_PRI_TRANSFER) p->xfer_data = NULL; #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_AOC_EVENTS) p->aoc_s_request_invoke_id_valid = 0; p->holding_aoce = 0; p->waiting_for_aoce = 0; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ p->allocated = 0; p->owner = NULL; sig_pri_span_devstate_changed(p->pri); pri_rel(p->pri); return 0; } /*! * \brief Extract the called number and subaddress from the dial string. * \since 1.8 * * \param p sig_pri channel structure. * \param rdest Dial string buffer to extract called number and subaddress. * \param called Buffer to fill with extracted [:] * \param called_buff_size Size of buffer to fill. * * \note Parsing must remain in sync with sig_pri_call(). * * \return Nothing */ void sig_pri_extract_called_num_subaddr(struct sig_pri_chan *p, const char *rdest, char *called, size_t called_buff_size) { char *dial; char *number; char *subaddr; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(group); /* channel/group token */ AST_APP_ARG(ext); /* extension token */ //AST_APP_ARG(opts); /* options token */ AST_APP_ARG(other); /* Any remining unused arguments */ ); /* Get private copy of dial string and break it up. */ dial = ast_strdupa(rdest); AST_NONSTANDARD_APP_ARGS(args, dial, '/'); number = args.ext; if (!number) { number = ""; } /* Find and extract dialed_subaddress */ subaddr = strchr(number, ':'); if (subaddr) { *subaddr++ = '\0'; /* Skip subaddress type prefix. */ switch (*subaddr) { case 'U': case 'u': case 'N': case 'n': ++subaddr; break; default: break; } } /* Skip type-of-number/dial-plan prefix characters. */ if (strlen(number) < p->stripmsd) { number = ""; } else { char *deferred; number += p->stripmsd; deferred = strchr(number, 'w'); if (deferred) { /* Remove any 'w' deferred digits. */ *deferred = '\0'; } while (isalpha(*number)) { ++number; } } /* Fill buffer with extracted number and subaddress. */ if (ast_strlen_zero(subaddr)) { /* Put in called number only since there is no subaddress. */ snprintf(called, called_buff_size, "%s", number); } else { /* Put in called number and subaddress. */ snprintf(called, called_buff_size, "%s:%s", number, subaddr); } } enum SIG_PRI_CALL_OPT_FLAGS { OPT_KEYPAD = (1 << 0), OPT_REVERSE_CHARGE = (1 << 1), /* Collect call */ OPT_AOC_REQUEST = (1 << 2), /* AOC Request */ }; enum SIG_PRI_CALL_OPT_ARGS { OPT_ARG_KEYPAD = 0, OPT_ARG_AOC_REQUEST, /* note: this entry _MUST_ be the last one in the enum */ OPT_ARG_ARRAY_SIZE, }; AST_APP_OPTIONS(sig_pri_call_opts, BEGIN_OPTIONS AST_APP_OPTION_ARG('K', OPT_KEYPAD, OPT_ARG_KEYPAD), AST_APP_OPTION('R', OPT_REVERSE_CHARGE), AST_APP_OPTION_ARG('A', OPT_AOC_REQUEST, OPT_ARG_AOC_REQUEST), END_OPTIONS); /*! \note Parsing must remain in sync with sig_pri_extract_called_num_subaddr(). */ int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, const char *rdest, int timeout, int layer1) { char dest[256]; /* must be same length as p->dialdest */ struct ast_party_subaddress dialed_subaddress; /* Called subaddress */ struct pri_sr *sr; char *c, *l, *n, *s; #ifdef SUPPORT_USERUSER const char *useruser; #endif int core_id; int pridialplan; int dp_strip; int prilocaldialplan; int ldp_strip; int exclusive; #if defined(HAVE_PRI_SETUP_KEYPAD) const char *keypad; #endif /* defined(HAVE_PRI_SETUP_KEYPAD) */ AST_DECLARE_APP_ARGS(args, AST_APP_ARG(group); /* channel/group token */ AST_APP_ARG(ext); /* extension token */ AST_APP_ARG(opts); /* options token */ AST_APP_ARG(other); /* Any remining unused arguments */ ); struct ast_flags opts; char *opt_args[OPT_ARG_ARRAY_SIZE]; struct ast_party_id connected_id = ast_channel_connected_effective_id(ast); ast_debug(1, "CALLER NAME: %s NUM: %s\n", S_COR(connected_id.name.valid, connected_id.name.str, ""), S_COR(connected_id.number.valid, connected_id.number.str, "")); if (!p->pri) { ast_log(LOG_ERROR, "Could not find pri on channel %d\n", p->channel); return -1; } if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "sig_pri_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } p->dialdest[0] = '\0'; sig_pri_set_outgoing(p, 1); ast_copy_string(dest, rdest, sizeof(dest)); AST_NONSTANDARD_APP_ARGS(args, dest, '/'); if (ast_app_parse_options(sig_pri_call_opts, &opts, opt_args, args.opts)) { /* General invalid option syntax. */ return -1; } c = args.ext; if (!c) { c = ""; } /* setup dialed_subaddress if found */ ast_party_subaddress_init(&dialed_subaddress); s = strchr(c, ':'); if (s) { *s = '\0'; s++; /* prefix */ /* 'n' = NSAP */ /* 'u' = User Specified */ /* Default = NSAP */ switch (*s) { case 'U': case 'u': s++; dialed_subaddress.type = 2; break; case 'N': case 'n': s++; /* default already covered with ast_party_subaddress_init */ break; } dialed_subaddress.str = s; dialed_subaddress.valid = 1; } l = NULL; n = NULL; if (!p->hidecallerid) { if (connected_id.number.valid) { /* If we get to the end of this loop without breaking, there's no * calleridnum. This is done instead of testing for "unknown" or * the thousands of other ways that the calleridnum could be * invalid. */ for (l = connected_id.number.str; l && *l; l++) { if (strchr("0123456789", *l)) { l = connected_id.number.str; break; } } } else { l = NULL; } if (!p->hidecalleridname) { n = connected_id.name.valid ? connected_id.name.str : NULL; } } if (strlen(c) < p->stripmsd) { ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd); return -1; } /* Extract any 'w' deferred digits. */ s = strchr(c + p->stripmsd, 'w'); if (s) { *s++ = '\0'; ast_copy_string(p->deferred_digits, s, sizeof(p->deferred_digits)); /* * Since we have a 'w', this means that there will not be any * more normal dialed digits. Therefore, the sending complete * ie needs to be sent with any normal digits. */ } else { p->deferred_digits[0] = '\0'; } pri_grab(p, p->pri); if (!(p->call = pri_new_call(p->pri->pri))) { ast_log(LOG_WARNING, "Unable to create call on channel %d\n", p->channel); pri_rel(p->pri); return -1; } if (!(sr = pri_sr_new())) { ast_log(LOG_WARNING, "Failed to allocate setup request on channel %d\n", p->channel); pri_destroycall(p->pri->pri, p->call); p->call = NULL; pri_rel(p->pri); return -1; } sig_pri_set_digital(p, IS_DIGITAL(ast_channel_transfercapability(ast))); /* push up to parent for EC */ #if defined(HAVE_PRI_CALL_WAITING) if (p->is_call_waiting) { /* * Indicate that this is a call waiting call. * i.e., Normal call but with no B channel. */ pri_sr_set_channel(sr, 0, 0, 1); } else #endif /* defined(HAVE_PRI_CALL_WAITING) */ { /* Should the picked channel be used exclusively? */ if (p->priexclusive || p->pri->nodetype == PRI_NETWORK) { exclusive = 1; } else { exclusive = 0; } pri_sr_set_channel(sr, PVT_TO_CHANNEL(p), exclusive, 1); } pri_sr_set_bearer(sr, p->digital ? PRI_TRANS_CAP_DIGITAL : ast_channel_transfercapability(ast), (p->digital ? -1 : layer1)); if (p->pri->facilityenable) pri_facility_enable(p->pri->pri); ast_verb(3, "Requested transfer capability: 0x%.2x - %s\n", (unsigned)ast_channel_transfercapability(ast), ast_transfercapability2str(ast_channel_transfercapability(ast))); dp_strip = 0; pridialplan = p->pri->dialplan - 1; if (pridialplan == -2 || pridialplan == -3) { /* compute dynamically */ if (strncmp(c + p->stripmsd, p->pri->internationalprefix, strlen(p->pri->internationalprefix)) == 0) { if (pridialplan == -2) { dp_strip = strlen(p->pri->internationalprefix); } pridialplan = PRI_INTERNATIONAL_ISDN; } else if (strncmp(c + p->stripmsd, p->pri->nationalprefix, strlen(p->pri->nationalprefix)) == 0) { if (pridialplan == -2) { dp_strip = strlen(p->pri->nationalprefix); } pridialplan = PRI_NATIONAL_ISDN; } else { pridialplan = PRI_LOCAL_ISDN; } } while (c[p->stripmsd] > '9' && c[p->stripmsd] != '*' && c[p->stripmsd] != '#') { switch (c[p->stripmsd]) { case 'U': pridialplan = (PRI_TON_UNKNOWN << 4) | (pridialplan & 0xf); break; case 'I': pridialplan = (PRI_TON_INTERNATIONAL << 4) | (pridialplan & 0xf); break; case 'N': pridialplan = (PRI_TON_NATIONAL << 4) | (pridialplan & 0xf); break; case 'L': pridialplan = (PRI_TON_NET_SPECIFIC << 4) | (pridialplan & 0xf); break; case 'S': pridialplan = (PRI_TON_SUBSCRIBER << 4) | (pridialplan & 0xf); break; case 'V': pridialplan = (PRI_TON_ABBREVIATED << 4) | (pridialplan & 0xf); break; case 'R': pridialplan = (PRI_TON_RESERVED << 4) | (pridialplan & 0xf); break; case 'u': pridialplan = PRI_NPI_UNKNOWN | (pridialplan & 0xf0); break; case 'e': pridialplan = PRI_NPI_E163_E164 | (pridialplan & 0xf0); break; case 'x': pridialplan = PRI_NPI_X121 | (pridialplan & 0xf0); break; case 'f': pridialplan = PRI_NPI_F69 | (pridialplan & 0xf0); break; case 'n': pridialplan = PRI_NPI_NATIONAL | (pridialplan & 0xf0); break; case 'p': pridialplan = PRI_NPI_PRIVATE | (pridialplan & 0xf0); break; case 'r': pridialplan = PRI_NPI_RESERVED | (pridialplan & 0xf0); break; default: if (isalpha(c[p->stripmsd])) { ast_log(LOG_WARNING, "Unrecognized pridialplan %s modifier: %c\n", c[p->stripmsd] > 'Z' ? "NPI" : "TON", c[p->stripmsd]); } break; } c++; } #if defined(HAVE_PRI_SETUP_KEYPAD) if (ast_test_flag(&opts, OPT_KEYPAD) && !ast_strlen_zero(opt_args[OPT_ARG_KEYPAD])) { /* We have a keypad facility digits option with digits. */ keypad = opt_args[OPT_ARG_KEYPAD]; pri_sr_set_keypad_digits(sr, keypad); } else { keypad = NULL; } if (!keypad || !ast_strlen_zero(c + p->stripmsd + dp_strip)) #endif /* defined(HAVE_PRI_SETUP_KEYPAD) */ { char *called = c + p->stripmsd + dp_strip; pri_sr_set_called(sr, called, pridialplan, s ? 1 : 0); #if defined(HAVE_PRI_SETUP_ACK_INBAND) p->no_dialed_digits = !called[0]; #endif /* defined(HAVE_PRI_SETUP_ACK_INBAND) */ } #if defined(HAVE_PRI_SUBADDR) if (dialed_subaddress.valid) { struct pri_party_subaddress subaddress; memset(&subaddress, 0, sizeof(subaddress)); sig_pri_party_subaddress_from_ast(&subaddress, &dialed_subaddress); pri_sr_set_called_subaddress(sr, &subaddress); } #endif /* defined(HAVE_PRI_SUBADDR) */ #if defined(HAVE_PRI_REVERSE_CHARGE) if (ast_test_flag(&opts, OPT_REVERSE_CHARGE)) { pri_sr_set_reversecharge(sr, PRI_REVERSECHARGE_REQUESTED); } #endif /* defined(HAVE_PRI_REVERSE_CHARGE) */ #if defined(HAVE_PRI_AOC_EVENTS) if (ast_test_flag(&opts, OPT_AOC_REQUEST) && !ast_strlen_zero(opt_args[OPT_ARG_AOC_REQUEST])) { if (strchr(opt_args[OPT_ARG_AOC_REQUEST], 's')) { pri_sr_set_aoc_charging_request(sr, PRI_AOC_REQUEST_S); } if (strchr(opt_args[OPT_ARG_AOC_REQUEST], 'd')) { pri_sr_set_aoc_charging_request(sr, PRI_AOC_REQUEST_D); } if (strchr(opt_args[OPT_ARG_AOC_REQUEST], 'e')) { pri_sr_set_aoc_charging_request(sr, PRI_AOC_REQUEST_E); } } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ /* Setup the user tag for party id's from this device for this call. */ if (p->pri->append_msn_to_user_tag) { snprintf(p->user_tag, sizeof(p->user_tag), "%s_%s", p->pri->initial_user_tag, p->pri->nodetype == PRI_NETWORK ? c + p->stripmsd + dp_strip : S_COR(ast_channel_connected(ast)->id.number.valid, ast_channel_connected(ast)->id.number.str, "")); } else { ast_copy_string(p->user_tag, p->pri->initial_user_tag, sizeof(p->user_tag)); } /* * Replace the caller id tag from the channel creation * with the actual tag value. */ ast_free(ast_channel_caller(ast)->id.tag); ast_channel_caller(ast)->id.tag = ast_strdup(p->user_tag); ldp_strip = 0; prilocaldialplan = p->pri->localdialplan - 1; if ((l != NULL) && (prilocaldialplan == -2 || prilocaldialplan == -3)) { /* compute dynamically */ if (strncmp(l, p->pri->internationalprefix, strlen(p->pri->internationalprefix)) == 0) { if (prilocaldialplan == -2) { ldp_strip = strlen(p->pri->internationalprefix); } prilocaldialplan = PRI_INTERNATIONAL_ISDN; } else if (strncmp(l, p->pri->nationalprefix, strlen(p->pri->nationalprefix)) == 0) { if (prilocaldialplan == -2) { ldp_strip = strlen(p->pri->nationalprefix); } prilocaldialplan = PRI_NATIONAL_ISDN; } else { prilocaldialplan = PRI_LOCAL_ISDN; } } else if (prilocaldialplan == -1) { /* Use the numbering plan passed in. */ prilocaldialplan = connected_id.number.plan; } if (l != NULL) { while (*l > '9' && *l != '*' && *l != '#') { switch (*l) { case 'U': prilocaldialplan = (PRI_TON_UNKNOWN << 4) | (prilocaldialplan & 0xf); break; case 'I': prilocaldialplan = (PRI_TON_INTERNATIONAL << 4) | (prilocaldialplan & 0xf); break; case 'N': prilocaldialplan = (PRI_TON_NATIONAL << 4) | (prilocaldialplan & 0xf); break; case 'L': prilocaldialplan = (PRI_TON_NET_SPECIFIC << 4) | (prilocaldialplan & 0xf); break; case 'S': prilocaldialplan = (PRI_TON_SUBSCRIBER << 4) | (prilocaldialplan & 0xf); break; case 'V': prilocaldialplan = (PRI_TON_ABBREVIATED << 4) | (prilocaldialplan & 0xf); break; case 'R': prilocaldialplan = (PRI_TON_RESERVED << 4) | (prilocaldialplan & 0xf); break; case 'u': prilocaldialplan = PRI_NPI_UNKNOWN | (prilocaldialplan & 0xf0); break; case 'e': prilocaldialplan = PRI_NPI_E163_E164 | (prilocaldialplan & 0xf0); break; case 'x': prilocaldialplan = PRI_NPI_X121 | (prilocaldialplan & 0xf0); break; case 'f': prilocaldialplan = PRI_NPI_F69 | (prilocaldialplan & 0xf0); break; case 'n': prilocaldialplan = PRI_NPI_NATIONAL | (prilocaldialplan & 0xf0); break; case 'p': prilocaldialplan = PRI_NPI_PRIVATE | (prilocaldialplan & 0xf0); break; case 'r': prilocaldialplan = PRI_NPI_RESERVED | (prilocaldialplan & 0xf0); break; default: if (isalpha(*l)) { ast_log(LOG_WARNING, "Unrecognized prilocaldialplan %s modifier: %c\n", *l > 'Z' ? "NPI" : "TON", *l); } break; } l++; } } pri_sr_set_caller(sr, l ? (l + ldp_strip) : NULL, n, prilocaldialplan, p->use_callingpres ? connected_id.number.presentation : (l ? PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN : PRES_NUMBER_NOT_AVAILABLE)); #if defined(HAVE_PRI_SUBADDR) if (connected_id.subaddress.valid) { struct pri_party_subaddress subaddress; memset(&subaddress, 0, sizeof(subaddress)); sig_pri_party_subaddress_from_ast(&subaddress, &connected_id.subaddress); pri_sr_set_caller_subaddress(sr, &subaddress); } #endif /* defined(HAVE_PRI_SUBADDR) */ sig_pri_redirecting_update(p, ast); #ifdef SUPPORT_USERUSER /* User-user info */ useruser = pbx_builtin_getvar_helper(p->owner, "USERUSERINFO"); if (useruser) pri_sr_set_useruser(sr, useruser); #endif #if defined(HAVE_PRI_CCSS) if (ast_cc_is_recall(ast, &core_id, sig_pri_cc_type_name)) { struct ast_cc_monitor *monitor; char device_name[AST_CHANNEL_NAME]; /* This is a CC recall call. */ ast_channel_get_device_name(ast, device_name, sizeof(device_name)); monitor = ast_cc_get_monitor_by_recall_core_id(core_id, device_name); if (monitor) { struct sig_pri_cc_monitor_instance *instance; instance = monitor->private_data; /* If this fails then we have monitor instance ambiguity. */ ast_assert(p->pri == instance->pri); if (pri_cc_call(p->pri->pri, instance->cc_id, p->call, sr)) { /* The CC recall call failed for some reason. */ ast_log(LOG_WARNING, "Unable to setup CC recall call to device %s\n", device_name); ao2_ref(monitor, -1); pri_destroycall(p->pri->pri, p->call); p->call = NULL; pri_rel(p->pri); pri_sr_free(sr); return -1; } ao2_ref(monitor, -1); } else { core_id = -1; } } else #endif /* defined(HAVE_PRI_CCSS) */ { core_id = -1; } if (core_id == -1 && pri_setup(p->pri->pri, p->call, sr)) { ast_log(LOG_WARNING, "Unable to setup call to %s (using %s)\n", c + p->stripmsd + dp_strip, dialplan2str(p->pri->dialplan)); pri_destroycall(p->pri->pri, p->call); p->call = NULL; pri_rel(p->pri); pri_sr_free(sr); return -1; } p->call_level = SIG_PRI_CALL_LEVEL_SETUP; pri_sr_free(sr); ast_setstate(ast, AST_STATE_DIALING); sig_pri_set_dialing(p, 1); pri_rel(p->pri); return 0; } int sig_pri_indicate(struct sig_pri_chan *p, struct ast_channel *chan, int condition, const void *data, size_t datalen) { int res = -1; switch (condition) { case AST_CONTROL_BUSY: if (p->priindication_oob || p->no_b_channel) { ast_channel_hangupcause_set(chan, AST_CAUSE_USER_BUSY); ast_channel_softhangup_internal_flag_add(chan, AST_SOFTHANGUP_DEV); res = 0; break; } res = sig_pri_play_tone(p, SIG_PRI_TONE_BUSY); if (p->call_level < SIG_PRI_CALL_LEVEL_ALERTING && !p->outgoing) { ast_channel_hangupcause_set(chan, AST_CAUSE_USER_BUSY); p->progress = 1;/* No need to send plain PROGRESS after this. */ if (p->pri && p->pri->pri) { pri_grab(p, p->pri); #ifdef HAVE_PRI_PROG_W_CAUSE pri_progress_with_cause(p->pri->pri, p->call, PVT_TO_CHANNEL(p), 1, ast_channel_hangupcause(chan)); #else pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1); #endif pri_rel(p->pri); } } break; case AST_CONTROL_RINGING: if (p->call_level < SIG_PRI_CALL_LEVEL_ALERTING && !p->outgoing) { p->call_level = SIG_PRI_CALL_LEVEL_ALERTING; if (p->pri && p->pri->pri) { pri_grab(p, p->pri); pri_acknowledge(p->pri->pri,p->call, PVT_TO_CHANNEL(p), p->no_b_channel || p->digital ? 0 : 1); pri_rel(p->pri); } } res = sig_pri_play_tone(p, SIG_PRI_TONE_RINGTONE); if (ast_channel_state(chan) != AST_STATE_UP) { if (ast_channel_state(chan) != AST_STATE_RING) ast_setstate(chan, AST_STATE_RINGING); } break; case AST_CONTROL_PROCEEDING: ast_debug(1, "Received AST_CONTROL_PROCEEDING on %s\n",ast_channel_name(chan)); if (p->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING && !p->outgoing) { p->call_level = SIG_PRI_CALL_LEVEL_PROCEEDING; if (p->pri && p->pri->pri) { pri_grab(p, p->pri); pri_proceeding(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 0); pri_rel(p->pri); } } /* don't continue in ast_indicate */ res = 0; break; case AST_CONTROL_PROGRESS: ast_debug(1, "Received AST_CONTROL_PROGRESS on %s\n",ast_channel_name(chan)); sig_pri_set_digital(p, 0); /* Digital-only calls isn't allowing any inband progress messages */ if (!p->progress && p->call_level < SIG_PRI_CALL_LEVEL_ALERTING && !p->outgoing && !p->no_b_channel) { p->progress = 1;/* No need to send plain PROGRESS again. */ if (p->pri && p->pri->pri) { pri_grab(p, p->pri); #ifdef HAVE_PRI_PROG_W_CAUSE pri_progress_with_cause(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1, -1); /* no cause at all */ #else pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1); #endif pri_rel(p->pri); } } /* don't continue in ast_indicate */ res = 0; break; case AST_CONTROL_INCOMPLETE: /* If we are connected or if we support overlap dialing, wait for additional digits */ if (p->call_level == SIG_PRI_CALL_LEVEL_CONNECT || (p->pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)) { res = 0; break; } /* Otherwise, treat as congestion */ ast_channel_hangupcause_set(chan, AST_CAUSE_INVALID_NUMBER_FORMAT); /* Falls through */ case AST_CONTROL_CONGESTION: if (p->priindication_oob || p->no_b_channel) { /* There are many cause codes that generate an AST_CONTROL_CONGESTION. */ switch (ast_channel_hangupcause(chan)) { case AST_CAUSE_USER_BUSY: case AST_CAUSE_NORMAL_CLEARING: case 0:/* Cause has not been set. */ /* Supply a more appropriate cause. */ ast_channel_hangupcause_set(chan, AST_CAUSE_SWITCH_CONGESTION); break; default: break; } ast_channel_softhangup_internal_flag_add(chan, AST_SOFTHANGUP_DEV); res = 0; break; } res = sig_pri_play_tone(p, SIG_PRI_TONE_CONGESTION); if (p->call_level < SIG_PRI_CALL_LEVEL_ALERTING && !p->outgoing) { /* There are many cause codes that generate an AST_CONTROL_CONGESTION. */ switch (ast_channel_hangupcause(chan)) { case AST_CAUSE_USER_BUSY: case AST_CAUSE_NORMAL_CLEARING: case 0:/* Cause has not been set. */ /* Supply a more appropriate cause. */ ast_channel_hangupcause_set(chan, AST_CAUSE_SWITCH_CONGESTION); break; default: break; } p->progress = 1;/* No need to send plain PROGRESS after this. */ if (p->pri && p->pri->pri) { pri_grab(p, p->pri); #ifdef HAVE_PRI_PROG_W_CAUSE pri_progress_with_cause(p->pri->pri, p->call, PVT_TO_CHANNEL(p), 1, ast_channel_hangupcause(chan)); #else pri_progress(p->pri->pri,p->call, PVT_TO_CHANNEL(p), 1); #endif pri_rel(p->pri); } } break; case AST_CONTROL_HOLD: ast_copy_string(p->moh_suggested, S_OR(data, ""), sizeof(p->moh_suggested)); if (p->pri) { pri_grab(p, p->pri); sig_pri_moh_fsm_event(chan, p, SIG_PRI_MOH_EVENT_HOLD); pri_rel(p->pri); } else { /* Something is wrong here. A PRI channel without the pri pointer? */ ast_moh_start(chan, data, p->mohinterpret); } break; case AST_CONTROL_UNHOLD: if (p->pri) { pri_grab(p, p->pri); sig_pri_moh_fsm_event(chan, p, SIG_PRI_MOH_EVENT_UNHOLD); pri_rel(p->pri); } else { /* Something is wrong here. A PRI channel without the pri pointer? */ ast_moh_stop(chan); } break; case AST_CONTROL_SRCUPDATE: res = 0; break; case -1: res = sig_pri_play_tone(p, -1); break; case AST_CONTROL_CONNECTED_LINE: ast_debug(1, "Received AST_CONTROL_CONNECTED_LINE on %s\n", ast_channel_name(chan)); if (p->pri) { struct pri_party_connected_line connected; int dialplan; int prefix_strip; int colp_allowed = 0; struct ast_party_id connected_id = ast_channel_connected_effective_id(chan); pri_grab(p, p->pri); /* Check if a connected line update is allowed at this time. */ switch (p->pri->colp_send) { case SIG_PRI_COLP_BLOCK: break; case SIG_PRI_COLP_CONNECT: /* * Outgoing calls receive CONNECT and act like an update before * the call is connected. */ if (p->call_level <= SIG_PRI_CALL_LEVEL_ALERTING && !p->outgoing) { colp_allowed = 1; } break; case SIG_PRI_COLP_UPDATE: colp_allowed = 1; break; } if (!colp_allowed) { pri_rel(p->pri); ast_debug(1, "Blocked AST_CONTROL_CONNECTED_LINE on %s\n", ast_channel_name(chan)); break; } memset(&connected, 0, sizeof(connected)); sig_pri_party_id_from_ast(&connected.id, &connected_id); /* Determine the connected line numbering plan to actually use. */ switch (p->pri->cpndialplan) { case -2:/* redundant */ case -1:/* dynamic */ /* compute dynamically */ prefix_strip = 0; if (!strncmp(connected.id.number.str, p->pri->internationalprefix, strlen(p->pri->internationalprefix))) { prefix_strip = strlen(p->pri->internationalprefix); dialplan = PRI_INTERNATIONAL_ISDN; } else if (!strncmp(connected.id.number.str, p->pri->nationalprefix, strlen(p->pri->nationalprefix))) { prefix_strip = strlen(p->pri->nationalprefix); dialplan = PRI_NATIONAL_ISDN; } else { dialplan = PRI_LOCAL_ISDN; } connected.id.number.plan = dialplan; if (prefix_strip && p->pri->cpndialplan != -2) { /* Strip the prefix from the connected line number. */ memmove(connected.id.number.str, connected.id.number.str + prefix_strip, strlen(connected.id.number.str + prefix_strip) + 1); } break; case 0:/* from_channel */ /* Use the numbering plan passed in. */ break; default: connected.id.number.plan = p->pri->cpndialplan - 1; break; } pri_connected_line_update(p->pri->pri, p->call, &connected); pri_rel(p->pri); } break; case AST_CONTROL_REDIRECTING: ast_debug(1, "Received AST_CONTROL_REDIRECTING on %s\n", ast_channel_name(chan)); if (p->pri) { pri_grab(p, p->pri); sig_pri_redirecting_update(p, chan); pri_rel(p->pri); } break; case AST_CONTROL_AOC: #if defined(HAVE_PRI_AOC_EVENTS) { struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, chan); ast_debug(1, "Received AST_CONTROL_AOC on %s\n", ast_channel_name(chan)); if (decoded && p->pri) { pri_grab(p, p->pri); switch (ast_aoc_get_msg_type(decoded)) { case AST_AOC_S: if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S) { sig_pri_aoc_s_from_ast(p, decoded); } break; case AST_AOC_D: if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D) { sig_pri_aoc_d_from_ast(p, decoded); } break; case AST_AOC_E: if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E) { sig_pri_aoc_e_from_ast(p, decoded); } /* if hangup was delayed for this AOC-E msg, waiting_for_aoc * will be set. A hangup is already occuring via a timeout during * this delay. Instead of waiting for that timeout to occur, go ahead * and initiate the softhangup since the delay is no longer necessary */ if (p->waiting_for_aoce) { p->waiting_for_aoce = 0; ast_debug(1, "Received final AOC-E msg, continue with hangup on %s\n", ast_channel_name(chan)); ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV); } break; case AST_AOC_REQUEST: /* We do not pass through AOC requests, So unless this * is an AOC termination request it will be ignored */ if (ast_aoc_get_termination_request(decoded)) { pri_hangup(p->pri->pri, p->call, -1); } break; default: break; } pri_rel(p->pri); } ast_aoc_destroy_decoded(decoded); } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ break; #if defined(HAVE_PRI_MCID) case AST_CONTROL_MCID: if (p->pri && p->pri->pri && p->pri->mcid_send) { pri_grab(p, p->pri); pri_mcid_req_send(p->pri->pri, p->call); pri_rel(p->pri); } break; #endif /* defined(HAVE_PRI_MCID) */ } return res; } int sig_pri_answer(struct sig_pri_chan *p, struct ast_channel *ast) { int res; /* Send a pri acknowledge */ pri_grab(p, p->pri); #if defined(HAVE_PRI_AOC_EVENTS) if (p->aoc_s_request_invoke_id_valid) { /* if AOC-S was requested and the invoke id is still present on answer. That means * no AOC-S rate list was provided, so send a NULL response which will indicate that * AOC-S is not available */ pri_aoc_s_request_response_send(p->pri->pri, p->call, p->aoc_s_request_invoke_id, NULL); p->aoc_s_request_invoke_id_valid = 0; } #endif /* defined(HAVE_PRI_AOC_EVENTS) */ if (p->call_level < SIG_PRI_CALL_LEVEL_CONNECT) { p->call_level = SIG_PRI_CALL_LEVEL_CONNECT; } sig_pri_set_dialing(p, 0); sig_pri_open_media(p); res = pri_answer(p->pri->pri, p->call, 0, !p->digital); pri_rel(p->pri); ast_setstate(ast, AST_STATE_UP); return res; } /*! * \internal * \brief Simple check if the channel is available to use. * \since 1.8 * * \param pvt Private channel control structure. * * \retval 0 Interface not available. * \retval 1 Interface is available. */ static int sig_pri_available_check(struct sig_pri_chan *pvt) { /* * If interface has a B channel and is available for use * then the channel is available. */ if (!pvt->no_b_channel && sig_pri_is_chan_available(pvt)) { return 1; } return 0; } #if defined(HAVE_PRI_CALL_WAITING) /*! * \internal * \brief Get an available call waiting interface. * \since 1.8 * * \param pri PRI span control structure. * * \note Assumes the pri->lock is already obtained. * * \retval cw Call waiting interface to use. * \retval NULL if no call waiting interface available. */ static struct sig_pri_chan *sig_pri_cw_available(struct sig_pri_span *pri) { struct sig_pri_chan *cw; int idx; cw = NULL; if (pri->num_call_waiting_calls < pri->max_call_waiting_calls) { if (!pri->num_call_waiting_calls) { /* * There are no outstanding call waiting calls. Check to see * if the span is in a congested state for the first call * waiting call. */ for (idx = 0; idx < pri->numchans; ++idx) { if (pri->pvts[idx] && sig_pri_available_check(pri->pvts[idx])) { /* There is another channel that is available on this span. */ return cw; } } } idx = pri_find_empty_nobch(pri); if (0 <= idx) { /* Setup the call waiting interface to use. */ cw = pri->pvts[idx]; cw->is_call_waiting = 1; sig_pri_init_config(cw, pri); ast_atomic_fetchadd_int(&pri->num_call_waiting_calls, 1); } } return cw; } #endif /* defined(HAVE_PRI_CALL_WAITING) */ int sig_pri_available(struct sig_pri_chan **pvt, int is_specific_channel) { struct sig_pri_chan *p = *pvt; struct sig_pri_span *pri; if (!p->pri) { /* Something is wrong here. A PRI channel without the pri pointer? */ return 0; } pri = p->pri; ast_mutex_lock(&pri->lock); if ( #if defined(HAVE_PRI_CALL_WAITING) /* * Only do call waiting calls if we have any * call waiting call outstanding. We do not * want new calls to steal a B channel * freed for an earlier call waiting call. */ !pri->num_call_waiting_calls && #endif /* defined(HAVE_PRI_CALL_WAITING) */ sig_pri_available_check(p)) { p->allocated = 1; ast_mutex_unlock(&pri->lock); return 1; } #if defined(HAVE_PRI_CALL_WAITING) if (!is_specific_channel) { struct sig_pri_chan *cw; cw = sig_pri_cw_available(pri); if (cw) { /* We have a call waiting interface to use instead. */ cw->allocated = 1; *pvt = cw; ast_mutex_unlock(&pri->lock); return 1; } } #endif /* defined(HAVE_PRI_CALL_WAITING) */ ast_mutex_unlock(&pri->lock); return 0; } /* If return 0, it means this function was able to handle it (pre setup digits). If non zero, the user of this * functions should handle it normally (generate inband DTMF) */ int sig_pri_digit_begin(struct sig_pri_chan *pvt, struct ast_channel *ast, char digit) { if (ast_channel_state(ast) == AST_STATE_DIALING) { if (pvt->call_level < SIG_PRI_CALL_LEVEL_OVERLAP) { unsigned int len; len = strlen(pvt->dialdest); if (len < sizeof(pvt->dialdest) - 1) { ast_debug(1, "Queueing digit '%c' since setup_ack not yet received\n", digit); pvt->dialdest[len++] = digit; pvt->dialdest[len] = '\0'; } else { ast_log(LOG_WARNING, "Span %d: Deferred digit buffer overflow for digit '%c'.\n", pvt->pri->span, digit); } return 0; } if (pvt->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING) { pri_grab(pvt, pvt->pri); pri_information(pvt->pri->pri, pvt->call, digit); pri_rel(pvt->pri); return 0; } if (pvt->call_level < SIG_PRI_CALL_LEVEL_CONNECT) { ast_log(LOG_WARNING, "Span %d: Digit '%c' may be ignored by peer. (Call level:%u(%s))\n", pvt->pri->span, digit, pvt->call_level, sig_pri_call_level2str(pvt->call_level)); } } return 1; } /*! * \brief DTMF dial string complete. * \since 1.8.11 * * \param pvt sig_pri private channel structure. * \param ast Asterisk channel * * \note Channel and private lock are already held. * * \return Nothing */ void sig_pri_dial_complete(struct sig_pri_chan *pvt, struct ast_channel *ast) { /* If we just completed 'w' deferred dialing digits, we need to answer now. */ if (pvt->call_level == SIG_PRI_CALL_LEVEL_DEFER_DIAL) { pvt->call_level = SIG_PRI_CALL_LEVEL_CONNECT; sig_pri_open_media(pvt); { struct ast_frame f = {AST_FRAME_CONTROL, }; if (sig_pri_callbacks.queue_control) { sig_pri_callbacks.queue_control(pvt->chan_pvt, AST_CONTROL_ANSWER); } f.subclass.integer = AST_CONTROL_ANSWER; ast_queue_frame(ast, &f); } sig_pri_set_dialing(pvt, 0); /* Enable echo cancellation if it's not on already */ sig_pri_set_echocanceller(pvt, 1); } } #if defined(HAVE_PRI_MWI) /*! * \internal * \brief Send a MWI indication to the given span. * \since 1.8 * * \param pri PRI span control structure. * \param vm_number Voicemail controlling number (NULL if not present). * \param vm_box Voicemail mailbox number * \param mbox_id Mailbox id * \param num_messages Number of messages waiting. * * \return Nothing */ static void sig_pri_send_mwi_indication(struct sig_pri_span *pri, const char *vm_number, const char *vm_box, const char *mbox_id, int num_messages) { struct pri_party_id voicemail; struct pri_party_id mailbox; ast_debug(1, "Send MWI indication for %s(%s) vm_number:%s num_messages:%d\n", vm_box, mbox_id, S_OR(vm_number, ""), num_messages); memset(&mailbox, 0, sizeof(mailbox)); mailbox.number.valid = 1; mailbox.number.presentation = PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; mailbox.number.plan = (PRI_TON_UNKNOWN << 4) | PRI_NPI_UNKNOWN; ast_copy_string(mailbox.number.str, vm_box, sizeof(mailbox.number.str)); memset(&voicemail, 0, sizeof(voicemail)); voicemail.number.valid = 1; voicemail.number.presentation = PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; voicemail.number.plan = (PRI_TON_UNKNOWN << 4) | PRI_NPI_UNKNOWN; if (vm_number) { ast_copy_string(voicemail.number.str, vm_number, sizeof(voicemail.number.str)); } ast_mutex_lock(&pri->lock); #if defined(HAVE_PRI_MWI_V2) pri_mwi_indicate_v2(pri->pri, &mailbox, &voicemail, 1 /* speech */, num_messages, NULL, NULL, -1, 0); #else /* !defined(HAVE_PRI_MWI_V2) */ pri_mwi_indicate(pri->pri, &mailbox, 1 /* speech */, num_messages, NULL, NULL, -1, 0); #endif /* !defined(HAVE_PRI_MWI_V2) */ ast_mutex_unlock(&pri->lock); } #endif /* defined(HAVE_PRI_MWI) */ #if defined(HAVE_PRI_MWI) /*! * \internal * \brief MWI subscription event callback. * \since 1.8 * * \param userdata the data provider in the call to stasis_subscribe() * \param sub the subscription to which the message was delivered for this callback * \param topic the topic on which the message was published * \param msg the message being passed to the subscriber * * \return Nothing */ static void sig_pri_mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { struct sig_pri_span *pri = userdata; int idx; struct ast_mwi_state *mwi_state; if (ast_mwi_state_type() != stasis_message_type(msg)) { return; } mwi_state = stasis_message_data(msg); for (idx = 0; idx < ARRAY_LEN(pri->mbox); ++idx) { if (!pri->mbox[idx].sub) { /* Mailbox slot is empty */ continue; } if (!strcmp(pri->mbox[idx].uniqueid, mwi_state->uniqueid)) { /* Found the mailbox. */ sig_pri_send_mwi_indication(pri, pri->mbox[idx].vm_number, pri->mbox[idx].vm_box, pri->mbox[idx].uniqueid, mwi_state->new_msgs); break; } } } #endif /* defined(HAVE_PRI_MWI) */ #if defined(HAVE_PRI_MWI) /*! * \internal * \brief Send update MWI indications from the event cache. * \since 1.8 * * \param pri PRI span control structure. * * \return Nothing */ static void sig_pri_mwi_cache_update(struct sig_pri_span *pri) { int idx; struct ast_mwi_state *mwi_state; for (idx = 0; idx < ARRAY_LEN(pri->mbox); ++idx) { RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); if (!pri->mbox[idx].sub) { /* Mailbox slot is empty */ continue; } msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), pri->mbox[idx].uniqueid); if (!msg) { /* No cached event for this mailbox. */ continue; } mwi_state = stasis_message_data(msg); sig_pri_send_mwi_indication(pri, pri->mbox[idx].vm_number, pri->mbox[idx].vm_box, pri->mbox[idx].uniqueid, mwi_state->new_msgs); } } #endif /* defined(HAVE_PRI_MWI) */ /*! * \brief Stop PRI span. * \since 1.8 * * \param pri PRI span control structure. * * \return Nothing */ void sig_pri_stop_pri(struct sig_pri_span *pri) { #if defined(HAVE_PRI_MWI) int idx; #endif /* defined(HAVE_PRI_MWI) */ #if defined(HAVE_PRI_MWI) for (idx = 0; idx < ARRAY_LEN(pri->mbox); ++idx) { if (pri->mbox[idx].sub) { pri->mbox[idx].sub = stasis_unsubscribe(pri->mbox[idx].sub); } } #endif /* defined(HAVE_PRI_MWI) */ } /*! * \internal * \brief qsort comparison function. * \since 1.8 * * \param left Ptr to sig_pri_chan ptr to compare. * \param right Ptr to sig_pri_chan ptr to compare. * * \retval <0 if left < right. * \retval =0 if left == right. * \retval >0 if left > right. */ static int sig_pri_cmp_pri_chans(const void *left, const void *right) { const struct sig_pri_chan *pvt_left; const struct sig_pri_chan *pvt_right; pvt_left = *(struct sig_pri_chan **) left; pvt_right = *(struct sig_pri_chan **) right; if (!pvt_left) { if (!pvt_right) { return 0; } return 1; } if (!pvt_right) { return -1; } return pvt_left->channel - pvt_right->channel; } /*! * \internal * \brief Sort the PRI B channel private pointer array. * \since 1.8 * * \param pri PRI span control structure. * * \details * Since the chan_dahdi.conf file can declare channels in any order, we need to sort * the private channel pointer array. * * \return Nothing */ static void sig_pri_sort_pri_chans(struct sig_pri_span *pri) { qsort(&pri->pvts, pri->numchans, sizeof(pri->pvts[0]), sig_pri_cmp_pri_chans); } int sig_pri_start_pri(struct sig_pri_span *pri) { int x; int i; #if defined(HAVE_PRI_MWI) char *saveptr; char *prev_vm_number; #endif /* defined(HAVE_PRI_MWI) */ #if defined(HAVE_PRI_MWI) /* Prepare the mbox[] for use. */ for (i = 0; i < ARRAY_LEN(pri->mbox); ++i) { if (pri->mbox[i].sub) { pri->mbox[i].sub = stasis_unsubscribe(pri->mbox[i].sub); } } #endif /* defined(HAVE_PRI_MWI) */ ast_mutex_init(&pri->lock); sig_pri_sort_pri_chans(pri); #if defined(HAVE_PRI_MWI) /* * Split the mwi_vm_numbers configuration string into the mbox[].vm_number: * vm_number{,vm_number} */ prev_vm_number = NULL; saveptr = pri->mwi_vm_numbers; for (i = 0; i < ARRAY_LEN(pri->mbox); ++i) { char *vm_number; vm_number = strsep(&saveptr, ","); if (vm_number) { vm_number = ast_strip(vm_number); } if (ast_strlen_zero(vm_number)) { /* There was no number so reuse the previous number. */ vm_number = prev_vm_number; } else { /* We have a new number. */ prev_vm_number = vm_number; } pri->mbox[i].vm_number = vm_number; } /* * Split the mwi_vm_boxes configuration string into the mbox[].vm_box: * vm_box{,vm_box} */ saveptr = pri->mwi_vm_boxes; for (i = 0; i < ARRAY_LEN(pri->mbox); ++i) { char *vm_box; vm_box = strsep(&saveptr, ","); if (vm_box) { vm_box = ast_strip(vm_box); if (ast_strlen_zero(vm_box)) { vm_box = NULL; } } pri->mbox[i].vm_box = vm_box; } /* * Split the mwi_mailboxes configuration string into the mbox[]: * vm_mailbox{,vm_mailbox} */ saveptr = pri->mwi_mailboxes; for (i = 0; i < ARRAY_LEN(pri->mbox); ++i) { char *mbox_id; struct stasis_topic *mailbox_specific_topic; mbox_id = strsep(&saveptr, ","); if (mbox_id) { mbox_id = ast_strip(mbox_id); if (ast_strlen_zero(mbox_id)) { mbox_id = NULL; } } pri->mbox[i].uniqueid = mbox_id; if (!pri->mbox[i].vm_box || !mbox_id) { /* The mailbox position is disabled. */ ast_debug(1, "%s span %d MWI position %d disabled. vm_box:%s mbox_id:%s.\n", sig_pri_cc_type_name, pri->span, i, pri->mbox[i].vm_box ?: "", mbox_id ?: ""); continue; } mailbox_specific_topic = ast_mwi_topic(mbox_id); if (mailbox_specific_topic) { pri->mbox[i].sub = stasis_subscribe_pool(mailbox_specific_topic, sig_pri_mwi_event_cb, pri); } if (!pri->mbox[i].sub) { ast_log(LOG_ERROR, "%s span %d could not subscribe to MWI events for %s(%s).\n", sig_pri_cc_type_name, pri->span, pri->mbox[i].vm_box, mbox_id); } #if defined(HAVE_PRI_MWI_V2) if (ast_strlen_zero(pri->mbox[i].vm_number)) { ast_log(LOG_WARNING, "%s span %d MWI voicemail number for %s(%s) is empty.\n", sig_pri_cc_type_name, pri->span, pri->mbox[i].vm_box, mbox_id); } #endif /* defined(HAVE_PRI_MWI_V2) */ } #endif /* defined(HAVE_PRI_MWI) */ for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { if (pri->fds[i] == -1) { break; } switch (pri->sig) { case SIG_BRI: pri->dchans[i] = pri_new_bri(pri->fds[i], 1, pri->nodetype, pri->switchtype); break; case SIG_BRI_PTMP: pri->dchans[i] = pri_new_bri(pri->fds[i], 0, pri->nodetype, pri->switchtype); break; default: pri->dchans[i] = pri_new(pri->fds[i], pri->nodetype, pri->switchtype); #if defined(HAVE_PRI_SERVICE_MESSAGES) if (pri->enable_service_message_support) { pri_set_service_message_support(pri->dchans[i], 1); } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ break; } pri_set_overlapdial(pri->dchans[i], (pri->overlapdial & DAHDI_OVERLAPDIAL_OUTGOING) ? 1 : 0); #ifdef HAVE_PRI_PROG_W_CAUSE pri_set_chan_mapping_logical(pri->dchans[i], pri->qsigchannelmapping == DAHDI_CHAN_MAPPING_LOGICAL); #endif #ifdef HAVE_PRI_INBANDDISCONNECT pri_set_inbanddisconnect(pri->dchans[i], pri->inbanddisconnect); #endif /* Enslave to master if appropriate */ if (i) pri_enslave(pri->dchans[0], pri->dchans[i]); if (!pri->dchans[i]) { if (pri->fds[i] > 0) close(pri->fds[i]); pri->fds[i] = -1; ast_log(LOG_ERROR, "Unable to create PRI structure\n"); return -1; } pri_set_debug(pri->dchans[i], SIG_PRI_DEBUG_DEFAULT); pri_set_nsf(pri->dchans[i], pri->nsf); #ifdef PRI_GETSET_TIMERS for (x = 0; x < PRI_MAX_TIMERS; x++) { if (pri->pritimers[x] != 0) pri_set_timer(pri->dchans[i], x, pri->pritimers[x]); } #endif } /* Assume primary is the one we use */ pri->pri = pri->dchans[0]; #if defined(HAVE_PRI_CALL_HOLD) pri_hold_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_REROUTING) pri_reroute_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_CALL_REROUTING) */ #if defined(HAVE_PRI_HANGUP_FIX) pri_hangup_fix_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_HANGUP_FIX) */ #if defined(HAVE_PRI_CCSS) pri_cc_enable(pri->pri, 1); pri_cc_recall_mode(pri->pri, pri->cc_ptmp_recall_mode); pri_cc_retain_signaling_req(pri->pri, pri->cc_qsig_signaling_link_req); pri_cc_retain_signaling_rsp(pri->pri, pri->cc_qsig_signaling_link_rsp); #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_TRANSFER) pri_transfer_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_TRANSFER) */ #if defined(HAVE_PRI_AOC_EVENTS) pri_aoc_events_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_CALL_WAITING) pri_connect_ack_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_CALL_WAITING) */ #if defined(HAVE_PRI_MCID) pri_mcid_enable(pri->pri, 1); #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_DISPLAY_TEXT) pri_display_options_send(pri->pri, pri->display_flags_send); pri_display_options_receive(pri->pri, pri->display_flags_receive); #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #if defined(HAVE_PRI_DATETIME_SEND) pri_date_time_send_option(pri->pri, pri->datetime_send); #endif /* defined(HAVE_PRI_DATETIME_SEND) */ #if defined(HAVE_PRI_L2_PERSISTENCE) pri_persistent_layer2_option(pri->pri, pri->l2_persistence); #endif /* defined(HAVE_PRI_L2_PERSISTENCE) */ pri->resetpos = -1; if (ast_pthread_create_background(&pri->master, NULL, pri_dchannel, pri)) { for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { if (!pri->dchans[i]) break; if (pri->fds[i] > 0) close(pri->fds[i]); pri->fds[i] = -1; } ast_log(LOG_ERROR, "Unable to spawn D-channel: %s\n", strerror(errno)); return -1; } #if defined(HAVE_PRI_MWI) /* * Send the initial MWI indications from the event cache for this span. * * If we were loaded after app_voicemail the event would already be in * the cache. If we were loaded before app_voicemail the event would not * be in the cache yet and app_voicemail will send the event when it * gets loaded. */ sig_pri_mwi_cache_update(pri); #endif /* defined(HAVE_PRI_MWI) */ return 0; } /*! * \brief Notify new alarm status. * * \param p Channel private pointer. * \param noalarm Non-zero if not in alarm mode. * * \note Assumes the sig_pri_lock_private(p) is already obtained. * * \return Nothing */ void sig_pri_chan_alarm_notify(struct sig_pri_chan *p, int noalarm) { pri_grab(p, p->pri); sig_pri_set_alarm(p, !noalarm); if (!noalarm) { if (pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0) { /* T309 is not enabled : destroy calls when alarm occurs */ if (p->call) { pri_destroycall(p->pri->pri, p->call); p->call = NULL; } if (p->owner) ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); } } sig_pri_span_devstate_changed(p->pri); pri_rel(p->pri); } /*! * \brief Determine if layer 1 alarms are ignored. * * \param p Channel private pointer. * * \return TRUE if the alarm is ignored. */ int sig_pri_is_alarm_ignored(struct sig_pri_span *pri) { return pri->layer1_ignored; } struct sig_pri_chan *sig_pri_chan_new(void *pvt_data, struct sig_pri_span *pri, int logicalspan, int channo, int trunkgroup) { struct sig_pri_chan *p; p = ast_calloc(1, sizeof(*p)); if (!p) return p; p->logicalspan = logicalspan; p->prioffset = channo; p->mastertrunkgroup = trunkgroup; p->chan_pvt = pvt_data; p->pri = pri; return p; } /*! * \brief Delete the sig_pri private channel structure. * \since 1.8 * * \param doomed sig_pri private channel structure to delete. * * \return Nothing */ void sig_pri_chan_delete(struct sig_pri_chan *doomed) { ast_free(doomed); } #define SIG_PRI_SC_HEADER "%-4s %4s %-4s %-4s %-10s %-4s %s\n" #define SIG_PRI_SC_LINE "%4d %4d %-4s %-4s %-10s %-4s %s" void sig_pri_cli_show_channels_header(int fd) { ast_cli(fd, SIG_PRI_SC_HEADER, "PRI", "", "B", "Chan", "Call", "PRI", "Channel"); ast_cli(fd, SIG_PRI_SC_HEADER, "Span", "Chan", "Chan", "Idle", "Level", "Call", "Name"); } void sig_pri_cli_show_channels(int fd, struct sig_pri_span *pri) { char line[256]; int idx; struct sig_pri_chan *pvt; ast_mutex_lock(&pri->lock); for (idx = 0; idx < pri->numchans; ++idx) { if (!pri->pvts[idx]) { continue; } pvt = pri->pvts[idx]; sig_pri_lock_private(pvt); sig_pri_lock_owner(pri, idx); if (pvt->no_b_channel && sig_pri_is_chan_available(pvt)) { /* Don't show held/call-waiting channels if they are not in use. */ sig_pri_unlock_private(pvt); continue; } snprintf(line, sizeof(line), SIG_PRI_SC_LINE, pri->span, pvt->channel, pvt->no_b_channel ? "No" : "Yes",/* Has media */ sig_pri_is_chan_available(pvt) ? "Yes" : "No", sig_pri_call_level2str(pvt->call_level), pvt->call ? "Yes" : "No", pvt->owner ? ast_channel_name(pvt->owner) : ""); if (pvt->owner) { ast_channel_unlock(pvt->owner); } sig_pri_unlock_private(pvt); ast_mutex_unlock(&pri->lock); ast_cli(fd, "%s\n", line); ast_mutex_lock(&pri->lock); } ast_mutex_unlock(&pri->lock); } static void build_status(char *s, size_t len, int status, int active) { if (!s || len < 1) { return; } snprintf(s, len, "%s%s, %s", (status & DCHAN_NOTINALARM) ? "" : "In Alarm, ", (status & DCHAN_UP) ? "Up" : "Down", (active) ? "Active" : "Standby"); } void sig_pri_cli_show_spans(int fd, int span, struct sig_pri_span *pri) { char status[256]; int x; for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if (pri->dchans[x]) { build_status(status, sizeof(status), pri->dchanavail[x], pri->dchans[x] == pri->pri); ast_cli(fd, "PRI span %d/%d: %s\n", span, x, status); } } } void sig_pri_cli_show_span(int fd, int *dchannels, struct sig_pri_span *pri) { int x; char status[256]; for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if (pri->dchans[x]) { #ifdef PRI_DUMP_INFO_STR char *info_str = NULL; #endif ast_cli(fd, "%s D-channel: %d\n", pri_order(x), dchannels[x]); build_status(status, sizeof(status), pri->dchanavail[x], pri->dchans[x] == pri->pri); ast_cli(fd, "Status: %s\n", status); ast_mutex_lock(&pri->lock); #ifdef PRI_DUMP_INFO_STR info_str = pri_dump_info_str(pri->pri); if (info_str) { ast_cli(fd, "%s", info_str); ast_std_free(info_str); } #else pri_dump_info(pri->pri); #endif ast_mutex_unlock(&pri->lock); ast_cli(fd, "Overlap Recv: %s\n\n", (pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)?"Yes":"No"); ast_cli(fd, "\n"); } } } int pri_send_keypad_facility_exec(struct sig_pri_chan *p, const char *digits) { sig_pri_lock_private(p); if (!p->pri || !p->call) { ast_debug(1, "Unable to find pri or call on channel!\n"); sig_pri_unlock_private(p); return -1; } pri_grab(p, p->pri); pri_keypad_facility(p->pri->pri, p->call, digits); pri_rel(p->pri); sig_pri_unlock_private(p); return 0; } int pri_send_callrerouting_facility_exec(struct sig_pri_chan *p, enum ast_channel_state chanstate, const char *destination, const char *original, const char *reason) { int res; sig_pri_lock_private(p); if (!p->pri || !p->call) { ast_debug(1, "Unable to find pri or call on channel!\n"); sig_pri_unlock_private(p); return -1; } pri_grab(p, p->pri); res = pri_callrerouting_facility(p->pri->pri, p->call, destination, original, reason); pri_rel(p->pri); sig_pri_unlock_private(p); return res; } #if defined(HAVE_PRI_SERVICE_MESSAGES) int pri_maintenance_bservice(struct pri *pri, struct sig_pri_chan *p, int changestatus) { int channel = PVT_TO_CHANNEL(p); int span = PRI_SPAN(channel); return pri_maintenance_service(pri, span, channel, changestatus); } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_pri_chan *pchan) { if (pchan->owner == oldchan) { pchan->owner = newchan; } } #if defined(HAVE_PRI_DISPLAY_TEXT) /*! * \brief Send display text. * \since 10.0 * * \param p Channel to send text over * \param text Text to send. * * \return Nothing */ void sig_pri_sendtext(struct sig_pri_chan *p, const char *text) { struct pri_subcmd_display_txt display; if (p->pri && p->pri->pri) { ast_copy_string(display.text, text, sizeof(display.text)); display.length = strlen(display.text); display.char_set = 0;/* unknown(0) */ pri_grab(p, p->pri); pri_display_text(p->pri->pri, p->call, &display); pri_rel(p->pri); } } #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #if defined(HAVE_PRI_CCSS) /*! * \brief PRI CC agent initialization. * \since 1.8 * * \param agent CC core agent control. * \param pvt_chan Original channel the agent will attempt to recall. * * \details * This callback is called when the CC core is initialized. Agents should allocate * any private data necessary for the call and assign it to the private_data * on the agent. Additionally, if any ast_cc_agent_flags are pertinent to the * specific agent type, they should be set in this function as well. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_init(struct ast_cc_agent *agent, struct sig_pri_chan *pvt_chan) { struct sig_pri_cc_agent_prv *cc_pvt; cc_pvt = ast_calloc(1, sizeof(*cc_pvt)); if (!cc_pvt) { return -1; } ast_mutex_lock(&pvt_chan->pri->lock); cc_pvt->pri = pvt_chan->pri; cc_pvt->cc_id = pri_cc_available(pvt_chan->pri->pri, pvt_chan->call); ast_mutex_unlock(&pvt_chan->pri->lock); if (cc_pvt->cc_id == -1) { ast_free(cc_pvt); return -1; } agent->private_data = cc_pvt; return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Start the offer timer. * \since 1.8 * * \param agent CC core agent control. * * \details * This is called by the core when the caller hangs up after * a call for which CC may be requested. The agent should * begin the timer as configured. * * The primary reason why this functionality is left to * the specific agent implementations is due to the differing * use of schedulers throughout the code. Some channel drivers * may already have a scheduler context they wish to use, and * amongst those, some may use the ast_sched API while others * may use the ast_sched_thread API, which are incompatible. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_start_offer_timer(struct ast_cc_agent *agent) { /* libpri maintains it's own offer timer in the form of T_RETENTION. */ return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Stop the offer timer. * \since 1.8 * * \param agent CC core agent control. * * \details * This callback is called by the CC core when the caller * has requested CC. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_stop_offer_timer(struct ast_cc_agent *agent) { /* libpri maintains it's own offer timer in the form of T_RETENTION. */ return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Response to a CC request. * \since 1.8 * * \param agent CC core agent control. * \param reason CC request response status. * * \details * When the core receives knowledge that a called * party has accepted a CC request, it will call * this callback. The core may also call this * if there is some error when attempting to process * the incoming CC request. * * The duty of this is to issue a propper response to a * CC request from the caller by acknowledging receipt * of that request or rejecting it. * * \return Nothing */ void sig_pri_cc_agent_req_rsp(struct ast_cc_agent *agent, enum ast_cc_agent_response_reason reason) { struct sig_pri_cc_agent_prv *cc_pvt; int res; int status; const char *failed_msg; static const char *failed_to_send = "Failed to send the CC request response."; static const char *not_accepted = "The core declined the CC request."; cc_pvt = agent->private_data; ast_mutex_lock(&cc_pvt->pri->lock); if (cc_pvt->cc_request_response_pending) { cc_pvt->cc_request_response_pending = 0; /* Convert core response reason to ISDN response status. */ status = 2;/* short_term_denial */ switch (reason) { case AST_CC_AGENT_RESPONSE_SUCCESS: status = 0;/* success */ break; case AST_CC_AGENT_RESPONSE_FAILURE_INVALID: status = 2;/* short_term_denial */ break; case AST_CC_AGENT_RESPONSE_FAILURE_TOO_MANY: status = 5;/* queue_full */ break; } res = pri_cc_req_rsp(cc_pvt->pri->pri, cc_pvt->cc_id, status); if (!status) { /* CC core request was accepted. */ if (res) { failed_msg = failed_to_send; } else { failed_msg = NULL; } } else { /* CC core request was declined. */ if (res) { failed_msg = failed_to_send; } else { failed_msg = not_accepted; } } } else { failed_msg = NULL; } ast_mutex_unlock(&cc_pvt->pri->lock); if (failed_msg) { ast_cc_failed(agent->core_id, "%s agent: %s", sig_pri_cc_type_name, failed_msg); } } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Request the status of the agent's device. * \since 1.8 * * \param agent CC core agent control. * * \details * Asynchronous request for the status of any caller * which may be a valid caller for the CC transaction. * Status responses should be made using the * ast_cc_status_response function. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_status_req(struct ast_cc_agent *agent) { struct sig_pri_cc_agent_prv *cc_pvt; cc_pvt = agent->private_data; ast_mutex_lock(&cc_pvt->pri->lock); pri_cc_status_req(cc_pvt->pri->pri, cc_pvt->cc_id); ast_mutex_unlock(&cc_pvt->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Request for an agent's phone to stop ringing. * \since 1.8 * * \param agent CC core agent control. * * \details * The usefulness of this is quite limited. The only specific * known case for this is if Asterisk requests CC over an ISDN * PTMP link as the TE side. If other phones are in the same * recall group as the Asterisk server, and one of those phones * picks up the recall notice, then Asterisk will receive a * "stop ringing" notification from the NT side of the PTMP * link. This indication needs to be passed to the phone * on the other side of the Asterisk server which originally * placed the call so that it will stop ringing. Since the * phone may be of any type, it is necessary to have a callback * that the core can know about. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_stop_ringing(struct ast_cc_agent *agent) { struct sig_pri_cc_agent_prv *cc_pvt; cc_pvt = agent->private_data; ast_mutex_lock(&cc_pvt->pri->lock); pri_cc_stop_alerting(cc_pvt->pri->pri, cc_pvt->cc_id); ast_mutex_unlock(&cc_pvt->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Let the caller know that the callee has become free * but that the caller cannot attempt to call back because * he is either busy or there is congestion on his line. * \since 1.8 * * \param agent CC core agent control. * * \details * This is something that really only affects a scenario where * a phone places a call over ISDN PTMP to Asterisk, who then * connects over PTMP again to the ISDN network. For most agent * types, there is no need to implement this callback at all * because they don't really need to actually do anything in * this situation. If you're having trouble understanding what * the purpose of this callback is, then you can be safe simply * not implementing it. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_party_b_free(struct ast_cc_agent *agent) { struct sig_pri_cc_agent_prv *cc_pvt; cc_pvt = agent->private_data; ast_mutex_lock(&cc_pvt->pri->lock); pri_cc_b_free(cc_pvt->pri->pri, cc_pvt->cc_id); ast_mutex_unlock(&cc_pvt->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Begin monitoring a busy device. * \since 1.8 * * \param agent CC core agent control. * * \details * The core will call this callback if the callee becomes * available but the caller has reported that he is busy. * The agent should begin monitoring the caller's device. * When the caller becomes available again, the agent should * call ast_cc_agent_caller_available. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_start_monitoring(struct ast_cc_agent *agent) { /* libpri already knows when and how it needs to monitor Party A. */ return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Alert the caller that it is time to try recalling. * \since 1.8 * * \param agent CC core agent control. * * \details * The core will call this function when it receives notice * that a monitored party has become available. * * The agent's job is to send a message to the caller to * notify it of such a change. If the agent is able to * discern that the caller is currently unavailable, then * the agent should react by calling the ast_cc_caller_unavailable * function. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_cc_agent_callee_available(struct ast_cc_agent *agent) { struct sig_pri_cc_agent_prv *cc_pvt; cc_pvt = agent->private_data; ast_mutex_lock(&cc_pvt->pri->lock); pri_cc_remote_user_free(cc_pvt->pri->pri, cc_pvt->cc_id); ast_mutex_unlock(&cc_pvt->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Destroy private data on the agent. * \since 1.8 * * \param agent CC core agent control. * * \details * The core will call this function upon completion * or failure of CC. * * \note * The agent private_data pointer may be NULL if the agent * constructor failed. * * \return Nothing */ void sig_pri_cc_agent_destructor(struct ast_cc_agent *agent) { struct sig_pri_cc_agent_prv *cc_pvt; int res; cc_pvt = agent->private_data; if (!cc_pvt) { /* The agent constructor probably failed. */ return; } ast_mutex_lock(&cc_pvt->pri->lock); res = -1; if (cc_pvt->cc_request_response_pending) { res = pri_cc_req_rsp(cc_pvt->pri->pri, cc_pvt->cc_id, 2/* short_term_denial */); } if (res) { pri_cc_cancel(cc_pvt->pri->pri, cc_pvt->cc_id); } ast_mutex_unlock(&cc_pvt->pri->lock); ast_free(cc_pvt); } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Return the hash value of the given CC monitor instance object. * \since 1.8 * * \param obj pointer to the (user-defined part) of an object. * \param flags flags from ao2_callback(). Ignored at the moment. * * \retval core_id */ static int sig_pri_cc_monitor_instance_hash_fn(const void *obj, const int flags) { const struct sig_pri_cc_monitor_instance *monitor_instance = obj; return monitor_instance->core_id; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Compere the monitor instance core_id key value. * \since 1.8 * * \param obj pointer to the (user-defined part) of an object. * \param arg callback argument from ao2_callback() * \param flags flags from ao2_callback() * * \return values are a combination of enum _cb_results. */ static int sig_pri_cc_monitor_instance_cmp_fn(void *obj, void *arg, int flags) { struct sig_pri_cc_monitor_instance *monitor_1 = obj; struct sig_pri_cc_monitor_instance *monitor_2 = arg; return monitor_1->core_id == monitor_2->core_id ? CMP_MATCH | CMP_STOP : 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Request CCSS. * \since 1.8 * * \param monitor CC core monitor control. * \param available_timer_id Where to put the available timer scheduler id. * Will never be NULL for a device monitor. * * \details * Perform whatever steps are necessary in order to request CC. * In addition, the monitor implementation is responsible for * starting the available timer in this callback. The scheduler * ID for the callback must be stored in the parent_link's child_avail_id * field. * * \retval 0 on success * \retval -1 on failure. */ int sig_pri_cc_monitor_req_cc(struct ast_cc_monitor *monitor, int *available_timer_id) { struct sig_pri_cc_monitor_instance *instance; int cc_mode; int res; switch (monitor->service_offered) { case AST_CC_CCBS: cc_mode = 0;/* CCBS */ break; case AST_CC_CCNR: cc_mode = 1;/* CCNR */ break; default: /* CC service not supported by ISDN. */ return -1; } instance = monitor->private_data; /* libpri handles it's own available timer. */ ast_mutex_lock(&instance->pri->lock); res = pri_cc_req(instance->pri->pri, instance->cc_id, cc_mode); ast_mutex_unlock(&instance->pri->lock); return res; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Suspend monitoring. * \since 1.8 * * \param monitor CC core monitor control. * * \details * Implementers must perform the necessary steps to suspend * monitoring. * * \retval 0 on success * \retval -1 on failure. */ int sig_pri_cc_monitor_suspend(struct ast_cc_monitor *monitor) { struct sig_pri_cc_monitor_instance *instance; instance = monitor->private_data; ast_mutex_lock(&instance->pri->lock); pri_cc_status(instance->pri->pri, instance->cc_id, 1/* busy */); ast_mutex_unlock(&instance->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Unsuspend monitoring. * \since 1.8 * * \param monitor CC core monitor control. * * \details * Perform the necessary steps to unsuspend monitoring. * * \retval 0 on success * \retval -1 on failure. */ int sig_pri_cc_monitor_unsuspend(struct ast_cc_monitor *monitor) { struct sig_pri_cc_monitor_instance *instance; instance = monitor->private_data; ast_mutex_lock(&instance->pri->lock); pri_cc_status(instance->pri->pri, instance->cc_id, 0/* free */); ast_mutex_unlock(&instance->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Status response to an ast_cc_monitor_status_request(). * \since 1.8 * * \param monitor CC core monitor control. * \param devstate Current status of a Party A device. * * \details * Alert a monitor as to the status of the agent for which * the monitor had previously requested a status request. * * \note Zero or more responses may come as a result. * * \retval 0 on success * \retval -1 on failure. */ int sig_pri_cc_monitor_status_rsp(struct ast_cc_monitor *monitor, enum ast_device_state devstate) { struct sig_pri_cc_monitor_instance *instance; int cc_status; switch (devstate) { case AST_DEVICE_UNKNOWN: case AST_DEVICE_NOT_INUSE: cc_status = 0;/* free */ break; case AST_DEVICE_BUSY: case AST_DEVICE_INUSE: cc_status = 1;/* busy */ break; default: /* Don't know how to interpret this device state into free/busy status. */ return 0; } instance = monitor->private_data; ast_mutex_lock(&instance->pri->lock); pri_cc_status_req_rsp(instance->pri->pri, instance->cc_id, cc_status); ast_mutex_unlock(&instance->pri->lock); return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Cancel the running available timer. * \since 1.8 * * \param monitor CC core monitor control. * \param sched_id Available timer scheduler id to cancel. * Will never be NULL for a device monitor. * * \details * In most cases, this function will likely consist of just a * call to AST_SCHED_DEL. It might have been possible to do this * within the core, but unfortunately the mixture of sched_thread * and sched usage in Asterisk prevents such usage. * * \retval 0 on success * \retval -1 on failure. */ int sig_pri_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id) { /* * libpri maintains it's own available timer as one of: * T_CCBS2/T_CCBS5/T_CCBS6/QSIG_CCBS_T2 * T_CCNR2/T_CCNR5/T_CCNR6/QSIG_CCNR_T2 */ return 0; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CCSS) /*! * \brief Destroy PRI private data on the monitor. * \since 1.8 * * \param monitor_pvt CC device monitor private data pointer. * * \details * Implementers of this callback are responsible for destroying * all heap-allocated data in the monitor's private_data pointer, including * the private_data itself. */ void sig_pri_cc_monitor_destructor(void *monitor_pvt) { struct sig_pri_cc_monitor_instance *instance; instance = monitor_pvt; if (!instance) { return; } ao2_unlink(sig_pri_cc_monitors, instance); ao2_ref(instance, -1); } #endif /* defined(HAVE_PRI_CCSS) */ /*! * \brief Load the sig_pri submodule. * \since 1.8 * * \param cc_type_name CC type name to use when looking up agent/monitor. * * \retval 0 on success. * \retval -1 on error. */ int sig_pri_load(const char *cc_type_name) { #if defined(HAVE_PRI_MCID) if (STASIS_MESSAGE_TYPE_INIT(mcid_type)) { return -1; } #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_CCSS) sig_pri_cc_type_name = cc_type_name; sig_pri_cc_monitors = ao2_container_alloc(37, sig_pri_cc_monitor_instance_hash_fn, sig_pri_cc_monitor_instance_cmp_fn); if (!sig_pri_cc_monitors) { return -1; } #endif /* defined(HAVE_PRI_CCSS) */ return 0; } /*! * \brief Unload the sig_pri submodule. * \since 1.8 * * \return Nothing */ void sig_pri_unload(void) { #if defined(HAVE_PRI_CCSS) if (sig_pri_cc_monitors) { ao2_ref(sig_pri_cc_monitors, -1); sig_pri_cc_monitors = NULL; } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_MCID) STASIS_MESSAGE_TYPE_CLEANUP(mcid_type); #endif /* defined(HAVE_PRI_MCID) */ } #endif /* HAVE_PRI */ asterisk-13.1.0/channels/chan_phone.c0000644000000000000000000013314612424141152016164 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2005, 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. */ /*! \file * * \brief Generic Linux Telephony Interface driver * * \author Mark Spencer * * \ingroup channel_drivers */ /*! \li \ref chan_phone.c uses the configuration file \ref phone.conf * \addtogroup configuration_file */ /*! \page phone.conf phone.conf * \verbinclude phone.conf.sample */ /*** MODULEINFO ixjuser extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 426570 $") #include #include #include #include #include #include #include #ifdef HAVE_LINUX_COMPILER_H #include #endif #include /* Still use some IXJ specific stuff */ #include #include #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/utils.h" #include "asterisk/callerid.h" #include "asterisk/causes.h" #include "asterisk/stringfields.h" #include "asterisk/musiconhold.h" #include "asterisk/format_cache.h" #include "asterisk/format_compatibility.h" #include "chan_phone.h" #ifdef QTI_PHONEJACK_TJ_PCI /* check for the newer quicknet driver v.3.1.0 which has this symbol */ #define QNDRV_VER 310 #else #define QNDRV_VER 100 #endif #if QNDRV_VER > 100 #ifdef __linux__ #define IXJ_PHONE_RING_START(x) ioctl(p->fd, PHONE_RING_START, &x); #else /* FreeBSD and others */ #define IXJ_PHONE_RING_START(x) ioctl(p->fd, PHONE_RING_START, x); #endif /* __linux__ */ #else /* older driver */ #define IXJ_PHONE_RING_START(x) ioctl(p->fd, PHONE_RING_START, &x); #endif #define DEFAULT_CALLER_ID "Unknown" #define PHONE_MAX_BUF 480 #define DEFAULT_GAIN 0x100 static const char tdesc[] = "Standard Linux Telephony API Driver"; static const char config[] = "phone.conf"; /* Default context for dialtone mode */ static char context[AST_MAX_EXTENSION] = "default"; /* Default language */ static char language[MAX_LANGUAGE] = ""; static int echocancel = AEC_OFF; static int silencesupression = 0; static struct ast_format_cap *prefcap; /* Protect the interface list (of phone_pvt's) */ AST_MUTEX_DEFINE_STATIC(iflock); /* 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(monlock); /* Boolean value whether the monitoring thread shall continue. */ static unsigned int monitor; /* This is the thread for the monitor which checks for input on the channels which are not currently in use. */ static pthread_t monitor_thread = AST_PTHREADT_NULL; static int restart_monitor(void); /* The private structures of the Phone Jack channels are linked for selecting outgoing channels */ #define MODE_DIALTONE 1 #define MODE_IMMEDIATE 2 #define MODE_FXO 3 #define MODE_FXS 4 #define MODE_SIGMA 5 static struct phone_pvt { int fd; /* Raw file descriptor for this device */ struct ast_channel *owner; /* Channel we belong to, possibly NULL */ int mode; /* Is this in the */ struct ast_format *lastformat; /* Last output format */ struct ast_format *lastinput; /* Last input format */ int ministate; /* Miniature state, for dialtone mode */ char dev[256]; /* Device name */ struct phone_pvt *next; /* Next channel in list */ struct ast_frame fr; /* Frame */ char offset[AST_FRIENDLY_OFFSET]; char buf[PHONE_MAX_BUF]; /* Static buffer for reading frames */ int obuflen; int dialtone; int txgain, rxgain; /* gain control for playing, recording */ /* 0x100 - 1.0, 0x200 - 2.0, 0x80 - 0.5 */ int cpt; /* Call Progress Tone playing? */ int silencesupression; char context[AST_MAX_EXTENSION]; char obuf[PHONE_MAX_BUF * 2]; char ext[AST_MAX_EXTENSION]; char language[MAX_LANGUAGE]; char cid_num[AST_MAX_EXTENSION]; char cid_name[AST_MAX_EXTENSION]; } *iflist = NULL; static char cid_num[AST_MAX_EXTENSION]; static char cid_name[AST_MAX_EXTENSION]; static struct ast_channel *phone_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int phone_digit_begin(struct ast_channel *ast, char digit); static int phone_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int phone_call(struct ast_channel *ast, const char *dest, int timeout); static int phone_hangup(struct ast_channel *ast); static int phone_answer(struct ast_channel *ast); static struct ast_frame *phone_read(struct ast_channel *ast); static int phone_write(struct ast_channel *ast, struct ast_frame *frame); static struct ast_frame *phone_exception(struct ast_channel *ast); static int phone_send_text(struct ast_channel *ast, const char *text); static int phone_fixup(struct ast_channel *old, struct ast_channel *new); static int phone_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen); static struct ast_channel_tech phone_tech = { .type = "Phone", .description = tdesc, .requester = phone_request, .send_digit_begin = phone_digit_begin, .send_digit_end = phone_digit_end, .call = phone_call, .hangup = phone_hangup, .answer = phone_answer, .read = phone_read, .write = phone_write, .exception = phone_exception, .indicate = phone_indicate, .fixup = phone_fixup }; static struct ast_channel_tech phone_tech_fxs = { .type = "Phone", .description = tdesc, .requester = phone_request, .send_digit_begin = phone_digit_begin, .send_digit_end = phone_digit_end, .call = phone_call, .hangup = phone_hangup, .answer = phone_answer, .read = phone_read, .write = phone_write, .exception = phone_exception, .write_video = phone_write, .send_text = phone_send_text, .indicate = phone_indicate, .fixup = phone_fixup }; static struct ast_channel_tech *cur_tech; static int phone_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen) { struct phone_pvt *p = ast_channel_tech_pvt(chan); int res=-1; ast_debug(1, "Requested indication %d on channel %s\n", condition, ast_channel_name(chan)); switch(condition) { case AST_CONTROL_FLASH: ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_ON_HOOK); usleep(320000); ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_OFF_HOOK); ao2_cleanup(p->lastformat); p->lastformat = NULL; res = 0; break; case AST_CONTROL_HOLD: ast_moh_start(chan, data, NULL); break; case AST_CONTROL_UNHOLD: ast_moh_stop(chan); break; case AST_CONTROL_SRCUPDATE: res = 0; break; case AST_CONTROL_PVT_CAUSE_CODE: break; default: ast_log(LOG_WARNING, "Condition %d is not supported on channel %s\n", condition, ast_channel_name(chan)); } return res; } static int phone_fixup(struct ast_channel *old, struct ast_channel *new) { struct phone_pvt *pvt = ast_channel_tech_pvt(old); if (pvt && pvt->owner == old) pvt->owner = new; return 0; } static int phone_digit_begin(struct ast_channel *chan, char digit) { /* XXX Modify this callback to let Asterisk support controlling the length of DTMF */ return 0; } static int phone_digit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct phone_pvt *p; int outdigit; p = ast_channel_tech_pvt(ast); ast_debug(1, "Dialed %c\n", digit); switch(digit) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': outdigit = digit - '0'; break; case '*': outdigit = 11; break; case '#': outdigit = 12; break; case 'f': /*flash*/ case 'F': ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_ON_HOOK); usleep(320000); ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_OFF_HOOK); ao2_cleanup(p->lastformat); p->lastformat = NULL; return 0; default: ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit); return -1; } ast_debug(1, "Dialed %d\n", outdigit); ioctl(p->fd, PHONE_PLAY_TONE, outdigit); ao2_cleanup(p->lastformat); p->lastformat = NULL; return 0; } static int phone_call(struct ast_channel *ast, const char *dest, int timeout) { struct phone_pvt *p; PHONE_CID cid; struct timeval UtcTime = ast_tvnow(); struct ast_tm tm; int start; ast_localtime(&UtcTime, &tm, NULL); memset(&cid, 0, sizeof(PHONE_CID)); snprintf(cid.month, sizeof(cid.month), "%02d",(tm.tm_mon + 1)); snprintf(cid.day, sizeof(cid.day), "%02d", tm.tm_mday); snprintf(cid.hour, sizeof(cid.hour), "%02d", tm.tm_hour); snprintf(cid.min, sizeof(cid.min), "%02d", tm.tm_min); /* the standard format of ast->callerid is: "name" , but not always complete */ if (!ast_channel_connected(ast)->id.name.valid || ast_strlen_zero(ast_channel_connected(ast)->id.name.str)) { strcpy(cid.name, DEFAULT_CALLER_ID); } else { ast_copy_string(cid.name, ast_channel_connected(ast)->id.name.str, sizeof(cid.name)); } if (ast_channel_connected(ast)->id.number.valid && ast_channel_connected(ast)->id.number.str) { ast_copy_string(cid.number, ast_channel_connected(ast)->id.number.str, sizeof(cid.number)); } p = ast_channel_tech_pvt(ast); if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "phone_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } ast_debug(1, "Ringing %s on %s (%d)\n", dest, ast_channel_name(ast), ast_channel_fd(ast, 0)); start = IXJ_PHONE_RING_START(cid); if (start == -1) return -1; if (p->mode == MODE_FXS) { const char *digit = strchr(dest, '/'); if (digit) { digit++; while (*digit) phone_digit_end(ast, *digit++, 0); } } ast_setstate(ast, AST_STATE_RINGING); ast_queue_control(ast, AST_CONTROL_RINGING); return 0; } static int phone_hangup(struct ast_channel *ast) { struct phone_pvt *p; p = ast_channel_tech_pvt(ast); ast_debug(1, "phone_hangup(%s)\n", ast_channel_name(ast)); if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } /* XXX Is there anything we can do to really hang up except stop recording? */ ast_setstate(ast, AST_STATE_DOWN); if (ioctl(p->fd, PHONE_REC_STOP)) ast_log(LOG_WARNING, "Failed to stop recording\n"); if (ioctl(p->fd, PHONE_PLAY_STOP)) ast_log(LOG_WARNING, "Failed to stop playing\n"); if (ioctl(p->fd, PHONE_RING_STOP)) ast_log(LOG_WARNING, "Failed to stop ringing\n"); if (ioctl(p->fd, PHONE_CPT_STOP)) ast_log(LOG_WARNING, "Failed to stop sounds\n"); /* If it's an FXO, hang them up */ if (p->mode == MODE_FXO) { if (ioctl(p->fd, PHONE_PSTN_SET_STATE, PSTN_ON_HOOK)) ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n",ast_channel_name(ast), strerror(errno)); } /* If they're off hook, give a busy signal */ if (ioctl(p->fd, PHONE_HOOKSTATE)) { ast_debug(1, "Got hunghup, giving busy signal\n"); ioctl(p->fd, PHONE_BUSY); p->cpt = 1; } ao2_cleanup(p->lastformat); p->lastformat = NULL; ao2_cleanup(p->lastinput); p->lastinput = NULL; p->ministate = 0; p->obuflen = 0; p->dialtone = 0; memset(p->ext, 0, sizeof(p->ext)); ((struct phone_pvt *)(ast_channel_tech_pvt(ast)))->owner = NULL; ast_module_unref(ast_module_info->self); ast_verb(3, "Hungup '%s'\n", ast_channel_name(ast)); ast_channel_tech_pvt_set(ast, NULL); ast_setstate(ast, AST_STATE_DOWN); restart_monitor(); return 0; } static int phone_setup(struct ast_channel *ast) { struct phone_pvt *p; p = ast_channel_tech_pvt(ast); ioctl(p->fd, PHONE_CPT_STOP); /* Nothing to answering really, just start recording */ if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_g729) == AST_FORMAT_CMP_EQUAL) { /* Prefer g729 */ ioctl(p->fd, PHONE_REC_STOP); if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_g729) != AST_FORMAT_CMP_EQUAL)) { ao2_replace(p->lastinput, ast_format_g729); if (ioctl(p->fd, PHONE_REC_CODEC, G729)) { ast_log(LOG_WARNING, "Failed to set codec to g729\n"); return -1; } } } else if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_g723) == AST_FORMAT_CMP_EQUAL) { ioctl(p->fd, PHONE_REC_STOP); if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_g723) != AST_FORMAT_CMP_EQUAL)) { ao2_replace(p->lastinput, ast_format_g723); if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) { ast_log(LOG_WARNING, "Failed to set codec to g723.1\n"); return -1; } } } else if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_slin) == AST_FORMAT_CMP_EQUAL) { ioctl(p->fd, PHONE_REC_STOP); if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_slin) != AST_FORMAT_CMP_EQUAL)) { ao2_replace(p->lastinput, ast_format_slin); if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) { ast_log(LOG_WARNING, "Failed to set codec to signed linear 16\n"); return -1; } } } else if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { ioctl(p->fd, PHONE_REC_STOP); if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_ulaw) != AST_FORMAT_CMP_EQUAL)) { ao2_replace(p->lastinput, ast_format_ulaw); if (ioctl(p->fd, PHONE_REC_CODEC, ULAW)) { ast_log(LOG_WARNING, "Failed to set codec to uLaw\n"); return -1; } } } else if (p->mode == MODE_FXS) { ioctl(p->fd, PHONE_REC_STOP); if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_channel_rawreadformat(ast)) == AST_FORMAT_CMP_NOT_EQUAL)) { ao2_replace(p->lastinput, ast_channel_rawreadformat(ast)); if (ioctl(p->fd, PHONE_REC_CODEC, ast_channel_rawreadformat(ast))) { ast_log(LOG_WARNING, "Failed to set codec to %s\n", ast_format_get_name(ast_channel_rawreadformat(ast))); return -1; } } } else { ast_log(LOG_WARNING, "Can't do format %s\n", ast_format_get_name(ast_channel_rawreadformat(ast))); return -1; } if (ioctl(p->fd, PHONE_REC_START)) { ast_log(LOG_WARNING, "Failed to start recording\n"); return -1; } /* set the DTMF times (the default is too short) */ ioctl(p->fd, PHONE_SET_TONE_ON_TIME, 300); ioctl(p->fd, PHONE_SET_TONE_OFF_TIME, 200); return 0; } static int phone_answer(struct ast_channel *ast) { struct phone_pvt *p; p = ast_channel_tech_pvt(ast); /* In case it's a LineJack, take it off hook */ if (p->mode == MODE_FXO) { if (ioctl(p->fd, PHONE_PSTN_SET_STATE, PSTN_OFF_HOOK)) ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n", ast_channel_name(ast), strerror(errno)); else ast_debug(1, "Took linejack off hook\n"); } phone_setup(ast); ast_debug(1, "phone_answer(%s)\n", ast_channel_name(ast)); ast_channel_rings_set(ast, 0); ast_setstate(ast, AST_STATE_UP); return 0; } #if 0 static char phone_2digit(char c) { if (c == 12) return '#'; else if (c == 11) return '*'; else if ((c < 10) && (c >= 0)) return '0' + c - 1; else return '?'; } #endif static struct ast_frame *phone_exception(struct ast_channel *ast) { int res; union telephony_exception phonee; struct phone_pvt *p = ast_channel_tech_pvt(ast); char digit; /* Some nice norms */ p->fr.datalen = 0; p->fr.samples = 0; p->fr.data.ptr = NULL; p->fr.src = "Phone"; p->fr.offset = 0; p->fr.mallocd=0; p->fr.delivery = ast_tv(0,0); phonee.bytes = ioctl(p->fd, PHONE_EXCEPTION); if (phonee.bits.dtmf_ready) { ast_debug(1, "phone_exception(): DTMF\n"); /* We've got a digit -- Just handle this nicely and easily */ digit = ioctl(p->fd, PHONE_GET_DTMF_ASCII); p->fr.subclass.integer = digit; p->fr.frametype = AST_FRAME_DTMF; return &p->fr; } if (phonee.bits.hookstate) { ast_debug(1, "Hookstate changed\n"); res = ioctl(p->fd, PHONE_HOOKSTATE); /* See if we've gone on hook, if so, notify by returning NULL */ ast_debug(1, "New hookstate: %d\n", res); if (!res && (p->mode != MODE_FXO)) return NULL; else { if (ast_channel_state(ast) == AST_STATE_RINGING) { /* They've picked up the phone */ p->fr.frametype = AST_FRAME_CONTROL; p->fr.subclass.integer = AST_CONTROL_ANSWER; phone_setup(ast); ast_setstate(ast, AST_STATE_UP); return &p->fr; } else ast_log(LOG_WARNING, "Got off hook in weird state %u\n", ast_channel_state(ast)); } } #if 1 if (phonee.bits.pstn_ring) ast_verbose("Unit is ringing\n"); if (phonee.bits.caller_id) { ast_verbose("We have caller ID\n"); } if (phonee.bits.pstn_wink) ast_verbose("Detected Wink\n"); #endif /* Strange -- nothing there.. */ p->fr.frametype = AST_FRAME_NULL; p->fr.subclass.integer = 0; return &p->fr; } static struct ast_frame *phone_read(struct ast_channel *ast) { int res; struct phone_pvt *p = ast_channel_tech_pvt(ast); /* Some nice norms */ p->fr.datalen = 0; p->fr.samples = 0; p->fr.data.ptr = NULL; p->fr.src = "Phone"; p->fr.offset = 0; p->fr.mallocd=0; p->fr.delivery = ast_tv(0,0); /* Try to read some data... */ CHECK_BLOCKING(ast); res = read(p->fd, p->buf, PHONE_MAX_BUF); ast_clear_flag(ast_channel_flags(ast), AST_FLAG_BLOCKING); if (res < 0) { #if 0 if (errno == EAGAIN) { ast_log(LOG_WARNING, "Null frame received\n"); p->fr.frametype = AST_FRAME_NULL; p->fr.subclass = 0; return &p->fr; } #endif ast_log(LOG_WARNING, "Error reading: %s\n", strerror(errno)); return NULL; } p->fr.data.ptr = p->buf; if (p->mode != MODE_FXS) switch(p->buf[0] & 0x3) { case '0': case '1': /* Normal */ break; case '2': case '3': /* VAD/CNG, only send two words */ res = 4; break; } p->fr.samples = 240; p->fr.datalen = res; p->fr.frametype = ast_format_get_type(p->lastinput) == AST_MEDIA_TYPE_AUDIO ? AST_FRAME_VOICE : ast_format_get_type(p->lastinput) == AST_MEDIA_TYPE_IMAGE ? AST_FRAME_IMAGE : AST_FRAME_VIDEO; p->fr.subclass.format = p->lastinput; p->fr.offset = AST_FRIENDLY_OFFSET; /* Byteswap from little-endian to native-endian */ if (ast_format_cmp(p->fr.subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) ast_frame_byteswap_le(&p->fr); return &p->fr; } static int phone_write_buf(struct phone_pvt *p, const char *buf, int len, int frlen, int swap) { int res; /* Store as much of the buffer as we can, then write fixed frames */ int space = sizeof(p->obuf) - p->obuflen; /* Make sure we have enough buffer space to store the frame */ if (space < len) len = space; if (swap) ast_swapcopy_samples(p->obuf+p->obuflen, buf, len/2); else memcpy(p->obuf + p->obuflen, buf, len); p->obuflen += len; while(p->obuflen > frlen) { res = write(p->fd, p->obuf, frlen); if (res != frlen) { if (res < 1) { /* * Card is in non-blocking mode now and it works well now, but there are * lot of messages like this. So, this message is temporarily disabled. */ return 0; } else { ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frlen); } } p->obuflen -= frlen; /* Move memory if necessary */ if (p->obuflen) memmove(p->obuf, p->obuf + frlen, p->obuflen); } return len; } static int phone_send_text(struct ast_channel *ast, const char *text) { int length = strlen(text); return phone_write_buf(ast_channel_tech_pvt(ast), text, length, length, 0) == length ? 0 : -1; } static int phone_write(struct ast_channel *ast, struct ast_frame *frame) { struct phone_pvt *p = ast_channel_tech_pvt(ast); int res; int maxfr=0; char *pos; int sofar; int expected; int codecset = 0; char tmpbuf[4]; /* Write a frame of (presumably voice) data */ if (frame->frametype != AST_FRAME_VOICE && p->mode != MODE_FXS) { if (frame->frametype != AST_FRAME_IMAGE) ast_log(LOG_WARNING, "Don't know what to do with frame type '%u'\n", frame->frametype); return 0; } #if 0 /* If we're not in up mode, go into up mode now */ if (ast->_state != AST_STATE_UP) { ast_setstate(ast, AST_STATE_UP); phone_setup(ast); } #else if (ast_channel_state(ast) != AST_STATE_UP) { /* Don't try tos end audio on-hook */ return 0; } #endif if (ast_format_cmp(frame->subclass.format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_g729) != AST_FORMAT_CMP_EQUAL)) { ioctl(p->fd, PHONE_PLAY_STOP); ioctl(p->fd, PHONE_REC_STOP); if (ioctl(p->fd, PHONE_PLAY_CODEC, G729)) { ast_log(LOG_WARNING, "Unable to set G729 mode\n"); return -1; } if (ioctl(p->fd, PHONE_REC_CODEC, G729)) { ast_log(LOG_WARNING, "Unable to set G729 mode\n"); return -1; } ao2_replace(p->lastformat, ast_format_g729); ao2_replace(p->lastinput, ast_format_g729); /* Reset output buffer */ p->obuflen = 0; codecset = 1; } if (frame->datalen > 80) { ast_log(LOG_WARNING, "Frame size too large for G.729 (%d bytes)\n", frame->datalen); return -1; } maxfr = 80; } else if (ast_format_cmp(frame->subclass.format, ast_format_g723) == AST_FORMAT_CMP_EQUAL) { if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_g723) != AST_FORMAT_CMP_EQUAL)) { ioctl(p->fd, PHONE_PLAY_STOP); ioctl(p->fd, PHONE_REC_STOP); if (ioctl(p->fd, PHONE_PLAY_CODEC, G723_63)) { ast_log(LOG_WARNING, "Unable to set G723.1 mode\n"); return -1; } if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) { ast_log(LOG_WARNING, "Unable to set G723.1 mode\n"); return -1; } ao2_replace(p->lastformat, ast_format_g723); ao2_replace(p->lastinput, ast_format_g723); /* Reset output buffer */ p->obuflen = 0; codecset = 1; } if (frame->datalen > 24) { ast_log(LOG_WARNING, "Frame size too large for G.723.1 (%d bytes)\n", frame->datalen); return -1; } maxfr = 24; } else if (ast_format_cmp(frame->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) { if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_slin) != AST_FORMAT_CMP_EQUAL)) { ioctl(p->fd, PHONE_PLAY_STOP); ioctl(p->fd, PHONE_REC_STOP); if (ioctl(p->fd, PHONE_PLAY_CODEC, LINEAR16)) { ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n"); return -1; } if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) { ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n"); return -1; } ao2_replace(p->lastformat, ast_format_slin); ao2_replace(p->lastinput, ast_format_slin); codecset = 1; /* Reset output buffer */ p->obuflen = 0; } maxfr = 480; } else if (ast_format_cmp(frame->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_ulaw) != AST_FORMAT_CMP_EQUAL)) { ioctl(p->fd, PHONE_PLAY_STOP); ioctl(p->fd, PHONE_REC_STOP); if (ioctl(p->fd, PHONE_PLAY_CODEC, ULAW)) { ast_log(LOG_WARNING, "Unable to set uLaw mode\n"); return -1; } if (ioctl(p->fd, PHONE_REC_CODEC, ULAW)) { ast_log(LOG_WARNING, "Unable to set uLaw mode\n"); return -1; } ao2_replace(p->lastformat, ast_format_ulaw); ao2_replace(p->lastinput, ast_format_ulaw); codecset = 1; /* Reset output buffer */ p->obuflen = 0; } maxfr = 240; } else { if (!p->lastformat || (ast_format_cmp(p->lastformat, frame->subclass.format) != AST_FORMAT_CMP_EQUAL)) { ioctl(p->fd, PHONE_PLAY_STOP); ioctl(p->fd, PHONE_REC_STOP); if (ioctl(p->fd, PHONE_PLAY_CODEC, ast_format_compatibility_format2bitfield(frame->subclass.format))) { ast_log(LOG_WARNING, "Unable to set %s mode\n", ast_format_get_name(frame->subclass.format)); return -1; } if (ioctl(p->fd, PHONE_REC_CODEC, ast_format_compatibility_format2bitfield(frame->subclass.format))) { ast_log(LOG_WARNING, "Unable to set %s mode\n", ast_format_get_name(frame->subclass.format)); return -1; } ao2_replace(p->lastformat, frame->subclass.format); ao2_replace(p->lastinput, frame->subclass.format); codecset = 1; /* Reset output buffer */ p->obuflen = 0; } maxfr = 480; } if (codecset) { ioctl(p->fd, PHONE_REC_DEPTH, 3); ioctl(p->fd, PHONE_PLAY_DEPTH, 3); if (ioctl(p->fd, PHONE_PLAY_START)) { ast_log(LOG_WARNING, "Failed to start playback\n"); return -1; } if (ioctl(p->fd, PHONE_REC_START)) { ast_log(LOG_WARNING, "Failed to start recording\n"); return -1; } } /* If we get here, we have a frame of Appropriate data */ sofar = 0; pos = frame->data.ptr; while(sofar < frame->datalen) { /* Write in no more than maxfr sized frames */ expected = frame->datalen - sofar; if (maxfr < expected) expected = maxfr; /* XXX Internet Phone Jack does not handle the 4-byte VAD frame properly! XXX we have to pad it to 24 bytes still. */ if (frame->datalen == 4) { if (p->silencesupression) { memcpy(tmpbuf, frame->data.ptr, 4); expected = 24; res = phone_write_buf(p, tmpbuf, expected, maxfr, 0); } res = 4; expected=4; } else { int swap = 0; #if __BYTE_ORDER == __BIG_ENDIAN if (ast_format_cmp(frame->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) swap = 1; /* Swap big-endian samples to little-endian as we copy */ #endif res = phone_write_buf(p, pos, expected, maxfr, swap); } if (res != expected) { if ((errno != EAGAIN) && (errno != EINTR)) { if (res < 0) ast_log(LOG_WARNING, "Write returned error (%s)\n", strerror(errno)); /* * Card is in non-blocking mode now and it works well now, but there are * lot of messages like this. So, this message is temporarily disabled. */ #if 0 else ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frame->datalen); #endif return -1; } else /* Pretend it worked */ res = expected; } sofar += res; pos += res; } return 0; } static struct ast_channel *phone_new(struct phone_pvt *i, int state, char *cntx, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_format_cap *caps = NULL; struct ast_channel *tmp; struct phone_codec_data queried_codec; struct ast_format *tmpfmt; caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, "", i->ext, i->context, assignedids, requestor, 0, "Phone/%s", i->dev + 5); if (tmp && caps) { ast_channel_lock(tmp); ast_channel_tech_set(tmp, cur_tech); ast_channel_set_fd(tmp, 0, i->fd); /* XXX Switching formats silently causes kernel panics XXX */ if (i->mode == MODE_FXS && ioctl(i->fd, PHONE_QUERY_CODEC, &queried_codec) == 0) { if (queried_codec.type == LINEAR16) { ast_format_cap_append(caps, ast_format_slin, 0); } else { ast_format_cap_remove(prefcap, ast_format_slin); ast_format_cap_append_from_cap(caps, prefcap, AST_MEDIA_TYPE_UNKNOWN); } } else { ast_format_cap_append_from_cap(caps, prefcap, AST_MEDIA_TYPE_UNKNOWN); } tmpfmt = ast_format_cap_get_format(caps, 0); ast_channel_nativeformats_set(tmp, caps); ao2_ref(caps, -1); ast_channel_set_rawreadformat(tmp, tmpfmt); ast_channel_set_rawwriteformat(tmp, tmpfmt); ao2_ref(tmpfmt, -1); /* no need to call ast_setstate: the channel_alloc already did its job */ if (state == AST_STATE_RING) ast_channel_rings_set(tmp, 1); ast_channel_tech_pvt_set(tmp, i); ast_channel_context_set(tmp, cntx); if (!ast_strlen_zero(i->ext)) ast_channel_exten_set(tmp, i->ext); else ast_channel_exten_set(tmp, "s"); if (!ast_strlen_zero(i->language)) ast_channel_language_set(tmp, i->language); /* Don't use ast_set_callerid() here because it will * generate a NewCallerID event before the NewChannel event */ if (!ast_strlen_zero(i->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num); } i->owner = tmp; ast_module_ref(ast_module_info->self); ast_channel_unlock(tmp); if (state != AST_STATE_DOWN) { if (state == AST_STATE_RING) { ioctl(ast_channel_fd(tmp, 0), PHONE_RINGBACK); i->cpt = 1; } if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); } } } else { ao2_cleanup(caps); ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); } return tmp; } static void phone_mini_packet(struct phone_pvt *i) { int res; char buf[1024]; /* Ignore stuff we read... */ res = read(i->fd, buf, sizeof(buf)); if (res < 1) { ast_log(LOG_WARNING, "Read returned %d: %s\n", res, strerror(errno)); return; } } static void phone_check_exception(struct phone_pvt *i) { int offhook=0; char digit[2] = {0 , 0}; union telephony_exception phonee; /* XXX Do something XXX */ #if 0 ast_debug(1, "Exception!\n"); #endif phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION); if (phonee.bits.dtmf_ready) { digit[0] = ioctl(i->fd, PHONE_GET_DTMF_ASCII); if (i->mode == MODE_DIALTONE || i->mode == MODE_FXS || i->mode == MODE_SIGMA) { ioctl(i->fd, PHONE_PLAY_STOP); ioctl(i->fd, PHONE_REC_STOP); ioctl(i->fd, PHONE_CPT_STOP); i->dialtone = 0; if (strlen(i->ext) < AST_MAX_EXTENSION - 1) strncat(i->ext, digit, sizeof(i->ext) - strlen(i->ext) - 1); if ((i->mode != MODE_FXS || !(phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION)) || !phonee.bits.dtmf_ready) && ast_exists_extension(NULL, i->context, i->ext, 1, i->cid_num)) { /* It's a valid extension in its context, get moving! */ phone_new(i, AST_STATE_RING, i->context, NULL, NULL); /* No need to restart monitor, we are the monitor */ } else if (!ast_canmatch_extension(NULL, i->context, i->ext, 1, i->cid_num)) { /* There is nothing in the specified extension that can match anymore. Try the default */ if (ast_exists_extension(NULL, "default", i->ext, 1, i->cid_num)) { /* Check the default, too... */ phone_new(i, AST_STATE_RING, "default", NULL, NULL); /* XXX This should probably be justified better XXX */ } else if (!ast_canmatch_extension(NULL, "default", i->ext, 1, i->cid_num)) { /* It's not a valid extension, give a busy signal */ ast_debug(1, "%s can't match anything in %s or default\n", i->ext, i->context); ioctl(i->fd, PHONE_BUSY); i->cpt = 1; } } #if 0 ast_verbose("Extension is %s\n", i->ext); #endif } } if (phonee.bits.hookstate) { offhook = ioctl(i->fd, PHONE_HOOKSTATE); if (offhook) { if (i->mode == MODE_IMMEDIATE) { phone_new(i, AST_STATE_RING, i->context, NULL, NULL); } else if (i->mode == MODE_DIALTONE) { ast_module_ref(ast_module_info->self); /* Reset the extension */ i->ext[0] = '\0'; /* Play the dialtone */ i->dialtone++; ioctl(i->fd, PHONE_PLAY_STOP); ioctl(i->fd, PHONE_PLAY_CODEC, ULAW); ioctl(i->fd, PHONE_PLAY_START); ao2_cleanup(i->lastformat); i->lastformat = NULL; } else if (i->mode == MODE_SIGMA) { ast_module_ref(ast_module_info->self); /* Reset the extension */ i->ext[0] = '\0'; /* Play the dialtone */ i->dialtone++; ioctl(i->fd, PHONE_DIALTONE); } } else { if (i->dialtone) ast_module_unref(ast_module_info->self); memset(i->ext, 0, sizeof(i->ext)); if (i->cpt) { ioctl(i->fd, PHONE_CPT_STOP); i->cpt = 0; } ioctl(i->fd, PHONE_PLAY_STOP); ioctl(i->fd, PHONE_REC_STOP); i->dialtone = 0; ao2_cleanup(i->lastformat); i->lastformat = NULL; } } if (phonee.bits.pstn_ring) { ast_verbose("Unit is ringing\n"); phone_new(i, AST_STATE_RING, i->context, NULL, NULL); } if (phonee.bits.caller_id) ast_verbose("We have caller ID\n"); } static void *do_monitor(void *data) { struct pollfd *fds = NULL; int nfds = 0, inuse_fds = 0, res; struct phone_pvt *i; int tonepos = 0; /* The tone we're playing this round */ struct timeval to = { 0, 0 }; int dotone; /* This thread monitors all the frame relay interfaces which are not yet in use (and thus do not have a separate thread) indefinitely */ while (monitor) { /* Don't let anybody kill us right away. Nobody should lock the interface list and wait for the monitor list, but the other way around is okay. */ /* Lock the interface list */ if (ast_mutex_lock(&iflock)) { ast_log(LOG_ERROR, "Unable to grab interface lock\n"); return NULL; } /* Build the stuff we're going to select on, that is the socket of every phone_pvt that does not have an associated owner channel */ i = iflist; dotone = 0; inuse_fds = 0; for (i = iflist; i; i = i->next) { if (!i->owner) { /* This needs to be watched, as it lacks an owner */ if (inuse_fds == nfds) { void *tmp = ast_realloc(fds, (nfds + 1) * sizeof(*fds)); if (!tmp) { /* Avoid leaking */ continue; } fds = tmp; nfds++; } fds[inuse_fds].fd = i->fd; fds[inuse_fds].events = POLLIN | POLLERR; fds[inuse_fds].revents = 0; inuse_fds++; if (i->dialtone && i->mode != MODE_SIGMA) { /* Remember we're going to have to come back and play more dialtones */ if (ast_tvzero(to)) { /* If we're due for a dialtone, play one */ if (write(i->fd, DialTone + tonepos, 240) != 240) { ast_log(LOG_WARNING, "Dial tone write error\n"); } } dotone++; } } } /* Okay, now that we know what to do, release the interface lock */ ast_mutex_unlock(&iflock); /* Wait indefinitely for something to happen */ if (dotone && i && i->mode != MODE_SIGMA) { /* If we're ready to recycle the time, set it to 30 ms */ tonepos += 240; if (tonepos >= sizeof(DialTone)) { tonepos = 0; } if (ast_tvzero(to)) { to = ast_tv(0, 30000); } res = ast_poll2(fds, inuse_fds, &to); } else { res = ast_poll(fds, inuse_fds, -1); to = ast_tv(0, 0); tonepos = 0; } /* Okay, select has finished. Let's see what happened. */ if (res < 0) { ast_debug(1, "poll returned %d: %s\n", res, strerror(errno)); continue; } /* If there are no fd's changed, just continue, it's probably time to play some more dialtones */ if (!res) { continue; } /* Alright, lock the interface list again, and let's look and see what has happened */ if (ast_mutex_lock(&iflock)) { ast_log(LOG_WARNING, "Unable to lock the interface list\n"); continue; } for (i = iflist; i; i = i->next) { int j; /* Find the record */ for (j = 0; j < inuse_fds; j++) { if (fds[j].fd == i->fd) { break; } } /* Not found? */ if (j == inuse_fds) { continue; } if (fds[j].revents & POLLIN) { if (i->owner) { continue; } phone_mini_packet(i); } if (fds[j].revents & POLLERR) { if (i->owner) { continue; } phone_check_exception(i); } } ast_mutex_unlock(&iflock); } return NULL; } static int restart_monitor() { /* If we're supposed to be stopped -- stay stopped */ if (monitor_thread == AST_PTHREADT_STOP) return 0; if (ast_mutex_lock(&monlock)) { ast_log(LOG_WARNING, "Unable to lock monitor\n"); return -1; } if (monitor_thread == pthread_self()) { ast_mutex_unlock(&monlock); ast_log(LOG_WARNING, "Cannot kill myself\n"); return -1; } if (monitor_thread != AST_PTHREADT_NULL) { if (ast_mutex_lock(&iflock)) { ast_mutex_unlock(&monlock); ast_log(LOG_WARNING, "Unable to lock the interface list\n"); return -1; } monitor = 0; while (pthread_kill(monitor_thread, SIGURG) == 0) sched_yield(); pthread_join(monitor_thread, NULL); ast_mutex_unlock(&iflock); } monitor = 1; /* Start a new monitor */ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) { ast_mutex_unlock(&monlock); ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); return -1; } ast_mutex_unlock(&monlock); return 0; } static struct phone_pvt *mkif(const char *iface, int mode, int txgain, int rxgain) { /* Make a phone_pvt structure for this interface */ struct phone_pvt *tmp; int flags; tmp = ast_calloc(1, sizeof(*tmp)); if (tmp) { tmp->fd = open(iface, O_RDWR); if (tmp->fd < 0) { ast_log(LOG_WARNING, "Unable to open '%s'\n", iface); ast_free(tmp); return NULL; } if (mode == MODE_FXO) { if (ioctl(tmp->fd, IXJCTL_PORT, PORT_PSTN)) { ast_debug(1, "Unable to set port to PSTN\n"); } } else { if (ioctl(tmp->fd, IXJCTL_PORT, PORT_POTS)) if (mode != MODE_FXS) ast_debug(1, "Unable to set port to POTS\n"); } ioctl(tmp->fd, PHONE_PLAY_STOP); ioctl(tmp->fd, PHONE_REC_STOP); ioctl(tmp->fd, PHONE_RING_STOP); ioctl(tmp->fd, PHONE_CPT_STOP); if (ioctl(tmp->fd, PHONE_PSTN_SET_STATE, PSTN_ON_HOOK)) ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n",iface, strerror(errno)); if (echocancel != AEC_OFF) ioctl(tmp->fd, IXJCTL_AEC_START, echocancel); if (silencesupression) tmp->silencesupression = 1; #ifdef PHONE_VAD ioctl(tmp->fd, PHONE_VAD, tmp->silencesupression); #endif tmp->mode = mode; flags = fcntl(tmp->fd, F_GETFL); fcntl(tmp->fd, F_SETFL, flags | O_NONBLOCK); tmp->owner = NULL; ao2_cleanup(tmp->lastformat); tmp->lastformat = NULL; ao2_cleanup(tmp->lastinput); tmp->lastinput = NULL; tmp->ministate = 0; memset(tmp->ext, 0, sizeof(tmp->ext)); ast_copy_string(tmp->language, language, sizeof(tmp->language)); ast_copy_string(tmp->dev, iface, sizeof(tmp->dev)); ast_copy_string(tmp->context, context, sizeof(tmp->context)); tmp->next = NULL; tmp->obuflen = 0; tmp->dialtone = 0; tmp->cpt = 0; ast_copy_string(tmp->cid_num, cid_num, sizeof(tmp->cid_num)); ast_copy_string(tmp->cid_name, cid_name, sizeof(tmp->cid_name)); tmp->txgain = txgain; ioctl(tmp->fd, PHONE_PLAY_VOLUME, tmp->txgain); tmp->rxgain = rxgain; ioctl(tmp->fd, PHONE_REC_VOLUME, tmp->rxgain); } return tmp; } static struct ast_channel *phone_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct phone_pvt *p; struct ast_channel *tmp = NULL; const char *name = data; /* Search for an unowned channel */ if (ast_mutex_lock(&iflock)) { ast_log(LOG_ERROR, "Unable to lock interface list???\n"); return NULL; } p = iflist; while(p) { if (p->mode == MODE_FXS || (ast_format_cap_iscompatible(cap, phone_tech.capabilities))) { size_t length = strlen(p->dev + 5); if (strncmp(name, p->dev + 5, length) == 0 && !isalnum(name[length])) { if (!p->owner) { tmp = phone_new(p, AST_STATE_DOWN, p->context, assignedids, requestor); break; } else *cause = AST_CAUSE_BUSY; } } p = p->next; } ast_mutex_unlock(&iflock); restart_monitor(); if (tmp == NULL) { if (!(ast_format_cap_iscompatible(cap, phone_tech.capabilities))) { struct ast_str *codec_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n", ast_format_cap_get_names(cap, &codec_buf)); return NULL; } } return tmp; } /* parse gain value from config file */ static int parse_gain_value(const char *gain_type, const char *value) { float gain; /* try to scan number */ if (sscanf(value, "%30f", &gain) != 1) { ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n", value, gain_type, config); return DEFAULT_GAIN; } /* multiplicate gain by 1.0 gain value */ gain = gain * (float)DEFAULT_GAIN; /* percentage? */ if (value[strlen(value) - 1] == '%') return (int)(gain / (float)100); return (int)gain; } static int __unload_module(void) { struct phone_pvt *p, *pl; /* First, take us out of the channel loop */ if (cur_tech) ast_channel_unregister(cur_tech); if (!ast_mutex_lock(&iflock)) { /* Hangup all interfaces if they have an owner */ p = iflist; while(p) { if (p->owner) ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD); p = p->next; } iflist = NULL; ast_mutex_unlock(&iflock); } else { ast_log(LOG_WARNING, "Unable to lock the monitor\n"); return -1; } if (!ast_mutex_lock(&monlock)) { if (monitor_thread > AST_PTHREADT_NULL) { monitor = 0; while (pthread_kill(monitor_thread, SIGURG) == 0) sched_yield(); pthread_join(monitor_thread, NULL); } monitor_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&monlock); } else { ast_log(LOG_WARNING, "Unable to lock the monitor\n"); return -1; } if (!ast_mutex_lock(&iflock)) { /* Destroy all the interfaces and free their memory */ p = iflist; while(p) { /* Close the socket, assuming it's real */ if (p->fd > -1) close(p->fd); pl = p; p = p->next; /* Free associated memory */ ast_free(pl); } iflist = NULL; ast_mutex_unlock(&iflock); } else { ast_log(LOG_WARNING, "Unable to lock the monitor\n"); return -1; } ao2_ref(phone_tech.capabilities, -1); ao2_ref(phone_tech_fxs.capabilities, -1); ao2_ref(prefcap, -1); return 0; } static int unload_module(void) { return __unload_module(); } static int load_module(void) { struct ast_config *cfg; struct ast_variable *v; struct phone_pvt *tmp; int mode = MODE_IMMEDIATE; int txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN; /* default gain 1.0 */ struct ast_flags config_flags = { 0 }; if (!(phone_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append(phone_tech.capabilities, ast_format_g723, 0); ast_format_cap_append(phone_tech.capabilities, ast_format_slin, 0); ast_format_cap_append(phone_tech.capabilities, ast_format_ulaw, 0); ast_format_cap_append(phone_tech.capabilities, ast_format_g729, 0); if (!(prefcap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append_from_cap(prefcap, phone_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN); if (!(phone_tech_fxs.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } if ((cfg = ast_config_load(config, config_flags)) == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Config file %s is in an invalid format. Aborting.\n", config); return AST_MODULE_LOAD_DECLINE; } /* We *must* have a config file otherwise stop immediately */ if (!cfg) { ast_log(LOG_ERROR, "Unable to load config %s\n", config); return AST_MODULE_LOAD_DECLINE; } if (ast_mutex_lock(&iflock)) { /* It's a little silly to lock it, but we mind as well just to be sure */ ast_log(LOG_ERROR, "Unable to lock interface list???\n"); return AST_MODULE_LOAD_FAILURE; } v = ast_variable_browse(cfg, "interfaces"); while(v) { /* Create the interface list */ if (!strcasecmp(v->name, "device")) { tmp = mkif(v->value, mode, txgain, rxgain); if (tmp) { tmp->next = iflist; iflist = tmp; } else { ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value); ast_config_destroy(cfg); ast_mutex_unlock(&iflock); __unload_module(); return AST_MODULE_LOAD_FAILURE; } } else if (!strcasecmp(v->name, "silencesupression")) { silencesupression = ast_true(v->value); } else if (!strcasecmp(v->name, "language")) { ast_copy_string(language, v->value, sizeof(language)); } else if (!strcasecmp(v->name, "callerid")) { ast_callerid_split(v->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); } else if (!strcasecmp(v->name, "mode")) { if (!strncasecmp(v->value, "di", 2)) mode = MODE_DIALTONE; else if (!strncasecmp(v->value, "sig", 3)) mode = MODE_SIGMA; else if (!strncasecmp(v->value, "im", 2)) mode = MODE_IMMEDIATE; else if (!strncasecmp(v->value, "fxs", 3)) { mode = MODE_FXS; ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_AUDIO); /* All non-voice */ } else if (!strncasecmp(v->value, "fx", 2)) mode = MODE_FXO; else ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value); } else if (!strcasecmp(v->name, "context")) { ast_copy_string(context, v->value, sizeof(context)); } else if (!strcasecmp(v->name, "format")) { if (!strcasecmp(v->value, "g729")) { ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(prefcap, ast_format_g729, 0); } else if (!strcasecmp(v->value, "g723.1")) { ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(prefcap, ast_format_g723, 0); } else if (!strcasecmp(v->value, "slinear")) { if (mode == MODE_FXS) { ast_format_cap_append(prefcap, ast_format_slin, 0); } else { ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(prefcap, ast_format_slin, 0); } } else if (!strcasecmp(v->value, "ulaw")) { ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN); ast_format_cap_append(prefcap, ast_format_ulaw, 0); } else ast_log(LOG_WARNING, "Unknown format '%s'\n", v->value); } else if (!strcasecmp(v->name, "echocancel")) { if (!strcasecmp(v->value, "off")) { echocancel = AEC_OFF; } else if (!strcasecmp(v->value, "low")) { echocancel = AEC_LOW; } else if (!strcasecmp(v->value, "medium")) { echocancel = AEC_MED; } else if (!strcasecmp(v->value, "high")) { echocancel = AEC_HIGH; } else ast_log(LOG_WARNING, "Unknown echo cancellation '%s'\n", v->value); } else if (!strcasecmp(v->name, "txgain")) { txgain = parse_gain_value(v->name, v->value); } else if (!strcasecmp(v->name, "rxgain")) { rxgain = parse_gain_value(v->name, v->value); } v = v->next; } ast_mutex_unlock(&iflock); if (mode == MODE_FXS) { ast_format_cap_append_from_cap(phone_tech_fxs.capabilities, prefcap, AST_MEDIA_TYPE_UNKNOWN); cur_tech = &phone_tech_fxs; } else cur_tech = (struct ast_channel_tech *) &phone_tech; /* Make sure we can register our Adtranphone channel type */ if (ast_channel_register(cur_tech)) { ast_log(LOG_ERROR, "Unable to register channel class 'Phone'\n"); ast_config_destroy(cfg); __unload_module(); return AST_MODULE_LOAD_FAILURE; } ast_config_destroy(cfg); /* And start the monitor for the first time */ restart_monitor(); return AST_MODULE_LOAD_SUCCESS; } AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Linux Telephony API Support"); asterisk-13.1.0/channels/sig_analog.c0000644000000000000000000036153712363151071016177 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2009, 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. */ /*! \file * * \brief Analog signaling module * * \author Matthew Fredrickson */ /*** MODULEINFO core ***/ #include "asterisk.h" #include #include #include "asterisk/utils.h" #include "asterisk/options.h" #include "asterisk/pickup.h" #include "asterisk/pbx.h" #include "asterisk/file.h" #include "asterisk/callerid.h" #include "asterisk/say.h" #include "asterisk/manager.h" #include "asterisk/astdb.h" #include "asterisk/features.h" #include "asterisk/causes.h" #include "asterisk/features_config.h" #include "asterisk/bridge.h" #include "asterisk/parking.h" #include "sig_analog.h" /*** DOCUMENTATION ***/ /*! \note * Define if you want to check the hook state for an FXO (FXS signalled) interface * before dialing on it. Certain FXO interfaces always think they're out of * service with this method however. */ /* #define DAHDI_CHECK_HOOKSTATE */ #define POLARITY_IDLE 0 #define POLARITY_REV 1 #define MIN_MS_SINCE_FLASH ( (2000) ) /*!< 2000 ms */ static int analog_matchdigittimeout = 3000; static int analog_gendigittimeout = 8000; static int analog_firstdigittimeout = 16000; static char analog_defaultcic[64] = ""; static char analog_defaultozz[64] = ""; static const struct { enum analog_sigtype sigtype; const char const *name; } sigtypes[] = { { ANALOG_SIG_FXOLS, "fxo_ls" }, { ANALOG_SIG_FXOKS, "fxo_ks" }, { ANALOG_SIG_FXOGS, "fxo_gs" }, { ANALOG_SIG_FXSLS, "fxs_ls" }, { ANALOG_SIG_FXSKS, "fxs_ks" }, { ANALOG_SIG_FXSGS, "fxs_gs" }, { ANALOG_SIG_EMWINK, "em_w" }, { ANALOG_SIG_EM, "em" }, { ANALOG_SIG_EM_E1, "em_e1" }, { ANALOG_SIG_FEATD, "featd" }, { ANALOG_SIG_FEATDMF, "featdmf" }, { ANALOG_SIG_FEATDMF_TA, "featdmf_ta" }, { ANALOG_SIG_FEATB, "featb" }, { ANALOG_SIG_FGC_CAMA, "fgccama" }, { ANALOG_SIG_FGC_CAMAMF, "fgccamamf" }, { ANALOG_SIG_SF, "sf" }, { ANALOG_SIG_SFWINK, "sf_w" }, { ANALOG_SIG_SF_FEATD, "sf_featd" }, { ANALOG_SIG_SF_FEATDMF, "sf_featdmf" }, { ANALOG_SIG_SF_FEATB, "sf_featb" }, { ANALOG_SIG_E911, "e911" }, }; static const struct { unsigned int cid_type; const char const *name; } cidtypes[] = { { CID_SIG_BELL, "bell" }, { CID_SIG_V23, "v23" }, { CID_SIG_V23_JP, "v23_jp" }, { CID_SIG_DTMF, "dtmf" }, /* "smdi" is intentionally not supported here, as there is a much better * way to do this in the dialplan now. */ }; #define ISTRUNK(p) ((p->sig == ANALOG_SIG_FXSLS) || (p->sig == ANALOG_SIG_FXSKS) || \ (p->sig == ANALOG_SIG_FXSGS)) enum analog_sigtype analog_str_to_sigtype(const char *name) { int i; for (i = 0; i < ARRAY_LEN(sigtypes); i++) { if (!strcasecmp(sigtypes[i].name, name)) { return sigtypes[i].sigtype; } } return 0; } const char *analog_sigtype_to_str(enum analog_sigtype sigtype) { int i; for (i = 0; i < ARRAY_LEN(sigtypes); i++) { if (sigtype == sigtypes[i].sigtype) { return sigtypes[i].name; } } return "Unknown"; } unsigned int analog_str_to_cidtype(const char *name) { int i; for (i = 0; i < ARRAY_LEN(cidtypes); i++) { if (!strcasecmp(cidtypes[i].name, name)) { return cidtypes[i].cid_type; } } return 0; } const char *analog_cidtype_to_str(unsigned int cid_type) { int i; for (i = 0; i < ARRAY_LEN(cidtypes); i++) { if (cid_type == cidtypes[i].cid_type) { return cidtypes[i].name; } } return "Unknown"; } static int analog_start_cid_detect(struct analog_pvt *p, int cid_signalling) { if (analog_callbacks.start_cid_detect) { return analog_callbacks.start_cid_detect(p->chan_pvt, cid_signalling); } return -1; } static int analog_stop_cid_detect(struct analog_pvt *p) { if (analog_callbacks.stop_cid_detect) { return analog_callbacks.stop_cid_detect(p->chan_pvt); } return -1; } static int analog_get_callerid(struct analog_pvt *p, char *name, char *number, enum analog_event *ev, size_t timeout) { if (analog_callbacks.get_callerid) { return analog_callbacks.get_callerid(p->chan_pvt, name, number, ev, timeout); } return -1; } static const char *analog_get_orig_dialstring(struct analog_pvt *p) { if (analog_callbacks.get_orig_dialstring) { return analog_callbacks.get_orig_dialstring(p->chan_pvt); } return ""; } static int analog_get_event(struct analog_pvt *p) { if (analog_callbacks.get_event) { return analog_callbacks.get_event(p->chan_pvt); } return -1; } static int analog_wait_event(struct analog_pvt *p) { if (analog_callbacks.wait_event) { return analog_callbacks.wait_event(p->chan_pvt); } return -1; } static int analog_have_progressdetect(struct analog_pvt *p) { if (analog_callbacks.have_progressdetect) { return analog_callbacks.have_progressdetect(p->chan_pvt); } /* Don't have progress detection. */ return 0; } enum analog_cid_start analog_str_to_cidstart(const char *value) { if (!strcasecmp(value, "ring")) { return ANALOG_CID_START_RING; } else if (!strcasecmp(value, "polarity")) { return ANALOG_CID_START_POLARITY; } else if (!strcasecmp(value, "polarity_in")) { return ANALOG_CID_START_POLARITY_IN; } else if (!strcasecmp(value, "dtmf")) { return ANALOG_CID_START_DTMF_NOALERT; } return 0; } const char *analog_cidstart_to_str(enum analog_cid_start cid_start) { switch (cid_start) { case ANALOG_CID_START_RING: return "Ring"; case ANALOG_CID_START_POLARITY: return "Polarity"; case ANALOG_CID_START_POLARITY_IN: return "Polarity_In"; case ANALOG_CID_START_DTMF_NOALERT: return "DTMF"; } return "Unknown"; } static char *analog_event2str(enum analog_event event) { char *res; switch (event) { case ANALOG_EVENT_ONHOOK: res = "ANALOG_EVENT_ONHOOK"; break; case ANALOG_EVENT_RINGOFFHOOK: res = "ANALOG_EVENT_RINGOFFHOOK"; break; case ANALOG_EVENT_WINKFLASH: res = "ANALOG_EVENT_WINKFLASH"; break; case ANALOG_EVENT_ALARM: res = "ANALOG_EVENT_ALARM"; break; case ANALOG_EVENT_NOALARM: res = "ANALOG_EVENT_NOALARM"; break; case ANALOG_EVENT_DIALCOMPLETE: res = "ANALOG_EVENT_DIALCOMPLETE"; break; case ANALOG_EVENT_HOOKCOMPLETE: res = "ANALOG_EVENT_HOOKCOMPLETE"; break; case ANALOG_EVENT_PULSE_START: res = "ANALOG_EVENT_PULSE_START"; break; case ANALOG_EVENT_POLARITY: res = "ANALOG_EVENT_POLARITY"; break; case ANALOG_EVENT_RINGBEGIN: res = "ANALOG_EVENT_RINGBEGIN"; break; case ANALOG_EVENT_EC_DISABLED: res = "ANALOG_EVENT_EC_DISABLED"; break; case ANALOG_EVENT_RINGERON: res = "ANALOG_EVENT_RINGERON"; break; case ANALOG_EVENT_RINGEROFF: res = "ANALOG_EVENT_RINGEROFF"; break; case ANALOG_EVENT_REMOVED: res = "ANALOG_EVENT_REMOVED"; break; case ANALOG_EVENT_NEONMWI_ACTIVE: res = "ANALOG_EVENT_NEONMWI_ACTIVE"; break; case ANALOG_EVENT_NEONMWI_INACTIVE: res = "ANALOG_EVENT_NEONMWI_INACTIVE"; break; #ifdef HAVE_DAHDI_ECHOCANCEL_FAX_MODE case ANALOG_EVENT_TX_CED_DETECTED: res = "ANALOG_EVENT_TX_CED_DETECTED"; break; case ANALOG_EVENT_RX_CED_DETECTED: res = "ANALOG_EVENT_RX_CED_DETECTED"; break; case ANALOG_EVENT_EC_NLP_DISABLED: res = "ANALOG_EVENT_EC_NLP_DISABLED"; break; case ANALOG_EVENT_EC_NLP_ENABLED: res = "ANALOG_EVENT_EC_NLP_ENABLED"; break; #endif case ANALOG_EVENT_PULSEDIGIT: res = "ANALOG_EVENT_PULSEDIGIT"; break; case ANALOG_EVENT_DTMFDOWN: res = "ANALOG_EVENT_DTMFDOWN"; break; case ANALOG_EVENT_DTMFUP: res = "ANALOG_EVENT_DTMFUP"; break; default: res = "UNKNOWN/OTHER"; break; } return res; } static void analog_swap_subs(struct analog_pvt *p, enum analog_sub a, enum analog_sub b) { int tinthreeway; struct ast_channel *towner; ast_debug(1, "Swapping %u and %u\n", a, b); towner = p->subs[a].owner; p->subs[a].owner = p->subs[b].owner; p->subs[b].owner = towner; tinthreeway = p->subs[a].inthreeway; p->subs[a].inthreeway = p->subs[b].inthreeway; p->subs[b].inthreeway = tinthreeway; if (analog_callbacks.swap_subs) { analog_callbacks.swap_subs(p->chan_pvt, a, p->subs[a].owner, b, p->subs[b].owner); } } static int analog_alloc_sub(struct analog_pvt *p, enum analog_sub x) { if (analog_callbacks.allocate_sub) { int res; res = analog_callbacks.allocate_sub(p->chan_pvt, x); if (!res) { p->subs[x].allocd = 1; } return res; } return 0; } static int analog_unalloc_sub(struct analog_pvt *p, enum analog_sub x) { p->subs[x].allocd = 0; p->subs[x].owner = NULL; if (analog_callbacks.unallocate_sub) { return analog_callbacks.unallocate_sub(p->chan_pvt, x); } return 0; } static int analog_send_callerid(struct analog_pvt *p, int cwcid, struct ast_party_caller *caller) { ast_debug(1, "Sending callerid. CID_NAME: '%s' CID_NUM: '%s'\n", caller->id.name.str, caller->id.number.str); if (cwcid) { p->callwaitcas = 0; } if (analog_callbacks.send_callerid) { return analog_callbacks.send_callerid(p->chan_pvt, cwcid, caller); } return 0; } #define analog_get_index(ast, p, nullok) _analog_get_index(ast, p, nullok, __PRETTY_FUNCTION__, __LINE__) static int _analog_get_index(struct ast_channel *ast, struct analog_pvt *p, int nullok, const char *fname, unsigned long line) { int res; if (p->subs[ANALOG_SUB_REAL].owner == ast) { res = ANALOG_SUB_REAL; } else if (p->subs[ANALOG_SUB_CALLWAIT].owner == ast) { res = ANALOG_SUB_CALLWAIT; } else if (p->subs[ANALOG_SUB_THREEWAY].owner == ast) { res = ANALOG_SUB_THREEWAY; } else { res = -1; if (!nullok) { ast_log(LOG_WARNING, "Unable to get index for '%s' on channel %d (%s(), line %lu)\n", ast ? ast_channel_name(ast) : "", p->channel, fname, line); } } return res; } static int analog_dsp_reset_and_flush_digits(struct analog_pvt *p) { if (analog_callbacks.dsp_reset_and_flush_digits) { return analog_callbacks.dsp_reset_and_flush_digits(p->chan_pvt); } /* Return 0 since I think this is unnecessary to do in most cases it is used. Mostly only for ast_dsp */ return 0; } static int analog_play_tone(struct analog_pvt *p, enum analog_sub sub, enum analog_tone tone) { if (analog_callbacks.play_tone) { return analog_callbacks.play_tone(p->chan_pvt, sub, tone); } return -1; } static void analog_set_new_owner(struct analog_pvt *p, struct ast_channel *new_owner) { p->owner = new_owner; if (analog_callbacks.set_new_owner) { analog_callbacks.set_new_owner(p->chan_pvt, new_owner); } } static struct ast_channel * analog_new_ast_channel(struct analog_pvt *p, int state, int startpbx, enum analog_sub sub, const struct ast_channel *requestor) { struct ast_channel *c; if (!analog_callbacks.new_ast_channel) { return NULL; } c = analog_callbacks.new_ast_channel(p->chan_pvt, state, startpbx, sub, requestor); if (c) { ast_channel_call_forward_set(c, p->call_forward); } p->subs[sub].owner = c; if (!p->owner) { analog_set_new_owner(p, c); } return c; } static int analog_set_echocanceller(struct analog_pvt *p, int enable) { if (analog_callbacks.set_echocanceller) { return analog_callbacks.set_echocanceller(p->chan_pvt, enable); } return -1; } static int analog_train_echocanceller(struct analog_pvt *p) { if (analog_callbacks.train_echocanceller) { return analog_callbacks.train_echocanceller(p->chan_pvt); } return -1; } static int analog_is_off_hook(struct analog_pvt *p) { if (analog_callbacks.is_off_hook) { return analog_callbacks.is_off_hook(p->chan_pvt); } return -1; } static int analog_ring(struct analog_pvt *p) { if (analog_callbacks.ring) { return analog_callbacks.ring(p->chan_pvt); } return -1; } static int analog_flash(struct analog_pvt *p) { if (analog_callbacks.flash) { return analog_callbacks.flash(p->chan_pvt); } return -1; } static int analog_start(struct analog_pvt *p) { if (analog_callbacks.start) { return analog_callbacks.start(p->chan_pvt); } return -1; } static int analog_dial_digits(struct analog_pvt *p, enum analog_sub sub, struct analog_dialoperation *dop) { if (analog_callbacks.dial_digits) { return analog_callbacks.dial_digits(p->chan_pvt, sub, dop); } return -1; } static int analog_on_hook(struct analog_pvt *p) { if (analog_callbacks.on_hook) { return analog_callbacks.on_hook(p->chan_pvt); } return -1; } static void analog_set_outgoing(struct analog_pvt *p, int is_outgoing) { p->outgoing = is_outgoing; if (analog_callbacks.set_outgoing) { analog_callbacks.set_outgoing(p->chan_pvt, is_outgoing); } } static int analog_check_for_conference(struct analog_pvt *p) { if (analog_callbacks.check_for_conference) { return analog_callbacks.check_for_conference(p->chan_pvt); } return -1; } static void analog_all_subchannels_hungup(struct analog_pvt *p) { if (analog_callbacks.all_subchannels_hungup) { analog_callbacks.all_subchannels_hungup(p->chan_pvt); } } static void analog_unlock_private(struct analog_pvt *p) { if (analog_callbacks.unlock_private) { analog_callbacks.unlock_private(p->chan_pvt); } } static void analog_lock_private(struct analog_pvt *p) { if (analog_callbacks.lock_private) { analog_callbacks.lock_private(p->chan_pvt); } } static void analog_deadlock_avoidance_private(struct analog_pvt *p) { if (analog_callbacks.deadlock_avoidance_private) { analog_callbacks.deadlock_avoidance_private(p->chan_pvt); } else { /* Fallback to manual avoidance if callback not present. */ analog_unlock_private(p); usleep(1); analog_lock_private(p); } } /*! * \internal * \brief Obtain the specified subchannel owner lock if the owner exists. * * \param pvt Analog private struct. * \param sub_idx Subchannel owner to lock. * * \note Assumes the analog_lock_private(pvt->chan_pvt) is already obtained. * * \note * Because deadlock avoidance may have been necessary, you need to confirm * the state of things before continuing. * * \return Nothing */ static void analog_lock_sub_owner(struct analog_pvt *pvt, enum analog_sub sub_idx) { for (;;) { if (!pvt->subs[sub_idx].owner) { /* No subchannel owner pointer */ break; } if (!ast_channel_trylock(pvt->subs[sub_idx].owner)) { /* Got subchannel owner lock */ break; } /* We must unlock the private to avoid the possibility of a deadlock */ analog_deadlock_avoidance_private(pvt); } } static int analog_off_hook(struct analog_pvt *p) { if (analog_callbacks.off_hook) { return analog_callbacks.off_hook(p->chan_pvt); } return -1; } static void analog_set_needringing(struct analog_pvt *p, int value) { if (analog_callbacks.set_needringing) { analog_callbacks.set_needringing(p->chan_pvt, value); } } #if 0 static void analog_set_polarity(struct analog_pvt *p, int value) { if (analog_callbacks.set_polarity) { analog_callbacks.set_polarity(p->chan_pvt, value); } } #endif static void analog_start_polarityswitch(struct analog_pvt *p) { if (analog_callbacks.start_polarityswitch) { analog_callbacks.start_polarityswitch(p->chan_pvt); } } static void analog_answer_polarityswitch(struct analog_pvt *p) { if (analog_callbacks.answer_polarityswitch) { analog_callbacks.answer_polarityswitch(p->chan_pvt); } } static void analog_hangup_polarityswitch(struct analog_pvt *p) { if (analog_callbacks.hangup_polarityswitch) { analog_callbacks.hangup_polarityswitch(p->chan_pvt); } } static int analog_dsp_set_digitmode(struct analog_pvt *p, enum analog_dsp_digitmode mode) { if (analog_callbacks.dsp_set_digitmode) { return analog_callbacks.dsp_set_digitmode(p->chan_pvt, mode); } return -1; } static void analog_cb_handle_dtmf(struct analog_pvt *p, struct ast_channel *ast, enum analog_sub analog_index, struct ast_frame **dest) { if (analog_callbacks.handle_dtmf) { analog_callbacks.handle_dtmf(p->chan_pvt, ast, analog_index, dest); } } static int analog_wink(struct analog_pvt *p, enum analog_sub index) { if (analog_callbacks.wink) { return analog_callbacks.wink(p->chan_pvt, index); } return -1; } static int analog_has_voicemail(struct analog_pvt *p) { if (analog_callbacks.has_voicemail) { return analog_callbacks.has_voicemail(p->chan_pvt); } return -1; } static int analog_is_dialing(struct analog_pvt *p, enum analog_sub index) { if (analog_callbacks.is_dialing) { return analog_callbacks.is_dialing(p->chan_pvt, index); } return -1; } /*! * \internal * \brief Attempt to transfer 3-way call. * * \param p Analog private structure. * * \note On entry these locks are held: real-call, private, 3-way call. * \note On exit these locks are held: real-call, private. * * \retval 0 on success. * \retval -1 on error. */ static int analog_attempt_transfer(struct analog_pvt *p) { struct ast_channel *owner_real; struct ast_channel *owner_3way; enum ast_transfer_result xfer_res; int res = 0; owner_real = ast_channel_ref(p->subs[ANALOG_SUB_REAL].owner); owner_3way = ast_channel_ref(p->subs[ANALOG_SUB_THREEWAY].owner); ast_verb(3, "TRANSFERRING %s to %s\n", ast_channel_name(owner_3way), ast_channel_name(owner_real)); ast_channel_unlock(owner_real); ast_channel_unlock(owner_3way); analog_unlock_private(p); xfer_res = ast_bridge_transfer_attended(owner_3way, owner_real); if (xfer_res != AST_BRIDGE_TRANSFER_SUCCESS) { ast_softhangup(owner_3way, AST_SOFTHANGUP_DEV); res = -1; } /* Must leave with these locked. */ ast_channel_lock(owner_real); analog_lock_private(p); ast_channel_unref(owner_real); ast_channel_unref(owner_3way); return res; } static int analog_update_conf(struct analog_pvt *p) { int x; int needconf = 0; /* Start with the obvious, general stuff */ for (x = 0; x < 3; x++) { /* Look for three way calls */ if ((p->subs[x].allocd) && p->subs[x].inthreeway) { if (analog_callbacks.conf_add) { analog_callbacks.conf_add(p->chan_pvt, x); } needconf++; } else { if (analog_callbacks.conf_del) { analog_callbacks.conf_del(p->chan_pvt, x); } } } ast_debug(1, "Updated conferencing on %d, with %d conference users\n", p->channel, needconf); if (analog_callbacks.complete_conference_update) { analog_callbacks.complete_conference_update(p->chan_pvt, needconf); } return 0; } struct ast_channel * analog_request(struct analog_pvt *p, int *callwait, const struct ast_channel *requestor) { struct ast_channel *ast; ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); *callwait = (p->owner != NULL); if (p->owner) { if (analog_alloc_sub(p, ANALOG_SUB_CALLWAIT)) { ast_log(LOG_ERROR, "Unable to alloc subchannel\n"); return NULL; } } analog_set_outgoing(p, 1); ast = analog_new_ast_channel(p, AST_STATE_RESERVED, 0, p->owner ? ANALOG_SUB_CALLWAIT : ANALOG_SUB_REAL, requestor); if (!ast) { analog_set_outgoing(p, 0); } return ast; } int analog_available(struct analog_pvt *p) { int offhook; ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); /* If do not disturb, definitely not */ if (p->dnd) { return 0; } /* If guard time, definitely not */ if (p->guardtime && (time(NULL) < p->guardtime)) { return 0; } /* If no owner definitely available */ if (!p->owner) { offhook = analog_is_off_hook(p); /* TDM FXO card, "onhook" means out of service (no battery on the line) */ if ((p->sig == ANALOG_SIG_FXSLS) || (p->sig == ANALOG_SIG_FXSKS) || (p->sig == ANALOG_SIG_FXSGS)) { #ifdef DAHDI_CHECK_HOOKSTATE if (offhook) { return 1; } return 0; #endif /* TDM FXS card, "offhook" means someone took the hook off so it's unavailable! */ } else if (offhook) { ast_debug(1, "Channel %d off hook, can't use\n", p->channel); /* Not available when the other end is off hook */ return 0; } return 1; } /* If it's not an FXO, forget about call wait */ if ((p->sig != ANALOG_SIG_FXOKS) && (p->sig != ANALOG_SIG_FXOLS) && (p->sig != ANALOG_SIG_FXOGS)) { return 0; } if (!p->callwaiting) { /* If they don't have call waiting enabled, then for sure they're unavailable at this point */ return 0; } if (p->subs[ANALOG_SUB_CALLWAIT].allocd) { /* If there is already a call waiting call, then we can't take a second one */ return 0; } if ((ast_channel_state(p->owner) != AST_STATE_UP) && ((ast_channel_state(p->owner) != AST_STATE_RINGING) || p->outgoing)) { /* If the current call is not up, then don't allow the call */ return 0; } if ((p->subs[ANALOG_SUB_THREEWAY].owner) && (!p->subs[ANALOG_SUB_THREEWAY].inthreeway)) { /* Can't take a call wait when the three way calling hasn't been merged yet. */ return 0; } /* We're cool */ return 1; } static int analog_stop_callwait(struct analog_pvt *p) { p->callwaitcas = 0; if (analog_callbacks.stop_callwait) { return analog_callbacks.stop_callwait(p->chan_pvt); } return 0; } static int analog_callwait(struct analog_pvt *p) { p->callwaitcas = p->callwaitingcallerid; if (analog_callbacks.callwait) { return analog_callbacks.callwait(p->chan_pvt); } return 0; } static void analog_set_callwaiting(struct analog_pvt *p, int callwaiting_enable) { p->callwaiting = callwaiting_enable; if (analog_callbacks.set_callwaiting) { analog_callbacks.set_callwaiting(p->chan_pvt, callwaiting_enable); } } static void analog_set_cadence(struct analog_pvt *p, struct ast_channel *chan) { if (analog_callbacks.set_cadence) { analog_callbacks.set_cadence(p->chan_pvt, &p->cidrings, chan); } } static void analog_set_dialing(struct analog_pvt *p, int is_dialing) { p->dialing = is_dialing; if (analog_callbacks.set_dialing) { analog_callbacks.set_dialing(p->chan_pvt, is_dialing); } } static void analog_set_alarm(struct analog_pvt *p, int in_alarm) { p->inalarm = in_alarm; if (analog_callbacks.set_alarm) { analog_callbacks.set_alarm(p->chan_pvt, in_alarm); } } static void analog_set_ringtimeout(struct analog_pvt *p, int ringt) { p->ringt = ringt; if (analog_callbacks.set_ringtimeout) { analog_callbacks.set_ringtimeout(p->chan_pvt, ringt); } } static void analog_set_waitingfordt(struct analog_pvt *p, struct ast_channel *ast) { if (analog_callbacks.set_waitingfordt) { analog_callbacks.set_waitingfordt(p->chan_pvt, ast); } } static int analog_check_waitingfordt(struct analog_pvt *p) { if (analog_callbacks.check_waitingfordt) { return analog_callbacks.check_waitingfordt(p->chan_pvt); } return 0; } static void analog_set_confirmanswer(struct analog_pvt *p, int flag) { if (analog_callbacks.set_confirmanswer) { analog_callbacks.set_confirmanswer(p->chan_pvt, flag); } } static int analog_check_confirmanswer(struct analog_pvt *p) { if (analog_callbacks.check_confirmanswer) { return analog_callbacks.check_confirmanswer(p->chan_pvt); } return 0; } static void analog_cancel_cidspill(struct analog_pvt *p) { if (analog_callbacks.cancel_cidspill) { analog_callbacks.cancel_cidspill(p->chan_pvt); } } static int analog_confmute(struct analog_pvt *p, int mute) { if (analog_callbacks.confmute) { return analog_callbacks.confmute(p->chan_pvt, mute); } return 0; } static void analog_set_pulsedial(struct analog_pvt *p, int flag) { if (analog_callbacks.set_pulsedial) { analog_callbacks.set_pulsedial(p->chan_pvt, flag); } } static int analog_set_linear_mode(struct analog_pvt *p, enum analog_sub sub, int linear_mode) { if (analog_callbacks.set_linear_mode) { /* Return provides old linear_mode setting or error indication */ return analog_callbacks.set_linear_mode(p->chan_pvt, sub, linear_mode); } return -1; } static void analog_set_inthreeway(struct analog_pvt *p, enum analog_sub sub, int inthreeway) { p->subs[sub].inthreeway = inthreeway; if (analog_callbacks.set_inthreeway) { analog_callbacks.set_inthreeway(p->chan_pvt, sub, inthreeway); } } int analog_call(struct analog_pvt *p, struct ast_channel *ast, const char *rdest, int timeout) { int res, idx, mysig; char *c, *n, *l; char dest[256]; /* must be same length as p->dialdest */ ast_debug(1, "CALLING CID_NAME: %s CID_NUM:: %s\n", S_COR(ast_channel_connected(ast)->id.name.valid, ast_channel_connected(ast)->id.name.str, ""), S_COR(ast_channel_connected(ast)->id.number.valid, ast_channel_connected(ast)->id.number.str, "")); ast_copy_string(dest, rdest, sizeof(dest)); ast_copy_string(p->dialdest, rdest, sizeof(p->dialdest)); if ((ast_channel_state(ast) == AST_STATE_BUSY)) { ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_BUSY); return 0; } if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "analog_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } p->dialednone = 0; analog_set_outgoing(p, 1); mysig = p->sig; if (p->outsigmod > -1) { mysig = p->outsigmod; } switch (mysig) { case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: if (p->owner == ast) { /* Normal ring, on hook */ /* Don't send audio while on hook, until the call is answered */ analog_set_dialing(p, 1); analog_set_cadence(p, ast); /* and set p->cidrings */ /* nick@dccinc.com 4/3/03 mods to allow for deferred dialing */ c = strchr(dest, '/'); if (c) { c++; } if (c && (strlen(c) < p->stripmsd)) { ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd); c = NULL; } if (c) { p->dop.op = ANALOG_DIAL_OP_REPLACE; snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "Tw%s", c); ast_debug(1, "FXO: setup deferred dialstring: %s\n", c); } else { p->dop.dialstr[0] = '\0'; } if (analog_ring(p)) { ast_log(LOG_WARNING, "Unable to ring phone: %s\n", strerror(errno)); return -1; } analog_set_dialing(p, 1); } else { /* Call waiting call */ if (ast_channel_connected(ast)->id.number.valid && ast_channel_connected(ast)->id.number.str) { ast_copy_string(p->callwait_num, ast_channel_connected(ast)->id.number.str, sizeof(p->callwait_num)); } else { p->callwait_num[0] = '\0'; } if (ast_channel_connected(ast)->id.name.valid && ast_channel_connected(ast)->id.name.str) { ast_copy_string(p->callwait_name, ast_channel_connected(ast)->id.name.str, sizeof(p->callwait_name)); } else { p->callwait_name[0] = '\0'; } /* Call waiting tone instead */ if (analog_callwait(p)) { return -1; } /* Make ring-back */ if (analog_play_tone(p, ANALOG_SUB_CALLWAIT, ANALOG_TONE_RINGTONE)) { ast_log(LOG_WARNING, "Unable to generate call-wait ring-back on channel %s\n", ast_channel_name(ast)); } } n = ast_channel_connected(ast)->id.name.valid ? ast_channel_connected(ast)->id.name.str : NULL; l = ast_channel_connected(ast)->id.number.valid ? ast_channel_connected(ast)->id.number.str : NULL; if (l) { ast_copy_string(p->lastcid_num, l, sizeof(p->lastcid_num)); } else { p->lastcid_num[0] = '\0'; } if (n) { ast_copy_string(p->lastcid_name, n, sizeof(p->lastcid_name)); } else { p->lastcid_name[0] = '\0'; } if (p->use_callerid) { p->caller.id.name.str = p->lastcid_name; p->caller.id.number.str = p->lastcid_num; } ast_setstate(ast, AST_STATE_RINGING); idx = analog_get_index(ast, p, 0); if (idx > -1) { struct ast_cc_config_params *cc_params; /* This is where the initial ringing frame is queued for an analog call. * As such, this is a great time to offer CCNR to the caller if it's available. */ cc_params = ast_channel_get_cc_config_params(p->subs[idx].owner); if (cc_params) { switch (ast_get_cc_monitor_policy(cc_params)) { case AST_CC_MONITOR_NEVER: break; case AST_CC_MONITOR_NATIVE: case AST_CC_MONITOR_ALWAYS: case AST_CC_MONITOR_GENERIC: ast_queue_cc_frame(p->subs[idx].owner, AST_CC_GENERIC_MONITOR_TYPE, analog_get_orig_dialstring(p), AST_CC_CCNR, NULL); break; } } ast_queue_control(p->subs[idx].owner, AST_CONTROL_RINGING); } break; case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: if (p->answeronpolarityswitch || p->hanguponpolarityswitch) { ast_debug(1, "Ignore possible polarity reversal on line seizure\n"); p->polaritydelaytv = ast_tvnow(); } /* fall through */ case ANALOG_SIG_EMWINK: case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_FEATD: case ANALOG_SIG_FEATDMF: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_SFWINK: case ANALOG_SIG_SF: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_SF_FEATB: c = strchr(dest, '/'); if (c) { c++; } else { c = ""; } if (strlen(c) < p->stripmsd) { ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd); return -1; } res = analog_start(p); if (res < 0) { if (errno != EINPROGRESS) { return -1; } } ast_debug(1, "Dialing '%s'\n", c); p->dop.op = ANALOG_DIAL_OP_REPLACE; c += p->stripmsd; switch (mysig) { case ANALOG_SIG_FEATD: l = ast_channel_connected(ast)->id.number.valid ? ast_channel_connected(ast)->id.number.str : NULL; if (l) { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T*%s*%s*", l, c); } else { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T**%s*", c); } break; case ANALOG_SIG_FEATDMF: l = ast_channel_connected(ast)->id.number.valid ? ast_channel_connected(ast)->id.number.str : NULL; if (l) { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*00%s#*%s#", l, c); } else { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*02#*%s#", c); } break; case ANALOG_SIG_FEATDMF_TA: { const char *cic = "", *ozz = ""; /* If you have to go through a Tandem Access point you need to use this */ #ifndef STANDALONE ozz = pbx_builtin_getvar_helper(p->owner, "FEATDMF_OZZ"); if (!ozz) { ozz = analog_defaultozz; } cic = pbx_builtin_getvar_helper(p->owner, "FEATDMF_CIC"); if (!cic) { cic = analog_defaultcic; } #endif if (!ozz || !cic) { ast_log(LOG_WARNING, "Unable to dial channel of type feature group D MF tandem access without CIC or OZZ set\n"); return -1; } snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%s%s#", ozz, cic); snprintf(p->finaldial, sizeof(p->finaldial), "M*%s#", c); p->whichwink = 0; } break; case ANALOG_SIG_E911: ast_copy_string(p->dop.dialstr, "M*911#", sizeof(p->dop.dialstr)); break; case ANALOG_SIG_FGC_CAMA: snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "P%s", c); break; case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%s#", c); break; default: if (p->pulse) { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "P%sw", c); } else { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "T%sw", c); } break; } if (p->echotraining && (strlen(p->dop.dialstr) > 4)) { memset(p->echorest, 'w', sizeof(p->echorest) - 1); strcpy(p->echorest + (p->echotraining / 400) + 1, p->dop.dialstr + strlen(p->dop.dialstr) - 2); p->echorest[sizeof(p->echorest) - 1] = '\0'; p->echobreak = 1; p->dop.dialstr[strlen(p->dop.dialstr)-2] = '\0'; } else { p->echobreak = 0; } analog_set_waitingfordt(p, ast); if (!res) { if (analog_dial_digits(p, ANALOG_SUB_REAL, &p->dop)) { analog_on_hook(p); return -1; } } else { ast_debug(1, "Deferring dialing...\n"); } analog_set_dialing(p, 1); if (ast_strlen_zero(c)) { p->dialednone = 1; } ast_setstate(ast, AST_STATE_DIALING); break; default: ast_debug(1, "not yet implemented\n"); return -1; } return 0; } int analog_hangup(struct analog_pvt *p, struct ast_channel *ast) { int res; int idx, x; ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } idx = analog_get_index(ast, p, 1); x = 0; if (p->origcid_num) { ast_copy_string(p->cid_num, p->origcid_num, sizeof(p->cid_num)); ast_free(p->origcid_num); p->origcid_num = NULL; } if (p->origcid_name) { ast_copy_string(p->cid_name, p->origcid_name, sizeof(p->cid_name)); ast_free(p->origcid_name); p->origcid_name = NULL; } analog_dsp_set_digitmode(p, ANALOG_DIGITMODE_DTMF); ast_debug(1, "Hangup: channel: %d index = %d, normal = %d, callwait = %d, thirdcall = %d\n", p->channel, idx, p->subs[ANALOG_SUB_REAL].allocd, p->subs[ANALOG_SUB_CALLWAIT].allocd, p->subs[ANALOG_SUB_THREEWAY].allocd); if (idx > -1) { /* Real channel, do some fixup */ p->subs[idx].owner = NULL; p->polarity = POLARITY_IDLE; analog_set_linear_mode(p, idx, 0); switch (idx) { case ANALOG_SUB_REAL: if (p->subs[ANALOG_SUB_CALLWAIT].allocd && p->subs[ANALOG_SUB_THREEWAY].allocd) { ast_debug(1, "Normal call hung up with both three way call and a call waiting call in place?\n"); if (p->subs[ANALOG_SUB_CALLWAIT].inthreeway) { /* We had flipped over to answer a callwait and now it's gone */ ast_debug(1, "We were flipped over to the callwait, moving back and unowning.\n"); /* Move to the call-wait, but un-own us until they flip back. */ analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_REAL); analog_unalloc_sub(p, ANALOG_SUB_CALLWAIT); analog_set_new_owner(p, NULL); } else { /* The three way hung up, but we still have a call wait */ ast_debug(1, "We were in the threeway and have a callwait still. Ditching the threeway.\n"); analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); if (p->subs[ANALOG_SUB_REAL].inthreeway) { /* This was part of a three way call. Immediately make way for another call */ ast_debug(1, "Call was complete, setting owner to former third call\n"); analog_set_inthreeway(p, ANALOG_SUB_REAL, 0); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); } else { /* This call hasn't been completed yet... Set owner to NULL */ ast_debug(1, "Call was incomplete, setting owner to NULL\n"); analog_set_new_owner(p, NULL); } } } else if (p->subs[ANALOG_SUB_CALLWAIT].allocd) { /* Need to hold the lock for real-call, private, and call-waiting call */ analog_lock_sub_owner(p, ANALOG_SUB_CALLWAIT); if (!p->subs[ANALOG_SUB_CALLWAIT].owner) { /* The call waiting call dissappeared. */ analog_set_new_owner(p, NULL); break; } /* Move to the call-wait and switch back to them. */ analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_REAL); analog_unalloc_sub(p, ANALOG_SUB_CALLWAIT); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); if (ast_channel_state(p->owner) != AST_STATE_UP) { ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_ANSWER); } ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner); /* Unlock the call-waiting call that we swapped to real-call. */ ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); } else if (p->subs[ANALOG_SUB_THREEWAY].allocd) { analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); if (p->subs[ANALOG_SUB_REAL].inthreeway) { /* This was part of a three way call. Immediately make way for another call */ ast_debug(1, "Call was complete, setting owner to former third call\n"); analog_set_inthreeway(p, ANALOG_SUB_REAL, 0); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); } else { /* This call hasn't been completed yet... Set owner to NULL */ ast_debug(1, "Call was incomplete, setting owner to NULL\n"); analog_set_new_owner(p, NULL); } } break; case ANALOG_SUB_CALLWAIT: /* Ditch the holding callwait call, and immediately make it available */ if (p->subs[ANALOG_SUB_CALLWAIT].inthreeway) { /* Need to hold the lock for call-waiting call, private, and 3-way call */ analog_lock_sub_owner(p, ANALOG_SUB_THREEWAY); /* This is actually part of a three way, placed on hold. Place the third part on music on hold now */ if (p->subs[ANALOG_SUB_THREEWAY].owner) { ast_queue_hold(p->subs[ANALOG_SUB_THREEWAY].owner, p->mohsuggest); } analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 0); /* Make it the call wait now */ analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_THREEWAY); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); if (p->subs[ANALOG_SUB_CALLWAIT].owner) { /* Unlock the 3-way call that we swapped to call-waiting call. */ ast_channel_unlock(p->subs[ANALOG_SUB_CALLWAIT].owner); } } else { analog_unalloc_sub(p, ANALOG_SUB_CALLWAIT); } break; case ANALOG_SUB_THREEWAY: /* Need to hold the lock for 3-way call, private, and call-waiting call */ analog_lock_sub_owner(p, ANALOG_SUB_CALLWAIT); if (p->subs[ANALOG_SUB_CALLWAIT].inthreeway) { /* The other party of the three way call is currently in a call-wait state. Start music on hold for them, and take the main guy out of the third call */ analog_set_inthreeway(p, ANALOG_SUB_CALLWAIT, 0); if (p->subs[ANALOG_SUB_CALLWAIT].owner) { ast_queue_hold(p->subs[ANALOG_SUB_CALLWAIT].owner, p->mohsuggest); } } if (p->subs[ANALOG_SUB_CALLWAIT].owner) { ast_channel_unlock(p->subs[ANALOG_SUB_CALLWAIT].owner); } analog_set_inthreeway(p, ANALOG_SUB_REAL, 0); /* If this was part of a three way call index, let us make another three way call */ analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); break; default: /* * Should never happen. * This wasn't any sort of call, so how are we an index? */ ast_log(LOG_ERROR, "Index found but not any type of call?\n"); break; } } if (!p->subs[ANALOG_SUB_REAL].owner && !p->subs[ANALOG_SUB_CALLWAIT].owner && !p->subs[ANALOG_SUB_THREEWAY].owner) { analog_set_new_owner(p, NULL); analog_set_ringtimeout(p, 0); analog_set_confirmanswer(p, 0); analog_set_pulsedial(p, 0); analog_set_outgoing(p, 0); p->onhooktime = time(NULL); p->cidrings = 1; /* Perform low level hangup if no owner left */ res = analog_on_hook(p); if (res < 0) { ast_log(LOG_WARNING, "Unable to hangup line %s\n", ast_channel_name(ast)); } switch (p->sig) { case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOKS: /* If they're off hook, try playing congestion */ if (analog_is_off_hook(p)) { analog_hangup_polarityswitch(p); analog_play_tone(p, ANALOG_SUB_REAL, ANALOG_TONE_CONGESTION); } else { analog_play_tone(p, ANALOG_SUB_REAL, -1); } break; case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSKS: /* Make sure we're not made available for at least two seconds assuming we were actually used for an inbound or outbound call. */ if (ast_channel_state(ast) != AST_STATE_RESERVED) { time(&p->guardtime); p->guardtime += 2; } break; default: analog_play_tone(p, ANALOG_SUB_REAL, -1); break; } analog_set_echocanceller(p, 0); x = 0; ast_channel_setoption(ast,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0); ast_channel_setoption(ast,AST_OPTION_TDD,&x,sizeof(char),0); p->callwaitcas = 0; analog_set_callwaiting(p, p->permcallwaiting); p->hidecallerid = p->permhidecallerid; analog_set_dialing(p, 0); analog_update_conf(p); analog_all_subchannels_hungup(p); } analog_stop_callwait(p); ast_verb(3, "Hanging up on '%s'\n", ast_channel_name(ast)); return 0; } int analog_answer(struct analog_pvt *p, struct ast_channel *ast) { int res = 0; int idx; int oldstate = ast_channel_state(ast); ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); ast_setstate(ast, AST_STATE_UP); idx = analog_get_index(ast, p, 1); if (idx < 0) { idx = ANALOG_SUB_REAL; } switch (p->sig) { case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: analog_set_ringtimeout(p, 0); /* Fall through */ case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_EMWINK: case ANALOG_SIG_FEATD: case ANALOG_SIG_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_SF: case ANALOG_SIG_SFWINK: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: /* Pick up the line */ ast_debug(1, "Took %s off hook\n", ast_channel_name(ast)); if (p->hanguponpolarityswitch) { gettimeofday(&p->polaritydelaytv, NULL); } res = analog_off_hook(p); analog_play_tone(p, idx, -1); analog_set_dialing(p, 0); if ((idx == ANALOG_SUB_REAL) && p->subs[ANALOG_SUB_THREEWAY].inthreeway) { if (oldstate == AST_STATE_RINGING) { ast_debug(1, "Finally swapping real and threeway\n"); analog_play_tone(p, ANALOG_SUB_THREEWAY, -1); analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); } } switch (p->sig) { case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSKS: case ANALOG_SIG_FXSGS: analog_set_echocanceller(p, 1); analog_train_echocanceller(p); break; case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOKS: case ANALOG_SIG_FXOGS: analog_answer_polarityswitch(p); break; default: break; } break; default: ast_log(LOG_WARNING, "Don't know how to answer signalling %d (channel %d)\n", p->sig, p->channel); res = -1; break; } ast_setstate(ast, AST_STATE_UP); return res; } static int analog_handles_digit(struct ast_frame *f) { char subclass = toupper(f->subclass.integer); switch (subclass) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '9': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return 1; default: return 0; } } void analog_handle_dtmf(struct analog_pvt *p, struct ast_channel *ast, enum analog_sub idx, struct ast_frame **dest) { struct ast_frame *f = *dest; ast_debug(1, "%s DTMF digit: 0x%02X '%c' on %s\n", f->frametype == AST_FRAME_DTMF_BEGIN ? "Begin" : "End", (unsigned)f->subclass.integer, f->subclass.integer, ast_channel_name(ast)); if (analog_check_confirmanswer(p)) { if (f->frametype == AST_FRAME_DTMF_END) { ast_debug(1, "Confirm answer on %s!\n", ast_channel_name(ast)); /* Upon receiving a DTMF digit, consider this an answer confirmation instead of a DTMF digit */ p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; /* Reset confirmanswer so DTMF's will behave properly for the duration of the call */ analog_set_confirmanswer(p, 0); } else { p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; } *dest = &p->subs[idx].f; } else if (p->callwaitcas) { if (f->frametype == AST_FRAME_DTMF_END) { if ((f->subclass.integer == 'A') || (f->subclass.integer == 'D')) { ast_debug(1, "Got some DTMF, but it's for the CAS\n"); p->caller.id.name.str = p->callwait_name; p->caller.id.number.str = p->callwait_num; analog_send_callerid(p, 1, &p->caller); } if (analog_handles_digit(f)) { p->callwaitcas = 0; } } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; *dest = &p->subs[idx].f; } else { analog_cb_handle_dtmf(p, ast, idx, dest); } } static int analog_my_getsigstr(struct ast_channel *chan, char *str, const char *term, int ms) { char c; *str = 0; /* start with empty output buffer */ for (;;) { /* Wait for the first digit (up to specified ms). */ c = ast_waitfordigit(chan, ms); /* if timeout, hangup or error, return as such */ if (c < 1) { return c; } *str++ = c; *str = 0; if (strchr(term, c)) { return 1; } } } static int analog_handle_notify_message(struct ast_channel *chan, struct analog_pvt *p, int cid_flags, int neon_mwievent) { if (analog_callbacks.handle_notify_message) { analog_callbacks.handle_notify_message(chan, p->chan_pvt, cid_flags, neon_mwievent); return 0; } return -1; } static void analog_increase_ss_count(void) { if (analog_callbacks.increase_ss_count) { analog_callbacks.increase_ss_count(); } } static void analog_decrease_ss_count(void) { if (analog_callbacks.decrease_ss_count) { analog_callbacks.decrease_ss_count(); } } static int analog_distinctive_ring(struct ast_channel *chan, struct analog_pvt *p, int idx, int *ringdata) { if (analog_callbacks.distinctive_ring) { return analog_callbacks.distinctive_ring(chan, p->chan_pvt, idx, ringdata); } return -1; } static void analog_get_and_handle_alarms(struct analog_pvt *p) { if (analog_callbacks.get_and_handle_alarms) { analog_callbacks.get_and_handle_alarms(p->chan_pvt); } } static void *analog_get_bridged_channel(struct ast_channel *chan) { if (analog_callbacks.get_sigpvt_bridged_channel) { return analog_callbacks.get_sigpvt_bridged_channel(chan); } return NULL; } static int analog_get_sub_fd(struct analog_pvt *p, enum analog_sub sub) { if (analog_callbacks.get_sub_fd) { return analog_callbacks.get_sub_fd(p->chan_pvt, sub); } return -1; } #define ANALOG_NEED_MFDETECT(p) (((p)->sig == ANALOG_SIG_FEATDMF) || ((p)->sig == ANALOG_SIG_FEATDMF_TA) || ((p)->sig == ANALOG_SIG_E911) || ((p)->sig == ANALOG_SIG_FGC_CAMA) || ((p)->sig == ANALOG_SIG_FGC_CAMAMF) || ((p)->sig == ANALOG_SIG_FEATB)) static int analog_canmatch_featurecode(const char *pickupexten, const char *exten) { int extlen = strlen(exten); if (!extlen) { return 1; } if (extlen < strlen(pickupexten) && !strncmp(pickupexten, exten, extlen)) { return 1; } /* hardcoded features are *60, *67, *69, *70, *72, *73, *78, *79, *82, *0 */ if (exten[0] == '*' && extlen < 3) { if (extlen == 1) { return 1; } /* "*0" should be processed before it gets here */ switch (exten[1]) { case '6': case '7': case '8': return 1; } } return 0; } static void *__analog_ss_thread(void *data) { struct analog_pvt *p = data; struct ast_channel *chan = p->ss_astchan; char exten[AST_MAX_EXTENSION] = ""; char exten2[AST_MAX_EXTENSION] = ""; char dtmfcid[300]; char dtmfbuf[300]; char namebuf[ANALOG_MAX_CID]; char numbuf[ANALOG_MAX_CID]; struct callerid_state *cs = NULL; char *name = NULL, *number = NULL; int flags = 0; struct ast_smdi_md_message *smdi_msg = NULL; int timeout; int getforward = 0; char *s1, *s2; int len = 0; int res; int idx; struct ast_callid *callid; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); const char *pickupexten; analog_increase_ss_count(); ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); if (!chan) { /* What happened to the channel? */ goto quit; } if ((callid = ast_channel_callid(chan))) { ast_callid_threadassoc_add(callid); ast_callid_unref(callid); } /* in the bizarre case where the channel has become a zombie before we even get started here, abort safely */ if (!ast_channel_tech_pvt(chan)) { ast_log(LOG_WARNING, "Channel became a zombie before simple switch could be started (%s)\n", ast_channel_name(chan)); ast_hangup(chan); goto quit; } ast_verb(3, "Starting simple switch on '%s'\n", ast_channel_name(chan)); idx = analog_get_index(chan, p, 0); if (idx < 0) { ast_hangup(chan); goto quit; } ast_channel_lock(chan); pickup_cfg = ast_get_chan_features_pickup_config(chan); 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); } ast_channel_unlock(chan); analog_dsp_reset_and_flush_digits(p); switch (p->sig) { case ANALOG_SIG_FEATD: case ANALOG_SIG_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_EMWINK: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: case ANALOG_SIG_SFWINK: if (analog_wink(p, idx)) goto quit; /* Fall through */ case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_SF: case ANALOG_SIG_FGC_CAMA: res = analog_play_tone(p, idx, -1); analog_dsp_reset_and_flush_digits(p); /* set digit mode appropriately */ if (ANALOG_NEED_MFDETECT(p)) { analog_dsp_set_digitmode(p, ANALOG_DIGITMODE_MF); } else { analog_dsp_set_digitmode(p, ANALOG_DIGITMODE_DTMF); } memset(dtmfbuf, 0, sizeof(dtmfbuf)); /* Wait for the first digit only if immediate=no */ if (!p->immediate) { /* Wait for the first digit (up to 5 seconds). */ res = ast_waitfordigit(chan, 5000); } else { res = 0; } if (res > 0) { /* save first char */ dtmfbuf[0] = res; switch (p->sig) { case ANALOG_SIG_FEATD: case ANALOG_SIG_SF_FEATD: res = analog_my_getsigstr(chan, dtmfbuf + 1, "*", 3000); if (res > 0) { res = analog_my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "*", 3000); } if (res < 1) { analog_dsp_reset_and_flush_digits(p); } break; case ANALOG_SIG_FEATDMF_TA: res = analog_my_getsigstr(chan, dtmfbuf + 1, "#", 3000); if (res < 1) { analog_dsp_reset_and_flush_digits(p); } if (analog_wink(p, idx)) { goto quit; } dtmfbuf[0] = 0; /* Wait for the first digit (up to 5 seconds). */ res = ast_waitfordigit(chan, 5000); if (res <= 0) { break; } dtmfbuf[0] = res; /* fall through intentionally */ case ANALOG_SIG_FEATDMF: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_SF_FEATDMF: res = analog_my_getsigstr(chan, dtmfbuf + 1, "#", 3000); /* if international caca, do it again to get real ANO */ if ((p->sig == ANALOG_SIG_FEATDMF) && (dtmfbuf[1] != '0') && (strlen(dtmfbuf) != 14)) { if (analog_wink(p, idx)) { goto quit; } dtmfbuf[0] = 0; /* Wait for the first digit (up to 5 seconds). */ res = ast_waitfordigit(chan, 5000); if (res <= 0) { break; } dtmfbuf[0] = res; res = analog_my_getsigstr(chan, dtmfbuf + 1, "#", 3000); } if (res > 0) { /* if E911, take off hook */ if (p->sig == ANALOG_SIG_E911) { analog_off_hook(p); } res = analog_my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "#", 3000); } if (res < 1) { analog_dsp_reset_and_flush_digits(p); } break; case ANALOG_SIG_FEATB: case ANALOG_SIG_SF_FEATB: res = analog_my_getsigstr(chan, dtmfbuf + 1, "#", 3000); if (res < 1) { analog_dsp_reset_and_flush_digits(p); } break; case ANALOG_SIG_EMWINK: /* if we received a '*', we are actually receiving Feature Group D dial syntax, so use that mode; otherwise, fall through to normal mode */ if (res == '*') { res = analog_my_getsigstr(chan, dtmfbuf + 1, "*", 3000); if (res > 0) { res = analog_my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "*", 3000); } if (res < 1) { analog_dsp_reset_and_flush_digits(p); } break; } default: /* If we got the first digit, get the rest */ len = 1; dtmfbuf[len] = '\0'; while ((len < AST_MAX_EXTENSION-1) && ast_matchmore_extension(chan, ast_channel_context(chan), dtmfbuf, 1, p->cid_num)) { if (ast_exists_extension(chan, ast_channel_context(chan), dtmfbuf, 1, p->cid_num)) { timeout = analog_matchdigittimeout; } else { timeout = analog_gendigittimeout; } res = ast_waitfordigit(chan, timeout); if (res < 0) { ast_debug(1, "waitfordigit returned < 0...\n"); ast_hangup(chan); goto quit; } else if (res) { dtmfbuf[len++] = res; dtmfbuf[len] = '\0'; } else { break; } } break; } } if (res == -1) { ast_log(LOG_WARNING, "getdtmf on channel %d: %s\n", p->channel, strerror(errno)); ast_hangup(chan); goto quit; } else if (res < 0) { ast_debug(1, "Got hung up before digits finished\n"); ast_hangup(chan); goto quit; } if (p->sig == ANALOG_SIG_FGC_CAMA) { char anibuf[100]; if (ast_safe_sleep(chan,1000) == -1) { ast_hangup(chan); goto quit; } analog_off_hook(p); analog_dsp_set_digitmode(p, ANALOG_DIGITMODE_MF); res = analog_my_getsigstr(chan, anibuf, "#", 10000); if ((res > 0) && (strlen(anibuf) > 2)) { if (anibuf[strlen(anibuf) - 1] == '#') { anibuf[strlen(anibuf) - 1] = 0; } ast_set_callerid(chan, anibuf + 2, NULL, anibuf + 2); } analog_dsp_set_digitmode(p, ANALOG_DIGITMODE_DTMF); } ast_copy_string(exten, dtmfbuf, sizeof(exten)); if (ast_strlen_zero(exten)) { ast_copy_string(exten, "s", sizeof(exten)); } if (p->sig == ANALOG_SIG_FEATD || p->sig == ANALOG_SIG_EMWINK) { /* Look for Feature Group D on all E&M Wink and Feature Group D trunks */ if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "*"); s2 = strsep(&stringp, "*"); if (s2) { if (!ast_strlen_zero(p->cid_num)) { ast_set_callerid(chan, p->cid_num, NULL, p->cid_num); } else { ast_set_callerid(chan, s1, NULL, s1); } ast_copy_string(exten, s2, sizeof(exten)); } else { ast_copy_string(exten, s1, sizeof(exten)); } } else if (p->sig == ANALOG_SIG_FEATD) { ast_log(LOG_WARNING, "Got a non-Feature Group D input on channel %d. Assuming E&M Wink instead\n", p->channel); } } if ((p->sig == ANALOG_SIG_FEATDMF) || (p->sig == ANALOG_SIG_FEATDMF_TA)) { if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "#"); s2 = strsep(&stringp, "#"); if (s2) { if (!ast_strlen_zero(p->cid_num)) { ast_set_callerid(chan, p->cid_num, NULL, p->cid_num); } else { if (*(s1 + 2)) { ast_set_callerid(chan, s1 + 2, NULL, s1 + 2); } } ast_copy_string(exten, s2 + 1, sizeof(exten)); } else { ast_copy_string(exten, s1 + 2, sizeof(exten)); } } else { ast_log(LOG_WARNING, "Got a non-Feature Group D input on channel %d. Assuming E&M Wink instead\n", p->channel); } } if ((p->sig == ANALOG_SIG_E911) || (p->sig == ANALOG_SIG_FGC_CAMAMF)) { if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "#"); s2 = strsep(&stringp, "#"); if (s2 && (*(s2 + 1) == '0')) { if (*(s2 + 2)) { ast_set_callerid(chan, s2 + 2, NULL, s2 + 2); } } if (s1) { ast_copy_string(exten, s1, sizeof(exten)); } else { ast_copy_string(exten, "911", sizeof(exten)); } } else { ast_log(LOG_WARNING, "Got a non-E911/FGC CAMA input on channel %d. Assuming E&M Wink instead\n", p->channel); } } if (p->sig == ANALOG_SIG_FEATB) { if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "#"); ast_copy_string(exten, exten2 + 1, sizeof(exten)); } else { ast_log(LOG_WARNING, "Got a non-Feature Group B input on channel %d. Assuming E&M Wink instead\n", p->channel); } } if ((p->sig == ANALOG_SIG_FEATDMF) || (p->sig == ANALOG_SIG_FEATDMF_TA)) { analog_wink(p, idx); /* * Some switches require a minimum guard time between the last * FGD wink and something that answers immediately. This * ensures it. */ if (ast_safe_sleep(chan, 100)) { ast_hangup(chan); goto quit; } } analog_set_echocanceller(p, 1); analog_dsp_set_digitmode(p, ANALOG_DIGITMODE_DTMF); if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, ast_channel_caller(chan)->id.number.valid ? ast_channel_caller(chan)->id.number.str : NULL)) { ast_channel_exten_set(chan, exten); analog_dsp_reset_and_flush_digits(p); res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); } goto quit; } else { ast_verb(3, "Unknown extension '%s' in context '%s' requested\n", exten, ast_channel_context(chan)); sleep(2); res = analog_play_tone(p, idx, ANALOG_TONE_INFO); if (res < 0) { ast_log(LOG_WARNING, "Unable to start special tone on %d\n", p->channel); } else { sleep(1); } res = ast_streamfile(chan, "ss-noservice", ast_channel_language(chan)); if (res >= 0) { ast_waitstream(chan, ""); } res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); ast_hangup(chan); goto quit; } break; case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: /* Read the first digit */ timeout = analog_firstdigittimeout; /* If starting a threeway call, never timeout on the first digit so someone can use flash-hook as a "hold" feature */ if (p->subs[ANALOG_SUB_THREEWAY].owner) { timeout = 999999; } while (len < AST_MAX_EXTENSION-1) { int is_exten_parking = 0; /* Read digit unless it's supposed to be immediate, in which case the only answer is 's' */ if (p->immediate) { res = 's'; } else { res = ast_waitfordigit(chan, timeout); } timeout = 0; if (res < 0) { ast_debug(1, "waitfordigit returned < 0...\n"); res = analog_play_tone(p, idx, -1); ast_hangup(chan); goto quit; } else if (res) { ast_debug(1,"waitfordigit returned '%c' (%d), timeout = %d\n", res, res, timeout); exten[len++]=res; exten[len] = '\0'; } if (!ast_ignore_pattern(ast_channel_context(chan), exten)) { analog_play_tone(p, idx, -1); } else { analog_play_tone(p, idx, ANALOG_TONE_DIALTONE); } if (ast_parking_provider_registered()) { is_exten_parking = ast_parking_is_exten_park(ast_channel_context(chan), exten); } if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num) && !is_exten_parking) { if (!res || !ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { if (getforward) { /* Record this as the forwarding extension */ ast_copy_string(p->call_forward, exten, sizeof(p->call_forward)); ast_verb(3, "Setting call forward to '%s' on channel %d\n", p->call_forward, p->channel); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); if (res) { break; } usleep(500000); res = analog_play_tone(p, idx, -1); sleep(1); memset(exten, 0, sizeof(exten)); res = analog_play_tone(p, idx, ANALOG_TONE_DIALTONE); len = 0; getforward = 0; } else { res = analog_play_tone(p, idx, -1); ast_channel_lock(chan); ast_channel_exten_set(chan, exten); if (!ast_strlen_zero(p->cid_num)) { if (!p->hidecallerid) { ast_set_callerid(chan, p->cid_num, NULL, p->cid_num); } else { ast_set_callerid(chan, NULL, NULL, p->cid_num); } } if (!ast_strlen_zero(p->cid_name)) { if (!p->hidecallerid) { ast_set_callerid(chan, NULL, p->cid_name, NULL); } } ast_setstate(chan, AST_STATE_RING); ast_channel_unlock(chan); analog_set_echocanceller(p, 1); res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); } goto quit; } } else { /* It's a match, but they just typed a digit, and there is an ambiguous match, so just set the timeout to analog_matchdigittimeout and wait some more */ timeout = analog_matchdigittimeout; } } else if (res == 0) { ast_debug(1, "not enough digits (and no ambiguous match)...\n"); res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); analog_wait_event(p); ast_hangup(chan); goto quit; } else if (p->callwaiting && !strcmp(exten, "*70")) { ast_verb(3, "Disabling call waiting on %s\n", ast_channel_name(chan)); /* Disable call waiting if enabled */ analog_set_callwaiting(p, 0); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } len = 0; memset(exten, 0, sizeof(exten)); timeout = analog_firstdigittimeout; } else if (!strcmp(exten, pickupexten)) { /* Scan all channels and see if there are any * ringing channels that have call groups * that equal this channels pickup group */ if (idx == ANALOG_SUB_REAL) { /* Switch us from Third call to Call Wait */ if (p->subs[ANALOG_SUB_THREEWAY].owner) { /* If you make a threeway call and the *8# a call, it should actually look like a callwait */ analog_alloc_sub(p, ANALOG_SUB_CALLWAIT); analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_THREEWAY); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); } analog_set_echocanceller(p, 1); if (ast_pickup_call(chan)) { ast_debug(1, "No call pickup possible...\n"); res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); analog_wait_event(p); } ast_hangup(chan); goto quit; } else { ast_log(LOG_WARNING, "Huh? Got *8# on call not on real\n"); ast_hangup(chan); goto quit; } } else if (!p->hidecallerid && !strcmp(exten, "*67")) { ast_verb(3, "Disabling Caller*ID on %s\n", ast_channel_name(chan)); /* Disable Caller*ID if enabled */ p->hidecallerid = 1; ast_party_number_free(&ast_channel_caller(chan)->id.number); ast_party_number_init(&ast_channel_caller(chan)->id.number); ast_party_name_free(&ast_channel_caller(chan)->id.name); ast_party_name_init(&ast_channel_caller(chan)->id.name); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } len = 0; memset(exten, 0, sizeof(exten)); timeout = analog_firstdigittimeout; } else if (p->callreturn && !strcmp(exten, "*69")) { res = 0; if (!ast_strlen_zero(p->lastcid_num)) { res = ast_say_digit_str(chan, p->lastcid_num, "", ast_channel_language(chan)); } if (!res) { res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); } break; } else if (!strcmp(exten, "*78")) { /* Do not disturb enabled */ analog_dnd(p, 1); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; } else if (!strcmp(exten, "*79")) { /* Do not disturb disabled */ analog_dnd(p, 0); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; } else if (p->cancallforward && !strcmp(exten, "*72")) { res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); getforward = 1; memset(exten, 0, sizeof(exten)); len = 0; } else if (p->cancallforward && !strcmp(exten, "*73")) { ast_verb(3, "Cancelling call forwarding on channel %d\n", p->channel); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); memset(p->call_forward, 0, sizeof(p->call_forward)); getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; } else if ((p->transfer || p->canpark) && is_exten_parking && p->subs[ANALOG_SUB_THREEWAY].owner) { struct ast_bridge_channel *bridge_channel; /* * This is a three way call, the main call being a real channel, * and we're parking the first call. */ ast_channel_lock(p->subs[ANALOG_SUB_THREEWAY].owner); bridge_channel = ast_channel_get_bridge_channel(p->subs[ANALOG_SUB_THREEWAY].owner); ast_channel_unlock(p->subs[ANALOG_SUB_THREEWAY].owner); if (bridge_channel) { if (!ast_parking_blind_transfer_park(bridge_channel, ast_channel_context(chan), exten, NULL, NULL)) { /* * Swap things around between the three-way and real call so we * can hear where the channel got parked. */ analog_lock_private(p); analog_set_new_owner(p, p->subs[ANALOG_SUB_THREEWAY].owner); analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); analog_unlock_private(p); ast_verb(3, "%s: Parked call\n", ast_channel_name(chan)); ast_hangup(chan); ao2_ref(bridge_channel, -1); goto quit; } ao2_ref(bridge_channel, -1); } break; } else if (!ast_strlen_zero(p->lastcid_num) && !strcmp(exten, "*60")) { ast_verb(3, "Blacklisting number %s\n", p->lastcid_num); res = ast_db_put("blacklist", p->lastcid_num, "1"); if (!res) { res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); memset(exten, 0, sizeof(exten)); len = 0; } } else if (p->hidecallerid && !strcmp(exten, "*82")) { ast_verb(3, "Enabling Caller*ID on %s\n", ast_channel_name(chan)); /* Enable Caller*ID if enabled */ p->hidecallerid = 0; ast_set_callerid(chan, p->cid_num, p->cid_name, NULL); res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } len = 0; memset(exten, 0, sizeof(exten)); timeout = analog_firstdigittimeout; } else if (!strcmp(exten, "*0")) { struct ast_channel *nbridge = p->subs[ANALOG_SUB_THREEWAY].owner; struct analog_pvt *pbridge = NULL; /* set up the private struct of the bridged one, if any */ if (nbridge) { pbridge = analog_get_bridged_channel(nbridge); } if (pbridge && ISTRUNK(pbridge)) { /* Clear out the dial buffer */ p->dop.dialstr[0] = '\0'; /* flash hookswitch */ if ((analog_flash(pbridge) == -1) && (errno != EINPROGRESS)) { ast_log(LOG_WARNING, "Unable to flash-hook bridged trunk from channel %s: %s\n", ast_channel_name(nbridge), strerror(errno)); } analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_THREEWAY); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner); ast_hangup(chan); goto quit; } else { analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); analog_wait_event(p); analog_play_tone(p, idx, -1); analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_THREEWAY); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); ast_hangup(chan); goto quit; } } else if (!ast_canmatch_extension(chan, ast_channel_context(chan), exten, 1, ast_channel_caller(chan)->id.number.valid ? ast_channel_caller(chan)->id.number.str : NULL) && !analog_canmatch_featurecode(pickupexten, exten)) { ast_debug(1, "Can't match %s from '%s' in context %s\n", exten, ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str ? ast_channel_caller(chan)->id.number.str : "", ast_channel_context(chan)); break; } if (!timeout) { timeout = analog_gendigittimeout; } if (len && !ast_ignore_pattern(ast_channel_context(chan), exten)) { analog_play_tone(p, idx, -1); } } break; case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: /* check for SMDI messages */ if (p->use_smdi && p->smdi_iface) { smdi_msg = ast_smdi_md_message_wait(p->smdi_iface, ANALOG_SMDI_MD_WAIT_TIMEOUT); if (smdi_msg != NULL) { ast_channel_exten_set(chan, smdi_msg->fwd_st); if (smdi_msg->type == 'B') pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "b"); else if (smdi_msg->type == 'N') pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "u"); ast_debug(1, "Received SMDI message on %s\n", ast_channel_name(chan)); } else { ast_log(LOG_WARNING, "SMDI enabled but no SMDI message present\n"); } } if (p->use_callerid && (p->cid_signalling == CID_SIG_SMDI && smdi_msg)) { number = smdi_msg->calling_st; /* If we want caller id, we're in a prering state due to a polarity reversal * and we're set to use a polarity reversal to trigger the start of caller id, * grab the caller id and wait for ringing to start... */ } else if (p->use_callerid && (ast_channel_state(chan) == AST_STATE_PRERING && (p->cid_start == ANALOG_CID_START_POLARITY || p->cid_start == ANALOG_CID_START_POLARITY_IN || p->cid_start == ANALOG_CID_START_DTMF_NOALERT))) { /* If set to use DTMF CID signalling, listen for DTMF */ if (p->cid_signalling == CID_SIG_DTMF) { int k = 0; int oldlinearity; int timeout_ms; int ms; struct timeval start = ast_tvnow(); cs = NULL; ast_debug(1, "Receiving DTMF cid on channel %s\n", ast_channel_name(chan)); oldlinearity = analog_set_linear_mode(p, idx, 0); /* * We are the only party interested in the Rx stream since * we have not answered yet. We don't need or even want DTMF * emulation. The DTMF digits can come so fast that emulation * can drop some of them. */ ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY); timeout_ms = 4000;/* This is a typical OFF time between rings. */ for (;;) { struct ast_frame *f; ms = ast_remaining_ms(start, timeout_ms); res = ast_waitfor(chan, ms); if (res <= 0) { /* * We do not need to restore the analog_set_linear_mode() * or AST_FLAG_END_DTMF_ONLY flag settings since we * are hanging up the channel. */ ast_log(LOG_WARNING, "DTMFCID timed out waiting for ring. " "Exiting simple switch\n"); ast_hangup(chan); goto quit; } f = ast_read(chan); if (!f) { break; } if (f->frametype == AST_FRAME_DTMF) { if (k < ARRAY_LEN(dtmfbuf) - 1) { dtmfbuf[k++] = f->subclass.integer; } ast_debug(1, "CID got digit '%c'\n", f->subclass.integer); start = ast_tvnow(); } ast_frfree(f); if (ast_channel_state(chan) == AST_STATE_RING || ast_channel_state(chan) == AST_STATE_RINGING) { break; /* Got ring */ } } ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY); dtmfbuf[k] = '\0'; analog_set_linear_mode(p, idx, oldlinearity); /* Got cid and ring. */ ast_debug(1, "CID got string '%s'\n", dtmfbuf); callerid_get_dtmf(dtmfbuf, dtmfcid, &flags); ast_debug(1, "CID is '%s', flags %d\n", dtmfcid, flags); /* If first byte is NULL, we have no cid */ if (!ast_strlen_zero(dtmfcid)) { number = dtmfcid; } else { number = NULL; } /* If set to use V23 Signalling, launch our FSK gubbins and listen for it */ } else if ((p->cid_signalling == CID_SIG_V23) || (p->cid_signalling == CID_SIG_V23_JP)) { int timeout = 10000; /* Ten seconds */ struct timeval start = ast_tvnow(); enum analog_event ev; namebuf[0] = 0; numbuf[0] = 0; if (!analog_start_cid_detect(p, p->cid_signalling)) { int off_ms; int ms; struct timeval off_start; while (1) { res = analog_get_callerid(p, namebuf, numbuf, &ev, timeout - ast_tvdiff_ms(ast_tvnow(), start)); if (res == 0) { break; } if (res == 1) { if (ev == ANALOG_EVENT_NOALARM) { analog_set_alarm(p, 0); } if (p->cid_signalling == CID_SIG_V23_JP) { if (ev == ANALOG_EVENT_RINGBEGIN) { analog_off_hook(p); usleep(1); } } else { ev = ANALOG_EVENT_NONE; break; } } if (ast_tvdiff_ms(ast_tvnow(), start) > timeout) break; } name = namebuf; number = numbuf; analog_stop_cid_detect(p); if (p->cid_signalling == CID_SIG_V23_JP) { res = analog_on_hook(p); usleep(1); } /* Finished with Caller*ID, now wait for a ring to make sure there really is a call coming */ off_start = ast_tvnow(); off_ms = 4000;/* This is a typical OFF time between rings. */ while ((ms = ast_remaining_ms(off_start, off_ms))) { struct ast_frame *f; res = ast_waitfor(chan, ms); if (res <= 0) { ast_log(LOG_WARNING, "CID timed out waiting for ring. " "Exiting simple switch\n"); ast_hangup(chan); goto quit; } if (!(f = ast_read(chan))) { ast_log(LOG_WARNING, "Hangup received waiting for ring. Exiting simple switch\n"); ast_hangup(chan); goto quit; } ast_frfree(f); if (ast_channel_state(chan) == AST_STATE_RING || ast_channel_state(chan) == AST_STATE_RINGING) break; /* Got ring */ } if (analog_distinctive_ring(chan, p, idx, NULL)) { goto quit; } if (res < 0) { ast_log(LOG_WARNING, "CallerID returned with error on channel '%s'\n", ast_channel_name(chan)); } } else { ast_log(LOG_WARNING, "Unable to get caller ID space\n"); } } else { ast_log(LOG_WARNING, "Channel %s in prering " "state, but I have nothing to do. " "Terminating simple switch, should be " "restarted by the actual ring.\n", ast_channel_name(chan)); ast_hangup(chan); goto quit; } } else if (p->use_callerid && p->cid_start == ANALOG_CID_START_RING) { int timeout = 10000; /* Ten seconds */ struct timeval start = ast_tvnow(); enum analog_event ev; int curRingData[RING_PATTERNS] = { 0 }; int receivedRingT = 0; namebuf[0] = 0; numbuf[0] = 0; if (!analog_start_cid_detect(p, p->cid_signalling)) { while (1) { res = analog_get_callerid(p, namebuf, numbuf, &ev, timeout - ast_tvdiff_ms(ast_tvnow(), start)); if (res == 0) { break; } if (res == 1 || res == 2) { if (ev == ANALOG_EVENT_NOALARM) { analog_set_alarm(p, 0); } else if (ev == ANALOG_EVENT_POLARITY && p->hanguponpolarityswitch && p->polarity == POLARITY_REV) { ast_debug(1, "Hanging up due to polarity reversal on channel %d while detecting callerid\n", p->channel); p->polarity = POLARITY_IDLE; ast_hangup(chan); goto quit; } else if (ev != ANALOG_EVENT_NONE && ev != ANALOG_EVENT_RINGBEGIN && ev != ANALOG_EVENT_RINGOFFHOOK) { break; } if (res != 2) { /* Let us detect callerid when the telco uses distinctive ring */ curRingData[receivedRingT] = p->ringt; if (p->ringt < p->ringt_base/2) { break; } /* Increment the ringT counter so we can match it against values in chan_dahdi.conf for distinctive ring */ if (++receivedRingT == RING_PATTERNS) { break; } } } if (ast_tvdiff_ms(ast_tvnow(), start) > timeout) { break; } } name = namebuf; number = numbuf; analog_stop_cid_detect(p); if (analog_distinctive_ring(chan, p, idx, curRingData)) { goto quit; } if (res < 0) { ast_log(LOG_WARNING, "CallerID returned with error on channel '%s'\n", ast_channel_name(chan)); } } else { ast_log(LOG_WARNING, "Unable to get caller ID space\n"); } } else { cs = NULL; } if (number) { ast_shrink_phone_number(number); } ast_set_callerid(chan, number, name, number); if (cs) { callerid_free(cs); } analog_handle_notify_message(chan, p, flags, -1); ast_channel_lock(chan); ast_setstate(chan, AST_STATE_RING); ast_channel_rings_set(chan, 1); ast_channel_unlock(chan); analog_set_ringtimeout(p, p->ringt_base); res = ast_pbx_run(chan); if (res) { ast_hangup(chan); ast_log(LOG_WARNING, "PBX exited non-zero\n"); } goto quit; default: ast_log(LOG_WARNING, "Don't know how to handle simple switch with signalling %s on channel %d\n", analog_sigtype_to_str(p->sig), p->channel); break; } res = analog_play_tone(p, idx, ANALOG_TONE_CONGESTION); if (res < 0) { ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", p->channel); } ast_hangup(chan); quit: ao2_cleanup(smdi_msg); analog_decrease_ss_count(); return NULL; } int analog_ss_thread_start(struct analog_pvt *p, struct ast_channel *chan) { pthread_t threadid; return ast_pthread_create_detached(&threadid, NULL, __analog_ss_thread, p); } static void analog_publish_channel_alarm_clear(int channel) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); ast_log(LOG_NOTICE, "Alarm cleared on channel %d\n", channel); body = ast_json_pack("{s: i}", "Channel", channel); if (!body) { return; } ast_manager_publish_event("AlarmClear", EVENT_FLAG_SYSTEM, body); } static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_channel *ast) { int res, x; int mysig; enum analog_sub idx; char *c; pthread_t threadid; struct ast_channel *chan; struct ast_frame *f; struct ast_control_pvt_cause_code *cause_code = NULL; int data_size = sizeof(*cause_code); char *subclass = NULL; ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); idx = analog_get_index(ast, p, 0); if (idx < 0) { return &ast_null_frame; } if (idx != ANALOG_SUB_REAL) { ast_log(LOG_ERROR, "We got an event on a non real sub. Fix it!\n"); } mysig = p->sig; if (p->outsigmod > -1) { mysig = p->outsigmod; } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.datalen = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.src = "dahdi_handle_event"; p->subs[idx].f.data.ptr = NULL; f = &p->subs[idx].f; res = analog_get_event(p); ast_debug(1, "Got event %s(%d) on channel %d (index %u)\n", analog_event2str(res), res, p->channel, idx); if (res & (ANALOG_EVENT_PULSEDIGIT | ANALOG_EVENT_DTMFUP)) { analog_set_pulsedial(p, (res & ANALOG_EVENT_PULSEDIGIT) ? 1 : 0); ast_debug(1, "Detected %sdigit '%c'\n", (res & ANALOG_EVENT_PULSEDIGIT) ? "pulse ": "", res & 0xff); analog_confmute(p, 0); p->subs[idx].f.frametype = AST_FRAME_DTMF_END; p->subs[idx].f.subclass.integer = res & 0xff; analog_handle_dtmf(p, ast, idx, &f); return f; } if (res & ANALOG_EVENT_DTMFDOWN) { ast_debug(1, "DTMF Down '%c'\n", res & 0xff); /* Mute conference */ analog_confmute(p, 1); p->subs[idx].f.frametype = AST_FRAME_DTMF_BEGIN; p->subs[idx].f.subclass.integer = res & 0xff; analog_handle_dtmf(p, ast, idx, &f); return f; } switch (res) { case ANALOG_EVENT_ALARM: case ANALOG_EVENT_POLARITY: case ANALOG_EVENT_ONHOOK: /* add length of "ANALOG " */ data_size += 7; subclass = analog_event2str(res); data_size += strlen(subclass); cause_code = ast_alloca(data_size); memset(cause_code, 0, data_size); cause_code->ast_cause = AST_CAUSE_NORMAL_CLEARING; ast_copy_string(cause_code->chan_name, ast_channel_name(ast), AST_CHANNEL_NAME); snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "ANALOG %s", subclass); break; default: break; } switch (res) { case ANALOG_EVENT_EC_DISABLED: ast_verb(3, "Channel %d echo canceler disabled due to CED detection\n", p->channel); analog_set_echocanceller(p, 0); break; #ifdef HAVE_DAHDI_ECHOCANCEL_FAX_MODE case ANALOG_EVENT_TX_CED_DETECTED: ast_verb(3, "Channel %d detected a CED tone towards the network.\n", p->channel); break; case ANALOG_EVENT_RX_CED_DETECTED: ast_verb(3, "Channel %d detected a CED tone from the network.\n", p->channel); break; case ANALOG_EVENT_EC_NLP_DISABLED: ast_verb(3, "Channel %d echo canceler disabled its NLP.\n", p->channel); break; case ANALOG_EVENT_EC_NLP_ENABLED: ast_verb(3, "Channel %d echo canceler enabled its NLP.\n", p->channel); break; #endif case ANALOG_EVENT_PULSE_START: /* Stop tone if there's a pulse start and the PBX isn't started */ if (!ast_channel_pbx(ast)) analog_play_tone(p, ANALOG_SUB_REAL, -1); break; case ANALOG_EVENT_DIALCOMPLETE: if (p->inalarm) { break; } x = analog_is_dialing(p, idx); if (!x) { /* if not still dialing in driver */ analog_set_echocanceller(p, 1); if (p->echobreak) { analog_train_echocanceller(p); ast_copy_string(p->dop.dialstr, p->echorest, sizeof(p->dop.dialstr)); p->dop.op = ANALOG_DIAL_OP_REPLACE; analog_dial_digits(p, ANALOG_SUB_REAL, &p->dop); p->echobreak = 0; } else { analog_set_dialing(p, 0); if ((mysig == ANALOG_SIG_E911) || (mysig == ANALOG_SIG_FGC_CAMA) || (mysig == ANALOG_SIG_FGC_CAMAMF)) { /* if thru with dialing after offhook */ if (ast_channel_state(ast) == AST_STATE_DIALING_OFFHOOK) { ast_setstate(ast, AST_STATE_UP); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; break; } else { /* if to state wait for offhook to dial rest */ /* we now wait for off hook */ ast_setstate(ast,AST_STATE_DIALING_OFFHOOK); } } if (ast_channel_state(ast) == AST_STATE_DIALING) { if (analog_have_progressdetect(p)) { ast_debug(1, "Done dialing, but waiting for progress detection before doing more...\n"); } else if (analog_check_confirmanswer(p) || (!p->dialednone && ((mysig == ANALOG_SIG_EM) || (mysig == ANALOG_SIG_EM_E1) || (mysig == ANALOG_SIG_EMWINK) || (mysig == ANALOG_SIG_FEATD) || (mysig == ANALOG_SIG_FEATDMF_TA) || (mysig == ANALOG_SIG_FEATDMF) || (mysig == ANALOG_SIG_E911) || (mysig == ANALOG_SIG_FGC_CAMA) || (mysig == ANALOG_SIG_FGC_CAMAMF) || (mysig == ANALOG_SIG_FEATB) || (mysig == ANALOG_SIG_SF) || (mysig == ANALOG_SIG_SFWINK) || (mysig == ANALOG_SIG_SF_FEATD) || (mysig == ANALOG_SIG_SF_FEATDMF) || (mysig == ANALOG_SIG_SF_FEATB)))) { ast_setstate(ast, AST_STATE_RINGING); } else if (!p->answeronpolarityswitch) { ast_setstate(ast, AST_STATE_UP); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; /* If aops=0 and hops=1, this is necessary */ p->polarity = POLARITY_REV; } else { /* Start clean, so we can catch the change to REV polarity when party answers */ p->polarity = POLARITY_IDLE; } } } } break; case ANALOG_EVENT_ALARM: analog_set_alarm(p, 1); analog_get_and_handle_alarms(p); cause_code->ast_cause = AST_CAUSE_NETWORK_OUT_OF_ORDER; case ANALOG_EVENT_ONHOOK: ast_queue_control_data(ast, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size); ast_channel_hangupcause_hash_set(ast, cause_code, data_size); switch (p->sig) { case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: analog_start_polarityswitch(p); p->fxsoffhookstate = 0; p->onhooktime = time(NULL); p->msgstate = -1; /* Check for some special conditions regarding call waiting */ if (idx == ANALOG_SUB_REAL) { /* The normal line was hung up */ if (p->subs[ANALOG_SUB_CALLWAIT].owner) { /* Need to hold the lock for real-call, private, and call-waiting call */ analog_lock_sub_owner(p, ANALOG_SUB_CALLWAIT); if (!p->subs[ANALOG_SUB_CALLWAIT].owner) { /* * The call waiting call dissappeared. * This is now a normal hangup. */ analog_set_echocanceller(p, 0); return NULL; } /* There's a call waiting call, so ring the phone, but make it unowned in the mean time */ analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_REAL); ast_verb(3, "Channel %d still has (callwait) call, ringing phone\n", p->channel); analog_unalloc_sub(p, ANALOG_SUB_CALLWAIT); analog_stop_callwait(p); analog_set_new_owner(p, NULL); /* Don't start streaming audio yet if the incoming call isn't up yet */ if (ast_channel_state(p->subs[ANALOG_SUB_REAL].owner) != AST_STATE_UP) { analog_set_dialing(p, 1); } /* Unlock the call-waiting call that we swapped to real-call. */ ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); analog_ring(p); } else if (p->subs[ANALOG_SUB_THREEWAY].owner) { unsigned int mssinceflash; /* Need to hold the lock for real-call, private, and 3-way call */ analog_lock_sub_owner(p, ANALOG_SUB_THREEWAY); if (!p->subs[ANALOG_SUB_THREEWAY].owner) { ast_log(LOG_NOTICE, "Whoa, threeway disappeared kinda randomly.\n"); /* Just hangup */ return NULL; } if (p->owner != ast) { ast_channel_unlock(p->subs[ANALOG_SUB_THREEWAY].owner); ast_log(LOG_WARNING, "This isn't good...\n"); /* Just hangup */ return NULL; } mssinceflash = ast_tvdiff_ms(ast_tvnow(), p->flashtime); ast_debug(1, "Last flash was %u ms ago\n", mssinceflash); if (mssinceflash < MIN_MS_SINCE_FLASH) { /* It hasn't been long enough since the last flashook. This is probably a bounce on hanging up. Hangup both channels now */ ast_debug(1, "Looks like a bounced flash, hanging up both calls on %d\n", p->channel); ast_queue_hangup_with_cause(p->subs[ANALOG_SUB_THREEWAY].owner, AST_CAUSE_NO_ANSWER); ast_softhangup_nolock(p->subs[ANALOG_SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(p->subs[ANALOG_SUB_THREEWAY].owner); } else if ((ast_channel_pbx(ast)) || (ast_channel_state(ast) == AST_STATE_UP)) { if (p->transfer) { /* In any case this isn't a threeway call anymore */ analog_set_inthreeway(p, ANALOG_SUB_REAL, 0); analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 0); /* Only attempt transfer if the phone is ringing; why transfer to busy tone eh? */ if (!p->transfertobusy && ast_channel_state(ast) == AST_STATE_BUSY) { /* Swap subs and dis-own channel */ analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); /* Unlock the 3-way call that we swapped to real-call. */ ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); analog_set_new_owner(p, NULL); /* Ring the phone */ analog_ring(p); } else if (!analog_attempt_transfer(p)) { /* * Transfer successful. Don't actually hang up at this point. * Let our channel legs of the calls die off as the transfer * percolates through the core. */ break; } } else { ast_softhangup_nolock(p->subs[ANALOG_SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); ast_channel_unlock(p->subs[ANALOG_SUB_THREEWAY].owner); } } else { /* Swap subs and dis-own channel */ analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); /* Unlock the 3-way call that we swapped to real-call. */ ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); analog_set_new_owner(p, NULL); /* Ring the phone */ analog_ring(p); } } } else { ast_log(LOG_WARNING, "Got a hangup and my index is %u?\n", idx); } /* Fall through */ default: analog_set_echocanceller(p, 0); return NULL; } break; case ANALOG_EVENT_RINGOFFHOOK: if (p->inalarm) { break; } /* for E911, its supposed to wait for offhook then dial the second half of the dial string */ if (((mysig == ANALOG_SIG_E911) || (mysig == ANALOG_SIG_FGC_CAMA) || (mysig == ANALOG_SIG_FGC_CAMAMF)) && (ast_channel_state(ast) == AST_STATE_DIALING_OFFHOOK)) { c = strchr(p->dialdest, '/'); if (c) { c++; } else { c = p->dialdest; } if (*c) { snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*0%s#", c); } else { ast_copy_string(p->dop.dialstr,"M*2#", sizeof(p->dop.dialstr)); } if (strlen(p->dop.dialstr) > 4) { memset(p->echorest, 'w', sizeof(p->echorest) - 1); strcpy(p->echorest + (p->echotraining / 401) + 1, p->dop.dialstr + strlen(p->dop.dialstr) - 2); p->echorest[sizeof(p->echorest) - 1] = '\0'; p->echobreak = 1; p->dop.dialstr[strlen(p->dop.dialstr)-2] = '\0'; } else { p->echobreak = 0; } if (analog_dial_digits(p, ANALOG_SUB_REAL, &p->dop)) { analog_on_hook(p); return NULL; } analog_set_dialing(p, 1); return &p->subs[idx].f; } switch (p->sig) { case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: p->fxsoffhookstate = 1; switch (ast_channel_state(ast)) { case AST_STATE_RINGING: analog_set_echocanceller(p, 1); analog_train_echocanceller(p); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; /* Make sure it stops ringing */ analog_set_needringing(p, 0); analog_off_hook(p); ast_debug(1, "channel %d answered\n", p->channel); /* Cancel any running CallerID spill */ analog_cancel_cidspill(p); analog_set_dialing(p, 0); p->callwaitcas = 0; if (analog_check_confirmanswer(p)) { /* Ignore answer if "confirm answer" is enabled */ p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; } else if (!ast_strlen_zero(p->dop.dialstr)) { /* nick@dccinc.com 4/3/03 - fxo should be able to do deferred dialing */ res = analog_dial_digits(p, ANALOG_SUB_REAL, &p->dop); if (res) { p->dop.dialstr[0] = '\0'; return NULL; } else { ast_debug(1, "Sent FXO deferred digit string: %s\n", p->dop.dialstr); p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; analog_set_dialing(p, 1); } p->dop.dialstr[0] = '\0'; ast_setstate(ast, AST_STATE_DIALING); } else { ast_setstate(ast, AST_STATE_UP); analog_answer_polarityswitch(p); } return &p->subs[idx].f; case AST_STATE_DOWN: ast_setstate(ast, AST_STATE_RING); ast_channel_rings_set(ast, 1); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_OFFHOOK; ast_debug(1, "channel %d picked up\n", p->channel); return &p->subs[idx].f; case AST_STATE_UP: /* Make sure it stops ringing */ analog_off_hook(p); /* Okay -- probably call waiting */ ast_queue_unhold(p->owner); break; case AST_STATE_RESERVED: /* Start up dialtone */ if (analog_has_voicemail(p)) { res = analog_play_tone(p, ANALOG_SUB_REAL, ANALOG_TONE_STUTTER); } else { res = analog_play_tone(p, ANALOG_SUB_REAL, ANALOG_TONE_DIALTONE); } break; default: ast_log(LOG_WARNING, "FXO phone off hook in weird state %u??\n", ast_channel_state(ast)); } break; case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: if (ast_channel_state(ast) == AST_STATE_RING) { analog_set_ringtimeout(p, p->ringt_base); } /* Fall through */ case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_EMWINK: case ANALOG_SIG_FEATD: case ANALOG_SIG_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_SF: case ANALOG_SIG_SFWINK: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: switch (ast_channel_state(ast)) { case AST_STATE_PRERING: ast_setstate(ast, AST_STATE_RING); /* Fall through */ case AST_STATE_DOWN: case AST_STATE_RING: ast_debug(1, "Ring detected\n"); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_RING; break; case AST_STATE_RINGING: case AST_STATE_DIALING: if (p->outgoing) { ast_debug(1, "Line answered\n"); if (analog_check_confirmanswer(p)) { p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; } else { p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; ast_setstate(ast, AST_STATE_UP); } break; } /* Fall through */ default: ast_log(LOG_WARNING, "Ring/Off-hook in strange state %u on channel %d\n", ast_channel_state(ast), p->channel); break; } break; default: ast_log(LOG_WARNING, "Don't know how to handle ring/off hook for signalling %d\n", p->sig); break; } break; case ANALOG_EVENT_RINGBEGIN: switch (p->sig) { case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: if (ast_channel_state(ast) == AST_STATE_RING) { analog_set_ringtimeout(p, p->ringt_base); } break; default: break; } break; case ANALOG_EVENT_RINGEROFF: if (p->inalarm) break; ast_channel_rings_set(ast, ast_channel_rings(ast) + 1); if (ast_channel_rings(ast) == p->cidrings) { analog_send_callerid(p, 0, &p->caller); } if (ast_channel_rings(ast) > p->cidrings) { analog_cancel_cidspill(p); p->callwaitcas = 0; } p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_RINGING; break; case ANALOG_EVENT_RINGERON: break; case ANALOG_EVENT_NOALARM: analog_set_alarm(p, 0); analog_publish_channel_alarm_clear(p->channel); break; case ANALOG_EVENT_WINKFLASH: if (p->inalarm) { break; } /* Remember last time we got a flash-hook */ gettimeofday(&p->flashtime, NULL); switch (mysig) { case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: ast_debug(1, "Winkflash, index: %u, normal: %d, callwait: %d, thirdcall: %d\n", idx, analog_get_sub_fd(p, ANALOG_SUB_REAL), analog_get_sub_fd(p, ANALOG_SUB_CALLWAIT), analog_get_sub_fd(p, ANALOG_SUB_THREEWAY)); /* Cancel any running CallerID spill */ analog_cancel_cidspill(p); p->callwaitcas = 0; if (idx != ANALOG_SUB_REAL) { ast_log(LOG_WARNING, "Got flash hook with index %u on channel %d?!?\n", idx, p->channel); goto winkflashdone; } if (p->subs[ANALOG_SUB_CALLWAIT].owner) { /* Need to hold the lock for real-call, private, and call-waiting call */ analog_lock_sub_owner(p, ANALOG_SUB_CALLWAIT); if (!p->subs[ANALOG_SUB_CALLWAIT].owner) { /* * The call waiting call dissappeared. * Let's just ignore this flash-hook. */ ast_log(LOG_NOTICE, "Whoa, the call-waiting call disappeared.\n"); goto winkflashdone; } /* Swap to call-wait */ analog_swap_subs(p, ANALOG_SUB_REAL, ANALOG_SUB_CALLWAIT); analog_play_tone(p, ANALOG_SUB_REAL, -1); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); ast_debug(1, "Making %s the new owner\n", ast_channel_name(p->owner)); if (ast_channel_state(p->subs[ANALOG_SUB_REAL].owner) == AST_STATE_RINGING) { ast_setstate(p->subs[ANALOG_SUB_REAL].owner, AST_STATE_UP); ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_ANSWER); } analog_stop_callwait(p); /* Start music on hold if appropriate */ if (!p->subs[ANALOG_SUB_CALLWAIT].inthreeway) { ast_queue_hold(p->subs[ANALOG_SUB_CALLWAIT].owner, p->mohsuggest); } ast_queue_hold(p->subs[ANALOG_SUB_REAL].owner, p->mohsuggest); ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner); /* Unlock the call-waiting call that we swapped to real-call. */ ast_channel_unlock(p->subs[ANALOG_SUB_REAL].owner); } else if (!p->subs[ANALOG_SUB_THREEWAY].owner) { if (!p->threewaycalling) { /* Just send a flash if no 3-way calling */ ast_queue_control(p->subs[ANALOG_SUB_REAL].owner, AST_CONTROL_FLASH); goto winkflashdone; } else if (!analog_check_for_conference(p)) { struct ast_callid *callid = NULL; int callid_created; char cid_num[256]; char cid_name[256]; cid_num[0] = '\0'; cid_name[0] = '\0'; if (p->dahditrcallerid && p->owner) { if (ast_channel_caller(p->owner)->id.number.valid && ast_channel_caller(p->owner)->id.number.str) { ast_copy_string(cid_num, ast_channel_caller(p->owner)->id.number.str, sizeof(cid_num)); } if (ast_channel_caller(p->owner)->id.name.valid && ast_channel_caller(p->owner)->id.name.str) { ast_copy_string(cid_name, ast_channel_caller(p->owner)->id.name.str, sizeof(cid_name)); } } /* XXX This section needs much more error checking!!! XXX */ /* Start a 3-way call if feasible */ if (!((ast_channel_pbx(ast)) || (ast_channel_state(ast) == AST_STATE_UP) || (ast_channel_state(ast) == AST_STATE_RING))) { ast_debug(1, "Flash when call not up or ringing\n"); goto winkflashdone; } if (analog_alloc_sub(p, ANALOG_SUB_THREEWAY)) { ast_log(LOG_WARNING, "Unable to allocate three-way subchannel\n"); goto winkflashdone; } callid_created = ast_callid_threadstorage_auto(&callid); /* * Make new channel * * We cannot hold the p or ast locks while creating a new * channel. */ analog_unlock_private(p); ast_channel_unlock(ast); chan = analog_new_ast_channel(p, AST_STATE_RESERVED, 0, ANALOG_SUB_THREEWAY, NULL); ast_channel_lock(ast); analog_lock_private(p); if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new call structure on channel %d\n", p->channel); analog_unalloc_sub(p, ANALOG_SUB_THREEWAY); ast_callid_threadstorage_auto_clean(callid, callid_created); goto winkflashdone; } if (p->dahditrcallerid) { if (!p->origcid_num) { p->origcid_num = ast_strdup(p->cid_num); } if (!p->origcid_name) { p->origcid_name = ast_strdup(p->cid_name); } ast_copy_string(p->cid_num, cid_num, sizeof(p->cid_num)); ast_copy_string(p->cid_name, cid_name, sizeof(p->cid_name)); } /* Swap things around between the three-way and real call */ analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); /* Disable echo canceller for better dialing */ analog_set_echocanceller(p, 0); res = analog_play_tone(p, ANALOG_SUB_REAL, ANALOG_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to start dial recall tone on channel %d\n", p->channel); } analog_set_new_owner(p, chan); p->ss_astchan = chan; if (ast_pthread_create_detached(&threadid, NULL, __analog_ss_thread, p)) { ast_log(LOG_WARNING, "Unable to start simple switch on channel %d\n", p->channel); res = analog_play_tone(p, ANALOG_SUB_REAL, ANALOG_TONE_CONGESTION); analog_set_echocanceller(p, 1); ast_hangup(chan); } else { ast_verb(3, "Started three way call on channel %d\n", p->channel); /* Start music on hold */ ast_queue_hold(p->subs[ANALOG_SUB_THREEWAY].owner, p->mohsuggest); } ast_callid_threadstorage_auto_clean(callid, callid_created); } } else { /* Already have a 3 way call */ enum analog_sub orig_3way_sub; /* Need to hold the lock for real-call, private, and 3-way call */ analog_lock_sub_owner(p, ANALOG_SUB_THREEWAY); if (!p->subs[ANALOG_SUB_THREEWAY].owner) { /* * The 3-way call dissappeared. * Let's just ignore this flash-hook. */ ast_log(LOG_NOTICE, "Whoa, the 3-way call disappeared.\n"); goto winkflashdone; } orig_3way_sub = ANALOG_SUB_THREEWAY; if (p->subs[ANALOG_SUB_THREEWAY].inthreeway) { /* Call is already up, drop the last person */ ast_debug(1, "Got flash with three way call up, dropping last call on %d\n", p->channel); /* If the primary call isn't answered yet, use it */ if ((ast_channel_state(p->subs[ANALOG_SUB_REAL].owner) != AST_STATE_UP) && (ast_channel_state(p->subs[ANALOG_SUB_THREEWAY].owner) == AST_STATE_UP)) { /* Swap back -- we're dropping the real 3-way that isn't finished yet*/ analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); orig_3way_sub = ANALOG_SUB_REAL; analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); } /* Drop the last call and stop the conference */ ast_verb(3, "Dropping three-way call on %s\n", ast_channel_name(p->subs[ANALOG_SUB_THREEWAY].owner)); ast_softhangup_nolock(p->subs[ANALOG_SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); analog_set_inthreeway(p, ANALOG_SUB_REAL, 0); analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 0); } else { /* Lets see what we're up to */ if (((ast_channel_pbx(ast)) || (ast_channel_state(ast) == AST_STATE_UP)) && (p->transfertobusy || (ast_channel_state(ast) != AST_STATE_BUSY))) { ast_verb(3, "Building conference call with %s and %s\n", ast_channel_name(p->subs[ANALOG_SUB_THREEWAY].owner), ast_channel_name(p->subs[ANALOG_SUB_REAL].owner)); /* Put them in the threeway, and flip */ analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 1); analog_set_inthreeway(p, ANALOG_SUB_REAL, 1); if (ast_channel_state(ast) == AST_STATE_UP) { analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); orig_3way_sub = ANALOG_SUB_REAL; } ast_queue_unhold(p->subs[orig_3way_sub].owner); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); } else { ast_verb(3, "Dumping incomplete call on %s\n", ast_channel_name(p->subs[ANALOG_SUB_THREEWAY].owner)); analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL); orig_3way_sub = ANALOG_SUB_REAL; ast_softhangup_nolock(p->subs[ANALOG_SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); ast_queue_unhold(p->subs[ANALOG_SUB_REAL].owner); analog_set_echocanceller(p, 1); } } ast_channel_unlock(p->subs[orig_3way_sub].owner); } winkflashdone: analog_update_conf(p); break; case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_FEATD: case ANALOG_SIG_SF: case ANALOG_SIG_SFWINK: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: if (p->dialing) { ast_debug(1, "Ignoring wink on channel %d\n", p->channel); } else { ast_debug(1, "Got wink in weird state %u on channel %d\n", ast_channel_state(ast), p->channel); } break; case ANALOG_SIG_FEATDMF_TA: switch (p->whichwink) { case 0: ast_debug(1, "ANI2 set to '%d' and ANI is '%s'\n", ast_channel_caller(p->owner)->ani2, S_COR(ast_channel_caller(p->owner)->ani.number.valid, ast_channel_caller(p->owner)->ani.number.str, "")); snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%d%s#", ast_channel_caller(p->owner)->ani2, S_COR(ast_channel_caller(p->owner)->ani.number.valid, ast_channel_caller(p->owner)->ani.number.str, "")); break; case 1: ast_copy_string(p->dop.dialstr, p->finaldial, sizeof(p->dop.dialstr)); break; case 2: ast_log(LOG_WARNING, "Received unexpected wink on channel of type ANALOG_SIG_FEATDMF_TA\n"); return NULL; } p->whichwink++; /* Fall through */ case ANALOG_SIG_FEATDMF: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FEATB: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: case ANALOG_SIG_EMWINK: /* FGD MF and EMWINK *Must* wait for wink */ if (!ast_strlen_zero(p->dop.dialstr)) { res = analog_dial_digits(p, ANALOG_SUB_REAL, &p->dop); if (res) { p->dop.dialstr[0] = '\0'; return NULL; } else { ast_debug(1, "Sent deferred digit string on channel %d: %s\n", p->channel, p->dop.dialstr); } } p->dop.dialstr[0] = '\0'; break; default: ast_log(LOG_WARNING, "Don't know how to handle ring/off hook for signalling %d\n", p->sig); } break; case ANALOG_EVENT_HOOKCOMPLETE: if (p->inalarm) break; if (analog_check_waitingfordt(p)) { break; } switch (mysig) { case ANALOG_SIG_FXSLS: /* only interesting for FXS */ case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_EMWINK: case ANALOG_SIG_FEATD: case ANALOG_SIG_SF: case ANALOG_SIG_SFWINK: case ANALOG_SIG_SF_FEATD: if (!ast_strlen_zero(p->dop.dialstr)) { res = analog_dial_digits(p, ANALOG_SUB_REAL, &p->dop); if (res) { p->dop.dialstr[0] = '\0'; return NULL; } else { ast_debug(1, "Sent deferred digit string on channel %d: %s\n", p->channel, p->dop.dialstr); } } p->dop.dialstr[0] = '\0'; p->dop.op = ANALOG_DIAL_OP_REPLACE; break; case ANALOG_SIG_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: ast_debug(1, "Got hook complete in MF FGD, waiting for wink now on channel %d\n",p->channel); break; default: break; } break; case ANALOG_EVENT_POLARITY: /* * If we get a Polarity Switch event, this could be * due to line seizure, remote end connect or remote end disconnect. * * Check to see if we should change the polarity state and * mark the channel as UP or if this is an indication * of remote end disconnect. */ if (p->polarityonanswerdelay > 0) { /* check if event is not too soon after OffHook or Answer */ if (ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) > p->polarityonanswerdelay) { switch (ast_channel_state(ast)) { case AST_STATE_DIALING: /*!< Digits (or equivalent) have been dialed */ case AST_STATE_RINGING: /*!< Remote end is ringing */ if (p->answeronpolarityswitch) { ast_debug(1, "Answering on polarity switch! channel %d\n", p->channel); ast_setstate(p->owner, AST_STATE_UP); p->polarity = POLARITY_REV; if (p->hanguponpolarityswitch) { p->polaritydelaytv = ast_tvnow(); } } else { ast_debug(1, "Ignore Answer on polarity switch, channel %d\n", p->channel); } break; case AST_STATE_UP: /*!< Line is up */ case AST_STATE_RING: /*!< Line is ringing */ if (p->hanguponpolarityswitch) { ast_debug(1, "HangingUp on polarity switch! channel %d\n", p->channel); ast_queue_control_data(ast, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size); ast_channel_hangupcause_hash_set(ast, cause_code, data_size); ast_softhangup(p->owner, AST_SOFTHANGUP_EXPLICIT); p->polarity = POLARITY_IDLE; } else { ast_debug(1, "Ignore Hangup on polarity switch, channel %d\n", p->channel); } break; case AST_STATE_DOWN: /*!< Channel is down and available */ case AST_STATE_RESERVED: /*!< Channel is down, but reserved */ case AST_STATE_OFFHOOK: /*!< Channel is off hook */ case AST_STATE_BUSY: /*!< Line is busy */ case AST_STATE_DIALING_OFFHOOK: /*!< Digits (or equivalent) have been dialed while offhook */ case AST_STATE_PRERING: /*!< Channel has detected an incoming call and is waiting for ring */ default: if (p->answeronpolarityswitch || p->hanguponpolarityswitch) { ast_debug(1, "Ignoring Polarity switch on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } break; } } else { /* event is too soon after OffHook or Answer */ switch (ast_channel_state(ast)) { case AST_STATE_DIALING: /*!< Digits (or equivalent) have been dialed */ case AST_STATE_RINGING: /*!< Remote end is ringing */ if (p->answeronpolarityswitch) { ast_debug(1, "Polarity switch detected but NOT answering (too close to OffHook event) on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } break; case AST_STATE_UP: /*!< Line is up */ case AST_STATE_RING: /*!< Line is ringing */ if (p->hanguponpolarityswitch) { ast_debug(1, "Polarity switch detected but NOT hanging up (too close to Answer event) on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } break; default: if (p->answeronpolarityswitch || p->hanguponpolarityswitch) { ast_debug(1, "Polarity switch detected (too close to previous event) on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } break; } } } /* Added more log_debug information below to provide a better indication of what is going on */ ast_debug(1, "Polarity Reversal event occured - DEBUG 2: channel %d, state %u, pol= %d, aonp= %d, honp= %d, pdelay= %d, tv= %" PRIi64 "\n", p->channel, ast_channel_state(ast), p->polarity, p->answeronpolarityswitch, p->hanguponpolarityswitch, p->polarityonanswerdelay, ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) ); break; default: ast_debug(1, "Dunno what to do with event %d on channel %d\n", res, p->channel); } return &p->subs[idx].f; } struct ast_frame *analog_exception(struct analog_pvt *p, struct ast_channel *ast) { int res; int idx; struct ast_frame *f; ast_debug(1, "%s %d\n", __FUNCTION__, p->channel); idx = analog_get_index(ast, p, 1); if (idx < 0) { idx = ANALOG_SUB_REAL; } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.datalen = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.delivery = ast_tv(0,0); p->subs[idx].f.src = "dahdi_exception"; p->subs[idx].f.data.ptr = NULL; if (!p->owner) { /* If nobody owns us, absorb the event appropriately, otherwise we loop indefinitely. This occurs when, during call waiting, the other end hangs up our channel so that it no longer exists, but we have neither FLASH'd nor ONHOOK'd to signify our desire to change to the other channel. */ res = analog_get_event(p); /* Switch to real if there is one and this isn't something really silly... */ if ((res != ANALOG_EVENT_RINGEROFF) && (res != ANALOG_EVENT_RINGERON) && (res != ANALOG_EVENT_HOOKCOMPLETE)) { ast_debug(1, "Restoring owner of channel %d on event %d\n", p->channel, res); analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner); if (p->owner && ast != p->owner) { /* * Could this even happen? * Possible deadlock because we do not have the real-call lock. */ ast_log(LOG_WARNING, "Event %s on %s is not restored owner %s\n", analog_event2str(res), ast_channel_name(ast), ast_channel_name(p->owner)); } if (p->owner) { ast_queue_unhold(p->owner); } } switch (res) { case ANALOG_EVENT_ONHOOK: analog_set_echocanceller(p, 0); if (p->owner) { ast_verb(3, "Channel %s still has call, ringing phone\n", ast_channel_name(p->owner)); analog_ring(p); analog_stop_callwait(p); } else { ast_log(LOG_WARNING, "Absorbed %s, but nobody is left!?!?\n", analog_event2str(res)); } analog_update_conf(p); break; case ANALOG_EVENT_RINGOFFHOOK: analog_set_echocanceller(p, 1); analog_off_hook(p); if (p->owner && (ast_channel_state(p->owner) == AST_STATE_RINGING)) { ast_queue_control(p->owner, AST_CONTROL_ANSWER); analog_set_dialing(p, 0); } break; case ANALOG_EVENT_HOOKCOMPLETE: case ANALOG_EVENT_RINGERON: case ANALOG_EVENT_RINGEROFF: /* Do nothing */ break; case ANALOG_EVENT_WINKFLASH: gettimeofday(&p->flashtime, NULL); if (p->owner) { ast_verb(3, "Channel %d flashed to other channel %s\n", p->channel, ast_channel_name(p->owner)); if (ast_channel_state(p->owner) != AST_STATE_UP) { /* Answer if necessary */ ast_queue_control(p->owner, AST_CONTROL_ANSWER); ast_setstate(p->owner, AST_STATE_UP); } analog_stop_callwait(p); ast_queue_unhold(p->owner); } else { ast_log(LOG_WARNING, "Absorbed %s, but nobody is left!?!?\n", analog_event2str(res)); } analog_update_conf(p); break; default: ast_log(LOG_WARNING, "Don't know how to absorb event %s\n", analog_event2str(res)); break; } f = &p->subs[idx].f; return f; } ast_debug(1, "Exception on %d, channel %d\n", ast_channel_fd(ast, 0), p->channel); /* If it's not us, return NULL immediately */ if (ast != p->owner) { ast_log(LOG_WARNING, "We're %s, not %s\n", ast_channel_name(ast), ast_channel_name(p->owner)); f = &p->subs[idx].f; return f; } f = __analog_handle_event(p, ast); if (!f) { const char *name = ast_strdupa(ast_channel_name(ast)); /* Tell the CDR this DAHDI device hung up */ analog_unlock_private(p); ast_channel_unlock(ast); ast_set_hangupsource(ast, name, 0); ast_channel_lock(ast); analog_lock_private(p); } return f; } void *analog_handle_init_event(struct analog_pvt *i, int event) { int res; pthread_t threadid; struct ast_channel *chan; struct ast_callid *callid = NULL; int callid_created; ast_debug(1, "channel (%d) - signaling (%d) - event (%s)\n", i->channel, i->sig, analog_event2str(event)); /* Handle an event on a given channel for the monitor thread. */ switch (event) { case ANALOG_EVENT_WINKFLASH: case ANALOG_EVENT_RINGOFFHOOK: if (i->inalarm) { break; } /* Got a ring/answer. What kind of channel are we? */ switch (i->sig) { case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: case ANALOG_SIG_FXOKS: res = analog_off_hook(i); i->fxsoffhookstate = 1; if (res && (errno == EBUSY)) { break; } callid_created = ast_callid_threadstorage_auto(&callid); /* Cancel VMWI spill */ analog_cancel_cidspill(i); if (i->immediate) { analog_set_echocanceller(i, 1); /* The channel is immediately up. Start right away */ res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_RINGTONE); chan = analog_new_ast_channel(i, AST_STATE_RING, 1, ANALOG_SUB_REAL, NULL); if (!chan) { ast_log(LOG_WARNING, "Unable to start PBX on channel %d\n", i->channel); res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_CONGESTION); if (res < 0) { ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); } } } else { /* Check for callerid, digits, etc */ chan = analog_new_ast_channel(i, AST_STATE_RESERVED, 0, ANALOG_SUB_REAL, NULL); i->ss_astchan = chan; if (chan) { if (analog_has_voicemail(i)) { res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_STUTTER); } else { res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_DIALTONE); } if (res < 0) ast_log(LOG_WARNING, "Unable to play dialtone on channel %d, do you have defaultzone and loadzone defined?\n", i->channel); if (ast_pthread_create_detached(&threadid, NULL, __analog_ss_thread, i)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_CONGESTION); if (res < 0) { ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); } ast_hangup(chan); } } else ast_log(LOG_WARNING, "Unable to create channel\n"); } ast_callid_threadstorage_auto_clean(callid, callid_created); break; case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: analog_set_ringtimeout(i, i->ringt_base); /* Fall through */ case ANALOG_SIG_EMWINK: case ANALOG_SIG_FEATD: case ANALOG_SIG_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_SFWINK: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: case ANALOG_SIG_SF: callid_created = ast_callid_threadstorage_auto(&callid); /* Check for callerid, digits, etc */ if (i->cid_start == ANALOG_CID_START_POLARITY_IN || i->cid_start == ANALOG_CID_START_DTMF_NOALERT) { chan = analog_new_ast_channel(i, AST_STATE_PRERING, 0, ANALOG_SUB_REAL, NULL); } else { chan = analog_new_ast_channel(i, AST_STATE_RING, 0, ANALOG_SUB_REAL, NULL); } i->ss_astchan = chan; if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel); } else if (ast_pthread_create_detached(&threadid, NULL, __analog_ss_thread, i)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_CONGESTION); if (res < 0) { ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); } ast_hangup(chan); } ast_callid_threadstorage_auto_clean(callid, callid_created); break; default: ast_log(LOG_WARNING, "Don't know how to handle ring/answer with signalling %s on channel %d\n", analog_sigtype_to_str(i->sig), i->channel); res = analog_play_tone(i, ANALOG_SUB_REAL, ANALOG_TONE_CONGESTION); if (res < 0) { ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); } return NULL; } break; case ANALOG_EVENT_NOALARM: analog_set_alarm(i, 0); analog_publish_channel_alarm_clear(i->channel); break; case ANALOG_EVENT_ALARM: analog_set_alarm(i, 1); analog_get_and_handle_alarms(i); /* fall thru intentionally */ case ANALOG_EVENT_ONHOOK: /* Back on hook. Hang up. */ switch (i->sig) { case ANALOG_SIG_FXOLS: case ANALOG_SIG_FXOGS: i->fxsoffhookstate = 0; analog_start_polarityswitch(i); /* Fall through */ case ANALOG_SIG_FEATD: case ANALOG_SIG_FEATDMF: case ANALOG_SIG_FEATDMF_TA: case ANALOG_SIG_E911: case ANALOG_SIG_FGC_CAMA: case ANALOG_SIG_FGC_CAMAMF: case ANALOG_SIG_FEATB: case ANALOG_SIG_EM: case ANALOG_SIG_EM_E1: case ANALOG_SIG_EMWINK: case ANALOG_SIG_SF_FEATD: case ANALOG_SIG_SF_FEATDMF: case ANALOG_SIG_SF_FEATB: case ANALOG_SIG_SF: case ANALOG_SIG_SFWINK: case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSGS: case ANALOG_SIG_FXSKS: analog_set_echocanceller(i, 0); res = analog_play_tone(i, ANALOG_SUB_REAL, -1); analog_on_hook(i); break; case ANALOG_SIG_FXOKS: i->fxsoffhookstate = 0; analog_start_polarityswitch(i); analog_set_echocanceller(i, 0); /* Diddle the battery for the zhone */ #ifdef ZHONE_HACK analog_off_hook(i); usleep(1); #endif res = analog_play_tone(i, ANALOG_SUB_REAL, -1); analog_on_hook(i); break; default: ast_log(LOG_WARNING, "Don't know how to handle on hook with signalling %s on channel %d\n", analog_sigtype_to_str(i->sig), i->channel); res = analog_play_tone(i, ANALOG_SUB_REAL, -1); return NULL; } break; case ANALOG_EVENT_POLARITY: switch (i->sig) { case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSKS: case ANALOG_SIG_FXSGS: callid_created = ast_callid_threadstorage_auto(&callid); /* We have already got a PR before the channel was created, but it wasn't handled. We need polarity to be REV for remote hangup detection to work. At least in Spain */ if (i->hanguponpolarityswitch) { i->polarity = POLARITY_REV; } if (i->cid_start == ANALOG_CID_START_POLARITY || i->cid_start == ANALOG_CID_START_POLARITY_IN) { i->polarity = POLARITY_REV; ast_verb(2, "Starting post polarity " "CID detection on channel %d\n", i->channel); chan = analog_new_ast_channel(i, AST_STATE_PRERING, 0, ANALOG_SUB_REAL, NULL); i->ss_astchan = chan; if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel); } else if (ast_pthread_create_detached(&threadid, NULL, __analog_ss_thread, i)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); ast_hangup(chan); } } ast_callid_threadstorage_auto_clean(callid, callid_created); break; default: ast_log(LOG_WARNING, "handle_init_event detected " "polarity reversal on non-FXO (ANALOG_SIG_FXS) " "interface %d\n", i->channel); break; } break; case ANALOG_EVENT_DTMFCID: switch (i->sig) { case ANALOG_SIG_FXSLS: case ANALOG_SIG_FXSKS: case ANALOG_SIG_FXSGS: callid_created = ast_callid_threadstorage_auto(&callid); if (i->cid_start == ANALOG_CID_START_DTMF_NOALERT) { ast_verb(2, "Starting DTMF CID detection on channel %d\n", i->channel); chan = analog_new_ast_channel(i, AST_STATE_PRERING, 0, ANALOG_SUB_REAL, NULL); i->ss_astchan = chan; if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel); } else if (ast_pthread_create_detached(&threadid, NULL, __analog_ss_thread, i)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); ast_hangup(chan); } } ast_callid_threadstorage_auto_clean(callid, callid_created); break; default: ast_log(LOG_WARNING, "handle_init_event detected " "dtmfcid generation event on non-FXO (ANALOG_SIG_FXS) " "interface %d\n", i->channel); break; } break; case ANALOG_EVENT_REMOVED: /* destroy channel, will actually do so in do_monitor */ ast_log(LOG_NOTICE, "Got ANALOG_EVENT_REMOVED. Destroying channel %d\n", i->channel); return i->chan_pvt; case ANALOG_EVENT_NEONMWI_ACTIVE: analog_handle_notify_message(NULL, i, -1, ANALOG_EVENT_NEONMWI_ACTIVE); break; case ANALOG_EVENT_NEONMWI_INACTIVE: analog_handle_notify_message(NULL, i, -1, ANALOG_EVENT_NEONMWI_INACTIVE); break; } return NULL; } struct analog_pvt *analog_new(enum analog_sigtype signallingtype, void *private_data) { struct analog_pvt *p; p = ast_calloc(1, sizeof(*p)); if (!p) { return p; } p->outsigmod = ANALOG_SIG_NONE; p->sig = signallingtype; p->chan_pvt = private_data; /* Some defaults for values */ p->cid_start = ANALOG_CID_START_RING; p->cid_signalling = CID_SIG_BELL; /* Sub real is assumed to always be alloc'd */ p->subs[ANALOG_SUB_REAL].allocd = 1; return p; } /*! * \brief Delete the analog private structure. * \since 1.8 * * \param doomed Analog private structure to delete. * * \return Nothing */ void analog_delete(struct analog_pvt *doomed) { ast_free(doomed); } int analog_config_complete(struct analog_pvt *p) { /* No call waiting on non FXS channels */ if ((p->sig != ANALOG_SIG_FXOKS) && (p->sig != ANALOG_SIG_FXOLS) && (p->sig != ANALOG_SIG_FXOGS)) { p->permcallwaiting = 0; } analog_set_callwaiting(p, p->permcallwaiting); return 0; } void analog_free(struct analog_pvt *p) { ast_free(p); } /* called while dahdi_pvt is locked in dahdi_fixup */ int analog_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, void *newp) { struct analog_pvt *new_pvt = newp; int x; ast_debug(1, "New owner for channel %d is %s\n", new_pvt->channel, ast_channel_name(newchan)); if (new_pvt->owner == oldchan) { analog_set_new_owner(new_pvt, newchan); } for (x = 0; x < 3; x++) { if (new_pvt->subs[x].owner == oldchan) { new_pvt->subs[x].owner = newchan; } } analog_update_conf(new_pvt); return 0; } static void analog_publish_dnd_state(int channel, const char *status) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); RAII_VAR(struct ast_str *, dahdichan, ast_str_create(32), ast_free); if (!dahdichan) { return; } ast_str_set(&dahdichan, 0, "DAHDI/%d", channel); body = ast_json_pack("{s: s, s: s}", "Channel", ast_str_buffer(dahdichan), "Status", status); if (!body) { return; } ast_manager_publish_event("DNDState", EVENT_FLAG_SYSTEM, body); } int analog_dnd(struct analog_pvt *p, int flag) { if (flag == -1) { return p->dnd; } p->dnd = flag; ast_verb(3, "%s DND on channel %d\n", flag ? "Enabled" : "Disabled", p->channel); analog_publish_dnd_state(p->channel, flag ? "enabled" : "disabled"); return 0; } asterisk-13.1.0/channels/chan_misdn.c0000644000000000000000000140115212413557546016201 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2004 - 2006, Christian Richter * * Christian Richter * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. * */ /*! * \file * * \brief the chan_misdn channel driver for Asterisk * * \author Christian Richter * * MISDN http://www.misdn.org/ * * \ingroup channel_drivers */ /*! \li \ref chan_misdn.c uses the configuration file \ref misdn.conf * \addtogroup configuration_file */ /*! \page misdn.conf misdn.conf * \verbinclude misdn.conf.sample */ /*! * \note * To use the CCBS/CCNR supplementary service feature and other * supplementary services using FACILITY messages requires a * modified version of mISDN. * * \note * The latest modified mISDN v1.1.x based version is available at: * http://svn.digium.com/svn/thirdparty/mISDN/trunk * http://svn.digium.com/svn/thirdparty/mISDNuser/trunk * * \note * Taged versions of the modified mISDN code are available under: * http://svn.digium.com/svn/thirdparty/mISDN/tags * http://svn.digium.com/svn/thirdparty/mISDNuser/tags */ /* Define to enable cli commands to generate canned CCBS messages. */ // #define CCBS_TEST_MESSAGES 1 /* * XXX The mISDN channel driver needs its native bridge code * converted to the new bridge technology scheme. The * chan_dahdi native bridge code can be used as an example. It * is unlikely that this will ever get done. Support for this * channel driver is dwindling because the supported version of * mISDN does not support newer kernels. * * Without native bridge support, the following config file * parameters have no effect: bridging. * * The existing native bridge code is marked with the * mISDN_NATIVE_BRIDGING conditional. */ /*** MODULEINFO isdnnet misdn suppserv extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 424472 $") #include #include #include #include #include #include #include #include #include #include #include #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/io.h" #include "asterisk/frame.h" #include "asterisk/translate.h" #include "asterisk/cli.h" #include "asterisk/musiconhold.h" #include "asterisk/dsp.h" #include "asterisk/file.h" #include "asterisk/callerid.h" #include "asterisk/indications.h" #include "asterisk/app.h" #include "asterisk/features.h" #include "asterisk/term.h" #include "asterisk/sched.h" #include "asterisk/stringfields.h" #include "asterisk/abstract_jb.h" #include "asterisk/causes.h" #include "asterisk/format.h" #include "asterisk/format_cap.h" #include "asterisk/features_config.h" #include "asterisk/bridge.h" #include "asterisk/pickup.h" #include "asterisk/format_cache.h" #include "chan_misdn_config.h" #include "isdn_lib.h" static char global_tracefile[BUFFERSIZE + 1]; static int g_config_initialized = 0; struct misdn_jb{ int size; int upper_threshold; char *samples, *ok; int wp,rp; int state_empty; int state_full; int state_buffer; int bytes_wrote; ast_mutex_t mutexjb; }; /*! \brief allocates the jb-structure and initialize the elements */ struct misdn_jb *misdn_jb_init(int size, int upper_threshold); /*! \brief frees the data and destroys the given jitterbuffer struct */ void misdn_jb_destroy(struct misdn_jb *jb); /*! \brief fills the jitterbuffer with len data returns < 0 if there was an error (buffer overrun). */ int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len); /*! \brief gets len bytes out of the jitterbuffer if available, else only the available data is returned and the return value indicates the number of data. */ int misdn_jb_empty(struct misdn_jb *jb, char *data, int len); static char *complete_ch(struct ast_cli_args *a); static char *complete_debug_port(struct ast_cli_args *a); static char *complete_show_config(struct ast_cli_args *a); /* BEGIN: chan_misdn.h */ #if defined(AST_MISDN_ENHANCEMENTS) /* * This timeout duration is to clean up any call completion records that * are forgotten about by the switch. */ #define MISDN_CC_RECORD_AGE_MAX (6UL * 60 * 60) /* seconds */ #define MISDN_CC_REQUEST_WAIT_MAX 5 /* seconds */ /*! * \brief Caller that initialized call completion services * * \details * This data is the payload for a datastore that is put on the channel that * initializes call completion services. This datastore is set to be inherited * by the outbound mISDN channel. When one of these channels hangs up, the * channel pointer will be set to NULL. That way, we can ensure that we do not * touch this channel after it gets destroyed. */ struct misdn_cc_caller { /*! \brief The channel that initialized call completion services */ struct ast_channel *chan; }; struct misdn_cc_notify { /*! \brief Dialplan: Notify extension priority */ int priority; /*! \brief Dialplan: Notify extension context */ char context[AST_MAX_CONTEXT]; /*! \brief Dialplan: Notify extension number (User-A) */ char exten[AST_MAX_EXTENSION]; }; /*! \brief mISDN call completion record */ struct misdn_cc_record { /*! \brief Call completion record linked list */ AST_LIST_ENTRY(misdn_cc_record) list; /*! \brief Time the record was created. */ time_t time_created; /*! \brief MISDN_CC_RECORD_ID value */ long record_id; /*! * \brief Logical Layer 1 port associated with this * call completion record */ int port; /*! \brief TRUE if point-to-point mode (CCBS-T/CCNR-T mode) */ int ptp; /*! \brief Mode specific parameters */ union { /*! \brief point-to-point specific parameters. */ struct { /*! * \brief Call-completion signaling link. * NULL if signaling link not established. */ struct misdn_bchannel *bc; /*! * \brief TRUE if we requested the request retention option * to be enabled. */ int requested_retention; /*! * \brief TRUE if the request retention option is enabled. */ int retention_enabled; } ptp; /*! \brief point-to-multi-point specific parameters. */ struct { /*! \brief CallLinkageID (valid when port determined) */ int linkage_id; /*! \breif CCBSReference (valid when activated is TRUE) */ int reference_id; /*! \brief globalRecall(0), specificRecall(1) */ int recall_mode; } ptmp; } mode; /*! \brief TRUE if call completion activated */ int activated; /*! \brief Outstanding message ID (valid when outstanding_message) */ int invoke_id; /*! \brief TRUE if waiting for a response from a message (invoke_id is valid) */ int outstanding_message; /*! \brief TRUE if activation has been requested */ int activation_requested; /*! * \brief TRUE if User-A is free * \note PTMP - Used to answer CCBSStatusRequest. * PTP - Determines how to respond to CCBS_T_RemoteUserFree. */ int party_a_free; /*! \brief Error code received from last outstanding message. */ enum FacErrorCode error_code; /*! \brief Reject code received from last outstanding message. */ enum FacRejectCode reject_code; /*! * \brief Saved struct misdn_bchannel call information when * attempted to call User-B */ struct { /*! \brief User-A caller id information */ struct misdn_party_id caller; /*! \brief User-B number information */ struct misdn_party_dialing dialed; /*! \brief The BC, HLC (optional) and LLC (optional) contents from the SETUP message. */ struct Q931_Bc_Hlc_Llc setup_bc_hlc_llc; /*! \brief SETUP message bearer capability field code value */ int capability; /*! \brief TRUE if call made in digital HDLC mode */ int hdlc; } redial; /*! \brief Dialplan location to indicate User-B free and User-A is free */ struct misdn_cc_notify remote_user_free; /*! \brief Dialplan location to indicate User-B free and User-A is busy */ struct misdn_cc_notify b_free; }; /*! \brief mISDN call completion record database */ static AST_LIST_HEAD_STATIC(misdn_cc_records_db, misdn_cc_record); /*! \brief Next call completion record ID to use */ static __u16 misdn_cc_record_id; /*! \brief Next invoke ID to use */ static __s16 misdn_invoke_id; static const char misdn_no_response_from_network[] = "No response from network"; static const char misdn_cc_record_not_found[] = "Call completion record not found"; /* mISDN channel variable names */ #define MISDN_CC_RECORD_ID "MISDN_CC_RECORD_ID" #define MISDN_CC_STATUS "MISDN_CC_STATUS" #define MISDN_ERROR_MSG "MISDN_ERROR_MSG" #endif /* defined(AST_MISDN_ENHANCEMENTS) */ static ast_mutex_t release_lock; enum misdn_chan_state { MISDN_NOTHING = 0, /*!< at beginning */ MISDN_WAITING4DIGS, /*!< when waiting for info */ MISDN_EXTCANTMATCH, /*!< when asterisk couldn't match our ext */ MISDN_INCOMING_SETUP, /*!< for incoming setup */ MISDN_DIALING, /*!< when pbx_start */ MISDN_PROGRESS, /*!< we have progress */ MISDN_PROCEEDING, /*!< we have progress */ MISDN_CALLING, /*!< when misdn_call is called */ MISDN_CALLING_ACKNOWLEDGE, /*!< when we get SETUP_ACK */ MISDN_ALERTING, /*!< when Alerting */ MISDN_BUSY, /*!< when BUSY */ MISDN_CONNECTED, /*!< when connected */ MISDN_DISCONNECTED, /*!< when connected */ MISDN_CLEANING, /*!< when hangup from * but we were connected before */ }; /*! Asterisk created the channel (outgoing call) */ #define ORG_AST 1 /*! mISDN created the channel (incoming call) */ #define ORG_MISDN 2 enum misdn_hold_state { MISDN_HOLD_IDLE, /*!< HOLD not active */ MISDN_HOLD_ACTIVE, /*!< Call is held */ MISDN_HOLD_TRANSFER, /*!< Held call is being transferred */ MISDN_HOLD_DISCONNECT, /*!< Held call is being disconnected */ }; struct hold_info { /*! * \brief Call HOLD state. */ enum misdn_hold_state state; /*! * \brief Logical port the channel call record is HELD on * because the B channel is no longer associated. */ int port; /*! * \brief Original B channel number the HELD call was using. * \note Used only for debug display messages. */ int channel; }; #define chan_list_ref(obj, debug) (ao2_t_ref((obj), +1, (debug)), (obj)) #define chan_list_unref(obj, debug) (ao2_t_ref((obj), -1, (debug)), NULL) /*! * \brief Channel call record structure */ struct chan_list { /*! * \brief The "allowed_bearers" string read in from /etc/asterisk/misdn.conf */ char allowed_bearers[BUFFERSIZE + 1]; /*! * \brief State of the channel */ enum misdn_chan_state state; /*! * \brief TRUE if a hangup needs to be queued * \note This is a debug flag only used to catch calls to hangup_chan() that are already hungup. */ int need_queue_hangup; /*! * \brief TRUE if a channel can be hung up by calling asterisk directly when done. */ int need_hangup; /*! * \brief TRUE if we could send an AST_CONTROL_BUSY if needed. */ int need_busy; /*! * \brief Who originally created this channel. ORG_AST or ORG_MISDN */ int originator; /*! * \brief TRUE of we are not to respond immediately to a SETUP message. Check the dialplan first. * \note The "noautorespond_on_setup" boolean read in from /etc/asterisk/misdn.conf */ int noautorespond_on_setup; int norxtone; /*!< Boolean assigned values but the value is not used. */ /*! * \brief TRUE if we are not to generate tones (Playtones) */ int notxtone; /*! * \brief TRUE if echo canceller is enabled. Value is toggled. */ int toggle_ec; /*! * \brief TRUE if you want to send Tone Indications to an incoming * ISDN channel on a TE Port. * \note The "incoming_early_audio" boolean read in from /etc/asterisk/misdn.conf */ int incoming_early_audio; /*! * \brief TRUE if DTMF digits are to be passed inband only. * \note It is settable by the misdn_set_opt() application. */ int ignore_dtmf; /*! * \brief Pipe file descriptor handles array. * Read from pipe[0], write to pipe[1] */ int pipe[2]; /*! * \brief Read buffer for inbound audio from pipe[0] */ char ast_rd_buf[4096]; /*! * \brief Inbound audio frame returned by misdn_read(). */ struct ast_frame frame; /*! * \brief Fax detection option. (0:no 1:yes 2:yes+nojump) * \note The "faxdetect" option string read in from /etc/asterisk/misdn.conf * \note It is settable by the misdn_set_opt() application. */ int faxdetect; /*! * \brief Number of seconds to detect a Fax machine when detection enabled. * \note 0 disables the timeout. * \note The "faxdetect_timeout" value read in from /etc/asterisk/misdn.conf */ int faxdetect_timeout; /*! * \brief Starting time of fax detection with timeout when nonzero. */ struct timeval faxdetect_tv; /*! * \brief TRUE if a fax has been detected. */ int faxhandled; /*! * \brief TRUE if we will use the Asterisk DSP to detect DTMF/Fax * \note The "astdtmf" boolean read in from /etc/asterisk/misdn.conf */ int ast_dsp; /*! * \brief Jitterbuffer length * \note The "jitterbuffer" value read in from /etc/asterisk/misdn.conf */ int jb_len; /*! * \brief Jitterbuffer upper threshold * \note The "jitterbuffer_upper_threshold" value read in from /etc/asterisk/misdn.conf */ int jb_upper_threshold; /*! * \brief Allocated jitterbuffer controller * \note misdn_jb_init() creates the jitterbuffer. * \note Must use misdn_jb_destroy() to clean up. */ struct misdn_jb *jb; /*! * \brief Allocated DSP controller * \note ast_dsp_new() creates the DSP controller. * \note Must use ast_dsp_free() to clean up. */ struct ast_dsp *dsp; /*! * \brief Associated Asterisk channel structure. */ struct ast_channel * ast; /*! * \brief Associated B channel structure. */ struct misdn_bchannel *bc; #if defined(AST_MISDN_ENHANCEMENTS) /*! * \brief Peer channel for which call completion was initialized. */ struct misdn_cc_caller *peer; /*! \brief Associated call completion record ID (-1 if not associated) */ long record_id; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /*! * \brief HELD channel call information */ struct hold_info hold; /*! * \brief From associated B channel: Layer 3 process ID * \note Used to find the HELD channel call record when retrieving a call. */ unsigned int l3id; /*! * \brief From associated B channel: B Channel mISDN driver layer ID from mISDN_get_layerid() * \note Used only for debug display messages. */ int addr; /*! * \brief Incoming call dialplan context identifier. * \note The "context" string read in from /etc/asterisk/misdn.conf */ char context[AST_MAX_CONTEXT]; /*! * \brief The configured music-on-hold class to use for this call. * \note The "musicclass" string read in from /etc/asterisk/misdn.conf */ char mohinterpret[MAX_MUSICCLASS]; /*! * \brief Number of outgoing audio frames dropped since last debug gripe message. */ int dropped_frame_cnt; /*! * \brief TRUE if we must do the ringback tones. * \note The "far_alerting" boolean read in from /etc/asterisk/misdn.conf */ int far_alerting; /*! * \brief TRUE if NT should disconnect an overlap dialing call when a timeout occurs. * \note The "nttimeout" boolean read in from /etc/asterisk/misdn.conf */ int nttimeout; /*! * \brief Tone zone sound used for dialtone generation. * \note Used as a boolean. Non-NULL to prod generation if enabled. */ struct ast_tone_zone_sound *ts; /*! * \brief Enables overlap dialing for the set amount of seconds. (0 = Disabled) * \note The "overlapdial" value read in from /etc/asterisk/misdn.conf */ int overlap_dial; /*! * \brief Overlap dialing timeout Task ID. -1 if not running. */ int overlap_dial_task; /*! * \brief overlap_tv access lock. */ ast_mutex_t overlap_tv_lock; /*! * \brief Overlap timer start time. Timer restarted for every digit received. */ struct timeval overlap_tv; /*! * \brief Next channel call record in the list. */ struct chan_list *next; }; int MAXTICS = 8; void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch); void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch); static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame); struct robin_list { char *group; int port; int channel; struct robin_list *next; struct robin_list *prev; }; static struct robin_list *robin = NULL; static void free_robin_list(void) { struct robin_list *r; struct robin_list *next; for (r = robin, robin = NULL; r; r = next) { next = r->next; ast_free(r->group); ast_free(r); } } static struct robin_list *get_robin_position(char *group) { struct robin_list *new; struct robin_list *iter = robin; for (; iter; iter = iter->next) { if (!strcasecmp(iter->group, group)) { return iter; } } new = ast_calloc(1, sizeof(*new)); if (!new) { return NULL; } new->group = ast_strdup(group); if (!new->group) { ast_free(new); return NULL; } new->channel = 1; if (robin) { new->next = robin; robin->prev = new; } robin = new; return robin; } /*! \brief the main schedule context for stuff like l1 watcher, overlap dial, ... */ static struct ast_sched_context *misdn_tasks = NULL; static pthread_t misdn_tasks_thread; static int *misdn_ports; static void chan_misdn_log(int level, int port, char *tmpl, ...) __attribute__((format(printf, 3, 4))); static struct ast_channel *misdn_new(struct chan_list *cl, int state, char *exten, char *callerid, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int port, int c); static void send_digit_to_chan(struct chan_list *cl, char digit); static int pbx_start_chan(struct chan_list *ch); #define MISDN_ASTERISK_TECH_PVT(ast) ast_channel_tech_pvt(ast) #define MISDN_ASTERISK_TECH_PVT_SET(ast, value) ast_channel_tech_pvt_set(ast, value) #include "asterisk/strings.h" /* #define MISDN_DEBUG 1 */ static const char misdn_type[] = "mISDN"; static int tracing = 0; static int *misdn_debug; static int *misdn_debug_only; static int max_ports; static int *misdn_in_calls; static int *misdn_out_calls; /*! * \brief Global channel call record list head. */ static struct chan_list *cl_te=NULL; static ast_mutex_t cl_te_lock; static enum event_response_e cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data); static int send_cause2ast(struct ast_channel *ast, struct misdn_bchannel *bc, struct chan_list *ch); static void cl_queue_chan(struct chan_list *chan); static int dialtone_indicate(struct chan_list *cl); static void hanguptone_indicate(struct chan_list *cl); static int stop_indicate(struct chan_list *cl); static int start_bc_tones(struct chan_list *cl); static int stop_bc_tones(struct chan_list *cl); static void release_chan_early(struct chan_list *ch); static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc); #if defined(AST_MISDN_ENHANCEMENTS) static const char misdn_command_name[] = "misdn_command"; static int misdn_command_exec(struct ast_channel *chan, const char *data); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ static int misdn_check_l2l1(struct ast_channel *chan, const char *data); static int misdn_set_opt_exec(struct ast_channel *chan, const char *data); static int misdn_facility_exec(struct ast_channel *chan, const char *data); int chan_misdn_jb_empty(struct misdn_bchannel *bc, char *buf, int len); void debug_numtype(int port, int numtype, char *type); int add_out_calls(int port); int add_in_calls(int port); #ifdef MISDN_1_2 static int update_pipeline_config(struct misdn_bchannel *bc); #else static int update_ec_config(struct misdn_bchannel *bc); #endif /*************** Helpers *****************/ static int misdn_chan_is_valid(struct chan_list *ch) { struct chan_list *list; ast_mutex_lock(&cl_te_lock); for (list = cl_te; list; list = list->next) { if (list == ch) { ast_mutex_unlock(&cl_te_lock); return 1; } } ast_mutex_unlock(&cl_te_lock); return 0; } #if defined(mISDN_NATIVE_BRIDGING) /*! Returns a reference to the found chan_list. */ static struct chan_list *get_chan_by_ast(struct ast_channel *ast) { struct chan_list *tmp; ast_mutex_lock(&cl_te_lock); for (tmp = cl_te; tmp; tmp = tmp->next) { if (tmp->ast == ast) { chan_list_ref(tmp, "Found chan_list by ast"); ast_mutex_unlock(&cl_te_lock); return tmp; } } ast_mutex_unlock(&cl_te_lock); return NULL; } #endif /* defined(mISDN_NATIVE_BRIDGING) */ /*! Returns a reference to the found chan_list. */ static struct chan_list *get_chan_by_ast_name(const char *name) { struct chan_list *tmp; ast_mutex_lock(&cl_te_lock); for (tmp = cl_te; tmp; tmp = tmp->next) { if (tmp->ast && strcmp(ast_channel_name(tmp->ast), name) == 0) { chan_list_ref(tmp, "Found chan_list by ast name"); ast_mutex_unlock(&cl_te_lock); return tmp; } } ast_mutex_unlock(&cl_te_lock); return NULL; } #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Destroy the misdn_cc_ds_info datastore payload * * \param[in] data the datastore payload, a reference to an misdn_cc_caller * * \details * Since the payload is a reference to an astobj2 object, we just decrement its * reference count. Before doing so, we NULL out the channel pointer inside of * the misdn_cc_caller instance. This function will be called in one of two * cases. In both cases, we no longer need the channel pointer: * * - The original channel that initialized call completion services, the same * channel that is stored here, has been destroyed early. This could happen * if it transferred the mISDN channel, for example. * * - The mISDN channel that had this datastore inherited on to it is now being * destroyed. If this is the case, then the call completion events have * already occurred and the appropriate channel variables have already been * set on the original channel that requested call completion services. * * \return Nothing */ static void misdn_cc_ds_destroy(void *data) { struct misdn_cc_caller *cc_caller = data; ao2_lock(cc_caller); cc_caller->chan = NULL; ao2_unlock(cc_caller); ao2_ref(cc_caller, -1); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Duplicate the misdn_cc_ds_info datastore payload * * \param[in] data the datastore payload, a reference to an misdn_cc_caller * * \details * All we need to do is bump the reference count and return the same instance. * * \return A reference to an instance of a misdn_cc_caller */ static void *misdn_cc_ds_duplicate(void *data) { struct misdn_cc_caller *cc_caller = data; ao2_ref(cc_caller, +1); return cc_caller; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static const struct ast_datastore_info misdn_cc_ds_info = { .type = "misdn_cc", .destroy = misdn_cc_ds_destroy, .duplicate = misdn_cc_ds_duplicate, }; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Set a channel var on the peer channel for call completion services * * \param[in] peer The peer that initialized call completion services * \param[in] var The variable name to set * \param[in] value The variable value to set * * This function may be called from outside of the channel thread. It handles * the fact that the peer channel may be hung up and destroyed at any time. * * \return nothing */ static void misdn_cc_set_peer_var(struct misdn_cc_caller *peer, const char *var, const char *value) { ao2_lock(peer); /*! \todo XXX This nastiness can go away once ast_channel is ref counted! */ while (peer->chan && ast_channel_trylock(peer->chan)) { ao2_unlock(peer); sched_yield(); ao2_lock(peer); } if (peer->chan) { pbx_builtin_setvar_helper(peer->chan, var, value); ast_channel_unlock(peer->chan); } ao2_unlock(peer); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Get a reference to the CC caller if it exists */ static struct misdn_cc_caller *misdn_cc_caller_get(struct ast_channel *chan) { struct ast_datastore *datastore; struct misdn_cc_caller *cc_caller; ast_channel_lock(chan); if (!(datastore = ast_channel_datastore_find(chan, &misdn_cc_ds_info, NULL))) { ast_channel_unlock(chan); return NULL; } ao2_ref(datastore->data, +1); cc_caller = datastore->data; ast_channel_unlock(chan); return cc_caller; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Find the call completion record given the record id. * * \param record_id * * \retval pointer to found call completion record * \retval NULL if not found * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static struct misdn_cc_record *misdn_cc_find_by_id(long record_id) { struct misdn_cc_record *current; AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) { if (current->record_id == record_id) { /* Found the record */ break; } } return current; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Find the call completion record given the port and call linkage id. * * \param port Logical port number * \param linkage_id Call linkage ID number from switch. * * \retval pointer to found call completion record * \retval NULL if not found * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static struct misdn_cc_record *misdn_cc_find_by_linkage(int port, int linkage_id) { struct misdn_cc_record *current; AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) { if (current->port == port && !current->ptp && current->mode.ptmp.linkage_id == linkage_id) { /* Found the record */ break; } } return current; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Find the call completion record given the port and outstanding invocation id. * * \param port Logical port number * \param invoke_id Outstanding message invocation ID number. * * \retval pointer to found call completion record * \retval NULL if not found * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static struct misdn_cc_record *misdn_cc_find_by_invoke(int port, int invoke_id) { struct misdn_cc_record *current; AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) { if (current->outstanding_message && current->invoke_id == invoke_id && current->port == port) { /* Found the record */ break; } } return current; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Find the call completion record given the port and CCBS reference id. * * \param port Logical port number * \param reference_id CCBS reference ID number from switch. * * \retval pointer to found call completion record * \retval NULL if not found * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static struct misdn_cc_record *misdn_cc_find_by_reference(int port, int reference_id) { struct misdn_cc_record *current; AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) { if (current->activated && current->port == port && !current->ptp && current->mode.ptmp.reference_id == reference_id) { /* Found the record */ break; } } return current; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Find the call completion record given the B channel pointer * * \param bc B channel control structure pointer. * * \retval pointer to found call completion record * \retval NULL if not found * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static struct misdn_cc_record *misdn_cc_find_by_bc(const struct misdn_bchannel *bc) { struct misdn_cc_record *current; if (bc) { AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) { if (current->ptp && current->mode.ptp.bc == bc) { /* Found the record */ break; } } } else { current = NULL; } return current; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Delete the given call completion record * * \param doomed Call completion record to destroy * * \return Nothing * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static void misdn_cc_delete(struct misdn_cc_record *doomed) { struct misdn_cc_record *current; AST_LIST_TRAVERSE_SAFE_BEGIN(&misdn_cc_records_db, current, list) { if (current == doomed) { AST_LIST_REMOVE_CURRENT(list); ast_free(current); return; } } AST_LIST_TRAVERSE_SAFE_END; /* The doomed node is not in the call completion database */ } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Delete all old call completion records * * \return Nothing * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static void misdn_cc_remove_old(void) { struct misdn_cc_record *current; time_t now; now = time(NULL); AST_LIST_TRAVERSE_SAFE_BEGIN(&misdn_cc_records_db, current, list) { if (MISDN_CC_RECORD_AGE_MAX < now - current->time_created) { if (current->ptp && current->mode.ptp.bc) { /* Close the old call-completion signaling link */ current->mode.ptp.bc->fac_out.Function = Fac_None; current->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(current->mode.ptp.bc, EVENT_RELEASE_COMPLETE); } /* Remove the old call completion record */ AST_LIST_REMOVE_CURRENT(list); ast_free(current); } } AST_LIST_TRAVERSE_SAFE_END; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Allocate the next record id. * * \retval New record id on success. * \retval -1 on error. * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static long misdn_cc_record_id_new(void) { long record_id; long first_id; record_id = ++misdn_cc_record_id; first_id = record_id; while (misdn_cc_find_by_id(record_id)) { record_id = ++misdn_cc_record_id; if (record_id == first_id) { /* * We have a resource leak. * We should never need to allocate 64k records. */ chan_misdn_log(0, 0, " --> ERROR Too many call completion records!\n"); record_id = -1; break; } } return record_id; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Create a new call completion record * * \retval pointer to new call completion record * \retval NULL if failed * * \note Assumes the misdn_cc_records_db lock is already obtained. */ static struct misdn_cc_record *misdn_cc_new(void) { struct misdn_cc_record *cc_record; long record_id; misdn_cc_remove_old(); cc_record = ast_calloc(1, sizeof(*cc_record)); if (cc_record) { record_id = misdn_cc_record_id_new(); if (record_id < 0) { ast_free(cc_record); return NULL; } /* Initialize the new record */ cc_record->record_id = record_id; cc_record->port = -1;/* Invalid port so it will never be found this way */ cc_record->invoke_id = ++misdn_invoke_id; cc_record->party_a_free = 1;/* Default User-A as free */ cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; cc_record->time_created = time(NULL); /* Insert the new record into the database */ AST_LIST_INSERT_HEAD(&misdn_cc_records_db, cc_record, list); } return cc_record; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Destroy the call completion record database * * \return Nothing */ static void misdn_cc_destroy(void) { struct misdn_cc_record *current; while ((current = AST_LIST_REMOVE_HEAD(&misdn_cc_records_db, list))) { /* Do a misdn_cc_delete(current) inline */ ast_free(current); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Initialize the call completion record database * * \return Nothing */ static void misdn_cc_init(void) { misdn_cc_record_id = 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Check the status of an outstanding invocation request. * * \param data Points to an integer containing the call completion record id. * * \retval 0 if got a response. * \retval -1 if no response yet. */ static int misdn_cc_response_check(void *data) { int not_responded; struct misdn_cc_record *cc_record; AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(*(long *) data); if (cc_record) { if (cc_record->outstanding_message) { not_responded = -1; } else { not_responded = 0; } } else { /* No record so there is no response to check. */ not_responded = 0; } AST_LIST_UNLOCK(&misdn_cc_records_db); return not_responded; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Wait for a response from the switch for an outstanding * invocation request. * * \param chan Asterisk channel to operate upon. * \param wait_seconds Number of seconds to wait * \param record_id Call completion record ID. * * \return Nothing */ static void misdn_cc_response_wait(struct ast_channel *chan, int wait_seconds, long record_id) { unsigned count; for (count = 2 * MISDN_CC_REQUEST_WAIT_MAX; count--;) { /* Sleep in 500 ms increments */ if (ast_safe_sleep_conditional(chan, 500, misdn_cc_response_check, &record_id) != 0) { /* We got hung up or our response came in. */ break; } } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the mISDN reject code to a string * * \param code mISDN reject code. * * \return The mISDN reject code as a string */ static const char *misdn_to_str_reject_code(enum FacRejectCode code) { static const struct { enum FacRejectCode code; char *name; } arr[] = { /* *INDENT-OFF* */ { FacReject_None, "No reject occurred" }, { FacReject_Unknown, "Unknown reject code" }, { FacReject_Gen_UnrecognizedComponent, "General: Unrecognized Component" }, { FacReject_Gen_MistypedComponent, "General: Mistyped Component" }, { FacReject_Gen_BadlyStructuredComponent, "General: Badly Structured Component" }, { FacReject_Inv_DuplicateInvocation, "Invoke: Duplicate Invocation" }, { FacReject_Inv_UnrecognizedOperation, "Invoke: Unrecognized Operation" }, { FacReject_Inv_MistypedArgument, "Invoke: Mistyped Argument" }, { FacReject_Inv_ResourceLimitation, "Invoke: Resource Limitation" }, { FacReject_Inv_InitiatorReleasing, "Invoke: Initiator Releasing" }, { FacReject_Inv_UnrecognizedLinkedID, "Invoke: Unrecognized Linked ID" }, { FacReject_Inv_LinkedResponseUnexpected, "Invoke: Linked Response Unexpected" }, { FacReject_Inv_UnexpectedChildOperation, "Invoke: Unexpected Child Operation" }, { FacReject_Res_UnrecognizedInvocation, "Result: Unrecognized Invocation" }, { FacReject_Res_ResultResponseUnexpected, "Result: Result Response Unexpected" }, { FacReject_Res_MistypedResult, "Result: Mistyped Result" }, { FacReject_Err_UnrecognizedInvocation, "Error: Unrecognized Invocation" }, { FacReject_Err_ErrorResponseUnexpected, "Error: Error Response Unexpected" }, { FacReject_Err_UnrecognizedError, "Error: Unrecognized Error" }, { FacReject_Err_UnexpectedError, "Error: Unexpected Error" }, { FacReject_Err_MistypedParameter, "Error: Mistyped Parameter" }, /* *INDENT-ON* */ }; unsigned index; for (index = 0; index < ARRAY_LEN(arr); ++index) { if (arr[index].code == code) { return arr[index].name; } } return "unknown"; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the mISDN error code to a string * * \param code mISDN error code. * * \return The mISDN error code as a string */ static const char *misdn_to_str_error_code(enum FacErrorCode code) { static const struct { enum FacErrorCode code; char *name; } arr[] = { /* *INDENT-OFF* */ { FacError_None, "No error occurred" }, { FacError_Unknown, "Unknown OID error code" }, { FacError_Gen_NotSubscribed, "General: Not Subscribed" }, { FacError_Gen_NotAvailable, "General: Not Available" }, { FacError_Gen_NotImplemented, "General: Not Implemented" }, { FacError_Gen_InvalidServedUserNr, "General: Invalid Served User Number" }, { FacError_Gen_InvalidCallState, "General: Invalid Call State" }, { FacError_Gen_BasicServiceNotProvided, "General: Basic Service Not Provided" }, { FacError_Gen_NotIncomingCall, "General: Not Incoming Call" }, { FacError_Gen_SupplementaryServiceInteractionNotAllowed,"General: Supplementary Service Interaction Not Allowed" }, { FacError_Gen_ResourceUnavailable, "General: Resource Unavailable" }, { FacError_Div_InvalidDivertedToNr, "Diversion: Invalid Diverted To Number" }, { FacError_Div_SpecialServiceNr, "Diversion: Special Service Number" }, { FacError_Div_DiversionToServedUserNr, "Diversion: Diversion To Served User Number" }, { FacError_Div_IncomingCallAccepted, "Diversion: Incoming Call Accepted" }, { FacError_Div_NumberOfDiversionsExceeded, "Diversion: Number Of Diversions Exceeded" }, { FacError_Div_NotActivated, "Diversion: Not Activated" }, { FacError_Div_RequestAlreadyAccepted, "Diversion: Request Already Accepted" }, { FacError_AOC_NoChargingInfoAvailable, "AOC: No Charging Info Available" }, { FacError_CCBS_InvalidCallLinkageID, "CCBS: Invalid Call Linkage ID" }, { FacError_CCBS_InvalidCCBSReference, "CCBS: Invalid CCBS Reference" }, { FacError_CCBS_LongTermDenial, "CCBS: Long Term Denial" }, { FacError_CCBS_ShortTermDenial, "CCBS: Short Term Denial" }, { FacError_CCBS_IsAlreadyActivated, "CCBS: Is Already Activated" }, { FacError_CCBS_AlreadyAccepted, "CCBS: Already Accepted" }, { FacError_CCBS_OutgoingCCBSQueueFull, "CCBS: Outgoing CCBS Queue Full" }, { FacError_CCBS_CallFailureReasonNotBusy, "CCBS: Call Failure Reason Not Busy" }, { FacError_CCBS_NotReadyForCall, "CCBS: Not Ready For Call" }, { FacError_CCBS_T_LongTermDenial, "CCBS-T: Long Term Denial" }, { FacError_CCBS_T_ShortTermDenial, "CCBS-T: Short Term Denial" }, { FacError_ECT_LinkIdNotAssignedByNetwork, "ECT: Link ID Not Assigned By Network" }, /* *INDENT-ON* */ }; unsigned index; for (index = 0; index < ARRAY_LEN(arr); ++index) { if (arr[index].code == code) { return arr[index].name; } } return "unknown"; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert mISDN redirecting reason to diversion reason. * * \param reason mISDN redirecting reason code. * * \return Supported diversion reason code. */ static unsigned misdn_to_diversion_reason(enum mISDN_REDIRECTING_REASON reason) { unsigned diversion_reason; switch (reason) { case mISDN_REDIRECTING_REASON_CALL_FWD: diversion_reason = 1;/* cfu */ break; case mISDN_REDIRECTING_REASON_CALL_FWD_BUSY: diversion_reason = 2;/* cfb */ break; case mISDN_REDIRECTING_REASON_NO_REPLY: diversion_reason = 3;/* cfnr */ break; default: diversion_reason = 0;/* unknown */ break; } return diversion_reason; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert diversion reason to mISDN redirecting reason * * \param diversion_reason Diversion reason to convert * * \return Supported redirecting reason code. */ static enum mISDN_REDIRECTING_REASON diversion_reason_to_misdn(unsigned diversion_reason) { enum mISDN_REDIRECTING_REASON reason; switch (diversion_reason) { case 1:/* cfu */ reason = mISDN_REDIRECTING_REASON_CALL_FWD; break; case 2:/* cfb */ reason = mISDN_REDIRECTING_REASON_CALL_FWD_BUSY; break; case 3:/* cfnr */ reason = mISDN_REDIRECTING_REASON_NO_REPLY; break; default: reason = mISDN_REDIRECTING_REASON_UNKNOWN; break; } return reason; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the mISDN presentation to PresentedNumberUnscreened type * * \param presentation mISDN presentation to convert * \param number_present TRUE if the number is present * * \return PresentedNumberUnscreened type */ static unsigned misdn_to_PresentedNumberUnscreened_type(int presentation, int number_present) { unsigned type; switch (presentation) { case 0:/* allowed */ if (number_present) { type = 0;/* presentationAllowedNumber */ } else { type = 2;/* numberNotAvailableDueToInterworking */ } break; case 1:/* restricted */ if (number_present) { type = 3;/* presentationRestrictedNumber */ } else { type = 1;/* presentationRestricted */ } break; default: type = 2;/* numberNotAvailableDueToInterworking */ break; } return type; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the PresentedNumberUnscreened type to mISDN presentation * * \param type PresentedNumberUnscreened type * * \return mISDN presentation */ static int PresentedNumberUnscreened_to_misdn_pres(unsigned type) { int presentation; switch (type) { default: case 0:/* presentationAllowedNumber */ presentation = 0;/* allowed */ break; case 1:/* presentationRestricted */ case 3:/* presentationRestrictedNumber */ presentation = 1;/* restricted */ break; case 2:/* numberNotAvailableDueToInterworking */ presentation = 2;/* unavailable */ break; } return presentation; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the mISDN numbering plan to PartyNumber numbering plan * * \param number_plan mISDN numbering plan * * \return PartyNumber numbering plan */ static unsigned misdn_to_PartyNumber_plan(enum mISDN_NUMBER_PLAN number_plan) { unsigned party_plan; switch (number_plan) { default: case NUMPLAN_UNKNOWN: party_plan = 0;/* unknown */ break; case NUMPLAN_ISDN: party_plan = 1;/* public */ break; case NUMPLAN_DATA: party_plan = 3;/* data */ break; case NUMPLAN_TELEX: party_plan = 4;/* telex */ break; case NUMPLAN_NATIONAL: party_plan = 8;/* nationalStandard */ break; case NUMPLAN_PRIVATE: party_plan = 5;/* private */ break; } return party_plan; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert PartyNumber numbering plan to mISDN numbering plan * * \param party_plan PartyNumber numbering plan * * \return mISDN numbering plan */ static enum mISDN_NUMBER_PLAN PartyNumber_to_misdn_plan(unsigned party_plan) { enum mISDN_NUMBER_PLAN number_plan; switch (party_plan) { default: case 0:/* unknown */ number_plan = NUMPLAN_UNKNOWN; break; case 1:/* public */ number_plan = NUMPLAN_ISDN; break; case 3:/* data */ number_plan = NUMPLAN_DATA; break; case 4:/* telex */ number_plan = NUMPLAN_TELEX; break; case 8:/* nationalStandard */ number_plan = NUMPLAN_NATIONAL; break; case 5:/* private */ number_plan = NUMPLAN_PRIVATE; break; } return number_plan; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert mISDN type-of-number to PartyNumber public type-of-number * * \param ton mISDN type-of-number * * \return PartyNumber public type-of-number */ static unsigned misdn_to_PartyNumber_ton_public(enum mISDN_NUMBER_TYPE ton) { unsigned party_ton; switch (ton) { default: case NUMTYPE_UNKNOWN: party_ton = 0;/* unknown */ break; case NUMTYPE_INTERNATIONAL: party_ton = 1;/* internationalNumber */ break; case NUMTYPE_NATIONAL: party_ton = 2;/* nationalNumber */ break; case NUMTYPE_NETWORK_SPECIFIC: party_ton = 3;/* networkSpecificNumber */ break; case NUMTYPE_SUBSCRIBER: party_ton = 4;/* subscriberNumber */ break; case NUMTYPE_ABBREVIATED: party_ton = 6;/* abbreviatedNumber */ break; } return party_ton; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the PartyNumber public type-of-number to mISDN type-of-number * * \param party_ton PartyNumber public type-of-number * * \return mISDN type-of-number */ static enum mISDN_NUMBER_TYPE PartyNumber_to_misdn_ton_public(unsigned party_ton) { enum mISDN_NUMBER_TYPE ton; switch (party_ton) { default: case 0:/* unknown */ ton = NUMTYPE_UNKNOWN; break; case 1:/* internationalNumber */ ton = NUMTYPE_INTERNATIONAL; break; case 2:/* nationalNumber */ ton = NUMTYPE_NATIONAL; break; case 3:/* networkSpecificNumber */ ton = NUMTYPE_NETWORK_SPECIFIC; break; case 4:/* subscriberNumber */ ton = NUMTYPE_SUBSCRIBER; break; case 6:/* abbreviatedNumber */ ton = NUMTYPE_ABBREVIATED; break; } return ton; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert mISDN type-of-number to PartyNumber private type-of-number * * \param ton mISDN type-of-number * * \return PartyNumber private type-of-number */ static unsigned misdn_to_PartyNumber_ton_private(enum mISDN_NUMBER_TYPE ton) { unsigned party_ton; switch (ton) { default: case NUMTYPE_UNKNOWN: party_ton = 0;/* unknown */ break; case NUMTYPE_INTERNATIONAL: party_ton = 1;/* level2RegionalNumber */ break; case NUMTYPE_NATIONAL: party_ton = 2;/* level1RegionalNumber */ break; case NUMTYPE_NETWORK_SPECIFIC: party_ton = 3;/* pTNSpecificNumber */ break; case NUMTYPE_SUBSCRIBER: party_ton = 4;/* localNumber */ break; case NUMTYPE_ABBREVIATED: party_ton = 6;/* abbreviatedNumber */ break; } return party_ton; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Convert the PartyNumber private type-of-number to mISDN type-of-number * * \param party_ton PartyNumber private type-of-number * * \return mISDN type-of-number */ static enum mISDN_NUMBER_TYPE PartyNumber_to_misdn_ton_private(unsigned party_ton) { enum mISDN_NUMBER_TYPE ton; switch (party_ton) { default: case 0:/* unknown */ ton = NUMTYPE_UNKNOWN; break; case 1:/* level2RegionalNumber */ ton = NUMTYPE_INTERNATIONAL; break; case 2:/* level1RegionalNumber */ ton = NUMTYPE_NATIONAL; break; case 3:/* pTNSpecificNumber */ ton = NUMTYPE_NETWORK_SPECIFIC; break; case 4:/* localNumber */ ton = NUMTYPE_SUBSCRIBER; break; case 6:/* abbreviatedNumber */ ton = NUMTYPE_ABBREVIATED; break; } return ton; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /*! * \internal * \brief Convert the mISDN type of number code to a string * * \param number_type mISDN type of number code. * * \return The mISDN type of number code as a string */ static const char *misdn_to_str_ton(enum mISDN_NUMBER_TYPE number_type) { const char *str; switch (number_type) { default: case NUMTYPE_UNKNOWN: str = "Unknown"; break; case NUMTYPE_INTERNATIONAL: str = "International"; break; case NUMTYPE_NATIONAL: str = "National"; break; case NUMTYPE_NETWORK_SPECIFIC: str = "Network Specific"; break; case NUMTYPE_SUBSCRIBER: str = "Subscriber"; break; case NUMTYPE_ABBREVIATED: str = "Abbreviated"; break; } return str; } /*! * \internal * \brief Convert the mISDN type of number code to Asterisk type of number code * * \param number_type mISDN type of number code. * * \return Asterisk type of number code */ static int misdn_to_ast_ton(enum mISDN_NUMBER_TYPE number_type) { int ast_number_type; switch (number_type) { default: case NUMTYPE_UNKNOWN: ast_number_type = NUMTYPE_UNKNOWN << 4; break; case NUMTYPE_INTERNATIONAL: ast_number_type = NUMTYPE_INTERNATIONAL << 4; break; case NUMTYPE_NATIONAL: ast_number_type = NUMTYPE_NATIONAL << 4; break; case NUMTYPE_NETWORK_SPECIFIC: ast_number_type = NUMTYPE_NETWORK_SPECIFIC << 4; break; case NUMTYPE_SUBSCRIBER: ast_number_type = NUMTYPE_SUBSCRIBER << 4; break; case NUMTYPE_ABBREVIATED: ast_number_type = NUMTYPE_ABBREVIATED << 4; break; } return ast_number_type; } /*! * \internal * \brief Convert the Asterisk type of number code to mISDN type of number code * * \param ast_number_type Asterisk type of number code. * * \return mISDN type of number code */ static enum mISDN_NUMBER_TYPE ast_to_misdn_ton(unsigned ast_number_type) { enum mISDN_NUMBER_TYPE number_type; switch ((ast_number_type >> 4) & 0x07) { default: case NUMTYPE_UNKNOWN: number_type = NUMTYPE_UNKNOWN; break; case NUMTYPE_INTERNATIONAL: number_type = NUMTYPE_INTERNATIONAL; break; case NUMTYPE_NATIONAL: number_type = NUMTYPE_NATIONAL; break; case NUMTYPE_NETWORK_SPECIFIC: number_type = NUMTYPE_NETWORK_SPECIFIC; break; case NUMTYPE_SUBSCRIBER: number_type = NUMTYPE_SUBSCRIBER; break; case NUMTYPE_ABBREVIATED: number_type = NUMTYPE_ABBREVIATED; break; } return number_type; } /*! * \internal * \brief Convert the mISDN numbering plan code to a string * * \param number_plan mISDN numbering plan code. * * \return The mISDN numbering plan code as a string */ static const char *misdn_to_str_plan(enum mISDN_NUMBER_PLAN number_plan) { const char *str; switch (number_plan) { default: case NUMPLAN_UNKNOWN: str = "Unknown"; break; case NUMPLAN_ISDN: str = "ISDN"; break; case NUMPLAN_DATA: str = "Data"; break; case NUMPLAN_TELEX: str = "Telex"; break; case NUMPLAN_NATIONAL: str = "National"; break; case NUMPLAN_PRIVATE: str = "Private"; break; } return str; } /*! * \internal * \brief Convert the mISDN numbering plan code to Asterisk numbering plan code * * \param number_plan mISDN numbering plan code. * * \return Asterisk numbering plan code */ static int misdn_to_ast_plan(enum mISDN_NUMBER_PLAN number_plan) { int ast_number_plan; switch (number_plan) { default: case NUMPLAN_UNKNOWN: ast_number_plan = NUMPLAN_UNKNOWN; break; case NUMPLAN_ISDN: ast_number_plan = NUMPLAN_ISDN; break; case NUMPLAN_DATA: ast_number_plan = NUMPLAN_DATA; break; case NUMPLAN_TELEX: ast_number_plan = NUMPLAN_TELEX; break; case NUMPLAN_NATIONAL: ast_number_plan = NUMPLAN_NATIONAL; break; case NUMPLAN_PRIVATE: ast_number_plan = NUMPLAN_PRIVATE; break; } return ast_number_plan; } /*! * \internal * \brief Convert the Asterisk numbering plan code to mISDN numbering plan code * * \param ast_number_plan Asterisk numbering plan code. * * \return mISDN numbering plan code */ static enum mISDN_NUMBER_PLAN ast_to_misdn_plan(unsigned ast_number_plan) { enum mISDN_NUMBER_PLAN number_plan; switch (ast_number_plan & 0x0F) { default: case NUMPLAN_UNKNOWN: number_plan = NUMPLAN_UNKNOWN; break; case NUMPLAN_ISDN: number_plan = NUMPLAN_ISDN; break; case NUMPLAN_DATA: number_plan = NUMPLAN_DATA; break; case NUMPLAN_TELEX: number_plan = NUMPLAN_TELEX; break; case NUMPLAN_NATIONAL: number_plan = NUMPLAN_NATIONAL; break; case NUMPLAN_PRIVATE: number_plan = NUMPLAN_PRIVATE; break; } return number_plan; } /*! * \internal * \brief Convert the mISDN presentation code to a string * * \param presentation mISDN number presentation restriction code. * * \return The mISDN presentation code as a string */ static const char *misdn_to_str_pres(int presentation) { const char *str; switch (presentation) { case 0: str = "Allowed"; break; case 1: str = "Restricted"; break; case 2: str = "Unavailable"; break; default: str = "Unknown"; break; } return str; } /*! * \internal * \brief Convert the mISDN presentation code to Asterisk presentation code * * \param presentation mISDN number presentation restriction code. * * \return Asterisk presentation code */ static int misdn_to_ast_pres(int presentation) { switch (presentation) { default: case 0: presentation = AST_PRES_ALLOWED; break; case 1: presentation = AST_PRES_RESTRICTED; break; case 2: presentation = AST_PRES_UNAVAILABLE; break; } return presentation; } /*! * \internal * \brief Convert the Asterisk presentation code to mISDN presentation code * * \param presentation Asterisk number presentation restriction code. * * \return mISDN presentation code */ static int ast_to_misdn_pres(int presentation) { switch (presentation & AST_PRES_RESTRICTION) { default: case AST_PRES_ALLOWED: presentation = 0; break; case AST_PRES_RESTRICTED: presentation = 1; break; case AST_PRES_UNAVAILABLE: presentation = 2; break; } return presentation; } /*! * \internal * \brief Convert the mISDN screening code to a string * * \param screening mISDN number screening code. * * \return The mISDN screening code as a string */ static const char *misdn_to_str_screen(int screening) { const char *str; switch (screening) { case 0: str = "Unscreened"; break; case 1: str = "Passed Screen"; break; case 2: str = "Failed Screen"; break; case 3: str = "Network Number"; break; default: str = "Unknown"; break; } return str; } /*! * \internal * \brief Convert the mISDN screening code to Asterisk screening code * * \param screening mISDN number screening code. * * \return Asterisk screening code */ static int misdn_to_ast_screen(int screening) { switch (screening) { default: case 0: screening = AST_PRES_USER_NUMBER_UNSCREENED; break; case 1: screening = AST_PRES_USER_NUMBER_PASSED_SCREEN; break; case 2: screening = AST_PRES_USER_NUMBER_FAILED_SCREEN; break; case 3: screening = AST_PRES_NETWORK_NUMBER; break; } return screening; } /*! * \internal * \brief Convert the Asterisk screening code to mISDN screening code * * \param screening Asterisk number screening code. * * \return mISDN screening code */ static int ast_to_misdn_screen(int screening) { switch (screening & AST_PRES_NUMBER_TYPE) { default: case AST_PRES_USER_NUMBER_UNSCREENED: screening = 0; break; case AST_PRES_USER_NUMBER_PASSED_SCREEN: screening = 1; break; case AST_PRES_USER_NUMBER_FAILED_SCREEN: screening = 2; break; case AST_PRES_NETWORK_NUMBER: screening = 3; break; } return screening; } /*! * \internal * \brief Convert Asterisk redirecting reason to mISDN redirecting reason code. * * \param ast Asterisk redirecting reason code. * * \return mISDN reason code */ static enum mISDN_REDIRECTING_REASON ast_to_misdn_reason(const enum AST_REDIRECTING_REASON ast) { unsigned index; static const struct misdn_reasons { enum AST_REDIRECTING_REASON ast; enum mISDN_REDIRECTING_REASON q931; } misdn_reason_table[] = { /* *INDENT-OFF* */ { AST_REDIRECTING_REASON_UNKNOWN, mISDN_REDIRECTING_REASON_UNKNOWN }, { AST_REDIRECTING_REASON_USER_BUSY, mISDN_REDIRECTING_REASON_CALL_FWD_BUSY }, { AST_REDIRECTING_REASON_NO_ANSWER, mISDN_REDIRECTING_REASON_NO_REPLY }, { AST_REDIRECTING_REASON_UNAVAILABLE, mISDN_REDIRECTING_REASON_NO_REPLY }, { AST_REDIRECTING_REASON_UNCONDITIONAL, mISDN_REDIRECTING_REASON_CALL_FWD }, { AST_REDIRECTING_REASON_TIME_OF_DAY, mISDN_REDIRECTING_REASON_UNKNOWN }, { AST_REDIRECTING_REASON_DO_NOT_DISTURB, mISDN_REDIRECTING_REASON_UNKNOWN }, { AST_REDIRECTING_REASON_DEFLECTION, mISDN_REDIRECTING_REASON_DEFLECTION }, { AST_REDIRECTING_REASON_FOLLOW_ME, mISDN_REDIRECTING_REASON_UNKNOWN }, { AST_REDIRECTING_REASON_OUT_OF_ORDER, mISDN_REDIRECTING_REASON_OUT_OF_ORDER }, { AST_REDIRECTING_REASON_AWAY, mISDN_REDIRECTING_REASON_UNKNOWN }, { AST_REDIRECTING_REASON_CALL_FWD_DTE, mISDN_REDIRECTING_REASON_CALL_FWD_DTE } /* *INDENT-ON* */ }; for (index = 0; index < ARRAY_LEN(misdn_reason_table); ++index) { if (misdn_reason_table[index].ast == ast) { return misdn_reason_table[index].q931; } } return mISDN_REDIRECTING_REASON_UNKNOWN; } /*! * \internal * \brief Convert the mISDN redirecting reason to Asterisk redirecting reason code * * \param q931 mISDN redirecting reason code. * * \return Asterisk redirecting reason code */ static enum AST_REDIRECTING_REASON misdn_to_ast_reason(const enum mISDN_REDIRECTING_REASON q931) { enum AST_REDIRECTING_REASON ast; switch (q931) { default: case mISDN_REDIRECTING_REASON_UNKNOWN: ast = AST_REDIRECTING_REASON_UNKNOWN; break; case mISDN_REDIRECTING_REASON_CALL_FWD_BUSY: ast = AST_REDIRECTING_REASON_USER_BUSY; break; case mISDN_REDIRECTING_REASON_NO_REPLY: ast = AST_REDIRECTING_REASON_NO_ANSWER; break; case mISDN_REDIRECTING_REASON_DEFLECTION: ast = AST_REDIRECTING_REASON_DEFLECTION; break; case mISDN_REDIRECTING_REASON_OUT_OF_ORDER: ast = AST_REDIRECTING_REASON_OUT_OF_ORDER; break; case mISDN_REDIRECTING_REASON_CALL_FWD_DTE: ast = AST_REDIRECTING_REASON_CALL_FWD_DTE; break; case mISDN_REDIRECTING_REASON_CALL_FWD: ast = AST_REDIRECTING_REASON_UNCONDITIONAL; break; } return ast; } struct allowed_bearers { char *name; /*!< Bearer capability name string used in /etc/misdn.conf allowed_bearers */ char *display; /*!< Bearer capability displayable name */ int cap; /*!< SETUP message bearer capability field code value */ int deprecated; /*!< TRUE if this entry is deprecated. (Misspelled or bad name to use) */ }; /* *INDENT-OFF* */ static const struct allowed_bearers allowed_bearers_array[] = { /* Name, Displayable Name Bearer Capability, Deprecated */ { "speech", "Speech", INFO_CAPABILITY_SPEECH, 0 }, { "3_1khz", "3.1KHz Audio", INFO_CAPABILITY_AUDIO_3_1K, 0 }, { "digital_unrestricted", "Unrestricted Digital", INFO_CAPABILITY_DIGITAL_UNRESTRICTED, 0 }, { "digital_restricted", "Restricted Digital", INFO_CAPABILITY_DIGITAL_RESTRICTED, 0 }, { "digital_restriced", "Restricted Digital", INFO_CAPABILITY_DIGITAL_RESTRICTED, 1 }, /* Allow misspelling for backwards compatibility */ { "video", "Video", INFO_CAPABILITY_VIDEO, 0 } }; /* *INDENT-ON* */ static const char *bearer2str(int cap) { unsigned index; for (index = 0; index < ARRAY_LEN(allowed_bearers_array); ++index) { if (allowed_bearers_array[index].cap == cap) { return allowed_bearers_array[index].display; } } return "Unknown Bearer"; } #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Fill in facility PartyNumber information * * \param party PartyNumber structure to fill in. * \param id Information to put in PartyNumber structure. * * \return Nothing */ static void misdn_PartyNumber_fill(struct FacPartyNumber *party, const struct misdn_party_id *id) { ast_copy_string((char *) party->Number, id->number, sizeof(party->Number)); party->LengthOfNumber = strlen((char *) party->Number); party->Type = misdn_to_PartyNumber_plan(id->number_plan); switch (party->Type) { case 1:/* public */ party->TypeOfNumber = misdn_to_PartyNumber_ton_public(id->number_type); break; case 5:/* private */ party->TypeOfNumber = misdn_to_PartyNumber_ton_private(id->number_type); break; default: party->TypeOfNumber = 0;/* Don't care */ break; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Extract the information from PartyNumber * * \param id Where to put extracted PartyNumber information * \param party PartyNumber information to extract * * \return Nothing */ static void misdn_PartyNumber_extract(struct misdn_party_id *id, const struct FacPartyNumber *party) { if (party->LengthOfNumber) { ast_copy_string(id->number, (char *) party->Number, sizeof(id->number)); id->number_plan = PartyNumber_to_misdn_plan(party->Type); switch (party->Type) { case 1:/* public */ id->number_type = PartyNumber_to_misdn_ton_public(party->TypeOfNumber); break; case 5:/* private */ id->number_type = PartyNumber_to_misdn_ton_private(party->TypeOfNumber); break; default: id->number_type = NUMTYPE_UNKNOWN; break; } } else { /* Number not present */ id->number_type = NUMTYPE_UNKNOWN; id->number_plan = NUMPLAN_ISDN; id->number[0] = 0; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Fill in facility Address information * * \param Address Address structure to fill in. * \param id Information to put in Address structure. * * \return Nothing */ static void misdn_Address_fill(struct FacAddress *Address, const struct misdn_party_id *id) { misdn_PartyNumber_fill(&Address->Party, id); /* Subaddresses are not supported yet */ Address->Subaddress.Length = 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Fill in facility PresentedNumberUnscreened information * * \param presented PresentedNumberUnscreened structure to fill in. * \param id Information to put in PresentedNumberUnscreened structure. * * \return Nothing */ static void misdn_PresentedNumberUnscreened_fill(struct FacPresentedNumberUnscreened *presented, const struct misdn_party_id *id) { presented->Type = misdn_to_PresentedNumberUnscreened_type(id->presentation, id->number[0] ? 1 : 0); misdn_PartyNumber_fill(&presented->Unscreened, id); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Extract the information from PartyNumber * * \param id Where to put extracted PresentedNumberUnscreened information * \param presented PresentedNumberUnscreened information to extract * * \return Nothing */ static void misdn_PresentedNumberUnscreened_extract(struct misdn_party_id *id, const struct FacPresentedNumberUnscreened *presented) { id->presentation = PresentedNumberUnscreened_to_misdn_pres(presented->Type); id->screening = 0;/* unscreened */ switch (presented->Type) { case 0:/* presentationAllowedNumber */ case 3:/* presentationRestrictedNumber */ misdn_PartyNumber_extract(id, &presented->Unscreened); break; case 1:/* presentationRestricted */ case 2:/* numberNotAvailableDueToInterworking */ default: /* Number not present (And uninitialized so do not even look at it!) */ id->number_type = NUMTYPE_UNKNOWN; id->number_plan = NUMPLAN_ISDN; id->number[0] = 0; break; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static const char Level_Spacing[] = " ";/* Work for up to 10 levels */ #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_PartyNumber(unsigned Level, const struct FacPartyNumber *Party, const struct misdn_bchannel *bc) { if (Party->LengthOfNumber) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s PartyNumber: Type:%d\n", Spacing, Party->Type); switch (Party->Type) { case 0: /* Unknown PartyNumber */ chan_misdn_log(1, bc->port, " -->%s Unknown: %s\n", Spacing, Party->Number); break; case 1: /* Public PartyNumber */ chan_misdn_log(1, bc->port, " -->%s Public TON:%d %s\n", Spacing, Party->TypeOfNumber, Party->Number); break; case 2: /* NSAP encoded PartyNumber */ chan_misdn_log(1, bc->port, " -->%s NSAP: %s\n", Spacing, Party->Number); break; case 3: /* Data PartyNumber (Not used) */ chan_misdn_log(1, bc->port, " -->%s Data: %s\n", Spacing, Party->Number); break; case 4: /* Telex PartyNumber (Not used) */ chan_misdn_log(1, bc->port, " -->%s Telex: %s\n", Spacing, Party->Number); break; case 5: /* Private PartyNumber */ chan_misdn_log(1, bc->port, " -->%s Private TON:%d %s\n", Spacing, Party->TypeOfNumber, Party->Number); break; case 8: /* National Standard PartyNumber (Not used) */ chan_misdn_log(1, bc->port, " -->%s National: %s\n", Spacing, Party->Number); break; default: break; } } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_Subaddress(unsigned Level, const struct FacPartySubaddress *Subaddress, const struct misdn_bchannel *bc) { if (Subaddress->Length) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s Subaddress: Type:%d\n", Spacing, Subaddress->Type); switch (Subaddress->Type) { case 0: /* UserSpecified */ if (Subaddress->u.UserSpecified.OddCountPresent) { chan_misdn_log(1, bc->port, " -->%s User BCD OddCount:%d NumOctets:%d\n", Spacing, Subaddress->u.UserSpecified.OddCount, Subaddress->Length); } else { chan_misdn_log(1, bc->port, " -->%s User: %s\n", Spacing, Subaddress->u.UserSpecified.Information); } break; case 1: /* NSAP */ chan_misdn_log(1, bc->port, " -->%s NSAP: %s\n", Spacing, Subaddress->u.Nsap); break; default: break; } } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_Address(unsigned Level, const struct FacAddress *Address, const struct misdn_bchannel *bc) { print_facility_PartyNumber(Level, &Address->Party, bc); print_facility_Subaddress(Level, &Address->Subaddress, bc); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_PresentedNumberUnscreened(unsigned Level, const struct FacPresentedNumberUnscreened *Presented, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s Unscreened Type:%d\n", Spacing, Presented->Type); switch (Presented->Type) { case 0: /* presentationAllowedNumber */ chan_misdn_log(1, bc->port, " -->%s Allowed:\n", Spacing); print_facility_PartyNumber(Level + 2, &Presented->Unscreened, bc); break; case 1: /* presentationRestricted */ chan_misdn_log(1, bc->port, " -->%s Restricted\n", Spacing); break; case 2: /* numberNotAvailableDueToInterworking */ chan_misdn_log(1, bc->port, " -->%s Not Available\n", Spacing); break; case 3: /* presentationRestrictedNumber */ chan_misdn_log(1, bc->port, " -->%s Restricted:\n", Spacing); print_facility_PartyNumber(Level + 2, &Presented->Unscreened, bc); break; default: break; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_AddressScreened(unsigned Level, const struct FacAddressScreened *Address, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s ScreeningIndicator:%d\n", Spacing, Address->ScreeningIndicator); print_facility_PartyNumber(Level, &Address->Party, bc); print_facility_Subaddress(Level, &Address->Subaddress, bc); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_PresentedAddressScreened(unsigned Level, const struct FacPresentedAddressScreened *Presented, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s Screened Type:%d\n", Spacing, Presented->Type); switch (Presented->Type) { case 0: /* presentationAllowedAddress */ chan_misdn_log(1, bc->port, " -->%s Allowed:\n", Spacing); print_facility_AddressScreened(Level + 2, &Presented->Address, bc); break; case 1: /* presentationRestricted */ chan_misdn_log(1, bc->port, " -->%s Restricted\n", Spacing); break; case 2: /* numberNotAvailableDueToInterworking */ chan_misdn_log(1, bc->port, " -->%s Not Available\n", Spacing); break; case 3: /* presentationRestrictedAddress */ chan_misdn_log(1, bc->port, " -->%s Restricted:\n", Spacing); print_facility_AddressScreened(Level + 2, &Presented->Address, bc); break; default: break; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_Q931_Bc_Hlc_Llc(unsigned Level, const struct Q931_Bc_Hlc_Llc *Q931ie, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s Q931ie:\n", Spacing); if (Q931ie->Bc.Length) { chan_misdn_log(1, bc->port, " -->%s Bc Len:%d\n", Spacing, Q931ie->Bc.Length); } if (Q931ie->Hlc.Length) { chan_misdn_log(1, bc->port, " -->%s Hlc Len:%d\n", Spacing, Q931ie->Hlc.Length); } if (Q931ie->Llc.Length) { chan_misdn_log(1, bc->port, " -->%s Llc Len:%d\n", Spacing, Q931ie->Llc.Length); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_Q931_Bc_Hlc_Llc_Uu(unsigned Level, const struct Q931_Bc_Hlc_Llc_Uu *Q931ie, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s Q931ie:\n", Spacing); if (Q931ie->Bc.Length) { chan_misdn_log(1, bc->port, " -->%s Bc Len:%d\n", Spacing, Q931ie->Bc.Length); } if (Q931ie->Hlc.Length) { chan_misdn_log(1, bc->port, " -->%s Hlc Len:%d\n", Spacing, Q931ie->Hlc.Length); } if (Q931ie->Llc.Length) { chan_misdn_log(1, bc->port, " -->%s Llc Len:%d\n", Spacing, Q931ie->Llc.Length); } if (Q931ie->UserInfo.Length) { chan_misdn_log(1, bc->port, " -->%s UserInfo Len:%d\n", Spacing, Q931ie->UserInfo.Length); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_CallInformation(unsigned Level, const struct FacCallInformation *CallInfo, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s CCBSReference:%d\n", Spacing, CallInfo->CCBSReference); chan_misdn_log(1, bc->port, " -->%s AddressOfB:\n", Spacing); print_facility_Address(Level + 1, &CallInfo->AddressOfB, bc); print_facility_Q931_Bc_Hlc_Llc(Level, &CallInfo->Q931ie, bc); if (CallInfo->SubaddressOfA.Length) { chan_misdn_log(1, bc->port, " -->%s SubaddressOfA:\n", Spacing); print_facility_Subaddress(Level + 1, &CallInfo->SubaddressOfA, bc); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_ServedUserNr(unsigned Level, const struct FacPartyNumber *Party, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; if (Party->LengthOfNumber) { print_facility_PartyNumber(Level, Party, bc); } else { chan_misdn_log(1, bc->port, " -->%s All Numbers\n", Spacing); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void print_facility_IntResult(unsigned Level, const struct FacForwardingRecord *ForwardingRecord, const struct misdn_bchannel *bc) { const char *Spacing; Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level]; chan_misdn_log(1, bc->port, " -->%s Procedure:%d BasicService:%d\n", Spacing, ForwardingRecord->Procedure, ForwardingRecord->BasicService); chan_misdn_log(1, bc->port, " -->%s ForwardedTo:\n", Spacing); print_facility_Address(Level + 1, &ForwardingRecord->ForwardedTo, bc); chan_misdn_log(1, bc->port, " -->%s ServedUserNr:\n", Spacing); print_facility_ServedUserNr(Level + 1, &ForwardingRecord->ServedUser, bc); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ static void print_facility(const struct FacParm *fac, const struct misdn_bchannel *bc) { #if defined(AST_MISDN_ENHANCEMENTS) unsigned Index; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ switch (fac->Function) { #if defined(AST_MISDN_ENHANCEMENTS) case Fac_ActivationDiversion: chan_misdn_log(1, bc->port, " --> ActivationDiversion: InvokeID:%d\n", fac->u.ActivationDiversion.InvokeID); switch (fac->u.ActivationDiversion.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: Procedure:%d BasicService:%d\n", fac->u.ActivationDiversion.Component.Invoke.Procedure, fac->u.ActivationDiversion.Component.Invoke.BasicService); chan_misdn_log(1, bc->port, " --> ForwardedTo:\n"); print_facility_Address(3, &fac->u.ActivationDiversion.Component.Invoke.ForwardedTo, bc); chan_misdn_log(1, bc->port, " --> ServedUserNr:\n"); print_facility_ServedUserNr(3, &fac->u.ActivationDiversion.Component.Invoke.ServedUser, bc); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result\n"); break; default: break; } break; case Fac_DeactivationDiversion: chan_misdn_log(1, bc->port, " --> DeactivationDiversion: InvokeID:%d\n", fac->u.DeactivationDiversion.InvokeID); switch (fac->u.DeactivationDiversion.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: Procedure:%d BasicService:%d\n", fac->u.DeactivationDiversion.Component.Invoke.Procedure, fac->u.DeactivationDiversion.Component.Invoke.BasicService); chan_misdn_log(1, bc->port, " --> ServedUserNr:\n"); print_facility_ServedUserNr(3, &fac->u.DeactivationDiversion.Component.Invoke.ServedUser, bc); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result\n"); break; default: break; } break; case Fac_ActivationStatusNotificationDiv: chan_misdn_log(1, bc->port, " --> ActivationStatusNotificationDiv: InvokeID:%d Procedure:%d BasicService:%d\n", fac->u.ActivationStatusNotificationDiv.InvokeID, fac->u.ActivationStatusNotificationDiv.Procedure, fac->u.ActivationStatusNotificationDiv.BasicService); chan_misdn_log(1, bc->port, " --> ForwardedTo:\n"); print_facility_Address(2, &fac->u.ActivationStatusNotificationDiv.ForwardedTo, bc); chan_misdn_log(1, bc->port, " --> ServedUserNr:\n"); print_facility_ServedUserNr(2, &fac->u.ActivationStatusNotificationDiv.ServedUser, bc); break; case Fac_DeactivationStatusNotificationDiv: chan_misdn_log(1, bc->port, " --> DeactivationStatusNotificationDiv: InvokeID:%d Procedure:%d BasicService:%d\n", fac->u.DeactivationStatusNotificationDiv.InvokeID, fac->u.DeactivationStatusNotificationDiv.Procedure, fac->u.DeactivationStatusNotificationDiv.BasicService); chan_misdn_log(1, bc->port, " --> ServedUserNr:\n"); print_facility_ServedUserNr(2, &fac->u.DeactivationStatusNotificationDiv.ServedUser, bc); break; case Fac_InterrogationDiversion: chan_misdn_log(1, bc->port, " --> InterrogationDiversion: InvokeID:%d\n", fac->u.InterrogationDiversion.InvokeID); switch (fac->u.InterrogationDiversion.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: Procedure:%d BasicService:%d\n", fac->u.InterrogationDiversion.Component.Invoke.Procedure, fac->u.InterrogationDiversion.Component.Invoke.BasicService); chan_misdn_log(1, bc->port, " --> ServedUserNr:\n"); print_facility_ServedUserNr(3, &fac->u.InterrogationDiversion.Component.Invoke.ServedUser, bc); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result:\n"); if (fac->u.InterrogationDiversion.Component.Result.NumRecords) { for (Index = 0; Index < fac->u.InterrogationDiversion.Component.Result.NumRecords; ++Index) { chan_misdn_log(1, bc->port, " --> IntResult[%d]:\n", Index); print_facility_IntResult(3, &fac->u.InterrogationDiversion.Component.Result.List[Index], bc); } } break; default: break; } break; case Fac_DiversionInformation: chan_misdn_log(1, bc->port, " --> DiversionInformation: InvokeID:%d Reason:%d BasicService:%d\n", fac->u.DiversionInformation.InvokeID, fac->u.DiversionInformation.DiversionReason, fac->u.DiversionInformation.BasicService); if (fac->u.DiversionInformation.ServedUserSubaddress.Length) { chan_misdn_log(1, bc->port, " --> ServedUserSubaddress:\n"); print_facility_Subaddress(2, &fac->u.DiversionInformation.ServedUserSubaddress, bc); } if (fac->u.DiversionInformation.CallingAddressPresent) { chan_misdn_log(1, bc->port, " --> CallingAddress:\n"); print_facility_PresentedAddressScreened(2, &fac->u.DiversionInformation.CallingAddress, bc); } if (fac->u.DiversionInformation.OriginalCalledPresent) { chan_misdn_log(1, bc->port, " --> OriginalCalledNr:\n"); print_facility_PresentedNumberUnscreened(2, &fac->u.DiversionInformation.OriginalCalled, bc); } if (fac->u.DiversionInformation.LastDivertingPresent) { chan_misdn_log(1, bc->port, " --> LastDivertingNr:\n"); print_facility_PresentedNumberUnscreened(2, &fac->u.DiversionInformation.LastDiverting, bc); } if (fac->u.DiversionInformation.LastDivertingReasonPresent) { chan_misdn_log(1, bc->port, " --> LastDivertingReason:%d\n", fac->u.DiversionInformation.LastDivertingReason); } if (fac->u.DiversionInformation.UserInfo.Length) { chan_misdn_log(1, bc->port, " --> UserInfo Length:%d\n", fac->u.DiversionInformation.UserInfo.Length); } break; case Fac_CallDeflection: chan_misdn_log(1, bc->port, " --> CallDeflection: InvokeID:%d\n", fac->u.CallDeflection.InvokeID); switch (fac->u.CallDeflection.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke:\n"); if (fac->u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent) { chan_misdn_log(1, bc->port, " --> PresentationAllowed:%d\n", fac->u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser); } chan_misdn_log(1, bc->port, " --> DeflectionAddress:\n"); print_facility_Address(3, &fac->u.CallDeflection.Component.Invoke.Deflection, bc); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result\n"); break; default: break; } break; case Fac_CallRerouteing: chan_misdn_log(1, bc->port, " --> CallRerouteing: InvokeID:%d\n", fac->u.CallRerouteing.InvokeID); switch (fac->u.CallRerouteing.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: Reason:%d Counter:%d\n", fac->u.CallRerouteing.Component.Invoke.ReroutingReason, fac->u.CallRerouteing.Component.Invoke.ReroutingCounter); chan_misdn_log(1, bc->port, " --> CalledAddress:\n"); print_facility_Address(3, &fac->u.CallRerouteing.Component.Invoke.CalledAddress, bc); print_facility_Q931_Bc_Hlc_Llc_Uu(2, &fac->u.CallRerouteing.Component.Invoke.Q931ie, bc); chan_misdn_log(1, bc->port, " --> LastReroutingNr:\n"); print_facility_PresentedNumberUnscreened(3, &fac->u.CallRerouteing.Component.Invoke.LastRerouting, bc); chan_misdn_log(1, bc->port, " --> SubscriptionOption:%d\n", fac->u.CallRerouteing.Component.Invoke.SubscriptionOption); if (fac->u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length) { chan_misdn_log(1, bc->port, " --> CallingParty:\n"); print_facility_Subaddress(3, &fac->u.CallRerouteing.Component.Invoke.CallingPartySubaddress, bc); } break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result\n"); break; default: break; } break; case Fac_InterrogateServedUserNumbers: chan_misdn_log(1, bc->port, " --> InterrogateServedUserNumbers: InvokeID:%d\n", fac->u.InterrogateServedUserNumbers.InvokeID); switch (fac->u.InterrogateServedUserNumbers.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke\n"); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result:\n"); if (fac->u.InterrogateServedUserNumbers.Component.Result.NumRecords) { for (Index = 0; Index < fac->u.InterrogateServedUserNumbers.Component.Result.NumRecords; ++Index) { chan_misdn_log(1, bc->port, " --> ServedUserNr[%d]:\n", Index); print_facility_PartyNumber(3, &fac->u.InterrogateServedUserNumbers.Component.Result.List[Index], bc); } } break; default: break; } break; case Fac_DivertingLegInformation1: chan_misdn_log(1, bc->port, " --> DivertingLegInformation1: InvokeID:%d Reason:%d SubscriptionOption:%d\n", fac->u.DivertingLegInformation1.InvokeID, fac->u.DivertingLegInformation1.DiversionReason, fac->u.DivertingLegInformation1.SubscriptionOption); if (fac->u.DivertingLegInformation1.DivertedToPresent) { chan_misdn_log(1, bc->port, " --> DivertedToNr:\n"); print_facility_PresentedNumberUnscreened(2, &fac->u.DivertingLegInformation1.DivertedTo, bc); } break; case Fac_DivertingLegInformation2: chan_misdn_log(1, bc->port, " --> DivertingLegInformation2: InvokeID:%d Reason:%d Count:%d\n", fac->u.DivertingLegInformation2.InvokeID, fac->u.DivertingLegInformation2.DiversionReason, fac->u.DivertingLegInformation2.DiversionCounter); if (fac->u.DivertingLegInformation2.DivertingPresent) { chan_misdn_log(1, bc->port, " --> DivertingNr:\n"); print_facility_PresentedNumberUnscreened(2, &fac->u.DivertingLegInformation2.Diverting, bc); } if (fac->u.DivertingLegInformation2.OriginalCalledPresent) { chan_misdn_log(1, bc->port, " --> OriginalCalledNr:\n"); print_facility_PresentedNumberUnscreened(2, &fac->u.DivertingLegInformation2.OriginalCalled, bc); } break; case Fac_DivertingLegInformation3: chan_misdn_log(1, bc->port, " --> DivertingLegInformation3: InvokeID:%d PresentationAllowed:%d\n", fac->u.DivertingLegInformation3.InvokeID, fac->u.DivertingLegInformation3.PresentationAllowedIndicator); break; #else /* !defined(AST_MISDN_ENHANCEMENTS) */ case Fac_CD: chan_misdn_log(1, bc->port, " --> calldeflect to: %s, presentable: %s\n", fac->u.CDeflection.DeflectedToNumber, fac->u.CDeflection.PresentationAllowed ? "yes" : "no"); break; #endif /* !defined(AST_MISDN_ENHANCEMENTS) */ case Fac_AOCDCurrency: if (fac->u.AOCDcur.chargeNotAvailable) { chan_misdn_log(1, bc->port, " --> AOCD currency: charge not available\n"); } else if (fac->u.AOCDcur.freeOfCharge) { chan_misdn_log(1, bc->port, " --> AOCD currency: free of charge\n"); } else if (fac->u.AOCDchu.billingId >= 0) { chan_misdn_log(1, bc->port, " --> AOCD currency: currency:%s amount:%d multiplier:%d typeOfChargingInfo:%s billingId:%d\n", fac->u.AOCDcur.currency, fac->u.AOCDcur.currencyAmount, fac->u.AOCDcur.multiplier, (fac->u.AOCDcur.typeOfChargingInfo == 0) ? "subTotal" : "total", fac->u.AOCDcur.billingId); } else { chan_misdn_log(1, bc->port, " --> AOCD currency: currency:%s amount:%d multiplier:%d typeOfChargingInfo:%s\n", fac->u.AOCDcur.currency, fac->u.AOCDcur.currencyAmount, fac->u.AOCDcur.multiplier, (fac->u.AOCDcur.typeOfChargingInfo == 0) ? "subTotal" : "total"); } break; case Fac_AOCDChargingUnit: if (fac->u.AOCDchu.chargeNotAvailable) { chan_misdn_log(1, bc->port, " --> AOCD charging unit: charge not available\n"); } else if (fac->u.AOCDchu.freeOfCharge) { chan_misdn_log(1, bc->port, " --> AOCD charging unit: free of charge\n"); } else if (fac->u.AOCDchu.billingId >= 0) { chan_misdn_log(1, bc->port, " --> AOCD charging unit: recordedUnits:%d typeOfChargingInfo:%s billingId:%d\n", fac->u.AOCDchu.recordedUnits, (fac->u.AOCDchu.typeOfChargingInfo == 0) ? "subTotal" : "total", fac->u.AOCDchu.billingId); } else { chan_misdn_log(1, bc->port, " --> AOCD charging unit: recordedUnits:%d typeOfChargingInfo:%s\n", fac->u.AOCDchu.recordedUnits, (fac->u.AOCDchu.typeOfChargingInfo == 0) ? "subTotal" : "total"); } break; #if defined(AST_MISDN_ENHANCEMENTS) case Fac_ERROR: chan_misdn_log(1, bc->port, " --> ERROR: InvokeID:%d, Code:0x%02x\n", fac->u.ERROR.invokeId, fac->u.ERROR.errorValue); break; case Fac_RESULT: chan_misdn_log(1, bc->port, " --> RESULT: InvokeID:%d\n", fac->u.RESULT.InvokeID); break; case Fac_REJECT: if (fac->u.REJECT.InvokeIDPresent) { chan_misdn_log(1, bc->port, " --> REJECT: InvokeID:%d, Code:0x%02x\n", fac->u.REJECT.InvokeID, fac->u.REJECT.Code); } else { chan_misdn_log(1, bc->port, " --> REJECT: Code:0x%02x\n", fac->u.REJECT.Code); } break; case Fac_EctExecute: chan_misdn_log(1, bc->port, " --> EctExecute: InvokeID:%d\n", fac->u.EctExecute.InvokeID); break; case Fac_ExplicitEctExecute: chan_misdn_log(1, bc->port, " --> ExplicitEctExecute: InvokeID:%d LinkID:%d\n", fac->u.ExplicitEctExecute.InvokeID, fac->u.ExplicitEctExecute.LinkID); break; case Fac_RequestSubaddress: chan_misdn_log(1, bc->port, " --> RequestSubaddress: InvokeID:%d\n", fac->u.RequestSubaddress.InvokeID); break; case Fac_SubaddressTransfer: chan_misdn_log(1, bc->port, " --> SubaddressTransfer: InvokeID:%d\n", fac->u.SubaddressTransfer.InvokeID); print_facility_Subaddress(1, &fac->u.SubaddressTransfer.Subaddress, bc); break; case Fac_EctLinkIdRequest: chan_misdn_log(1, bc->port, " --> EctLinkIdRequest: InvokeID:%d\n", fac->u.EctLinkIdRequest.InvokeID); switch (fac->u.EctLinkIdRequest.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke\n"); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: LinkID:%d\n", fac->u.EctLinkIdRequest.Component.Result.LinkID); break; default: break; } break; case Fac_EctInform: chan_misdn_log(1, bc->port, " --> EctInform: InvokeID:%d Status:%d\n", fac->u.EctInform.InvokeID, fac->u.EctInform.Status); if (fac->u.EctInform.RedirectionPresent) { chan_misdn_log(1, bc->port, " --> Redirection Number\n"); print_facility_PresentedNumberUnscreened(2, &fac->u.EctInform.Redirection, bc); } break; case Fac_EctLoopTest: chan_misdn_log(1, bc->port, " --> EctLoopTest: InvokeID:%d\n", fac->u.EctLoopTest.InvokeID); switch (fac->u.EctLoopTest.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: CallTransferID:%d\n", fac->u.EctLoopTest.Component.Invoke.CallTransferID); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: LoopResult:%d\n", fac->u.EctLoopTest.Component.Result.LoopResult); break; default: break; } break; case Fac_StatusRequest: chan_misdn_log(1, bc->port, " --> StatusRequest: InvokeID:%d\n", fac->u.StatusRequest.InvokeID); switch (fac->u.StatusRequest.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: Compatibility:%d\n", fac->u.StatusRequest.Component.Invoke.CompatibilityMode); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: Status:%d\n", fac->u.StatusRequest.Component.Result.Status); break; default: break; } break; case Fac_CallInfoRetain: chan_misdn_log(1, bc->port, " --> CallInfoRetain: InvokeID:%d, LinkageID:%d\n", fac->u.CallInfoRetain.InvokeID, fac->u.CallInfoRetain.CallLinkageID); break; case Fac_CCBSDeactivate: chan_misdn_log(1, bc->port, " --> CCBSDeactivate: InvokeID:%d\n", fac->u.CCBSDeactivate.InvokeID); switch (fac->u.CCBSDeactivate.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: CCBSReference:%d\n", fac->u.CCBSDeactivate.Component.Invoke.CCBSReference); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result\n"); break; default: break; } break; case Fac_CCBSErase: chan_misdn_log(1, bc->port, " --> CCBSErase: InvokeID:%d, CCBSReference:%d RecallMode:%d, Reason:%d\n", fac->u.CCBSErase.InvokeID, fac->u.CCBSErase.CCBSReference, fac->u.CCBSErase.RecallMode, fac->u.CCBSErase.Reason); chan_misdn_log(1, bc->port, " --> AddressOfB\n"); print_facility_Address(2, &fac->u.CCBSErase.AddressOfB, bc); print_facility_Q931_Bc_Hlc_Llc(1, &fac->u.CCBSErase.Q931ie, bc); break; case Fac_CCBSRemoteUserFree: chan_misdn_log(1, bc->port, " --> CCBSRemoteUserFree: InvokeID:%d, CCBSReference:%d RecallMode:%d\n", fac->u.CCBSRemoteUserFree.InvokeID, fac->u.CCBSRemoteUserFree.CCBSReference, fac->u.CCBSRemoteUserFree.RecallMode); chan_misdn_log(1, bc->port, " --> AddressOfB\n"); print_facility_Address(2, &fac->u.CCBSRemoteUserFree.AddressOfB, bc); print_facility_Q931_Bc_Hlc_Llc(1, &fac->u.CCBSRemoteUserFree.Q931ie, bc); break; case Fac_CCBSCall: chan_misdn_log(1, bc->port, " --> CCBSCall: InvokeID:%d, CCBSReference:%d\n", fac->u.CCBSCall.InvokeID, fac->u.CCBSCall.CCBSReference); break; case Fac_CCBSStatusRequest: chan_misdn_log(1, bc->port, " --> CCBSStatusRequest: InvokeID:%d\n", fac->u.CCBSStatusRequest.InvokeID); switch (fac->u.CCBSStatusRequest.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: CCBSReference:%d RecallMode:%d\n", fac->u.CCBSStatusRequest.Component.Invoke.CCBSReference, fac->u.CCBSStatusRequest.Component.Invoke.RecallMode); print_facility_Q931_Bc_Hlc_Llc(2, &fac->u.CCBSStatusRequest.Component.Invoke.Q931ie, bc); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: Free:%d\n", fac->u.CCBSStatusRequest.Component.Result.Free); break; default: break; } break; case Fac_CCBSBFree: chan_misdn_log(1, bc->port, " --> CCBSBFree: InvokeID:%d, CCBSReference:%d RecallMode:%d\n", fac->u.CCBSBFree.InvokeID, fac->u.CCBSBFree.CCBSReference, fac->u.CCBSBFree.RecallMode); chan_misdn_log(1, bc->port, " --> AddressOfB\n"); print_facility_Address(2, &fac->u.CCBSBFree.AddressOfB, bc); print_facility_Q931_Bc_Hlc_Llc(1, &fac->u.CCBSBFree.Q931ie, bc); break; case Fac_EraseCallLinkageID: chan_misdn_log(1, bc->port, " --> EraseCallLinkageID: InvokeID:%d, LinkageID:%d\n", fac->u.EraseCallLinkageID.InvokeID, fac->u.EraseCallLinkageID.CallLinkageID); break; case Fac_CCBSStopAlerting: chan_misdn_log(1, bc->port, " --> CCBSStopAlerting: InvokeID:%d, CCBSReference:%d\n", fac->u.CCBSStopAlerting.InvokeID, fac->u.CCBSStopAlerting.CCBSReference); break; case Fac_CCBSRequest: chan_misdn_log(1, bc->port, " --> CCBSRequest: InvokeID:%d\n", fac->u.CCBSRequest.InvokeID); switch (fac->u.CCBSRequest.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: LinkageID:%d\n", fac->u.CCBSRequest.Component.Invoke.CallLinkageID); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: CCBSReference:%d RecallMode:%d\n", fac->u.CCBSRequest.Component.Result.CCBSReference, fac->u.CCBSRequest.Component.Result.RecallMode); break; default: break; } break; case Fac_CCBSInterrogate: chan_misdn_log(1, bc->port, " --> CCBSInterrogate: InvokeID:%d\n", fac->u.CCBSInterrogate.InvokeID); switch (fac->u.CCBSInterrogate.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke\n"); if (fac->u.CCBSInterrogate.Component.Invoke.CCBSReferencePresent) { chan_misdn_log(1, bc->port, " --> CCBSReference:%d\n", fac->u.CCBSInterrogate.Component.Invoke.CCBSReference); } if (fac->u.CCBSInterrogate.Component.Invoke.AParty.LengthOfNumber) { chan_misdn_log(1, bc->port, " --> AParty\n"); print_facility_PartyNumber(3, &fac->u.CCBSInterrogate.Component.Invoke.AParty, bc); } break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: RecallMode:%d\n", fac->u.CCBSInterrogate.Component.Result.RecallMode); if (fac->u.CCBSInterrogate.Component.Result.NumRecords) { for (Index = 0; Index < fac->u.CCBSInterrogate.Component.Result.NumRecords; ++Index) { chan_misdn_log(1, bc->port, " --> CallDetails[%d]:\n", Index); print_facility_CallInformation(3, &fac->u.CCBSInterrogate.Component.Result.CallDetails[Index], bc); } } break; default: break; } break; case Fac_CCNRRequest: chan_misdn_log(1, bc->port, " --> CCNRRequest: InvokeID:%d\n", fac->u.CCNRRequest.InvokeID); switch (fac->u.CCNRRequest.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke: LinkageID:%d\n", fac->u.CCNRRequest.Component.Invoke.CallLinkageID); break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: CCBSReference:%d RecallMode:%d\n", fac->u.CCNRRequest.Component.Result.CCBSReference, fac->u.CCNRRequest.Component.Result.RecallMode); break; default: break; } break; case Fac_CCNRInterrogate: chan_misdn_log(1, bc->port, " --> CCNRInterrogate: InvokeID:%d\n", fac->u.CCNRInterrogate.InvokeID); switch (fac->u.CCNRInterrogate.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke\n"); if (fac->u.CCNRInterrogate.Component.Invoke.CCBSReferencePresent) { chan_misdn_log(1, bc->port, " --> CCBSReference:%d\n", fac->u.CCNRInterrogate.Component.Invoke.CCBSReference); } if (fac->u.CCNRInterrogate.Component.Invoke.AParty.LengthOfNumber) { chan_misdn_log(1, bc->port, " --> AParty\n"); print_facility_PartyNumber(3, &fac->u.CCNRInterrogate.Component.Invoke.AParty, bc); } break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: RecallMode:%d\n", fac->u.CCNRInterrogate.Component.Result.RecallMode); if (fac->u.CCNRInterrogate.Component.Result.NumRecords) { for (Index = 0; Index < fac->u.CCNRInterrogate.Component.Result.NumRecords; ++Index) { chan_misdn_log(1, bc->port, " --> CallDetails[%d]:\n", Index); print_facility_CallInformation(3, &fac->u.CCNRInterrogate.Component.Result.CallDetails[Index], bc); } } break; default: break; } break; case Fac_CCBS_T_Call: chan_misdn_log(1, bc->port, " --> CCBS_T_Call: InvokeID:%d\n", fac->u.CCBS_T_Call.InvokeID); break; case Fac_CCBS_T_Suspend: chan_misdn_log(1, bc->port, " --> CCBS_T_Suspend: InvokeID:%d\n", fac->u.CCBS_T_Suspend.InvokeID); break; case Fac_CCBS_T_Resume: chan_misdn_log(1, bc->port, " --> CCBS_T_Resume: InvokeID:%d\n", fac->u.CCBS_T_Resume.InvokeID); break; case Fac_CCBS_T_RemoteUserFree: chan_misdn_log(1, bc->port, " --> CCBS_T_RemoteUserFree: InvokeID:%d\n", fac->u.CCBS_T_RemoteUserFree.InvokeID); break; case Fac_CCBS_T_Available: chan_misdn_log(1, bc->port, " --> CCBS_T_Available: InvokeID:%d\n", fac->u.CCBS_T_Available.InvokeID); break; case Fac_CCBS_T_Request: chan_misdn_log(1, bc->port, " --> CCBS_T_Request: InvokeID:%d\n", fac->u.CCBS_T_Request.InvokeID); switch (fac->u.CCBS_T_Request.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke\n"); chan_misdn_log(1, bc->port, " --> DestinationAddress:\n"); print_facility_Address(3, &fac->u.CCBS_T_Request.Component.Invoke.Destination, bc); print_facility_Q931_Bc_Hlc_Llc(2, &fac->u.CCBS_T_Request.Component.Invoke.Q931ie, bc); if (fac->u.CCBS_T_Request.Component.Invoke.RetentionSupported) { chan_misdn_log(1, bc->port, " --> RetentionSupported:1\n"); } if (fac->u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent) { chan_misdn_log(1, bc->port, " --> PresentationAllowed:%d\n", fac->u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator); } if (fac->u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber) { chan_misdn_log(1, bc->port, " --> OriginatingAddress:\n"); print_facility_Address(3, &fac->u.CCBS_T_Request.Component.Invoke.Originating, bc); } break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: RetentionSupported:%d\n", fac->u.CCBS_T_Request.Component.Result.RetentionSupported); break; default: break; } break; case Fac_CCNR_T_Request: chan_misdn_log(1, bc->port, " --> CCNR_T_Request: InvokeID:%d\n", fac->u.CCNR_T_Request.InvokeID); switch (fac->u.CCNR_T_Request.ComponentType) { case FacComponent_Invoke: chan_misdn_log(1, bc->port, " --> Invoke\n"); chan_misdn_log(1, bc->port, " --> DestinationAddress:\n"); print_facility_Address(3, &fac->u.CCNR_T_Request.Component.Invoke.Destination, bc); print_facility_Q931_Bc_Hlc_Llc(2, &fac->u.CCNR_T_Request.Component.Invoke.Q931ie, bc); if (fac->u.CCNR_T_Request.Component.Invoke.RetentionSupported) { chan_misdn_log(1, bc->port, " --> RetentionSupported:1\n"); } if (fac->u.CCNR_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent) { chan_misdn_log(1, bc->port, " --> PresentationAllowed:%d\n", fac->u.CCNR_T_Request.Component.Invoke.PresentationAllowedIndicator); } if (fac->u.CCNR_T_Request.Component.Invoke.Originating.Party.LengthOfNumber) { chan_misdn_log(1, bc->port, " --> OriginatingAddress:\n"); print_facility_Address(3, &fac->u.CCNR_T_Request.Component.Invoke.Originating, bc); } break; case FacComponent_Result: chan_misdn_log(1, bc->port, " --> Result: RetentionSupported:%d\n", fac->u.CCNR_T_Request.Component.Result.RetentionSupported); break; default: break; } break; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ case Fac_None: /* No facility so print nothing */ break; default: chan_misdn_log(1, bc->port, " --> unknown facility\n"); break; } } static void print_bearer(struct misdn_bchannel *bc) { chan_misdn_log(2, bc->port, " --> Bearer: %s\n", bearer2str(bc->capability)); switch(bc->law) { case INFO_CODEC_ALAW: chan_misdn_log(2, bc->port, " --> Codec: Alaw\n"); break; case INFO_CODEC_ULAW: chan_misdn_log(2, bc->port, " --> Codec: Ulaw\n"); break; } } /*! * \internal * \brief Prefix a string to another string in place. * * \param str_prefix String to prefix to the main string. * \param str_main String to get the prefix added to it. * \param size Buffer size of the main string (Includes null terminator). * * \note The str_main buffer size must be greater than one. * * \return Nothing */ static void misdn_prefix_string(const char *str_prefix, char *str_main, size_t size) { size_t len_over; size_t len_total; size_t len_main; size_t len_prefix; len_prefix = strlen(str_prefix); if (!len_prefix) { /* There is no prefix to prepend. */ return; } len_main = strlen(str_main); len_total = len_prefix + len_main; if (size <= len_total) { /* We need to truncate since the buffer is too small. */ len_over = len_total + 1 - size; if (len_over <= len_main) { len_main -= len_over; } else { len_over -= len_main; len_main = 0; len_prefix -= len_over; } } if (len_main) { memmove(str_main + len_prefix, str_main, len_main); } memcpy(str_main, str_prefix, len_prefix); str_main[len_prefix + len_main] = '\0'; } /*! * \internal * \brief Add a configured prefix to the given number. * * \param port Logical port number * \param number_type Type-of-number passed in. * \param number Given number string to add prefix * \param size Buffer size number string occupies. * * \return Nothing */ static void misdn_add_number_prefix(int port, enum mISDN_NUMBER_TYPE number_type, char *number, size_t size) { enum misdn_cfg_elements type_prefix; char num_prefix[MISDN_MAX_NUMBER_LEN]; /* Get prefix string. */ switch (number_type) { case NUMTYPE_UNKNOWN: type_prefix = MISDN_CFG_TON_PREFIX_UNKNOWN; break; case NUMTYPE_INTERNATIONAL: type_prefix = MISDN_CFG_TON_PREFIX_INTERNATIONAL; break; case NUMTYPE_NATIONAL: type_prefix = MISDN_CFG_TON_PREFIX_NATIONAL; break; case NUMTYPE_NETWORK_SPECIFIC: type_prefix = MISDN_CFG_TON_PREFIX_NETWORK_SPECIFIC; break; case NUMTYPE_SUBSCRIBER: type_prefix = MISDN_CFG_TON_PREFIX_SUBSCRIBER; break; case NUMTYPE_ABBREVIATED: type_prefix = MISDN_CFG_TON_PREFIX_ABBREVIATED; break; default: /* Type-of-number does not have a prefix that can be added. */ return; } misdn_cfg_get(port, type_prefix, num_prefix, sizeof(num_prefix)); misdn_prefix_string(num_prefix, number, size); } static void export_aoc_vars(int originator, struct ast_channel *ast, struct misdn_bchannel *bc) { RAII_VAR(struct ast_channel *, chan, NULL, ast_channel_cleanup); char buf[128]; if (!bc->AOCD_need_export || !ast) { return; } if (originator == ORG_AST) { chan = ast_channel_bridge_peer(ast); if (!chan) { return; } } else { chan = ast_channel_ref(ast); } switch (bc->AOCDtype) { case Fac_AOCDCurrency: pbx_builtin_setvar_helper(chan, "AOCD_Type", "currency"); if (bc->AOCD.currency.chargeNotAvailable) { pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "no"); } else { pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "yes"); if (bc->AOCD.currency.freeOfCharge) { pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "yes"); } else { pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "no"); if (snprintf(buf, sizeof(buf), "%d %s", bc->AOCD.currency.currencyAmount * bc->AOCD.currency.multiplier, bc->AOCD.currency.currency) < sizeof(buf)) { pbx_builtin_setvar_helper(chan, "AOCD_Amount", buf); if (bc->AOCD.currency.billingId >= 0 && snprintf(buf, sizeof(buf), "%d", bc->AOCD.currency.billingId) < sizeof(buf)) { pbx_builtin_setvar_helper(chan, "AOCD_BillingId", buf); } } } } break; case Fac_AOCDChargingUnit: pbx_builtin_setvar_helper(chan, "AOCD_Type", "charging_unit"); if (bc->AOCD.chargingUnit.chargeNotAvailable) { pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "no"); } else { pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "yes"); if (bc->AOCD.chargingUnit.freeOfCharge) { pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "yes"); } else { pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "no"); if (snprintf(buf, sizeof(buf), "%d", bc->AOCD.chargingUnit.recordedUnits) < sizeof(buf)) { pbx_builtin_setvar_helper(chan, "AOCD_RecordedUnits", buf); if (bc->AOCD.chargingUnit.billingId >= 0 && snprintf(buf, sizeof(buf), "%d", bc->AOCD.chargingUnit.billingId) < sizeof(buf)) { pbx_builtin_setvar_helper(chan, "AOCD_BillingId", buf); } } } } break; default: break; } bc->AOCD_need_export = 0; } /*************** Helpers END *************/ static void sighandler(int sig) { } static void *misdn_tasks_thread_func(void *data) { int wait; struct sigaction sa; sa.sa_handler = sighandler; sa.sa_flags = SA_NODEFER; sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGUSR1); sigaction(SIGUSR1, &sa, NULL); sem_post((sem_t *)data); while (1) { wait = ast_sched_wait(misdn_tasks); if (wait < 0) { wait = 8000; } if (poll(NULL, 0, wait) < 0) { chan_misdn_log(4, 0, "Waking up misdn_tasks thread\n"); } ast_sched_runq(misdn_tasks); } return NULL; } static void misdn_tasks_init(void) { sem_t blocker; int i = 5; if (sem_init(&blocker, 0, 0)) { perror("chan_misdn: Failed to initialize semaphore!"); exit(1); } chan_misdn_log(4, 0, "Starting misdn_tasks thread\n"); misdn_tasks = ast_sched_context_create(); pthread_create(&misdn_tasks_thread, NULL, misdn_tasks_thread_func, &blocker); while (sem_wait(&blocker) && --i) { } sem_destroy(&blocker); } static void misdn_tasks_destroy(void) { if (misdn_tasks) { chan_misdn_log(4, 0, "Killing misdn_tasks thread\n"); if (pthread_cancel(misdn_tasks_thread) == 0) { cb_log(4, 0, "Joining misdn_tasks thread\n"); pthread_join(misdn_tasks_thread, NULL); } ast_sched_context_destroy(misdn_tasks); } } static inline void misdn_tasks_wakeup(void) { pthread_kill(misdn_tasks_thread, SIGUSR1); } static inline int _misdn_tasks_add_variable(int timeout, ast_sched_cb callback, const void *data, int variable) { int task_id; if (!misdn_tasks) { misdn_tasks_init(); } task_id = ast_sched_add_variable(misdn_tasks, timeout, callback, data, variable); misdn_tasks_wakeup(); return task_id; } static int misdn_tasks_add(int timeout, ast_sched_cb callback, const void *data) { return _misdn_tasks_add_variable(timeout, callback, data, 0); } static int misdn_tasks_add_variable(int timeout, ast_sched_cb callback, const void *data) { return _misdn_tasks_add_variable(timeout, callback, data, 1); } static void misdn_tasks_remove(int task_id) { AST_SCHED_DEL(misdn_tasks, task_id); } static int misdn_l1_task(const void *vdata) { const int *data = vdata; misdn_lib_isdn_l1watcher(*data); chan_misdn_log(5, *data, "L1watcher timeout\n"); return 1; } static int misdn_overlap_dial_task(const void *data) { struct timeval tv_end, tv_now; int diff; struct chan_list *ch = (struct chan_list *) data; char *dad; chan_misdn_log(4, ch->bc->port, "overlap dial task, chan_state: %d\n", ch->state); if (ch->state != MISDN_WAITING4DIGS) { ch->overlap_dial_task = -1; return 0; } ast_mutex_lock(&ch->overlap_tv_lock); tv_end = ch->overlap_tv; ast_mutex_unlock(&ch->overlap_tv_lock); tv_end.tv_sec += ch->overlap_dial; tv_now = ast_tvnow(); diff = ast_tvdiff_ms(tv_end, tv_now); if (100 < diff) { return diff; } /* if we are 100ms near the timeout, we are satisfied.. */ stop_indicate(ch); if (ast_strlen_zero(ch->bc->dialed.number)) { dad = "s"; ast_channel_exten_set(ch->ast, dad); } else { dad = ch->bc->dialed.number; } if (ast_exists_extension(ch->ast, ch->context, dad, 1, ch->bc->caller.number)) { ch->state = MISDN_DIALING; if (pbx_start_chan(ch) < 0) { chan_misdn_log(-1, ch->bc->port, "ast_pbx_start returned < 0 in misdn_overlap_dial_task\n"); goto misdn_overlap_dial_task_disconnect; } } else { misdn_overlap_dial_task_disconnect: hanguptone_indicate(ch); ch->bc->out_cause = AST_CAUSE_UNALLOCATED; ch->state = MISDN_CLEANING; misdn_lib_send_event(ch->bc, EVENT_DISCONNECT); } ch->overlap_dial_task = -1; return 0; } static void send_digit_to_chan(struct chan_list *cl, char digit) { static const char * const dtmf_tones[] = { /* *INDENT-OFF* */ "!941+1336/100,!0/100", /* 0 */ "!697+1209/100,!0/100", /* 1 */ "!697+1336/100,!0/100", /* 2 */ "!697+1477/100,!0/100", /* 3 */ "!770+1209/100,!0/100", /* 4 */ "!770+1336/100,!0/100", /* 5 */ "!770+1477/100,!0/100", /* 6 */ "!852+1209/100,!0/100", /* 7 */ "!852+1336/100,!0/100", /* 8 */ "!852+1477/100,!0/100", /* 9 */ "!697+1633/100,!0/100", /* A */ "!770+1633/100,!0/100", /* B */ "!852+1633/100,!0/100", /* C */ "!941+1633/100,!0/100", /* D */ "!941+1209/100,!0/100", /* * */ "!941+1477/100,!0/100", /* # */ /* *INDENT-ON* */ }; struct ast_channel *chan = cl->ast; if (digit >= '0' && digit <='9') { ast_playtones_start(chan, 0, dtmf_tones[digit - '0'], 0); } else if (digit >= 'A' && digit <= 'D') { ast_playtones_start(chan, 0, dtmf_tones[digit - 'A' + 10], 0); } else if (digit == '*') { ast_playtones_start(chan, 0, dtmf_tones[14], 0); } else if (digit == '#') { ast_playtones_start(chan, 0, dtmf_tones[15], 0); } else { /* not handled */ ast_debug(1, "Unable to handle DTMF tone '%c' for '%s'\n", digit, ast_channel_name(chan)); } } /*** CLI HANDLING ***/ static char *handle_cli_misdn_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int level; switch (cmd) { case CLI_INIT: e->command = "misdn set debug [on|off]"; e->usage = "Usage: misdn set debug {on|off|} [only] | [port [only]]\n" " Set the debug level of the mISDN channel.\n"; return NULL; case CLI_GENERATE: return complete_debug_port(a); } if (a->argc < 4 || a->argc > 7) { return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[3], "on")) { level = 1; } else if (!strcasecmp(a->argv[3], "off")) { level = 0; } else if (isdigit(a->argv[3][0])) { level = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } switch (a->argc) { case 4: case 5: { int i; int only = 0; if (a->argc == 5) { if (strncasecmp(a->argv[4], "only", strlen(a->argv[4]))) { return CLI_SHOWUSAGE; } else { only = 1; } } for (i = 0; i <= max_ports; i++) { misdn_debug[i] = level; misdn_debug_only[i] = only; } ast_cli(a->fd, "changing debug level for all ports to %d%s\n", misdn_debug[0], only ? " (only)" : ""); } break; case 6: case 7: { int port; if (strncasecmp(a->argv[4], "port", strlen(a->argv[4]))) return CLI_SHOWUSAGE; port = atoi(a->argv[5]); if (port <= 0 || port > max_ports) { switch (max_ports) { case 0: ast_cli(a->fd, "port number not valid! no ports available so you won't get lucky with any number here...\n"); break; case 1: ast_cli(a->fd, "port number not valid! only port 1 is available.\n"); break; default: ast_cli(a->fd, "port number not valid! only ports 1 to %d are available.\n", max_ports); } return 0; } if (a->argc == 7) { if (strncasecmp(a->argv[6], "only", strlen(a->argv[6]))) { return CLI_SHOWUSAGE; } else { misdn_debug_only[port] = 1; } } else { misdn_debug_only[port] = 0; } misdn_debug[port] = level; ast_cli(a->fd, "changing debug level to %d%s for port %d\n", misdn_debug[port], misdn_debug_only[port] ? " (only)" : "", port); } } return CLI_SUCCESS; } static char *handle_cli_misdn_set_crypt_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn set crypt debug"; e->usage = "Usage: misdn set crypt debug \n" " Set the crypt debug level of the mISDN channel. Level\n" " must be 1 or 2.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 5) { return CLI_SHOWUSAGE; } /* XXX Is this supposed to not do anything? XXX */ return CLI_SUCCESS; } static char *handle_cli_misdn_port_block(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn port block"; e->usage = "Usage: misdn port block \n" " Block the specified port by .\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } misdn_lib_port_block(atoi(a->argv[3])); return CLI_SUCCESS; } static char *handle_cli_misdn_port_unblock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn port unblock"; e->usage = "Usage: misdn port unblock \n" " Unblock the port specified by .\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } misdn_lib_port_unblock(atoi(a->argv[3])); return CLI_SUCCESS; } static char *handle_cli_misdn_restart_port(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn restart port"; e->usage = "Usage: misdn restart port \n" " Restart the given port.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } misdn_lib_port_restart(atoi(a->argv[3])); return CLI_SUCCESS; } static char *handle_cli_misdn_restart_pid(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn restart pid"; e->usage = "Usage: misdn restart pid \n" " Restart the given pid\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } misdn_lib_pid_restart(atoi(a->argv[3])); return CLI_SUCCESS; } static char *handle_cli_misdn_port_up(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn port up"; e->usage = "Usage: misdn port up \n" " Try to establish L1 on the given port.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } misdn_lib_get_port_up(atoi(a->argv[3])); return CLI_SUCCESS; } static char *handle_cli_misdn_port_down(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn port down"; e->usage = "Usage: misdn port down \n" " Try to deactivate the L1 on the given port.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } misdn_lib_get_port_down(atoi(a->argv[3])); return CLI_SUCCESS; } static inline void show_config_description(int fd, enum misdn_cfg_elements elem) { char section[BUFFERSIZE]; char name[BUFFERSIZE]; char desc[BUFFERSIZE]; char def[BUFFERSIZE]; char tmp[BUFFERSIZE]; misdn_cfg_get_name(elem, tmp, sizeof(tmp)); term_color(name, tmp, COLOR_BRWHITE, 0, sizeof(tmp)); misdn_cfg_get_desc(elem, desc, sizeof(desc), def, sizeof(def)); if (elem < MISDN_CFG_LAST) { term_color(section, "PORTS SECTION", COLOR_YELLOW, 0, sizeof(section)); } else { term_color(section, "GENERAL SECTION", COLOR_YELLOW, 0, sizeof(section)); } if (*def) { ast_cli(fd, "[%s] %s (Default: %s)\n\t%s\n", section, name, def, desc); } else { ast_cli(fd, "[%s] %s\n\t%s\n", section, name, desc); } } static char *handle_cli_misdn_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char buffer[BUFFERSIZE]; enum misdn_cfg_elements elem; int linebreak; int onlyport = -1; int ok = 0; switch (cmd) { case CLI_INIT: e->command = "misdn show config"; e->usage = "Usage: misdn show config [ | description | descriptions [general|ports]]\n" " Use 0 for to only print the general config.\n"; return NULL; case CLI_GENERATE: return complete_show_config(a); } if (a->argc >= 4) { if (!strcmp(a->argv[3], "description")) { if (a->argc == 5) { enum misdn_cfg_elements elem = misdn_cfg_get_elem(a->argv[4]); if (elem == MISDN_CFG_FIRST) { ast_cli(a->fd, "Unknown element: %s\n", a->argv[4]); } else { show_config_description(a->fd, elem); } return CLI_SUCCESS; } return CLI_SHOWUSAGE; } else if (!strcmp(a->argv[3], "descriptions")) { if ((a->argc == 4) || ((a->argc == 5) && !strcmp(a->argv[4], "general"))) { for (elem = MISDN_GEN_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) { show_config_description(a->fd, elem); ast_cli(a->fd, "\n"); } ok = 1; } if ((a->argc == 4) || ((a->argc == 5) && !strcmp(a->argv[4], "ports"))) { for (elem = MISDN_CFG_FIRST + 1; elem < MISDN_CFG_LAST - 1 /* the ptp hack, remove the -1 when ptp is gone */; ++elem) { show_config_description(a->fd, elem); ast_cli(a->fd, "\n"); } ok = 1; } return ok ? CLI_SUCCESS : CLI_SHOWUSAGE; } else if (!sscanf(a->argv[3], "%5d", &onlyport) || onlyport < 0) { ast_cli(a->fd, "Unknown option: %s\n", a->argv[3]); return CLI_SHOWUSAGE; } } if (a->argc == 3 || onlyport == 0) { ast_cli(a->fd, "mISDN General-Config:\n"); for (elem = MISDN_GEN_FIRST + 1, linebreak = 1; elem < MISDN_GEN_LAST; elem++, linebreak++) { misdn_cfg_get_config_string(0, elem, buffer, sizeof(buffer)); ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : ""); } ast_cli(a->fd, "\n"); } if (onlyport < 0) { int port = misdn_cfg_get_next_port(0); for (; port > 0; port = misdn_cfg_get_next_port(port)) { ast_cli(a->fd, "\n[PORT %d]\n", port); for (elem = MISDN_CFG_FIRST + 1, linebreak = 1; elem < MISDN_CFG_LAST; elem++, linebreak++) { misdn_cfg_get_config_string(port, elem, buffer, sizeof(buffer)); ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : ""); } ast_cli(a->fd, "\n"); } } if (onlyport > 0) { if (misdn_cfg_is_port_valid(onlyport)) { ast_cli(a->fd, "[PORT %d]\n", onlyport); for (elem = MISDN_CFG_FIRST + 1, linebreak = 1; elem < MISDN_CFG_LAST; elem++, linebreak++) { misdn_cfg_get_config_string(onlyport, elem, buffer, sizeof(buffer)); ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : ""); } ast_cli(a->fd, "\n"); } else { ast_cli(a->fd, "Port %d is not active!\n", onlyport); } } return CLI_SUCCESS; } struct state_struct { enum misdn_chan_state state; char txt[255]; }; static const struct state_struct state_array[] = { /* *INDENT-OFF* */ { MISDN_NOTHING, "NOTHING" }, /* at beginning */ { MISDN_WAITING4DIGS, "WAITING4DIGS" }, /* when waiting for infos */ { MISDN_EXTCANTMATCH, "EXTCANTMATCH" }, /* when asterisk couldn't match our ext */ { MISDN_INCOMING_SETUP, "INCOMING SETUP" }, /* when pbx_start */ { MISDN_DIALING, "DIALING" }, /* when pbx_start */ { MISDN_PROGRESS, "PROGRESS" }, /* when pbx_start */ { MISDN_PROCEEDING, "PROCEEDING" }, /* when pbx_start */ { MISDN_CALLING, "CALLING" }, /* when misdn_call is called */ { MISDN_CALLING_ACKNOWLEDGE, "CALLING_ACKNOWLEDGE" }, /* when misdn_call is called */ { MISDN_ALERTING, "ALERTING" }, /* when Alerting */ { MISDN_BUSY, "BUSY" }, /* when BUSY */ { MISDN_CONNECTED, "CONNECTED" }, /* when connected */ { MISDN_DISCONNECTED, "DISCONNECTED" }, /* when connected */ { MISDN_CLEANING, "CLEANING" }, /* when hangup from * but we were connected before */ /* *INDENT-ON* */ }; static const char *misdn_get_ch_state(struct chan_list *p) { int i; static char state[8]; if (!p) { return NULL; } for (i = 0; i < ARRAY_LEN(state_array); i++) { if (state_array[i].state == p->state) { return state_array[i].txt; } } snprintf(state, sizeof(state), "%d", p->state) ; return state; } static void reload_config(void) { int i, cfg_debug; if (!g_config_initialized) { ast_log(LOG_WARNING, "chan_misdn is not initialized properly, still reloading ?\n"); return ; } free_robin_list(); misdn_cfg_reload(); misdn_cfg_update_ptp(); misdn_cfg_get(0, MISDN_GEN_TRACEFILE, global_tracefile, sizeof(global_tracefile)); misdn_cfg_get(0, MISDN_GEN_DEBUG, &cfg_debug, sizeof(cfg_debug)); for (i = 0; i <= max_ports; i++) { misdn_debug[i] = cfg_debug; misdn_debug_only[i] = 0; } } static char *handle_cli_misdn_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn reload"; e->usage = "Usage: misdn reload\n" " Reload internal mISDN config, read from the config\n" " file.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 2) { return CLI_SHOWUSAGE; } ast_cli(a->fd, "Reloading mISDN configuration\n"); reload_config(); return CLI_SUCCESS; } static void print_bc_info(int fd, struct chan_list *help, struct misdn_bchannel *bc) { struct ast_channel *ast = help->ast; ast_cli(fd, "* Pid:%d Port:%d Ch:%d Mode:%s Orig:%s dialed:%s\n" " --> caller:\"%s\" <%s>\n" " --> redirecting-from:\"%s\" <%s>\n" " --> redirecting-to:\"%s\" <%s>\n" " --> context:%s state:%s\n", bc->pid, bc->port, bc->channel, bc->nt ? "NT" : "TE", help->originator == ORG_AST ? "*" : "I", ast ? ast_channel_exten(ast) : "", (ast && ast_channel_caller(ast)->id.name.valid && ast_channel_caller(ast)->id.name.str) ? ast_channel_caller(ast)->id.name.str : "", (ast && ast_channel_caller(ast)->id.number.valid && ast_channel_caller(ast)->id.number.str) ? ast_channel_caller(ast)->id.number.str : "", bc->redirecting.from.name, bc->redirecting.from.number, bc->redirecting.to.name, bc->redirecting.to.number, ast ? ast_channel_context(ast) : "", misdn_get_ch_state(help)); if (misdn_debug[bc->port] > 0) { ast_cli(fd, " --> astname: %s\n" " --> ch_l3id: %x\n" " --> ch_addr: %x\n" " --> bc_addr: %x\n" " --> bc_l3id: %x\n" " --> display: %s\n" " --> activated: %d\n" " --> state: %s\n" " --> capability: %s\n" #ifdef MISDN_1_2 " --> pipeline: %s\n" #else " --> echo_cancel: %d\n" #endif " --> notone : rx %d tx:%d\n" " --> bc_hold: %d\n", ast ? ast_channel_name(ast) : "", help->l3id, help->addr, bc->addr, bc->l3_id, bc->display, bc->active, bc_state2str(bc->bc_state), bearer2str(bc->capability), #ifdef MISDN_1_2 bc->pipeline, #else bc->ec_enable, #endif help->norxtone, help->notxtone, bc->holded); } } static char *handle_cli_misdn_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_list *help; switch (cmd) { case CLI_INIT: e->command = "misdn show channels"; e->usage = "Usage: misdn show channels\n" " Show the internal mISDN channel list\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) { return CLI_SHOWUSAGE; } ast_cli(a->fd, "Channel List: %p\n", cl_te); /* * Walking the list and dumping the channel information could * take awhile. With the list locked for the duration, the * channel driver cannot process signaling messages. However, * since this is a CLI command it should not be run very often. */ ast_mutex_lock(&cl_te_lock); for (help = cl_te; help; help = help->next) { struct misdn_bchannel *bc = help->bc; struct ast_channel *ast = help->ast; if (!ast) { if (!bc) { ast_cli(a->fd, "chan_list obj. with l3id:%x has no bc and no ast Leg\n", help->l3id); continue; } ast_cli(a->fd, "bc with pid:%d has no Ast Leg\n", bc->pid); } if (misdn_debug[0] > 2) { ast_cli(a->fd, "Bc:%p Ast:%p\n", bc, ast); } if (bc) { print_bc_info(a->fd, help, bc); } else { if (help->hold.state != MISDN_HOLD_IDLE) { ast_cli(a->fd, "ITS A HELD CALL BC:\n"); ast_cli(a->fd, " --> l3_id: %x\n" " --> dialed:%s\n" " --> caller:\"%s\" <%s>\n" " --> hold_port: %d\n" " --> hold_channel: %d\n", help->l3id, ast_channel_exten(ast), S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""), S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""), help->hold.port, help->hold.channel ); } else { ast_cli(a->fd, "* Channel in unknown STATE !!! Exten:%s, Callerid:%s\n", ast_channel_exten(ast), S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, "")); } } } ast_mutex_unlock(&cl_te_lock); misdn_dump_chanlist(); return CLI_SUCCESS; } static char *handle_cli_misdn_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct chan_list *help; switch (cmd) { case CLI_INIT: e->command = "misdn show channel"; e->usage = "Usage: misdn show channel \n" " Show an internal mISDN channel\n."; return NULL; case CLI_GENERATE: return complete_ch(a); } if (a->argc != 4) { return CLI_SHOWUSAGE; } ast_mutex_lock(&cl_te_lock); for (help = cl_te; help; help = help->next) { struct misdn_bchannel *bc = help->bc; struct ast_channel *ast = help->ast; if (bc && ast) { if (!strcasecmp(ast_channel_name(ast), a->argv[3])) { print_bc_info(a->fd, help, bc); break; } } } ast_mutex_unlock(&cl_te_lock); return CLI_SUCCESS; } static char *handle_cli_misdn_set_tics(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "misdn set tics"; e->usage = "Usage: misdn set tics \n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } /* XXX Wow, this does... a whole lot of nothing... XXX */ MAXTICS = atoi(a->argv[3]); return CLI_SUCCESS; } static char *handle_cli_misdn_show_stacks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int port; switch (cmd) { case CLI_INIT: e->command = "misdn show stacks"; e->usage = "Usage: misdn show stacks\n" " Show internal mISDN stack_list.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) { return CLI_SHOWUSAGE; } ast_cli(a->fd, "BEGIN STACK_LIST:\n"); for (port = misdn_cfg_get_next_port(0); port > 0; port = misdn_cfg_get_next_port(port)) { char buf[128]; get_show_stack_details(port, buf); ast_cli(a->fd, " %s Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : ""); } return CLI_SUCCESS; } static char *handle_cli_misdn_show_ports_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int port; switch (cmd) { case CLI_INIT: e->command = "misdn show ports stats"; e->usage = "Usage: misdn show ports stats\n" " Show mISDNs channel's call statistics per port.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } ast_cli(a->fd, "Port\tin_calls\tout_calls\n"); for (port = misdn_cfg_get_next_port(0); port > 0; port = misdn_cfg_get_next_port(port)) { ast_cli(a->fd, "%d\t%d\t\t%d\n", port, misdn_in_calls[port], misdn_out_calls[port]); } ast_cli(a->fd, "\n"); return CLI_SUCCESS; } static char *handle_cli_misdn_show_port(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int port; char buf[128]; switch (cmd) { case CLI_INIT: e->command = "misdn show port"; e->usage = "Usage: misdn show port \n" " Show detailed information for given port.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) { return CLI_SHOWUSAGE; } port = atoi(a->argv[3]); ast_cli(a->fd, "BEGIN STACK_LIST:\n"); get_show_stack_details(port, buf); ast_cli(a->fd, " %s Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : ""); return CLI_SUCCESS; } #if defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES) static const struct FacParm Fac_Msgs[] = { /* *INDENT-OFF* */ [0].Function = Fac_ERROR, [0].u.ERROR.invokeId = 8, [0].u.ERROR.errorValue = FacError_CCBS_AlreadyAccepted, [1].Function = Fac_RESULT, [1].u.RESULT.InvokeID = 9, [2].Function = Fac_REJECT, [2].u.REJECT.Code = FacReject_Gen_BadlyStructuredComponent, [3].Function = Fac_REJECT, [3].u.REJECT.InvokeIDPresent = 1, [3].u.REJECT.InvokeID = 10, [3].u.REJECT.Code = FacReject_Inv_InitiatorReleasing, [4].Function = Fac_REJECT, [4].u.REJECT.InvokeIDPresent = 1, [4].u.REJECT.InvokeID = 11, [4].u.REJECT.Code = FacReject_Res_MistypedResult, [5].Function = Fac_REJECT, [5].u.REJECT.InvokeIDPresent = 1, [5].u.REJECT.InvokeID = 12, [5].u.REJECT.Code = FacReject_Err_ErrorResponseUnexpected, [6].Function = Fac_StatusRequest, [6].u.StatusRequest.InvokeID = 13, [6].u.StatusRequest.ComponentType = FacComponent_Invoke, [6].u.StatusRequest.Component.Invoke.Q931ie.Bc.Length = 2, [6].u.StatusRequest.Component.Invoke.Q931ie.Bc.Contents = "AB", [6].u.StatusRequest.Component.Invoke.Q931ie.Llc.Length = 3, [6].u.StatusRequest.Component.Invoke.Q931ie.Llc.Contents = "CDE", [6].u.StatusRequest.Component.Invoke.Q931ie.Hlc.Length = 4, [6].u.StatusRequest.Component.Invoke.Q931ie.Hlc.Contents = "FGHI", [6].u.StatusRequest.Component.Invoke.CompatibilityMode = 1, [7].Function = Fac_StatusRequest, [7].u.StatusRequest.InvokeID = 14, [7].u.StatusRequest.ComponentType = FacComponent_Result, [7].u.StatusRequest.Component.Result.Status = 2, [8].Function = Fac_CallInfoRetain, [8].u.CallInfoRetain.InvokeID = 15, [8].u.CallInfoRetain.CallLinkageID = 115, [9].Function = Fac_EraseCallLinkageID, [9].u.EraseCallLinkageID.InvokeID = 16, [9].u.EraseCallLinkageID.CallLinkageID = 105, [10].Function = Fac_CCBSDeactivate, [10].u.CCBSDeactivate.InvokeID = 17, [10].u.CCBSDeactivate.ComponentType = FacComponent_Invoke, [10].u.CCBSDeactivate.Component.Invoke.CCBSReference = 2, [11].Function = Fac_CCBSDeactivate, [11].u.CCBSDeactivate.InvokeID = 18, [11].u.CCBSDeactivate.ComponentType = FacComponent_Result, [12].Function = Fac_CCBSErase, [12].u.CCBSErase.InvokeID = 19, [12].u.CCBSErase.Q931ie.Bc.Length = 2, [12].u.CCBSErase.Q931ie.Bc.Contents = "JK", [12].u.CCBSErase.AddressOfB.Party.Type = 0, [12].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 5, [12].u.CCBSErase.AddressOfB.Party.Number = "33403", [12].u.CCBSErase.AddressOfB.Subaddress.Type = 0, [12].u.CCBSErase.AddressOfB.Subaddress.Length = 4, [12].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.Information = "3748", [12].u.CCBSErase.RecallMode = 1, [12].u.CCBSErase.CCBSReference = 102, [12].u.CCBSErase.Reason = 3, [13].Function = Fac_CCBSErase, [13].u.CCBSErase.InvokeID = 20, [13].u.CCBSErase.Q931ie.Bc.Length = 2, [13].u.CCBSErase.Q931ie.Bc.Contents = "JK", [13].u.CCBSErase.AddressOfB.Party.Type = 1, [13].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 11, [13].u.CCBSErase.AddressOfB.Party.TypeOfNumber = 1, [13].u.CCBSErase.AddressOfB.Party.Number = "18003020102", [13].u.CCBSErase.AddressOfB.Subaddress.Type = 0, [13].u.CCBSErase.AddressOfB.Subaddress.Length = 4, [13].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.OddCountPresent = 1, [13].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.OddCount = 1, [13].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.Information = "3748", [13].u.CCBSErase.RecallMode = 1, [13].u.CCBSErase.CCBSReference = 102, [13].u.CCBSErase.Reason = 3, [14].Function = Fac_CCBSErase, [14].u.CCBSErase.InvokeID = 21, [14].u.CCBSErase.Q931ie.Bc.Length = 2, [14].u.CCBSErase.Q931ie.Bc.Contents = "JK", [14].u.CCBSErase.AddressOfB.Party.Type = 2, [14].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4, [14].u.CCBSErase.AddressOfB.Party.Number = "1803", [14].u.CCBSErase.AddressOfB.Subaddress.Type = 1, [14].u.CCBSErase.AddressOfB.Subaddress.Length = 4, [14].u.CCBSErase.AddressOfB.Subaddress.u.Nsap = "6492", [14].u.CCBSErase.RecallMode = 1, [14].u.CCBSErase.CCBSReference = 102, [14].u.CCBSErase.Reason = 3, [15].Function = Fac_CCBSErase, [15].u.CCBSErase.InvokeID = 22, [15].u.CCBSErase.Q931ie.Bc.Length = 2, [15].u.CCBSErase.Q931ie.Bc.Contents = "JK", [15].u.CCBSErase.AddressOfB.Party.Type = 3, [15].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4, [15].u.CCBSErase.AddressOfB.Party.Number = "1803", [15].u.CCBSErase.RecallMode = 1, [15].u.CCBSErase.CCBSReference = 102, [15].u.CCBSErase.Reason = 3, [16].Function = Fac_CCBSErase, [16].u.CCBSErase.InvokeID = 23, [16].u.CCBSErase.Q931ie.Bc.Length = 2, [16].u.CCBSErase.Q931ie.Bc.Contents = "JK", [16].u.CCBSErase.AddressOfB.Party.Type = 4, [16].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4, [16].u.CCBSErase.AddressOfB.Party.Number = "1803", [16].u.CCBSErase.RecallMode = 1, [16].u.CCBSErase.CCBSReference = 102, [16].u.CCBSErase.Reason = 3, [17].Function = Fac_CCBSErase, [17].u.CCBSErase.InvokeID = 24, [17].u.CCBSErase.Q931ie.Bc.Length = 2, [17].u.CCBSErase.Q931ie.Bc.Contents = "JK", [17].u.CCBSErase.AddressOfB.Party.Type = 5, [17].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 11, [17].u.CCBSErase.AddressOfB.Party.TypeOfNumber = 4, [17].u.CCBSErase.AddressOfB.Party.Number = "18003020102", [17].u.CCBSErase.RecallMode = 1, [17].u.CCBSErase.CCBSReference = 102, [17].u.CCBSErase.Reason = 3, [18].Function = Fac_CCBSErase, [18].u.CCBSErase.InvokeID = 25, [18].u.CCBSErase.Q931ie.Bc.Length = 2, [18].u.CCBSErase.Q931ie.Bc.Contents = "JK", [18].u.CCBSErase.AddressOfB.Party.Type = 8, [18].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4, [18].u.CCBSErase.AddressOfB.Party.Number = "1803", [18].u.CCBSErase.RecallMode = 1, [18].u.CCBSErase.CCBSReference = 102, [18].u.CCBSErase.Reason = 3, [19].Function = Fac_CCBSRemoteUserFree, [19].u.CCBSRemoteUserFree.InvokeID = 26, [19].u.CCBSRemoteUserFree.Q931ie.Bc.Length = 2, [19].u.CCBSRemoteUserFree.Q931ie.Bc.Contents = "JK", [19].u.CCBSRemoteUserFree.AddressOfB.Party.Type = 8, [19].u.CCBSRemoteUserFree.AddressOfB.Party.LengthOfNumber = 4, [19].u.CCBSRemoteUserFree.AddressOfB.Party.Number = "1803", [19].u.CCBSRemoteUserFree.RecallMode = 1, [19].u.CCBSRemoteUserFree.CCBSReference = 102, [20].Function = Fac_CCBSCall, [20].u.CCBSCall.InvokeID = 27, [20].u.CCBSCall.CCBSReference = 115, [21].Function = Fac_CCBSStatusRequest, [21].u.CCBSStatusRequest.InvokeID = 28, [21].u.CCBSStatusRequest.ComponentType = FacComponent_Invoke, [21].u.CCBSStatusRequest.Component.Invoke.Q931ie.Bc.Length = 2, [21].u.CCBSStatusRequest.Component.Invoke.Q931ie.Bc.Contents = "JK", [21].u.CCBSStatusRequest.Component.Invoke.RecallMode = 1, [21].u.CCBSStatusRequest.Component.Invoke.CCBSReference = 102, [22].Function = Fac_CCBSStatusRequest, [22].u.CCBSStatusRequest.InvokeID = 29, [22].u.CCBSStatusRequest.ComponentType = FacComponent_Result, [22].u.CCBSStatusRequest.Component.Result.Free = 1, [23].Function = Fac_CCBSBFree, [23].u.CCBSBFree.InvokeID = 30, [23].u.CCBSBFree.Q931ie.Bc.Length = 2, [23].u.CCBSBFree.Q931ie.Bc.Contents = "JK", [23].u.CCBSBFree.AddressOfB.Party.Type = 8, [23].u.CCBSBFree.AddressOfB.Party.LengthOfNumber = 4, [23].u.CCBSBFree.AddressOfB.Party.Number = "1803", [23].u.CCBSBFree.RecallMode = 1, [23].u.CCBSBFree.CCBSReference = 14, [24].Function = Fac_CCBSStopAlerting, [24].u.CCBSStopAlerting.InvokeID = 31, [24].u.CCBSStopAlerting.CCBSReference = 37, [25].Function = Fac_CCBSRequest, [25].u.CCBSRequest.InvokeID = 32, [25].u.CCBSRequest.ComponentType = FacComponent_Invoke, [25].u.CCBSRequest.Component.Invoke.CallLinkageID = 57, [26].Function = Fac_CCBSRequest, [26].u.CCBSRequest.InvokeID = 33, [26].u.CCBSRequest.ComponentType = FacComponent_Result, [26].u.CCBSRequest.Component.Result.RecallMode = 1, [26].u.CCBSRequest.Component.Result.CCBSReference = 102, [27].Function = Fac_CCBSInterrogate, [27].u.CCBSInterrogate.InvokeID = 34, [27].u.CCBSInterrogate.ComponentType = FacComponent_Invoke, [27].u.CCBSInterrogate.Component.Invoke.AParty.Type = 8, [27].u.CCBSInterrogate.Component.Invoke.AParty.LengthOfNumber = 4, [27].u.CCBSInterrogate.Component.Invoke.AParty.Number = "1803", [27].u.CCBSInterrogate.Component.Invoke.CCBSReferencePresent = 1, [27].u.CCBSInterrogate.Component.Invoke.CCBSReference = 76, [28].Function = Fac_CCBSInterrogate, [28].u.CCBSInterrogate.InvokeID = 35, [28].u.CCBSInterrogate.ComponentType = FacComponent_Invoke, [28].u.CCBSInterrogate.Component.Invoke.AParty.Type = 8, [28].u.CCBSInterrogate.Component.Invoke.AParty.LengthOfNumber = 4, [28].u.CCBSInterrogate.Component.Invoke.AParty.Number = "1803", [29].Function = Fac_CCBSInterrogate, [29].u.CCBSInterrogate.InvokeID = 36, [29].u.CCBSInterrogate.ComponentType = FacComponent_Invoke, [29].u.CCBSInterrogate.Component.Invoke.CCBSReferencePresent = 1, [29].u.CCBSInterrogate.Component.Invoke.CCBSReference = 76, [30].Function = Fac_CCBSInterrogate, [30].u.CCBSInterrogate.InvokeID = 37, [30].u.CCBSInterrogate.ComponentType = FacComponent_Invoke, [31].Function = Fac_CCBSInterrogate, [31].u.CCBSInterrogate.InvokeID = 38, [31].u.CCBSInterrogate.ComponentType = FacComponent_Result, [31].u.CCBSInterrogate.Component.Result.RecallMode = 1, [32].Function = Fac_CCBSInterrogate, [32].u.CCBSInterrogate.InvokeID = 39, [32].u.CCBSInterrogate.ComponentType = FacComponent_Result, [32].u.CCBSInterrogate.Component.Result.RecallMode = 1, [32].u.CCBSInterrogate.Component.Result.NumRecords = 1, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].CCBSReference = 12, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Length = 2, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Contents = "JK", [32].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Type = 8, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.LengthOfNumber = 4, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Number = "1803", [32].u.CCBSInterrogate.Component.Result.CallDetails[0].SubaddressOfA.Type = 1, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].SubaddressOfA.Length = 4, [32].u.CCBSInterrogate.Component.Result.CallDetails[0].SubaddressOfA.u.Nsap = "6492", [33].Function = Fac_CCBSInterrogate, [33].u.CCBSInterrogate.InvokeID = 40, [33].u.CCBSInterrogate.ComponentType = FacComponent_Result, [33].u.CCBSInterrogate.Component.Result.RecallMode = 1, [33].u.CCBSInterrogate.Component.Result.NumRecords = 2, [33].u.CCBSInterrogate.Component.Result.CallDetails[0].CCBSReference = 12, [33].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Length = 2, [33].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Contents = "JK", [33].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Type = 8, [33].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.LengthOfNumber = 4, [33].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Number = "1803", [33].u.CCBSInterrogate.Component.Result.CallDetails[1].CCBSReference = 102, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].Q931ie.Bc.Length = 2, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].Q931ie.Bc.Contents = "LM", [33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Party.Type = 8, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Party.LengthOfNumber = 4, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Party.Number = "6229", [33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Subaddress.Type = 1, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Subaddress.Length = 4, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Subaddress.u.Nsap = "8592", [33].u.CCBSInterrogate.Component.Result.CallDetails[1].SubaddressOfA.Type = 1, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].SubaddressOfA.Length = 4, [33].u.CCBSInterrogate.Component.Result.CallDetails[1].SubaddressOfA.u.Nsap = "6492", [34].Function = Fac_CCNRRequest, [34].u.CCNRRequest.InvokeID = 512, [34].u.CCNRRequest.ComponentType = FacComponent_Invoke, [34].u.CCNRRequest.Component.Invoke.CallLinkageID = 57, [35].Function = Fac_CCNRRequest, [35].u.CCNRRequest.InvokeID = 150, [35].u.CCNRRequest.ComponentType = FacComponent_Result, [35].u.CCNRRequest.Component.Result.RecallMode = 1, [35].u.CCNRRequest.Component.Result.CCBSReference = 102, [36].Function = Fac_CCNRInterrogate, [36].u.CCNRInterrogate.InvokeID = -129, [36].u.CCNRInterrogate.ComponentType = FacComponent_Invoke, [37].Function = Fac_CCNRInterrogate, [37].u.CCNRInterrogate.InvokeID = -3, [37].u.CCNRInterrogate.ComponentType = FacComponent_Result, [37].u.CCNRInterrogate.Component.Result.RecallMode = 1, [38].Function = Fac_CCBS_T_Call, [38].u.EctExecute.InvokeID = 41, [39].Function = Fac_CCBS_T_Suspend, [39].u.EctExecute.InvokeID = 42, [40].Function = Fac_CCBS_T_Resume, [40].u.EctExecute.InvokeID = 43, [41].Function = Fac_CCBS_T_RemoteUserFree, [41].u.EctExecute.InvokeID = 44, [42].Function = Fac_CCBS_T_Available, [42].u.EctExecute.InvokeID = 45, [43].Function = Fac_CCBS_T_Request, [43].u.CCBS_T_Request.InvokeID = 46, [43].u.CCBS_T_Request.ComponentType = FacComponent_Invoke, [43].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8, [43].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4, [43].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229", [43].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2, [43].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM", [43].u.CCBS_T_Request.Component.Invoke.RetentionSupported = 1, [43].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1, [43].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = 1, [43].u.CCBS_T_Request.Component.Invoke.Originating.Party.Type = 8, [43].u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber = 4, [43].u.CCBS_T_Request.Component.Invoke.Originating.Party.Number = "9864", [44].Function = Fac_CCBS_T_Request, [44].u.CCBS_T_Request.InvokeID = 47, [44].u.CCBS_T_Request.ComponentType = FacComponent_Invoke, [44].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8, [44].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4, [44].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229", [44].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2, [44].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM", [44].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1, [44].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = 1, [44].u.CCBS_T_Request.Component.Invoke.Originating.Party.Type = 8, [44].u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber = 4, [44].u.CCBS_T_Request.Component.Invoke.Originating.Party.Number = "9864", [45].Function = Fac_CCBS_T_Request, [45].u.CCBS_T_Request.InvokeID = 48, [45].u.CCBS_T_Request.ComponentType = FacComponent_Invoke, [45].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8, [45].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4, [45].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229", [45].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2, [45].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM", [45].u.CCBS_T_Request.Component.Invoke.Originating.Party.Type = 8, [45].u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber = 4, [45].u.CCBS_T_Request.Component.Invoke.Originating.Party.Number = "9864", [46].Function = Fac_CCBS_T_Request, [46].u.CCBS_T_Request.InvokeID = 49, [46].u.CCBS_T_Request.ComponentType = FacComponent_Invoke, [46].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8, [46].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4, [46].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229", [46].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2, [46].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM", [46].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1, [46].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = 1, [47].Function = Fac_CCBS_T_Request, [47].u.CCBS_T_Request.InvokeID = 50, [47].u.CCBS_T_Request.ComponentType = FacComponent_Invoke, [47].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8, [47].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4, [47].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229", [47].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2, [47].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM", [48].Function = Fac_CCBS_T_Request, [48].u.CCBS_T_Request.InvokeID = 51, [48].u.CCBS_T_Request.ComponentType = FacComponent_Result, [48].u.CCBS_T_Request.Component.Result.RetentionSupported = 1, [49].Function = Fac_CCNR_T_Request, [49].u.CCNR_T_Request.InvokeID = 52, [49].u.CCNR_T_Request.ComponentType = FacComponent_Invoke, [49].u.CCNR_T_Request.Component.Invoke.Destination.Party.Type = 8, [49].u.CCNR_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4, [49].u.CCNR_T_Request.Component.Invoke.Destination.Party.Number = "6229", [49].u.CCNR_T_Request.Component.Invoke.Q931ie.Bc.Length = 2, [49].u.CCNR_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM", [50].Function = Fac_CCNR_T_Request, [50].u.CCNR_T_Request.InvokeID = 53, [50].u.CCNR_T_Request.ComponentType = FacComponent_Result, [50].u.CCNR_T_Request.Component.Result.RetentionSupported = 1, [51].Function = Fac_EctExecute, [51].u.EctExecute.InvokeID = 54, [52].Function = Fac_ExplicitEctExecute, [52].u.ExplicitEctExecute.InvokeID = 55, [52].u.ExplicitEctExecute.LinkID = 23, [53].Function = Fac_RequestSubaddress, [53].u.RequestSubaddress.InvokeID = 56, [54].Function = Fac_SubaddressTransfer, [54].u.SubaddressTransfer.InvokeID = 57, [54].u.SubaddressTransfer.Subaddress.Type = 1, [54].u.SubaddressTransfer.Subaddress.Length = 4, [54].u.SubaddressTransfer.Subaddress.u.Nsap = "6492", [55].Function = Fac_EctLinkIdRequest, [55].u.EctLinkIdRequest.InvokeID = 58, [55].u.EctLinkIdRequest.ComponentType = FacComponent_Invoke, [56].Function = Fac_EctLinkIdRequest, [56].u.EctLinkIdRequest.InvokeID = 59, [56].u.EctLinkIdRequest.ComponentType = FacComponent_Result, [56].u.EctLinkIdRequest.Component.Result.LinkID = 76, [57].Function = Fac_EctInform, [57].u.EctInform.InvokeID = 60, [57].u.EctInform.Status = 1, [57].u.EctInform.RedirectionPresent = 1, [57].u.EctInform.Redirection.Type = 0, [57].u.EctInform.Redirection.Unscreened.Type = 8, [57].u.EctInform.Redirection.Unscreened.LengthOfNumber = 4, [57].u.EctInform.Redirection.Unscreened.Number = "6229", [58].Function = Fac_EctInform, [58].u.EctInform.InvokeID = 61, [58].u.EctInform.Status = 1, [58].u.EctInform.RedirectionPresent = 1, [58].u.EctInform.Redirection.Type = 1, [59].Function = Fac_EctInform, [59].u.EctInform.InvokeID = 62, [59].u.EctInform.Status = 1, [59].u.EctInform.RedirectionPresent = 1, [59].u.EctInform.Redirection.Type = 2, [60].Function = Fac_EctInform, [60].u.EctInform.InvokeID = 63, [60].u.EctInform.Status = 1, [60].u.EctInform.RedirectionPresent = 1, [60].u.EctInform.Redirection.Type = 3, [60].u.EctInform.Redirection.Unscreened.Type = 8, [60].u.EctInform.Redirection.Unscreened.LengthOfNumber = 4, [60].u.EctInform.Redirection.Unscreened.Number = "3340", [61].Function = Fac_EctInform, [61].u.EctInform.InvokeID = 64, [61].u.EctInform.Status = 1, [61].u.EctInform.RedirectionPresent = 0, [62].Function = Fac_EctLoopTest, [62].u.EctLoopTest.InvokeID = 65, [62].u.EctLoopTest.ComponentType = FacComponent_Invoke, [62].u.EctLoopTest.Component.Invoke.CallTransferID = 7, [63].Function = Fac_EctLoopTest, [63].u.EctLoopTest.InvokeID = 66, [63].u.EctLoopTest.ComponentType = FacComponent_Result, [63].u.EctLoopTest.Component.Result.LoopResult = 2, [64].Function = Fac_ActivationDiversion, [64].u.ActivationDiversion.InvokeID = 67, [64].u.ActivationDiversion.ComponentType = FacComponent_Invoke, [64].u.ActivationDiversion.Component.Invoke.Procedure = 2, [64].u.ActivationDiversion.Component.Invoke.BasicService = 3, [64].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Type = 4, [64].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.LengthOfNumber = 4, [64].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number = "1803", [64].u.ActivationDiversion.Component.Invoke.ServedUser.Type = 4, [64].u.ActivationDiversion.Component.Invoke.ServedUser.LengthOfNumber = 4, [64].u.ActivationDiversion.Component.Invoke.ServedUser.Number = "5398", [65].Function = Fac_ActivationDiversion, [65].u.ActivationDiversion.InvokeID = 68, [65].u.ActivationDiversion.ComponentType = FacComponent_Invoke, [65].u.ActivationDiversion.Component.Invoke.Procedure = 1, [65].u.ActivationDiversion.Component.Invoke.BasicService = 5, [65].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Type = 4, [65].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.LengthOfNumber = 4, [65].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number = "1803", [66].Function = Fac_ActivationDiversion, [66].u.ActivationDiversion.InvokeID = 69, [66].u.ActivationDiversion.ComponentType = FacComponent_Result, [67].Function = Fac_DeactivationDiversion, [67].u.DeactivationDiversion.InvokeID = 70, [67].u.DeactivationDiversion.ComponentType = FacComponent_Invoke, [67].u.DeactivationDiversion.Component.Invoke.Procedure = 1, [67].u.DeactivationDiversion.Component.Invoke.BasicService = 5, [68].Function = Fac_DeactivationDiversion, [68].u.DeactivationDiversion.InvokeID = 71, [68].u.DeactivationDiversion.ComponentType = FacComponent_Result, [69].Function = Fac_ActivationStatusNotificationDiv, [69].u.ActivationStatusNotificationDiv.InvokeID = 72, [69].u.ActivationStatusNotificationDiv.Procedure = 1, [69].u.ActivationStatusNotificationDiv.BasicService = 5, [69].u.ActivationStatusNotificationDiv.ForwardedTo.Party.Type = 4, [69].u.ActivationStatusNotificationDiv.ForwardedTo.Party.LengthOfNumber = 4, [69].u.ActivationStatusNotificationDiv.ForwardedTo.Party.Number = "1803", [70].Function = Fac_DeactivationStatusNotificationDiv, [70].u.DeactivationStatusNotificationDiv.InvokeID = 73, [70].u.DeactivationStatusNotificationDiv.Procedure = 1, [70].u.DeactivationStatusNotificationDiv.BasicService = 5, [71].Function = Fac_InterrogationDiversion, [71].u.InterrogationDiversion.InvokeID = 74, [71].u.InterrogationDiversion.ComponentType = FacComponent_Invoke, [71].u.InterrogationDiversion.Component.Invoke.Procedure = 1, [71].u.InterrogationDiversion.Component.Invoke.BasicService = 5, [72].Function = Fac_InterrogationDiversion, [72].u.InterrogationDiversion.InvokeID = 75, [72].u.InterrogationDiversion.ComponentType = FacComponent_Invoke, [72].u.InterrogationDiversion.Component.Invoke.Procedure = 1, [73].Function = Fac_InterrogationDiversion, [73].u.InterrogationDiversion.InvokeID = 76, [73].u.InterrogationDiversion.ComponentType = FacComponent_Result, [73].u.InterrogationDiversion.Component.Result.NumRecords = 2, [73].u.InterrogationDiversion.Component.Result.List[0].Procedure = 2, [73].u.InterrogationDiversion.Component.Result.List[0].BasicService = 5, [73].u.InterrogationDiversion.Component.Result.List[0].ForwardedTo.Party.Type = 4, [73].u.InterrogationDiversion.Component.Result.List[0].ForwardedTo.Party.LengthOfNumber = 4, [73].u.InterrogationDiversion.Component.Result.List[0].ForwardedTo.Party.Number = "1803", [73].u.InterrogationDiversion.Component.Result.List[1].Procedure = 1, [73].u.InterrogationDiversion.Component.Result.List[1].BasicService = 3, [73].u.InterrogationDiversion.Component.Result.List[1].ForwardedTo.Party.Type = 4, [73].u.InterrogationDiversion.Component.Result.List[1].ForwardedTo.Party.LengthOfNumber = 4, [73].u.InterrogationDiversion.Component.Result.List[1].ForwardedTo.Party.Number = "1903", [73].u.InterrogationDiversion.Component.Result.List[1].ServedUser.Type = 4, [73].u.InterrogationDiversion.Component.Result.List[1].ServedUser.LengthOfNumber = 4, [73].u.InterrogationDiversion.Component.Result.List[1].ServedUser.Number = "5398", [74].Function = Fac_DiversionInformation, [74].u.DiversionInformation.InvokeID = 77, [74].u.DiversionInformation.DiversionReason = 3, [74].u.DiversionInformation.BasicService = 5, [74].u.DiversionInformation.ServedUserSubaddress.Type = 1, [74].u.DiversionInformation.ServedUserSubaddress.Length = 4, [74].u.DiversionInformation.ServedUserSubaddress.u.Nsap = "6492", [74].u.DiversionInformation.CallingAddressPresent = 1, [74].u.DiversionInformation.CallingAddress.Type = 0, [74].u.DiversionInformation.CallingAddress.Address.ScreeningIndicator = 3, [74].u.DiversionInformation.CallingAddress.Address.Party.Type = 4, [74].u.DiversionInformation.CallingAddress.Address.Party.LengthOfNumber = 4, [74].u.DiversionInformation.CallingAddress.Address.Party.Number = "1803", [74].u.DiversionInformation.OriginalCalledPresent = 1, [74].u.DiversionInformation.OriginalCalled.Type = 1, [74].u.DiversionInformation.LastDivertingPresent = 1, [74].u.DiversionInformation.LastDiverting.Type = 2, [74].u.DiversionInformation.LastDivertingReasonPresent = 1, [74].u.DiversionInformation.LastDivertingReason = 3, [74].u.DiversionInformation.UserInfo.Length = 5, [74].u.DiversionInformation.UserInfo.Contents = "79828", [75].Function = Fac_DiversionInformation, [75].u.DiversionInformation.InvokeID = 78, [75].u.DiversionInformation.DiversionReason = 3, [75].u.DiversionInformation.BasicService = 5, [75].u.DiversionInformation.CallingAddressPresent = 1, [75].u.DiversionInformation.CallingAddress.Type = 1, [75].u.DiversionInformation.OriginalCalledPresent = 1, [75].u.DiversionInformation.OriginalCalled.Type = 2, [75].u.DiversionInformation.LastDivertingPresent = 1, [75].u.DiversionInformation.LastDiverting.Type = 1, [76].Function = Fac_DiversionInformation, [76].u.DiversionInformation.InvokeID = 79, [76].u.DiversionInformation.DiversionReason = 2, [76].u.DiversionInformation.BasicService = 3, [76].u.DiversionInformation.CallingAddressPresent = 1, [76].u.DiversionInformation.CallingAddress.Type = 2, [77].Function = Fac_DiversionInformation, [77].u.DiversionInformation.InvokeID = 80, [77].u.DiversionInformation.DiversionReason = 3, [77].u.DiversionInformation.BasicService = 5, [77].u.DiversionInformation.CallingAddressPresent = 1, [77].u.DiversionInformation.CallingAddress.Type = 3, [77].u.DiversionInformation.CallingAddress.Address.ScreeningIndicator = 2, [77].u.DiversionInformation.CallingAddress.Address.Party.Type = 4, [77].u.DiversionInformation.CallingAddress.Address.Party.LengthOfNumber = 4, [77].u.DiversionInformation.CallingAddress.Address.Party.Number = "1803", [78].Function = Fac_DiversionInformation, [78].u.DiversionInformation.InvokeID = 81, [78].u.DiversionInformation.DiversionReason = 2, [78].u.DiversionInformation.BasicService = 4, [78].u.DiversionInformation.UserInfo.Length = 5, [78].u.DiversionInformation.UserInfo.Contents = "79828", [79].Function = Fac_DiversionInformation, [79].u.DiversionInformation.InvokeID = 82, [79].u.DiversionInformation.DiversionReason = 2, [79].u.DiversionInformation.BasicService = 4, [80].Function = Fac_CallDeflection, [80].u.CallDeflection.InvokeID = 83, [80].u.CallDeflection.ComponentType = FacComponent_Invoke, [80].u.CallDeflection.Component.Invoke.Deflection.Party.Type = 4, [80].u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = 4, [80].u.CallDeflection.Component.Invoke.Deflection.Party.Number = "1803", [80].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1, [80].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 1, [81].Function = Fac_CallDeflection, [81].u.CallDeflection.InvokeID = 84, [81].u.CallDeflection.ComponentType = FacComponent_Invoke, [81].u.CallDeflection.Component.Invoke.Deflection.Party.Type = 4, [81].u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = 4, [81].u.CallDeflection.Component.Invoke.Deflection.Party.Number = "1803", [81].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1, [81].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 0, [82].Function = Fac_CallDeflection, [82].u.CallDeflection.InvokeID = 85, [82].u.CallDeflection.ComponentType = FacComponent_Invoke, [82].u.CallDeflection.Component.Invoke.Deflection.Party.Type = 4, [82].u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = 4, [82].u.CallDeflection.Component.Invoke.Deflection.Party.Number = "1803", [83].Function = Fac_CallDeflection, [83].u.CallDeflection.InvokeID = 86, [83].u.CallDeflection.ComponentType = FacComponent_Result, [84].Function = Fac_CallRerouteing, [84].u.CallRerouteing.InvokeID = 87, [84].u.CallRerouteing.ComponentType = FacComponent_Invoke, [84].u.CallRerouteing.Component.Invoke.ReroutingReason = 3, [84].u.CallRerouteing.Component.Invoke.ReroutingCounter = 2, [84].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 4, [84].u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = 4, [84].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number = "1803", [84].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 2, [84].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents = "RT", [84].u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Length = 3, [84].u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Contents = "RTG", [84].u.CallRerouteing.Component.Invoke.Q931ie.Llc.Length = 2, [84].u.CallRerouteing.Component.Invoke.Q931ie.Llc.Contents = "MY", [84].u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Length = 5, [84].u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Contents = "YEHAW", [84].u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1, [84].u.CallRerouteing.Component.Invoke.SubscriptionOption = 2, [84].u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Type = 1, [84].u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length = 4, [84].u.CallRerouteing.Component.Invoke.CallingPartySubaddress.u.Nsap = "6492", [85].Function = Fac_CallRerouteing, [85].u.CallRerouteing.InvokeID = 88, [85].u.CallRerouteing.ComponentType = FacComponent_Invoke, [85].u.CallRerouteing.Component.Invoke.ReroutingReason = 3, [85].u.CallRerouteing.Component.Invoke.ReroutingCounter = 2, [85].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 4, [85].u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = 4, [85].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number = "1803", [85].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 2, [85].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents = "RT", [85].u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1, [85].u.CallRerouteing.Component.Invoke.SubscriptionOption = 2, [86].Function = Fac_CallRerouteing, [86].u.CallRerouteing.InvokeID = 89, [86].u.CallRerouteing.ComponentType = FacComponent_Invoke, [86].u.CallRerouteing.Component.Invoke.ReroutingReason = 3, [86].u.CallRerouteing.Component.Invoke.ReroutingCounter = 2, [86].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 4, [86].u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = 4, [86].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number = "1803", [86].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 2, [86].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents = "RT", [86].u.CallRerouteing.Component.Invoke.LastRerouting.Type = 2, [87].Function = Fac_CallRerouteing, [87].u.CallRerouteing.InvokeID = 90, [87].u.CallRerouteing.ComponentType = FacComponent_Result, [88].Function = Fac_InterrogateServedUserNumbers, [88].u.InterrogateServedUserNumbers.InvokeID = 91, [88].u.InterrogateServedUserNumbers.ComponentType = FacComponent_Invoke, [89].Function = Fac_InterrogateServedUserNumbers, [89].u.InterrogateServedUserNumbers.InvokeID = 92, [89].u.InterrogateServedUserNumbers.ComponentType = FacComponent_Result, [89].u.InterrogateServedUserNumbers.Component.Result.NumRecords = 2, [89].u.InterrogateServedUserNumbers.Component.Result.List[0].Type = 4, [89].u.InterrogateServedUserNumbers.Component.Result.List[0].LengthOfNumber = 4, [89].u.InterrogateServedUserNumbers.Component.Result.List[0].Number = "1803", [89].u.InterrogateServedUserNumbers.Component.Result.List[1].Type = 4, [89].u.InterrogateServedUserNumbers.Component.Result.List[1].LengthOfNumber = 4, [89].u.InterrogateServedUserNumbers.Component.Result.List[1].Number = "5786", [90].Function = Fac_DivertingLegInformation1, [90].u.DivertingLegInformation1.InvokeID = 93, [90].u.DivertingLegInformation1.DiversionReason = 4, [90].u.DivertingLegInformation1.SubscriptionOption = 1, [90].u.DivertingLegInformation1.DivertedToPresent = 1, [90].u.DivertingLegInformation1.DivertedTo.Type = 2, [91].Function = Fac_DivertingLegInformation1, [91].u.DivertingLegInformation1.InvokeID = 94, [91].u.DivertingLegInformation1.DiversionReason = 4, [91].u.DivertingLegInformation1.SubscriptionOption = 1, [92].Function = Fac_DivertingLegInformation2, [92].u.DivertingLegInformation2.InvokeID = 95, [92].u.DivertingLegInformation2.DiversionCounter = 3, [92].u.DivertingLegInformation2.DiversionReason = 2, [92].u.DivertingLegInformation2.DivertingPresent = 1, [92].u.DivertingLegInformation2.Diverting.Type = 2, [92].u.DivertingLegInformation2.OriginalCalledPresent = 1, [92].u.DivertingLegInformation2.OriginalCalled.Type = 1, [93].Function = Fac_DivertingLegInformation2, [93].u.DivertingLegInformation2.InvokeID = 96, [93].u.DivertingLegInformation2.DiversionCounter = 3, [93].u.DivertingLegInformation2.DiversionReason = 2, [93].u.DivertingLegInformation2.OriginalCalledPresent = 1, [93].u.DivertingLegInformation2.OriginalCalled.Type = 1, [94].Function = Fac_DivertingLegInformation2, [94].u.DivertingLegInformation2.InvokeID = 97, [94].u.DivertingLegInformation2.DiversionCounter = 1, [94].u.DivertingLegInformation2.DiversionReason = 2, [95].Function = Fac_DivertingLegInformation3, [95].u.DivertingLegInformation3.InvokeID = 98, [95].u.DivertingLegInformation3.PresentationAllowedIndicator = 1, /* *INDENT-ON* */ }; #endif /* defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES) */ static char *handle_cli_misdn_send_facility(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { const char *channame; const char *nr; struct chan_list *tmp; int port; const char *served_nr; struct misdn_bchannel dummy, *bc=&dummy; unsigned max_len; switch (cmd) { case CLI_INIT: e->command = "misdn send facility"; e->usage = "Usage: misdn send facility \"\" \n" "\t type is one of:\n" "\t - calldeflect\n" #if defined(AST_MISDN_ENHANCEMENTS) "\t - callrerouting\n" #endif /* defined(AST_MISDN_ENHANCEMENTS) */ "\t - CFActivate\n" "\t - CFDeactivate\n"; return NULL; case CLI_GENERATE: return complete_ch(a); } if (a->argc < 5) { return CLI_SHOWUSAGE; } if (strstr(a->argv[3], "calldeflect")) { if (a->argc < 6) { ast_verbose("calldeflect requires 1 arg: ToNumber\n\n"); return 0; } channame = a->argv[4]; nr = a->argv[5]; ast_verbose("Sending Calldeflection (%s) to %s\n", nr, channame); tmp = get_chan_by_ast_name(channame); if (!tmp) { ast_verbose("Sending CD with nr %s to %s failed: Channel does not exist.\n", nr, channame); return 0; } ao2_lock(tmp); #if defined(AST_MISDN_ENHANCEMENTS) max_len = sizeof(tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number) - 1; if (max_len < strlen(nr)) { ast_verbose("Sending CD with nr %s to %s failed: Number too long (up to %u digits are allowed).\n", nr, channame, max_len); ao2_unlock(tmp); chan_list_unref(tmp, "Number too long"); return 0; } tmp->bc->fac_out.Function = Fac_CallDeflection; tmp->bc->fac_out.u.CallDeflection.InvokeID = ++misdn_invoke_id; tmp->bc->fac_out.u.CallDeflection.ComponentType = FacComponent_Invoke; tmp->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1; tmp->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 0; tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Type = 0;/* unknown */ tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = strlen(nr); strcpy((char *) tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number, nr); tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Subaddress.Length = 0; #else /* !defined(AST_MISDN_ENHANCEMENTS) */ max_len = sizeof(tmp->bc->fac_out.u.CDeflection.DeflectedToNumber) - 1; if (max_len < strlen(nr)) { ast_verbose("Sending CD with nr %s to %s failed: Number too long (up to %u digits are allowed).\n", nr, channame, max_len); ao2_unlock(tmp); chan_list_unref(tmp, "Number too long"); return 0; } tmp->bc->fac_out.Function = Fac_CD; tmp->bc->fac_out.u.CDeflection.PresentationAllowed = 0; //tmp->bc->fac_out.u.CDeflection.DeflectedToSubaddress[0] = 0; strcpy((char *) tmp->bc->fac_out.u.CDeflection.DeflectedToNumber, nr); #endif /* !defined(AST_MISDN_ENHANCEMENTS) */ /* Send message */ print_facility(&tmp->bc->fac_out, tmp->bc); ao2_unlock(tmp); misdn_lib_send_event(tmp->bc, EVENT_FACILITY); chan_list_unref(tmp, "Send facility complete"); #if defined(AST_MISDN_ENHANCEMENTS) } else if (strstr(a->argv[3], "callrerouteing") || strstr(a->argv[3], "callrerouting")) { if (a->argc < 6) { ast_verbose("callrerouting requires 1 arg: ToNumber\n\n"); return 0; } channame = a->argv[4]; nr = a->argv[5]; ast_verbose("Sending Callrerouting (%s) to %s\n", nr, channame); tmp = get_chan_by_ast_name(channame); if (!tmp) { ast_verbose("Sending Call Rerouting with nr %s to %s failed: Channel does not exist.\n", nr, channame); return 0; } ao2_lock(tmp); max_len = sizeof(tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number) - 1; if (max_len < strlen(nr)) { ast_verbose("Sending Call Rerouting with nr %s to %s failed: Number too long (up to %u digits are allowed).\n", nr, channame, max_len); ao2_unlock(tmp); chan_list_unref(tmp, "Number too long"); return 0; } tmp->bc->fac_out.Function = Fac_CallRerouteing; tmp->bc->fac_out.u.CallRerouteing.InvokeID = ++misdn_invoke_id; tmp->bc->fac_out.u.CallRerouteing.ComponentType = FacComponent_Invoke; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingReason = 0;/* unknown */ tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingCounter = 1; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 0;/* unknown */ tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = strlen(nr); strcpy((char *) tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number, nr); tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Subaddress.Length = 0; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length = 0; /* 0x90 0x90 0xa3 3.1 kHz audio, circuit mode, 64kbit/sec, level1/a-Law */ tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 3; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[0] = 0x90; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[1] = 0x90; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[2] = 0xa3; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Length = 0; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Llc.Length = 0; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Length = 0; tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1;/* presentationRestricted */ tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.SubscriptionOption = 0;/* no notification to caller */ /* Send message */ print_facility(&tmp->bc->fac_out, tmp->bc); ao2_unlock(tmp); misdn_lib_send_event(tmp->bc, EVENT_FACILITY); chan_list_unref(tmp, "Send facility complete"); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } else if (strstr(a->argv[3], "CFActivate")) { if (a->argc < 7) { ast_verbose("CFActivate requires 2 args: 1.FromNumber, 2.ToNumber\n\n"); return 0; } port = atoi(a->argv[4]); served_nr = a->argv[5]; nr = a->argv[6]; misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0); ast_verbose("Sending CFActivate Port:(%d) FromNr. (%s) to Nr. (%s)\n", port, served_nr, nr); #if defined(AST_MISDN_ENHANCEMENTS) bc->fac_out.Function = Fac_ActivationDiversion; bc->fac_out.u.ActivationDiversion.InvokeID = ++misdn_invoke_id; bc->fac_out.u.ActivationDiversion.ComponentType = FacComponent_Invoke; bc->fac_out.u.ActivationDiversion.Component.Invoke.BasicService = 0;/* allServices */ bc->fac_out.u.ActivationDiversion.Component.Invoke.Procedure = 0;/* cfu (Call Forward Unconditional) */ ast_copy_string((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Number, served_nr, sizeof(bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Number)); bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.LengthOfNumber = strlen((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Number); bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Type = 0;/* unknown */ ast_copy_string((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number, nr, sizeof(bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number)); bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.LengthOfNumber = strlen((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number); bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Type = 0;/* unknown */ bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Subaddress.Length = 0; #else /* !defined(AST_MISDN_ENHANCEMENTS) */ bc->fac_out.Function = Fac_CFActivate; bc->fac_out.u.CFActivate.BasicService = 0; /* All Services */ bc->fac_out.u.CFActivate.Procedure = 0; /* Unconditional */ ast_copy_string((char *) bc->fac_out.u.CFActivate.ServedUserNumber, served_nr, sizeof(bc->fac_out.u.CFActivate.ServedUserNumber)); ast_copy_string((char *) bc->fac_out.u.CFActivate.ForwardedToNumber, nr, sizeof(bc->fac_out.u.CFActivate.ForwardedToNumber)); #endif /* !defined(AST_MISDN_ENHANCEMENTS) */ /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); } else if (strstr(a->argv[3], "CFDeactivate")) { if (a->argc < 6) { ast_verbose("CFDeactivate requires 1 arg: FromNumber\n\n"); return 0; } port = atoi(a->argv[4]); served_nr = a->argv[5]; misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0); ast_verbose("Sending CFDeactivate Port:(%d) FromNr. (%s)\n", port, served_nr); #if defined(AST_MISDN_ENHANCEMENTS) bc->fac_out.Function = Fac_DeactivationDiversion; bc->fac_out.u.DeactivationDiversion.InvokeID = ++misdn_invoke_id; bc->fac_out.u.DeactivationDiversion.ComponentType = FacComponent_Invoke; bc->fac_out.u.DeactivationDiversion.Component.Invoke.BasicService = 0;/* allServices */ bc->fac_out.u.DeactivationDiversion.Component.Invoke.Procedure = 0;/* cfu (Call Forward Unconditional) */ ast_copy_string((char *) bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Number, served_nr, sizeof(bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Number)); bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.LengthOfNumber = strlen((char *) bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Number); bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Type = 0;/* unknown */ #else /* !defined(AST_MISDN_ENHANCEMENTS) */ bc->fac_out.Function = Fac_CFDeactivate; bc->fac_out.u.CFDeactivate.BasicService = 0; /* All Services */ bc->fac_out.u.CFDeactivate.Procedure = 0; /* Unconditional */ ast_copy_string((char *) bc->fac_out.u.CFActivate.ServedUserNumber, served_nr, sizeof(bc->fac_out.u.CFActivate.ServedUserNumber)); #endif /* !defined(AST_MISDN_ENHANCEMENTS) */ /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); #if defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES) } else if (strstr(a->argv[3], "test")) { int msg_number; if (a->argc < 5) { ast_verbose("test ( []) | ( )\n\n"); return 0; } port = atoi(a->argv[4]); channame = a->argv[4]; tmp = get_chan_by_ast_name(channame); if (tmp) { /* We are going to send this FACILITY message out on an existing connection */ msg_number = atoi(a->argv[5]); if (msg_number < ARRAY_LEN(Fac_Msgs)) { ao2_lock(tmp); tmp->bc->fac_out = Fac_Msgs[msg_number]; /* Send message */ print_facility(&tmp->bc->fac_out, tmp->bc); ao2_unlock(tmp); misdn_lib_send_event(tmp->bc, EVENT_FACILITY); } else { ast_verbose("test \n\n"); } chan_list_unref(tmp, "Facility test done"); } else if (a->argc < 6) { for (msg_number = 0; msg_number < ARRAY_LEN(Fac_Msgs); ++msg_number) { misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0); bc->fac_out = Fac_Msgs[msg_number]; /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); sleep(1); } } else { msg_number = atoi(a->argv[5]); if (msg_number < ARRAY_LEN(Fac_Msgs)) { misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0); bc->fac_out = Fac_Msgs[msg_number]; /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); } else { ast_verbose("test []\n\n"); } } } else if (strstr(a->argv[3], "register")) { if (a->argc < 5) { ast_verbose("register \n\n"); return 0; } port = atoi(a->argv[4]); bc = misdn_lib_get_register_bc(port); if (!bc) { ast_verbose("Could not allocate REGISTER bc struct\n\n"); return 0; } bc->fac_out = Fac_Msgs[45]; /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_REGISTER); #endif /* defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES) */ } return CLI_SUCCESS; } static char *handle_cli_misdn_send_restart(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int port; int channel; switch (cmd) { case CLI_INIT: e->command = "misdn send restart"; e->usage = "Usage: misdn send restart [port [channel]]\n" " Send a restart for every bchannel on the given port.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 4 || a->argc > 5) { return CLI_SHOWUSAGE; } port = atoi(a->argv[3]); if (a->argc == 5) { channel = atoi(a->argv[4]); misdn_lib_send_restart(port, channel); } else { misdn_lib_send_restart(port, -1); } return CLI_SUCCESS; } static char *handle_cli_misdn_send_digit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { const char *channame; const char *msg; struct chan_list *tmp; int i, msglen; switch (cmd) { case CLI_INIT: e->command = "misdn send digit"; e->usage = "Usage: misdn send digit \"\" \n" " Send to as DTMF Tone\n" " when channel is a mISDN channel\n"; return NULL; case CLI_GENERATE: return complete_ch(a); } if (a->argc != 5) { return CLI_SHOWUSAGE; } channame = a->argv[3]; msg = a->argv[4]; msglen = strlen(msg); ast_cli(a->fd, "Sending %s to %s\n", msg, channame); tmp = get_chan_by_ast_name(channame); if (!tmp) { ast_cli(a->fd, "Sending %s to %s failed Channel does not exist\n", msg, channame); return CLI_SUCCESS; } #if 1 for (i = 0; i < msglen; i++) { if (!tmp->ast) { break; } ast_cli(a->fd, "Sending: %c\n", msg[i]); send_digit_to_chan(tmp, msg[i]); /* res = ast_safe_sleep(tmp->ast, 250); */ usleep(250000); /* res = ast_waitfor(tmp->ast,100); */ } #else if (tmp->ast) { ast_dtmf_stream(tmp->ast, NULL, msg, 250); } #endif chan_list_unref(tmp, "Digit(s) sent"); return CLI_SUCCESS; } static char *handle_cli_misdn_toggle_echocancel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { const char *channame; struct chan_list *tmp; switch (cmd) { case CLI_INIT: e->command = "misdn toggle echocancel"; e->usage = "Usage: misdn toggle echocancel \n" " Toggle EchoCancel on mISDN Channel.\n"; return NULL; case CLI_GENERATE: return complete_ch(a); } if (a->argc != 4) { return CLI_SHOWUSAGE; } channame = a->argv[3]; ast_cli(a->fd, "Toggling EchoCancel on %s\n", channame); tmp = get_chan_by_ast_name(channame); if (!tmp) { ast_cli(a->fd, "Toggling EchoCancel %s failed Channel does not exist\n", channame); return CLI_SUCCESS; } tmp->toggle_ec = tmp->toggle_ec ? 0 : 1; if (tmp->toggle_ec) { #ifdef MISDN_1_2 update_pipeline_config(tmp->bc); #else update_ec_config(tmp->bc); #endif manager_ec_enable(tmp->bc); } else { manager_ec_disable(tmp->bc); } chan_list_unref(tmp, "Done toggling echo cancel"); return CLI_SUCCESS; } static char *handle_cli_misdn_send_display(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { const char *channame; const char *msg; struct chan_list *tmp; switch (cmd) { case CLI_INIT: e->command = "misdn send display"; e->usage = "Usage: misdn send display \"\" \n" " Send to as Display Message\n" " when channel is a mISDN channel\n"; return NULL; case CLI_GENERATE: return complete_ch(a); } if (a->argc != 5) { return CLI_SHOWUSAGE; } channame = a->argv[3]; msg = a->argv[4]; ast_cli(a->fd, "Sending %s to %s\n", msg, channame); tmp = get_chan_by_ast_name(channame); if (tmp && tmp->bc) { ast_copy_string(tmp->bc->display, msg, sizeof(tmp->bc->display)); misdn_lib_send_event(tmp->bc, EVENT_INFORMATION); chan_list_unref(tmp, "Done sending display"); } else { if (tmp) { chan_list_unref(tmp, "Display failed"); } ast_cli(a->fd, "No such channel %s\n", channame); return CLI_SUCCESS; } return CLI_SUCCESS; } static char *complete_ch(struct ast_cli_args *a) { return ast_complete_channels(a->line, a->word, a->pos, a->n, 3); } static char *complete_debug_port(struct ast_cli_args *a) { if (a->n) { return NULL; } switch (a->pos) { case 4: if (a->word[0] == 'p') { return ast_strdup("port"); } else if (a->word[0] == 'o') { return ast_strdup("only"); } break; case 6: if (a->word[0] == 'o') { return ast_strdup("only"); } break; } return NULL; } static char *complete_show_config(struct ast_cli_args *a) { char buffer[BUFFERSIZE]; enum misdn_cfg_elements elem; int wordlen = strlen(a->word); int which = 0; int port = 0; switch (a->pos) { case 3: if ((!strncmp(a->word, "description", wordlen)) && (++which > a->n)) { return ast_strdup("description"); } if ((!strncmp(a->word, "descriptions", wordlen)) && (++which > a->n)) { return ast_strdup("descriptions"); } if ((!strncmp(a->word, "0", wordlen)) && (++which > a->n)) { return ast_strdup("0"); } while ((port = misdn_cfg_get_next_port(port)) != -1) { snprintf(buffer, sizeof(buffer), "%d", port); if ((!strncmp(a->word, buffer, wordlen)) && (++which > a->n)) { return ast_strdup(buffer); } } break; case 4: if (strstr(a->line, "description ")) { for (elem = MISDN_CFG_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) { if ((elem == MISDN_CFG_LAST) || (elem == MISDN_GEN_FIRST)) { continue; } misdn_cfg_get_name(elem, buffer, sizeof(buffer)); if (!wordlen || !strncmp(a->word, buffer, wordlen)) { if (++which > a->n) { return ast_strdup(buffer); } } } } else if (strstr(a->line, "descriptions ")) { if ((!wordlen || !strncmp(a->word, "general", wordlen)) && (++which > a->n)) { return ast_strdup("general"); } if ((!wordlen || !strncmp(a->word, "ports", wordlen)) && (++which > a->n)) { return ast_strdup("ports"); } } break; } return NULL; } static struct ast_cli_entry chan_misdn_clis[] = { /* *INDENT-OFF* */ AST_CLI_DEFINE(handle_cli_misdn_port_block, "Block the given port"), AST_CLI_DEFINE(handle_cli_misdn_port_down, "Try to deactivate the L1 on the given port"), AST_CLI_DEFINE(handle_cli_misdn_port_unblock, "Unblock the given port"), AST_CLI_DEFINE(handle_cli_misdn_port_up, "Try to establish L1 on the given port"), AST_CLI_DEFINE(handle_cli_misdn_reload, "Reload internal mISDN config, read from the config file"), AST_CLI_DEFINE(handle_cli_misdn_restart_pid, "Restart the given pid"), AST_CLI_DEFINE(handle_cli_misdn_restart_port, "Restart the given port"), AST_CLI_DEFINE(handle_cli_misdn_show_channel, "Show an internal mISDN channel"), AST_CLI_DEFINE(handle_cli_misdn_show_channels, "Show the internal mISDN channel list"), AST_CLI_DEFINE(handle_cli_misdn_show_config, "Show internal mISDN config, read from the config file"), AST_CLI_DEFINE(handle_cli_misdn_show_port, "Show detailed information for given port"), AST_CLI_DEFINE(handle_cli_misdn_show_ports_stats, "Show mISDNs channel's call statistics per port"), AST_CLI_DEFINE(handle_cli_misdn_show_stacks, "Show internal mISDN stack_list"), AST_CLI_DEFINE(handle_cli_misdn_send_facility, "Sends a Facility Message to the mISDN Channel"), AST_CLI_DEFINE(handle_cli_misdn_send_digit, "Send DTMF digit to mISDN Channel"), AST_CLI_DEFINE(handle_cli_misdn_send_display, "Send Text to mISDN Channel"), AST_CLI_DEFINE(handle_cli_misdn_send_restart, "Send a restart for every bchannel on the given port"), AST_CLI_DEFINE(handle_cli_misdn_set_crypt_debug, "Set CryptDebuglevel of chan_misdn, at the moment, level={1,2}"), AST_CLI_DEFINE(handle_cli_misdn_set_debug, "Set Debuglevel of chan_misdn"), AST_CLI_DEFINE(handle_cli_misdn_set_tics, "???"), AST_CLI_DEFINE(handle_cli_misdn_toggle_echocancel, "Toggle EchoCancel on mISDN Channel"), /* *INDENT-ON* */ }; /*! \brief Updates caller ID information from config */ static void update_config(struct chan_list *ch) { struct ast_channel *ast; struct misdn_bchannel *bc; int port; int hdlc = 0; int pres; int screen; if (!ch) { ast_log(LOG_WARNING, "Cannot configure without chanlist\n"); return; } ast = ch->ast; bc = ch->bc; if (! ast || ! bc) { ast_log(LOG_WARNING, "Cannot configure without ast || bc\n"); return; } port = bc->port; chan_misdn_log(7, port, "update_config: Getting Config\n"); misdn_cfg_get(port, MISDN_CFG_HDLC, &hdlc, sizeof(int)); if (hdlc) { switch (bc->capability) { case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: case INFO_CAPABILITY_DIGITAL_RESTRICTED: chan_misdn_log(1, bc->port, " --> CONF HDLC\n"); bc->hdlc = 1; break; } } misdn_cfg_get(port, MISDN_CFG_PRES, &pres, sizeof(pres)); misdn_cfg_get(port, MISDN_CFG_SCREEN, &screen, sizeof(screen)); chan_misdn_log(2, port, " --> pres: %d screen: %d\n", pres, screen); if (pres < 0 || screen < 0) { chan_misdn_log(2, port, " --> pres: %x\n", ast_channel_connected(ast)->id.number.presentation); bc->caller.presentation = ast_to_misdn_pres(ast_channel_connected(ast)->id.number.presentation); chan_misdn_log(2, port, " --> PRES: %s(%d)\n", misdn_to_str_pres(bc->caller.presentation), bc->caller.presentation); bc->caller.screening = ast_to_misdn_screen(ast_channel_connected(ast)->id.number.presentation); chan_misdn_log(2, port, " --> SCREEN: %s(%d)\n", misdn_to_str_screen(bc->caller.screening), bc->caller.screening); } else { bc->caller.screening = screen; bc->caller.presentation = pres; } } static void config_jitterbuffer(struct chan_list *ch) { struct misdn_bchannel *bc = ch->bc; int len = ch->jb_len; int threshold = ch->jb_upper_threshold; chan_misdn_log(5, bc->port, "config_jb: Called\n"); if (!len) { chan_misdn_log(1, bc->port, "config_jb: Deactivating Jitterbuffer\n"); bc->nojitter = 1; } else { if (len <= 100 || len > 8000) { chan_misdn_log(0, bc->port, "config_jb: Jitterbuffer out of Bounds, setting to 1000\n"); len = 1000; } if (threshold > len) { chan_misdn_log(0, bc->port, "config_jb: Jitterbuffer Threshold > Jitterbuffer setting to Jitterbuffer -1\n"); } if (ch->jb) { cb_log(0, bc->port, "config_jb: We've got a Jitterbuffer Already on this port.\n"); misdn_jb_destroy(ch->jb); ch->jb = NULL; } ch->jb = misdn_jb_init(len, threshold); if (!ch->jb) { bc->nojitter = 1; } } } void debug_numtype(int port, int numtype, char *type) { switch (numtype) { case NUMTYPE_UNKNOWN: chan_misdn_log(2, port, " --> %s: Unknown\n", type); break; case NUMTYPE_INTERNATIONAL: chan_misdn_log(2, port, " --> %s: International\n", type); break; case NUMTYPE_NATIONAL: chan_misdn_log(2, port, " --> %s: National\n", type); break; case NUMTYPE_NETWORK_SPECIFIC: chan_misdn_log(2, port, " --> %s: Network Specific\n", type); break; case NUMTYPE_SUBSCRIBER: chan_misdn_log(2, port, " --> %s: Subscriber\n", type); break; case NUMTYPE_ABBREVIATED: chan_misdn_log(2, port, " --> %s: Abbreviated\n", type); break; /* Maybe we should cut off the prefix if present ? */ default: chan_misdn_log(0, port, " --> !!!! Wrong dialplan setting, please see the misdn.conf sample file\n "); break; } } #ifdef MISDN_1_2 static int update_pipeline_config(struct misdn_bchannel *bc) { int ec; misdn_cfg_get(bc->port, MISDN_CFG_PIPELINE, bc->pipeline, sizeof(bc->pipeline)); if (*bc->pipeline) { return 0; } misdn_cfg_get(bc->port, MISDN_CFG_ECHOCANCEL, &ec, sizeof(ec)); if (ec == 1) { ast_copy_string(bc->pipeline, "mg2ec", sizeof(bc->pipeline)); } else if (ec > 1) { snprintf(bc->pipeline, sizeof(bc->pipeline), "mg2ec(deftaps=%d)", ec); } return 0; } #else static int update_ec_config(struct misdn_bchannel *bc) { int ec; int port = bc->port; misdn_cfg_get(port, MISDN_CFG_ECHOCANCEL, &ec, sizeof(ec)); if (ec == 1) { bc->ec_enable = 1; } else if (ec > 1) { bc->ec_enable = 1; bc->ec_deftaps = ec; } return 0; } #endif static int read_config(struct chan_list *ch) { struct ast_channel *ast; struct misdn_bchannel *bc; int port; int hdlc = 0; char lang[BUFFERSIZE + 1]; char faxdetect[BUFFERSIZE + 1]; char buf[256]; char buf2[256]; ast_group_t pg; ast_group_t cg; struct ast_namedgroups *npg; struct ast_namedgroups *ncg; struct ast_str *tmp_str; if (!ch) { ast_log(LOG_WARNING, "Cannot configure without chanlist\n"); return -1; } ast = ch->ast; bc = ch->bc; if (! ast || ! bc) { ast_log(LOG_WARNING, "Cannot configure without ast || bc\n"); return -1; } port = bc->port; chan_misdn_log(1, port, "read_config: Getting Config\n"); misdn_cfg_get(port, MISDN_CFG_LANGUAGE, lang, sizeof(lang)); ast_channel_lock(ast); ast_channel_language_set(ast, lang); ast_channel_unlock(ast); misdn_cfg_get(port, MISDN_CFG_MUSICCLASS, ch->mohinterpret, sizeof(ch->mohinterpret)); misdn_cfg_get(port, MISDN_CFG_TXGAIN, &bc->txgain, sizeof(bc->txgain)); misdn_cfg_get(port, MISDN_CFG_RXGAIN, &bc->rxgain, sizeof(bc->rxgain)); misdn_cfg_get(port, MISDN_CFG_INCOMING_EARLY_AUDIO, &ch->incoming_early_audio, sizeof(ch->incoming_early_audio)); misdn_cfg_get(port, MISDN_CFG_SENDDTMF, &bc->send_dtmf, sizeof(bc->send_dtmf)); misdn_cfg_get(port, MISDN_CFG_ASTDTMF, &ch->ast_dsp, sizeof(int)); if (ch->ast_dsp) { ch->ignore_dtmf = 1; } misdn_cfg_get(port, MISDN_CFG_NEED_MORE_INFOS, &bc->need_more_infos, sizeof(bc->need_more_infos)); misdn_cfg_get(port, MISDN_CFG_NTTIMEOUT, &ch->nttimeout, sizeof(ch->nttimeout)); misdn_cfg_get(port, MISDN_CFG_NOAUTORESPOND_ON_SETUP, &ch->noautorespond_on_setup, sizeof(ch->noautorespond_on_setup)); misdn_cfg_get(port, MISDN_CFG_FAR_ALERTING, &ch->far_alerting, sizeof(ch->far_alerting)); misdn_cfg_get(port, MISDN_CFG_ALLOWED_BEARERS, &ch->allowed_bearers, sizeof(ch->allowed_bearers)); misdn_cfg_get(port, MISDN_CFG_FAXDETECT, faxdetect, sizeof(faxdetect)); misdn_cfg_get(port, MISDN_CFG_HDLC, &hdlc, sizeof(hdlc)); if (hdlc) { switch (bc->capability) { case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: case INFO_CAPABILITY_DIGITAL_RESTRICTED: chan_misdn_log(1, bc->port, " --> CONF HDLC\n"); bc->hdlc = 1; break; } } /*Initialize new Jitterbuffer*/ misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER, &ch->jb_len, sizeof(ch->jb_len)); misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, &ch->jb_upper_threshold, sizeof(ch->jb_upper_threshold)); config_jitterbuffer(ch); misdn_cfg_get(bc->port, MISDN_CFG_CONTEXT, ch->context, sizeof(ch->context)); ast_channel_lock(ast); ast_channel_context_set(ast, ch->context); ast_channel_unlock(ast); #ifdef MISDN_1_2 update_pipeline_config(bc); #else update_ec_config(bc); #endif misdn_cfg_get(bc->port, MISDN_CFG_EARLY_BCONNECT, &bc->early_bconnect, sizeof(bc->early_bconnect)); misdn_cfg_get(port, MISDN_CFG_DISPLAY_CONNECTED, &bc->display_connected, sizeof(bc->display_connected)); misdn_cfg_get(port, MISDN_CFG_DISPLAY_SETUP, &bc->display_setup, sizeof(bc->display_setup)); misdn_cfg_get(port, MISDN_CFG_OUTGOING_COLP, &bc->outgoing_colp, sizeof(bc->outgoing_colp)); misdn_cfg_get(port, MISDN_CFG_PICKUPGROUP, &pg, sizeof(pg)); misdn_cfg_get(port, MISDN_CFG_CALLGROUP, &cg, sizeof(cg)); chan_misdn_log(5, port, " --> * CallGrp:%s PickupGrp:%s\n", ast_print_group(buf, sizeof(buf), cg), ast_print_group(buf2, sizeof(buf2), pg)); ast_channel_lock(ast); ast_channel_pickupgroup_set(ast, pg); ast_channel_callgroup_set(ast, cg); ast_channel_unlock(ast); misdn_cfg_get(port, MISDN_CFG_NAMEDPICKUPGROUP, &npg, sizeof(npg)); misdn_cfg_get(port, MISDN_CFG_NAMEDCALLGROUP, &ncg, sizeof(ncg)); tmp_str = ast_str_create(1024); if (tmp_str) { chan_misdn_log(5, port, " --> * NamedCallGrp:%s\n", ast_print_namedgroups(&tmp_str, ncg)); ast_str_reset(tmp_str); chan_misdn_log(5, port, " --> * NamedPickupGrp:%s\n", ast_print_namedgroups(&tmp_str, npg)); ast_free(tmp_str); } ast_channel_lock(ast); ast_channel_named_pickupgroups_set(ast, npg); ast_channel_named_callgroups_set(ast, ncg); ast_channel_unlock(ast); if (ch->originator == ORG_AST) { char callerid[BUFFERSIZE + 1]; /* ORIGINATOR Asterisk (outgoing call) */ misdn_cfg_get(port, MISDN_CFG_TE_CHOOSE_CHANNEL, &(bc->te_choose_channel), sizeof(bc->te_choose_channel)); if (strstr(faxdetect, "outgoing") || strstr(faxdetect, "both")) { ch->faxdetect = strstr(faxdetect, "nojump") ? 2 : 1; } misdn_cfg_get(port, MISDN_CFG_CALLERID, callerid, sizeof(callerid)); if (!ast_strlen_zero(callerid)) { char *cid_name = NULL; char *cid_num = NULL; ast_callerid_parse(callerid, &cid_name, &cid_num); if (cid_name) { ast_copy_string(bc->caller.name, cid_name, sizeof(bc->caller.name)); } else { bc->caller.name[0] = '\0'; } if (cid_num) { ast_copy_string(bc->caller.number, cid_num, sizeof(bc->caller.number)); } else { bc->caller.number[0] = '\0'; } chan_misdn_log(1, port, " --> * Setting caller to \"%s\" <%s>\n", bc->caller.name, bc->caller.number); } misdn_cfg_get(port, MISDN_CFG_DIALPLAN, &bc->dialed.number_type, sizeof(bc->dialed.number_type)); bc->dialed.number_plan = NUMPLAN_ISDN; debug_numtype(port, bc->dialed.number_type, "TON"); ch->overlap_dial = 0; } else { /* ORIGINATOR MISDN (incoming call) */ if (strstr(faxdetect, "incoming") || strstr(faxdetect, "both")) { ch->faxdetect = (strstr(faxdetect, "nojump")) ? 2 : 1; } /* Add configured prefix to caller.number */ misdn_add_number_prefix(bc->port, bc->caller.number_type, bc->caller.number, sizeof(bc->caller.number)); if (ast_strlen_zero(bc->dialed.number) && !ast_strlen_zero(bc->keypad)) { ast_copy_string(bc->dialed.number, bc->keypad, sizeof(bc->dialed.number)); } /* Add configured prefix to dialed.number */ misdn_add_number_prefix(bc->port, bc->dialed.number_type, bc->dialed.number, sizeof(bc->dialed.number)); ast_channel_lock(ast); ast_channel_exten_set(ast, bc->dialed.number); ast_channel_unlock(ast); misdn_cfg_get(bc->port, MISDN_CFG_OVERLAP_DIAL, &ch->overlap_dial, sizeof(ch->overlap_dial)); ast_mutex_init(&ch->overlap_tv_lock); } /* ORIG MISDN END */ misdn_cfg_get(port, MISDN_CFG_INCOMING_CALLERID_TAG, bc->incoming_cid_tag, sizeof(bc->incoming_cid_tag)); if (!ast_strlen_zero(bc->incoming_cid_tag)) { chan_misdn_log(1, port, " --> * Setting incoming caller id tag to \"%s\"\n", bc->incoming_cid_tag); } ch->overlap_dial_task = -1; if (ch->faxdetect || ch->ast_dsp) { misdn_cfg_get(port, MISDN_CFG_FAXDETECT_TIMEOUT, &ch->faxdetect_timeout, sizeof(ch->faxdetect_timeout)); if (!ch->dsp) { ch->dsp = ast_dsp_new(); } if (ch->dsp) { ast_dsp_set_features(ch->dsp, DSP_FEATURE_DIGIT_DETECT | (ch->faxdetect ? DSP_FEATURE_FAX_DETECT : 0)); } } /* AOCD initialization */ bc->AOCDtype = Fac_None; return 0; } /*! * \internal * \brief Send a connected line update to the other channel * * \param ast Current Asterisk channel * \param id Party id information to send to the other side * \param source Why are we sending this update * \param cid_tag User tag to apply to the party id. * * \return Nothing */ static void misdn_queue_connected_line_update(struct ast_channel *ast, const struct misdn_party_id *id, enum AST_CONNECTED_LINE_UPDATE_SOURCE source, char *cid_tag) { 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 = 1; connected.id.number.valid = 1; connected.id.number.str = (char *) id->number; connected.id.number.plan = misdn_to_ast_ton(id->number_type) | misdn_to_ast_plan(id->number_plan); connected.id.number.presentation = misdn_to_ast_pres(id->presentation) | misdn_to_ast_screen(id->screening); /* * Make sure that any earlier private connected id * representation at the remote end is invalidated */ ast_set_party_id_all(&update_connected.priv); connected.id.tag = cid_tag; connected.source = source; ast_channel_queue_connected_line_update(ast, &connected, &update_connected); } /*! * \internal * \brief Update the caller id party on this channel. * * \param ast Current Asterisk channel * \param id Remote party id information to update. * \param cid_tag User tag to apply to the party id. * * \return Nothing */ static void misdn_update_caller_id(struct ast_channel *ast, const struct misdn_party_id *id, char *cid_tag) { struct ast_party_caller caller; struct ast_set_party_caller update_caller; memset(&update_caller, 0, sizeof(update_caller)); update_caller.id.number = 1; update_caller.ani.number = 1; ast_channel_lock(ast); ast_party_caller_set_init(&caller, ast_channel_caller(ast)); caller.id.number.valid = 1; caller.id.number.str = (char *) id->number; caller.id.number.plan = misdn_to_ast_ton(id->number_type) | misdn_to_ast_plan(id->number_plan); caller.id.number.presentation = misdn_to_ast_pres(id->presentation) | misdn_to_ast_screen(id->screening); caller.ani.number = caller.id.number; caller.id.tag = cid_tag; caller.ani.tag = cid_tag; ast_channel_set_caller_event(ast, &caller, &update_caller); ast_channel_unlock(ast); } /*! * \internal * \brief Update the remote party id information. * * \param ast Current Asterisk channel * \param id Remote party id information to update. * \param source Why are we sending this update * \param cid_tag User tag to apply to the party id. * * \return Nothing */ static void misdn_update_remote_party(struct ast_channel *ast, const struct misdn_party_id *id, enum AST_CONNECTED_LINE_UPDATE_SOURCE source, char *cid_tag) { misdn_update_caller_id(ast, id, cid_tag); misdn_queue_connected_line_update(ast, id, source, cid_tag); } /*! * \internal * \brief Get the connected line information out of the Asterisk channel. * * \param ast Current Asterisk channel * \param bc Associated B channel * \param originator Who originally created this channel. ORG_AST or ORG_MISDN * * \return Nothing */ static void misdn_get_connected_line(struct ast_channel *ast, struct misdn_bchannel *bc, int originator) { int number_type; struct ast_party_id connected_id = ast_channel_connected_effective_id(ast); if (originator == ORG_MISDN) { /* ORIGINATOR MISDN (incoming call) */ ast_copy_string(bc->connected.name, S_COR(connected_id.name.valid, connected_id.name.str, ""), sizeof(bc->connected.name)); if (connected_id.number.valid) { ast_copy_string(bc->connected.number, S_OR(connected_id.number.str, ""), sizeof(bc->connected.number)); bc->connected.presentation = ast_to_misdn_pres(connected_id.number.presentation); bc->connected.screening = ast_to_misdn_screen(connected_id.number.presentation); bc->connected.number_type = ast_to_misdn_ton(connected_id.number.plan); bc->connected.number_plan = ast_to_misdn_plan(connected_id.number.plan); } else { bc->connected.number[0] = '\0'; bc->connected.presentation = 0;/* Allowed */ bc->connected.screening = 0;/* Unscreened */ bc->connected.number_type = NUMTYPE_UNKNOWN; bc->connected.number_plan = NUMPLAN_UNKNOWN; } misdn_cfg_get(bc->port, MISDN_CFG_CPNDIALPLAN, &number_type, sizeof(number_type)); if (0 <= number_type) { /* Force us to send in CONNECT message */ bc->connected.number_type = number_type; bc->connected.number_plan = NUMPLAN_ISDN; } debug_numtype(bc->port, bc->connected.number_type, "CTON"); } else { /* ORIGINATOR Asterisk (outgoing call) */ ast_copy_string(bc->caller.name, S_COR(connected_id.name.valid, connected_id.name.str, ""), sizeof(bc->caller.name)); if (connected_id.number.valid) { ast_copy_string(bc->caller.number, S_OR(connected_id.number.str, ""), sizeof(bc->caller.number)); bc->caller.presentation = ast_to_misdn_pres(connected_id.number.presentation); bc->caller.screening = ast_to_misdn_screen(connected_id.number.presentation); bc->caller.number_type = ast_to_misdn_ton(connected_id.number.plan); bc->caller.number_plan = ast_to_misdn_plan(connected_id.number.plan); } else { bc->caller.number[0] = '\0'; bc->caller.presentation = 0;/* Allowed */ bc->caller.screening = 0;/* Unscreened */ bc->caller.number_type = NUMTYPE_UNKNOWN; bc->caller.number_plan = NUMPLAN_UNKNOWN; } misdn_cfg_get(bc->port, MISDN_CFG_LOCALDIALPLAN, &number_type, sizeof(number_type)); if (0 <= number_type) { /* Force us to send in SETUP message */ bc->caller.number_type = number_type; bc->caller.number_plan = NUMPLAN_ISDN; } debug_numtype(bc->port, bc->caller.number_type, "LTON"); } } /*! * \internal * \brief Notify peer that the connected line has changed. * * \param ast Current Asterisk channel * \param bc Associated B channel * \param originator Who originally created this channel. ORG_AST or ORG_MISDN * * \return Nothing */ static void misdn_update_connected_line(struct ast_channel *ast, struct misdn_bchannel *bc, int originator) { struct chan_list *ch; misdn_get_connected_line(ast, bc, originator); if (originator == ORG_MISDN) { bc->redirecting.to = bc->connected; } else { bc->redirecting.to = bc->caller; } switch (bc->outgoing_colp) { case 1:/* restricted */ bc->redirecting.to.presentation = 1;/* restricted */ break; case 2:/* blocked */ /* Don't tell the remote party that the call was transferred. */ return; default: break; } ch = MISDN_ASTERISK_TECH_PVT(ast); if (ch->state == MISDN_CONNECTED || originator != ORG_MISDN) { int is_ptmp; is_ptmp = !misdn_lib_is_ptp(bc->port); if (is_ptmp) { /* * We should not send these messages to the network if we are * the CPE side since phones do not transfer calls within * themselves. Well... If you consider handing the handset to * someone else a transfer then how is the network to know? */ if (!misdn_lib_port_is_nt(bc->port)) { return; } if (ch->state != MISDN_CONNECTED) { /* Send NOTIFY(Nie(transfer-active), RDNie(redirecting.to data)) */ bc->redirecting.to_changed = 1; bc->notify_description_code = mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE; misdn_lib_send_event(bc, EVENT_NOTIFY); #if defined(AST_MISDN_ENHANCEMENTS) } else { /* Send FACILITY(Fie(RequestSubaddress), Nie(transfer-active), RDNie(redirecting.to data)) */ bc->redirecting.to_changed = 1; bc->notify_description_code = mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE; bc->fac_out.Function = Fac_RequestSubaddress; bc->fac_out.u.RequestSubaddress.InvokeID = ++misdn_invoke_id; /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } #if defined(AST_MISDN_ENHANCEMENTS) } else { /* Send FACILITY(Fie(EctInform(transfer-active, redirecting.to data))) */ bc->fac_out.Function = Fac_EctInform; bc->fac_out.u.EctInform.InvokeID = ++misdn_invoke_id; bc->fac_out.u.EctInform.Status = 1;/* active */ bc->fac_out.u.EctInform.RedirectionPresent = 1;/* Must be present when status is active */ misdn_PresentedNumberUnscreened_fill(&bc->fac_out.u.EctInform.Redirection, &bc->redirecting.to); /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } } } /*! * \internal * \brief Copy the redirecting information out of the Asterisk channel * * \param bc Associated B channel * \param ast Current Asterisk channel * * \return Nothing */ static void misdn_copy_redirecting_from_ast(struct misdn_bchannel *bc, struct ast_channel *ast) { struct ast_party_id from_id = ast_channel_redirecting_effective_from(ast); struct ast_party_id to_id = ast_channel_redirecting_effective_to(ast); ast_copy_string(bc->redirecting.from.name, S_COR(from_id.name.valid, from_id.name.str, ""), sizeof(bc->redirecting.from.name)); if (from_id.number.valid) { ast_copy_string(bc->redirecting.from.number, S_OR(from_id.number.str, ""), sizeof(bc->redirecting.from.number)); bc->redirecting.from.presentation = ast_to_misdn_pres(from_id.number.presentation); bc->redirecting.from.screening = ast_to_misdn_screen(from_id.number.presentation); bc->redirecting.from.number_type = ast_to_misdn_ton(from_id.number.plan); bc->redirecting.from.number_plan = ast_to_misdn_plan(from_id.number.plan); } else { bc->redirecting.from.number[0] = '\0'; bc->redirecting.from.presentation = 0;/* Allowed */ bc->redirecting.from.screening = 0;/* Unscreened */ bc->redirecting.from.number_type = NUMTYPE_UNKNOWN; bc->redirecting.from.number_plan = NUMPLAN_UNKNOWN; } ast_copy_string(bc->redirecting.to.name, S_COR(to_id.name.valid, to_id.name.str, ""), sizeof(bc->redirecting.to.name)); if (to_id.number.valid) { ast_copy_string(bc->redirecting.to.number, S_OR(to_id.number.str, ""), sizeof(bc->redirecting.to.number)); bc->redirecting.to.presentation = ast_to_misdn_pres(to_id.number.presentation); bc->redirecting.to.screening = ast_to_misdn_screen(to_id.number.presentation); bc->redirecting.to.number_type = ast_to_misdn_ton(to_id.number.plan); bc->redirecting.to.number_plan = ast_to_misdn_plan(to_id.number.plan); } else { bc->redirecting.to.number[0] = '\0'; bc->redirecting.to.presentation = 0;/* Allowed */ bc->redirecting.to.screening = 0;/* Unscreened */ bc->redirecting.to.number_type = NUMTYPE_UNKNOWN; bc->redirecting.to.number_plan = NUMPLAN_UNKNOWN; } bc->redirecting.reason = ast_to_misdn_reason(ast_channel_redirecting(ast)->reason.code); bc->redirecting.count = ast_channel_redirecting(ast)->count; } /*! * \internal * \brief Copy the redirecting info into the Asterisk channel * * \param ast Current Asterisk channel * \param redirect Associated B channel redirecting info * \param tag Caller ID tag to set in the redirecting party fields * * \return Nothing */ static void misdn_copy_redirecting_to_ast(struct ast_channel *ast, const struct misdn_party_redirecting *redirect, char *tag) { struct ast_party_redirecting redirecting; struct ast_set_party_redirecting update_redirecting; ast_party_redirecting_set_init(&redirecting, ast_channel_redirecting(ast)); memset(&update_redirecting, 0, sizeof(update_redirecting)); update_redirecting.from.number = 1; redirecting.from.number.valid = 1; redirecting.from.number.str = (char *) redirect->from.number; redirecting.from.number.plan = misdn_to_ast_ton(redirect->from.number_type) | misdn_to_ast_plan(redirect->from.number_plan); redirecting.from.number.presentation = misdn_to_ast_pres(redirect->from.presentation) | misdn_to_ast_screen(redirect->from.screening); redirecting.from.tag = tag; update_redirecting.to.number = 1; redirecting.to.number.valid = 1; redirecting.to.number.str = (char *) redirect->to.number; redirecting.to.number.plan = misdn_to_ast_ton(redirect->to.number_type) | misdn_to_ast_plan(redirect->to.number_plan); redirecting.to.number.presentation = misdn_to_ast_pres(redirect->to.presentation) | misdn_to_ast_screen(redirect->to.screening); redirecting.to.tag = tag; redirecting.reason.code = misdn_to_ast_reason(redirect->reason); redirecting.count = redirect->count; ast_channel_set_redirecting(ast, &redirecting, &update_redirecting); } /*! * \internal * \brief Notify peer that the redirecting information has changed. * * \param ast Current Asterisk channel * \param bc Associated B channel * \param originator Who originally created this channel. ORG_AST or ORG_MISDN * * \return Nothing */ static void misdn_update_redirecting(struct ast_channel *ast, struct misdn_bchannel *bc, int originator) { int is_ptmp; misdn_copy_redirecting_from_ast(bc, ast); switch (bc->outgoing_colp) { case 1:/* restricted */ bc->redirecting.to.presentation = 1;/* restricted */ break; case 2:/* blocked */ /* Don't tell the remote party that the call was redirected. */ return; default: break; } if (originator != ORG_MISDN) { return; } is_ptmp = !misdn_lib_is_ptp(bc->port); if (is_ptmp) { /* * We should not send these messages to the network if we are * the CPE side since phones do not redirect calls within * themselves. Well... If you consider someone else picking up * the handset a redirection then how is the network to know? */ if (!misdn_lib_port_is_nt(bc->port)) { return; } /* Send NOTIFY(call-is-diverting, redirecting.to data) */ bc->redirecting.to_changed = 1; bc->notify_description_code = mISDN_NOTIFY_CODE_CALL_IS_DIVERTING; misdn_lib_send_event(bc, EVENT_NOTIFY); #if defined(AST_MISDN_ENHANCEMENTS) } else { int match; /* TRUE if the dialed number matches the redirecting to number */ match = (strcmp(ast_channel_exten(ast), bc->redirecting.to.number) == 0) ? 1 : 0; if (!bc->div_leg_3_tx_pending || !match) { /* Send DivertingLegInformation1 */ bc->fac_out.Function = Fac_DivertingLegInformation1; bc->fac_out.u.DivertingLegInformation1.InvokeID = ++misdn_invoke_id; bc->fac_out.u.DivertingLegInformation1.DiversionReason = misdn_to_diversion_reason(bc->redirecting.reason); bc->fac_out.u.DivertingLegInformation1.SubscriptionOption = 2;/* notificationWithDivertedToNr */ bc->fac_out.u.DivertingLegInformation1.DivertedToPresent = 1; misdn_PresentedNumberUnscreened_fill(&bc->fac_out.u.DivertingLegInformation1.DivertedTo, &bc->redirecting.to); print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); } bc->div_leg_3_tx_pending = 0; /* Send DivertingLegInformation3 */ bc->fac_out.Function = Fac_DivertingLegInformation3; bc->fac_out.u.DivertingLegInformation3.InvokeID = ++misdn_invoke_id; bc->fac_out.u.DivertingLegInformation3.PresentationAllowedIndicator = bc->redirecting.to.presentation == 0 ? 1 : 0; print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } } /*****************************/ /*** AST Indications Start ***/ /*****************************/ static int misdn_call(struct ast_channel *ast, const char *dest, int timeout) { int port = 0; int r; int exceed; int number_type; struct chan_list *ch; struct misdn_bchannel *newbc; char *dest_cp; int append_msn = 0; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(intf); /* The interface token is discarded. */ AST_APP_ARG(ext); /* extension token */ AST_APP_ARG(opts); /* options token */ ); if (!ast) { ast_log(LOG_WARNING, " --> ! misdn_call called on ast_channel *ast where ast == NULL\n"); return -1; } if (((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) || !dest) { ast_log(LOG_WARNING, " --> ! misdn_call called on %s, neither down nor reserved (or dest==NULL)\n", ast_channel_name(ast)); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE); ast_setstate(ast, AST_STATE_DOWN); return -1; } ch = MISDN_ASTERISK_TECH_PVT(ast); if (!ch) { ast_log(LOG_WARNING, " --> ! misdn_call called on %s, chan_list *ch==NULL\n", ast_channel_name(ast)); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE); ast_setstate(ast, AST_STATE_DOWN); return -1; } newbc = ch->bc; if (!newbc) { ast_log(LOG_WARNING, " --> ! misdn_call called on %s, newbc==NULL\n", ast_channel_name(ast)); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE); ast_setstate(ast, AST_STATE_DOWN); return -1; } port = newbc->port; #if defined(AST_MISDN_ENHANCEMENTS) if ((ch->peer = misdn_cc_caller_get(ast))) { chan_misdn_log(3, port, " --> Found CC caller data, peer:%s\n", ch->peer->chan ? "available" : "NULL"); } if (ch->record_id != -1) { struct misdn_cc_record *cc_record; /* This is a call completion retry call */ AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(ch->record_id); if (!cc_record) { AST_LIST_UNLOCK(&misdn_cc_records_db); ast_log(LOG_WARNING, " --> ! misdn_call called on %s, cc_record==NULL\n", ast_channel_name(ast)); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE); ast_setstate(ast, AST_STATE_DOWN); return -1; } /* Setup calling parameters to retry the call. */ newbc->dialed = cc_record->redial.dialed; newbc->caller = cc_record->redial.caller; memset(&newbc->redirecting, 0, sizeof(newbc->redirecting)); newbc->capability = cc_record->redial.capability; newbc->hdlc = cc_record->redial.hdlc; newbc->sending_complete = 1; if (cc_record->ptp) { newbc->fac_out.Function = Fac_CCBS_T_Call; newbc->fac_out.u.CCBS_T_Call.InvokeID = ++misdn_invoke_id; } else { newbc->fac_out.Function = Fac_CCBSCall; newbc->fac_out.u.CCBSCall.InvokeID = ++misdn_invoke_id; newbc->fac_out.u.CCBSCall.CCBSReference = cc_record->mode.ptmp.reference_id; } AST_LIST_UNLOCK(&misdn_cc_records_db); ast_channel_exten_set(ast, newbc->dialed.number); chan_misdn_log(1, port, "* Call completion to: %s\n", newbc->dialed.number); chan_misdn_log(2, port, " --> * tech:%s context:%s\n", ast_channel_name(ast), ast_channel_context(ast)); } else #endif /* defined(AST_MISDN_ENHANCEMENTS) */ { struct ast_party_id connected_id = ast_channel_connected_effective_id(ast); /* * dest is ---v * Dial(mISDN/g:group_name[/extension[/options]]) * Dial(mISDN/port[:preselected_channel][/extension[/options]]) * * The dial extension could be empty if you are using MISDN_KEYPAD * to control ISDN provider features. */ dest_cp = ast_strdupa(dest); AST_NONSTANDARD_APP_ARGS(args, dest_cp, '/'); if (!args.ext) { args.ext = ""; } chan_misdn_log(1, port, "* CALL: %s\n", dest); chan_misdn_log(2, port, " --> * dialed:%s tech:%s context:%s\n", args.ext, ast_channel_name(ast), ast_channel_context(ast)); ast_channel_exten_set(ast, args.ext); ast_copy_string(newbc->dialed.number, args.ext, sizeof(newbc->dialed.number)); if (ast_strlen_zero(newbc->caller.name) && connected_id.name.valid && !ast_strlen_zero(connected_id.name.str)) { ast_copy_string(newbc->caller.name, connected_id.name.str, sizeof(newbc->caller.name)); chan_misdn_log(3, port, " --> * set caller:\"%s\" <%s>\n", newbc->caller.name, newbc->caller.number); } if (ast_strlen_zero(newbc->caller.number) && connected_id.number.valid && !ast_strlen_zero(connected_id.number.str)) { ast_copy_string(newbc->caller.number, connected_id.number.str, sizeof(newbc->caller.number)); chan_misdn_log(3, port, " --> * set caller:\"%s\" <%s>\n", newbc->caller.name, newbc->caller.number); } misdn_cfg_get(port, MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, &append_msn, sizeof(append_msn)); if (append_msn) { strncat(newbc->incoming_cid_tag, "_", sizeof(newbc->incoming_cid_tag) - strlen(newbc->incoming_cid_tag) - 1); strncat(newbc->incoming_cid_tag, newbc->caller.number, sizeof(newbc->incoming_cid_tag) - strlen(newbc->incoming_cid_tag) - 1); } ast_channel_caller(ast)->id.tag = ast_strdup(newbc->incoming_cid_tag); misdn_cfg_get(port, MISDN_CFG_LOCALDIALPLAN, &number_type, sizeof(number_type)); if (number_type < 0) { if (connected_id.number.valid) { newbc->caller.number_type = ast_to_misdn_ton(connected_id.number.plan); newbc->caller.number_plan = ast_to_misdn_plan(connected_id.number.plan); } else { newbc->caller.number_type = NUMTYPE_UNKNOWN; newbc->caller.number_plan = NUMPLAN_ISDN; } } else { /* Force us to send in SETUP message */ newbc->caller.number_type = number_type; newbc->caller.number_plan = NUMPLAN_ISDN; } debug_numtype(port, newbc->caller.number_type, "LTON"); newbc->capability = ast_channel_transfercapability(ast); pbx_builtin_setvar_helper(ast, "TRANSFERCAPABILITY", ast_transfercapability2str(newbc->capability)); if (ast_channel_transfercapability(ast) == INFO_CAPABILITY_DIGITAL_UNRESTRICTED) { chan_misdn_log(2, port, " --> * Call with flag Digital\n"); } /* update caller screening and presentation */ update_config(ch); /* fill in some ies from channel dialplan variables */ import_ch(ast, newbc, ch); /* Finally The Options Override Everything */ if (!ast_strlen_zero(args.opts)) { misdn_set_opt_exec(ast, args.opts); } else { chan_misdn_log(2, port, "NO OPTS GIVEN\n"); } if (newbc->set_presentation) { newbc->caller.presentation = newbc->presentation; } misdn_copy_redirecting_from_ast(newbc, ast); switch (newbc->outgoing_colp) { case 1:/* restricted */ case 2:/* blocked */ newbc->redirecting.from.presentation = 1;/* restricted */ break; default: break; } #if defined(AST_MISDN_ENHANCEMENTS) if (newbc->redirecting.from.number[0] && misdn_lib_is_ptp(port)) { if (newbc->redirecting.count < 1) { newbc->redirecting.count = 1; } /* Create DivertingLegInformation2 facility */ newbc->fac_out.Function = Fac_DivertingLegInformation2; newbc->fac_out.u.DivertingLegInformation2.InvokeID = ++misdn_invoke_id; newbc->fac_out.u.DivertingLegInformation2.DivertingPresent = 1; misdn_PresentedNumberUnscreened_fill( &newbc->fac_out.u.DivertingLegInformation2.Diverting, &newbc->redirecting.from); switch (newbc->outgoing_colp) { case 2:/* blocked */ /* Block the number going out */ newbc->fac_out.u.DivertingLegInformation2.Diverting.Type = 1;/* presentationRestricted */ /* Don't tell about any previous diversions or why for that matter. */ newbc->fac_out.u.DivertingLegInformation2.DiversionCounter = 1; newbc->fac_out.u.DivertingLegInformation2.DiversionReason = 0;/* unknown */ break; default: newbc->fac_out.u.DivertingLegInformation2.DiversionCounter = newbc->redirecting.count; newbc->fac_out.u.DivertingLegInformation2.DiversionReason = misdn_to_diversion_reason(newbc->redirecting.reason); break; } newbc->fac_out.u.DivertingLegInformation2.OriginalCalledPresent = 0; if (1 < newbc->fac_out.u.DivertingLegInformation2.DiversionCounter) { newbc->fac_out.u.DivertingLegInformation2.OriginalCalledPresent = 1; newbc->fac_out.u.DivertingLegInformation2.OriginalCalled.Type = 2;/* numberNotAvailableDueToInterworking */ } /* * Expect a DivertingLegInformation3 to update the COLR of the * redirecting-to party we are attempting to call now. */ newbc->div_leg_3_rx_wanted = 1; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } exceed = add_out_calls(port); if (exceed != 0) { char tmp[16]; snprintf(tmp, sizeof(tmp), "%d", exceed); pbx_builtin_setvar_helper(ast, "MAX_OVERFLOW", tmp); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE); ast_setstate(ast, AST_STATE_DOWN); return -1; } #if defined(AST_MISDN_ENHANCEMENTS) if (newbc->fac_out.Function != Fac_None) { print_facility(&newbc->fac_out, newbc); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ r = misdn_lib_send_event(newbc, EVENT_SETUP); /** we should have l3id after sending setup **/ ch->l3id = newbc->l3_id; if (r == -ENOCHAN) { chan_misdn_log(0, port, " --> * Theres no Channel at the moment .. !\n"); chan_misdn_log(1, port, " --> * SEND: State Down pid:%d\n", newbc ? newbc->pid : -1); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_CIRCUIT_CONGESTION); ast_setstate(ast, AST_STATE_DOWN); return -1; } chan_misdn_log(2, port, " --> * SEND: State Dialing pid:%d\n", newbc ? newbc->pid : 1); ast_setstate(ast, AST_STATE_DIALING); ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_CLEARING); if (newbc->nt) { stop_bc_tones(ch); } ch->state = MISDN_CALLING; return 0; } static int misdn_answer(struct ast_channel *ast) { struct chan_list *p; const char *tmp; if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) { return -1; } chan_misdn_log(1, p ? (p->bc ? p->bc->port : 0) : 0, "* ANSWER:\n"); if (!p) { ast_log(LOG_WARNING, " --> Channel not connected ??\n"); ast_queue_hangup_with_cause(ast, AST_CAUSE_NETWORK_OUT_OF_ORDER); } if (!p->bc) { chan_misdn_log(1, 0, " --> Got Answer, but there is no bc obj ??\n"); ast_queue_hangup_with_cause(ast, AST_CAUSE_PROTOCOL_ERROR); } ast_channel_lock(ast); tmp = pbx_builtin_getvar_helper(ast, "CRYPT_KEY"); if (!ast_strlen_zero(tmp)) { chan_misdn_log(1, p->bc->port, " --> Connection will be BF crypted\n"); ast_copy_string(p->bc->crypt_key, tmp, sizeof(p->bc->crypt_key)); } else { chan_misdn_log(3, p->bc->port, " --> Connection is without BF encryption\n"); } tmp = pbx_builtin_getvar_helper(ast, "MISDN_DIGITAL_TRANS"); if (!ast_strlen_zero(tmp) && ast_true(tmp)) { chan_misdn_log(1, p->bc->port, " --> Connection is transparent digital\n"); p->bc->nodsp = 1; p->bc->hdlc = 0; p->bc->nojitter = 1; } ast_channel_unlock(ast); p->state = MISDN_CONNECTED; stop_indicate(p); if (ast_strlen_zero(p->bc->connected.number)) { chan_misdn_log(2,p->bc->port," --> empty connected number using dialed number\n"); ast_copy_string(p->bc->connected.number, p->bc->dialed.number, sizeof(p->bc->connected.number)); /* * Use the misdn_set_opt() application to set the presentation * before we answer or you can use the CONECTEDLINE() function * to set everything before using the Answer() application. */ p->bc->connected.presentation = p->bc->presentation; p->bc->connected.screening = 0; /* unscreened */ p->bc->connected.number_type = p->bc->dialed.number_type; p->bc->connected.number_plan = p->bc->dialed.number_plan; } switch (p->bc->outgoing_colp) { case 1:/* restricted */ case 2:/* blocked */ p->bc->connected.presentation = 1;/* restricted */ break; default: break; } #if defined(AST_MISDN_ENHANCEMENTS) if (p->bc->div_leg_3_tx_pending) { p->bc->div_leg_3_tx_pending = 0; /* Send DivertingLegInformation3 */ p->bc->fac_out.Function = Fac_DivertingLegInformation3; p->bc->fac_out.u.DivertingLegInformation3.InvokeID = ++misdn_invoke_id; p->bc->fac_out.u.DivertingLegInformation3.PresentationAllowedIndicator = (p->bc->connected.presentation == 0) ? 1 : 0; print_facility(&p->bc->fac_out, p->bc); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ misdn_lib_send_event(p->bc, EVENT_CONNECT); start_bc_tones(p); return 0; } static int misdn_digit_begin(struct ast_channel *chan, char digit) { /* XXX Modify this callback to support Asterisk controlling the length of DTMF */ return 0; } static int misdn_digit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct chan_list *p; struct misdn_bchannel *bc; char buf[2] = { digit, 0 }; if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) { return -1; } bc = p->bc; chan_misdn_log(1, bc ? bc->port : 0, "* IND : Digit %c\n", digit); if (!bc) { ast_log(LOG_WARNING, " --> !! Got Digit Event without having bchannel Object\n"); return -1; } switch (p->state) { case MISDN_CALLING: if (strlen(bc->infos_pending) < sizeof(bc->infos_pending) - 1) { strncat(bc->infos_pending, buf, sizeof(bc->infos_pending) - strlen(bc->infos_pending) - 1); } break; case MISDN_CALLING_ACKNOWLEDGE: ast_copy_string(bc->info_dad, buf, sizeof(bc->info_dad)); if (strlen(bc->dialed.number) < sizeof(bc->dialed.number) - 1) { strncat(bc->dialed.number, buf, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1); } ast_channel_exten_set(p->ast, bc->dialed.number); misdn_lib_send_event(bc, EVENT_INFORMATION); break; default: if (bc->send_dtmf) { send_digit_to_chan(p, digit); } break; } return 0; } static int misdn_fixup(struct ast_channel *oldast, struct ast_channel *ast) { struct chan_list *p; if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) { return -1; } chan_misdn_log(1, p->bc ? p->bc->port : 0, "* IND: Got Fixup State:%s L3id:%x\n", misdn_get_ch_state(p), p->l3id); p->ast = ast; return 0; } static int misdn_indication(struct ast_channel *ast, int cond, const void *data, size_t datalen) { struct chan_list *p; if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) { ast_log(LOG_WARNING, "Returned -1 in misdn_indication\n"); return -1; } if (!p->bc) { if (p->hold.state == MISDN_HOLD_IDLE) { chan_misdn_log(1, 0, "* IND : Indication [%d] ignored on %s\n", cond, ast_channel_name(ast)); ast_log(LOG_WARNING, "Private Pointer but no bc ?\n"); } else { chan_misdn_log(1, 0, "* IND : Indication [%d] ignored on hold %s\n", cond, ast_channel_name(ast)); } return -1; } chan_misdn_log(5, p->bc->port, "* IND : Indication [%d] on %s\n", cond, ast_channel_name(ast)); switch (cond) { case AST_CONTROL_BUSY: chan_misdn_log(1, p->bc->port, "* IND :\tbusy pid:%d\n", p->bc->pid); ast_setstate(ast, AST_STATE_BUSY); p->bc->out_cause = AST_CAUSE_USER_BUSY; if (p->state != MISDN_CONNECTED) { start_bc_tones(p); misdn_lib_send_event(p->bc, EVENT_DISCONNECT); } return -1; case AST_CONTROL_RING: chan_misdn_log(1, p->bc->port, "* IND :\tring pid:%d\n", p->bc->pid); return -1; case AST_CONTROL_RINGING: chan_misdn_log(1, p->bc->port, "* IND :\tringing pid:%d\n", p->bc->pid); switch (p->state) { case MISDN_ALERTING: chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d but I was Ringing before, so ignoring it\n", p->bc->pid); break; case MISDN_CONNECTED: chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d but Connected, so just send TONE_ALERTING without state changes \n", p->bc->pid); return -1; default: p->state = MISDN_ALERTING; chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d\n", p->bc->pid); misdn_lib_send_event(p->bc, EVENT_ALERTING); chan_misdn_log(3, p->bc->port, " --> * SEND: State Ring pid:%d\n", p->bc->pid); ast_setstate(ast, AST_STATE_RING); if (!p->bc->nt && (p->originator == ORG_MISDN) && !p->incoming_early_audio) { chan_misdn_log(2, p->bc->port, " --> incoming_early_audio off\n"); } else { return -1; } } break; case AST_CONTROL_ANSWER: chan_misdn_log(1, p->bc->port, " --> * IND :\tanswer pid:%d\n", p->bc->pid); start_bc_tones(p); break; case AST_CONTROL_TAKEOFFHOOK: chan_misdn_log(1, p->bc->port, " --> *\ttakeoffhook pid:%d\n", p->bc->pid); return -1; case AST_CONTROL_OFFHOOK: chan_misdn_log(1, p->bc->port, " --> *\toffhook pid:%d\n", p->bc->pid); return -1; case AST_CONTROL_FLASH: chan_misdn_log(1, p->bc->port, " --> *\tflash pid:%d\n", p->bc->pid); break; case AST_CONTROL_PROGRESS: chan_misdn_log(1, p->bc->port, " --> * IND :\tprogress pid:%d\n", p->bc->pid); misdn_lib_send_event(p->bc, EVENT_PROGRESS); break; case AST_CONTROL_PROCEEDING: chan_misdn_log(1, p->bc->port, " --> * IND :\tproceeding pid:%d\n", p->bc->pid); misdn_lib_send_event(p->bc, EVENT_PROCEEDING); break; case AST_CONTROL_INCOMPLETE: chan_misdn_log(1, p->bc->port, " --> *\tincomplete pid:%d\n", p->bc->pid); if (!p->overlap_dial) { /* Overlapped dialing not enabled - send hangup */ p->bc->out_cause = AST_CAUSE_INVALID_NUMBER_FORMAT; start_bc_tones(p); misdn_lib_send_event(p->bc, EVENT_DISCONNECT); if (p->bc->nt) { hanguptone_indicate(p); } } break; case AST_CONTROL_CONGESTION: chan_misdn_log(1, p->bc->port, " --> * IND :\tcongestion pid:%d\n", p->bc->pid); p->bc->out_cause = AST_CAUSE_SWITCH_CONGESTION; start_bc_tones(p); misdn_lib_send_event(p->bc, EVENT_DISCONNECT); if (p->bc->nt) { hanguptone_indicate(p); } break; case -1 : chan_misdn_log(1, p->bc->port, " --> * IND :\t-1! (stop indication) pid:%d\n", p->bc->pid); stop_indicate(p); if (p->state == MISDN_CONNECTED) { start_bc_tones(p); } break; case AST_CONTROL_HOLD: ast_moh_start(ast, data, p->mohinterpret); chan_misdn_log(1, p->bc->port, " --> *\tHOLD pid:%d\n", p->bc->pid); break; case AST_CONTROL_UNHOLD: ast_moh_stop(ast); chan_misdn_log(1, p->bc->port, " --> *\tUNHOLD pid:%d\n", p->bc->pid); break; case AST_CONTROL_CONNECTED_LINE: chan_misdn_log(1, p->bc->port, "* IND :\tconnected line update pid:%d\n", p->bc->pid); misdn_update_connected_line(ast, p->bc, p->originator); break; case AST_CONTROL_REDIRECTING: chan_misdn_log(1, p->bc->port, "* IND :\tredirecting info update pid:%d\n", p->bc->pid); misdn_update_redirecting(ast, p->bc, p->originator); break; default: chan_misdn_log(1, p->bc->port, " --> * Unknown Indication:%d pid:%d\n", cond, p->bc->pid); /* fallthrough */ case AST_CONTROL_PVT_CAUSE_CODE: case AST_CONTROL_MASQUERADE_NOTIFY: return -1; } return 0; } static int misdn_hangup(struct ast_channel *ast) { struct chan_list *p; struct misdn_bchannel *bc; const char *var; if (!ast) { return -1; } ast_debug(1, "misdn_hangup(%s)\n", ast_channel_name(ast)); /* Take the ast_channel's tech_pvt reference. */ ast_mutex_lock(&release_lock); p = MISDN_ASTERISK_TECH_PVT(ast); if (!p) { ast_mutex_unlock(&release_lock); return -1; } MISDN_ASTERISK_TECH_PVT_SET(ast, NULL); if (!misdn_chan_is_valid(p)) { ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference. Was not active?"); return 0; } if (p->hold.state == MISDN_HOLD_IDLE) { bc = p->bc; } else { p->hold.state = MISDN_HOLD_DISCONNECT; bc = misdn_lib_find_held_bc(p->hold.port, p->l3id); if (!bc) { chan_misdn_log(4, p->hold.port, "misdn_hangup: Could not find held bc for (%s)\n", ast_channel_name(ast)); release_chan_early(p); ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference"); return 0; } } if (ast_channel_state(ast) == AST_STATE_RESERVED || p->state == MISDN_NOTHING) { /* between request and call */ ast_debug(1, "State Reserved (or nothing) => chanIsAvail\n"); release_chan_early(p); if (bc) { misdn_lib_release(bc); } ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference"); return 0; } if (!bc) { ast_log(LOG_WARNING, "Hangup with private but no bc ? state:%s l3id:%x\n", misdn_get_ch_state(p), p->l3id); release_chan_early(p); ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference"); return 0; } p->ast = NULL; p->need_hangup = 0; p->need_queue_hangup = 0; p->need_busy = 0; if (!bc->nt) { stop_bc_tones(p); } bc->out_cause = ast_channel_hangupcause(ast) ? ast_channel_hangupcause(ast) : AST_CAUSE_NORMAL_CLEARING; /* Channel lock is already held when we are called. */ //ast_channel_lock(ast); var = pbx_builtin_getvar_helper(ast, "HANGUPCAUSE"); if (!var) { var = pbx_builtin_getvar_helper(ast, "PRI_CAUSE"); } if (var) { int tmpcause; tmpcause = atoi(var); bc->out_cause = tmpcause ? tmpcause : AST_CAUSE_NORMAL_CLEARING; } var = pbx_builtin_getvar_helper(ast, "MISDN_USERUSER"); if (var) { ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", var); ast_copy_string(bc->uu, var, sizeof(bc->uu)); bc->uulen = strlen(bc->uu); } //ast_channel_unlock(ast); chan_misdn_log(1, bc->port, "* IND : HANGUP\tpid:%d context:%s dialed:%s caller:\"%s\" <%s> State:%s\n", bc->pid, ast_channel_context(ast), ast_channel_exten(ast), (ast_channel_caller(ast)->id.name.valid && ast_channel_caller(ast)->id.name.str) ? ast_channel_caller(ast)->id.name.str : "", (ast_channel_caller(ast)->id.number.valid && ast_channel_caller(ast)->id.number.str) ? ast_channel_caller(ast)->id.number.str : "", misdn_get_ch_state(p)); chan_misdn_log(3, bc->port, " --> l3id:%x\n", p->l3id); chan_misdn_log(3, bc->port, " --> cause:%d\n", bc->cause); chan_misdn_log(2, bc->port, " --> out_cause:%d\n", bc->out_cause); switch (p->state) { case MISDN_INCOMING_SETUP: /* * This is the only place in misdn_hangup, where we * can call release_chan, else it might create a lot of trouble. */ ast_log(LOG_NOTICE, "release channel, in INCOMING_SETUP state.. no other events happened\n"); release_chan(p, bc); misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference"); return 0; case MISDN_DIALING: if (p->hold.state == MISDN_HOLD_IDLE) { start_bc_tones(p); hanguptone_indicate(p); } if (bc->need_disconnect) { misdn_lib_send_event(bc, EVENT_DISCONNECT); } break; case MISDN_CALLING_ACKNOWLEDGE: if (p->hold.state == MISDN_HOLD_IDLE) { start_bc_tones(p); hanguptone_indicate(p); } if (bc->need_disconnect) { misdn_lib_send_event(bc, EVENT_DISCONNECT); } break; case MISDN_CALLING: case MISDN_ALERTING: case MISDN_PROGRESS: case MISDN_PROCEEDING: if (p->originator != ORG_AST && p->hold.state == MISDN_HOLD_IDLE) { hanguptone_indicate(p); } if (bc->need_disconnect) { misdn_lib_send_event(bc, EVENT_DISCONNECT); } break; case MISDN_CONNECTED: /* Alerting or Disconnect */ if (bc->nt && p->hold.state == MISDN_HOLD_IDLE) { start_bc_tones(p); hanguptone_indicate(p); bc->progress_indicator = INFO_PI_INBAND_AVAILABLE; } if (bc->need_disconnect) { misdn_lib_send_event(bc, EVENT_DISCONNECT); } break; case MISDN_DISCONNECTED: if (bc->need_release) { misdn_lib_send_event(bc, EVENT_RELEASE); } break; case MISDN_CLEANING: ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference"); return 0; case MISDN_BUSY: break; default: if (bc->nt) { bc->out_cause = -1; if (bc->need_release) { misdn_lib_send_event(bc, EVENT_RELEASE); } } else { if (bc->need_disconnect) { misdn_lib_send_event(bc, EVENT_DISCONNECT); } } break; } p->state = MISDN_CLEANING; chan_misdn_log(3, bc->port, " --> Channel: %s hungup new state:%s\n", ast_channel_name(ast), misdn_get_ch_state(p)); ast_mutex_unlock(&release_lock); chan_list_unref(p, "Release ast_channel reference"); return 0; } static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame) { struct ast_frame *f; if (tmp->dsp) { f = ast_dsp_process(tmp->ast, tmp->dsp, frame); } else { chan_misdn_log(0, tmp->bc->port, "No DSP-Path found\n"); return NULL; } if (!f || (f->frametype != AST_FRAME_DTMF)) { return f; } ast_debug(1, "Detected inband DTMF digit: %c\n", f->subclass.integer); if (tmp->faxdetect && (f->subclass.integer == 'f')) { /* Fax tone -- Handle and return NULL */ if (!tmp->faxhandled) { struct ast_channel *ast = tmp->ast; tmp->faxhandled++; chan_misdn_log(0, tmp->bc->port, "Fax detected, preparing %s for fax transfer.\n", ast_channel_name(ast)); tmp->bc->rxgain = 0; isdn_lib_update_rxgain(tmp->bc); tmp->bc->txgain = 0; isdn_lib_update_txgain(tmp->bc); #ifdef MISDN_1_2 *tmp->bc->pipeline = 0; #else tmp->bc->ec_enable = 0; #endif isdn_lib_update_ec(tmp->bc); isdn_lib_stop_dtmf(tmp->bc); switch (tmp->faxdetect) { case 1: if (strcmp(ast_channel_exten(ast), "fax")) { const char *context; char context_tmp[BUFFERSIZE]; misdn_cfg_get(tmp->bc->port, MISDN_CFG_FAXDETECT_CONTEXT, &context_tmp, sizeof(context_tmp)); context = S_OR(context_tmp, S_OR(ast_channel_macrocontext(ast), ast_channel_context(ast))); if (ast_exists_extension(ast, context, "fax", 1, S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, NULL))) { ast_verb(3, "Redirecting %s to fax extension (context:%s)\n", ast_channel_name(ast), context); /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */ pbx_builtin_setvar_helper(ast,"FAXEXTEN",ast_channel_exten(ast)); if (ast_async_goto(ast, context, "fax", 1)) { ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(ast), context); } } else { ast_log(LOG_NOTICE, "Fax detected but no fax extension, context:%s exten:%s\n", context, ast_channel_exten(ast)); } } else { ast_debug(1, "Already in a fax extension, not redirecting\n"); } break; case 2: ast_verb(3, "Not redirecting %s to fax extension, nojump is set.\n", ast_channel_name(ast)); break; default: break; } } else { ast_debug(1, "Fax already handled\n"); } } if (tmp->ast_dsp && (f->subclass.integer != 'f')) { chan_misdn_log(2, tmp->bc->port, " --> * SEND: DTMF (AST_DSP) :%c\n", f->subclass.integer); } return f; } static struct ast_frame *misdn_read(struct ast_channel *ast) { struct chan_list *tmp; int len, t; struct pollfd pfd = { .fd = -1, .events = POLLIN }; if (!ast) { chan_misdn_log(1, 0, "misdn_read called without ast\n"); return NULL; } if (!(tmp = MISDN_ASTERISK_TECH_PVT(ast))) { chan_misdn_log(1, 0, "misdn_read called without ast->pvt\n"); return NULL; } if (!tmp->bc && tmp->hold.state == MISDN_HOLD_IDLE) { chan_misdn_log(1, 0, "misdn_read called without bc\n"); return NULL; } pfd.fd = tmp->pipe[0]; t = ast_poll(&pfd, 1, 20); if (t < 0) { chan_misdn_log(-1, tmp->bc->port, "poll() error (err=%s)\n", strerror(errno)); return NULL; } if (!t) { chan_misdn_log(3, tmp->bc->port, "poll() timed out\n"); len = 160; } else if (pfd.revents & POLLIN) { len = read(tmp->pipe[0], tmp->ast_rd_buf, sizeof(tmp->ast_rd_buf)); if (len <= 0) { /* we hangup here, since our pipe is closed */ chan_misdn_log(2, tmp->bc->port, "misdn_read: Pipe closed, hanging up\n"); return NULL; } } else { return NULL; } tmp->frame.frametype = AST_FRAME_VOICE; tmp->frame.subclass.format = ast_format_alaw; tmp->frame.datalen = len; tmp->frame.samples = len; tmp->frame.mallocd = 0; tmp->frame.offset = 0; tmp->frame.delivery = ast_tv(0, 0); tmp->frame.src = NULL; tmp->frame.data.ptr = tmp->ast_rd_buf; if (tmp->faxdetect && !tmp->faxhandled) { if (tmp->faxdetect_timeout) { if (ast_tvzero(tmp->faxdetect_tv)) { tmp->faxdetect_tv = ast_tvnow(); chan_misdn_log(2, tmp->bc->port, "faxdetect: starting detection with timeout: %ds ...\n", tmp->faxdetect_timeout); return process_ast_dsp(tmp, &tmp->frame); } else { struct timeval tv_now = ast_tvnow(); int diff = ast_tvdiff_ms(tv_now, tmp->faxdetect_tv); if (diff <= (tmp->faxdetect_timeout * 1000)) { chan_misdn_log(5, tmp->bc->port, "faxdetect: detecting ...\n"); return process_ast_dsp(tmp, &tmp->frame); } else { chan_misdn_log(2, tmp->bc->port, "faxdetect: stopping detection (time ran out) ...\n"); tmp->faxdetect = 0; return &tmp->frame; } } } else { chan_misdn_log(5, tmp->bc->port, "faxdetect: detecting ... (no timeout)\n"); return process_ast_dsp(tmp, &tmp->frame); } } else { if (tmp->ast_dsp) { return process_ast_dsp(tmp, &tmp->frame); } else { return &tmp->frame; } } } static int misdn_write(struct ast_channel *ast, struct ast_frame *frame) { struct chan_list *ch; if (!ast || !(ch = MISDN_ASTERISK_TECH_PVT(ast))) { return -1; } if (ch->hold.state != MISDN_HOLD_IDLE) { chan_misdn_log(7, 0, "misdn_write: Returning because hold active\n"); return 0; } if (!ch->bc) { ast_log(LOG_WARNING, "private but no bc\n"); return -1; } if (ch->notxtone) { chan_misdn_log(7, ch->bc->port, "misdn_write: Returning because notxtone\n"); return 0; } if (!frame->subclass.format) { chan_misdn_log(4, ch->bc->port, "misdn_write: * prods us\n"); return 0; } if (ast_format_cmp(frame->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_NOT_EQUAL) { chan_misdn_log(-1, ch->bc->port, "Got Unsupported Frame with Format:%s\n", ast_format_get_name(frame->subclass.format)); return 0; } if (!frame->samples) { chan_misdn_log(4, ch->bc->port, "misdn_write: zero write\n"); if (!strcmp(frame->src,"ast_prod")) { chan_misdn_log(1, ch->bc->port, "misdn_write: state (%s) prodded.\n", misdn_get_ch_state(ch)); if (ch->ts) { chan_misdn_log(4, ch->bc->port, "Starting Playtones\n"); misdn_lib_tone_generator_start(ch->bc); } return 0; } return -1; } if (!ch->bc->addr) { chan_misdn_log(8, ch->bc->port, "misdn_write: no addr for bc dropping:%d\n", frame->samples); return 0; } #ifdef MISDN_DEBUG { int i; int max = 5 > frame->samples ? frame->samples : 5; ast_debug(1, "write2mISDN %p %d bytes: ", p, frame->samples); for (i = 0; i < max; i++) { ast_debug(1, "%2.2x ", ((char *) frame->data.ptr)[i]); } } #endif switch (ch->bc->bc_state) { case BCHAN_ACTIVATED: case BCHAN_BRIDGED: break; default: if (!ch->dropped_frame_cnt) { chan_misdn_log(5, ch->bc->port, "BC not active (nor bridged) dropping: %d frames addr:%x exten:%s cid:%s ch->state:%s bc_state:%d l3id:%x\n", frame->samples, ch->bc->addr, ast_channel_exten(ast), S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""), misdn_get_ch_state(ch), ch->bc->bc_state, ch->bc->l3_id); } if (++ch->dropped_frame_cnt > 100) { ch->dropped_frame_cnt = 0; chan_misdn_log(5, ch->bc->port, "BC not active (nor bridged) dropping: %d frames addr:%x dropped > 100 frames!\n", frame->samples, ch->bc->addr); } return 0; } chan_misdn_log(9, ch->bc->port, "Sending :%d bytes to MISDN\n", frame->samples); if (!ch->bc->nojitter && misdn_cap_is_speech(ch->bc->capability)) { /* Buffered Transmit (triggered by read from isdn side)*/ if (misdn_jb_fill(ch->jb, frame->data.ptr, frame->samples) < 0) { if (ch->bc->active) { cb_log(0, ch->bc->port, "Misdn Jitterbuffer Overflow.\n"); } } } else { /* transmit without jitterbuffer */ misdn_lib_tx2misdn_frm(ch->bc, frame->data.ptr, frame->samples); } return 0; } #if defined(mISDN_NATIVE_BRIDGING) static enum ast_bridge_result misdn_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms) { struct chan_list *ch1, *ch2; struct ast_channel *carr[2], *who; int to = -1; struct ast_frame *f; int p1_b, p2_b; int bridging; misdn_cfg_get(0, MISDN_GEN_BRIDGING, &bridging, sizeof(bridging)); if (!bridging) { /* Native mISDN bridging globally disabled. */ return AST_BRIDGE_FAILED_NOWARN; } ch1 = get_chan_by_ast(c0); if (!ch1) { return AST_BRIDGE_FAILED; } ch2 = get_chan_by_ast(c1); if (!ch2) { chan_list_unref(ch1, "Failed to find ch2"); return AST_BRIDGE_FAILED; } carr[0] = c0; carr[1] = c1; misdn_cfg_get(ch1->bc->port, MISDN_CFG_BRIDGING, &p1_b, sizeof(p1_b)); misdn_cfg_get(ch2->bc->port, MISDN_CFG_BRIDGING, &p2_b, sizeof(p2_b)); if (!p1_b || !p2_b) { ast_log(LOG_NOTICE, "Falling back to Asterisk bridging\n"); chan_list_unref(ch1, "Bridge fallback ch1"); chan_list_unref(ch2, "Bridge fallback ch2"); return AST_BRIDGE_FAILED_NOWARN; } /* make a mISDN_dsp conference */ chan_misdn_log(1, ch1->bc->port, "I SEND: Making conference with Number:%d\n", ch1->bc->pid + 1); misdn_lib_bridge(ch1->bc, ch2->bc); ast_verb(3, "Native bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1)); chan_misdn_log(1, ch1->bc->port, "* Making Native Bridge between \"%s\" <%s> and \"%s\" <%s>\n", ch1->bc->caller.name, ch1->bc->caller.number, ch2->bc->caller.name, ch2->bc->caller.number); if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0)) { ch1->ignore_dtmf = 1; } if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1)) { ch2->ignore_dtmf = 1; } for (;/*ever*/;) { to = -1; who = ast_waitfor_n(carr, 2, &to); if (!who) { ast_log(LOG_NOTICE, "misdn_bridge: empty read, breaking out\n"); break; } f = ast_read(who); if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass.integer != AST_CONTROL_PVT_CAUSE_CODE)) { /* got hangup .. */ if (!f) { chan_misdn_log(4, ch1->bc->port, "Read Null Frame\n"); } else { chan_misdn_log(4, ch1->bc->port, "Read Frame Control class:%d\n", f->subclass.integer); } *fo = f; *rc = who; break; } if (f->frametype == AST_FRAME_DTMF) { chan_misdn_log(1, 0, "Read DTMF %d from %s\n", f->subclass.integer, ast_channel_exten(who)); *fo = f; *rc = who; break; } #if 0 if (f->frametype == AST_FRAME_VOICE) { chan_misdn_log(1, ch1->bc->port, "I SEND: Splitting conference with Number:%d\n", ch1->bc->pid +1); continue; } #endif if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) { ast_channel_hangupcause_hash_set((who == c0) ? c1 : c0, f->data.ptr, f->datalen); } else { ast_write((who == c0) ? c1 : c0, f); } } chan_misdn_log(1, ch1->bc->port, "I SEND: Splitting conference with Number:%d\n", ch1->bc->pid + 1); misdn_lib_split_bridge(ch1->bc, ch2->bc); chan_list_unref(ch1, "Bridge complete ch1"); chan_list_unref(ch2, "Bridge complete ch2"); return AST_BRIDGE_COMPLETE; } #endif /* defined(mISDN_NATIVE_BRIDGING) */ /** AST INDICATIONS END **/ static int dialtone_indicate(struct chan_list *cl) { struct ast_channel *ast = cl->ast; int nd = 0; if (!ast) { chan_misdn_log(0, cl->bc->port, "No Ast in dialtone_indicate\n"); return -1; } misdn_cfg_get(cl->bc->port, MISDN_CFG_NODIALTONE, &nd, sizeof(nd)); if (nd) { chan_misdn_log(1, cl->bc->port, "Not sending Dialtone, because config wants it\n"); return 0; } chan_misdn_log(3, cl->bc->port, " --> Dial\n"); cl->ts = ast_get_indication_tone(ast_channel_zone(ast), "dial"); if (cl->ts) { cl->notxtone = 0; cl->norxtone = 0; /* This prods us in misdn_write */ ast_playtones_start(ast, 0, cl->ts->data, 0); } return 0; } static void hanguptone_indicate(struct chan_list *cl) { misdn_lib_send_tone(cl->bc, TONE_HANGUP); } static int stop_indicate(struct chan_list *cl) { struct ast_channel *ast = cl->ast; if (!ast) { chan_misdn_log(0, cl->bc->port, "No Ast in stop_indicate\n"); return -1; } chan_misdn_log(3, cl->bc->port, " --> None\n"); misdn_lib_tone_generator_stop(cl->bc); ast_playtones_stop(ast); if (cl->ts) { cl->ts = ast_tone_zone_sound_unref(cl->ts); } return 0; } static int start_bc_tones(struct chan_list* cl) { misdn_lib_tone_generator_stop(cl->bc); cl->notxtone = 0; cl->norxtone = 0; return 0; } static int stop_bc_tones(struct chan_list *cl) { if (!cl) { return -1; } cl->notxtone = 1; cl->norxtone = 1; return 0; } /*! * \internal * \brief Destroy the chan_list object. * * \param obj chan_list object to destroy. * * \return Nothing */ static void chan_list_destructor(void *obj) { struct chan_list *ch = obj; #if defined(AST_MISDN_ENHANCEMENTS) if (ch->peer) { ao2_ref(ch->peer, -1); ch->peer = NULL; } #endif /* AST_MISDN_ENHANCEMENTS */ if (ch->dsp) { ast_dsp_free(ch->dsp); ch->dsp = NULL; } /* releasing jitterbuffer */ if (ch->jb) { misdn_jb_destroy(ch->jb); ch->jb = NULL; } if (ch->overlap_dial) { if (ch->overlap_dial_task != -1) { misdn_tasks_remove(ch->overlap_dial_task); ch->overlap_dial_task = -1; } ast_mutex_destroy(&ch->overlap_tv_lock); } if (-1 < ch->pipe[0]) { close(ch->pipe[0]); } if (-1 < ch->pipe[1]) { close(ch->pipe[1]); } } /*! Returns a reference to the new chan_list. */ static struct chan_list *chan_list_init(int orig) { struct chan_list *cl; cl = ao2_alloc(sizeof(*cl), chan_list_destructor); if (!cl) { chan_misdn_log(-1, 0, "misdn_request: malloc failed!"); return NULL; } cl->originator = orig; cl->need_queue_hangup = 1; cl->need_hangup = 1; cl->need_busy = 1; cl->overlap_dial_task = -1; #if defined(AST_MISDN_ENHANCEMENTS) cl->record_id = -1; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ cl->pipe[0] = -1; cl->pipe[1] = -1; return cl; } static struct ast_channel *misdn_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct ast_channel *ast; char group[BUFFERSIZE + 1] = ""; char dial_str[128]; char *dest_cp; char *p = NULL; int channel = 0; int port = 0; struct misdn_bchannel *newbc = NULL; int dec = 0; #if defined(AST_MISDN_ENHANCEMENTS) int cc_retry_call = 0; /* TRUE if this is a call completion retry call */ long record_id = -1; struct misdn_cc_record *cc_record; const char *err_msg; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ struct chan_list *cl; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(intf); /* interface token */ AST_APP_ARG(ext); /* extension token */ AST_APP_ARG(opts); /* options token */ ); snprintf(dial_str, sizeof(dial_str), "%s/%s", misdn_type, data); /* * data is ---v * Dial(mISDN/g:group_name[/extension[/options]]) * Dial(mISDN/port[:preselected_channel][/extension[/options]]) * Dial(mISDN/cc/cc-record-id) * * The dial extension could be empty if you are using MISDN_KEYPAD * to control ISDN provider features. */ dest_cp = ast_strdupa(data); AST_NONSTANDARD_APP_ARGS(args, dest_cp, '/'); if (!args.ext) { args.ext = ""; } if (!ast_strlen_zero(args.intf)) { if (args.intf[0] == 'g' && args.intf[1] == ':') { /* We make a group call lets checkout which ports are in my group */ args.intf += 2; ast_copy_string(group, args.intf, sizeof(group)); chan_misdn_log(2, 0, " --> Group Call group: %s\n", group); #if defined(AST_MISDN_ENHANCEMENTS) } else if (strcmp(args.intf, "cc") == 0) { cc_retry_call = 1; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } else if ((p = strchr(args.intf, ':'))) { /* we have a preselected channel */ *p++ = 0; channel = atoi(p); port = atoi(args.intf); chan_misdn_log(2, port, " --> Call on preselected Channel (%d).\n", channel); } else { port = atoi(args.intf); } } else { ast_log(LOG_WARNING, " --> ! IND : Dial(%s) WITHOUT Port or Group, check extensions.conf\n", dial_str); return NULL; } #if defined(AST_MISDN_ENHANCEMENTS) if (cc_retry_call) { if (ast_strlen_zero(args.ext)) { ast_log(LOG_WARNING, " --> ! IND : Dial(%s) WITHOUT cc-record-id, check extensions.conf\n", dial_str); return NULL; } if (!isdigit(*args.ext)) { ast_log(LOG_WARNING, " --> ! IND : Dial(%s) cc-record-id must be a number.\n", dial_str); return NULL; } record_id = atol(args.ext); AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (!cc_record) { AST_LIST_UNLOCK(&misdn_cc_records_db); err_msg = misdn_cc_record_not_found; ast_log(LOG_WARNING, " --> ! IND : Dial(%s) %s.\n", dial_str, err_msg); return NULL; } if (!cc_record->activated) { AST_LIST_UNLOCK(&misdn_cc_records_db); err_msg = "Call completion has not been activated"; ast_log(LOG_WARNING, " --> ! IND : Dial(%s) %s.\n", dial_str, err_msg); return NULL; } port = cc_record->port; AST_LIST_UNLOCK(&misdn_cc_records_db); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ if (misdn_cfg_is_group_method(group, METHOD_STANDARD_DEC)) { chan_misdn_log(4, port, " --> STARTING STANDARD DEC...\n"); dec = 1; } if (!ast_strlen_zero(group)) { char cfg_group[BUFFERSIZE + 1]; struct robin_list *rr = NULL; /* Group dial */ if (misdn_cfg_is_group_method(group, METHOD_ROUND_ROBIN)) { chan_misdn_log(4, port, " --> STARTING ROUND ROBIN...\n"); rr = get_robin_position(group); } if (rr) { int port_start; int bchan_start; int port_up; int check; int maxbchans; int wraped = 0; if (!rr->port) { rr->port = misdn_cfg_get_next_port_spin(0); } if (!rr->channel) { rr->channel = 1; } bchan_start = rr->channel; port_start = rr->port; do { misdn_cfg_get(rr->port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group)); if (strcasecmp(cfg_group, group)) { wraped = 1; rr->port = misdn_cfg_get_next_port_spin(rr->port); rr->channel = 1; continue; } misdn_cfg_get(rr->port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(check)); port_up = misdn_lib_port_up(rr->port, check); if (!port_up) { chan_misdn_log(1, rr->port, "L1 is not Up on this Port\n"); rr->port = misdn_cfg_get_next_port_spin(rr->port); rr->channel = 1; } else if (port_up < 0) { ast_log(LOG_WARNING, "This port (%d) is blocked\n", rr->port); rr->port = misdn_cfg_get_next_port_spin(rr->port); rr->channel = 1; } else { chan_misdn_log(4, rr->port, "portup\n"); maxbchans = misdn_lib_get_maxchans(rr->port); for (;rr->channel <= maxbchans;rr->channel++) { /* ive come full circle and can stop now */ if (wraped && (rr->port == port_start) && (rr->channel == bchan_start)) { break; } chan_misdn_log(4, rr->port, "Checking channel %d\n", rr->channel); if ((newbc = misdn_lib_get_free_bc(rr->port, rr->channel, 0, 0))) { chan_misdn_log(4, rr->port, " Success! Found port:%d channel:%d\n", newbc->port, newbc->channel); rr->channel++; break; } } if (wraped && (rr->port == port_start) && (rr->channel <= bchan_start)) { break; } else if (!newbc || (rr->channel == maxbchans)) { rr->port = misdn_cfg_get_next_port_spin(rr->port); rr->channel = 1; } } wraped = 1; } while (!newbc && (rr->port > 0)); } else { for (port = misdn_cfg_get_next_port(0); port > 0; port = misdn_cfg_get_next_port(port)) { misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group)); chan_misdn_log(3, port, "Group [%s] Port [%d]\n", group, port); if (!strcasecmp(cfg_group, group)) { int port_up; int check; misdn_cfg_get(port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(check)); port_up = misdn_lib_port_up(port, check); chan_misdn_log(4, port, "portup:%d\n", port_up); if (port_up > 0) { newbc = misdn_lib_get_free_bc(port, 0, 0, dec); if (newbc) { break; } } } } } /* Group dial failed ?*/ if (!newbc) { ast_log(LOG_WARNING, "Could not Dial out on group '%s'.\n" "\tEither the L2 and L1 on all of these ports where DOWN (see 'show application misdn_check_l2l1')\n" "\tOr there was no free channel on none of the ports\n\n", group); return NULL; } } else { /* 'Normal' Port dial * Port dial */ if (channel) { chan_misdn_log(1, port, " --> preselected_channel: %d\n", channel); } newbc = misdn_lib_get_free_bc(port, channel, 0, dec); if (!newbc) { ast_log(LOG_WARNING, "Could not create channel on port:%d for Dial(%s)\n", port, dial_str); return NULL; } } /* create ast_channel and link all the objects together */ cl = chan_list_init(ORG_AST); if (!cl) { misdn_lib_release(newbc); ast_log(LOG_ERROR, "Could not create call record for Dial(%s)\n", dial_str); return NULL; } cl->bc = newbc; ast = misdn_new(cl, AST_STATE_RESERVED, args.ext, NULL, cap, assignedids, requestor, port, channel); if (!ast) { chan_list_unref(cl, "Failed to create a new channel"); misdn_lib_release(newbc); ast_log(LOG_ERROR, "Could not create Asterisk channel for Dial(%s)\n", dial_str); return NULL; } #if defined(AST_MISDN_ENHANCEMENTS) cl->record_id = record_id; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /* register chan in local list */ cl_queue_chan(cl); /* fill in the config into the objects */ read_config(cl); /* important */ cl->need_hangup = 0; chan_list_unref(cl, "Successful misdn_request()"); return ast; } static int misdn_send_text(struct ast_channel *chan, const char *text) { struct chan_list *tmp = MISDN_ASTERISK_TECH_PVT(chan); if (tmp && tmp->bc) { ast_copy_string(tmp->bc->display, text, sizeof(tmp->bc->display)); misdn_lib_send_event(tmp->bc, EVENT_INFORMATION); } else { ast_log(LOG_WARNING, "No chan_list but send_text request?\n"); return -1; } return 0; } static struct ast_channel_tech misdn_tech = { .type = misdn_type, .description = "Channel driver for mISDN Support (Bri/Pri)", .requester = misdn_request, .send_digit_begin = misdn_digit_begin, .send_digit_end = misdn_digit_end, .call = misdn_call, .hangup = misdn_hangup, .answer = misdn_answer, .read = misdn_read, .write = misdn_write, .indicate = misdn_indication, .fixup = misdn_fixup, .send_text = misdn_send_text, .properties = 0, }; static int glob_channel = 0; static void update_name(struct ast_channel *tmp, int port, int c) { int chan_offset = 0; int tmp_port = misdn_cfg_get_next_port(0); char newname[255]; for (; tmp_port > 0; tmp_port = misdn_cfg_get_next_port(tmp_port)) { if (tmp_port == port) { break; } chan_offset += misdn_lib_port_is_pri(tmp_port) ? 30 : 2; } if (c < 0) { c = 0; } snprintf(newname, sizeof(newname), "%s/%d-", misdn_type, chan_offset + c); if (strncmp(ast_channel_name(tmp), newname, strlen(newname))) { snprintf(newname, sizeof(newname), "%s/%d-u%d", misdn_type, chan_offset + c, glob_channel++); ast_change_name(tmp, newname); chan_misdn_log(3, port, " --> updating channel name to [%s]\n", ast_channel_name(tmp)); } } static struct ast_channel *misdn_new(struct chan_list *chlist, int state, char *exten, char *callerid, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int port, int c) { struct ast_format_cap *native; struct ast_channel *tmp; char *cid_name = NULL; char *cid_num = NULL; int chan_offset = 0; int tmp_port = misdn_cfg_get_next_port(0); struct ast_format *tmpfmt; native = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!native) { return NULL; } for (; tmp_port > 0; tmp_port = misdn_cfg_get_next_port(tmp_port)) { if (tmp_port == port) { break; } chan_offset += misdn_lib_port_is_pri(tmp_port) ? 30 : 2; } if (c < 0) { c = 0; } if (callerid) { ast_callerid_parse(callerid, &cid_name, &cid_num); } tmp = ast_channel_alloc(1, state, cid_num, cid_name, "", exten, "", assignedids, requestor, 0, "%s/%s%d-u%d", misdn_type, c ? "" : "tmp", chan_offset + c, glob_channel++); if (tmp) { chan_misdn_log(2, port, " --> * NEW CHANNEL dialed:%s caller:%s\n", exten, callerid); tmpfmt = ast_format_cap_get_format(cap, 0); ast_format_cap_append(native, ast_format_alaw, 0); ast_channel_nativeformats_set(tmp, native); ast_channel_set_writeformat(tmp, tmpfmt); ast_channel_set_rawwriteformat(tmp, tmpfmt); ast_channel_set_readformat(tmp, tmpfmt); ast_channel_set_rawreadformat(tmp, tmpfmt); ao2_ref(tmpfmt, -1); /* Link the channel and private together */ chan_list_ref(chlist, "Give a reference to ast_channel"); MISDN_ASTERISK_TECH_PVT_SET(tmp, chlist); chlist->ast = tmp; ast_channel_tech_set(tmp, &misdn_tech); ast_channel_priority_set(tmp, 1); if (exten) { ast_channel_exten_set(tmp, exten); } else { chan_misdn_log(1, 0, "misdn_new: no exten given.\n"); } if (!ast_strlen_zero(cid_num)) { /* Don't use ast_set_callerid() here because it will * generate a needless NewCallerID event */ ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(cid_num); } if (pipe(chlist->pipe) < 0) { ast_log(LOG_ERROR, "Pipe failed\n"); } ast_channel_set_fd(tmp, 0, chlist->pipe[0]); ast_channel_rings_set(tmp, (state == AST_STATE_RING) ? 1 : 0); ast_jb_configure(tmp, misdn_get_global_jbconf()); ast_channel_unlock(tmp); } else { chan_misdn_log(-1, 0, "Unable to allocate channel structure\n"); } ao2_ref(native, -1); return tmp; } /*! Returns a reference to the found chan_list. */ static struct chan_list *find_chan_by_bc(struct misdn_bchannel *bc) { struct chan_list *help; ast_mutex_lock(&cl_te_lock); for (help = cl_te; help; help = help->next) { if (help->bc == bc) { chan_list_ref(help, "Found chan_list by bc"); ast_mutex_unlock(&cl_te_lock); return help; } } ast_mutex_unlock(&cl_te_lock); chan_misdn_log(6, bc->port, "$$$ find_chan_by_bc: No channel found for dialed:%s caller:\"%s\" <%s>\n", bc->dialed.number, bc->caller.name, bc->caller.number); return NULL; } /*! Returns a reference to the found chan_list. */ static struct chan_list *find_hold_call(struct misdn_bchannel *bc) { struct chan_list *help; if (bc->pri) { return NULL; } chan_misdn_log(6, bc->port, "$$$ find_hold_call: channel:%d dialed:%s caller:\"%s\" <%s>\n", bc->channel, bc->dialed.number, bc->caller.name, bc->caller.number); ast_mutex_lock(&cl_te_lock); for (help = cl_te; help; help = help->next) { chan_misdn_log(4, bc->port, "$$$ find_hold_call: --> hold:%d channel:%d\n", help->hold.state, help->hold.channel); if (help->hold.state == MISDN_HOLD_ACTIVE && help->hold.port == bc->port) { chan_list_ref(help, "Found chan_list hold call"); ast_mutex_unlock(&cl_te_lock); return help; } } ast_mutex_unlock(&cl_te_lock); chan_misdn_log(6, bc->port, "$$$ find_hold_call: No channel found for dialed:%s caller:\"%s\" <%s>\n", bc->dialed.number, bc->caller.name, bc->caller.number); return NULL; } /*! Returns a reference to the found chan_list. */ static struct chan_list *find_hold_call_l3(unsigned long l3_id) { struct chan_list *help; ast_mutex_lock(&cl_te_lock); for (help = cl_te; help; help = help->next) { if (help->hold.state != MISDN_HOLD_IDLE && help->l3id == l3_id) { chan_list_ref(help, "Found chan_list hold call l3"); ast_mutex_unlock(&cl_te_lock); return help; } } ast_mutex_unlock(&cl_te_lock); return NULL; } #define TRANSFER_ON_HELD_CALL_HANGUP 1 #if defined(TRANSFER_ON_HELD_CALL_HANGUP) /*! * \internal * \brief Find a suitable active call to go with a held call so we could try a transfer. * * \param bc B channel record. * * \return Found call record or NULL. * * \note Returns a reference to the found chan_list. * * \note There could be a possibility where we find the wrong active call to transfer. * This concern is mitigated by the fact that there could be at most one other call * on a PTMP BRI link to another device. Maybe the l3_id could help in locating an * active call on the same TEI? */ static struct chan_list *find_hold_active_call(struct misdn_bchannel *bc) { struct chan_list *list; ast_mutex_lock(&cl_te_lock); for (list = cl_te; list; list = list->next) { if (list->hold.state == MISDN_HOLD_IDLE && list->bc && list->bc->port == bc->port && list->ast) { switch (list->state) { case MISDN_PROCEEDING: case MISDN_PROGRESS: case MISDN_ALERTING: case MISDN_CONNECTED: chan_list_ref(list, "Found chan_list hold active call"); ast_mutex_unlock(&cl_te_lock); return list; default: break; } } } ast_mutex_unlock(&cl_te_lock); return NULL; } #endif /* defined(TRANSFER_ON_HELD_CALL_HANGUP) */ static void cl_queue_chan(struct chan_list *chan) { chan_misdn_log(4, chan->bc ? chan->bc->port : 0, "* Queuing chan %p\n", chan); chan_list_ref(chan, "Adding chan_list to list"); ast_mutex_lock(&cl_te_lock); chan->next = NULL; if (!cl_te) { /* List is empty, make head of list. */ cl_te = chan; } else { struct chan_list *help; /* Put at end of list. */ for (help = cl_te; help->next; help = help->next) { } help->next = chan; } ast_mutex_unlock(&cl_te_lock); } static int cl_dequeue_chan(struct chan_list *chan) { int found_it; struct chan_list *help; ast_mutex_lock(&cl_te_lock); if (!cl_te) { /* List is empty. */ ast_mutex_unlock(&cl_te_lock); return 0; } if (cl_te == chan) { /* What we want is the head of the list. */ cl_te = cl_te->next; ast_mutex_unlock(&cl_te_lock); chan_list_unref(chan, "Removed chan_list from list head"); return 1; } found_it = 0; for (help = cl_te; help->next; help = help->next) { if (help->next == chan) { /* Found it in the list. */ help->next = help->next->next; found_it = 1; break; } } ast_mutex_unlock(&cl_te_lock); if (found_it) { chan_list_unref(chan, "Removed chan_list from list"); } return found_it; } /** Channel Queue End **/ static int pbx_start_chan(struct chan_list *ch) { int ret = ast_pbx_start(ch->ast); ch->need_hangup = (ret >= 0) ? 0 : 1; return ret; } static void hangup_chan(struct chan_list *ch, struct misdn_bchannel *bc) { int port = bc->port; if (!ch) { cb_log(1, port, "Cannot hangup chan, no ch\n"); return; } cb_log(5, port, "hangup_chan called\n"); if (ch->need_hangup) { cb_log(2, port, " --> hangup\n"); ch->need_hangup = 0; ch->need_queue_hangup = 0; if (ch->ast && send_cause2ast(ch->ast, bc, ch)) { ast_hangup(ch->ast); } return; } if (!ch->need_queue_hangup) { cb_log(2, port, " --> No need to queue hangup\n"); return; } ch->need_queue_hangup = 0; if (ch->ast) { if (send_cause2ast(ch->ast, bc, ch)) { ast_queue_hangup_with_cause(ch->ast, bc->cause); cb_log(2, port, " --> queue_hangup\n"); } } else { cb_log(1, port, "Cannot hangup chan, no ast\n"); } } /*! * \internal * \brief ISDN asked us to release channel, pendant to misdn_hangup. * * \param ch Call channel record to release. * \param bc Current B channel record associated with ch. * * \return Nothing * * \note The only valid thing to do with ch after calling is to chan_list_unref(ch, ""). */ static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc) { struct ast_channel *ast; chan_misdn_log(5, bc->port, "release_chan: bc with pid:%d l3id: %x\n", bc->pid, bc->l3_id); ast_mutex_lock(&release_lock); for (;;) { ast = ch->ast; if (!ast || !ast_channel_trylock(ast)) { break; } DEADLOCK_AVOIDANCE(&release_lock); } if (!cl_dequeue_chan(ch)) { /* Someone already released it. */ if (ast) { ast_channel_unlock(ast); } ast_mutex_unlock(&release_lock); return; } ch->state = MISDN_CLEANING; ch->ast = NULL; if (ast) { struct chan_list *ast_ch; ast_ch = MISDN_ASTERISK_TECH_PVT(ast); MISDN_ASTERISK_TECH_PVT_SET(ast, NULL); chan_misdn_log(1, bc->port, "* RELEASING CHANNEL pid:%d context:%s dialed:%s caller:\"%s\" <%s>\n", bc->pid, ast_channel_context(ast), ast_channel_exten(ast), S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""), S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, "")); if (ast_channel_state(ast) != AST_STATE_RESERVED) { chan_misdn_log(3, bc->port, " --> Setting AST State to down\n"); ast_setstate(ast, AST_STATE_DOWN); } ast_channel_unlock(ast); if (ast_ch) { chan_list_unref(ast_ch, "Release ast_channel reference."); } } if (ch->originator == ORG_AST) { --misdn_out_calls[bc->port]; } else { --misdn_in_calls[bc->port]; } ast_mutex_unlock(&release_lock); } /*! * \internal * \brief Do everything in release_chan() that makes sense without a bc. * * \param ch Call channel record to release. * * \return Nothing * * \note The only valid thing to do with ch after calling is to chan_list_unref(ch, ""). */ static void release_chan_early(struct chan_list *ch) { struct ast_channel *ast; ast_mutex_lock(&release_lock); for (;;) { ast = ch->ast; if (!ast || !ast_channel_trylock(ast)) { break; } DEADLOCK_AVOIDANCE(&release_lock); } if (!cl_dequeue_chan(ch)) { /* Someone already released it. */ if (ast) { ast_channel_unlock(ast); } ast_mutex_unlock(&release_lock); return; } ch->state = MISDN_CLEANING; ch->ast = NULL; if (ast) { struct chan_list *ast_ch; ast_ch = MISDN_ASTERISK_TECH_PVT(ast); MISDN_ASTERISK_TECH_PVT_SET(ast, NULL); if (ast_channel_state(ast) != AST_STATE_RESERVED) { ast_setstate(ast, AST_STATE_DOWN); } ast_channel_unlock(ast); if (ast_ch) { chan_list_unref(ast_ch, "Release ast_channel reference."); } } if (ch->hold.state != MISDN_HOLD_IDLE) { if (ch->originator == ORG_AST) { --misdn_out_calls[ch->hold.port]; } else { --misdn_in_calls[ch->hold.port]; } } ast_mutex_unlock(&release_lock); } /*! * \internal * \brief Attempt to transfer the active channel party to the held channel party. * * \param active_ch Channel currently connected. * \param held_ch Channel currently on hold. * * \retval 0 on success. * \retval -1 on error. */ static int misdn_attempt_transfer(struct chan_list *active_ch, struct chan_list *held_ch) { int retval; enum ast_transfer_result xfer_res; struct ast_channel *to_target; struct ast_channel *to_transferee; switch (active_ch->state) { case MISDN_PROCEEDING: case MISDN_PROGRESS: case MISDN_ALERTING: case MISDN_CONNECTED: break; default: return -1; } ast_channel_lock_both(held_ch->ast, active_ch->ast); to_target = active_ch->ast; to_transferee = held_ch->ast; chan_misdn_log(1, held_ch->hold.port, "TRANSFERRING %s to %s\n", ast_channel_name(to_transferee), ast_channel_name(to_target)); held_ch->hold.state = MISDN_HOLD_TRANSFER; ast_channel_ref(to_target); ast_channel_ref(to_transferee); ast_channel_unlock(to_target); ast_channel_unlock(to_transferee); retval = 0; xfer_res = ast_bridge_transfer_attended(to_transferee, to_target); if (xfer_res != AST_BRIDGE_TRANSFER_SUCCESS) { retval = -1; } ast_channel_unref(to_target); ast_channel_unref(to_transferee); return retval; } static void do_immediate_setup(struct misdn_bchannel *bc, struct chan_list *ch, struct ast_channel *ast) { char *predial; struct ast_frame fr; predial = ast_strdupa(ast_channel_exten(ast)); ch->state = MISDN_DIALING; if (!ch->noautorespond_on_setup) { if (bc->nt) { misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE); } else { if (misdn_lib_is_ptp(bc->port)) { misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE); } else { misdn_lib_send_event(bc, EVENT_PROCEEDING); } } } else { ch->state = MISDN_INCOMING_SETUP; } chan_misdn_log(1, bc->port, "* Starting Ast context:%s dialed:%s caller:\"%s\" <%s> with 's' extension\n", ast_channel_context(ast), ast_channel_exten(ast), (ast_channel_caller(ast)->id.name.valid && ast_channel_caller(ast)->id.name.str) ? ast_channel_caller(ast)->id.name.str : "", (ast_channel_caller(ast)->id.number.valid && ast_channel_caller(ast)->id.number.str) ? ast_channel_caller(ast)->id.number.str : ""); ast_channel_exten_set(ast, "s"); if (!ast_canmatch_extension(ast, ast_channel_context(ast), ast_channel_exten(ast), 1, bc->caller.number) || pbx_start_chan(ch) < 0) { ast = NULL; bc->out_cause = AST_CAUSE_UNALLOCATED; hangup_chan(ch, bc); hanguptone_indicate(ch); misdn_lib_send_event(bc, bc->nt ? EVENT_RELEASE_COMPLETE : EVENT_DISCONNECT); } while (!ast_strlen_zero(predial)) { fr.frametype = AST_FRAME_DTMF; fr.subclass.integer = *predial; fr.src = NULL; fr.data.ptr = NULL; fr.datalen = 0; fr.samples = 0; fr.mallocd = 0; fr.offset = 0; fr.delivery = ast_tv(0,0); if (ch->ast && MISDN_ASTERISK_TECH_PVT(ch->ast)) { ast_queue_frame(ch->ast, &fr); } predial++; } } /*! * \retval -1 if can hangup after calling. * \retval 0 if cannot hangup after calling. */ static int send_cause2ast(struct ast_channel *ast, struct misdn_bchannel *bc, struct chan_list *ch) { int can_hangup; if (!ast) { chan_misdn_log(1, 0, "send_cause2ast: No Ast\n"); return 0; } if (!bc) { chan_misdn_log(1, 0, "send_cause2ast: No BC\n"); return 0; } if (!ch) { chan_misdn_log(1, 0, "send_cause2ast: No Ch\n"); return 0; } ast_channel_hangupcause_set(ast, bc->cause); can_hangup = -1; switch (bc->cause) { case AST_CAUSE_UNALLOCATED: case AST_CAUSE_NO_ROUTE_TRANSIT_NET: case AST_CAUSE_NO_ROUTE_DESTINATION: case 4: /* Send special information tone */ case AST_CAUSE_NUMBER_CHANGED: case AST_CAUSE_DESTINATION_OUT_OF_ORDER: /* Congestion Cases */ /* * Not Queueing the Congestion anymore, since we want to hear * the inband message * chan_misdn_log(1, bc ? bc->port : 0, " --> * SEND: Queue Congestion pid:%d\n", bc ? bc->pid : -1); ch->state = MISDN_BUSY; ast_queue_control(ast, AST_CONTROL_CONGESTION); */ break; case AST_CAUSE_CALL_REJECTED: case AST_CAUSE_USER_BUSY: ch->state = MISDN_BUSY; if (!ch->need_busy) { chan_misdn_log(1, bc ? bc->port : 0, "Queued busy already\n"); break; } ch->need_busy = 0; chan_misdn_log(1, bc ? bc->port : 0, " --> * SEND: Queue Busy pid:%d\n", bc ? bc->pid : -1); ast_queue_control(ast, AST_CONTROL_BUSY); /* The BUSY is likely to cause a hangup or the user needs to hear it. */ can_hangup = 0; break; } return can_hangup; } /*! \brief Import parameters from the dialplan environment variables */ void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch) { const char *tmp; ast_channel_lock(chan); tmp = pbx_builtin_getvar_helper(chan, "MISDN_ADDRESS_COMPLETE"); if (tmp && (atoi(tmp) == 1)) { bc->sending_complete = 1; } tmp = pbx_builtin_getvar_helper(chan, "MISDN_USERUSER"); if (tmp) { ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", tmp); ast_copy_string(bc->uu, tmp, sizeof(bc->uu)); bc->uulen = strlen(bc->uu); } tmp = pbx_builtin_getvar_helper(chan, "MISDN_KEYPAD"); if (tmp) { ast_copy_string(bc->keypad, tmp, sizeof(bc->keypad)); } ast_channel_unlock(chan); } /*! \brief Export parameters to the dialplan environment variables */ void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch) { char tmp[32]; /* * The only use for MISDN_PID is if there is a problem and you * have to use the "misdn restart pid" CLI command. Otherwise, * the pid is not used by anyone. The internal use of MISDN_PID * has been deleted. */ chan_misdn_log(3, bc->port, " --> EXPORT_PID: pid:%d\n", bc->pid); snprintf(tmp, sizeof(tmp), "%d", bc->pid); pbx_builtin_setvar_helper(chan, "_MISDN_PID", tmp); if (bc->sending_complete) { snprintf(tmp, sizeof(tmp), "%d", bc->sending_complete); pbx_builtin_setvar_helper(chan, "MISDN_ADDRESS_COMPLETE", tmp); } if (bc->urate) { snprintf(tmp, sizeof(tmp), "%d", bc->urate); pbx_builtin_setvar_helper(chan, "MISDN_URATE", tmp); } if (bc->uulen) { pbx_builtin_setvar_helper(chan, "MISDN_USERUSER", bc->uu); } if (!ast_strlen_zero(bc->keypad)) { pbx_builtin_setvar_helper(chan, "MISDN_KEYPAD", bc->keypad); } } int add_in_calls(int port) { int max_in_calls; misdn_cfg_get(port, MISDN_CFG_MAX_IN, &max_in_calls, sizeof(max_in_calls)); misdn_in_calls[port]++; if (max_in_calls >= 0 && max_in_calls < misdn_in_calls[port]) { ast_log(LOG_NOTICE, "Marking Incoming Call on port[%d]\n", port); return misdn_in_calls[port] - max_in_calls; } return 0; } int add_out_calls(int port) { int max_out_calls; misdn_cfg_get(port, MISDN_CFG_MAX_OUT, &max_out_calls, sizeof(max_out_calls)); if (max_out_calls >= 0 && max_out_calls <= misdn_out_calls[port]) { ast_log(LOG_NOTICE, "Rejecting Outgoing Call on port[%d]\n", port); return (misdn_out_calls[port] + 1) - max_out_calls; } misdn_out_calls[port]++; return 0; } static void start_pbx(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan) { if (pbx_start_chan(ch) < 0) { hangup_chan(ch, bc); chan_misdn_log(-1, bc->port, "ast_pbx_start returned <0 in SETUP\n"); if (bc->nt) { hanguptone_indicate(ch); misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); } else { misdn_lib_send_event(bc, EVENT_RELEASE); } } } static void wait_for_digits(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan) { ch->state = MISDN_WAITING4DIGS; misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE); if (bc->nt && !bc->dialed.number[0]) { dialtone_indicate(ch); } } #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Handle the FACILITY CCBSStatusRequest message. * * \param port Logical port number. * \param facility Facility ie contents. * * \return Nothing */ static void misdn_cc_handle_ccbs_status_request(int port, const struct FacParm *facility) { struct misdn_cc_record *cc_record; struct misdn_bchannel dummy; switch (facility->u.CCBSStatusRequest.ComponentType) { case FacComponent_Invoke: /* Build message */ misdn_make_dummy(&dummy, port, 0, misdn_lib_port_is_nt(port), 0); dummy.fac_out.Function = Fac_CCBSStatusRequest; dummy.fac_out.u.CCBSStatusRequest.InvokeID = facility->u.CCBSStatusRequest.InvokeID; dummy.fac_out.u.CCBSStatusRequest.ComponentType = FacComponent_Result; /* Answer User-A free question */ AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_reference(port, facility->u.CCBSStatusRequest.Component.Invoke.CCBSReference); if (cc_record) { dummy.fac_out.u.CCBSStatusRequest.Component.Result.Free = cc_record->party_a_free; } else { /* No record so say User-A is free */ dummy.fac_out.u.CCBSStatusRequest.Component.Result.Free = 1; } AST_LIST_UNLOCK(&misdn_cc_records_db); /* Send message */ print_facility(&dummy.fac_out, &dummy); misdn_lib_send_event(&dummy, EVENT_FACILITY); break; default: chan_misdn_log(0, port, " --> not yet handled: facility type:0x%04X\n", facility->Function); break; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Start a PBX to notify that User-B is available. * * \param record_id Call completion record ID * \param notify Dialplan location to start processing. * * \return Nothing */ static void misdn_cc_pbx_notify(long record_id, const struct misdn_cc_notify *notify) { struct ast_channel *chan; char id_str[32]; static unsigned short sequence = 0; /* Create a channel to notify with */ snprintf(id_str, sizeof(id_str), "%ld", record_id); chan = ast_channel_alloc(0, AST_STATE_DOWN, id_str, NULL, NULL, notify->exten, notify->context, NULL, 0, "mISDN-CC/%ld-%X", record_id, (unsigned) ++sequence); if (!chan) { ast_log(LOG_ERROR, "Unable to allocate channel!\n"); return; } ast_channel_priority_set(chan, notify->priority); ast_free(ast_channel_dialed(chan)->number.str); ast_channel_dialed(chan)->number.str = ast_strdup(notify->exten); ast_channel_unlock(chan); if (ast_pbx_start(chan)) { ast_log(LOG_WARNING, "Unable to start pbx channel %s!\n", ast_channel_name(chan)); ast_channel_release(chan); } else { ast_verb(1, "Started pbx for call completion notify channel %s\n", ast_channel_name(chan)); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Handle the FACILITY CCBS_T_RemoteUserFree message. * * \param bc B channel control structure message came in on * * \return Nothing */ static void misdn_cc_handle_T_remote_user_free(struct misdn_bchannel *bc) { struct misdn_cc_record *cc_record; struct misdn_cc_notify notify; long record_id; AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_bc(bc); if (cc_record) { if (cc_record->party_a_free) { notify = cc_record->remote_user_free; } else { /* Send CCBS_T_Suspend message */ bc->fac_out.Function = Fac_CCBS_T_Suspend; bc->fac_out.u.CCBS_T_Suspend.InvokeID = ++misdn_invoke_id; print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); notify = cc_record->b_free; } record_id = cc_record->record_id; AST_LIST_UNLOCK(&misdn_cc_records_db); if (notify.context[0]) { /* Party A is free or B-Free notify has been setup. */ misdn_cc_pbx_notify(record_id, ¬ify); } } else { AST_LIST_UNLOCK(&misdn_cc_records_db); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Handle the FACILITY CCBSRemoteUserFree message. * * \param port Logical port number. * \param facility Facility ie contents. * * \return Nothing */ static void misdn_cc_handle_remote_user_free(int port, const struct FacParm *facility) { struct misdn_cc_record *cc_record; struct misdn_cc_notify notify; long record_id; AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_reference(port, facility->u.CCBSRemoteUserFree.CCBSReference); if (cc_record) { notify = cc_record->remote_user_free; record_id = cc_record->record_id; AST_LIST_UNLOCK(&misdn_cc_records_db); misdn_cc_pbx_notify(record_id, ¬ify); } else { AST_LIST_UNLOCK(&misdn_cc_records_db); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Handle the FACILITY CCBSBFree message. * * \param port Logical port number. * \param facility Facility ie contents. * * \return Nothing */ static void misdn_cc_handle_b_free(int port, const struct FacParm *facility) { struct misdn_cc_record *cc_record; struct misdn_cc_notify notify; long record_id; AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_reference(port, facility->u.CCBSBFree.CCBSReference); if (cc_record && cc_record->b_free.context[0]) { /* B-Free notify has been setup. */ notify = cc_record->b_free; record_id = cc_record->record_id; AST_LIST_UNLOCK(&misdn_cc_records_db); misdn_cc_pbx_notify(record_id, ¬ify); } else { AST_LIST_UNLOCK(&misdn_cc_records_db); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /*! * \internal * \brief Handle the incoming facility ie contents * * \param event Message type facility ie came in on * \param bc B channel control structure message came in on * \param ch Associated channel call record * * \return Nothing */ static void misdn_facility_ie_handler(enum event_e event, struct misdn_bchannel *bc, struct chan_list *ch) { #if defined(AST_MISDN_ENHANCEMENTS) const char *diagnostic_msg; struct misdn_cc_record *cc_record; char buf[32]; struct misdn_party_id party_id; long new_record_id; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ print_facility(&bc->fac_in, bc); switch (bc->fac_in.Function) { #if defined(AST_MISDN_ENHANCEMENTS) case Fac_ActivationDiversion: switch (bc->fac_in.u.ActivationDiversion.ComponentType) { case FacComponent_Result: /* Positive ACK to activation */ /* We don't handle this yet */ break; default: chan_misdn_log(0, bc->port," --> not yet handled: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_DeactivationDiversion: switch (bc->fac_in.u.DeactivationDiversion.ComponentType) { case FacComponent_Result: /* Positive ACK to deactivation */ /* We don't handle this yet */ break; default: chan_misdn_log(0, bc->port," --> not yet handled: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_ActivationStatusNotificationDiv: /* Sent to other MSN numbers on the line when a user activates call forwarding. */ /* Sent in the first call control message of an outgoing call from the served user. */ /* We do not have anything to do for this message. */ break; case Fac_DeactivationStatusNotificationDiv: /* Sent to other MSN numbers on the line when a user deactivates call forwarding. */ /* We do not have anything to do for this message. */ break; #if 0 /* We don't handle this yet */ case Fac_InterrogationDiversion: /* We don't handle this yet */ break; case Fac_InterrogateServedUserNumbers: /* We don't handle this yet */ break; #endif /* We don't handle this yet */ case Fac_DiversionInformation: /* Sent to the served user when a call is forwarded. */ /* We do not have anything to do for this message. */ break; case Fac_CallDeflection: if (ch && ch->ast) { switch (bc->fac_in.u.CallDeflection.ComponentType) { case FacComponent_Invoke: ast_copy_string(bc->redirecting.from.number, bc->dialed.number, sizeof(bc->redirecting.from.number)); bc->redirecting.from.name[0] = 0; bc->redirecting.from.number_plan = bc->dialed.number_plan; bc->redirecting.from.number_type = bc->dialed.number_type; bc->redirecting.from.screening = 0;/* Unscreened */ if (bc->fac_in.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent) { bc->redirecting.from.presentation = bc->fac_in.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser ? 0 /* Allowed */ : 1 /* Restricted */; } else { bc->redirecting.from.presentation = 0;/* Allowed */ } /* Add configured prefix to the call deflection number */ memset(&party_id, 0, sizeof(party_id)); misdn_PartyNumber_extract(&party_id, &bc->fac_in.u.CallDeflection.Component.Invoke.Deflection.Party); misdn_add_number_prefix(bc->port, party_id.number_type, party_id.number, sizeof(party_id.number)); //party_id.presentation = 0;/* Allowed */ //party_id.screening = 0;/* Unscreened */ bc->redirecting.to = party_id; ++bc->redirecting.count; bc->redirecting.reason = mISDN_REDIRECTING_REASON_DEFLECTION; misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag); ast_channel_call_forward_set(ch->ast, bc->redirecting.to.number); /* Send back positive ACK */ #if 1 /* * Since there are no return result arguments it must be a * generic result message. ETSI 300-196 */ bc->fac_out.Function = Fac_RESULT; bc->fac_out.u.RESULT.InvokeID = bc->fac_in.u.CallDeflection.InvokeID; #else bc->fac_out.Function = Fac_CallDeflection; bc->fac_out.u.CallDeflection.InvokeID = bc->fac_in.u.CallDeflection.InvokeID; bc->fac_out.u.CallDeflection.ComponentType = FacComponent_Result; #endif print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_DISCONNECT); /* This line is BUSY to further attempts by this dialing attempt. */ ast_queue_control(ch->ast, AST_CONTROL_BUSY); break; case FacComponent_Result: /* Positive ACK to call deflection */ /* * Sent in DISCONNECT or FACILITY message depending upon network option. * It is in the FACILITY message if the call is still offered to the user * while trying to alert the deflected to party. */ /* Ignore the ACK */ break; default: break; } } break; #if 0 /* We don't handle this yet */ case Fac_CallRerouteing: /* Private-Public ISDN interworking message */ /* We don't handle this yet */ break; #endif /* We don't handle this yet */ case Fac_DivertingLegInformation1: /* Private-Public ISDN interworking message */ bc->div_leg_3_rx_wanted = 0; if (ch && ch->ast) { bc->redirecting.reason = diversion_reason_to_misdn(bc->fac_in.u.DivertingLegInformation1.DiversionReason); if (bc->fac_in.u.DivertingLegInformation1.DivertedToPresent) { misdn_PresentedNumberUnscreened_extract(&bc->redirecting.to, &bc->fac_in.u.DivertingLegInformation1.DivertedTo); /* Add configured prefix to redirecting.to.number */ misdn_add_number_prefix(bc->port, bc->redirecting.to.number_type, bc->redirecting.to.number, sizeof(bc->redirecting.to.number)); } else { bc->redirecting.to.number[0] = '\0'; bc->redirecting.to.number_plan = NUMPLAN_ISDN; bc->redirecting.to.number_type = NUMTYPE_UNKNOWN; bc->redirecting.to.presentation = 1;/* restricted */ bc->redirecting.to.screening = 0;/* unscreened */ } misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag); bc->div_leg_3_rx_wanted = 1; } break; case Fac_DivertingLegInformation2: /* Private-Public ISDN interworking message */ switch (event) { case EVENT_SETUP: /* Comes in on a SETUP with redirecting.from information */ bc->div_leg_3_tx_pending = 1; if (ch && ch->ast) { /* * Setup the redirecting.to informtion so we can identify * if the user wants to manually supply the COLR for this * redirected to number if further redirects could happen. * * All the user needs to do is set the REDIRECTING(to-pres) * to the COLR and REDIRECTING(to-num) = ${EXTEN} to be safe * after determining that the incoming call was redirected by * checking if there is a REDIRECTING(from-num). */ ast_copy_string(bc->redirecting.to.number, bc->dialed.number, sizeof(bc->redirecting.to.number)); bc->redirecting.to.number_plan = bc->dialed.number_plan; bc->redirecting.to.number_type = bc->dialed.number_type; bc->redirecting.to.presentation = 1;/* restricted */ bc->redirecting.to.screening = 0;/* unscreened */ bc->redirecting.reason = diversion_reason_to_misdn(bc->fac_in.u.DivertingLegInformation2.DiversionReason); bc->redirecting.count = bc->fac_in.u.DivertingLegInformation2.DiversionCounter; if (bc->fac_in.u.DivertingLegInformation2.DivertingPresent) { /* This information is redundant if there was a redirecting ie in the SETUP. */ misdn_PresentedNumberUnscreened_extract(&bc->redirecting.from, &bc->fac_in.u.DivertingLegInformation2.Diverting); /* Add configured prefix to redirecting.from.number */ misdn_add_number_prefix(bc->port, bc->redirecting.from.number_type, bc->redirecting.from.number, sizeof(bc->redirecting.from.number)); } #if 0 if (bc->fac_in.u.DivertingLegInformation2.OriginalCalledPresent) { /* We have no place to put the OriginalCalled number */ } #endif misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag); } break; default: chan_misdn_log(0, bc->port," --> Expected in a SETUP message: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_DivertingLegInformation3: /* Private-Public ISDN interworking message */ if (bc->div_leg_3_rx_wanted) { bc->div_leg_3_rx_wanted = 0; if (ch && ch->ast) { struct ast_party_redirecting redirecting; ast_channel_redirecting(ch->ast)->to.number.presentation = bc->fac_in.u.DivertingLegInformation3.PresentationAllowedIndicator ? AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_UNSCREENED : AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED; ast_party_redirecting_init(&redirecting); ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(ch->ast)); /* * Reset any earlier private redirecting id representations and * make sure that it is invalidated at the remote end. */ ast_party_id_reset(&redirecting.priv_orig); ast_party_id_reset(&redirecting.priv_from); ast_party_id_reset(&redirecting.priv_to); ast_channel_queue_redirecting_update(ch->ast, &redirecting, NULL); ast_party_redirecting_free(&redirecting); } } break; #else /* !defined(AST_MISDN_ENHANCEMENTS) */ case Fac_CD: if (ch && ch->ast) { ast_copy_string(bc->redirecting.from.number, bc->dialed.number, sizeof(bc->redirecting.from.number)); bc->redirecting.from.name[0] = 0; bc->redirecting.from.number_plan = bc->dialed.number_plan; bc->redirecting.from.number_type = bc->dialed.number_type; bc->redirecting.from.screening = 0;/* Unscreened */ bc->redirecting.from.presentation = bc->fac_in.u.CDeflection.PresentationAllowed ? 0 /* Allowed */ : 1 /* Restricted */; ast_copy_string(bc->redirecting.to.number, (char *) bc->fac_in.u.CDeflection.DeflectedToNumber, sizeof(bc->redirecting.to.number)); bc->redirecting.to.name[0] = 0; bc->redirecting.to.number_plan = NUMPLAN_UNKNOWN; bc->redirecting.to.number_type = NUMTYPE_UNKNOWN; bc->redirecting.to.presentation = 0;/* Allowed */ bc->redirecting.to.screening = 0;/* Unscreened */ ++bc->redirecting.count; bc->redirecting.reason = mISDN_REDIRECTING_REASON_DEFLECTION; misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag); ast_channel_call_forward_set(ch->ast, bc->redirecting.to.number); misdn_lib_send_event(bc, EVENT_DISCONNECT); /* This line is BUSY to further attempts by this dialing attempt. */ ast_queue_control(ch->ast, AST_CONTROL_BUSY); } break; #endif /* !defined(AST_MISDN_ENHANCEMENTS) */ case Fac_AOCDCurrency: if (ch && ch->ast) { bc->AOCDtype = Fac_AOCDCurrency; memcpy(&bc->AOCD.currency, &bc->fac_in.u.AOCDcur, sizeof(bc->AOCD.currency)); bc->AOCD_need_export = 1; export_aoc_vars(ch->originator, ch->ast, bc); } break; case Fac_AOCDChargingUnit: if (ch && ch->ast) { bc->AOCDtype = Fac_AOCDChargingUnit; memcpy(&bc->AOCD.chargingUnit, &bc->fac_in.u.AOCDchu, sizeof(bc->AOCD.chargingUnit)); bc->AOCD_need_export = 1; export_aoc_vars(ch->originator, ch->ast, bc); } break; #if defined(AST_MISDN_ENHANCEMENTS) case Fac_ERROR: diagnostic_msg = misdn_to_str_error_code(bc->fac_in.u.ERROR.errorValue); chan_misdn_log(1, bc->port, " --> Facility error code: %s\n", diagnostic_msg); switch (event) { case EVENT_DISCONNECT: case EVENT_RELEASE: case EVENT_RELEASE_COMPLETE: /* Possible call failure as a result of Fac_CCBSCall/Fac_CCBS_T_Call */ if (ch && ch->peer) { misdn_cc_set_peer_var(ch->peer, MISDN_ERROR_MSG, diagnostic_msg); } break; default: break; } AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.ERROR.invokeId); if (cc_record) { cc_record->outstanding_message = 0; cc_record->error_code = bc->fac_in.u.ERROR.errorValue; } AST_LIST_UNLOCK(&misdn_cc_records_db); break; case Fac_REJECT: diagnostic_msg = misdn_to_str_reject_code(bc->fac_in.u.REJECT.Code); chan_misdn_log(1, bc->port, " --> Facility reject code: %s\n", diagnostic_msg); switch (event) { case EVENT_DISCONNECT: case EVENT_RELEASE: case EVENT_RELEASE_COMPLETE: /* Possible call failure as a result of Fac_CCBSCall/Fac_CCBS_T_Call */ if (ch && ch->peer) { misdn_cc_set_peer_var(ch->peer, MISDN_ERROR_MSG, diagnostic_msg); } break; default: break; } if (bc->fac_in.u.REJECT.InvokeIDPresent) { AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.REJECT.InvokeID); if (cc_record) { cc_record->outstanding_message = 0; cc_record->reject_code = bc->fac_in.u.REJECT.Code; } AST_LIST_UNLOCK(&misdn_cc_records_db); } break; case Fac_RESULT: AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.RESULT.InvokeID); if (cc_record) { cc_record->outstanding_message = 0; } AST_LIST_UNLOCK(&misdn_cc_records_db); break; #if 0 /* We don't handle this yet */ case Fac_EctExecute: /* We don't handle this yet */ break; case Fac_ExplicitEctExecute: /* We don't handle this yet */ break; case Fac_EctLinkIdRequest: /* We don't handle this yet */ break; #endif /* We don't handle this yet */ case Fac_SubaddressTransfer: /* We do not have anything to do for this message since we do not handle subaddreses. */ break; case Fac_RequestSubaddress: /* * We do not have anything to do for this message since we do not handle subaddreses. * However, we do care about some other ie's that should be present. */ if (bc->redirecting.to_changed) { /* Add configured prefix to redirecting.to.number */ misdn_add_number_prefix(bc->port, bc->redirecting.to.number_type, bc->redirecting.to.number, sizeof(bc->redirecting.to.number)); } switch (bc->notify_description_code) { case mISDN_NOTIFY_CODE_INVALID: /* Notify ie was not present. */ bc->redirecting.to_changed = 0; break; case mISDN_NOTIFY_CODE_CALL_TRANSFER_ALERTING: /* * It would be preferable to update the connected line information * only when the message callStatus is active. However, the * optional redirection number may not be present in the active * message if an alerting message were received earlier. * * The consequences if we wind up sending two updates is benign. * The other end will think that it got transferred twice. */ if (!bc->redirecting.to_changed) { break; } bc->redirecting.to_changed = 0; if (!ch || !ch->ast) { break; } misdn_update_remote_party(ch->ast, &bc->redirecting.to, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, bc->incoming_cid_tag); break; case mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE: if (!bc->redirecting.to_changed) { break; } bc->redirecting.to_changed = 0; if (!ch || !ch->ast) { break; } misdn_update_remote_party(ch->ast, &bc->redirecting.to, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, bc->incoming_cid_tag); break; default: bc->redirecting.to_changed = 0; chan_misdn_log(0, bc->port," --> not yet handled: notify code:0x%02X\n", bc->notify_description_code); break; } bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; break; case Fac_EctInform: /* Private-Public ISDN interworking message */ if (ch && ch->ast && bc->fac_in.u.EctInform.RedirectionPresent) { /* Add configured prefix to the redirection number */ memset(&party_id, 0, sizeof(party_id)); misdn_PresentedNumberUnscreened_extract(&party_id, &bc->fac_in.u.EctInform.Redirection); misdn_add_number_prefix(bc->port, party_id.number_type, party_id.number, sizeof(party_id.number)); /* * It would be preferable to update the connected line information * only when the message callStatus is active. However, the * optional redirection number may not be present in the active * message if an alerting message were received earlier. * * The consequences if we wind up sending two updates is benign. * The other end will think that it got transferred twice. */ misdn_update_remote_party(ch->ast, &party_id, (bc->fac_in.u.EctInform.Status == 0 /* alerting */) ? AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING : AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, bc->incoming_cid_tag); } break; #if 0 /* We don't handle this yet */ case Fac_EctLoopTest: /* The use of this message is unclear on how it works to detect loops. */ /* We don't handle this yet */ break; #endif /* We don't handle this yet */ case Fac_CallInfoRetain: switch (event) { case EVENT_ALERTING: case EVENT_DISCONNECT: /* CCBS/CCNR is available */ if (ch && ch->peer) { AST_LIST_LOCK(&misdn_cc_records_db); if (ch->record_id == -1) { cc_record = misdn_cc_new(); } else { /* * We are doing a call-completion attempt * or the switch is sending us extra call-completion * availability indications (erroneously?). * * Assume that the network request retention option * is not on and that the current call-completion * request is disabled. */ cc_record = misdn_cc_find_by_id(ch->record_id); if (cc_record) { if (cc_record->ptp && cc_record->mode.ptp.bc) { /* * What? We are getting mixed messages from the * switch. We are currently setup for * point-to-point. Now we are switching to * point-to-multipoint. * * Close the call-completion signaling link */ cc_record->mode.ptp.bc->fac_out.Function = Fac_None; cc_record->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(cc_record->mode.ptp.bc, EVENT_RELEASE_COMPLETE); } /* * Resetup the existing record for a possible new * call-completion request. */ new_record_id = misdn_cc_record_id_new(); if (new_record_id < 0) { /* Looks like we must keep the old id anyway. */ } else { cc_record->record_id = new_record_id; ch->record_id = new_record_id; } cc_record->ptp = 0; cc_record->port = bc->port; memset(&cc_record->mode, 0, sizeof(cc_record->mode)); cc_record->mode.ptmp.linkage_id = bc->fac_in.u.CallInfoRetain.CallLinkageID; cc_record->invoke_id = ++misdn_invoke_id; cc_record->activated = 0; cc_record->outstanding_message = 0; cc_record->activation_requested = 0; cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; memset(&cc_record->remote_user_free, 0, sizeof(cc_record->remote_user_free)); memset(&cc_record->b_free, 0, sizeof(cc_record->b_free)); cc_record->time_created = time(NULL); cc_record = NULL; } else { /* * Where did the record go? We will have to recapture * the call setup information. Unfortunately, some * setup information may have been changed. */ ch->record_id = -1; cc_record = misdn_cc_new(); } } if (cc_record) { ch->record_id = cc_record->record_id; cc_record->ptp = 0; cc_record->port = bc->port; cc_record->mode.ptmp.linkage_id = bc->fac_in.u.CallInfoRetain.CallLinkageID; /* Record call information for possible call-completion attempt. */ cc_record->redial.caller = bc->caller; cc_record->redial.dialed = bc->dialed; cc_record->redial.setup_bc_hlc_llc = bc->setup_bc_hlc_llc; cc_record->redial.capability = bc->capability; cc_record->redial.hdlc = bc->hdlc; } AST_LIST_UNLOCK(&misdn_cc_records_db); /* Set MISDN_CC_RECORD_ID in original channel */ if (ch->record_id != -1) { snprintf(buf, sizeof(buf), "%ld", ch->record_id); } else { buf[0] = 0; } misdn_cc_set_peer_var(ch->peer, MISDN_CC_RECORD_ID, buf); } break; default: chan_misdn_log(0, bc->port, " --> Expected in a DISCONNECT or ALERTING message: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_CCBS_T_Call: case Fac_CCBSCall: switch (event) { case EVENT_SETUP: /* * This is a call completion retry call. * If we had anything to do we would do it here. */ break; default: chan_misdn_log(0, bc->port, " --> Expected in a SETUP message: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_CCBSDeactivate: switch (bc->fac_in.u.CCBSDeactivate.ComponentType) { case FacComponent_Result: AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.CCBSDeactivate.InvokeID); if (cc_record) { cc_record->outstanding_message = 0; } AST_LIST_UNLOCK(&misdn_cc_records_db); break; default: chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_CCBSErase: AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_reference(bc->port, bc->fac_in.u.CCBSErase.CCBSReference); if (cc_record) { misdn_cc_delete(cc_record); } AST_LIST_UNLOCK(&misdn_cc_records_db); break; case Fac_CCBSRemoteUserFree: misdn_cc_handle_remote_user_free(bc->port, &bc->fac_in); break; case Fac_CCBSBFree: misdn_cc_handle_b_free(bc->port, &bc->fac_in); break; case Fac_CCBSStatusRequest: misdn_cc_handle_ccbs_status_request(bc->port, &bc->fac_in); break; case Fac_EraseCallLinkageID: AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_linkage(bc->port, bc->fac_in.u.EraseCallLinkageID.CallLinkageID); if (cc_record && !cc_record->activation_requested) { /* * The T-RETENTION timer expired before we requested * call completion activation. Call completion is no * longer available. */ misdn_cc_delete(cc_record); } AST_LIST_UNLOCK(&misdn_cc_records_db); break; case Fac_CCBSStopAlerting: /* We do not have anything to do for this message. */ break; case Fac_CCBSRequest: case Fac_CCNRRequest: switch (bc->fac_in.u.CCBSRequest.ComponentType) { case FacComponent_Result: AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.CCBSRequest.InvokeID); if (cc_record && !cc_record->ptp) { cc_record->outstanding_message = 0; cc_record->activated = 1; cc_record->mode.ptmp.recall_mode = bc->fac_in.u.CCBSRequest.Component.Result.RecallMode; cc_record->mode.ptmp.reference_id = bc->fac_in.u.CCBSRequest.Component.Result.CCBSReference; } AST_LIST_UNLOCK(&misdn_cc_records_db); break; default: chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n", bc->fac_in.Function); break; } break; #if 0 /* We don't handle this yet */ case Fac_CCBSInterrogate: case Fac_CCNRInterrogate: /* We don't handle this yet */ break; case Fac_StatusRequest: /* We don't handle this yet */ break; #endif /* We don't handle this yet */ #if 0 /* We don't handle this yet */ case Fac_CCBS_T_Suspend: case Fac_CCBS_T_Resume: /* We don't handle this yet */ break; #endif /* We don't handle this yet */ case Fac_CCBS_T_RemoteUserFree: misdn_cc_handle_T_remote_user_free(bc); break; case Fac_CCBS_T_Available: switch (event) { case EVENT_ALERTING: case EVENT_DISCONNECT: /* CCBS-T/CCNR-T is available */ if (ch && ch->peer) { int set_id = 1; AST_LIST_LOCK(&misdn_cc_records_db); if (ch->record_id == -1) { cc_record = misdn_cc_new(); } else { /* * We are doing a call-completion attempt * or the switch is sending us extra call-completion * availability indications (erroneously?). */ cc_record = misdn_cc_find_by_id(ch->record_id); if (cc_record) { if (cc_record->ptp && cc_record->mode.ptp.retention_enabled) { /* * Call-completion is still activated. * The user does not have to request it again. */ chan_misdn_log(1, bc->port, " --> Call-completion request retention option is enabled\n"); set_id = 0; } else { if (cc_record->ptp && cc_record->mode.ptp.bc) { /* * The network request retention option * is not on and the current call-completion * request is to be disabled. * * We should get here only if EVENT_DISCONNECT * * Close the call-completion signaling link */ cc_record->mode.ptp.bc->fac_out.Function = Fac_None; cc_record->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(cc_record->mode.ptp.bc, EVENT_RELEASE_COMPLETE); } /* * Resetup the existing record for a possible new * call-completion request. */ new_record_id = misdn_cc_record_id_new(); if (new_record_id < 0) { /* Looks like we must keep the old id anyway. */ } else { cc_record->record_id = new_record_id; ch->record_id = new_record_id; } cc_record->ptp = 1; cc_record->port = bc->port; memset(&cc_record->mode, 0, sizeof(cc_record->mode)); cc_record->invoke_id = ++misdn_invoke_id; cc_record->activated = 0; cc_record->outstanding_message = 0; cc_record->activation_requested = 0; cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; memset(&cc_record->remote_user_free, 0, sizeof(cc_record->remote_user_free)); memset(&cc_record->b_free, 0, sizeof(cc_record->b_free)); cc_record->time_created = time(NULL); } cc_record = NULL; } else { /* * Where did the record go? We will have to recapture * the call setup information. Unfortunately, some * setup information may have been changed. */ ch->record_id = -1; cc_record = misdn_cc_new(); } } if (cc_record) { ch->record_id = cc_record->record_id; cc_record->ptp = 1; cc_record->port = bc->port; /* Record call information for possible call-completion attempt. */ cc_record->redial.caller = bc->caller; cc_record->redial.dialed = bc->dialed; cc_record->redial.setup_bc_hlc_llc = bc->setup_bc_hlc_llc; cc_record->redial.capability = bc->capability; cc_record->redial.hdlc = bc->hdlc; } AST_LIST_UNLOCK(&misdn_cc_records_db); /* Set MISDN_CC_RECORD_ID in original channel */ if (ch->record_id != -1 && set_id) { snprintf(buf, sizeof(buf), "%ld", ch->record_id); } else { buf[0] = 0; } misdn_cc_set_peer_var(ch->peer, MISDN_CC_RECORD_ID, buf); } break; default: chan_misdn_log(0, bc->port, " --> Expected in a DISCONNECT or ALERTING message: facility type:0x%04X\n", bc->fac_in.Function); break; } break; case Fac_CCBS_T_Request: case Fac_CCNR_T_Request: switch (bc->fac_in.u.CCBS_T_Request.ComponentType) { case FacComponent_Result: AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.CCBS_T_Request.InvokeID); if (cc_record && cc_record->ptp) { cc_record->outstanding_message = 0; cc_record->activated = 1; cc_record->mode.ptp.retention_enabled = cc_record->mode.ptp.requested_retention ? bc->fac_in.u.CCBS_T_Request.Component.Result.RetentionSupported ? 1 : 0 : 0; } AST_LIST_UNLOCK(&misdn_cc_records_db); break; case FacComponent_Invoke: /* We cannot be User-B in ptp mode. */ default: chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n", bc->fac_in.Function); break; } break; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ case Fac_None: break; default: chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n", bc->fac_in.Function); break; } } /*! * \internal * \brief Determine if the given dialed party matches our MSN. * \since 1.8 * * \param port ISDN port * \param dialed Dialed party information of incoming call. * * \retval non-zero if MSN is valid. * \retval 0 if MSN invalid. */ static int misdn_is_msn_valid(int port, const struct misdn_party_dialing *dialed) { char number[sizeof(dialed->number)]; ast_copy_string(number, dialed->number, sizeof(number)); misdn_add_number_prefix(port, dialed->number_type, number, sizeof(number)); return misdn_cfg_is_msn_valid(port, number); } /************************************************************/ /* Receive Events from isdn_lib here */ /************************************************************/ static enum event_response_e cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data) { #if defined(AST_MISDN_ENHANCEMENTS) struct misdn_cc_record *cc_record; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ struct chan_list *held_ch; struct chan_list *ch = find_chan_by_bc(bc); if (event != EVENT_BCHAN_DATA && event != EVENT_TONE_GENERATE) { int debuglevel = 1; /* Debug Only Non-Bchan */ if (event == EVENT_CLEANUP && !user_data) { debuglevel = 5; } chan_misdn_log(debuglevel, bc->port, "I IND :%s caller:\"%s\" <%s> dialed:%s pid:%d state:%s\n", manager_isdn_get_info(event), bc->caller.name, bc->caller.number, bc->dialed.number, bc->pid, ch ? misdn_get_ch_state(ch) : "none"); if (debuglevel == 1) { misdn_lib_log_ies(bc); chan_misdn_log(4, bc->port, " --> bc_state:%s\n", bc_state2str(bc->bc_state)); } } if (!ch) { switch(event) { case EVENT_SETUP: case EVENT_DISCONNECT: case EVENT_RELEASE: case EVENT_RELEASE_COMPLETE: case EVENT_PORT_ALARM: case EVENT_RETRIEVE: case EVENT_NEW_BC: case EVENT_FACILITY: case EVENT_REGISTER: break; case EVENT_CLEANUP: case EVENT_TONE_GENERATE: case EVENT_BCHAN_DATA: return -1; default: chan_misdn_log(1, bc->port, "Chan not existing at the moment bc->l3id:%x bc:%p event:%s port:%d channel:%d\n", bc->l3_id, bc, manager_isdn_get_info(event), bc->port, bc->channel); return -1; } } else { switch (event) { case EVENT_TONE_GENERATE: break; case EVENT_DISCONNECT: case EVENT_RELEASE: case EVENT_RELEASE_COMPLETE: case EVENT_CLEANUP: case EVENT_TIMEOUT: if (!ch->ast) { chan_misdn_log(3, bc->port, "ast_hangup already called, so we have no ast ptr anymore in event(%s)\n", manager_isdn_get_info(event)); } break; default: if (!ch->ast || !MISDN_ASTERISK_TECH_PVT(ch->ast)) { if (event != EVENT_BCHAN_DATA) { ast_log(LOG_NOTICE, "No Ast or No private Pointer in Event (%d:%s)\n", event, manager_isdn_get_info(event)); } chan_list_unref(ch, "No Ast or Ast private pointer"); return -1; } break; } } switch (event) { case EVENT_PORT_ALARM: { int boa = 0; misdn_cfg_get(bc->port, MISDN_CFG_ALARM_BLOCK, &boa, sizeof(boa)); if (boa) { cb_log(1, bc->port, " --> blocking\n"); misdn_lib_port_block(bc->port); } } break; case EVENT_BCHAN_ACTIVATED: break; case EVENT_NEW_CHANNEL: update_name(ch->ast,bc->port,bc->channel); break; case EVENT_NEW_L3ID: ch->l3id=bc->l3_id; ch->addr=bc->addr; break; case EVENT_NEW_BC: if (!ch) { ch = find_hold_call(bc); } if (!ch) { ast_log(LOG_WARNING, "NEW_BC without chan_list?\n"); break; } if (bc) { ch->bc = (struct misdn_bchannel *) user_data; } break; case EVENT_DTMF_TONE: { /* sending INFOS as DTMF-Frames :) */ struct ast_frame fr; memset(&fr, 0, sizeof(fr)); fr.frametype = AST_FRAME_DTMF; fr.subclass.integer = bc->dtmf ; fr.src = NULL; fr.data.ptr = NULL; fr.datalen = 0; fr.samples = 0; fr.mallocd = 0; fr.offset = 0; fr.delivery = ast_tv(0,0); if (!ch->ignore_dtmf) { chan_misdn_log(2, bc->port, " --> DTMF:%c\n", bc->dtmf); ast_queue_frame(ch->ast, &fr); } else { chan_misdn_log(2, bc->port, " --> Ignoring DTMF:%c due to bridge flags\n", bc->dtmf); } break; } case EVENT_STATUS: break; case EVENT_INFORMATION: if (ch->state != MISDN_CONNECTED) { stop_indicate(ch); } if (!ch->ast) { break; } if (ch->state == MISDN_WAITING4DIGS) { RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); const char *pickupexten; /* Ok, incomplete Setup, waiting till extension exists */ if (ast_strlen_zero(bc->info_dad) && ! ast_strlen_zero(bc->keypad)) { chan_misdn_log(1, bc->port, " --> using keypad as info\n"); ast_copy_string(bc->info_dad, bc->keypad, sizeof(bc->info_dad)); } strncat(bc->dialed.number, bc->info_dad, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1); ast_channel_exten_set(ch->ast, bc->dialed.number); ast_channel_lock(ch->ast); pickup_cfg = ast_get_chan_features_pickup_config(ch->ast); 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); } ast_channel_unlock(ch->ast); /* Check for Pickup Request first */ if (!strcmp(ast_channel_exten(ch->ast), pickupexten)) { if (ast_pickup_call(ch->ast)) { hangup_chan(ch, bc); } else { ch->state = MISDN_CALLING_ACKNOWLEDGE; hangup_chan(ch, bc); ch->ast = NULL; break; } } if (!ast_canmatch_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) { if (ast_exists_extension(ch->ast, ch->context, "i", 1, bc->caller.number)) { ast_log(LOG_WARNING, "Extension '%s@%s' can never match. Jumping to 'i' extension. port:%d\n", bc->dialed.number, ch->context, bc->port); pbx_builtin_setvar_helper(ch->ast, "INVALID_EXTEN", bc->dialed.number); ast_channel_exten_set(ch->ast, "i"); ch->state = MISDN_DIALING; start_pbx(ch, bc, ch->ast); break; } ast_log(LOG_WARNING, "Extension '%s@%s' can never match. Disconnecting. port:%d\n" "\tMaybe you want to add an 'i' extension to catch this case.\n", bc->dialed.number, ch->context, bc->port); if (bc->nt) { hanguptone_indicate(ch); } ch->state = MISDN_EXTCANTMATCH; bc->out_cause = AST_CAUSE_UNALLOCATED; misdn_lib_send_event(bc, EVENT_DISCONNECT); break; } if (ch->overlap_dial) { ast_mutex_lock(&ch->overlap_tv_lock); ch->overlap_tv = ast_tvnow(); ast_mutex_unlock(&ch->overlap_tv_lock); if (ch->overlap_dial_task == -1) { ch->overlap_dial_task = misdn_tasks_add_variable(ch->overlap_dial, misdn_overlap_dial_task, ch); } break; } if (ast_exists_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) { ch->state = MISDN_DIALING; start_pbx(ch, bc, ch->ast); } } else { /* sending INFOS as DTMF-Frames :) */ struct ast_frame fr; int digits; memset(&fr, 0, sizeof(fr)); fr.frametype = AST_FRAME_DTMF; fr.subclass.integer = bc->info_dad[0] ; fr.src = NULL; fr.data.ptr = NULL; fr.datalen = 0; fr.samples = 0; fr.mallocd = 0; fr.offset = 0; fr.delivery = ast_tv(0,0); misdn_cfg_get(0, MISDN_GEN_APPEND_DIGITS2EXTEN, &digits, sizeof(digits)); if (ch->state != MISDN_CONNECTED) { if (digits) { strncat(bc->dialed.number, bc->info_dad, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1); ast_channel_exten_set(ch->ast, bc->dialed.number); } ast_queue_frame(ch->ast, &fr); } } break; case EVENT_SETUP: { struct ast_channel *chan; int exceed; int ai; int im; int append_msn = 0; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); const char *pickupexten; if (ch) { switch (ch->state) { case MISDN_NOTHING: chan_list_unref(ch, "Ignore found ch. Is it for an outgoing call?"); ch = NULL; break; default: chan_list_unref(ch, "Already have a call."); chan_misdn_log(1, bc->port, " --> Ignoring Call we have already one\n"); return RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE; /* Ignore MSNs which are not in our List */ } } if (!bc->nt && !misdn_is_msn_valid(bc->port, &bc->dialed)) { chan_misdn_log(1, bc->port, " --> Ignoring Call, its not in our MSN List\n"); return RESPONSE_IGNORE_SETUP; /* Ignore MSNs which are not in our List */ } if (bc->cw) { int cause; chan_misdn_log(0, bc->port, " --> Call Waiting on PMP sending RELEASE_COMPLETE\n"); misdn_cfg_get(bc->port, MISDN_CFG_REJECT_CAUSE, &cause, sizeof(cause)); bc->out_cause = cause ? cause : AST_CAUSE_NORMAL_CLEARING; return RESPONSE_RELEASE_SETUP; } print_bearer(bc); ch = chan_list_init(ORG_MISDN); if (!ch) { chan_misdn_log(-1, bc->port, "cb_events: malloc for chan_list failed!\n"); return RESPONSE_RELEASE_SETUP; } ch->bc = bc; ch->l3id = bc->l3_id; ch->addr = bc->addr; { struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!(cap)) { return RESPONSE_ERR; } ast_format_cap_append(cap, ast_format_alaw, 0); chan = misdn_new(ch, AST_STATE_RESERVED, bc->dialed.number, bc->caller.number, cap, NULL, NULL, bc->port, bc->channel); ao2_ref(cap, -1); } if (!chan) { chan_list_unref(ch, "Failed to create a new channel"); ast_log(LOG_ERROR, "cb_events: misdn_new failed!\n"); return RESPONSE_RELEASE_SETUP; } ast_channel_lock(chan); pickup_cfg = ast_get_chan_features_pickup_config(chan); 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); } ast_channel_unlock(chan); if ((exceed = add_in_calls(bc->port))) { char tmp[16]; snprintf(tmp, sizeof(tmp), "%d", exceed); pbx_builtin_setvar_helper(chan, "MAX_OVERFLOW", tmp); } read_config(ch); export_ch(chan, bc, ch); ast_channel_lock(ch->ast); ast_channel_rings_set(ch->ast, 1); ast_setstate(ch->ast, AST_STATE_RINGING); ast_channel_unlock(ch->ast); /* Update asterisk channel caller information */ chan_misdn_log(2, bc->port, " --> TON: %s(%d)\n", misdn_to_str_ton(bc->caller.number_type), bc->caller.number_type); chan_misdn_log(2, bc->port, " --> PLAN: %s(%d)\n", misdn_to_str_plan(bc->caller.number_plan), bc->caller.number_plan); ast_channel_caller(chan)->id.number.plan = misdn_to_ast_ton(bc->caller.number_type) | misdn_to_ast_plan(bc->caller.number_plan); chan_misdn_log(2, bc->port, " --> PRES: %s(%d)\n", misdn_to_str_pres(bc->caller.presentation), bc->caller.presentation); chan_misdn_log(2, bc->port, " --> SCREEN: %s(%d)\n", misdn_to_str_screen(bc->caller.screening), bc->caller.screening); ast_channel_caller(chan)->id.number.presentation = misdn_to_ast_pres(bc->caller.presentation) | misdn_to_ast_screen(bc->caller.screening); ast_set_callerid(chan, bc->caller.number, NULL, bc->caller.number); misdn_cfg_get(bc->port, MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, &append_msn, sizeof(append_msn)); if (append_msn) { strncat(bc->incoming_cid_tag, "_", sizeof(bc->incoming_cid_tag) - strlen(bc->incoming_cid_tag) - 1); strncat(bc->incoming_cid_tag, bc->dialed.number, sizeof(bc->incoming_cid_tag) - strlen(bc->incoming_cid_tag) - 1); } ast_channel_lock(chan); ast_channel_caller(chan)->id.tag = ast_strdup(bc->incoming_cid_tag); ast_channel_unlock(chan); if (!ast_strlen_zero(bc->redirecting.from.number)) { /* Add configured prefix to redirecting.from.number */ misdn_add_number_prefix(bc->port, bc->redirecting.from.number_type, bc->redirecting.from.number, sizeof(bc->redirecting.from.number)); /* Update asterisk channel redirecting information */ misdn_copy_redirecting_to_ast(chan, &bc->redirecting, bc->incoming_cid_tag); } pbx_builtin_setvar_helper(chan, "TRANSFERCAPABILITY", ast_transfercapability2str(bc->capability)); ast_channel_transfercapability_set(chan, bc->capability); switch (bc->capability) { case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: pbx_builtin_setvar_helper(chan, "CALLTYPE", "DIGITAL"); break; default: pbx_builtin_setvar_helper(chan, "CALLTYPE", "SPEECH"); break; } if (!strstr(ch->allowed_bearers, "all")) { int i; for (i = 0; i < ARRAY_LEN(allowed_bearers_array); ++i) { if (allowed_bearers_array[i].cap == bc->capability) { if (strstr(ch->allowed_bearers, allowed_bearers_array[i].name)) { /* The bearer capability is allowed */ if (allowed_bearers_array[i].deprecated) { chan_misdn_log(0, bc->port, "%s in allowed_bearers list is deprecated\n", allowed_bearers_array[i].name); } break; } } } if (i == ARRAY_LEN(allowed_bearers_array)) { /* We did not find the bearer capability */ chan_misdn_log(0, bc->port, "Bearer capability not allowed: %s(%d)\n", bearer2str(bc->capability), bc->capability); ch->state = MISDN_EXTCANTMATCH; chan_list_unref(ch, "BC not allowed, releasing call"); bc->out_cause = AST_CAUSE_INCOMPATIBLE_DESTINATION; return RESPONSE_RELEASE_SETUP; } } /** queue new chan **/ cl_queue_chan(ch); if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } /* Check for Pickup Request first */ if (!strcmp(ast_channel_exten(chan), pickupexten)) { if (!ch->noautorespond_on_setup) { /* Sending SETUP_ACK */ misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE); } else { ch->state = MISDN_INCOMING_SETUP; } if (ast_pickup_call(chan)) { hangup_chan(ch, bc); } else { ch->state = MISDN_CALLING_ACKNOWLEDGE; hangup_chan(ch, bc); ch->ast = NULL; break; } } /* * added support for s extension hope it will help those poor cretains * which haven't overlap dial. */ misdn_cfg_get(bc->port, MISDN_CFG_ALWAYS_IMMEDIATE, &ai, sizeof(ai)); if (ai) { do_immediate_setup(bc, ch, chan); break; } /* check if we should jump into s when we have no dialed.number */ misdn_cfg_get(bc->port, MISDN_CFG_IMMEDIATE, &im, sizeof(im)); if (im && ast_strlen_zero(bc->dialed.number)) { do_immediate_setup(bc, ch, chan); break; } chan_misdn_log(5, bc->port, "CONTEXT:%s\n", ch->context); if (!ast_canmatch_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) { if (ast_exists_extension(ch->ast, ch->context, "i", 1, bc->caller.number)) { ast_log(LOG_WARNING, "Extension '%s@%s' can never match. Jumping to 'i' extension. port:%d\n", bc->dialed.number, ch->context, bc->port); pbx_builtin_setvar_helper(ch->ast, "INVALID_EXTEN", bc->dialed.number); ast_channel_exten_set(ch->ast, "i"); misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE); ch->state = MISDN_DIALING; start_pbx(ch, bc, chan); break; } ast_log(LOG_WARNING, "Extension '%s@%s' can never match. Disconnecting. port:%d\n" "\tMaybe you want to add an 'i' extension to catch this case.\n", bc->dialed.number, ch->context, bc->port); if (bc->nt) { hanguptone_indicate(ch); } ch->state = MISDN_EXTCANTMATCH; bc->out_cause = AST_CAUSE_UNALLOCATED; misdn_lib_send_event(bc, bc->nt ? EVENT_RELEASE_COMPLETE : EVENT_RELEASE); break; } /* Whatever happens, when sending_complete is set or we are PTMP TE, we will definitely * jump into the dialplan, when the dialed extension does not exist, the 's' extension * will be used by Asterisk automatically. */ if (bc->sending_complete || (!bc->nt && !misdn_lib_is_ptp(bc->port))) { if (!ch->noautorespond_on_setup) { ch->state=MISDN_DIALING; misdn_lib_send_event(bc, EVENT_PROCEEDING); } else { ch->state = MISDN_INCOMING_SETUP; } start_pbx(ch, bc, chan); break; } /* * When we are NT and overlapdial is set and if * the number is empty, we wait for the ISDN timeout * instead of our own timer. */ if (ch->overlap_dial && bc->nt && !bc->dialed.number[0]) { wait_for_digits(ch, bc, chan); break; } /* * If overlapdial we will definitely send a SETUP_ACKNOWLEDGE and wait for more * Infos with a Interdigit Timeout. * */ if (ch->overlap_dial) { ast_mutex_lock(&ch->overlap_tv_lock); ch->overlap_tv = ast_tvnow(); ast_mutex_unlock(&ch->overlap_tv_lock); wait_for_digits(ch, bc, chan); if (ch->overlap_dial_task == -1) { ch->overlap_dial_task = misdn_tasks_add_variable(ch->overlap_dial, misdn_overlap_dial_task, ch); } break; } /* If the extension does not exist and we're not TE_PTMP we wait for more digits * without interdigit timeout. * */ if (!ast_exists_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) { wait_for_digits(ch, bc, chan); break; } /* * If the extension exists let's just jump into it. * */ if (ast_exists_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) { misdn_lib_send_event(bc, bc->need_more_infos ? EVENT_SETUP_ACKNOWLEDGE : EVENT_PROCEEDING); ch->state = MISDN_DIALING; start_pbx(ch, bc, chan); break; } break; } #if defined(AST_MISDN_ENHANCEMENTS) case EVENT_REGISTER: if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } /* * Shut down this connection immediately. * The current design of chan_misdn data structures * does not allow the proper handling of inbound call records * without an assigned B channel. Therefore, we cannot * be the CCBS User-B party in a point-to-point setup. */ bc->fac_out.Function = Fac_None; bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); break; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ case EVENT_SETUP_ACKNOWLEDGE: ch->state = MISDN_CALLING_ACKNOWLEDGE; if (bc->channel) { update_name(ch->ast,bc->port,bc->channel); } if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } if (!ast_strlen_zero(bc->infos_pending)) { /* TX Pending Infos */ strncat(bc->dialed.number, bc->infos_pending, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1); if (!ch->ast) { break; } ast_channel_exten_set(ch->ast, bc->dialed.number); ast_copy_string(bc->info_dad, bc->infos_pending, sizeof(bc->info_dad)); ast_copy_string(bc->infos_pending, "", sizeof(bc->infos_pending)); misdn_lib_send_event(bc, EVENT_INFORMATION); } break; case EVENT_PROCEEDING: if (misdn_cap_is_speech(bc->capability) && misdn_inband_avail(bc)) { start_bc_tones(ch); } ch->state = MISDN_PROCEEDING; if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } if (!ch->ast) { break; } ast_queue_control(ch->ast, AST_CONTROL_PROCEEDING); break; case EVENT_PROGRESS: if (bc->channel) { update_name(ch->ast, bc->port, bc->channel); } if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } if (!bc->nt) { if (misdn_cap_is_speech(bc->capability) && misdn_inband_avail(bc)) { start_bc_tones(ch); } ch->state = MISDN_PROGRESS; if (!ch->ast) { break; } ast_queue_control(ch->ast, AST_CONTROL_PROGRESS); } break; case EVENT_ALERTING: ch->state = MISDN_ALERTING; if (!ch->ast) { break; } if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } ast_queue_control(ch->ast, AST_CONTROL_RINGING); ast_channel_lock(ch->ast); ast_setstate(ch->ast, AST_STATE_RINGING); ast_channel_unlock(ch->ast); cb_log(7, bc->port, " --> Set State Ringing\n"); if (misdn_cap_is_speech(bc->capability) && misdn_inband_avail(bc)) { cb_log(1, bc->port, "Starting Tones, we have inband Data\n"); start_bc_tones(ch); } else { cb_log(3, bc->port, " --> We have no inband Data, the other end must create ringing\n"); if (ch->far_alerting) { cb_log(1, bc->port, " --> The other end can not do ringing eh ?.. we must do all ourself.."); start_bc_tones(ch); /*tone_indicate(ch, TONE_FAR_ALERTING);*/ } } break; case EVENT_CONNECT: if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } #if defined(AST_MISDN_ENHANCEMENTS) if (bc->div_leg_3_rx_wanted) { bc->div_leg_3_rx_wanted = 0; if (ch->ast) { struct ast_party_redirecting redirecting; ast_channel_redirecting(ch->ast)->to.number.presentation = AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED; ast_party_redirecting_init(&redirecting); ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(ch->ast)); /* * Reset any earlier private redirecting id representations and * make sure that it is invalidated at the remote end. */ ast_party_id_reset(&redirecting.priv_orig); ast_party_id_reset(&redirecting.priv_from); ast_party_id_reset(&redirecting.priv_to); ast_channel_queue_redirecting_update(ch->ast, &redirecting, NULL); ast_party_redirecting_free(&redirecting); } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /* we answer when we've got our very new L3 ID from the NT stack */ misdn_lib_send_event(bc, EVENT_CONNECT_ACKNOWLEDGE); if (!ch->ast) { break; } stop_indicate(ch); #if defined(AST_MISDN_ENHANCEMENTS) if (ch->record_id != -1) { /* * We will delete the associated call completion * record since we now have a completed call. * We will not wait/depend on the network to tell * us to delete it. */ AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(ch->record_id); if (cc_record) { if (cc_record->ptp && cc_record->mode.ptp.bc) { /* Close the call-completion signaling link */ cc_record->mode.ptp.bc->fac_out.Function = Fac_None; cc_record->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(cc_record->mode.ptp.bc, EVENT_RELEASE_COMPLETE); } misdn_cc_delete(cc_record); } AST_LIST_UNLOCK(&misdn_cc_records_db); ch->record_id = -1; if (ch->peer) { misdn_cc_set_peer_var(ch->peer, MISDN_CC_RECORD_ID, ""); ao2_ref(ch->peer, -1); ch->peer = NULL; } } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ if (!ast_strlen_zero(bc->connected.number)) { /* Add configured prefix to connected.number */ misdn_add_number_prefix(bc->port, bc->connected.number_type, bc->connected.number, sizeof(bc->connected.number)); /* Update the connected line information on the other channel */ misdn_update_remote_party(ch->ast, &bc->connected, AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER, bc->incoming_cid_tag); } ch->l3id = bc->l3_id; ch->addr = bc->addr; start_bc_tones(ch); ch->state = MISDN_CONNECTED; ast_queue_control(ch->ast, AST_CONTROL_ANSWER); break; case EVENT_CONNECT_ACKNOWLEDGE: ch->l3id = bc->l3_id; ch->addr = bc->addr; start_bc_tones(ch); ch->state = MISDN_CONNECTED; break; case EVENT_DISCONNECT: /* we might not have an ch->ast ptr here anymore */ if (ch) { if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } chan_misdn_log(3, bc->port, " --> org:%d nt:%d, inbandavail:%d state:%d\n", ch->originator, bc->nt, misdn_inband_avail(bc), ch->state); if (ch->originator == ORG_AST && !bc->nt && misdn_inband_avail(bc) && ch->state != MISDN_CONNECTED) { /* If there's inband information available (e.g. a recorded message saying what was wrong with the dialled number, or perhaps even giving an alternative number, then play it instead of immediately releasing the call */ chan_misdn_log(1, bc->port, " --> Inband Info Avail, not sending RELEASE\n"); ch->state = MISDN_DISCONNECTED; start_bc_tones(ch); if (ch->ast) { ast_channel_hangupcause_set(ch->ast, bc->cause); if (bc->cause == AST_CAUSE_USER_BUSY) { ast_queue_control(ch->ast, AST_CONTROL_BUSY); } } ch->need_busy = 0; break; } bc->need_disconnect = 0; stop_bc_tones(ch); /* Check for held channel, to implement transfer */ held_ch = find_hold_call(bc); if (!held_ch || !ch->ast || misdn_attempt_transfer(ch, held_ch)) { hangup_chan(ch, bc); } } else { held_ch = find_hold_call_l3(bc->l3_id); if (held_ch) { if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, held_ch); } if (held_ch->hold.state == MISDN_HOLD_ACTIVE) { bc->need_disconnect = 0; #if defined(TRANSFER_ON_HELD_CALL_HANGUP) /* * Some phones disconnect the held call and the active call at the * same time to do the transfer. Unfortunately, either call could * be disconnected first. */ ch = find_hold_active_call(bc); if (!ch || misdn_attempt_transfer(ch, held_ch)) { held_ch->hold.state = MISDN_HOLD_DISCONNECT; hangup_chan(held_ch, bc); } #else hangup_chan(held_ch, bc); #endif /* defined(TRANSFER_ON_HELD_CALL_HANGUP) */ } } } if (held_ch) { chan_list_unref(held_ch, "Done with held call"); } bc->out_cause = -1; if (bc->need_release) { misdn_lib_send_event(bc, EVENT_RELEASE); } break; case EVENT_RELEASE: if (!ch) { ch = find_hold_call_l3(bc->l3_id); if (!ch) { chan_misdn_log(1, bc->port, " --> no Ch, so we've already released. (%s)\n", manager_isdn_get_info(event)); return -1; } } if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } bc->need_disconnect = 0; bc->need_release = 0; hangup_chan(ch, bc); release_chan(ch, bc); break; case EVENT_RELEASE_COMPLETE: if (!ch) { ch = find_hold_call_l3(bc->l3_id); } bc->need_disconnect = 0; bc->need_release = 0; bc->need_release_complete = 0; if (ch) { if (bc->fac_in.Function != Fac_None) { misdn_facility_ie_handler(event, bc, ch); } stop_bc_tones(ch); hangup_chan(ch, bc); release_chan(ch, bc); } else { #if defined(AST_MISDN_ENHANCEMENTS) /* * A call-completion signaling link established with * REGISTER does not have a struct chan_list record * associated with it. */ AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_bc(bc); if (cc_record) { /* The call-completion signaling link is closed. */ misdn_cc_delete(cc_record); } AST_LIST_UNLOCK(&misdn_cc_records_db); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ chan_misdn_log(1, bc->port, " --> no Ch, so we've already released. (%s)\n", manager_isdn_get_info(event)); } break; case EVENT_BCHAN_ERROR: case EVENT_CLEANUP: stop_bc_tones(ch); switch (ch->state) { case MISDN_CALLING: bc->cause = AST_CAUSE_DESTINATION_OUT_OF_ORDER; break; default: break; } hangup_chan(ch, bc); release_chan(ch, bc); break; case EVENT_TONE_GENERATE: { int tone_len = bc->tone_cnt; struct ast_channel *ast = ch->ast; void *tmp; int res; int (*generate)(struct ast_channel *chan, void *tmp, int datalen, int samples); chan_misdn_log(9, bc->port, "TONE_GEN: len:%d\n", tone_len); if (!ast) { break; } if (!ast_channel_generator(ast)) { break; } tmp = ast_channel_generatordata(ast); ast_channel_generatordata_set(ast, NULL); generate = ast_channel_generator(ast)->generate; if (tone_len < 0 || tone_len > 512) { ast_log(LOG_NOTICE, "TONE_GEN: len was %d, set to 128\n", tone_len); tone_len = 128; } res = generate(ast, tmp, tone_len, tone_len); ast_channel_generatordata_set(ast, tmp); if (res) { ast_log(LOG_WARNING, "Auto-deactivating generator\n"); ast_deactivate_generator(ast); } else { bc->tone_cnt = 0; } break; } case EVENT_BCHAN_DATA: if (ch->bc->AOCD_need_export) { export_aoc_vars(ch->originator, ch->ast, ch->bc); } if (!misdn_cap_is_speech(ch->bc->capability)) { struct ast_frame frame; /* In Data Modes we queue frames */ memset(&frame, 0, sizeof(frame)); frame.frametype = AST_FRAME_VOICE; /* we have no data frames yet */ frame.subclass.format = ast_format_alaw; frame.datalen = bc->bframe_len; frame.samples = bc->bframe_len; frame.mallocd = 0; frame.offset = 0; frame.delivery = ast_tv(0, 0); frame.src = NULL; frame.data.ptr = bc->bframe; if (ch->ast) { ast_queue_frame(ch->ast, &frame); } } else { struct pollfd pfd = { .fd = ch->pipe[1], .events = POLLOUT }; int t; t = ast_poll(&pfd, 1, 0); if (t < 0) { chan_misdn_log(-1, bc->port, "poll() error (err=%s)\n", strerror(errno)); break; } if (!t) { chan_misdn_log(9, bc->port, "poll() timed out\n"); break; } if (pfd.revents & POLLOUT) { chan_misdn_log(9, bc->port, "writing %d bytes to asterisk\n", bc->bframe_len); if (write(ch->pipe[1], bc->bframe, bc->bframe_len) <= 0) { chan_misdn_log(0, bc->port, "Write returned <=0 (err=%s) --> hanging up channel\n", strerror(errno)); stop_bc_tones(ch); hangup_chan(ch, bc); release_chan(ch, bc); } } else { chan_misdn_log(1, bc->port, "Write Pipe full!\n"); } } break; case EVENT_TIMEOUT: if (ch && bc) { chan_misdn_log(1, bc->port, "--> state: %s\n", misdn_get_ch_state(ch)); } switch (ch->state) { case MISDN_DIALING: case MISDN_PROGRESS: if (bc->nt && !ch->nttimeout) { break; } /* fall-through */ case MISDN_CALLING: case MISDN_ALERTING: case MISDN_PROCEEDING: case MISDN_CALLING_ACKNOWLEDGE: if (bc->nt) { bc->progress_indicator = INFO_PI_INBAND_AVAILABLE; hanguptone_indicate(ch); } bc->out_cause = AST_CAUSE_UNALLOCATED; misdn_lib_send_event(bc, EVENT_DISCONNECT); break; case MISDN_WAITING4DIGS: if (bc->nt) { bc->progress_indicator = INFO_PI_INBAND_AVAILABLE; bc->out_cause = AST_CAUSE_UNALLOCATED; hanguptone_indicate(ch); misdn_lib_send_event(bc, EVENT_DISCONNECT); } else { bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(bc, EVENT_RELEASE); } break; case MISDN_CLEANING: chan_misdn_log(1, bc->port, " --> in state cleaning .. so ignoring, the stack should clean it for us\n"); break; default: misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); break; } break; /****************************/ /** Supplementary Services **/ /****************************/ case EVENT_RETRIEVE: if (!ch) { chan_misdn_log(4, bc->port, " --> no CH, searching for held call\n"); ch = find_hold_call_l3(bc->l3_id); if (!ch || ch->hold.state != MISDN_HOLD_ACTIVE) { ast_log(LOG_WARNING, "No held call found, cannot Retrieve\n"); misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT); break; } } /* remember the channel again */ ch->bc = bc; ch->hold.state = MISDN_HOLD_IDLE; ch->hold.port = 0; ch->hold.channel = 0; ast_queue_unhold(ch->ast); if (misdn_lib_send_event(bc, EVENT_RETRIEVE_ACKNOWLEDGE) < 0) { chan_misdn_log(4, bc->port, " --> RETRIEVE_ACK failed\n"); misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT); } break; case EVENT_HOLD: { int hold_allowed; RAII_VAR(struct ast_channel *, bridged, NULL, ast_channel_cleanup); misdn_cfg_get(bc->port, MISDN_CFG_HOLD_ALLOWED, &hold_allowed, sizeof(hold_allowed)); if (!hold_allowed) { chan_misdn_log(-1, bc->port, "Hold not allowed this port.\n"); misdn_lib_send_event(bc, EVENT_HOLD_REJECT); break; } bridged = ast_channel_bridge_peer(ch->ast); if (bridged) { chan_misdn_log(2, bc->port, "Bridge Partner is of type: %s\n", ast_channel_tech(bridged)->type); ch->l3id = bc->l3_id; /* forget the channel now */ ch->bc = NULL; ch->hold.state = MISDN_HOLD_ACTIVE; ch->hold.port = bc->port; ch->hold.channel = bc->channel; ast_queue_hold(ch->ast, NULL); misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE); } else { misdn_lib_send_event(bc, EVENT_HOLD_REJECT); chan_misdn_log(0, bc->port, "We aren't bridged to anybody\n"); } break; } case EVENT_NOTIFY: if (bc->redirecting.to_changed) { /* Add configured prefix to redirecting.to.number */ misdn_add_number_prefix(bc->port, bc->redirecting.to.number_type, bc->redirecting.to.number, sizeof(bc->redirecting.to.number)); } switch (bc->notify_description_code) { case mISDN_NOTIFY_CODE_DIVERSION_ACTIVATED: /* Ignore for now. */ bc->redirecting.to_changed = 0; break; case mISDN_NOTIFY_CODE_CALL_IS_DIVERTING: { struct ast_party_redirecting redirecting; if (!bc->redirecting.to_changed) { break; } bc->redirecting.to_changed = 0; if (!ch || !ch->ast) { break; } switch (ch->state) { case MISDN_ALERTING: /* Call is deflecting after we have seen an ALERTING message */ bc->redirecting.reason = mISDN_REDIRECTING_REASON_NO_REPLY; break; default: /* Call is deflecting for call forwarding unconditional or busy reason. */ bc->redirecting.reason = mISDN_REDIRECTING_REASON_UNKNOWN; break; } misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag); ast_party_redirecting_init(&redirecting); ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(ch->ast)); /* * Reset any earlier private redirecting id representations and * make sure that it is invalidated at the remote end. */ ast_party_id_reset(&redirecting.priv_orig); ast_party_id_reset(&redirecting.priv_from); ast_party_id_reset(&redirecting.priv_to); ast_channel_queue_redirecting_update(ch->ast, &redirecting, NULL); ast_party_redirecting_free(&redirecting); break; } case mISDN_NOTIFY_CODE_CALL_TRANSFER_ALERTING: /* * It would be preferable to update the connected line information * only when the message callStatus is active. However, the * optional redirection number may not be present in the active * message if an alerting message were received earlier. * * The consequences if we wind up sending two updates is benign. * The other end will think that it got transferred twice. */ if (!bc->redirecting.to_changed) { break; } bc->redirecting.to_changed = 0; if (!ch || !ch->ast) { break; } misdn_update_remote_party(ch->ast, &bc->redirecting.to, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, bc->incoming_cid_tag); break; case mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE: if (!bc->redirecting.to_changed) { break; } bc->redirecting.to_changed = 0; if (!ch || !ch->ast) { break; } misdn_update_remote_party(ch->ast, &bc->redirecting.to, AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, bc->incoming_cid_tag); break; default: bc->redirecting.to_changed = 0; chan_misdn_log(0, bc->port," --> not yet handled: notify code:0x%02X\n", bc->notify_description_code); break; } bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; break; case EVENT_FACILITY: if (bc->fac_in.Function == Fac_None) { /* This is a FACILITY message so we MUST have a facility ie */ chan_misdn_log(0, bc->port," --> Missing facility ie or unknown facility ie contents.\n"); } else { misdn_facility_ie_handler(event, bc, ch); } /* In case it came in on a FACILITY message and we did not handle it. */ bc->redirecting.to_changed = 0; bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID; break; case EVENT_RESTART: if (!bc->dummy) { stop_bc_tones(ch); release_chan(ch, bc); } break; default: chan_misdn_log(1, 0, "Got Unknown Event\n"); break; } if (ch) { chan_list_unref(ch, "cb_event complete OK"); } return RESPONSE_OK; } /** TE STUFF END **/ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief Get call completion record information. * * \param chan Asterisk channel to operate upon. (Not used) * \param function_name Name of the function that called us. * \param function_args Argument string passed to function (Could be NULL) * \param buf Buffer to put returned string. * \param size Size of the supplied buffer including the null terminator. * * \retval 0 on success. * \retval -1 on error. */ static int misdn_cc_read(struct ast_channel *chan, const char *function_name, char *function_args, char *buf, size_t size) { char *parse; struct misdn_cc_record *cc_record; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(cc_id); /* Call completion record ID value. */ AST_APP_ARG(get_name); /* Name of what to get */ AST_APP_ARG(other); /* Any extraneous garbage arguments */ ); /* Ensure that the buffer is empty */ *buf = 0; if (ast_strlen_zero(function_args)) { ast_log(LOG_ERROR, "Function '%s' requires arguments.\n", function_name); return -1; } parse = ast_strdupa(function_args); AST_STANDARD_APP_ARGS(args, parse); if (!args.argc || ast_strlen_zero(args.cc_id)) { ast_log(LOG_ERROR, "Function '%s' missing call completion record ID.\n", function_name); return -1; } if (!isdigit(*args.cc_id)) { ast_log(LOG_ERROR, "Function '%s' call completion record ID must be numeric.\n", function_name); return -1; } if (ast_strlen_zero(args.get_name)) { ast_log(LOG_ERROR, "Function '%s' missing what-to-get parameter.\n", function_name); return -1; } AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(atoi(args.cc_id)); if (cc_record) { if (!strcasecmp("a-all", args.get_name)) { snprintf(buf, size, "\"%s\" <%s>", cc_record->redial.caller.name, cc_record->redial.caller.number); } else if (!strcasecmp("a-name", args.get_name)) { ast_copy_string(buf, cc_record->redial.caller.name, size); } else if (!strncasecmp("a-num", args.get_name, 5)) { ast_copy_string(buf, cc_record->redial.caller.number, size); } else if (!strcasecmp("a-ton", args.get_name)) { snprintf(buf, size, "%d", misdn_to_ast_plan(cc_record->redial.caller.number_plan) | misdn_to_ast_ton(cc_record->redial.caller.number_type)); } else if (!strncasecmp("a-pres", args.get_name, 6)) { ast_copy_string(buf, ast_named_caller_presentation( misdn_to_ast_pres(cc_record->redial.caller.presentation) | misdn_to_ast_screen(cc_record->redial.caller.screening)), size); } else if (!strcasecmp("a-busy", args.get_name)) { ast_copy_string(buf, cc_record->party_a_free ? "no" : "yes", size); } else if (!strncasecmp("b-num", args.get_name, 5)) { ast_copy_string(buf, cc_record->redial.dialed.number, size); } else if (!strcasecmp("b-ton", args.get_name)) { snprintf(buf, size, "%d", misdn_to_ast_plan(cc_record->redial.dialed.number_plan) | misdn_to_ast_ton(cc_record->redial.dialed.number_type)); } else if (!strcasecmp("port", args.get_name)) { snprintf(buf, size, "%d", cc_record->port); } else if (!strcasecmp("available-notify-priority", args.get_name)) { snprintf(buf, size, "%d", cc_record->remote_user_free.priority); } else if (!strcasecmp("available-notify-exten", args.get_name)) { ast_copy_string(buf, cc_record->remote_user_free.exten, size); } else if (!strcasecmp("available-notify-context", args.get_name)) { ast_copy_string(buf, cc_record->remote_user_free.context, size); } else if (!strcasecmp("busy-notify-priority", args.get_name)) { snprintf(buf, size, "%d", cc_record->b_free.priority); } else if (!strcasecmp("busy-notify-exten", args.get_name)) { ast_copy_string(buf, cc_record->b_free.exten, size); } else if (!strcasecmp("busy-notify-context", args.get_name)) { ast_copy_string(buf, cc_record->b_free.context, size); } else { AST_LIST_UNLOCK(&misdn_cc_records_db); ast_log(LOG_ERROR, "Function '%s': Unknown what-to-get '%s'.\n", function_name, args.get_name); return -1; } } AST_LIST_UNLOCK(&misdn_cc_records_db); return 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static struct ast_custom_function misdn_cc_function = { .name = "mISDN_CC", .synopsis = "Get call completion record information.", .syntax = "mISDN_CC(${MISDN_CC_RECORD_ID},)", .desc = "mISDN_CC(${MISDN_CC_RECORD_ID},)\n" "The following can be retrieved:\n" "\"a-num\", \"a-name\", \"a-all\", \"a-ton\", \"a-pres\", \"a-busy\",\n" "\"b-num\", \"b-ton\", \"port\",\n" " User-A is available for call completion:\n" " \"available-notify-priority\",\n" " \"available-notify-exten\",\n" " \"available-notify-context\",\n" " User-A is busy:\n" " \"busy-notify-priority\",\n" " \"busy-notify-exten\",\n" " \"busy-notify-context\"\n", .read = misdn_cc_read, }; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ /****************************************** * * Asterisk Channel Endpoint END * * *******************************************/ static int unload_module(void) { /* First, take us out of the channel loop */ ast_verb(0, "-- Unregistering mISDN Channel Driver --\n"); misdn_tasks_destroy(); if (!g_config_initialized) { return 0; } ast_cli_unregister_multiple(chan_misdn_clis, sizeof(chan_misdn_clis) / sizeof(struct ast_cli_entry)); /* ast_unregister_application("misdn_crypt"); */ ast_unregister_application("misdn_set_opt"); ast_unregister_application("misdn_facility"); ast_unregister_application("misdn_check_l2l1"); #if defined(AST_MISDN_ENHANCEMENTS) ast_unregister_application(misdn_command_name); ast_custom_function_unregister(&misdn_cc_function); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ ast_channel_unregister(&misdn_tech); free_robin_list(); misdn_cfg_destroy(); misdn_lib_destroy(); ast_free(misdn_out_calls); ast_free(misdn_in_calls); ast_free(misdn_debug_only); ast_free(misdn_ports); ast_free(misdn_debug); #if defined(AST_MISDN_ENHANCEMENTS) misdn_cc_destroy(); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ ao2_cleanup(misdn_tech.capabilities); misdn_tech.capabilities = NULL; return 0; } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { int i, port; int ntflags = 0, ntkc = 0; char ports[256] = ""; char tempbuf[BUFFERSIZE + 1]; char ntfile[BUFFERSIZE + 1]; struct misdn_lib_iface iface = { .cb_event = cb_events, .cb_log = chan_misdn_log, .cb_jb_empty = chan_misdn_jb_empty, }; if (!(misdn_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append(misdn_tech.capabilities, ast_format_alaw, 0); max_ports = misdn_lib_maxports_get(); if (max_ports <= 0) { ast_log(LOG_ERROR, "Unable to initialize mISDN\n"); return AST_MODULE_LOAD_DECLINE; } if (misdn_cfg_init(max_ports, 0)) { ast_log(LOG_ERROR, "Unable to initialize misdn_config.\n"); return AST_MODULE_LOAD_DECLINE; } g_config_initialized = 1; #if defined(AST_MISDN_ENHANCEMENTS) misdn_cc_init(); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ misdn_debug = ast_malloc(sizeof(int) * (max_ports + 1)); if (!misdn_debug) { ast_log(LOG_ERROR, "Out of memory for misdn_debug\n"); return AST_MODULE_LOAD_DECLINE; } misdn_ports = ast_malloc(sizeof(int) * (max_ports + 1)); if (!misdn_ports) { ast_free(misdn_debug); ast_log(LOG_ERROR, "Out of memory for misdn_ports\n"); return AST_MODULE_LOAD_DECLINE; } misdn_cfg_get(0, MISDN_GEN_DEBUG, &misdn_debug[0], sizeof(misdn_debug[0])); for (i = 1; i <= max_ports; i++) { misdn_debug[i] = misdn_debug[0]; misdn_ports[i] = i; } *misdn_ports = 0; misdn_debug_only = ast_calloc(max_ports + 1, sizeof(int)); if (!misdn_debug_only) { ast_free(misdn_ports); ast_free(misdn_debug); ast_log(LOG_ERROR, "Out of memory for misdn_debug_only\n"); return AST_MODULE_LOAD_DECLINE; } misdn_cfg_get(0, MISDN_GEN_TRACEFILE, tempbuf, sizeof(tempbuf)); if (!ast_strlen_zero(tempbuf)) { tracing = 1; } misdn_in_calls = ast_malloc(sizeof(int) * (max_ports + 1)); if (!misdn_in_calls) { ast_free(misdn_debug_only); ast_free(misdn_ports); ast_free(misdn_debug); ast_log(LOG_ERROR, "Out of memory for misdn_in_calls\n"); return AST_MODULE_LOAD_DECLINE; } misdn_out_calls = ast_malloc(sizeof(int) * (max_ports + 1)); if (!misdn_out_calls) { ast_free(misdn_in_calls); ast_free(misdn_debug_only); ast_free(misdn_ports); ast_free(misdn_debug); ast_log(LOG_ERROR, "Out of memory for misdn_out_calls\n"); return AST_MODULE_LOAD_DECLINE; } for (i = 1; i <= max_ports; i++) { misdn_in_calls[i] = 0; misdn_out_calls[i] = 0; } ast_mutex_init(&cl_te_lock); ast_mutex_init(&release_lock); misdn_cfg_update_ptp(); misdn_cfg_get_ports_string(ports); if (!ast_strlen_zero(ports)) { chan_misdn_log(0, 0, "Got: %s from get_ports\n", ports); } if (misdn_lib_init(ports, &iface, NULL)) { chan_misdn_log(0, 0, "No te ports initialized\n"); } misdn_cfg_get(0, MISDN_GEN_NTDEBUGFLAGS, &ntflags, sizeof(ntflags)); misdn_cfg_get(0, MISDN_GEN_NTDEBUGFILE, &ntfile, sizeof(ntfile)); misdn_cfg_get(0, MISDN_GEN_NTKEEPCALLS, &ntkc, sizeof(ntkc)); misdn_lib_nt_keepcalls(ntkc); misdn_lib_nt_debug_init(ntflags, ntfile); if (ast_channel_register(&misdn_tech)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", misdn_type); unload_module(); return AST_MODULE_LOAD_DECLINE; } ast_cli_register_multiple(chan_misdn_clis, sizeof(chan_misdn_clis) / sizeof(struct ast_cli_entry)); ast_register_application("misdn_set_opt", misdn_set_opt_exec, "misdn_set_opt", "misdn_set_opt(::...):\n" "Sets mISDN opts. and optargs\n" "\n" "The available options are:\n" " a - Have Asterisk detect DTMF tones on called channel\n" " c - Make crypted outgoing call, optarg is keyindex\n" " d - Send display text to called phone, text is the optarg\n" " e - Perform echo cancellation on this channel,\n" " takes taps as optarg (32,64,128,256)\n" " e! - Disable echo cancellation on this channel\n" " f - Enable fax detection\n" " h - Make digital outgoing call\n" " h1 - Make HDLC mode digital outgoing call\n" " i - Ignore detected DTMF tones, don't signal them to Asterisk,\n" " they will be transported inband.\n" " jb - Set jitter buffer length, optarg is length\n" " jt - Set jitter buffer upper threshold, optarg is threshold\n" " jn - Disable jitter buffer\n" " n - Disable mISDN DSP on channel.\n" " Disables: echo cancel, DTMF detection, and volume control.\n" " p - Caller ID presentation,\n" " optarg is either 'allowed' or 'restricted'\n" " s - Send Non-inband DTMF as inband\n" " vr - Rx gain control, optarg is gain\n" " vt - Tx gain control, optarg is gain\n" ); ast_register_application("misdn_facility", misdn_facility_exec, "misdn_facility", "misdn_facility(||..)\n" "Sends the Facility Message FACILITY_TYPE with \n" "the given Arguments to the current ISDN Channel\n" "Supported Facilities are:\n" "\n" "type=calldeflect args=Nr where to deflect\n" #if defined(AST_MISDN_ENHANCEMENTS) "type=callrerouting args=Nr where to deflect\n" #endif /* defined(AST_MISDN_ENHANCEMENTS) */ ); ast_register_application("misdn_check_l2l1", misdn_check_l2l1, "misdn_check_l2l1", "misdn_check_l2l1(||g:,timeout)\n" "Checks if the L2 and L1 are up on either the given or\n" "on the ports in the group with \n" "If the L1/L2 are down, check_l2l1 gets up the L1/L2 and waits\n" "for seconds that this happens. Otherwise, nothing happens\n" "\n" "This application, ensures the L1/L2 state of the Ports in a group\n" "it is intended to make the pmp_l1_check option redundant and to\n" "fix a buggy switch config from your provider\n" "\n" "a sample dialplan would look like:\n\n" "exten => _X.,1,misdn_check_l2l1(g:out|2)\n" "exten => _X.,n,dial(mISDN/g:out/${EXTEN})\n" ); #if defined(AST_MISDN_ENHANCEMENTS) ast_register_application(misdn_command_name, misdn_command_exec, misdn_command_name, "misdn_command([,])\n" "The following commands are defined:\n" "cc-initialize\n" " Setup mISDN support for call completion\n" " Must call before doing any Dial() involving call completion.\n" "ccnr-request,${MISDN_CC_RECORD_ID},,,\n" " Request Call Completion No Reply activation\n" "ccbs-request,${MISDN_CC_RECORD_ID},,,\n" " Request Call Completion Busy Subscriber activation\n" "cc-b-free,${MISDN_CC_RECORD_ID},,,\n" " Set the dialplan location to notify when User-B is available but User-A is busy.\n" " Setting this dialplan location is optional.\n" "cc-a-busy,${MISDN_CC_RECORD_ID},\n" " Set the busy status of call completion User-A\n" "cc-deactivate,${MISDN_CC_RECORD_ID}\n" " Deactivate the identified call completion request\n" "\n" "MISDN_CC_RECORD_ID is set when Dial() returns and call completion is possible\n" "MISDN_CC_STATUS is set to ACTIVATED or ERROR after the call completion\n" "activation request.\n" "MISDN_ERROR_MSG is set to a descriptive message on error.\n" ); ast_custom_function_register(&misdn_cc_function); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ misdn_cfg_get(0, MISDN_GEN_TRACEFILE, global_tracefile, sizeof(global_tracefile)); /* start the l1 watchers */ for (port = misdn_cfg_get_next_port(0); port >= 0; port = misdn_cfg_get_next_port(port)) { int l1timeout; misdn_cfg_get(port, MISDN_CFG_L1_TIMEOUT, &l1timeout, sizeof(l1timeout)); if (l1timeout) { chan_misdn_log(4, 0, "Adding L1watcher task: port:%d timeout:%ds\n", port, l1timeout); misdn_tasks_add(l1timeout * 1000, misdn_l1_task, &misdn_ports[port]); } } chan_misdn_log(0, 0, "-- mISDN Channel Driver Registered --\n"); return 0; } static int reload(void) { reload_config(); return 0; } /*** SOME APPS ;)***/ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \brief misdn_command arguments container. */ AST_DEFINE_APP_ARGS_TYPE(misdn_command_args, AST_APP_ARG(name); /* Subcommand name */ AST_APP_ARG(arg)[10 + 1]; /* Subcommand arguments */ ); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static void misdn_cc_caller_destroy(void *obj) { /* oh snap! */ } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) static struct misdn_cc_caller *misdn_cc_caller_alloc(struct ast_channel *chan) { struct misdn_cc_caller *cc_caller; if (!(cc_caller = ao2_alloc(sizeof(*cc_caller), misdn_cc_caller_destroy))) { return NULL; } cc_caller->chan = chan; return cc_caller; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(cc-initialize) subcommand handler * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_cc_initialize(struct ast_channel *chan, struct misdn_command_args *subcommand) { struct misdn_cc_caller *cc_caller; struct ast_datastore *datastore; if (!(cc_caller = misdn_cc_caller_alloc(chan))) { return -1; } if (!(datastore = ast_datastore_alloc(&misdn_cc_ds_info, NULL))) { ao2_ref(cc_caller, -1); return -1; } ast_channel_lock(chan); /* Inherit reference */ datastore->data = cc_caller; cc_caller = NULL; datastore->inheritance = DATASTORE_INHERIT_FOREVER; ast_channel_datastore_add(chan, datastore); ast_channel_unlock(chan); return 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(cc-deactivate) subcommand handler * * \details * misdn_command(cc-deactivate,${MISDN_CC_RECORD_ID}) * Deactivate a call completion service instance. * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_cc_deactivate(struct ast_channel *chan, struct misdn_command_args *subcommand) { long record_id; const char *error_str; struct misdn_cc_record *cc_record; struct misdn_bchannel *bc; struct misdn_bchannel dummy; static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID})\n"; if (ast_strlen_zero(subcommand->arg[0]) || !isdigit(*subcommand->arg[0])) { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } record_id = atol(subcommand->arg[0]); AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (cc_record && 0 <= cc_record->port) { if (cc_record->ptp) { if (cc_record->mode.ptp.bc) { /* Close the call-completion signaling link */ bc = cc_record->mode.ptp.bc; bc->fac_out.Function = Fac_None; bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); } misdn_cc_delete(cc_record); } else if (cc_record->activated) { cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; cc_record->invoke_id = ++misdn_invoke_id; cc_record->outstanding_message = 1; /* Build message */ misdn_make_dummy(&dummy, cc_record->port, 0, misdn_lib_port_is_nt(cc_record->port), 0); dummy.fac_out.Function = Fac_CCBSDeactivate; dummy.fac_out.u.CCBSDeactivate.InvokeID = cc_record->invoke_id; dummy.fac_out.u.CCBSDeactivate.ComponentType = FacComponent_Invoke; dummy.fac_out.u.CCBSDeactivate.Component.Invoke.CCBSReference = cc_record->mode.ptmp.reference_id; /* Send message */ print_facility(&dummy.fac_out, &dummy); misdn_lib_send_event(&dummy, EVENT_FACILITY); } } AST_LIST_UNLOCK(&misdn_cc_records_db); /* Wait for the response to the call completion deactivation request. */ misdn_cc_response_wait(chan, MISDN_CC_REQUEST_WAIT_MAX, record_id); AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (cc_record) { if (cc_record->port < 0) { /* The network did not tell us that call completion was available. */ error_str = NULL; } else if (cc_record->outstanding_message) { cc_record->outstanding_message = 0; error_str = misdn_no_response_from_network; } else if (cc_record->reject_code != FacReject_None) { error_str = misdn_to_str_reject_code(cc_record->reject_code); } else if (cc_record->error_code != FacError_None) { error_str = misdn_to_str_error_code(cc_record->error_code); } else { error_str = NULL; } misdn_cc_delete(cc_record); } else { error_str = NULL; } AST_LIST_UNLOCK(&misdn_cc_records_db); if (error_str) { ast_verb(1, "%s(%s) diagnostic '%s' on channel %s\n", misdn_command_name, subcommand->name, error_str, ast_channel_name(chan)); pbx_builtin_setvar_helper(chan, MISDN_ERROR_MSG, error_str); } return 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(cc-a-busy) subcommand handler * * \details * misdn_command(cc-a-busy,${MISDN_CC_RECORD_ID},) * Set the status of User-A for a call completion service instance. * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_cc_a_busy(struct ast_channel *chan, struct misdn_command_args *subcommand) { long record_id; int party_a_free; struct misdn_cc_record *cc_record; struct misdn_bchannel *bc; static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID},)\n"; if (ast_strlen_zero(subcommand->arg[0]) || !isdigit(*subcommand->arg[0])) { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } record_id = atol(subcommand->arg[0]); if (ast_true(subcommand->arg[1])) { party_a_free = 0; } else if (ast_false(subcommand->arg[1])) { party_a_free = 1; } else { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (cc_record && cc_record->party_a_free != party_a_free) { /* User-A's status has changed */ cc_record->party_a_free = party_a_free; if (cc_record->ptp && cc_record->mode.ptp.bc) { cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; /* Build message */ bc = cc_record->mode.ptp.bc; if (cc_record->party_a_free) { bc->fac_out.Function = Fac_CCBS_T_Resume; bc->fac_out.u.CCBS_T_Resume.InvokeID = ++misdn_invoke_id; } else { bc->fac_out.Function = Fac_CCBS_T_Suspend; bc->fac_out.u.CCBS_T_Suspend.InvokeID = ++misdn_invoke_id; } /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_FACILITY); } } AST_LIST_UNLOCK(&misdn_cc_records_db); return 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(cc-b-free) subcommand handler * * \details * misdn_command(cc-b-free,${MISDN_CC_RECORD_ID},,,) * Set the dialplan location to notify when User-B is free and User-A is busy. * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_cc_b_free(struct ast_channel *chan, struct misdn_command_args *subcommand) { unsigned index; long record_id; int priority; char *context; char *exten; struct misdn_cc_record *cc_record; static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID},,,)\n"; /* Check that all arguments are present */ for (index = 0; index < 4; ++index) { if (ast_strlen_zero(subcommand->arg[index])) { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } } /* These must be numeric */ if (!isdigit(*subcommand->arg[0]) || !isdigit(*subcommand->arg[3])) { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } record_id = atol(subcommand->arg[0]); context = subcommand->arg[1]; exten = subcommand->arg[2]; priority = atoi(subcommand->arg[3]); AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (cc_record) { /* Save User-B free information */ ast_copy_string(cc_record->b_free.context, context, sizeof(cc_record->b_free.context)); ast_copy_string(cc_record->b_free.exten, exten, sizeof(cc_record->b_free.exten)); cc_record->b_free.priority = priority; } AST_LIST_UNLOCK(&misdn_cc_records_db); return 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) struct misdn_cc_request { enum FacFunction ptmp; enum FacFunction ptp; }; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(ccbs-request/ccnr-request) subcommand handler helper * * \details * misdn_command(ccbs-request,${MISDN_CC_RECORD_ID},,,) * misdn_command(ccnr-request,${MISDN_CC_RECORD_ID},,,) * Set the dialplan location to notify when User-B is free and User-A is free. * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * \param request Which call-completion request message to generate. * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_cc_request(struct ast_channel *chan, struct misdn_command_args *subcommand, const struct misdn_cc_request *request) { unsigned index; int request_retention; long record_id; int priority; char *context; char *exten; const char *error_str; struct misdn_cc_record *cc_record; struct misdn_bchannel *bc; struct misdn_bchannel dummy; struct misdn_party_id id; static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID},,,)\n"; /* Check that all arguments are present */ for (index = 0; index < 4; ++index) { if (ast_strlen_zero(subcommand->arg[index])) { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } } /* These must be numeric */ if (!isdigit(*subcommand->arg[0]) || !isdigit(*subcommand->arg[3])) { ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name); return -1; } record_id = atol(subcommand->arg[0]); context = subcommand->arg[1]; exten = subcommand->arg[2]; priority = atoi(subcommand->arg[3]); AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (cc_record) { /* Save User-B free information */ ast_copy_string(cc_record->remote_user_free.context, context, sizeof(cc_record->remote_user_free.context)); ast_copy_string(cc_record->remote_user_free.exten, exten, sizeof(cc_record->remote_user_free.exten)); cc_record->remote_user_free.priority = priority; if (0 <= cc_record->port) { if (cc_record->ptp) { if (!cc_record->mode.ptp.bc) { bc = misdn_lib_get_register_bc(cc_record->port); if (bc) { cc_record->mode.ptp.bc = bc; cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; cc_record->invoke_id = ++misdn_invoke_id; cc_record->outstanding_message = 1; cc_record->activation_requested = 1; misdn_cfg_get(bc->port, MISDN_CFG_CC_REQUEST_RETENTION, &request_retention, sizeof(request_retention)); cc_record->mode.ptp.requested_retention = request_retention ? 1 : 0; /* Build message */ bc->fac_out.Function = request->ptp; bc->fac_out.u.CCBS_T_Request.InvokeID = cc_record->invoke_id; bc->fac_out.u.CCBS_T_Request.ComponentType = FacComponent_Invoke; bc->fac_out.u.CCBS_T_Request.Component.Invoke.Q931ie = cc_record->redial.setup_bc_hlc_llc; memset(&id, 0, sizeof(id)); id.number_plan = cc_record->redial.dialed.number_plan; id.number_type = cc_record->redial.dialed.number_type; ast_copy_string(id.number, cc_record->redial.dialed.number, sizeof(id.number)); misdn_Address_fill( &bc->fac_out.u.CCBS_T_Request.Component.Invoke.Destination, &id); misdn_Address_fill( &bc->fac_out.u.CCBS_T_Request.Component.Invoke.Originating, &cc_record->redial.caller); bc->fac_out.u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1; bc->fac_out.u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = (cc_record->redial.caller.presentation != 0) ? 0 : 1; bc->fac_out.u.CCBS_T_Request.Component.Invoke.RetentionSupported = request_retention ? 1 : 0; /* Send message */ print_facility(&bc->fac_out, bc); misdn_lib_send_event(bc, EVENT_REGISTER); } } } else { cc_record->error_code = FacError_None; cc_record->reject_code = FacReject_None; cc_record->invoke_id = ++misdn_invoke_id; cc_record->outstanding_message = 1; cc_record->activation_requested = 1; /* Build message */ misdn_make_dummy(&dummy, cc_record->port, 0, misdn_lib_port_is_nt(cc_record->port), 0); dummy.fac_out.Function = request->ptmp; dummy.fac_out.u.CCBSRequest.InvokeID = cc_record->invoke_id; dummy.fac_out.u.CCBSRequest.ComponentType = FacComponent_Invoke; dummy.fac_out.u.CCBSRequest.Component.Invoke.CallLinkageID = cc_record->mode.ptmp.linkage_id; /* Send message */ print_facility(&dummy.fac_out, &dummy); misdn_lib_send_event(&dummy, EVENT_FACILITY); } } } AST_LIST_UNLOCK(&misdn_cc_records_db); /* Wait for the response to the call completion request. */ misdn_cc_response_wait(chan, MISDN_CC_REQUEST_WAIT_MAX, record_id); AST_LIST_LOCK(&misdn_cc_records_db); cc_record = misdn_cc_find_by_id(record_id); if (cc_record) { if (!cc_record->activated) { if (cc_record->port < 0) { /* The network did not tell us that call completion was available. */ error_str = "No port number"; } else if (cc_record->outstanding_message) { cc_record->outstanding_message = 0; error_str = misdn_no_response_from_network; } else if (cc_record->reject_code != FacReject_None) { error_str = misdn_to_str_reject_code(cc_record->reject_code); } else if (cc_record->error_code != FacError_None) { error_str = misdn_to_str_error_code(cc_record->error_code); } else if (cc_record->ptp) { if (cc_record->mode.ptp.bc) { error_str = "Call-completion already requested"; } else { error_str = "Could not allocate call-completion signaling link"; } } else { /* Should never happen. */ error_str = "Unexpected error"; } /* No need to keep the call completion record. */ if (cc_record->ptp && cc_record->mode.ptp.bc) { /* Close the call-completion signaling link */ bc = cc_record->mode.ptp.bc; bc->fac_out.Function = Fac_None; bc->out_cause = AST_CAUSE_NORMAL_CLEARING; misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE); } misdn_cc_delete(cc_record); } else { error_str = NULL; } } else { error_str = misdn_cc_record_not_found; } AST_LIST_UNLOCK(&misdn_cc_records_db); if (error_str) { ast_verb(1, "%s(%s) diagnostic '%s' on channel %s\n", misdn_command_name, subcommand->name, error_str, ast_channel_name(chan)); pbx_builtin_setvar_helper(chan, MISDN_ERROR_MSG, error_str); pbx_builtin_setvar_helper(chan, MISDN_CC_STATUS, "ERROR"); } else { pbx_builtin_setvar_helper(chan, MISDN_CC_STATUS, "ACTIVATED"); } return 0; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(ccbs-request) subcommand handler * * \details * misdn_command(ccbs-request,${MISDN_CC_RECORD_ID},,,) * Set the dialplan location to notify when User-B is free and User-A is free. * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_ccbs_request(struct ast_channel *chan, struct misdn_command_args *subcommand) { static const struct misdn_cc_request request = { .ptmp = Fac_CCBSRequest, .ptp = Fac_CCBS_T_Request }; return misdn_command_cc_request(chan, subcommand, &request); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command(ccnr-request) subcommand handler * * \details * misdn_command(ccnr-request,${MISDN_CC_RECORD_ID},,,) * Set the dialplan location to notify when User-B is free and User-A is free. * * \param chan Asterisk channel to operate upon. * \param subcommand Arguments for the subcommand * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_ccnr_request(struct ast_channel *chan, struct misdn_command_args *subcommand) { static const struct misdn_cc_request request = { .ptmp = Fac_CCNRRequest, .ptp = Fac_CCNR_T_Request }; return misdn_command_cc_request(chan, subcommand, &request); } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) struct misdn_command_table { /*! \brief subcommand name */ const char *name; /*! \brief subcommand handler */ int (*func)(struct ast_channel *chan, struct misdn_command_args *subcommand); /*! \brief TRUE if the subcommand can only be executed on mISDN channels */ int misdn_only; }; static const struct misdn_command_table misdn_commands[] = { /* *INDENT-OFF* */ /* subcommand-name subcommand-handler mISDN only */ { "cc-initialize", misdn_command_cc_initialize, 0 }, { "cc-deactivate", misdn_command_cc_deactivate, 0 }, { "cc-a-busy", misdn_command_cc_a_busy, 0 }, { "cc-b-free", misdn_command_cc_b_free, 0 }, { "ccbs-request", misdn_command_ccbs_request, 0 }, { "ccnr-request", misdn_command_ccnr_request, 0 }, /* *INDENT-ON* */ }; #endif /* defined(AST_MISDN_ENHANCEMENTS) */ #if defined(AST_MISDN_ENHANCEMENTS) /*! * \internal * \brief misdn_command() dialplan application. * * \param chan Asterisk channel to operate upon. * \param data Application options string. * * \retval 0 on success. * \retval -1 on error. */ static int misdn_command_exec(struct ast_channel *chan, const char *data) { char *parse; unsigned index; struct misdn_command_args subcommand; if (ast_strlen_zero((char *) data)) { ast_log(LOG_ERROR, "%s requires arguments\n", misdn_command_name); return -1; } ast_debug(1, "%s(%s)\n", misdn_command_name, (char *) data); parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(subcommand, parse); if (!subcommand.argc || ast_strlen_zero(subcommand.name)) { ast_log(LOG_ERROR, "%s requires a subcommand\n", misdn_command_name); return -1; } for (index = 0; index < ARRAY_LEN(misdn_commands); ++index) { if (strcasecmp(misdn_commands[index].name, subcommand.name) == 0) { strcpy(subcommand.name, misdn_commands[index].name); if (misdn_commands[index].misdn_only && strcasecmp(ast_channel_tech(chan)->type, misdn_type) != 0) { ast_log(LOG_WARNING, "%s(%s) only makes sense with %s channels!\n", misdn_command_name, subcommand.name, misdn_type); return -1; } return misdn_commands[index].func(chan, &subcommand); } } ast_log(LOG_WARNING, "%s(%s) subcommand is unknown\n", misdn_command_name, subcommand.name); return -1; } #endif /* defined(AST_MISDN_ENHANCEMENTS) */ static int misdn_facility_exec(struct ast_channel *chan, const char *data) { struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(chan); char *parse; unsigned max_len; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(facility_type); AST_APP_ARG(arg)[99]; ); chan_misdn_log(0, 0, "TYPE: %s\n", ast_channel_tech(chan)->type); if (strcasecmp(ast_channel_tech(chan)->type, misdn_type)) { ast_log(LOG_WARNING, "misdn_facility only makes sense with %s channels!\n", misdn_type); return -1; } if (ast_strlen_zero((char *) data)) { ast_log(LOG_WARNING, "misdn_facility requires arguments: facility_type[,]\n"); return -1; } parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); if (ast_strlen_zero(args.facility_type)) { ast_log(LOG_WARNING, "misdn_facility requires arguments: facility_type[,]\n"); return -1; } if (!strcasecmp(args.facility_type, "calldeflect")) { if (ast_strlen_zero(args.arg[0])) { ast_log(LOG_WARNING, "Facility: Call Deflection requires an argument: Number\n"); } #if defined(AST_MISDN_ENHANCEMENTS) max_len = sizeof(ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number) - 1; if (max_len < strlen(args.arg[0])) { ast_log(LOG_WARNING, "Facility: Number argument too long (up to %u digits are allowed). Ignoring.\n", max_len); return 0; } ch->bc->fac_out.Function = Fac_CallDeflection; ch->bc->fac_out.u.CallDeflection.InvokeID = ++misdn_invoke_id; ch->bc->fac_out.u.CallDeflection.ComponentType = FacComponent_Invoke; ch->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1; ch->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 0; ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Type = 0;/* unknown */ ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = strlen(args.arg[0]); strcpy((char *) ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number, args.arg[0]); ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Subaddress.Length = 0; #else /* !defined(AST_MISDN_ENHANCEMENTS) */ max_len = sizeof(ch->bc->fac_out.u.CDeflection.DeflectedToNumber) - 1; if (max_len < strlen(args.arg[0])) { ast_log(LOG_WARNING, "Facility: Number argument too long (up to %u digits are allowed). Ignoring.\n", max_len); return 0; } ch->bc->fac_out.Function = Fac_CD; ch->bc->fac_out.u.CDeflection.PresentationAllowed = 0; //ch->bc->fac_out.u.CDeflection.DeflectedToSubaddress[0] = 0; strcpy((char *) ch->bc->fac_out.u.CDeflection.DeflectedToNumber, args.arg[0]); #endif /* !defined(AST_MISDN_ENHANCEMENTS) */ /* Send message */ print_facility(&ch->bc->fac_out, ch->bc); misdn_lib_send_event(ch->bc, EVENT_FACILITY); #if defined(AST_MISDN_ENHANCEMENTS) } else if (!strcasecmp(args.facility_type, "callrerouteing") || !strcasecmp(args.facility_type, "callrerouting")) { if (ast_strlen_zero(args.arg[0])) { ast_log(LOG_WARNING, "Facility: Call rerouting requires an argument: Number\n"); } max_len = sizeof(ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number) - 1; if (max_len < strlen(args.arg[0])) { ast_log(LOG_WARNING, "Facility: Number argument too long (up to %u digits are allowed). Ignoring.\n", max_len); return 0; } ch->bc->fac_out.Function = Fac_CallRerouteing; ch->bc->fac_out.u.CallRerouteing.InvokeID = ++misdn_invoke_id; ch->bc->fac_out.u.CallRerouteing.ComponentType = FacComponent_Invoke; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingReason = 0;/* unknown */ ch->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingCounter = 1; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 0;/* unknown */ ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = strlen(args.arg[0]); strcpy((char *) ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number, args.arg[0]); ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Subaddress.Length = 0; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length = 0; /* 0x90 0x90 0xa3 3.1 kHz audio, circuit mode, 64kbit/sec, level1/a-Law */ ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 3; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[0] = 0x90; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[1] = 0x90; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[2] = 0xa3; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Length = 0; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Llc.Length = 0; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Length = 0; ch->bc->fac_out.u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1;/* presentationRestricted */ ch->bc->fac_out.u.CallRerouteing.Component.Invoke.SubscriptionOption = 0;/* no notification to caller */ /* Send message */ print_facility(&ch->bc->fac_out, ch->bc); misdn_lib_send_event(ch->bc, EVENT_FACILITY); #endif /* defined(AST_MISDN_ENHANCEMENTS) */ } else { chan_misdn_log(1, ch->bc->port, "Unknown Facility: %s\n", args.facility_type); } return 0; } static int misdn_check_l2l1(struct ast_channel *chan, const char *data) { char *parse; char group[BUFFERSIZE + 1]; char *port_str; int port = 0; int timeout; int dowait = 0; int port_up; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(grouppar); AST_APP_ARG(timeout); ); if (ast_strlen_zero((char *) data)) { ast_log(LOG_WARNING, "misdn_check_l2l1 Requires arguments\n"); return -1; } parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); if (args.argc != 2) { ast_log(LOG_WARNING, "Wrong argument count\n"); return 0; } /*ast_log(LOG_NOTICE, "Arguments: group/port '%s' timeout '%s'\n", args.grouppar, args.timeout);*/ timeout = atoi(args.timeout); port_str = args.grouppar; if (port_str[0] == 'g' && port_str[1] == ':') { /* We make a group call lets checkout which ports are in my group */ port_str += 2; ast_copy_string(group, port_str, sizeof(group)); chan_misdn_log(2, 0, "Checking Ports in group: %s\n", group); for (port = misdn_cfg_get_next_port(port); port > 0; port = misdn_cfg_get_next_port(port)) { char cfg_group[BUFFERSIZE + 1]; chan_misdn_log(2, 0, "trying port %d\n", port); misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group)); if (!strcasecmp(cfg_group, group)) { port_up = misdn_lib_port_up(port, 1); if (!port_up) { chan_misdn_log(2, 0, " --> port '%d'\n", port); misdn_lib_get_port_up(port); dowait = 1; } } } } else { port = atoi(port_str); chan_misdn_log(2, 0, "Checking Port: %d\n", port); port_up = misdn_lib_port_up(port, 1); if (!port_up) { misdn_lib_get_port_up(port); dowait = 1; } } if (dowait) { chan_misdn_log(2, 0, "Waiting for '%d' seconds\n", timeout); ast_safe_sleep(chan, timeout * 1000); } return 0; } static int misdn_set_opt_exec(struct ast_channel *chan, const char *data) { struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(chan); char *tok; char *tokb; char *parse; int keyidx = 0; int rxgain = 0; int txgain = 0; int change_jitter = 0; if (strcasecmp(ast_channel_tech(chan)->type, misdn_type)) { ast_log(LOG_WARNING, "misdn_set_opt makes sense only with %s channels!\n", misdn_type); return -1; } if (ast_strlen_zero((char *) data)) { ast_log(LOG_WARNING, "misdn_set_opt Requires arguments\n"); return -1; } parse = ast_strdupa(data); for (tok = strtok_r(parse, ":", &tokb); tok; tok = strtok_r(NULL, ":", &tokb)) { int neglect = 0; if (tok[0] == '!') { neglect = 1; tok++; } switch(tok[0]) { case 'd' : ast_copy_string(ch->bc->display, ++tok, sizeof(ch->bc->display)); chan_misdn_log(1, ch->bc->port, "SETOPT: Display:%s\n", ch->bc->display); break; case 'n': chan_misdn_log(1, ch->bc->port, "SETOPT: No DSP\n"); ch->bc->nodsp = 1; break; case 'j': chan_misdn_log(1, ch->bc->port, "SETOPT: jitter\n"); tok++; change_jitter = 1; switch (tok[0]) { case 'b': ch->jb_len = atoi(++tok); chan_misdn_log(1, ch->bc->port, " --> buffer_len:%d\n", ch->jb_len); break; case 't' : ch->jb_upper_threshold = atoi(++tok); chan_misdn_log(1, ch->bc->port, " --> upper_threshold:%d\n", ch->jb_upper_threshold); break; case 'n': ch->bc->nojitter = 1; chan_misdn_log(1, ch->bc->port, " --> nojitter\n"); break; default: ch->jb_len = 4000; ch->jb_upper_threshold = 0; chan_misdn_log(1, ch->bc->port, " --> buffer_len:%d (default)\n", ch->jb_len); chan_misdn_log(1, ch->bc->port, " --> upper_threshold:%d (default)\n", ch->jb_upper_threshold); break; } break; case 'v': tok++; switch (tok[0]) { case 'r' : rxgain = atoi(++tok); if (rxgain < -8) { rxgain = -8; } if (rxgain > 8) { rxgain = 8; } ch->bc->rxgain = rxgain; chan_misdn_log(1, ch->bc->port, "SETOPT: Volume:%d\n", rxgain); break; case 't': txgain = atoi(++tok); if (txgain < -8) { txgain = -8; } if (txgain > 8) { txgain = 8; } ch->bc->txgain = txgain; chan_misdn_log(1, ch->bc->port, "SETOPT: Volume:%d\n", txgain); break; } break; case 'c': keyidx = atoi(++tok); { char keys[4096]; char *key = NULL; char *tmp = keys; int i; misdn_cfg_get(0, MISDN_GEN_CRYPT_KEYS, keys, sizeof(keys)); for (i = 0; i < keyidx; i++) { key = strsep(&tmp, ","); } if (key) { ast_copy_string(ch->bc->crypt_key, key, sizeof(ch->bc->crypt_key)); } chan_misdn_log(0, ch->bc->port, "SETOPT: crypt with key:%s\n", ch->bc->crypt_key); break; } case 'e': chan_misdn_log(1, ch->bc->port, "SETOPT: EchoCancel\n"); if (neglect) { chan_misdn_log(1, ch->bc->port, " --> disabled\n"); #ifdef MISDN_1_2 *ch->bc->pipeline = 0; #else ch->bc->ec_enable = 0; #endif } else { #ifdef MISDN_1_2 update_pipeline_config(ch->bc); #else ch->bc->ec_enable = 1; ch->bc->orig = ch->originator; tok++; if (*tok) { ch->bc->ec_deftaps = atoi(tok); } #endif } break; case 'h': chan_misdn_log(1, ch->bc->port, "SETOPT: Digital\n"); if (strlen(tok) > 1 && tok[1] == '1') { chan_misdn_log(1, ch->bc->port, "SETOPT: HDLC \n"); if (!ch->bc->hdlc) { ch->bc->hdlc = 1; } } ch->bc->capability = INFO_CAPABILITY_DIGITAL_UNRESTRICTED; break; case 's': chan_misdn_log(1, ch->bc->port, "SETOPT: Send DTMF\n"); ch->bc->send_dtmf = 1; break; case 'f': chan_misdn_log(1, ch->bc->port, "SETOPT: Faxdetect\n"); ch->faxdetect = 1; misdn_cfg_get(ch->bc->port, MISDN_CFG_FAXDETECT_TIMEOUT, &ch->faxdetect_timeout, sizeof(ch->faxdetect_timeout)); break; case 'a': chan_misdn_log(1, ch->bc->port, "SETOPT: AST_DSP (for DTMF)\n"); ch->ast_dsp = 1; break; case 'p': chan_misdn_log(1, ch->bc->port, "SETOPT: callerpres: %s\n", &tok[1]); /* CRICH: callingpres!!! */ if (strstr(tok, "allowed")) { ch->bc->presentation = 0; ch->bc->set_presentation = 1; } else if (strstr(tok, "restricted")) { ch->bc->presentation = 1; ch->bc->set_presentation = 1; } else if (strstr(tok, "not_screened")) { chan_misdn_log(0, ch->bc->port, "SETOPT: callerpres: not_screened is deprecated\n"); ch->bc->presentation = 1; ch->bc->set_presentation = 1; } break; case 'i' : chan_misdn_log(1, ch->bc->port, "Ignoring dtmf tones, just use them inband\n"); ch->ignore_dtmf = 1; break; default: break; } } if (change_jitter) { config_jitterbuffer(ch); } if (ch->faxdetect || ch->ast_dsp) { if (!ch->dsp) { ch->dsp = ast_dsp_new(); } if (ch->dsp) { ast_dsp_set_features(ch->dsp, DSP_FEATURE_DIGIT_DETECT | DSP_FEATURE_FAX_DETECT); } } if (ch->ast_dsp) { chan_misdn_log(1, ch->bc->port, "SETOPT: with AST_DSP we deactivate mISDN_dsp\n"); ch->bc->nodsp = 1; } return 0; } int chan_misdn_jb_empty(struct misdn_bchannel *bc, char *buf, int len) { struct chan_list *ch; int res; ch = find_chan_by_bc(bc); if (!ch) { return 0; } if (ch->jb) { res = misdn_jb_empty(ch->jb, buf, len); } else { res = 0; } chan_list_unref(ch, "Done emptying jb"); return res; } /*******************************************************/ /***************** JITTERBUFFER ************************/ /*******************************************************/ /* allocates the jb-structure and initialize the elements*/ struct misdn_jb *misdn_jb_init(int size, int upper_threshold) { struct misdn_jb *jb; jb = ast_calloc(1, sizeof(*jb)); if (!jb) { chan_misdn_log(-1, 0, "No free Mem for jb\n"); return NULL; } jb->size = size; jb->upper_threshold = upper_threshold; //jb->wp = 0; //jb->rp = 0; //jb->state_full = 0; //jb->state_empty = 0; //jb->bytes_wrote = 0; jb->samples = ast_calloc(size, sizeof(*jb->samples)); if (!jb->samples) { ast_free(jb); chan_misdn_log(-1, 0, "No free Mem for jb->samples\n"); return NULL; } jb->ok = ast_calloc(size, sizeof(*jb->ok)); if (!jb->ok) { ast_free(jb->samples); ast_free(jb); chan_misdn_log(-1, 0, "No free Mem for jb->ok\n"); return NULL; } ast_mutex_init(&jb->mutexjb); return jb; } /* frees the data and destroys the given jitterbuffer struct */ void misdn_jb_destroy(struct misdn_jb *jb) { ast_mutex_destroy(&jb->mutexjb); ast_free(jb->ok); ast_free(jb->samples); ast_free(jb); } /* fills the jitterbuffer with len data returns < 0 if there was an error (buffer overflow). */ int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len) { int i; int j; int rp; int wp; if (!jb || ! data) { return 0; } ast_mutex_lock(&jb->mutexjb); wp = jb->wp; rp = jb->rp; for (i = 0; i < len; i++) { jb->samples[wp] = data[i]; jb->ok[wp] = 1; wp = (wp != jb->size - 1) ? wp + 1 : 0; if (wp == jb->rp) { jb->state_full = 1; } } if (wp >= rp) { jb->state_buffer = wp - rp; } else { jb->state_buffer = jb->size - rp + wp; } chan_misdn_log(9, 0, "misdn_jb_fill: written:%d | Buffer status:%d p:%p\n", len, jb->state_buffer, jb); if (jb->state_full) { jb->wp = wp; rp = wp; for (j = 0; j < jb->upper_threshold; j++) { rp = (rp != 0) ? rp - 1 : jb->size - 1; } jb->rp = rp; jb->state_full = 0; jb->state_empty = 1; ast_mutex_unlock(&jb->mutexjb); return -1; } if (!jb->state_empty) { jb->bytes_wrote += len; if (jb->bytes_wrote >= jb->upper_threshold) { jb->state_empty = 1; jb->bytes_wrote = 0; } } jb->wp = wp; ast_mutex_unlock(&jb->mutexjb); return 0; } /* gets len bytes out of the jitterbuffer if available, else only the available data is returned and the return value indicates the number of data. */ int misdn_jb_empty(struct misdn_jb *jb, char *data, int len) { int i; int wp; int rp; int read = 0; ast_mutex_lock(&jb->mutexjb); rp = jb->rp; wp = jb->wp; if (jb->state_empty) { for (i = 0; i < len; i++) { if (wp == rp) { jb->rp = rp; jb->state_empty = 0; ast_mutex_unlock(&jb->mutexjb); return read; } else { if (jb->ok[rp] == 1) { data[i] = jb->samples[rp]; jb->ok[rp] = 0; rp = (rp != jb->size - 1) ? rp + 1 : 0; read += 1; } } } if (wp >= rp) { jb->state_buffer = wp - rp; } else { jb->state_buffer = jb->size - rp + wp; } chan_misdn_log(9, 0, "misdn_jb_empty: read:%d | Buffer status:%d p:%p\n", len, jb->state_buffer, jb); jb->rp = rp; } else { chan_misdn_log(9, 0, "misdn_jb_empty: Wait...requested:%d p:%p\n", len, jb); } ast_mutex_unlock(&jb->mutexjb); return read; } /*******************************************************/ /*************** JITTERBUFFER END *********************/ /*******************************************************/ static void chan_misdn_log(int level, int port, char *tmpl, ...) { va_list ap; char buf[1024]; char port_buf[8]; if (!(0 <= port && port <= max_ports)) { ast_log(LOG_WARNING, "chan_misdn_log called with out-of-range port number! (%d)\n", port); port = 0; level = -1; } else if (!(level == -1 || (misdn_debug_only[port] ? (level == 1 && misdn_debug[port]) || level == misdn_debug[port] : level <= misdn_debug[port]) || (level <= misdn_debug[0] && !ast_strlen_zero(global_tracefile)))) { /* * We are not going to print anything so lets not * go to all the work of generating a string. */ return; } snprintf(port_buf, sizeof(port_buf), "P[%2d] ", port); va_start(ap, tmpl); vsnprintf(buf, sizeof(buf), tmpl, ap); va_end(ap); if (level == -1) { ast_log(LOG_WARNING, "%s", buf); } else if (misdn_debug_only[port] ? (level == 1 && misdn_debug[port]) || level == misdn_debug[port] : level <= misdn_debug[port]) { ast_verbose("%s%s", port_buf, buf); } if (level <= misdn_debug[0] && !ast_strlen_zero(global_tracefile)) { char ctimebuf[30]; time_t tm; char *tmp; char *p; FILE *fp; fp = fopen(global_tracefile, "a+"); if (!fp) { ast_verbose("Error opening Tracefile: [ %s ] %s\n", global_tracefile, strerror(errno)); return; } tm = time(NULL); tmp = ctime_r(&tm, ctimebuf); p = strchr(tmp, '\n'); if (p) { *p = ':'; } fputs(tmp, fp); fputs(" ", fp); fputs(port_buf, fp); fputs(" ", fp); fputs(buf, fp); fclose(fp); } } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Channel driver for mISDN Support (BRI/PRI)", .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, ); asterisk-13.1.0/channels/sip/0000755000000000000000000000000012443600112014476 5ustar rootrootasterisk-13.1.0/channels/sip/include/0000755000000000000000000000000012443600112016121 5ustar rootrootasterisk-13.1.0/channels/sip/include/sip_utils.h0000644000000000000000000000631511715006417020323 0ustar rootroot/* * 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. */ /*! * \file * \brief sip utils header file */ #ifndef _SIP_UTILS_H #define _SIP_UTILS_H /* wrapper macro to tell whether t points to one of the sip_tech descriptors */ #define IS_SIP_TECH(t) ((t) == &sip_tech || (t) == &sip_tech_info) /*! * \brief converts ascii port to int representation. * * \arg pt[in] string that contains a port. * \arg standard[in] port to return in case the port string input is NULL * or if there is a parsing error. * * \return An integer port representation. */ unsigned int port_str2int(const char *pt, unsigned int standard); /*! \brief 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 *find_closing_quote(const char *start, const char *lim); /*! \brief Convert SIP hangup causes to Asterisk hangup causes */ int hangup_sip2cause(int cause); /*! \brief Convert Asterisk hangup causes to SIP codes \verbatim Possible values from causes.h AST_CAUSE_NOTDEFINED AST_CAUSE_NORMAL AST_CAUSE_BUSY AST_CAUSE_FAILURE AST_CAUSE_CONGESTION AST_CAUSE_UNALLOCATED In addition to these, a lot of PRI codes is defined in causes.h ...should we take care of them too ? Quote RFC 3398 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 \endverbatim */ const char *hangup_cause2sip(int cause); /*! \brief Return a string describing the force_rport value for the given flags */ const char *force_rport_string(struct ast_flags *flags); /*! \brief Return a string describing the comedia value for the given flags */ const char *comedia_string(struct ast_flags *flags); #endif asterisk-13.1.0/channels/sip/include/reqresp_parser.h0000644000000000000000000001627712424600510021345 0ustar rootroot/* * 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. */ /*! * \file * \brief sip request response parser header file */ #ifndef _SIP_REQRESP_H #define _SIP_REQRESP_H /*! \brief uri parameters */ struct uriparams { char *transport; char *user; char *method; char *ttl; char *maddr; int lr; }; struct contact { AST_LIST_ENTRY(contact) list; char *name; char *user; char *pass; char *hostport; struct uriparams params; char *headers; char *expires; char *q; }; AST_LIST_HEAD_NOLOCK(contactliststruct, contact); /*! * \brief parses a URI in its components. * * \note * - Multiple scheme's can be specified ',' delimited. ex: "sip:,sips:" * - If a component is not requested, do not split around it. This means * that if we don't have domain, we cannot split name:pass. * - It is safe to call with ret_name, pass, hostport pointing all to * the same place. * - If no secret parameter is provided, ret_name will return with both * parts, user:secret. * - If the URI contains a port number, hostport will return with both * parts, host:port. * - This function overwrites the the URI string. * * \retval 0 on success * \retval -1 on error. * * \verbatim * general form we are expecting is sip:user:password;user-parameters@host:port;uri-parameters?headers * \endverbatim */ int parse_uri(char *uri, const char *scheme, char **ret_name, char **pass, char **hostport, char **transport); /*! * \brief parses a URI in to all of its components and any trailing residue * * \retval 0 on success * \retval -1 on error. * */ int parse_uri_full(char *uri, const char *scheme, char **user, char **pass, char **hostport, struct uriparams *params, char **headers, char **residue); /*! * \brief Get caller id name from SIP headers, copy into output buffer * * \retval input string pointer placed after display-name field if possible */ const char *get_calleridname(const char *input, char *output, size_t outputsize); /*! * \brief Get name and number from sip header * * \note name and number point to malloced memory on return and must be * freed. If name or number is not found, they will be returned as NULL. * * \retval 0 success * \retval -1 failure */ int get_name_and_number(const char *hdr, char **name, char **number); /*! \brief Pick out text in brackets from character string * \return pointer to terminated stripped string * \param tmp input string that will be modified * * Examples: * \verbatim * "foo" valid input, returns bar * foo returns the whole string * < "foo ... > returns the string between brackets * < "foo... bogus (missing closing bracket), returns the whole string * \endverbatim */ char *get_in_brackets(char *tmp); /*! \brief Get text in brackets on a const without copy * * \param src String to search * \param[out] start Set to first character inside left bracket. * \param[out] length Set to lenght of string inside brackets * \retval 0 success * \retval -1 failure * \retval 1 no brackets so got all */ int get_in_brackets_const(const char *src,const char **start,int *length); /*! \brief Get text in brackets and any trailing residue * * \retval 0 success * \retval -1 failure * \retval 1 no brackets so got all */ int get_in_brackets_full(char *tmp, char **out, char **residue); /*! \brief Parse the ABNF structure * name-andor-addr = name-addr / addr-spec * into its components and return any trailing message-header parameters * * \retval 0 success * \retval -1 failure */ int parse_name_andor_addr(char *uri, const char *scheme, char **name, char **user, char **pass, char **domain, struct uriparams *params, char **headers, char **remander); /*! \brief Parse all contact header contacts * \retval 0 success * \retval -1 failure * \retval 1 all contacts (star) */ int get_comma(char *parse, char **out); int parse_contact_header(char *contactheader, struct contactliststruct *contactlist); /*! * \brief register request parsing tests */ void sip_request_parser_register_tests(void); /*! * \brief unregister request parsing tests */ void sip_request_parser_unregister_tests(void); /*! * \brief Parse supported header in incoming packet * * \details 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. * * \param option list * \param unsupported out buffer (optional) * \param unsupported out buffer length (optional) * * \note Because this function can be called multiple times, it will append * whatever options are specified in \c options to \c unsupported. Callers * of this function should make sure the unsupported buffer is clear before * calling this function. */ unsigned int parse_sip_options(const char *options, char *unsupported, size_t unsupported_len); /*! * \brief Compare two URIs as described in RFC 3261 Section 19.1.4 * * \param input1 First URI * \param input2 Second URI * \retval 0 URIs match * \retval nonzero URIs do not match or one or both is malformed */ int sip_uri_cmp(const char *input1, const char *input2); /*! * \brief initialize request and response parser data * * \retval 0 Success * \retval -1 Failure */ int sip_reqresp_parser_init(void); /*! * \brief Free resources used by request and response parser */ void sip_reqresp_parser_exit(void); /*! * \brief Parse a Via header * * This function parses the Via header and processes it according to section * 18.2 of RFC 3261 and RFC 3581. Since we don't have a transport layer, we * only care about the maddr and ttl parms. The received and rport params are * not parsed. * * \note This function fails to parse some odd combinations of SWS in parameter * lists. * * \code * VIA syntax. RFC 3261 section 25.1 * Via = ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm) * via-parm = sent-protocol LWS sent-by *( SEMI via-params ) * via-params = via-ttl / via-maddr * / via-received / via-branch * / via-extension * via-ttl = "ttl" EQUAL ttl * via-maddr = "maddr" EQUAL host * via-received = "received" EQUAL (IPv4address / IPv6address) * via-branch = "branch" EQUAL token * via-extension = generic-param * sent-protocol = protocol-name SLASH protocol-version * SLASH transport * protocol-name = "SIP" / token * protocol-version = token * transport = "UDP" / "TCP" / "TLS" / "SCTP" * / other-transport * sent-by = host [ COLON port ] * ttl = 1*3DIGIT ; 0 to 255 * \endcode */ struct sip_via *parse_via(const char *header); /* * \brief Free parsed Via data. */ void free_via(struct sip_via *v); #endif asterisk-13.1.0/channels/sip/include/dialplan_functions.h0000644000000000000000000000211512146461424022160 0ustar rootroot/* * 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. */ /*! * \file * \brief SIP dialplan functions header file */ #include "sip.h" #ifndef _SIP_DIALPLAN_FUNCTIONS_H #define _SIP_DIALPLAN_FUNCTIONS_H /*! * \brief Channel read dialplan function for SIP */ int sip_acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen); /*! * \brief register dialplan function tests */ void sip_dialplan_function_register_tests(void); /*! * \brief unregister dialplan function tests */ void sip_dialplan_function_unregister_tests(void); #endif /* !defined(_SIP_DIALPLAN_FUNCTIONS_H) */ asterisk-13.1.0/channels/sip/include/sip.h0000644000000000000000000027005612364243261017111 0ustar rootroot/* * 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. */ /*! * \file * \brief chan_sip header file */ #ifndef _SIP_H #define _SIP_H #include "asterisk.h" #include "asterisk/stringfields.h" #include "asterisk/linkedlists.h" #include "asterisk/strings.h" #include "asterisk/tcptls.h" #include "asterisk/test.h" #include "asterisk/channel.h" #include "asterisk/app.h" #include "asterisk/indications.h" #include "asterisk/security_events.h" #include "asterisk/features.h" #include "asterisk/rtp_engine.h" #include "asterisk/netsock2.h" #include "asterisk/features_config.h" #include "route.h" #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif /* Arguments for sip_find_peer */ #define FINDUSERS (1 << 0) #define FINDPEERS (1 << 1) #define FINDALLDEVICES (FINDUSERS | FINDPEERS) #define SIPBUFSIZE 512 /*!< Buffer size for many operations */ #define XMIT_ERROR -2 #define SIP_RESERVED ";/?:@&=+$,# " /*!< Reserved characters in the username part of the URI */ #define DEFAULT_DEFAULT_EXPIRY 120 #define DEFAULT_MIN_EXPIRY 60 #define DEFAULT_MAX_EXPIRY 3600 #define DEFAULT_MWI_EXPIRY 3600 #define DEFAULT_REGISTRATION_TIMEOUT 20 #define DEFAULT_MAX_FORWARDS 70 #define DEFAULT_AUTHLIMIT 100 #define DEFAULT_AUTHTIMEOUT 30 /* guard limit must be larger than guard secs */ /* guard min must be < 1000, and should be >= 250 */ #define EXPIRY_GUARD_SECS 15 /*!< How long before expiry do we reregister */ #define EXPIRY_GUARD_LIMIT 30 /*!< Below here, we use EXPIRY_GUARD_PCT instead of EXPIRY_GUARD_SECS */ #define 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 EXPIRY_GUARD_PCT 0.20 /*!< Percentage of expires timeout to use when * below EXPIRY_GUARD_LIMIT */ #define DEFAULT_EXPIRY 900 /*!< Expire slowly */ #define DEFAULT_QUALIFY_GAP 100 #define DEFAULT_QUALIFY_PEERS 1 #define CALLERID_UNKNOWN "Anonymous" #define FROMDOMAIN_INVALID "anonymous.invalid" #define DEFAULT_MAXMS 2000 /*!< Qualification: Must be faster than 2 seconds by default */ #define DEFAULT_QUALIFYFREQ 60 * 1000 /*!< Qualification: How often to check for the host to be up */ #define DEFAULT_FREQ_NOTOK 10 * 1000 /*!< Qualification: How often to check, if the host is down... */ #define DEFAULT_RETRANS 1000 /*!< How frequently to retransmit Default: 2 * 500 ms in RFC 3261 */ #define DEFAULT_TIMER_T1 500 /*!< SIP timer T1 (according to RFC 3261) */ #define SIP_TRANS_TIMEOUT 64 * DEFAULT_TIMER_T1 /*!< SIP request timeout (rfc 3261) 64*T1 * \todo Use known T1 for timeout (peerpoke) */ #define DEFAULT_TRANS_TIMEOUT -1 /*!< Use default SIP transaction timeout */ #define PROVIS_KEEPALIVE_TIMEOUT 60000 /*!< How long to wait before retransmitting a provisional response (rfc 3261 13.3.1.1) */ #define MAX_AUTHTRIES 3 /*!< Try authentication three times, then fail */ #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) */ #define SIP_MAX_PACKET_SIZE 20480 /*!< Max SIP packet size */ #define SIP_MIN_PACKET 4096 /*!< Initialize size of memory to allocate for packets */ #define MAX_HISTORY_ENTRIES 50 /*!< Max entires in the history list for a sip_pvt */ #define INITIAL_CSEQ 101 /*!< Our initial sip sequence number */ #define DEFAULT_MAX_SE 1800 /*!< Session-Timer Default Session-Expires period (RFC 4028) */ #define DEFAULT_MIN_SE 90 /*!< Session-Timer Default Min-SE period (RFC 4028) */ #define SDP_MAX_RTPMAP_CODECS 32 /*!< Maximum number of codecs allowed in received SDP */ #define RTP 1 #define NO_RTP 0 #define DEC_CALL_LIMIT 0 #define INC_CALL_LIMIT 1 #define DEC_CALL_RINGING 2 #define INC_CALL_RINGING 3 /*! 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, but will in the future. * This is documented in RFC 3261 */ #define SUPPORTED 1 #define NOT_SUPPORTED 0 /* SIP options */ #define SIP_OPT_REPLACES (1 << 0) #define SIP_OPT_100REL (1 << 1) #define SIP_OPT_TIMER (1 << 2) #define SIP_OPT_EARLY_SESSION (1 << 3) #define SIP_OPT_JOIN (1 << 4) #define SIP_OPT_PATH (1 << 5) #define SIP_OPT_PREF (1 << 6) #define SIP_OPT_PRECONDITION (1 << 7) #define SIP_OPT_PRIVACY (1 << 8) #define SIP_OPT_SDP_ANAT (1 << 9) #define SIP_OPT_SEC_AGREE (1 << 10) #define SIP_OPT_EVENTLIST (1 << 11) #define SIP_OPT_GRUU (1 << 12) #define SIP_OPT_TARGET_DIALOG (1 << 13) #define SIP_OPT_NOREFERSUB (1 << 14) #define SIP_OPT_HISTINFO (1 << 15) #define SIP_OPT_RESPRIORITY (1 << 16) #define SIP_OPT_FROMCHANGE (1 << 17) #define SIP_OPT_RECLISTINV (1 << 18) #define SIP_OPT_RECLISTSUB (1 << 19) #define SIP_OPT_OUTBOUND (1 << 20) #define SIP_OPT_UNKNOWN (1 << 21) /*! \brief SIP Methods we support * \todo This string should be set dynamically. We only support REFER and SUBSCRIBE if we have * allowsubscribe and allowrefer on in sip.conf. */ #define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE" /*! \brief Standard SIP unsecure port for UDP and TCP from RFC 3261. DO NOT CHANGE THIS */ #define STANDARD_SIP_PORT 5060 /*! \brief Standard SIP TLS port from RFC 3261. DO NOT CHANGE THIS */ #define STANDARD_TLS_PORT 5061 /*! \note in many SIP headers, absence of a port number implies port 5060, * and this is why we cannot change the above 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. * */ /*! \name DefaultValues 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 DEFAULT_CONTEXT "default" /*!< The default context for [general] section as well as devices */ #define DEFAULT_RECORD_FEATURE "automon" /*!< The default feature specified for use with INFO */ #define DEFAULT_MOHINTERPRET "default" /*!< The default music class */ #define DEFAULT_MOHSUGGEST "" #define DEFAULT_VMEXTEN "asterisk" /*!< Default voicemail extension */ #define DEFAULT_CALLERID "asterisk" /*!< Default caller ID */ #define DEFAULT_MWI_FROM "" #define DEFAULT_NOTIFYMIME "application/simple-message-summary" #define DEFAULT_ALLOWGUEST TRUE #define DEFAULT_RTPKEEPALIVE 0 /*!< Default RTPkeepalive setting */ #define DEFAULT_CALLCOUNTER FALSE /*!< Do not enable call counters by default */ #define DEFAULT_SRVLOOKUP TRUE /*!< Recommended setting is ON */ #define DEFAULT_COMPACTHEADERS FALSE /*!< Send compact (one-character) SIP headers. Default off */ #define 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 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 DEFAULT_TOS_VIDEO 0 /*!< Video packets should be marked as DSCP AF41, but the default is 0 to be compatible with previous versions. */ #define DEFAULT_TOS_TEXT 0 /*!< Text packets should be marked as XXXX XXXX, but the default is 0 to be compatible with previous versions. */ #define DEFAULT_COS_SIP 4 /*!< Level 2 class of service for SIP signalling */ #define DEFAULT_COS_AUDIO 5 /*!< Level 2 class of service for audio media */ #define DEFAULT_COS_VIDEO 6 /*!< Level 2 class of service for video media */ #define DEFAULT_COS_TEXT 5 /*!< Level 2 class of service for text media (T.140) */ #define DEFAULT_ALLOW_EXT_DOM TRUE /*!< Allow external domains */ #define DEFAULT_REALM "asterisk" /*!< Realm for HTTP digest authentication */ #define DEFAULT_DOMAINSASREALM FALSE /*!< Use the domain option to guess the realm for registration and invite requests */ #define DEFAULT_NOTIFYRINGING TRUE /*!< Notify devicestate system on ringing state */ #define DEFAULT_NOTIFYCID DISABLED /*!< Include CID with ringing notifications */ #define DEFAULT_PEDANTIC TRUE /*!< Follow SIP standards for dialog matching */ #define DEFAULT_AUTOCREATEPEER AUTOPEERS_DISABLED /*!< Don't create peers automagically */ #define DEFAULT_MATCHEXTERNADDRLOCALLY FALSE /*!< Match extern IP locally default setting */ #define DEFAULT_QUALIFY FALSE /*!< Don't monitor devices */ #define DEFAULT_KEEPALIVE 0 /*!< Don't send keep alive packets */ #define DEFAULT_KEEPALIVE_INTERVAL 60 /*!< Send keep alive packets at 60 second intervals */ #define DEFAULT_ALWAYSAUTHREJECT TRUE /*!< Don't reject authentication requests always */ #define DEFAULT_AUTH_OPTIONS FALSE #define DEFAULT_AUTH_MESSAGE TRUE #define DEFAULT_ACCEPT_OUTOFCALL_MESSAGE TRUE #define DEFAULT_REGEXTENONQUALIFY FALSE #define DEFAULT_LEGACY_USEROPTION_PARSING FALSE #define DEFAULT_SEND_DIVERSION TRUE #define DEFAULT_T1MIN 100 /*!< 100 MS for minimal roundtrip time */ #define DEFAULT_MAX_CALL_BITRATE (384) /*!< Max bitrate for video */ #ifndef DEFAULT_USERAGENT #define DEFAULT_USERAGENT "Asterisk PBX" /*!< Default Useragent: header unless re-defined in sip.conf */ #define DEFAULT_SDPSESSION "Asterisk PBX" /*!< Default SDP session name, (s=) header unless re-defined in sip.conf */ #define DEFAULT_SDPOWNER "root" /*!< Default SDP username field in (o=) header unless re-defined in sip.conf */ #define DEFAULT_ENGINE "asterisk" /*!< Default RTP engine to use for sessions */ #define DEFAULT_STORE_SIP_CAUSE FALSE /*!< Don't store HASH(SIP_CAUSE,) for channels by default */ #endif /*@}*/ /*! \name SIPflags Various flags for the flags field in the pvt structure Trying to sort these up (one or more of the following): D: Dialog P: Peer/user G: Global flag When flags are used by multiple structures, it is important that they have a common layout so it is easy to copy them. */ /*@{*/ #define SIP_OUTGOING (1 << 0) /*!< D: Direction of the last transaction in this dialog */ #define SIP_OFFER_CC (1 << 1) /*!< D: Offer CC on subsequent responses */ #define SIP_RINGING (1 << 2) /*!< D: Have sent 180 ringing */ #define SIP_PROGRESS_SENT (1 << 3) /*!< D: Have sent 183 message progress */ #define SIP_NEEDREINVITE (1 << 4) /*!< D: Do we need to send another reinvite? */ #define SIP_PENDINGBYE (1 << 5) /*!< D: Need to send bye after we ack? */ #define SIP_GOTREFER (1 << 6) /*!< D: Got a refer? */ #define SIP_CALL_LIMIT (1 << 7) /*!< D: Call limit enforced for this call */ #define SIP_INC_COUNT (1 << 8) /*!< D: Did this dialog increment the counter of in-use calls? */ #define SIP_INC_RINGING (1 << 9) /*!< D: Did this connection increment the counter of in-use calls? */ #define SIP_DEFER_BYE_ON_TRANSFER (1 << 10) /*!< D: Do not hangup at first ast_hangup */ #define SIP_PROMISCREDIR (1 << 11) /*!< DP: Promiscuous redirection */ #define SIP_TRUSTRPID (1 << 12) /*!< DP: Trust RPID headers? */ #define SIP_USEREQPHONE (1 << 13) /*!< DP: Add user=phone to numeric URI. Default off */ #define SIP_USECLIENTCODE (1 << 14) /*!< DP: Trust X-ClientCode info message */ /* DTMF flags - see str2dtmfmode() and dtmfmode2str() */ #define SIP_DTMF (7 << 15) /*!< DP: DTMF Support: five settings, uses three bits */ #define SIP_DTMF_RFC2833 (0 << 15) /*!< DP: DTMF Support: RTP DTMF - "rfc2833" */ #define SIP_DTMF_INBAND (1 << 15) /*!< DP: DTMF Support: Inband audio, only for ULAW/ALAW - "inband" */ #define SIP_DTMF_INFO (2 << 15) /*!< DP: DTMF Support: SIP Info messages - "info" */ #define SIP_DTMF_AUTO (3 << 15) /*!< DP: DTMF Support: AUTO switch between rfc2833 and in-band DTMF */ #define SIP_DTMF_SHORTINFO (4 << 15) /*!< DP: DTMF Support: SIP Info messages - "info" - short variant */ /* NAT settings */ #define SIP_NAT_FORCE_RPORT (1 << 18) /*!< DP: Force rport even if not present in the request */ #define SIP_NAT_RPORT_PRESENT (1 << 19) /*!< DP: rport was present in the request */ /* re-INVITE related settings */ #define SIP_REINVITE (7 << 20) /*!< DP: four settings, uses three bits */ #define SIP_REINVITE_NONE (0 << 20) /*!< DP: no reinvite allowed */ #define SIP_DIRECT_MEDIA (1 << 20) /*!< DP: allow peers to be reinvited to send media directly p2p */ #define SIP_DIRECT_MEDIA_NAT (2 << 20) /*!< DP: allow media reinvite when new peer is behind NAT */ #define SIP_REINVITE_UPDATE (4 << 20) /*!< DP: use UPDATE (RFC3311) when reinviting this peer */ /* "insecure" settings - see insecure2str() */ #define SIP_INSECURE (3 << 23) /*!< DP: three settings, uses two bits */ #define SIP_INSECURE_NONE (0 << 23) /*!< DP: secure mode */ #define SIP_INSECURE_PORT (1 << 23) /*!< DP: don't require matching port for incoming requests */ #define SIP_INSECURE_INVITE (1 << 24) /*!< DP: don't require authentication for incoming INVITEs */ /* Sending PROGRESS in-band settings */ #define SIP_PROG_INBAND (3 << 25) /*!< DP: three settings, uses two bits */ #define SIP_PROG_INBAND_NEVER (0 << 25) #define SIP_PROG_INBAND_NO (1 << 25) #define SIP_PROG_INBAND_YES (2 << 25) #define SIP_USEPATH (1 << 27) /*!< GDP: Trust and use incoming Path headers? */ #define SIP_SENDRPID (3 << 29) /*!< DP: Remote Party-ID Support */ #define SIP_SENDRPID_NO (0 << 29) #define SIP_SENDRPID_PAI (1 << 29) /*!< Use "P-Asserted-Identity" for rpid */ #define SIP_SENDRPID_RPID (2 << 29) /*!< Use "Remote-Party-ID" for rpid */ #define SIP_G726_NONSTANDARD (1 << 31) /*!< DP: Use non-standard packing for G726-32 data */ /*! \brief Flags to copy from peer/user to dialog */ #define SIP_FLAGS_TO_COPY \ (SIP_PROMISCREDIR | SIP_TRUSTRPID | SIP_SENDRPID | SIP_DTMF | SIP_REINVITE | \ SIP_PROG_INBAND | SIP_USECLIENTCODE | SIP_NAT_FORCE_RPORT | SIP_G726_NONSTANDARD | \ SIP_USEREQPHONE | SIP_INSECURE | SIP_USEPATH) /*@}*/ /*! \name SIPflags2 a second page of flags (for flags[1] */ /*@{*/ /* realtime flags */ #define SIP_PAGE2_RTCACHEFRIENDS (1 << 0) /*!< GP: Should we keep RT objects in memory for extended time? */ #define SIP_PAGE2_RTAUTOCLEAR (1 << 1) /*!< GP: Should we clean memory from peers after expiry? */ #define SIP_PAGE2_RPID_UPDATE (1 << 2) #define SIP_PAGE2_Q850_REASON (1 << 3) /*!< DP: Get/send cause code via Reason header */ #define SIP_PAGE2_SYMMETRICRTP (1 << 4) /*!< GDP: Whether symmetric RTP is enabled or not */ #define SIP_PAGE2_STATECHANGEQUEUE (1 << 5) /*!< D: Unsent state pending change exists */ #define SIP_PAGE2_CONNECTLINEUPDATE_PEND (1 << 6) #define SIP_PAGE2_RPID_IMMEDIATE (1 << 7) #define SIP_PAGE2_RPORT_PRESENT (1 << 8) /*!< Was rport received in the Via header? */ #define SIP_PAGE2_PREFERRED_CODEC (1 << 9) /*!< GDP: Only respond with single most preferred joint codec */ #define SIP_PAGE2_VIDEOSUPPORT (1 << 10) /*!< DP: Video supported if offered? */ #define SIP_PAGE2_TEXTSUPPORT (1 << 11) /*!< GDP: Global text enable */ #define SIP_PAGE2_ALLOWSUBSCRIBE (1 << 12) /*!< GP: Allow subscriptions from this peer? */ #define SIP_PAGE2_ALLOWOVERLAP (3 << 13) /*!< DP: Allow overlap dialing ? */ #define SIP_PAGE2_ALLOWOVERLAP_NO (0 << 13) /*!< No, terminate with 404 Not found */ #define SIP_PAGE2_ALLOWOVERLAP_YES (1 << 13) /*!< Yes, using the 484 Address Incomplete response */ #define SIP_PAGE2_ALLOWOVERLAP_DTMF (2 << 13) /*!< Yes, using the DTMF transmission through Early Media */ #define SIP_PAGE2_ALLOWOVERLAP_SPARE (3 << 13) /*!< Spare (reserved for another dialling transmission mechanisms like KPML) */ #define SIP_PAGE2_SUBSCRIBEMWIONLY (1 << 15) /*!< GP: Only issue MWI notification if subscribed to */ #define SIP_PAGE2_IGNORESDPVERSION (1 << 16) /*!< GDP: Ignore the SDP session version number we receive and treat all sessions as new */ #define SIP_PAGE2_T38SUPPORT (3 << 17) /*!< GDP: T.38 Fax Support */ #define SIP_PAGE2_T38SUPPORT_UDPTL (1 << 17) /*!< GDP: T.38 Fax Support (no error correction) */ #define SIP_PAGE2_T38SUPPORT_UDPTL_FEC (2 << 17) /*!< GDP: T.38 Fax Support (FEC error correction) */ #define SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY (3 << 17) /*!< GDP: T.38 Fax Support (redundancy error correction) */ #define SIP_PAGE2_CALL_ONHOLD (3 << 19) /*!< D: Call hold states: */ #define SIP_PAGE2_CALL_ONHOLD_ACTIVE (1 << 19) /*!< D: Active hold */ #define SIP_PAGE2_CALL_ONHOLD_ONEDIR (2 << 19) /*!< D: One directional hold */ #define SIP_PAGE2_CALL_ONHOLD_INACTIVE (3 << 19) /*!< D: Inactive hold */ #define SIP_PAGE2_RFC2833_COMPENSATE (1 << 21) /*!< DP: Compensate for buggy RFC2833 implementations */ #define SIP_PAGE2_BUGGY_MWI (1 << 22) /*!< DP: Buggy CISCO MWI fix */ #define SIP_PAGE2_DIALOG_ESTABLISHED (1 << 23) /*!< 29: Has a dialog been established? */ #define SIP_PAGE2_FAX_DETECT (3 << 24) /*!< DP: Fax Detection support */ #define SIP_PAGE2_FAX_DETECT_CNG (1 << 24) /*!< DP: Fax Detection support - detect CNG in audio */ #define SIP_PAGE2_FAX_DETECT_T38 (2 << 24) /*!< DP: Fax Detection support - detect T.38 reinvite from peer */ #define SIP_PAGE2_FAX_DETECT_BOTH (3 << 24) /*!< DP: Fax Detection support - detect both */ #define SIP_PAGE2_UDPTL_DESTINATION (1 << 26) /*!< DP: Use source IP of RTP as destination if NAT is enabled */ #define SIP_PAGE2_VIDEOSUPPORT_ALWAYS (1 << 27) /*!< DP: Always set up video, even if endpoints don't support it */ #define SIP_PAGE2_HAVEPEERCONTEXT (1 << 28) /*< Are we associated with a configured peer context? */ #define SIP_PAGE2_USE_SRTP (1 << 29) /*!< DP: Whether we should offer (only) SRTP */ #define SIP_PAGE2_TRUST_ID_OUTBOUND (3 << 30) /*!< DP: Do we trust the peer with private presence information? */ #define SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY (0 << 30) /*!< Legacy, Do not provide private presence information, but include PAI/RPID when private */ #define SIP_PAGE2_TRUST_ID_OUTBOUND_NO (1 << 30) /*!< No, Do not provide private presence information, do not include PAI/RPID when private */ #define SIP_PAGE2_TRUST_ID_OUTBOUND_YES (2 << 30) /*!< Yes, provide private presence information in PAI/RPID headers */ #define SIP_PAGE2_FLAGS_TO_COPY \ (SIP_PAGE2_ALLOWSUBSCRIBE | SIP_PAGE2_ALLOWOVERLAP | SIP_PAGE2_IGNORESDPVERSION | \ SIP_PAGE2_VIDEOSUPPORT | SIP_PAGE2_T38SUPPORT | SIP_PAGE2_RFC2833_COMPENSATE | \ SIP_PAGE2_BUGGY_MWI | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ SIP_PAGE2_UDPTL_DESTINATION | SIP_PAGE2_VIDEOSUPPORT_ALWAYS | SIP_PAGE2_PREFERRED_CODEC | \ SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE | SIP_PAGE2_SYMMETRICRTP |\ SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT | SIP_PAGE2_USE_SRTP | SIP_PAGE2_TRUST_ID_OUTBOUND) #define SIP_PAGE3_SNOM_AOC (1 << 0) /*!< DPG: Allow snom aoc messages */ #define SIP_PAGE3_SRTP_TAG_32 (1 << 1) /*!< DP: Use a 32bit auth tag in INVITE not 80bit */ #define SIP_PAGE3_NAT_AUTO_RPORT (1 << 2) /*!< DGP: Set SIP_NAT_FORCE_RPORT when NAT is detected */ #define SIP_PAGE3_NAT_AUTO_COMEDIA (1 << 3) /*!< DGP: Set SIP_PAGE2_SYMMETRICRTP when NAT is detected */ #define SIP_PAGE3_DIRECT_MEDIA_OUTGOING (1 << 4) /*!< DP: Only send direct media reinvites on outgoing calls */ #define SIP_PAGE3_USE_AVPF (1 << 5) /*!< DGP: Support a minimal AVPF-compatible profile */ #define SIP_PAGE3_ICE_SUPPORT (1 << 6) /*!< DGP: Enable ICE support */ #define SIP_PAGE3_IGNORE_PREFCAPS (1 << 7) /*!< DP: Ignore prefcaps when setting up an outgoing call leg */ #define SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL (1 << 8) /*!< DGP: Stop telling the peer to start music on hold */ #define SIP_PAGE3_FORCE_AVP (1 << 9) /*!< DGP: Force 'RTP/AVP' for all streams, even DTLS */ #define SIP_PAGE3_FLAGS_TO_COPY \ (SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \ SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF | SIP_PAGE3_ICE_SUPPORT | SIP_PAGE3_IGNORE_PREFCAPS | \ SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP) #define CHECK_AUTH_BUF_INITLEN 256 /*@}*/ /*----------------------------------------------------------*/ /*---- ENUMS ----*/ /*----------------------------------------------------------*/ /*! \brief Authorization scheme for call transfers * * \note Not a bitfield flag, since there are plans for other modes, * like "only allow transfers for authenticated devices" */ enum transfermodes { TRANSFER_OPENFORALL, /*!< Allow all SIP transfers */ TRANSFER_CLOSED, /*!< Allow no SIP transfers */ }; /*! \brief The result of a lot of functions */ enum sip_result { AST_SUCCESS = 0, /*!< FALSE means success, funny enough */ AST_FAILURE = -1, /*!< Failure code */ }; /*! \brief The results from handling an invite request * * \note Start at these values so we do not conflict with * check_auth_results values when returning from * handle_request_invite. check_auth_results only returned during * authentication routines * */ enum inv_req_result { INV_REQ_SUCCESS = 11, /*!< Success code */ INV_REQ_FAILED = 10, /*!< Failure code */ INV_REQ_ERROR = 9, /*!< Error code */ }; /*! \brief States for the INVITE transaction, not the dialog * \note this is for the INVITE that sets up the dialog */ enum invitestates { INV_NONE = 0, /*!< No state at all, maybe not an INVITE dialog */ INV_CALLING = 1, /*!< Invite sent, no answer */ INV_PROCEEDING = 2, /*!< We got/sent 1xx message */ INV_EARLY_MEDIA = 3, /*!< We got 18x message with to-tag back */ INV_COMPLETED = 4, /*!< Got final response with error. Wait for ACK, then CONFIRMED */ INV_CONFIRMED = 5, /*!< Confirmed response - we've got an ack (Incoming calls only) */ INV_TERMINATED = 6, /*!< Transaction done - either successful (AST_STATE_UP) or failed, but done The only way out of this is a BYE from one side */ INV_CANCELLED = 7, /*!< Transaction cancelled by client or server in non-terminated state */ }; /*! \brief When sending a SIP message, we can send with a few options, depending on * type of SIP request. UNRELIABLE is moslty used for responses to repeated requests, * where the original response would be sent RELIABLE in an INVITE transaction */ enum xmittype { XMIT_CRITICAL = 2, /*!< Transmit critical SIP message reliably, with re-transmits. * If it fails, it's critical and will cause a teardown of the session */ XMIT_RELIABLE = 1, /*!< Transmit SIP message reliably, with re-transmits */ XMIT_UNRELIABLE = 0, /*!< Transmit SIP message without bothering with re-transmits */ }; /*! \brief Results from the parse_register() function */ enum parse_register_result { PARSE_REGISTER_DENIED, PARSE_REGISTER_FAILED, PARSE_REGISTER_UPDATE, PARSE_REGISTER_QUERY, }; /*! \brief Type of subscription, based on the packages we do support, see \ref subscription_types */ enum subscriptiontype { NONE = 0, XPIDF_XML, DIALOG_INFO_XML, CPIM_PIDF_XML, PIDF_XML, MWI_NOTIFICATION, CALL_COMPLETION, }; /*! \brief The number of media types in enum \ref media_type below. */ #define OFFERED_MEDIA_COUNT 4 /*! \brief 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 media_type { SDP_AUDIO, /*!< RTP/AVP Audio */ SDP_VIDEO, /*!< RTP/AVP Video */ SDP_IMAGE, /*!< Image udptl, not TCP or RTP */ SDP_TEXT, /*!< RTP/AVP Realtime Text */ SDP_UNKNOWN, /*!< Unknown media type */ }; /*! \brief Authentication types - proxy or www authentication * \note 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_auth_type { PROXY_AUTH = 407, WWW_AUTH = 401, }; /*! \brief Result from get_destination function */ enum sip_get_dest_result { SIP_GET_DEST_EXTEN_MATCHMORE = 1, SIP_GET_DEST_EXTEN_FOUND = 0, SIP_GET_DEST_EXTEN_NOT_FOUND = -1, SIP_GET_DEST_REFUSED = -2, SIP_GET_DEST_INVALID_URI = -3, }; /*! \brief Authentication result from check_auth* functions */ enum check_auth_result { AUTH_DONT_KNOW = -100, /*!< no result, need to check further */ /* XXX maybe this is the same as AUTH_NOT_FOUND */ AUTH_SUCCESSFUL = 0, AUTH_CHALLENGE_SENT = 1, AUTH_SECRET_FAILED = -1, AUTH_USERNAME_MISMATCH = -2, AUTH_NOT_FOUND = -3, /*!< returned by register_verify */ AUTH_UNKNOWN_DOMAIN = -5, AUTH_PEER_NOT_DYNAMIC = -6, AUTH_ACL_FAILED = -7, AUTH_BAD_TRANSPORT = -8, AUTH_RTP_FAILED = -9, AUTH_SESSION_LIMIT = -10, }; /*! \brief States for outbound registrations (with register= lines in sip.conf */ enum sipregistrystate { REG_STATE_UNREGISTERED = 0, /*!< We are not registered * \note Initial state. We should have a timeout scheduled for the initial * (or next) registration transmission, calling sip_reregister */ REG_STATE_REGSENT, /*!< Registration request sent * \note sent initial request, waiting for an ack or a timeout to * retransmit the initial request. */ REG_STATE_AUTHSENT, /*!< We have tried to authenticate * \note entered after transmit_register with auth info, * waiting for an ack. */ REG_STATE_REGISTERED, /*!< Registered and done */ REG_STATE_REJECTED, /*!< Registration rejected * \note 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). */ REG_STATE_TIMEOUT, /*!< Registration timed out * \note XXX unused */ REG_STATE_NOAUTH, /*!< We have no accepted credentials * \note fatal - no chance to proceed */ REG_STATE_FAILED, /*!< Registration failed after several tries * \note fatal - no chance to proceed */ }; /*! \brief Modes in which Asterisk can be configured to run SIP Session-Timers */ enum st_mode { SESSION_TIMER_MODE_INVALID = 0, /*!< Invalid value */ SESSION_TIMER_MODE_ACCEPT, /*!< Honor inbound Session-Timer requests */ SESSION_TIMER_MODE_ORIGINATE, /*!< Originate outbound and honor inbound requests */ SESSION_TIMER_MODE_REFUSE /*!< Ignore inbound Session-Timers requests */ }; /*! \brief The entity playing the refresher role for Session-Timers */ enum st_refresher { SESSION_TIMER_REFRESHER_AUTO, /*!< Negotiated */ SESSION_TIMER_REFRESHER_US, /*!< Initially prefer session refresh by Asterisk */ SESSION_TIMER_REFRESHER_THEM, /*!< Initially prefer session refresh by the other side */ }; enum st_refresher_param { SESSION_TIMER_REFRESHER_PARAM_UNKNOWN, SESSION_TIMER_REFRESHER_PARAM_UAC, SESSION_TIMER_REFRESHER_PARAM_UAS, }; /*! \brief Automatic peer registration behavior */ enum autocreatepeer_mode { AUTOPEERS_DISABLED = 0, /*!< Automatic peer creation disabled */ AUTOPEERS_VOLATILE, /*!< Automatic peers dropped on sip reload (pre-1.8 behavior) */ AUTOPEERS_PERSIST /*!< Automatic peers survive sip configuration reload */ }; /*! \brief States whether a SIP message can create a dialog in Asterisk. */ enum can_create_dialog { CAN_NOT_CREATE_DIALOG, CAN_CREATE_DIALOG, CAN_CREATE_DIALOG_UNSUPPORTED_METHOD, }; /*! \brief SIP Request methods known by Asterisk * * \note Do _NOT_ make any changes to this enum, or the array following it; * if you think you are doing the right thing, you are probably * not doing the right thing. If you think there are changes * needed, get someone else to review them first _before_ * submitting a patch. If these two lists do not match properly * bad things will happen. */ enum sipmethod { SIP_UNKNOWN, /*!< Unknown response */ SIP_RESPONSE, /*!< 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, /*!< Not supported in Asterisk */ SIP_PING, /*!< Not supported at all, no standard but still implemented out there */ }; /*! \brief Settings for the 'notifycid' option, see sip.conf.sample for details. */ enum notifycid_setting { DISABLED = 0, ENABLED = 1, IGNORE_CONTEXT = 2, }; /*! \brief Modes for SIP domain handling in the PBX */ enum domain_mode { SIP_DOMAIN_AUTO, /*!< This domain is auto-configured */ SIP_DOMAIN_CONFIG, /*!< This domain is from configuration */ }; /*! \brief debugging state * 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_e { sip_debug_none = 0, sip_debug_config = 1, sip_debug_console = 2, }; /*! \brief T38 States for a call */ enum t38state { T38_DISABLED = 0, /*!< Not enabled */ T38_LOCAL_REINVITE, /*!< Offered from local - REINVITE */ T38_PEER_REINVITE, /*!< Offered from peer - REINVITE */ T38_ENABLED, /*!< Negotiated (enabled) */ T38_REJECTED /*!< Refused */ }; /*! \brief Parameters to know status of transfer */ enum referstatus { REFER_IDLE, /*!< No REFER is in progress */ REFER_SENT, /*!< Sent REFER to transferee */ REFER_RECEIVED, /*!< Received REFER from transferrer */ REFER_CONFIRMED, /*!< Refer confirmed with a 100 TRYING (unused) */ REFER_ACCEPTED, /*!< Accepted by transferee */ REFER_RINGING, /*!< Target Ringing */ REFER_200OK, /*!< Answered by transfer target */ REFER_FAILED, /*!< REFER declined - go on */ REFER_NOAUTH /*!< We had no auth for REFER */ }; enum sip_peer_type { SIP_TYPE_PEER = (1 << 0), SIP_TYPE_USER = (1 << 1), }; enum t38_action_flag { SDP_T38_NONE = 0, /*!< Do not modify T38 information at all */ SDP_T38_INITIATE, /*!< Remote side has requested T38 with us */ SDP_T38_ACCEPT, /*!< Remote side accepted our T38 request */ }; enum sip_tcptls_alert { TCPTLS_ALERT_DATA, /*!< \brief There is new data to be sent out */ TCPTLS_ALERT_STOP, /*!< \brief A request to stop the tcp_handler thread */ }; enum digest_keys { K_RESP, K_URI, K_USER, K_NONCE, K_LAST }; /*----------------------------------------------------------*/ /*---- STRUCTS ----*/ /*----------------------------------------------------------*/ /*! \brief 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_cfg.outboundproxy. */ struct sip_proxy { char name[MAXHOSTNAMELEN]; /*!< DNS name of domain/host or IP */ struct ast_sockaddr ip; /*!< Currently used IP address and port */ int port; time_t last_dnsupdate; /*!< When this was resolved */ enum ast_transport transport; int force; /*!< If it's an outbound proxy, Force use of this outbound proxy for all outbound requests */ /* Room for a SRV record chain based on the name */ }; /*! \brief argument for the 'show channels|subscriptions' callback. */ struct __show_chan_arg { int fd; int subscriptions; int numchans; /* return value */ }; /*! \name GlobalSettings Global settings apply to the channel (often settings you can change in the general section of sip.conf */ /*@{*/ /*! \brief a place to store all global settings for the sip channel driver These are settings that will be possibly to apply on a group level later on. \note Do not add settings that only apply to the channel itself and can't be applied to devices (trunks, services, phones) */ struct sip_settings { 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 ignore_regexpire; /*!< G: Ignore expiration of peer */ int rtautoclear; /*!< Realtime ?? */ int directrtpsetup; /*!< Enable support for Direct RTP setup (no re-invites) */ int pedanticsipchecking; /*!< Extra checking ? Default off */ enum autocreatepeer_mode autocreatepeer; /*!< Auto creation of peers at registration? Default off. */ int srvlookup; /*!< SRV Lookup on or off. Default is on */ int allowguest; /*!< allow unauthenticated peers to connect? */ 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 compactheaders; /*!< send compact sip headers */ int allow_external_domains; /*!< Accept calls to external SIP domains? */ int regextenonqualify; /*!< Whether to add/remove regexten when qualifying peers */ int legacy_useroption_parsing; /*!< Whether to strip useroptions in URI via semicolons */ 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 */ enum notifycid_setting notifycid; /*!< Send CID with ringing notifications */ enum transfermodes 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 globals_flags[1] */ char realm[MAXHOSTNAMELEN]; /*!< Default realm */ int domainsasrealm; /*!< Use domains lists as realms */ struct sip_proxy outboundproxy; /*!< Outbound proxy */ char default_context[AST_MAX_CONTEXT]; char default_subscribecontext[AST_MAX_CONTEXT]; char default_record_on_feature[AST_FEATURE_MAX_LEN]; char default_record_off_feature[AST_FEATURE_MAX_LEN]; struct ast_acl_list *contact_acl; /*! \brief Global list of addresses dynamic peers are not allowed to use */ struct ast_format_cap *caps; /*!< Supported codecs */ int tcp_enabled; int default_max_forwards; /*!< Default max forwards (SIP Anti-loop) */ int websocket_write_timeout; /*!< Socket write timeout for websocket transports, in ms */ }; struct ast_websocket; /*! \brief The SIP socket definition */ struct sip_socket { enum ast_transport type; /*!< UDP, TCP or TLS */ int fd; /*!< Filed descriptor, the actual socket */ uint16_t port; struct ast_tcptls_session_instance *tcptls_session; /* If tcp or tls, a socket manager */ struct ast_websocket *ws_session; /*! If ws or wss, a WebSocket session */ }; /*! \brief sip_request: The data grabbed from the UDP socket * * \verbatim * 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 = find_sip_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 get_header() on this kind of packet. * * parse_request() 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 init_req() or init_resp() * (which fills the first line to "METHOD uri SIP/2.0" or "SIP/2.0 code text"), * and then fill the rest with add_header() and add_line(). * The \r\n at the end of the line are still there, so the get_header() * and similar functions don't work on these packets. * \endverbatim */ struct sip_request { 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; /* XXX Do we need to unref socket.ser when the request goes away? */ struct sip_socket socket; /*!< The socket used for this request */ AST_LIST_ENTRY(sip_request) next; unsigned int reqsipoptions; /*!< Items needed for Required header in responses */ }; /* \brief 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 REQ_OFFSET_TO_STR(req,offset) (ast_str_buffer((req)->data) + ((req)->offset)) /*! \brief Parameters to the transmit_invite function */ struct sip_invite_param { int addsipheaders; /*!< Add extra SIP headers */ const char *uri_options; /*!< URI options to add to the URI */ const char *vxml_url; /*!< VXML url for Cisco phones */ char *auth; /*!< Authentication */ char *authheader; /*!< Auth header */ enum sip_auth_type auth_type; /*!< Authentication type */ const char *replaces; /*!< Replaces header for call transfers */ int transfer; /*!< Flag - is this Invite part of a SIP transfer? (invite/replaces) */ struct sip_proxy *outboundproxy; /*!< Outbound proxy URI */ }; /*! \brief 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; }; /*! \brief Domain data structure. \note In the future, we will connect this to a configuration tree specific for this domain */ struct domain { char domain[MAXHOSTNAMELEN]; /*!< SIP domain we are responsible for */ char context[AST_MAX_EXTENSION]; /*!< Incoming context for this domain */ enum domain_mode mode; /*!< How did we find this domain? */ AST_LIST_ENTRY(domain) list; /*!< List mechanics */ }; /*! \brief sip_history: Structure for saving transactions within a SIP dialog */ struct sip_history { AST_LIST_ENTRY(sip_history) list; char event[0]; /* actually more, depending on needs */ }; /*! \brief sip_auth: Credentials for authentication to other SIP services */ struct sip_auth { AST_LIST_ENTRY(sip_auth) node; char realm[AST_MAX_EXTENSION]; /*!< Realm in which these credentials are valid */ char username[256]; /*!< Username */ char secret[256]; /*!< Secret */ char md5secret[256]; /*!< MD5Secret */ }; /*! \brief Container of SIP authentication credentials. */ struct sip_auth_container { AST_LIST_HEAD_NOLOCK(, sip_auth) list; }; /*! \brief T.38 channel settings (at some point we need to make this alloc'ed */ struct t38properties { enum t38state state; /*!< T.38 state */ struct ast_control_t38_parameters our_parms; struct ast_control_t38_parameters their_parms; }; /*! \brief generic struct to map between strings and integers. * Fill it with x-s pairs, terminate with an entry with s = NULL; * Then you can call map_x_s(...) to map an integer to a string, * and map_s_x() for the string -> integer mapping. */ struct _map_x_s { int x; const char *s; }; /*! \brief 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 */ ); int attendedtransfer; /*!< Attended or blind transfer? */ int localtransfer; /*!< Transfer to local domain? */ enum referstatus status; /*!< REFER status */ }; /*! \brief Struct to handle custom SIP notify requests. Dynamically allocated when needed */ struct sip_notify { struct ast_variable *headers; struct ast_str *content; }; /*! \brief Structure that encapsulates all attributes related to running * SIP Session-Timers feature on a per dialog basis. */ struct sip_st_dlg { int st_active; /*!< Session-Timers on/off */ int st_interval; /*!< Session-Timers negotiated session refresh interval */ enum st_refresher st_ref; /*!< Session-Timers cached refresher */ int st_schedid; /*!< Session-Timers ast_sched scheduler id */ int st_active_peer_ua; /*!< Session-Timers on/off in peer UA */ int st_cached_min_se; /*!< Session-Timers cached Min-SE */ int st_cached_max_se; /*!< Session-Timers cached Session-Expires */ enum st_mode st_cached_mode; /*!< Session-Timers cached M.O. */ enum st_refresher st_cached_ref; /*!< Session-Timers session refresher */ unsigned char quit_flag:1; /*!< Stop trying to lock; just quit */ }; /*! \brief Structure that encapsulates all attributes related to configuration * of SIP Session-Timers feature on a per user/peer basis. */ struct sip_st_cfg { enum st_mode st_mode_oper; /*!< Mode of operation for Session-Timers */ enum st_refresher_param st_ref; /*!< Session-Timer refresher */ int st_min_se; /*!< Lowest threshold for session refresh interval */ int st_max_se; /*!< Highest threshold for session refresh interval */ }; /*! \brief Structure for remembering offered media in an INVITE, to make sure we reply to all media streams. */ struct offered_media { enum media_type 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 */ AST_LIST_ENTRY(offered_media) next; }; /*! Additional headers to send with MESSAGE method packet. */ struct sip_msg_hdr { AST_LIST_ENTRY(sip_msg_hdr) next; /*! Name of header to stick in MESSAGE */ const char *name; /*! Value of header to stick in MESSAGE */ const char *value; /*! The name and value strings are stuffed here in that order. */ char stuff[0]; }; /*! \brief Structure used for each SIP dialog, ie. a call, a registration, a subscribe. * Created and initialized by sip_alloc(), the descriptor goes into the list of * descriptors (dialoglist). */ struct sip_pvt { struct sip_pvt *next; /*!< Next dialog in chain */ enum invitestates invitestate; /*!< Track state of SIP_INVITEs */ struct 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 */ /* we only store the part in in this field. */ AST_STRING_FIELD(our_contact); /*!< Our contact header */ 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(tel_phone_context); /*!< The phone-context portion of a TEL URI */ ); char via[128]; /*!< Via: header */ 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 */ /* boolean flags that don't belong in flags */ unsigned short do_history: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 provisonal 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 t38properties 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) */ enum transfermodes 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_container *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 */ char lastmsg[256]; /*!< Last Message sent/received */ 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 transaction 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 */ enum subscriptiontype subscribed; /*!< SUBSCRIBE: Is this dialog a subscription? */ int stateid; /*!< SUBSCRIBE: ID for devicestate subscriptions */ int laststate; /*!< SUBSCRIBE: Last known extension state */ 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_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 peerpoke, 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 */ struct sip_pkt *packets; /*!< 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_hdr) msg_headers; /*!< Additional MESSAGE headers to send. */ AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */ struct sip_invite_param *options; /*!< Options for INVITE */ struct sip_st_dlg *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 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_subscription_mwi *mwi; /*!< If this is a subscription MWI dialog, to which subscription */ /*! 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 allowed_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 */ unsigned int disallowed_methods; /*! 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. * * The reason for the length being 4 (OFFERED_MEDIA_COUNT) is that in this branch of Asterisk, the only media types supported are * image, audio, text, and video. Therefore we need to keep track of which types of media were offered. * Note that secure RTP defines new types of SDP media. * * 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. * * The large-scale changes would be a good idea for implementing during an SDP rewrite. */ AST_LIST_HEAD_NOLOCK(, offered_media) offered_media; struct ast_cc_config_params *cc_params; struct sip_epa_entry *epa_entry; int fromdomainport; /*!< Domain port to show in from field */ struct ast_rtp_dtls_cfg dtls_cfg; }; /*! \brief 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_reliable_xmit() and only for packets that * require retransmissions. */ struct sip_pkt { struct sip_pkt *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; }; /*! * \brief A peer's mailbox * * We could use STRINGFIELDS here, but for only one string, its * too much effort ... */ struct sip_mailbox { /*! Associated MWI subscription */ struct stasis_subscription *event_sub; AST_LIST_ENTRY(sip_mailbox) entry; unsigned int delme:1; char id[1]; }; /*! \brief Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) */ /* XXX field 'name' must be first otherwise sip_addrcmp() will fail, as will astobj2 hashing of the structure */ struct sip_peer { char name[80]; /*!< the unique name of this object */ AST_DECLARE_STRING_FIELDS( 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(record_on_feature); /*!< Feature to use when receiving INFO with record: on during a call */ AST_STRING_FIELD(record_off_feature); /*!< Feature to use when receiving INFO with record: off during a call */ AST_STRING_FIELD(callback); /*!< Callback extension */ ); 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 selfdestruct:1; /*!< Automatic peers need to destruct themselves */ unsigned short the_mark: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_container *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 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 */ enum transfermodes allowtransfer; /*! SIP Refer restriction scheme */ int lastmsgssent; /*!< The last known VM message counts (new/old) */ unsigned int sipoptions; /*!< Supported SIP options */ struct ast_flags flags[3]; /*!< SIP_ flags */ /*! Mailboxes that this peer cares about */ AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes; 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 *call; /*!< Call pointer */ int pokeexpire; /*!< Qualification: When to expire poke (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 ps; /*!< Qualification: Time for sending SIP OPTION in sip_pke_peer() */ 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_st_cfg 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) */ /*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */ enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */ unsigned int disallowed_methods; struct ast_cc_config_params *cc_params; struct ast_endpoint *endpoint; struct ast_rtp_dtls_cfg dtls_cfg; }; /*! * \brief Registrations with other SIP proxies * * Created by sip_register(), the entry is linked in the 'regl' 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(configvalue);/*!< 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(regdomain); /*!< Registration doamin */ 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(callback); /*!< 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 portno; /*!< Optional port override */ int regdomainport; /*!< 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 regattempts; /*!< Number of attempts (since the last success) */ int timeout; /*!< sched id of sip_reg_timeout */ int refresh; /*!< How often to refresh */ struct sip_pvt *call; /*!< create a sip_pvt structure for each outbound "registration dialog" in progress */ enum sipregistrystate regstate; /*!< Registration state (see above) */ struct timeval regtime; /*!< 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 */ char lastmsg[256]; /*!< Last Message sent/received */ }; struct tcptls_packet { AST_LIST_ENTRY(tcptls_packet) entry; struct ast_str *data; size_t len; }; /*! \brief Definition of a thread that handles a socket */ struct sip_threadinfo { /*! TRUE if the thread needs to kill itself. (The module is being unloaded.) */ int stop; 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(, tcptls_packet) packet_q; }; /*! * \brief Definition of an MWI subscription to another server */ struct sip_subscription_mwi { 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 */ ); enum ast_transport transport; /*!< Transport to use */ int portno; /*!< Optional port override */ int resub; /*!< Sched ID of resubscription */ unsigned int subscribed:1; /*!< Whether we are currently subscribed or not */ struct sip_pvt *call; /*!< Outbound subscription dialog */ struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager for subscription */ struct ast_sockaddr us; /*!< Who the server thinks we are */ }; /*! * SIP PUBLISH support! * PUBLISH support was added to chan_sip due to its use in the call-completion * event package. In order to suspend and unsuspend monitoring of a called party, * a PUBLISH message must be sent. Rather than try to hack in PUBLISH transmission * and reception solely for the purposes of handling call-completion-related messages, * an effort has been made to create a generic framework for handling PUBLISH messages. * * There are two main components to the effort, the event publication agent (EPA) and * the event state compositor (ESC). Both of these terms appear in RFC 3903, and the * implementation in Asterisk conforms to the defintions there. An EPA is a UAC that * transmits PUBLISH requests. An ESC is a UAS that receives PUBLISH requests and * acts appropriately based on the content of those requests. * * ESC: * The main structure in chan_sip is the event_state_compositor. There is an * event_state_compositor structure for each event package supported (as of Nov 2009 * this is only the call-completion package). The structure contains data which is * intrinsic to the event package itself, such as the name of the package and a set * of callbacks for handling incoming PUBLISH requests. In addition, the * event_state_compositor struct contains an ao2_container of sip_esc_entries. * * A sip_esc_entry corresponds to an entity which has sent a PUBLISH to Asterisk. We are * able to match the incoming PUBLISH to a sip_esc_entry using the Sip-If-Match header * of the message. Of course, if none is present, then a new sip_esc_entry will be created. * * Once it is determined what type of PUBLISH request has come in (from RFC 3903, it may * be an initial, modify, refresh, or remove), then the event package-specific callbacks * may be called. If your event package doesn't need to take any specific action for a * specific PUBLISH type, it is perfectly safe to not define the callback at all. The callback * only needs to take care of application-specific information. If there is a problem, it is * up to the callback to take care of sending an appropriate 4xx or 5xx response code. In such * a case, the callback should return -1. This will tell the function that called the handler * that an appropriate error response has been sent. If the callback returns 0, however, then * the caller of the callback will generate a new entity tag and send a 200 OK response. * * ESC entries are reference-counted, however as an implementor of a specific event package, * this should be transparent, since the reference counts are handled by the general ESC * framework. * * EPA: * The event publication agent in chan_sip is structured quite a bit differently than the * ESC. With an ESC, an appropriate entry has to be found based on the contents of an incoming * PUBLISH message. With an EPA, the application interested in sending the PUBLISH can maintain * a reference to the appropriate EPA entry instead. Similarly, when matching a PUBLISH response * to an appropriate EPA entry, the sip_pvt can maintain a reference to the corresponding * EPA entry. The result of this train of thought is that there is no compelling reason to * maintain a container of these entries. * * Instead, there is only the sip_epa_entry structure. Every sip_epa_entry has an entity tag * that it maintains so that subsequent PUBLISH requests will be identifiable by the ESC on * the far end. In addition, there is a static_data field which contains information that is * common to all sip_epa_entries for a specific event package. This static data includes the * name of the event package and callbacks for handling specific responses for outgoing PUBLISHes. * Also, there is a field for pointing to instance-specific data. This can include the current * published state or other identifying information that is specific to an instance of an EPA * entry of a particular event package. * * When an application wishes to send a PUBLISH request, it simply will call create_epa_entry, * followed by transmit_publish in order to send the PUBLISH. That's all that is necessary. * Like with ESC entries, sip_epa_entries are reference counted. Unlike ESC entries, though, * sip_epa_entries reference counts have to be maintained to some degree by the application making * use of the sip_epa_entry. The application will acquire a reference to the EPA entry when it * calls create_epa_entry. When the application has finished using the EPA entry (which may not * be until after several PUBLISH transactions have taken place) it must use ao2_ref to decrease * the reference count by 1. */ /*! * \brief The states that can be represented in a SIP call-completion PUBLISH */ enum sip_cc_publish_state { /*! Closed, i.e. unavailable */ CC_CLOSED, /*! Open, i.e. available */ CC_OPEN, }; /*! * \brief The states that can be represented in a SIP call-completion NOTIFY */ enum sip_cc_notify_state { /*! Queued, i.e. unavailable */ CC_QUEUED, /*! Ready, i.e. available */ CC_READY, }; /*! * \brief The types of PUBLISH messages defined in RFC 3903 */ enum sip_publish_type { /*! * \brief Unknown * * \details * This actually is not defined in RFC 3903. We use this as a constant * to indicate that an incoming PUBLISH does not fit into any of the * other categories and is thus invalid. */ SIP_PUBLISH_UNKNOWN, /*! * \brief Initial * * \details * The first PUBLISH sent. This will contain a non-zero Expires header * as well as a body that indicates the current state of the endpoint * that has sent the message. The initial PUBLISH is the only type * of PUBLISH to not contain a Sip-If-Match header in it. */ SIP_PUBLISH_INITIAL, /*! * \brief Refresh * * \details * Used to keep a published state from expiring. This will contain a * non-zero Expires header but no body since its purpose is not to * update state. */ SIP_PUBLISH_REFRESH, /*! * \brief Modify * * \details * Used to change state from its previous value. This will contain * a body updating the published state. May or may not contain an * Expires header. */ SIP_PUBLISH_MODIFY, /*! * \brief Remove * * \details * Used to remove published state from an ESC. This will contain * an Expires header set to 0 and likely no body. */ SIP_PUBLISH_REMOVE, }; /*! * Data which is the same for all instances of an EPA for a * particular event package */ struct epa_static_data { /*! The event type */ enum subscriptiontype event; /*! * The name of the event as it would * appear in a SIP message */ const char *name; /*! * The callback called when a 200 OK is received on an outbound PUBLISH */ void (*handle_ok)(struct sip_pvt *, struct sip_request *, struct sip_epa_entry *); /*! * The callback called when an error response is received on an outbound PUBLISH */ void (*handle_error)(struct sip_pvt *, const int resp, struct sip_request *, struct sip_epa_entry *); /*! * Destructor to call to clean up instance data */ void (*destructor)(void *instance_data); }; /*! * \brief backend for an event publication agent */ struct epa_backend { const struct epa_static_data *static_data; AST_LIST_ENTRY(epa_backend) next; }; struct sip_epa_entry { /*! * When we are going to send a publish, we need to * know the type of PUBLISH to send. */ enum sip_publish_type publish_type; /*! * When we send a PUBLISH, we have to be * sure to include the entity tag that we * received in the previous response. */ char entity_tag[SIPBUFSIZE]; /*! * The destination to which this EPA should send * PUBLISHes. This may be the name of a SIP peer * or a hostname. */ char destination[SIPBUFSIZE]; /*! * The body of the most recently-sent PUBLISH message. * This is useful for situations such as authentication, * in which we must send a message identical to the * one previously sent */ char body[SIPBUFSIZE]; /*! * Every event package has some constant data and * callbacks that all instances will share. This * data resides in this field. */ const struct epa_static_data *static_data; /*! * In addition to the static data that all instances * of sip_epa_entry will have, each instance will * require its own instance-specific data. */ void *instance_data; }; /*! * \brief Instance data for a Call completion EPA entry */ struct cc_epa_entry { /*! * The core ID of the CC transaction * for which this EPA entry belongs. This * essentially acts as a unique identifier * for the entry and is used in the hash * and comparison functions */ int core_id; /*! * We keep the last known state of the * device in question handy in case * it needs to be known by a third party. * Also, in the case where for some reason * we get asked to transmit state that we * already sent, we can just ignore the * request. */ enum sip_cc_publish_state current_state; }; struct event_state_compositor; /*! * \brief common ESC items for all event types * * The entity_id field serves as a means by which * A specific entry may be found. */ struct sip_esc_entry { /*! * The name of the party who * sent us the PUBLISH. This will more * than likely correspond to a peer name. * * This field's utility isn't really that * great. It's mainly just a user-recognizable * handle that can be printed in debug messages. */ const char *device_name; /*! * The event package for which this esc_entry * exists. Most of the time this isn't really * necessary since you'll have easy access to the * ESC which contains this entry. However, in * some circumstances, we won't have the ESC * available. */ const char *event; /*! * The entity ID used when corresponding * with the EPA on the other side. As the * ESC, we generate an entity ID for each * received PUBLISH and store it in this * structure. */ char entity_tag[30]; /*! * The ID for the scheduler. We schedule * destruction of a sip_esc_entry when we * receive a PUBLISH. The destruction is * scheduled for the duration received in * the Expires header. */ int sched_id; /*! * Each ESC entry will be for a specific * event type. Those entries will need to * carry data which is intrinsic to the * ESC entry but which is specific to * the event package */ void *event_specific_data; }; typedef int (* const esc_publish_callback)(struct sip_pvt *, struct sip_request *, struct event_state_compositor *, struct sip_esc_entry *); /*! * \brief Callbacks for SIP ESCs * * \details * The names of the callbacks are self-explanatory. The * corresponding handler is called whenever the specific * type of PUBLISH is received. */ struct sip_esc_publish_callbacks { const esc_publish_callback initial_handler; const esc_publish_callback refresh_handler; const esc_publish_callback modify_handler; const esc_publish_callback remove_handler; }; struct sip_cc_agent_pvt { int offer_timer_id; /* A copy of the original call's Call-ID. * We use this as a search key when attempting * to find a particular sip_pvt. */ char original_callid[SIPBUFSIZE]; /* A copy of the exten called originally. * We use this to set the proper extension * to dial during the recall since the incoming * request URI is one that was generated just * for the recall */ char original_exten[SIPBUFSIZE]; /* A reference to the dialog which we will * be sending a NOTIFY on when it comes time * to send one */ struct sip_pvt *subscribe_pvt; /* When we send a NOTIFY, we include a URI * that should be used by the caller when he * wishes to send a PUBLISH or INVITE to us. * We store that URI here. */ char notify_uri[SIPBUFSIZE]; /* When we advertise call completion to a caller, * we provide a URI for the caller to use when * he sends us a SUBSCRIBE. We store it for matching * purposes when we receive the SUBSCRIBE from the * caller. */ char subscribe_uri[SIPBUFSIZE]; char is_available; }; struct sip_monitor_instance { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(subscribe_uri); AST_STRING_FIELD(notify_uri); AST_STRING_FIELD(peername); AST_STRING_FIELD(device_name); ); int core_id; struct sip_pvt *subscription_pvt; struct sip_epa_entry *suspension_entry; }; /*! \brief List of well-known SIP options. If we get this in a require, we should check the list and answer accordingly. */ static const struct cfsip_options { int id; /*!< Bitmap ID */ int supported; /*!< Supported by Asterisk ? */ char * const text; /*!< Text id, as in standard */ } sip_options[] = { /* XXX used in 3 places */ /* RFC3262: PRACK 100% reliability */ { SIP_OPT_100REL, NOT_SUPPORTED, "100rel" }, /* RFC3959: SIP Early session support */ { SIP_OPT_EARLY_SESSION, NOT_SUPPORTED, "early-session" }, /* SIMPLE events: RFC4662 */ { SIP_OPT_EVENTLIST, NOT_SUPPORTED, "eventlist" }, /* RFC 4916- Connected line ID updates */ { SIP_OPT_FROMCHANGE, NOT_SUPPORTED, "from-change" }, /* GRUU: Globally Routable User Agent URI's */ { SIP_OPT_GRUU, NOT_SUPPORTED, "gruu" }, /* RFC4244 History info */ { SIP_OPT_HISTINFO, NOT_SUPPORTED, "histinfo" }, /* RFC3911: SIP Join header support */ { SIP_OPT_JOIN, NOT_SUPPORTED, "join" }, /* Disable the REFER subscription, RFC 4488 */ { SIP_OPT_NOREFERSUB, NOT_SUPPORTED, "norefersub" }, /* SIP outbound - the final NAT battle - draft-sip-outbound */ { SIP_OPT_OUTBOUND, NOT_SUPPORTED, "outbound" }, /* RFC3327: Path support */ { SIP_OPT_PATH, NOT_SUPPORTED, "path" }, /* RFC3840: Callee preferences */ { SIP_OPT_PREF, NOT_SUPPORTED, "pref" }, /* RFC3312: Precondition support */ { SIP_OPT_PRECONDITION, NOT_SUPPORTED, "precondition" }, /* RFC3323: Privacy with proxies*/ { SIP_OPT_PRIVACY, NOT_SUPPORTED, "privacy" }, /* RFC-ietf-sip-uri-list-conferencing-02.txt conference invite lists */ { SIP_OPT_RECLISTINV, NOT_SUPPORTED, "recipient-list-invite" }, /* RFC-ietf-sip-uri-list-subscribe-02.txt - subscription lists */ { SIP_OPT_RECLISTSUB, NOT_SUPPORTED, "recipient-list-subscribe" }, /* RFC3891: Replaces: header for transfer */ { SIP_OPT_REPLACES, SUPPORTED, "replaces" }, /* One version of Polycom firmware has the wrong label */ { SIP_OPT_REPLACES, SUPPORTED, "replace" }, /* RFC4412 Resource priorities */ { SIP_OPT_RESPRIORITY, NOT_SUPPORTED, "resource-priority" }, /* RFC3329: Security agreement mechanism */ { SIP_OPT_SEC_AGREE, NOT_SUPPORTED, "sec_agree" }, /* RFC4092: Usage of the SDP ANAT Semantics in the SIP */ { SIP_OPT_SDP_ANAT, NOT_SUPPORTED, "sdp-anat" }, /* RFC4028: SIP Session-Timers */ { SIP_OPT_TIMER, SUPPORTED, "timer" }, /* RFC4538: Target-dialog */ { SIP_OPT_TARGET_DIALOG,NOT_SUPPORTED, "tdialog" }, }; struct digestkeys { const char *key; const char *s; }; AST_THREADSTORAGE(check_auth_buf); /*----------------------------------------------------------*/ /*---- FUNCTIONS ----*/ /*----------------------------------------------------------*/ struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int realtime, int which_objects, int devstate_only, int transport); void sip_auth_headers(enum sip_auth_type code, char **header, char **respheader); const char *sip_get_header(const struct sip_request *req, const char *name); const char *sip_get_transport(enum ast_transport t); #ifdef REF_DEBUG #define sip_ref_peer(arg1,arg2) _ref_peer((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__) #define sip_unref_peer(arg1,arg2) _unref_peer((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__) struct sip_peer *_ref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func); void *_unref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func); #else struct sip_peer *sip_ref_peer(struct sip_peer *peer, char *tag); void *sip_unref_peer(struct sip_peer *peer, char *tag); #endif /* REF_DEBUG */ #endif asterisk-13.1.0/channels/sip/include/dialog.h0000644000000000000000000000562212146461424017551 0ustar rootroot/* * 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. */ /*! * \file * \brief sip dialog management header file */ #include "sip.h" #ifndef _SIP_DIALOG_H #define _SIP_DIALOG_H /*! \brief * when we create or delete references, make sure to use these * functions so we keep track of the refcounts. * To simplify the code, we allow a NULL to be passed to dialog_unref(). */ #define dialog_ref(arg1,arg2) dialog_ref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__) #define dialog_unref(arg1,arg2) dialog_unref_debug((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__) struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func); struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func); struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *sin, int useglobal_nat, const int intended_method, struct sip_request *req, struct ast_callid *logger_callid); void sip_scheddestroy_final(struct sip_pvt *p, int ms); void sip_scheddestroy(struct sip_pvt *p, int ms); int sip_cancel_destroy(struct sip_pvt *p); /*! \brief Destroy SIP call structure. * Make it return NULL so the caller can do things like * foo = sip_destroy(foo); * and reduce the chance of bugs due to dangling pointers. */ struct sip_pvt *sip_destroy(struct sip_pvt *p); /*! \brief Destroy SIP call structure. * Make it return NULL so the caller can do things like * foo = sip_destroy(foo); * and reduce the chance of bugs due to dangling pointers. */ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist); /*! * \brief Unlink a dialog from the dialogs container, as well as any other places * that it may be currently stored. * * \note A reference to the dialog must be held before calling * this function, and this function does not release that * reference. * * \note The dialog must not be locked when called. */ void dialog_unlink_all(struct sip_pvt *dialog); /*! \brief Acknowledges receipt of a packet and stops retransmission * called with p locked*/ int __sip_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod); /*! \brief Pretend to ack all packets * called with p locked */ void __sip_pretend_ack(struct sip_pvt *p); /*! \brief Acks receipt of packet, keep it around (used for provisional responses) */ int __sip_semi_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod); #endif /* defined(_SIP_DIALOG_H) */ asterisk-13.1.0/channels/sip/include/security_events.h0000644000000000000000000000306712133023142021531 0ustar rootroot/* * 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. */ /*! * \file * * \brief Generate security events in the SIP channel * * \author Michael L. Young */ #include "sip.h" #ifndef _SIP_SECURITY_EVENTS_H #define _SIP_SECURITY_EVENTS_H void sip_report_invalid_peer(const struct sip_pvt *p); void sip_report_failed_acl(const struct sip_pvt *p, const char *aclname); void sip_report_inval_password(const struct sip_pvt *p, const char *responsechallenge, const char *responsehash); void sip_report_auth_success(const struct sip_pvt *p, uint32_t *using_password); void sip_report_session_limit(const struct sip_pvt *p); void sip_report_failed_challenge_response(const struct sip_pvt *p, const char *response, const char *expected_response); void sip_report_chal_sent(const struct sip_pvt *p); void sip_report_inval_transport(const struct sip_pvt *p, const char *transport); void sip_digest_parser(char *c, struct digestkeys *keys); int sip_report_security_event(const struct sip_pvt *p, const struct sip_request *req, const int res); #endif asterisk-13.1.0/channels/sip/include/config_parser.h0000644000000000000000000000376612145465212021140 0ustar rootroot/* * 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. */ /*! * \file * \brief sip.conf parser header file */ #include "sip.h" #ifndef _SIP_CONF_PARSE_H #define _SIP_CONF_PARSE_H /*! * \brief Parse register=> line in sip.conf * * \retval 0 on success * \retval -1 on failure */ int sip_parse_register_line(struct sip_registry *reg, int default_expiry, const char *value, int lineno); /*! * \brief parses a config line for a host with a transport * * An example input would be: * tls://www.google.com:8056 * * \retval 0 on success * \retval -1 on failure */ int sip_parse_host(char *line, int lineno, char **hostname, int *portnum, enum ast_transport *transport); /*! \brief Parse the comma-separated nat= option values * \param value The comma-separated value * \param mask An array of ast_flags that will be set by this function * and used as a mask for copying the flags later * \param flags An array of ast_flags that will be set by this function * * \note The nat-related values in both mask and flags are assumed to empty. This function * will treat the first "yes" or "no" value in a list of values as overiding all other values * and will stop parsing. Auto values will override their non-auto counterparts. */ void sip_parse_nat_option(const char *value, struct ast_flags *mask, struct ast_flags *flags); /*! * \brief register config parsing tests */ void sip_config_parser_register_tests(void); /*! * \brief unregister config parsing tests */ void sip_config_parser_unregister_tests(void); #endif asterisk-13.1.0/channels/sip/include/route.h0000644000000000000000000000604012276215123017440 0ustar rootroot/* * 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. */ /*! * \file * \brief sip_route header file */ #ifndef _SIP_ROUTE_H #define _SIP_ROUTE_H #include "asterisk/linkedlists.h" #include "asterisk/strings.h" /*! * \brief Opaque storage of a sip route hop */ struct sip_route_hop; /*! * \internal \brief Internal enum to remember last calculated */ enum sip_route_type { route_loose = 0, /*!< The first hop contains ;lr or does not exist */ route_strict, /*!< The first hop exists and does not contain ;lr */ route_invalidated, /*!< strict/loose routing needs to be rechecked */ }; /*! * \brief Structure to store route information * * \note This must be zero-filled on allocation */ struct sip_route { AST_LIST_HEAD_NOLOCK(, sip_route_hop) list; enum sip_route_type type; }; /*! * \brief Add a new hop to the route * * \param route Route * \param uri Address of this hop * \param len Length of hop not including null terminator * \param inserthead If true then inserted the new route to the top of the list * * \retval Pointer to null terminated copy of URI on success * \retval NULL on error */ const char *sip_route_add(struct sip_route *route, const char *uri, size_t len, int inserthead); /*! * \brief Add routes from header * * \note This procedure is for headers that require use of . */ void sip_route_process_header(struct sip_route *route, const char *header, int inserthead); /*! * \brief copy route-set * * \retval non-zero on failure * \retval 0 on success */ void sip_route_copy(struct sip_route *dst, const struct sip_route *src); /*! * \brief Free all routes in the list */ void sip_route_clear(struct sip_route *route); /*! * \brief Verbose dump of all hops for debugging */ void sip_route_dump(const struct sip_route *route); /*! * \brief Make the comma separated list of route hops * * \param route Source of route list * \param formatcli Add's space after comma's, print's N/A if list is empty. * \param skip Number of hops to skip * * \retval an allocated struct ast_str on success * \retval NULL on failure */ struct ast_str *sip_route_list(const struct sip_route *route, int formatcli, int skip) __attribute_malloc__ __attribute_warn_unused_result__; /*! * \brief Check if the route is strict * * \note The result is cached in route->type */ int sip_route_is_strict(struct sip_route *route); /*! * \brief Get the URI of the route's first hop */ const char *sip_route_first_uri(const struct sip_route *route); /*! * \brief Check if route has no URI's */ #define sip_route_empty(route) AST_LIST_EMPTY(&(route)->list) #endif asterisk-13.1.0/channels/sip/include/globals.h0000644000000000000000000000241312146461424017730 0ustar rootroot/* * 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. */ /*! * \file * \brief sip global declaration header file */ #include "sip.h" #ifndef _SIP_GLOBALS_H #define _SIP_GLOBALS_H extern struct ast_sockaddr bindaddr; /*!< UDP: The address we bind to */ extern struct ast_sched_context *sched; /*!< The scheduling context */ /*! \brief Definition of this channel for PBX channel registration */ extern struct ast_channel_tech sip_tech; /*! \brief This version of the sip channel tech has no send_digit_begin * callback so that the core knows that the channel does not want * DTMF BEGIN frames. * The struct is initialized just before registering the channel driver, * and is for use with channels using SIP INFO DTMF. */ extern struct ast_channel_tech sip_tech_info; #endif /* !defined(SIP_GLOBALS_H) */ asterisk-13.1.0/channels/sip/config_parser.c0000644000000000000000000007274712371207211017507 0ustar rootroot/* * 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. */ /*! * \file * \brief sip config parsing functions and unit tests */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 420562 $") #include "include/sip.h" #include "include/config_parser.h" #include "include/sip_utils.h" /*! \brief Parse register=> line in sip.conf * * \retval 0 on success * \retval -1 on failure */ int sip_parse_register_line(struct sip_registry *reg, int default_expiry, const char *value, int lineno) { int portnum = 0; int domainport = 0; enum ast_transport transport = AST_TRANSPORT_UDP; char buf[256] = ""; char *userpart = NULL, *hostpart = NULL; /* register => [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] */ AST_DECLARE_APP_ARGS(pre1, AST_APP_ARG(peer); 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(extension); ); AST_DECLARE_APP_ARGS(host3, AST_APP_ARG(host); AST_APP_ARG(port); ); if (!value) { return -1; } if (!reg) { return -1; } ast_copy_string(buf, value, sizeof(buf)); /*! register => [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] * becomes * userpart => [peer?][transport://]user[@domain][:secret[:authuser]] * hostpart => host[:port][/extension][~expiry] */ if ((hostpart = strrchr(buf, '@'))) { *hostpart++ = '\0'; userpart = buf; } if (ast_strlen_zero(userpart) || ast_strlen_zero(hostpart)) { ast_log(LOG_WARNING, "Format for registration is [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] at line %d\n", lineno); return -1; } /*! * pre1.peer => peer * pre1.userpart => [transport://]user[@domain][:secret[:authuser]] * hostpart => host[:port][/extension][~expiry] */ AST_NONSTANDARD_RAW_ARGS(pre1, userpart, '?'); if (ast_strlen_zero(pre1.userpart)) { pre1.userpart = pre1.peer; pre1.peer = NULL; } /*! * pre1.peer => peer * pre2.transport = transport * pre2.userpart => user[@domain][:secret[:authuser]] * hostpart => host[:port][/extension][~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 [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] at line %d\n", lineno); return -1; } /*! * pre1.peer => peer * pre2.transport = transport * user1.userpart => user[@domain] * user1.secret => secret * user1.authuser => authuser * hostpart => host[:port][/extension][~expiry] */ AST_NONSTANDARD_RAW_ARGS(user1, pre2.userpart, ':'); /*! * pre1.peer => peer * pre2.transport = transport * user1.userpart => user[@domain] * user1.secret => secret * user1.authuser => authuser * host1.hostpart => host[:port][/extension] * host1.expiry => [expiry] */ AST_NONSTANDARD_RAW_ARGS(host1, hostpart, '~'); /*! * pre1.peer => peer * pre2.transport = transport * user1.userpart => user[@domain] * user1.secret => secret * user1.authuser => authuser * host2.hostpart => host[:port] * host2.extension => [extension] * host1.expiry => [expiry] */ AST_NONSTANDARD_RAW_ARGS(host2, host1.hostpart, '/'); /*! * pre1.peer => peer * pre2.transport = transport * user1.userpart => user[@domain] * user1.secret => secret * user1.authuser => authuser * host3.host => host * host3.port => port * host2.extension => extension * host1.expiry => expiry */ AST_NONSTANDARD_RAW_ARGS(host3, host2.hostpart, ':'); /*! * pre1.peer => peer * pre2.transport = transport * user2.user => user * user2.domain => domain * user1.secret => secret * user1.authuser => authuser * host3.host => host * host3.port => port * host2.extension => extension * host1.expiry => expiry */ AST_NONSTANDARD_RAW_ARGS(user2, user1.userpart, '@'); /*! * pre1.peer => peer * 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.extension => extension * host1.expiry => expiry */ AST_NONSTANDARD_RAW_ARGS(user3, user1.authuser, ':'); /* Reordering needed due to fields being [(:secret[:username])|(:regdomainport:secret:username)] but parsing being [secret[:username[:regdomainport]]] */ if (user3.argc == 2) { char *reorder = user3.domainport; user3.domainport = user1.secret; user1.secret = user3.authuser; user3.authuser = reorder; } if (host3.port) { if (!(portnum = port_str2int(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 = port_str2int(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 portnum specified, set default for transport */ if (!portnum) { if (transport == AST_TRANSPORT_TLS) { portnum = STANDARD_TLS_PORT; } else { portnum = STANDARD_SIP_PORT; } } /* copy into sip_registry object */ ast_string_field_set(reg, callback, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); ast_string_field_set(reg, username, ast_strip_quoted(S_OR(user2.user, ""), "\"", "\"")); ast_string_field_set(reg, hostname, ast_strip_quoted(S_OR(host3.host, ""), "\"", "\"")); ast_string_field_set(reg, authuser, ast_strip_quoted(S_OR(user3.authuser, ""), "\"", "\"")); ast_string_field_set(reg, secret, ast_strip_quoted(S_OR(user1.secret, ""), "\"", "\"")); ast_string_field_set(reg, peername, ast_strip_quoted(S_OR(pre1.peer, ""), "\"", "\"")); ast_string_field_set(reg, regdomain, ast_strip_quoted(S_OR(user2.domain, ""), "\"", "\"")); reg->transport = transport; reg->timeout = reg->expire = -1; reg->portno = portnum; reg->regdomainport = domainport; reg->callid_valid = FALSE; reg->ocseq = INITIAL_CSEQ; reg->refresh = reg->expiry = reg->configured_expiry = (host1.expiry ? atoi(ast_strip_quoted(host1.expiry, "\"", "\"")) : default_expiry); return 0; } AST_TEST_DEFINE(sip_parse_register_line_test) { int res = AST_TEST_PASS; struct sip_registry *reg; int default_expiry = 120; const char *reg1 = "name@domain"; const char *reg2 = "name:pass@domain"; const char *reg3 = "name@namedomain:pass:authuser@domain"; const char *reg4 = "name@namedomain:pass:authuser@domain/extension"; const char *reg5 = "tcp://name@namedomain:pass:authuser@domain/extension"; const char *reg6 = "tls://name@namedomain:pass:authuser@domain/extension~111"; const char *reg7 = "peer?tcp://name@namedomain:pass:authuser@domain:1234/extension~111"; const char *reg8 = "peer?name@namedomain:pass:authuser@domain:1234/extension~111"; const char *reg9 = "peer?name:pass:authuser:1234/extension~111"; const char *reg10 = "@domin:1234"; const char *reg12 = "name@namedomain:4321:pass:authuser@domain"; const char *reg13 = "name@namedomain:4321::@domain"; switch (cmd) { case TEST_INIT: info->name = "sip_parse_register_line_test"; info->category = "/channels/chan_sip/"; info->summary = "tests sip register line parsing"; info->description = "Tests parsing of various register line configurations. " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* ---Test reg 1, simple config --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg1, 1) || strcmp(reg->callback, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "") || strcmp(reg->secret, "") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 1: simple config failed\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 2, add secret --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg2, 1) || strcmp(reg->callback, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 2: add secret failed\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 3, add userdomain and authuser --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg3, 1) || strcmp(reg->callback, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 3: add userdomain and authuser failed\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 4, add callback extension --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg4, 1) || strcmp(reg->callback, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 4: add callback extension failed\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 5, add transport --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg5, 1) || strcmp(reg->callback, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_TCP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 5: add transport failed\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 6, change to tls transport, add expiry --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg6, 1) || strcmp(reg->callback, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_TLS || reg->timeout != -1 || reg->expire != -1 || reg->refresh != 111 || reg->expiry != 111 || reg->configured_expiry != 111 || reg->portno != STANDARD_TLS_PORT || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 6: change to tls transport and add expiry failed\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 7, change transport to tcp, add custom port, and add peer --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg7, 1) || strcmp(reg->callback, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "peer") || reg->transport != AST_TRANSPORT_TCP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != 111 || reg->expiry != 111 || reg->configured_expiry != 111 || reg->portno != 1234 || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 7, change transport to tcp, add custom port, and add peer failed.\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 8, remove transport --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg8, 1) || strcmp(reg->callback, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "peer") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != 111 || reg->expiry != 111 || reg->configured_expiry != 111 || reg->portno != 1234 || (reg->regdomainport) || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 8, remove transport failed.\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 9, missing domain, expected to fail --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if (!sip_parse_register_line(reg, default_expiry, reg9, 1)) { ast_test_status_update(test, "Test 9, missing domain, expected to fail but did not.\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 10, missing user, expected to fail --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if (!sip_parse_register_line(reg, default_expiry, reg10, 1)) { ast_test_status_update(test, "Test 10, missing user expected to fail but did not\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg 11, no registry object, expected to fail--- */ if (!sip_parse_register_line(NULL, default_expiry, reg1, 1)) { ast_test_status_update(test, "Test 11, no registry object, expected to fail but did not.\n"); res = AST_TEST_FAIL; } /* ---Test reg 12, no registry line, expected to fail --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if (!sip_parse_register_line(reg, default_expiry, NULL, 1)) { ast_test_status_update(test, "Test 12, NULL register line expected to fail but did not.\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg13, add domain port --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg12, 1) || strcmp(reg->callback, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "authuser") || strcmp(reg->secret, "pass") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || reg->regdomainport != 4321 || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 13, add domain port failed.\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); /* ---Test reg14, domain port without secret --- */ if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg13, 1) || strcmp(reg->callback, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || strcmp(reg->authuser, "") || strcmp(reg->secret, "") || strcmp(reg->peername, "") || reg->transport != AST_TRANSPORT_UDP || reg->timeout != -1 || reg->expire != -1 || reg->refresh != default_expiry || reg->expiry != default_expiry || reg->configured_expiry != default_expiry || reg->portno != STANDARD_SIP_PORT || reg->regdomainport != 4321 || reg->callid_valid != FALSE || reg->ocseq != INITIAL_CSEQ) { ast_test_status_update(test, "Test 14, domain port without secret failed.\n"); res = AST_TEST_FAIL; } ast_string_field_free_memory(reg); ast_free(reg); return res; alloc_fail: ast_test_status_update(test, "Out of memory. \n"); return res; } int sip_parse_host(char *line, int lineno, char **hostname, int *portnum, enum ast_transport *transport) { char *port; if (ast_strlen_zero(line)) { *hostname = NULL; return -1; } if ((*hostname = strstr(line, "://"))) { *hostname += 3; if (!strncasecmp(line, "tcp", 3)) { *transport = AST_TRANSPORT_TCP; } else if (!strncasecmp(line, "tls", 3)) { *transport = AST_TRANSPORT_TLS; } else if (!strncasecmp(line, "udp", 3)) { *transport = AST_TRANSPORT_UDP; } else if (lineno) { ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type on line %d of sip.conf. defaulting to udp.\n", line, lineno); } else { ast_log(LOG_NOTICE, "'%.3s' is not a valid transport type in sip config. defaulting to udp.\n", line); } } else { *hostname = line; *transport = AST_TRANSPORT_UDP; } if ((line = strrchr(*hostname, '@'))) line++; else line = *hostname; if (ast_sockaddr_split_hostport(line, hostname, &port, 0) == 0) { if (lineno) { ast_log(LOG_WARNING, "Cannot parse host '%s' on line %d of sip.conf.\n", line, lineno); } else { ast_log(LOG_WARNING, "Cannot parse host '%s' in sip config.\n", line); } return -1; } if (port) { if (!sscanf(port, "%5d", portnum)) { if (lineno) { ast_log(LOG_NOTICE, "'%s' is not a valid port number on line %d of sip.conf. using default.\n", port, lineno); } else { ast_log(LOG_NOTICE, "'%s' is not a valid port number in sip config. using default.\n", port); } port = NULL; } } if (!port) { if (*transport & AST_TRANSPORT_TLS) { *portnum = STANDARD_TLS_PORT; } else { *portnum = STANDARD_SIP_PORT; } } return 0; } AST_TEST_DEFINE(sip_parse_host_line_test) { int res = AST_TEST_PASS; char *host; int port; enum ast_transport transport; char host1[] = "www.blah.com"; char host2[] = "tcp://www.blah.com"; char host3[] = "tls://10.10.10.10"; char host4[] = "tls://10.10.10.10:1234"; char host5[] = "10.10.10.10:1234"; switch (cmd) { case TEST_INIT: info->name = "sip_parse_host_line_test"; info->category = "/channels/chan_sip/"; info->summary = "tests sip.conf host line parsing"; info->description = "Tests parsing of various host line configurations. " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* test 1, simple host */ sip_parse_host(host1, 1, &host, &port, &transport); if (port != STANDARD_SIP_PORT || ast_strlen_zero(host) || strcmp(host, "www.blah.com") || transport != AST_TRANSPORT_UDP) { ast_test_status_update(test, "Test 1: simple host failed.\n"); res = AST_TEST_FAIL; } /* test 2, add tcp transport */ sip_parse_host(host2, 1, &host, &port, &transport); if (port != STANDARD_SIP_PORT || ast_strlen_zero(host) || strcmp(host, "www.blah.com") || transport != AST_TRANSPORT_TCP) { ast_test_status_update(test, "Test 2: tcp host failed.\n"); res = AST_TEST_FAIL; } /* test 3, add tls transport */ sip_parse_host(host3, 1, &host, &port, &transport); if (port != STANDARD_TLS_PORT || ast_strlen_zero(host) || strcmp(host, "10.10.10.10") || transport != AST_TRANSPORT_TLS) { ast_test_status_update(test, "Test 3: tls host failed. \n"); res = AST_TEST_FAIL; } /* test 4, add custom port with tls */ sip_parse_host(host4, 1, &host, &port, &transport); if (port != 1234 || ast_strlen_zero(host) || strcmp(host, "10.10.10.10") || transport != AST_TRANSPORT_TLS) { ast_test_status_update(test, "Test 4: tls host with custom port failed.\n"); res = AST_TEST_FAIL; } /* test 5, simple host with custom port */ sip_parse_host(host5, 1, &host, &port, &transport); if (port != 1234 || ast_strlen_zero(host) || strcmp(host, "10.10.10.10") || transport != AST_TRANSPORT_UDP) { ast_test_status_update(test, "Test 5: simple host with custom port failed.\n"); res = AST_TEST_FAIL; } /* test 6, expected failure with NULL input */ if (!sip_parse_host(NULL, 1, &host, &port, &transport)) { ast_test_status_update(test, "Test 6: expected error on NULL input did not occur.\n"); res = AST_TEST_FAIL; } return res; } /*! \brief Parse the comma-separated nat= option values */ void sip_parse_nat_option(const char *value, struct ast_flags *mask, struct ast_flags *flags) { char *parse, *this; if (!(parse = ast_strdupa(value))) { return; } /* Since we need to completely override the general settings if we are being called * later for a peer, always set the flags for all options on the mask */ ast_set_flag(&mask[0], SIP_NAT_FORCE_RPORT); ast_set_flag(&mask[1], SIP_PAGE2_SYMMETRICRTP); ast_set_flag(&mask[2], SIP_PAGE3_NAT_AUTO_RPORT); ast_set_flag(&mask[2], SIP_PAGE3_NAT_AUTO_COMEDIA); while ((this = strsep(&parse, ","))) { if (ast_false(this)) { ast_clear_flag(&flags[0], SIP_NAT_FORCE_RPORT); ast_clear_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP); ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT); ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA); break; /* It doesn't make sense to have no + something else */ } else if (!strcasecmp(this, "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_PAGE2_SYMMETRICRTP); ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT); ast_clear_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA); break; /* It doesn't make sense to have yes + something else */ } else if (!strcasecmp(this, "force_rport") && !ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) { ast_set_flag(&flags[0], SIP_NAT_FORCE_RPORT); } else if (!strcasecmp(this, "comedia") && !ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) { ast_set_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP); } else if (!strcasecmp(this, "auto_force_rport")) { ast_set_flag(&flags[2], SIP_PAGE3_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(this, "auto_comedia")) { ast_set_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA); /* In case someone did something dumb like nat=comedia,auto_comedia*/ ast_clear_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP); } } } #define TEST_FORCE_RPORT 1 << 0 #define TEST_COMEDIA 1 << 1 #define TEST_AUTO_FORCE_RPORT 1 << 2 #define TEST_AUTO_COMEDIA 1 << 3 static int match_nat_options(int val, struct ast_flags *flags) { if ((!ast_test_flag(&flags[0], SIP_NAT_FORCE_RPORT)) != !(val & TEST_FORCE_RPORT)) { return 0; } if (!ast_test_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP) != !(val & TEST_COMEDIA)) { return 0; } if (!ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_RPORT) != !(val & TEST_AUTO_FORCE_RPORT)) { return 0; } if (!ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA) != !(val & TEST_AUTO_COMEDIA)) { return 0; } return 1; } AST_TEST_DEFINE(sip_parse_nat_test) { int i, res = AST_TEST_PASS; struct ast_flags mask[3] = {{0}}, flags[3] = {{0}}; struct { const char *str; int i; } options[] = { { "yes", TEST_FORCE_RPORT | TEST_COMEDIA }, { "no", 0 }, { "force_rport", TEST_FORCE_RPORT }, { "comedia", TEST_COMEDIA }, { "auto_force_rport", TEST_AUTO_FORCE_RPORT }, { "auto_comedia", TEST_AUTO_COMEDIA }, { "force_rport,auto_force_rport", TEST_AUTO_FORCE_RPORT }, { "auto_force_rport,force_rport", TEST_AUTO_FORCE_RPORT }, { "comedia,auto_comedia", TEST_AUTO_COMEDIA }, { "auto_comedia,comedia", TEST_AUTO_COMEDIA }, { "force_rport,comedia", TEST_FORCE_RPORT | TEST_COMEDIA }, { "force_rport,auto_comedia", TEST_FORCE_RPORT | TEST_AUTO_COMEDIA }, { "force_rport,yes,no", TEST_FORCE_RPORT | TEST_COMEDIA }, { "auto_comedia,no,yes", 0 }, }; switch (cmd) { case TEST_INIT: info->name = "sip_parse_nat_test"; info->category = "/channels/chan_sip/"; info->summary = "tests sip.conf nat line parsing"; info->description = "Tests parsing of various nat line configurations. " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } for (i = 0; i < ARRAY_LEN(options); i++) { sip_parse_nat_option(options[i].str, mask, flags); if (!match_nat_options(options[i].i, flags)) { ast_test_status_update(test, "Failed nat=%s\n", options[i].str); res = AST_TEST_FAIL; } memset(flags, 0, sizeof(flags)); memset(mask, 0, sizeof(mask)); } return res; } /*! \brief SIP test registration */ void sip_config_parser_register_tests(void) { AST_TEST_REGISTER(sip_parse_register_line_test); AST_TEST_REGISTER(sip_parse_host_line_test); AST_TEST_REGISTER(sip_parse_nat_test); } /*! \brief SIP test registration */ void sip_config_parser_unregister_tests(void) { AST_TEST_UNREGISTER(sip_parse_register_line_test); AST_TEST_UNREGISTER(sip_parse_host_line_test); AST_TEST_UNREGISTER(sip_parse_nat_test); } asterisk-13.1.0/channels/sip/security_events.c0000644000000000000000000002721012433147671020116 0ustar rootroot/* * 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. */ /*! * \file * * \brief Generate security events in the SIP channel * * \author Michael L. Young */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 428246 $") #include "include/sip.h" #include "include/security_events.h" /*! \brief Determine transport type used to receive request*/ static enum ast_transport security_event_get_transport(const struct sip_pvt *p) { return p->socket.type; } void sip_report_invalid_peer(const struct sip_pvt *p) { 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 = p->exten, .common.local_addr = { .addr = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, }; snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&inval_acct_id)); } void sip_report_failed_acl(const struct sip_pvt *p, 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 = p->exten, .common.local_addr = { .addr = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, .acl_name = aclname, }; snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&failed_acl_event)); } void sip_report_inval_password(const struct sip_pvt *p, 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 = p->exten, .common.local_addr = { .addr = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, .challenge = p->nonce, .received_challenge = response_challenge, .received_hash = response_hash, }; snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&inval_password)); } void sip_report_auth_success(const struct sip_pvt *p, 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 = p->exten, .common.local_addr = { .addr = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, .using_password = using_password, }; snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&successful_auth)); } void sip_report_session_limit(const struct sip_pvt *p) { 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 = p->exten, .common.local_addr = { .addr = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, }; snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&session_limit)); } void sip_report_failed_challenge_response(const struct sip_pvt *p, const char *response, const char *expected_response) { char session_id[32]; char 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 = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, .challenge = p->nonce, .response = response, .expected_response = expected_response, }; if (!ast_strlen_zero(p->from)) { /* When dialing, show account making call */ ast_copy_string(account_id, p->from, sizeof(account_id)); } else { ast_copy_string(account_id, p->exten, sizeof(account_id)); } snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&chal_resp_failed)); } void sip_report_chal_sent(const struct sip_pvt *p) { char session_id[32]; char 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 = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, .challenge = p->nonce, }; if (!ast_strlen_zero(p->from)) { /* When dialing, show account making call */ ast_copy_string(account_id, p->from, sizeof(account_id)); } else { ast_copy_string(account_id, p->exten, sizeof(account_id)); } snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&chal_sent)); } void sip_report_inval_transport(const struct sip_pvt *p, 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 = p->exten, .common.local_addr = { .addr = &p->ourip, .transport = security_event_get_transport(p) }, .common.remote_addr = { .addr = &p->sa, .transport = security_event_get_transport(p) }, .common.session_id = session_id, .transport = transport, }; snprintf(session_id, sizeof(session_id), "%p", p); ast_security_event_report(AST_SEC_EVT(&inval_transport)); } int sip_report_security_event(const struct sip_pvt *p, const struct sip_request *req, const int res) { struct sip_peer *peer_report; enum check_auth_result res_report = res; struct ast_str *buf; char *c; const char *authtoken; char *reqheader, *respheader; int result = 0; char aclname[256]; struct digestkeys keys[] = { [K_RESP] = { "response=", "" }, [K_URI] = { "uri=", "" }, [K_USER] = { "username=", "" }, [K_NONCE] = { "nonce=", "" }, [K_LAST] = { NULL, NULL} }; peer_report = sip_find_peer(p->exten, NULL, TRUE, FINDPEERS, FALSE, 0); switch(res_report) { case AUTH_DONT_KNOW: break; case AUTH_SUCCESSFUL: if (peer_report) { if (ast_strlen_zero(peer_report->secret) && ast_strlen_zero(peer_report->md5secret)) { sip_report_auth_success(p, (uint32_t *) 0); } else { sip_report_auth_success(p, (uint32_t *) 1); } } break; case AUTH_CHALLENGE_SENT: sip_report_chal_sent(p); break; case AUTH_SECRET_FAILED: case AUTH_USERNAME_MISMATCH: sip_auth_headers(WWW_AUTH, &respheader, &reqheader); authtoken = sip_get_header(req, reqheader); buf = ast_str_thread_get(&check_auth_buf, CHECK_AUTH_BUF_INITLEN); ast_str_set(&buf, 0, "%s", authtoken); c = ast_str_buffer(buf); sip_digest_parser(c, keys); if (res_report == AUTH_SECRET_FAILED) { sip_report_inval_password(p, keys[K_NONCE].s, keys[K_RESP].s); } else { if (peer_report) { sip_report_failed_challenge_response(p, keys[K_USER].s, peer_report->username); } } break; case AUTH_NOT_FOUND: /* with sip_cfg.alwaysauthreject on, generates 2 events */ sip_report_invalid_peer(p); break; case AUTH_UNKNOWN_DOMAIN: snprintf(aclname, sizeof(aclname), "domain_must_match"); sip_report_failed_acl(p, aclname); break; case AUTH_PEER_NOT_DYNAMIC: snprintf(aclname, sizeof(aclname), "peer_not_dynamic"); sip_report_failed_acl(p, aclname); break; case AUTH_ACL_FAILED: /* with sip_cfg.alwaysauthreject on, generates 2 events */ snprintf(aclname, sizeof(aclname), "device_must_match_acl"); sip_report_failed_acl(p, aclname); break; case AUTH_BAD_TRANSPORT: sip_report_inval_transport(p, sip_get_transport(req->socket.type)); break; case AUTH_RTP_FAILED: break; case AUTH_SESSION_LIMIT: sip_report_session_limit(p); break; } if (peer_report) { sip_unref_peer(peer_report, "sip_report_security_event: sip_unref_peer: from handle_incoming"); } return result; } asterisk-13.1.0/channels/sip/reqresp_parser.c0000644000000000000000000023033712424600510017710 0ustar rootroot/* * 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. */ /*! * \file * \brief sip request parsing functions and unit tests */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 426865 $") #include "include/sip.h" #include "include/sip_utils.h" #include "include/reqresp_parser.h" #ifdef HAVE_XLOCALE_H locale_t c_locale; #endif /*! \brief * parses a URI in its components.*/ int parse_uri_full(char *uri, const char *scheme, char **user, char **pass, char **hostport, struct uriparams *params, char **headers, char **residue) { char *userinfo = NULL; char *parameters = NULL; char *endparams = NULL; char *c = NULL; int error = 0; int teluri_scheme = 0; /* * Initialize requested strings - some functions don't care if parse_uri fails * and will attempt to use string pointers passed into parse_uri even after a * parse_uri failure */ if (user) { *user = ""; } if (pass) { *pass = ""; } if (hostport) { *hostport = ""; } if (headers) { *headers = ""; } if (residue) { *residue = ""; } /* check for valid input */ if (ast_strlen_zero(uri)) { return -1; } if (scheme) { int l; char *scheme2 = ast_strdupa(scheme); char *cur = strsep(&scheme2, ","); for (; !ast_strlen_zero(cur); cur = strsep(&scheme2, ",")) { l = strlen(cur); if (!strncasecmp(uri, cur, l)) { teluri_scheme = !strncasecmp(uri, "tel:", 4); /* TEL URI */ uri += l; break; } } if (ast_strlen_zero(cur)) { ast_debug(1, "No supported scheme found in '%s' using the scheme[s] %s\n", uri, scheme); error = -1; } } if (!hostport) { /* if we don't want to split around hostport, keep everything as a * userinfo - cos thats how old parse_uri operated*/ userinfo = uri; } else if (teluri_scheme) { /* * tel: TEL URI INVITE RFC 3966 patch * See http://www.ietf.org/rfc/rfc3966.txt * * Once the full RFC 3966 parsing is implemented, * the ext= or isub= parameters would be extracted from userinfo. * When this kind of subaddressing would be implemented, the userinfo must be further parsed. * Those parameters would be used for ISDN or PSTN local extensions. * * Current restrictions: * We currently consider the ";isub=" or the ";ext=" as part of the userinfo (unparsed). */ if ((c = strstr(uri, ";phone-context="))) { /* * Local number with context or domain. * ext= or isub= TEL URI parameters should be upfront. * All other parameters should come after the ";phone-context=" parameter. * If other parameters would occur before ";phone-context=" they will be ignored. */ *c = '\0'; userinfo = uri; uri = c + 15; *hostport = uri; } else if ('+' == uri[0]) { /* Global number without context or domain; possibly followed by RFC 3966 and optional other parameters. */ userinfo = uri; *hostport = uri; } else { ast_debug(1, "No RFC 3966 global number or context found in '%s'; returning local number anyway\n", uri); userinfo = uri; /* Return local number anyway */ error = -1; } } else { char *dom = ""; if ((c = strchr(uri, '@'))) { *c++ = '\0'; dom = c; userinfo = uri; uri = c; /* userinfo can contain ? and ; chars so step forward before looking for params and headers */ } else { /* domain-only URI, according to the SIP RFC. */ dom = uri; userinfo = ""; } *hostport = dom; } if (pass && (c = strchr(userinfo, ':'))) { /* user:password */ *c++ = '\0'; *pass = c; } else if (pass) { *pass = ""; } if (user) { *user = userinfo; } parameters = uri; /* strip [?headers] from end of uri - even if no header pointer exists*/ if ((c = strrchr(uri, '?'))) { *c++ = '\0'; uri = c; if (headers) { *headers = c; } if ((c = strrchr(uri, ';'))) { *c++ = '\0'; } else { c = strrchr(uri, '\0'); } uri = c; /* residue */ } else if (headers) { *headers = ""; } /* parse parameters */ endparams = strchr(parameters,'\0'); if ((c = strchr(parameters, ';'))) { *c++ = '\0'; parameters = c; } else { parameters = endparams; } if (params) { char *rem = parameters; /* unparsed or unrecognised remainder */ char *label; char *value; int lr = 0; params->transport = ""; params->user = ""; params->method = ""; params->ttl = ""; params->maddr = ""; params->lr = 0; rem = parameters; while ((value = strchr(parameters, '=')) || (lr = !strncmp(parameters, "lr", 2))) { /* The while condition will not continue evaluation to set lr if it matches "lr=" */ if (lr) { value = parameters; } else { *value++ = '\0'; } label = parameters; if ((c = strchr(value, ';'))) { *c++ = '\0'; parameters = c; } else { parameters = endparams; } if (!strcmp(label, "transport")) { params->transport = value; rem = parameters; } else if (!strcmp(label, "user")) { params->user = value; rem = parameters; } else if (!strcmp(label, "method")) { params->method = value; rem = parameters; } else if (!strcmp(label, "ttl")) { params->ttl = value; rem = parameters; } else if (!strcmp(label, "maddr")) { params->maddr = value; rem = parameters; /* Treat "lr", "lr=yes", "lr=on", "lr=1", "lr=almostanything" as lr enabled and "", "lr=no", "lr=off", "lr=0", "lr=" and "lranything" as lr disabled */ } else if ((!strcmp(label, "lr") && strcmp(value, "no") && strcmp(value, "off") && strcmp(value, "0") && strcmp(value, "")) || ((lr) && strcmp(value, "lr"))) { params->lr = 1; rem = parameters; } else { value--; *value = '='; if (c) { c--; *c = ';'; } } } if (rem > uri) { /* no headers */ uri = rem; } } if (residue) { *residue = uri; } return error; } AST_TEST_DEFINE(sip_parse_uri_full_test) { int res = AST_TEST_PASS; char uri[1024]; char *user, *pass, *hostport, *headers, *residue; struct uriparams params; struct testdata { char *desc; char *uri; char *user; char *pass; char *hostport; char *headers; char *residue; struct uriparams params; AST_LIST_ENTRY(testdata) list; }; struct testdata *testdataptr; static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist; struct testdata td1 = { .desc = "no headers", .uri = "sip:user:secret@host:5060;param=discard;transport=tcp;param2=residue", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "", .residue = "param2=residue", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td2 = { .desc = "with headers", .uri = "sip:user:secret@host:5060;param=discard;transport=tcp;param2=discard2?header=blah&header2=blah2;param3=residue", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "header=blah&header2=blah2", .residue = "param3=residue", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td3 = { .desc = "difficult user", .uri = "sip:-_.!~*'()&=+$,;?/:secret@host:5060;transport=tcp", .user = "-_.!~*'()&=+$,;?/", .pass = "secret", .hostport = "host:5060", .headers = "", .residue = "", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td4 = { .desc = "difficult pass", .uri = "sip:user:-_.!~*'()&=+$,@host:5060;transport=tcp", .user = "user", .pass = "-_.!~*'()&=+$,", .hostport = "host:5060", .headers = "", .residue = "", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td5 = { .desc = "difficult host", .uri = "sip:user:secret@1-1.a-1.:5060;transport=tcp", .user = "user", .pass = "secret", .hostport = "1-1.a-1.:5060", .headers = "", .residue = "", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td6 = { .desc = "difficult params near transport", .uri = "sip:user:secret@host:5060;-_.!~*'()[]/:&+$=-_.!~*'()[]/:&+$;transport=tcp", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "", .residue = "", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td7 = { .desc = "difficult params near headers", .uri = "sip:user:secret@host:5060;-_.!~*'()[]/:&+$=-_.!~*'()[]/:&+$?header=blah&header2=blah2;-_.!~*'()[]/:&+$=residue", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "header=blah&header2=blah2", .residue = "-_.!~*'()[]/:&+$=residue", .params.transport = "", .params.lr = 0, .params.user = "" }; struct testdata td8 = { .desc = "lr parameter", .uri = "sip:user:secret@host:5060;param=discard;lr?header=blah", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "header=blah", .residue = "", .params.transport = "", .params.lr = 1, .params.user = "" }; struct testdata td9 = { .desc = "alternative lr parameter", .uri = "sip:user:secret@host:5060;param=discard;lr=yes?header=blah", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "header=blah", .residue = "", .params.transport = "", .params.lr = 1, .params.user = "" }; struct testdata td10 = { .desc = "no lr parameter", .uri = "sip:user:secret@host:5060;paramlr=lr;lr=no;lr=off;lr=0;lr=;=lr;lrextra;lrparam2=lr?header=blah", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "header=blah", .residue = "", .params.transport = "", .params.lr = 0, .params.user = "" }; /* RFC 3966 TEL URI INVITE */ struct testdata td11 = { .desc = "tel local number", .uri = "tel:0987654321;phone-context=+32987654321", .user = "0987654321", .pass = "", .hostport = "+32987654321", .headers = "", .residue = "", .params.transport = "", .params.lr = 0, .params.user = "" }; struct testdata td12 = { .desc = "tel global number", .uri = "tel:+32987654321", .user = "+32987654321", .pass = "", .hostport = "+32987654321", .headers = "", .residue = "", .params.transport = "", .params.lr = 0, .params.user = "" }; /* * Once the full RFC 3966 parsing is implemented, * only the ext= or isub= parameters would be extracted from .user * Then the ;param=discard would be ignored, * and the .user would only contain "0987654321" */ struct testdata td13 = { .desc = "tel local number", .uri = "tel:0987654321;ext=1234;param=discard;phone-context=+32987654321;transport=udp;param2=discard2?header=blah&header2=blah2;param3=residue", .user = "0987654321;ext=1234;param=discard", .pass = "", .hostport = "+32987654321", .headers = "header=blah&header2=blah2", .residue = "param3=residue", .params.transport = "udp", .params.lr = 0, .params.user = "" }; AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &td1); AST_LIST_INSERT_TAIL(&testdatalist, &td2, list); AST_LIST_INSERT_TAIL(&testdatalist, &td3, list); AST_LIST_INSERT_TAIL(&testdatalist, &td4, list); AST_LIST_INSERT_TAIL(&testdatalist, &td5, list); AST_LIST_INSERT_TAIL(&testdatalist, &td6, list); AST_LIST_INSERT_TAIL(&testdatalist, &td7, list); AST_LIST_INSERT_TAIL(&testdatalist, &td8, list); AST_LIST_INSERT_TAIL(&testdatalist, &td9, list); AST_LIST_INSERT_TAIL(&testdatalist, &td10, list); AST_LIST_INSERT_TAIL(&testdatalist, &td11, list); AST_LIST_INSERT_TAIL(&testdatalist, &td12, list); AST_LIST_INSERT_TAIL(&testdatalist, &td13, list); switch (cmd) { case TEST_INIT: info->name = "sip_uri_full_parse_test"; info->category = "/channels/chan_sip/"; info->summary = "tests sip full uri parsing"; info->description = "Tests full parsing of various URIs " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) { user = pass = hostport = headers = residue = NULL; params.transport = params.user = params.method = params.ttl = params.maddr = NULL; params.lr = 0; ast_copy_string(uri,testdataptr->uri,sizeof(uri)); if (parse_uri_full(uri, "sip:,sips:,tel:", &user, &pass, &hostport, ¶ms, &headers, &residue) || (user && strcmp(testdataptr->user, user)) || (pass && strcmp(testdataptr->pass, pass)) || (hostport && strcmp(testdataptr->hostport, hostport)) || (headers && strcmp(testdataptr->headers, headers)) || (residue && strcmp(testdataptr->residue, residue)) || (strcmp(testdataptr->params.transport,params.transport)) || (testdataptr->params.lr != params.lr) || (strcmp(testdataptr->params.user,params.user)) ) { ast_test_status_update(test, "Sub-Test: %s, failed.\n", testdataptr->desc); res = AST_TEST_FAIL; } } return res; } int parse_uri(char *uri, const char *scheme, char **user, char **pass, char **hostport, char **transport) { int ret; char *headers; struct uriparams params; headers = NULL; ret = parse_uri_full(uri, scheme, user, pass, hostport, ¶ms, &headers, NULL); if (transport) { *transport=params.transport; } return ret; } AST_TEST_DEFINE(sip_parse_uri_test) { int res = AST_TEST_PASS; char *name, *pass, *hostport, *transport; char uri1[] = "sip:name@host"; char uri2[] = "sip:name@host;transport=tcp"; char uri3[] = "sip:name:secret@host;transport=tcp"; char uri4[] = "sip:name:secret@host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah"; /* test 5 is for NULL input */ char uri6[] = "sip:name:secret@host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah"; char uri7[] = "sip:name:secret@host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah"; char uri8[] = "sip:host"; char uri9[] = "sip:host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah"; char uri10[] = "host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah"; char uri11[] = "host"; char uri12[] = "tel:911"; /* TEL URI Local number without context or global number */ switch (cmd) { case TEST_INIT: info->name = "sip_uri_parse_test"; info->category = "/channels/chan_sip/"; info->summary = "tests sip uri parsing"; info->description = "Tests parsing of various URIs " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* Test 1, simple URI */ name = pass = hostport = transport = NULL; if (parse_uri(uri1, "sip:,sips:", &name, &pass, &hostport, &transport) || strcmp(name, "name") || !ast_strlen_zero(pass) || strcmp(hostport, "host") || !ast_strlen_zero(transport)) { ast_test_status_update(test, "Test 1: simple uri failed. \n"); res = AST_TEST_FAIL; } /* Test 2, add tcp transport */ name = pass = hostport = transport = NULL; if (parse_uri(uri2, "sip:,sips:", &name, &pass, &hostport, &transport) || strcmp(name, "name") || !ast_strlen_zero(pass) || strcmp(hostport, "host") || strcmp(transport, "tcp")) { ast_test_status_update(test, "Test 2: uri with addtion of tcp transport failed. \n"); res = AST_TEST_FAIL; } /* Test 3, add secret */ name = pass = hostport = transport = NULL; if (parse_uri(uri3, "sip:,sips:", &name, &pass, &hostport, &transport) || strcmp(name, "name") || strcmp(pass, "secret") || strcmp(hostport, "host") || strcmp(transport, "tcp")) { ast_test_status_update(test, "Test 3: uri with addition of secret failed.\n"); res = AST_TEST_FAIL; } /* Test 4, add port and unparsed header field*/ name = pass = hostport = transport = NULL; if (parse_uri(uri4, "sip:,sips:", &name, &pass, &hostport, &transport) || strcmp(name, "name") || strcmp(pass, "secret") || strcmp(hostport, "host:port") || strcmp(transport, "tcp")) { ast_test_status_update(test, "Test 4: add port and unparsed header field failed.\n"); res = AST_TEST_FAIL; } /* Test 5, verify parse_uri does not crash when given a NULL uri */ name = pass = hostport = transport = NULL; if (!parse_uri(NULL, "sip:,sips:", &name, &pass, &hostport, &transport)) { ast_test_status_update(test, "Test 5: passing a NULL uri failed.\n"); res = AST_TEST_FAIL; } /* Test 6, verify parse_uri does not crash when given a NULL output parameters */ name = pass = hostport = transport = NULL; if (parse_uri(uri6, "sip:,sips:", NULL, NULL, NULL, NULL)) { ast_test_status_update(test, "Test 6: passing NULL output parameters failed.\n"); res = AST_TEST_FAIL; } /* Test 7, verify parse_uri returns user:secret and hostport when no port or secret output parameters are supplied. */ name = pass = hostport = transport = NULL; if (parse_uri(uri7, "sip:,sips:", &name, NULL, &hostport, NULL) || strcmp(name, "name:secret") || strcmp(hostport, "host:port")) { ast_test_status_update(test, "Test 7: providing no port and secret output parameters failed.\n"); res = AST_TEST_FAIL; } /* Test 8, verify parse_uri can handle a hostport only uri */ name = pass = hostport = transport = NULL; if (parse_uri(uri8, "sip:,sips:", &name, &pass, &hostport, &transport) || strcmp(hostport, "host") || !ast_strlen_zero(name)) { ast_test_status_update(test, "Test 8: add port and unparsed header field failed.\n"); res = AST_TEST_FAIL; } /* Test 9, add port and unparsed header field with hostport only uri*/ name = pass = hostport = transport = NULL; if (parse_uri(uri9, "sip:,sips:", &name, &pass, &hostport, &transport) || !ast_strlen_zero(name) || !ast_strlen_zero(pass) || strcmp(hostport, "host:port") || strcmp(transport, "tcp")) { ast_test_status_update(test, "Test 9: hostport only uri failed \n"); res = AST_TEST_FAIL; } /* Test 10, handle invalid/missing "sip:,sips:" scheme * we expect parse_uri to return an error, but still parse * the results correctly here */ name = pass = hostport = transport = NULL; if (!parse_uri(uri10, "sip:,sips:", &name, &pass, &hostport, &transport) || !ast_strlen_zero(name) || !ast_strlen_zero(pass) || strcmp(hostport, "host:port") || strcmp(transport, "tcp")) { ast_test_status_update(test, "Test 10: missing \"sip:sips:\" scheme failed\n"); res = AST_TEST_FAIL; } /* Test 11, simple hostport only URI with missing scheme * we expect parse_uri to return an error, but still parse * the results correctly here */ name = pass = hostport = transport = NULL; if (!parse_uri(uri11, "sip:,sips:", &name, &pass, &hostport, &transport) || !ast_strlen_zero(name) || !ast_strlen_zero(pass) || strcmp(hostport, "host") || !ast_strlen_zero(transport)) { ast_test_status_update(test, "Test 11: simple uri with missing scheme failed. \n"); res = AST_TEST_FAIL; } /* Test 12, simple URI */ name = pass = hostport = transport = NULL; if (!parse_uri(uri12, "sip:,sips:,tel:", &name, &pass, &hostport, &transport) || strcmp(name, "911") || /* We return local number anyway */ !ast_strlen_zero(pass) || !ast_strlen_zero(hostport) || /* No global number nor context */ !ast_strlen_zero(transport)) { ast_test_status_update(test, "Test 12: TEL URI INVITE failed.\n"); res = AST_TEST_FAIL; } return res; } /*! \brief Get caller id name from SIP headers, copy into output buffer * * \retval input string pointer placed after display-name field if possible */ const char *get_calleridname(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 initilized */ *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[0] == '"') { 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 (0 < outputsize) { /* 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 (0 < outputsize) { /* 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; } AST_TEST_DEFINE(get_calleridname_test) { int res = AST_TEST_PASS; const char *in1 = " \" quoted-text internal \\\" quote \""; const char *in2 = " token text with no quotes "; const char *overflow1 = " \"quoted-text overflow 1234567890123456789012345678901234567890\" "; const char *overflow2 = " non-quoted text overflow 1234567890123456789012345678901234567890 "; const char *noendquote = " \"quoted-text no end "; const char *addrspec = " sip:blah@blah"; const char *no_quotes_no_brackets = "blah@blah"; const char *after_dname; char dname[40]; switch (cmd) { case TEST_INIT: info->name = "sip_get_calleridname_test"; info->category = "/channels/chan_sip/"; info->summary = "decodes callerid name from sip header"; info->description = "Decodes display-name field of sip header. Checks for valid output and expected failure cases."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* quoted-text with backslash escaped quote */ after_dname = get_calleridname(in1, dname, sizeof(dname)); ast_test_status_update(test, "display-name1: %s\nafter: %s\n", dname, after_dname); if (strcmp(dname, " quoted-text internal \" quote ")) { ast_test_status_update(test, "display-name1 test failed\n"); res = AST_TEST_FAIL; } /* token text */ after_dname = get_calleridname(in2, dname, sizeof(dname)); ast_test_status_update(test, "display-name2: %s\nafter: %s\n", dname, after_dname); if (strcmp(dname, "token text with no quotes")) { ast_test_status_update(test, "display-name2 test failed\n"); res = AST_TEST_FAIL; } /* quoted-text buffer overflow */ after_dname = get_calleridname(overflow1, dname, sizeof(dname)); ast_test_status_update(test, "overflow display-name1: %s\nafter: %s\n", dname, after_dname); if (strcmp(dname, "quoted-text overflow 123456789012345678")) { ast_test_status_update(test, "overflow display-name1 test failed\n"); res = AST_TEST_FAIL; } /* non-quoted-text buffer overflow */ after_dname = get_calleridname(overflow2, dname, sizeof(dname)); ast_test_status_update(test, "overflow display-name2: %s\nafter: %s\n", dname, after_dname); if (strcmp(dname, "non-quoted text overflow 12345678901234")) { ast_test_status_update(test, "overflow display-name2 test failed\n"); res = AST_TEST_FAIL; } /* quoted-text buffer with no terminating end quote */ after_dname = get_calleridname(noendquote, dname, sizeof(dname)); ast_test_status_update(test, "noendquote display-name1: %s\nafter: %s\n", dname, after_dname); if (*dname != '\0' && after_dname != noendquote) { ast_test_status_update(test, "no end quote for quoted-text display-name failed\n"); res = AST_TEST_FAIL; } /* addr-spec rather than display-name. */ after_dname = get_calleridname(addrspec, dname, sizeof(dname)); ast_test_status_update(test, "addr-spec display-name1: %s\nafter: %s\n", dname, after_dname); if (*dname != '\0' && after_dname != addrspec) { ast_test_status_update(test, "detection of addr-spec failed\n"); res = AST_TEST_FAIL; } /* no quotes, no brackets */ after_dname = get_calleridname(no_quotes_no_brackets, dname, sizeof(dname)); ast_test_status_update(test, "no_quotes_no_brackets display-name1: %s\nafter: %s\n", dname, after_dname); if (*dname != '\0' && after_dname != no_quotes_no_brackets) { ast_test_status_update(test, "detection of addr-spec failed\n"); res = AST_TEST_FAIL; } return res; } int get_name_and_number(const char *hdr, char **name, char **number) { char header[256]; char tmp_name[256]; char *tmp_number = NULL; char *hostport = NULL; char *dummy = NULL; if (!name || !number || ast_strlen_zero(hdr)) { return -1; } *number = NULL; *name = NULL; ast_copy_string(header, hdr, sizeof(header)); /* strip the display-name portion off the beginning of the header. */ get_calleridname(header, tmp_name, sizeof(tmp_name)); /* get uri within < > brackets */ tmp_number = get_in_brackets(header); /* parse out the number here */ if (parse_uri(tmp_number, "sip:,sips:", &tmp_number, &dummy, &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; } AST_TEST_DEFINE(get_name_and_number_test) { int res = AST_TEST_PASS; char *name = NULL; char *number = NULL; const char *in1 = "NAME "; const char *in2 = "\"NA>"; const char *in3 = "NAME"; const char *in4 = ""; const char *in5 = "This is a screwed up string @place>"; switch (cmd) { case TEST_INIT: info->name = "sip_get_name_and_number_test"; info->category = "/channels/chan_sip/"; info->summary = "Tests getting name and number from sip header"; info->description = "Runs through various test situations in which a name and " "and number can be retrieved from a sip header."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* Test 1. get name and number */ number = name = NULL; if ((get_name_and_number(in1, &name, &number)) || strcmp(name, "NAME") || strcmp(number, "NUMBER")) { ast_test_status_update(test, "Test 1, simple get name and number failed.\n"); res = AST_TEST_FAIL; } ast_free(name); ast_free(number); /* Test 2. get quoted name and number */ number = name = NULL; if ((get_name_and_number(in2, &name, &number)) || strcmp(name, "NA>= first_bracket) { break; /* no need to look at quoted part */ } /* the bracket is within quotes, so ignore it */ parse = find_closing_quote(first_quote + 1, NULL); if (!*parse) { ast_log(LOG_WARNING, "No closing quote found in '%s'\n", src); return -1; } parse++; } /* Require a first bracket. Unlike get_in_brackets_full, this procedure is passed a const, * so it can expect a pointer to an original value */ if (!first_bracket) { ast_log(LOG_WARNING, "No opening bracket found in '%s'\n", src); return 1; } if ((second_bracket = strchr(first_bracket, '>'))) { *start = first_bracket; *length = second_bracket - first_bracket; return 0; } ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", src); return -1; } int get_in_brackets_full(char *tmp,char **out,char **residue) { const char *parse = tmp; char *first_bracket; char *second_bracket; if (out) { *out = ""; } if (residue) { *residue = ""; } if (ast_strlen_zero(tmp)) { return 1; } /* * Skip any quoted text until we find the part in brackets. * On any error give up and return -1 */ while ( (first_bracket = strchr(parse, '<')) ) { char *first_quote = strchr(parse, '"'); first_bracket++; if (!first_quote || first_quote >= first_bracket) { break; /* no need to look at quoted part */ } /* the bracket is within quotes, so ignore it */ parse = find_closing_quote(first_quote + 1, NULL); if (!*parse) { ast_log(LOG_WARNING, "No closing quote found in '%s'\n", tmp); return -1; } 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 (first_bracket) { parse = first_bracket; } else { parse = tmp; } if ((second_bracket = strchr(parse, '>'))) { *second_bracket++ = '\0'; if (out) { *out = (char *) parse; } if (residue) { *residue = second_bracket; } return 0; } if ((first_bracket)) { ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp); return -1; } if (out) { *out = tmp; } return 1; } char *get_in_brackets(char *tmp) { char *out; if ((get_in_brackets_full(tmp, &out, NULL))) { return tmp; } return out; } AST_TEST_DEFINE(get_in_brackets_test) { int res = AST_TEST_PASS; char in_brackets[] = "sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah"; char no_name[] = ""; char quoted_string[] = "\"I'm a quote stri>"; char missing_end_quote[] = "\"I'm a quote string "; char name_no_quotes[] = "name not in quotes "; char no_end_bracket[] = "name not in quotes name = "sip_get_in_brackets_test"; info->category = "/channels/chan_sip/"; info->summary = "Tests getting a sip uri in <> brackets within a sip header."; info->description = "Runs through various test situations in which a sip uri " "in angle brackets needs to be retrieved"; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } /* Test 1, simple get in brackets */ if (!(uri = get_in_brackets(no_name)) || strcmp(uri, in_brackets)) { ast_test_status_update(test, "Test 1, simple get in brackets failed. %s\n", uri); res = AST_TEST_FAIL; } /* Test 2, starts with quoted string */ if (!(uri = get_in_brackets(quoted_string)) || strcmp(uri, in_brackets)) { ast_test_status_update(test, "Test 2, get in brackets with quoted string in front failed. %s\n", uri); res = AST_TEST_FAIL; } /* Test 3, missing end quote */ if (!(uri = get_in_brackets(missing_end_quote)) || !strcmp(uri, in_brackets)) { ast_test_status_update(test, "Test 3, missing end quote failed. %s\n", uri); res = AST_TEST_FAIL; } /* Test 4, starts with a name not in quotes */ if (!(uri = get_in_brackets(name_no_quotes)) || strcmp(uri, in_brackets)) { ast_test_status_update(test, "Test 4, passing name not in quotes failed. %s\n", uri); res = AST_TEST_FAIL; } /* Test 5, no end bracket, should just return everything after the first '<' */ if (!(uri = get_in_brackets(no_end_bracket)) || !strcmp(uri, in_brackets)) { ast_test_status_update(test, "Test 5, no end bracket failed. %s\n", uri); res = AST_TEST_FAIL; } /* Test 6, NULL input */ if (get_in_brackets(NULL)) { ast_test_status_update(test, "Test 6, NULL input failed.\n"); res = AST_TEST_FAIL; } /* Test 7, no name, and no brackets. */ if (!(uri = get_in_brackets(no_name_no_brackets)) || strcmp(uri, "sip:name@host")) { ast_test_status_update(test, "Test 7 failed. %s\n", uri); res = AST_TEST_FAIL; } /* Test 8, no start bracket, but with ending bracket. */ if (!(uri = get_in_brackets(missing_start_bracket)) || strcmp(uri, in_brackets)) { ast_test_status_update(test, "Test 8 failed. %s\n", uri); res = AST_TEST_FAIL; } return res; } int parse_name_andor_addr(char *uri, const char *scheme, char **name, char **user, char **pass, char **hostport, struct uriparams *params, char **headers, char **residue) { char buf[1024]; char **residue2 = residue; char *orig_uri = uri; int ret; buf[0] = '\0'; if (name) { uri = (char *) get_calleridname(uri, buf, sizeof(buf)); } ret = get_in_brackets_full(uri, &uri, residue); if (ret == 0) { /* * The uri is in brackets so do not treat unknown trailing uri * parameters as potential message header parameters. */ if (residue && **residue) { /* step over the first semicolon as per parse_uri_full residue */ *residue = *residue + 1; } residue2 = NULL; } if (name) { if (buf[0]) { /* * There is always room at orig_uri for the display-name because * at least one character has always been removed. A '"' or '<' * has been removed. */ strcpy(orig_uri, buf); *name = orig_uri; } else { *name = ""; } } return parse_uri_full(uri, scheme, user, pass, hostport, params, headers, residue2); } AST_TEST_DEFINE(parse_name_andor_addr_test) { int res = AST_TEST_PASS; char uri[1024]; char *name, *user, *pass, *hostport, *headers, *residue; struct uriparams params; struct testdata { char *desc; char *uri; char *name; char *user; char *pass; char *hostport; char *headers; char *residue; struct uriparams params; AST_LIST_ENTRY(testdata) list; }; struct testdata *testdataptr; static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist; struct testdata td1 = { .desc = "quotes and brackets", .uri = "\"name :@ \" ;tag=tag", .name = "name :@ ", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "", .residue = "tag=tag", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td2 = { .desc = "no quotes", .uri = "givenname familyname ;expires=3600", .name = "givenname familyname", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "", .residue = "expires=3600", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td3 = { .desc = "no brackets", .uri = "sip:user:secret@host:5060;param=discard;transport=tcp;q=1", .name = "", .user = "user", .pass = "secret", .hostport = "host:5060", .headers = "", .residue = "q=1", .params.transport = "tcp", .params.lr = 0, .params.user = "" }; struct testdata td4 = { .desc = "just host", .uri = "sips:host", .name = "", .user = "", .pass = "", .hostport = "host", .headers = "", .residue = "", .params.transport = "", .params.lr = 0, .params.user = "" }; AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &td1); AST_LIST_INSERT_TAIL(&testdatalist, &td2, list); AST_LIST_INSERT_TAIL(&testdatalist, &td3, list); AST_LIST_INSERT_TAIL(&testdatalist, &td4, list); switch (cmd) { case TEST_INIT: info->name = "parse_name_andor_addr_test"; info->category = "/channels/chan_sip/"; info->summary = "tests parsing of name_andor_addr abnf structure"; info->description = "Tests parsing of abnf name-andor-addr = name-addr / addr-spec " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) { name = user = pass = hostport = headers = residue = NULL; params.transport = params.user = params.method = params.ttl = params.maddr = NULL; params.lr = 0; ast_copy_string(uri,testdataptr->uri,sizeof(uri)); if (parse_name_andor_addr(uri, "sip:,sips:", &name, &user, &pass, &hostport, ¶ms, &headers, &residue) || (name && strcmp(testdataptr->name, name)) || (user && strcmp(testdataptr->user, user)) || (pass && strcmp(testdataptr->pass, pass)) || (hostport && strcmp(testdataptr->hostport, hostport)) || (headers && strcmp(testdataptr->headers, headers)) || (residue && strcmp(testdataptr->residue, residue)) || (strcmp(testdataptr->params.transport,params.transport)) || (strcmp(testdataptr->params.user,params.user)) ) { ast_test_status_update(test, "Sub-Test: %s,failed.\n", testdataptr->desc); res = AST_TEST_FAIL; } } return res; } int get_comma(char *in, char **out) { char *c; char *parse = in; if (out) { *out = in; } /* Skip any quoted text */ while (*parse) { if ((c = strchr(parse, '"'))) { in = (char *)find_closing_quote((const char *)c + 1, NULL); if (!*in) { ast_log(LOG_WARNING, "No closing quote found in '%s'\n", c); return -1; } else { break; } } else { break; } parse++; } parse = in; /* Skip any userinfo components of a uri as they may contain commas */ if ((c = strchr(parse,'@'))) { parse = c+1; } if ((out) && (c = strchr(parse,','))) { *c++ = '\0'; *out = c; return 0; } return 1; } int parse_contact_header(char *contactheader, struct contactliststruct *contactlist) { int res; int last; char *comma; char *residue; char *param; char *value; struct contact *split_contact = NULL; if (*contactheader == '*') { return 1; } split_contact = ast_calloc(1, sizeof(*split_contact)); AST_LIST_HEAD_SET_NOLOCK(contactlist, split_contact); while ((last = get_comma(contactheader, &comma)) != -1) { res = parse_name_andor_addr(contactheader, "sip:,sips:", &split_contact->name, &split_contact->user, &split_contact->pass, &split_contact->hostport, &split_contact->params, &split_contact->headers, &residue); if (res == -1) { return res; } /* parse contact params */ split_contact->expires = split_contact->q = ""; while ((value = strchr(residue,'='))) { *value++ = '\0'; param = residue; if ((residue = strchr(value,';'))) { *residue++ = '\0'; } else { residue = ""; } if (!strcmp(param,"expires")) { split_contact->expires = value; } else if (!strcmp(param,"q")) { split_contact->q = value; } } if (last) { return 0; } contactheader = comma; split_contact = ast_calloc(1, sizeof(*split_contact)); AST_LIST_INSERT_TAIL(contactlist, split_contact, list); } return last; } AST_TEST_DEFINE(parse_contact_header_test) { int res = AST_TEST_PASS; char contactheader[1024]; int star; struct contactliststruct contactlist; struct contactliststruct *contactlistptr=&contactlist; struct testdata { char *desc; char *contactheader; int star; struct contactliststruct *contactlist; AST_LIST_ENTRY(testdata) list; }; struct testdata *testdataptr; struct contact *tdcontactptr; struct contact *contactptr; static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist; struct contactliststruct contactlist1, contactlist2; struct testdata td1 = { .desc = "single contact", .contactheader = "\"name :@;?&,\" ;expires=3600", .contactlist = &contactlist1, .star = 0 }; struct contact contact11 = { .name = "name :@;?&,", .user = "user", .pass = "secret", .hostport = "host:5082", .params.transport = "tcp", .params.ttl = "", .params.lr = 0, .headers = "", .expires = "3600", .q = "" }; struct testdata td2 = { .desc = "multiple contacts", .contactheader = "sip:,user1,:,secret1,@host1;ttl=7;q=1;expires=3600,sips:host2", .contactlist = &contactlist2, .star = 0, }; struct contact contact21 = { .name = "", .user = ",user1,", .pass = ",secret1,", .hostport = "host1", .params.transport = "", .params.ttl = "7", .params.lr = 0, .headers = "", .expires = "3600", .q = "1" }; struct contact contact22 = { .name = "", .user = "", .pass = "", .hostport = "host2", .params.transport = "", .params.ttl = "", .params.lr = 0, .headers = "", .expires = "", .q = "" }; struct testdata td3 = { .desc = "star - all contacts", .contactheader = "*", .star = 1, .contactlist = NULL }; AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &td1); AST_LIST_INSERT_TAIL(&testdatalist, &td2, list); AST_LIST_INSERT_TAIL(&testdatalist, &td3, list); AST_LIST_HEAD_SET_NOLOCK(&contactlist1, &contact11); AST_LIST_HEAD_SET_NOLOCK(&contactlist2, &contact21); AST_LIST_INSERT_TAIL(&contactlist2, &contact22, list); switch (cmd) { case TEST_INIT: info->name = "parse_contact_header_test"; info->category = "/channels/chan_sip/"; info->summary = "tests parsing of sip contact header"; info->description = "Tests parsing of a contact header including those with multiple contacts " "Verifies output matches expected behavior."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) { ast_copy_string(contactheader,testdataptr->contactheader,sizeof(contactheader)); star = parse_contact_header(contactheader,contactlistptr); if (testdataptr->star) { /* expecting star rather than list of contacts */ if (!star) { ast_test_status_update(test, "Sub-Test: %s,failed.\n", testdataptr->desc); res = AST_TEST_FAIL; break; } } else { contactptr = AST_LIST_FIRST(contactlistptr); AST_LIST_TRAVERSE(testdataptr->contactlist, tdcontactptr, list) { if (!contactptr || strcmp(tdcontactptr->name, contactptr->name) || strcmp(tdcontactptr->user, contactptr->user) || strcmp(tdcontactptr->pass, contactptr->pass) || strcmp(tdcontactptr->hostport, contactptr->hostport) || strcmp(tdcontactptr->headers, contactptr->headers) || strcmp(tdcontactptr->expires, contactptr->expires) || strcmp(tdcontactptr->q, contactptr->q) || strcmp(tdcontactptr->params.transport, contactptr->params.transport) || strcmp(tdcontactptr->params.ttl, contactptr->params.ttl) || (tdcontactptr->params.lr != contactptr->params.lr) ) { ast_test_status_update(test, "Sub-Test: %s,failed.\n", testdataptr->desc); res = AST_TEST_FAIL; break; } contactptr = AST_LIST_NEXT(contactptr,list); } while ((contactptr = AST_LIST_REMOVE_HEAD(contactlistptr,list))) { ast_free(contactptr); } } } return res; } /*! * \brief Parse supported header in incoming packet * * \details 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. * * \param options list * \param unsupported out buffer (optional) * \param unsupported_len out buffer length (optional) */ unsigned int parse_sip_options(const char *options, char *unsupported, size_t unsupported_len) { char *next, *sep; char *temp; int i, found, supported; unsigned int profile = 0; char *out = unsupported; size_t outlen = unsupported_len; char *cur_out = out; if (ast_strlen_zero(options) ) return 0; temp = ast_strdupa(options); ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", options); for (next = temp; next; next = sep) { found = FALSE; supported = FALSE; if ((sep = strchr(next, ',')) != NULL) { *sep++ = '\0'; } /* trim leading and trailing whitespace */ next = ast_strip(next); if (ast_strlen_zero(next)) { continue; /* if there is a blank argument in there just skip it */ } ast_debug(3, "Found SIP option: -%s-\n", next); for (i = 0; i < ARRAY_LEN(sip_options); i++) { if (!strcasecmp(next, sip_options[i].text)) { profile |= sip_options[i].id; if (sip_options[i].supported == SUPPORTED) { supported = TRUE; } found = TRUE; ast_debug(3, "Matched SIP option: %s\n", next); break; } } /* If option is not supported, add to unsupported out buffer */ if (!supported && out && outlen) { size_t copylen = strlen(next); size_t cur_outlen = strlen(out); /* Check to see if there is enough room to store this option. * Copy length is string length plus 2 for the ',' and '\0' */ if ((cur_outlen + copylen + 2) < outlen) { /* if this isn't the first item, add the ',' */ if (cur_outlen) { *cur_out = ','; cur_out++; cur_outlen++; } ast_copy_string(cur_out, next, (outlen - cur_outlen)); cur_out += copylen; } } if (!found) { profile |= SIP_OPT_UNKNOWN; if (!strncasecmp(next, "x-", 2)) ast_debug(3, "Found private SIP option, not supported: %s\n", next); else ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next); } } return profile; } AST_TEST_DEFINE(sip_parse_options_test) { int res = AST_TEST_PASS; char unsupported[64]; unsigned int option_profile = 0; struct testdata { char *name; char *input_options; char *expected_unsupported; unsigned int expected_profile; AST_LIST_ENTRY(testdata) list; }; struct testdata *testdataptr; static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist; struct testdata test1 = { .name = "test_all_unsupported", .input_options = "unsupported1,,, ,unsupported2,unsupported3,unsupported4", .expected_unsupported = "unsupported1,unsupported2,unsupported3,unsupported4", .expected_profile = SIP_OPT_UNKNOWN, }; struct testdata test2 = { .name = "test_all_unsupported_one_supported", .input_options = " unsupported1, replaces, unsupported3 , , , ,unsupported4", .expected_unsupported = "unsupported1,unsupported3,unsupported4", .expected_profile = SIP_OPT_UNKNOWN | SIP_OPT_REPLACES }; struct testdata test3 = { .name = "test_two_supported_two_unsupported", .input_options = ",, timer ,replaces ,unsupported3,unsupported4", .expected_unsupported = "unsupported3,unsupported4", .expected_profile = SIP_OPT_UNKNOWN | SIP_OPT_REPLACES | SIP_OPT_TIMER, }; struct testdata test4 = { .name = "test_all_supported", .input_options = "timer,replaces", .expected_unsupported = "", .expected_profile = SIP_OPT_REPLACES | SIP_OPT_TIMER, }; struct testdata test5 = { .name = "test_all_supported_redundant", .input_options = "timer,replaces,timer,replace,timer,replaces", .expected_unsupported = "", .expected_profile = SIP_OPT_REPLACES | SIP_OPT_TIMER, }; struct testdata test6 = { .name = "test_buffer_overflow", .input_options = "unsupported1,replaces,timer,unsupported4,unsupported_huge____" "____________________________________,__________________________________________" "________________________________________________", .expected_unsupported = "unsupported1,unsupported4", .expected_profile = SIP_OPT_UNKNOWN | SIP_OPT_REPLACES | SIP_OPT_TIMER, }; struct testdata test7 = { .name = "test_null_input", .input_options = NULL, .expected_unsupported = "", .expected_profile = 0, }; struct testdata test8 = { .name = "test_whitespace_input", .input_options = " ", .expected_unsupported = "", .expected_profile = 0, }; struct testdata test9 = { .name = "test_whitespace_plus_option_input", .input_options = " , , ,timer , , , , , ", .expected_unsupported = "", .expected_profile = SIP_OPT_TIMER, }; switch (cmd) { case TEST_INIT: info->name = "sip_parse_options_test"; info->category = "/channels/chan_sip/"; info->summary = "Tests parsing of sip options"; info->description = "Tests parsing of SIP options from supported and required " "header fields. Verifies when unsupported options are encountered " "that they are appended to the unsupported out buffer and that the " "correct bit field representnig the option profile is returned."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &test1); AST_LIST_INSERT_TAIL(&testdatalist, &test2, list); AST_LIST_INSERT_TAIL(&testdatalist, &test3, list); AST_LIST_INSERT_TAIL(&testdatalist, &test4, list); AST_LIST_INSERT_TAIL(&testdatalist, &test5, list); AST_LIST_INSERT_TAIL(&testdatalist, &test6, list); AST_LIST_INSERT_TAIL(&testdatalist, &test7, list); AST_LIST_INSERT_TAIL(&testdatalist, &test8, list); AST_LIST_INSERT_TAIL(&testdatalist, &test9, list); /* Test with unsupported char buffer */ AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) { memset(unsupported, 0, sizeof(unsupported)); option_profile = parse_sip_options(testdataptr->input_options, unsupported, ARRAY_LEN(unsupported)); if (option_profile != testdataptr->expected_profile || strcmp(unsupported, testdataptr->expected_unsupported)) { ast_test_status_update(test, "Test with output buffer \"%s\", expected unsupported: %s actual unsupported:" "%s expected bit profile: %x actual bit profile: %x\n", testdataptr->name, testdataptr->expected_unsupported, unsupported, testdataptr->expected_profile, option_profile); res = AST_TEST_FAIL; } else { ast_test_status_update(test, "\"%s\" passed got expected unsupported: %s and bit profile: %x\n", testdataptr->name, unsupported, option_profile); } option_profile = parse_sip_options(testdataptr->input_options, NULL, 0); if (option_profile != testdataptr->expected_profile) { ast_test_status_update(test, "NULL output test \"%s\", expected bit profile: %x actual bit profile: %x\n", testdataptr->name, testdataptr->expected_profile, option_profile); res = AST_TEST_FAIL; } else { ast_test_status_update(test, "\"%s\" with NULL output buf passed, bit profile: %x\n", testdataptr->name, option_profile); } } return res; } /*! \brief 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 * * \param input1 Parameters from URI 1 * \param input2 Parameters from URI 2 * \retval 0 URIs' parameters match * \retval nonzero URIs' parameters do not 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 = 0; int zerolength2 = 0; int maddrmatch = 0; int ttlmatch = 0; int usermatch = 0; int methodmatch = 0; if (ast_strlen_zero(input1)) { zerolength1 = 1; } else { params1 = ast_strdupa(input1); } if (ast_strlen_zero(input2)) { zerolength2 = 1; } 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 = 0; 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)) { goto fail; } else { matched = 1; break; } } } /* Check to see if the parameter is one of the 'must-match' parameters */ if (!strcasecmp(name1, "maddr")) { if (matched) { maddrmatch = 1; } else { goto fail; } } else if (!strcasecmp(name1, "ttl")) { if (matched) { ttlmatch = 1; } else { goto fail; } } else if (!strcasecmp(name1, "user")) { if (matched) { usermatch = 1; } else { goto fail; } } else if (!strcasecmp(name1, "method")) { if (matched) { methodmatch = 1; } else { goto fail; } } } /* 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)) { goto fail; } } return 0; fail: return 1; } /*! \brief 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. * * \param input1 Headers from URI 1 * \param input2 Headers from URI 2 * \retval 0 URI headers match * \retval nonzero URI headers do not match */ static int sip_uri_headers_cmp(const char *input1, const char *input2) { char *headers1 = NULL; char *headers2 = NULL; int zerolength1 = 0; int zerolength2 = 0; int different = 0; char *header1; if (ast_strlen_zero(input1)) { zerolength1 = 1; } else { headers1 = ast_strdupa(input1); } if (ast_strlen_zero(input2)) { zerolength2 = 1; } 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 = 1; break; } } return different; } /*! * \brief 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 * * \param host1 The domain from the first URI * \param host2 THe domain from the second URI * \retval 0 The domains match * \retval nonzero The domains do not match */ 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. Specifying the "C" locale ensures that * The LC_CTYPE conventions use those defined in ANSI C, * i.e. ASCII. */ if (!addr1_parsed) { #ifdef HAVE_XLOCALE_H if(!c_locale) { return strcasecmp(host1, host2); } else { return strcasecmp_l(host1, host2, c_locale); } #else return strcasecmp(host1, host2); #endif } /* 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; /* XXX It would be really nice if we could just use parse_uri_full() here * to separate the components of the URI, but unfortunately it is written * in a way that can cause URI parameters to be discarded. */ 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); } #define URI_CMP_MATCH 0 #define URI_CMP_NOMATCH 1 AST_TEST_DEFINE(sip_uri_cmp_test) { static const struct { const char *uri1; const char *uri2; int expected_result; } uri_cmp_tests [] = { /* These are identical, so they match */ { "sip:bob@example.com", "sip:bob@example.com", URI_CMP_MATCH }, /* Different usernames. No match */ { "sip:alice@example.com", "sip:bob@example.com", URI_CMP_NOMATCH }, /* Different hosts. No match */ { "sip:bob@example.com", "sip:bob@examplez.com", URI_CMP_NOMATCH }, /* Now start using IP addresses. Identical, so they match */ { "sip:bob@1.2.3.4", "sip:bob@1.2.3.4", URI_CMP_MATCH }, /* Two identical IPv4 addresses represented differently. Match */ { "sip:bob@1.2.3.4", "sip:bob@001.002.003.004", URI_CMP_MATCH }, /* Logically equivalent IPv4 Address and hostname. No Match */ { "sip:bob@127.0.0.1", "sip:bob@localhost", URI_CMP_NOMATCH }, /* Logically equivalent IPv6 address and hostname. No Match */ { "sip:bob@[::1]", "sip:bob@localhost", URI_CMP_NOMATCH }, /* Try an IPv6 one as well */ { "sip:bob@[2001:db8::1234]", "sip:bob@[2001:db8::1234]", URI_CMP_MATCH }, /* Two identical IPv6 addresses represented differently. Match */ { "sip:bob@[2001:db8::1234]", "sip:bob@[2001:0db8::1234]", URI_CMP_MATCH }, /* Different ports. No match */ { "sip:bob@1.2.3.4:5060", "sip:bob@1.2.3.4:5061", URI_CMP_NOMATCH }, /* Same port logically, but only one address specifies it. No match */ { "sip:bob@1.2.3.4:5060", "sip:bob@1.2.3.4", URI_CMP_NOMATCH }, /* And for safety, try with IPv6 */ { "sip:bob@[2001:db8:1234]:5060", "sip:bob@[2001:db8:1234]", URI_CMP_NOMATCH }, /* User comparison is case sensitive. No match */ { "sip:bob@example.com", "sip:BOB@example.com", URI_CMP_NOMATCH }, /* Host comparison is case insensitive. Match */ { "sip:bob@example.com", "sip:bob@EXAMPLE.COM", URI_CMP_MATCH }, /* Add headers to the URI. Identical, so they match */ { "sip:bob@example.com?header1=value1&header2=value2", "sip:bob@example.com?header1=value1&header2=value2", URI_CMP_MATCH }, /* Headers in URI 1 are not in URI 2. No Match */ { "sip:bob@example.com?header1=value1&header2=value2", "sip:bob@example.com", URI_CMP_NOMATCH }, /* Header present in both URIs does not have matching values. No match */ { "sip:bob@example.com?header1=value1&header2=value2", "sip:bob@example.com?header1=value1&header2=value3", URI_CMP_NOMATCH }, /* Add parameters to the URI. Identical so they match */ { "sip:bob@example.com;param1=value1;param2=value2", "sip:bob@example.com;param1=value1;param2=value2", URI_CMP_MATCH }, /* Same parameters in both URIs but appear in different order. Match */ { "sip:bob@example.com;param2=value2;param1=value1", "sip:bob@example.com;param1=value1;param2=value2", URI_CMP_MATCH }, /* params in URI 1 are not in URI 2. Match */ { "sip:bob@example.com;param1=value1;param2=value2", "sip:bob@example.com", URI_CMP_MATCH }, /* param present in both URIs does not have matching values. No match */ { "sip:bob@example.com;param1=value1;param2=value2", "sip:bob@example.com;param1=value1;param2=value3", URI_CMP_NOMATCH }, /* URI 1 has a maddr param but URI 2 does not. No match */ { "sip:bob@example.com;param1=value1;maddr=192.168.0.1", "sip:bob@example.com;param1=value1", URI_CMP_NOMATCH }, /* URI 1 and URI 2 both have identical maddr params. Match */ { "sip:bob@example.com;param1=value1;maddr=192.168.0.1", "sip:bob@example.com;param1=value1;maddr=192.168.0.1", URI_CMP_MATCH }, /* URI 1 is a SIPS URI and URI 2 is a SIP URI. No Match */ { "sips:bob@example.com", "sip:bob@example.com", URI_CMP_NOMATCH }, /* No URI schemes. No match */ { "bob@example.com", "bob@example.com", URI_CMP_NOMATCH }, /* Crashiness tests. Just an address scheme. No match */ { "sip", "sips", URI_CMP_NOMATCH }, /* Still just an address scheme. Even though they're the same, No match */ { "sip", "sip", URI_CMP_NOMATCH }, /* Empty strings. No match */ { "", "", URI_CMP_NOMATCH }, /* An empty string and a NULL. No match */ { "", NULL, URI_CMP_NOMATCH }, }; int i; int test_res = AST_TEST_PASS; switch (cmd) { case TEST_INIT: info->name = "sip_uri_cmp_test"; info->category = "/channels/chan_sip/"; info->summary = "Tests comparison of SIP URIs"; info->description = "Several would-be tricky URI comparisons are performed"; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } for (i = 0; i < ARRAY_LEN(uri_cmp_tests); ++i) { int cmp_res1; int cmp_res2; if ((cmp_res1 = sip_uri_cmp(uri_cmp_tests[i].uri1, uri_cmp_tests[i].uri2))) { /* URI comparison may return -1 or +1 depending on the failure. Standardize * the return value to be URI_CMP_NOMATCH on any failure */ cmp_res1 = URI_CMP_NOMATCH; } if (cmp_res1 != uri_cmp_tests[i].expected_result) { ast_test_status_update(test, "Unexpected comparison result for URIs %s and %s. " "Expected %s but got %s\n", uri_cmp_tests[i].uri1, uri_cmp_tests[i].uri2, uri_cmp_tests[i].expected_result == URI_CMP_MATCH ? "Match" : "No Match", cmp_res1 == URI_CMP_MATCH ? "Match" : "No Match"); test_res = AST_TEST_FAIL; } /* All URI comparisons are commutative, so for the sake of being thorough, we'll * rerun the comparison with the parameters reversed */ if ((cmp_res2 = sip_uri_cmp(uri_cmp_tests[i].uri2, uri_cmp_tests[i].uri1))) { /* URI comparison may return -1 or +1 depending on the failure. Standardize * the return value to be URI_CMP_NOMATCH on any failure */ cmp_res2 = URI_CMP_NOMATCH; } if (cmp_res2 != uri_cmp_tests[i].expected_result) { ast_test_status_update(test, "Unexpected comparison result for URIs %s and %s. " "Expected %s but got %s\n", uri_cmp_tests[i].uri2, uri_cmp_tests[i].uri1, uri_cmp_tests[i].expected_result == URI_CMP_MATCH ? "Match" : "No Match", cmp_res2 == URI_CMP_MATCH ? "Match" : "No Match"); test_res = AST_TEST_FAIL; } } return test_res; } void free_via(struct sip_via *v) { if (!v) { return; } ast_free(v->via); ast_free(v); } struct sip_via *parse_via(const char *header) { struct sip_via *v = ast_calloc(1, sizeof(*v)); char *via, *parm; if (!v) { return NULL; } v->via = ast_strdup(header); v->ttl = 1; via = v->via; if (ast_strlen_zero(via)) { ast_log(LOG_ERROR, "received request without a Via header\n"); free_via(v); return NULL; } /* seperate the first via-parm */ via = strsep(&via, ","); /* chop off sent-protocol */ v->protocol = strsep(&via, " \t\r\n"); if (ast_strlen_zero(v->protocol)) { ast_log(LOG_ERROR, "missing sent-protocol in Via header\n"); free_via(v); return NULL; } v->protocol = ast_skip_blanks(v->protocol); if (via) { via = ast_skip_blanks(via); } /* chop off sent-by */ v->sent_by = strsep(&via, "; \t\r\n"); if (ast_strlen_zero(v->sent_by)) { ast_log(LOG_ERROR, "missing sent-by in Via header\n"); free_via(v); return NULL; } v->sent_by = ast_skip_blanks(v->sent_by); /* store the port, we have to handle ipv6 addresses containing ':' * characters gracefully */ if (((parm = strchr(v->sent_by, ']')) && *(++parm) == ':') || (parm = strchr(v->sent_by, ':'))) { char *endptr; v->port = strtol(++parm, &endptr, 10); } /* evaluate any via-parms */ while ((parm = strsep(&via, "; \t\r\n"))) { char *c; if ((c = strstr(parm, "maddr="))) { v->maddr = ast_skip_blanks(c + sizeof("maddr=") - 1); } else if ((c = strstr(parm, "branch="))) { v->branch = ast_skip_blanks(c + sizeof("branch=") - 1); } else if ((c = strstr(parm, "ttl="))) { char *endptr; c = ast_skip_blanks(c + sizeof("ttl=") - 1); v->ttl = strtol(c, &endptr, 10); /* make sure we got a valid ttl value */ if (c == endptr) { v->ttl = 1; } } } return v; } AST_TEST_DEFINE(parse_via_test) { int res = AST_TEST_PASS; int i = 1; struct sip_via *via; struct testdata { char *in; char *expected_protocol; char *expected_branch; char *expected_sent_by; char *expected_maddr; unsigned int expected_port; unsigned char expected_ttl; int expected_null; AST_LIST_ENTRY(testdata) list; }; struct testdata *testdataptr; static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist; struct testdata t1 = { .in = "SIP/2.0/UDP host:port;branch=thebranch", .expected_protocol = "SIP/2.0/UDP", .expected_sent_by = "host:port", .expected_branch = "thebranch", }; struct testdata t2 = { .in = "SIP/2.0/UDP host:port", .expected_protocol = "SIP/2.0/UDP", .expected_sent_by = "host:port", .expected_branch = "", }; struct testdata t3 = { .in = "SIP/2.0/UDP", .expected_null = 1, }; struct testdata t4 = { .in = "BLAH/BLAH/BLAH host:port;branch=", .expected_protocol = "BLAH/BLAH/BLAH", .expected_sent_by = "host:port", .expected_branch = "", }; struct testdata t5 = { .in = "SIP/2.0/UDP host:5060;branch=thebranch;maddr=224.0.0.1;ttl=1", .expected_protocol = "SIP/2.0/UDP", .expected_sent_by = "host:5060", .expected_port = 5060, .expected_branch = "thebranch", .expected_maddr = "224.0.0.1", .expected_ttl = 1, }; struct testdata t6 = { .in = "SIP/2.0/UDP host:5060;\n branch=thebranch;\r\n maddr=224.0.0.1; ttl=1", .expected_protocol = "SIP/2.0/UDP", .expected_sent_by = "host:5060", .expected_port = 5060, .expected_branch = "thebranch", .expected_maddr = "224.0.0.1", .expected_ttl = 1, }; struct testdata t7 = { .in = "SIP/2.0/UDP [::1]:5060", .expected_protocol = "SIP/2.0/UDP", .expected_sent_by = "[::1]:5060", .expected_port = 5060, .expected_branch = "", }; switch (cmd) { case TEST_INIT: info->name = "parse_via_test"; info->category = "/channels/chan_sip/"; info->summary = "Tests parsing the Via header"; info->description = "Runs through various test situations in which various " " parameters parameter must be extracted from a VIA header"; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &t1); AST_LIST_INSERT_TAIL(&testdatalist, &t2, list); AST_LIST_INSERT_TAIL(&testdatalist, &t3, list); AST_LIST_INSERT_TAIL(&testdatalist, &t4, list); AST_LIST_INSERT_TAIL(&testdatalist, &t5, list); AST_LIST_INSERT_TAIL(&testdatalist, &t6, list); AST_LIST_INSERT_TAIL(&testdatalist, &t7, list); AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) { via = parse_via(testdataptr->in); if (!via) { if (!testdataptr->expected_null) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "failed to parse header\n", i, testdataptr->in); res = AST_TEST_FAIL; } i++; continue; } if (testdataptr->expected_null) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "successfully parased invalid via header\n", i, testdataptr->in); res = AST_TEST_FAIL; free_via(via); i++; continue; } if ((ast_strlen_zero(via->protocol) && !ast_strlen_zero(testdataptr->expected_protocol)) || (!ast_strlen_zero(via->protocol) && strcmp(via->protocol, testdataptr->expected_protocol))) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "parsed protocol = \"%s\"\n" "expected = \"%s\"\n" "failed to parse protocol\n", i, testdataptr->in, via->protocol, testdataptr->expected_protocol); res = AST_TEST_FAIL; } if ((ast_strlen_zero(via->sent_by) && !ast_strlen_zero(testdataptr->expected_sent_by)) || (!ast_strlen_zero(via->sent_by) && strcmp(via->sent_by, testdataptr->expected_sent_by))) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "parsed sent_by = \"%s\"\n" "expected = \"%s\"\n" "failed to parse sent-by\n", i, testdataptr->in, via->sent_by, testdataptr->expected_sent_by); res = AST_TEST_FAIL; } if (testdataptr->expected_port && testdataptr->expected_port != via->port) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "parsed port = \"%u\"\n" "expected = \"%u\"\n" "failed to parse port\n", i, testdataptr->in, via->port, testdataptr->expected_port); res = AST_TEST_FAIL; } if ((ast_strlen_zero(via->branch) && !ast_strlen_zero(testdataptr->expected_branch)) || (!ast_strlen_zero(via->branch) && strcmp(via->branch, testdataptr->expected_branch))) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "parsed branch = \"%s\"\n" "expected = \"%s\"\n" "failed to parse branch\n", i, testdataptr->in, via->branch, testdataptr->expected_branch); res = AST_TEST_FAIL; } if ((ast_strlen_zero(via->maddr) && !ast_strlen_zero(testdataptr->expected_maddr)) || (!ast_strlen_zero(via->maddr) && strcmp(via->maddr, testdataptr->expected_maddr))) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "parsed maddr = \"%s\"\n" "expected = \"%s\"\n" "failed to parse maddr\n", i, testdataptr->in, via->maddr, testdataptr->expected_maddr); res = AST_TEST_FAIL; } if (testdataptr->expected_ttl && testdataptr->expected_ttl != via->ttl) { ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n" "parsed ttl = \"%d\"\n" "expected = \"%d\"\n" "failed to parse ttl\n", i, testdataptr->in, via->ttl, testdataptr->expected_ttl); res = AST_TEST_FAIL; } free_via(via); i++; } return res; } void sip_request_parser_register_tests(void) { AST_TEST_REGISTER(get_calleridname_test); AST_TEST_REGISTER(sip_parse_uri_test); AST_TEST_REGISTER(get_in_brackets_test); AST_TEST_REGISTER(get_name_and_number_test); AST_TEST_REGISTER(sip_parse_uri_full_test); AST_TEST_REGISTER(parse_name_andor_addr_test); AST_TEST_REGISTER(parse_contact_header_test); AST_TEST_REGISTER(sip_parse_options_test); AST_TEST_REGISTER(sip_uri_cmp_test); AST_TEST_REGISTER(parse_via_test); } void sip_request_parser_unregister_tests(void) { AST_TEST_UNREGISTER(sip_parse_uri_test); AST_TEST_UNREGISTER(get_calleridname_test); AST_TEST_UNREGISTER(get_in_brackets_test); AST_TEST_UNREGISTER(get_name_and_number_test); AST_TEST_UNREGISTER(sip_parse_uri_full_test); AST_TEST_UNREGISTER(parse_name_andor_addr_test); AST_TEST_UNREGISTER(parse_contact_header_test); AST_TEST_UNREGISTER(sip_parse_options_test); AST_TEST_UNREGISTER(sip_uri_cmp_test); AST_TEST_UNREGISTER(parse_via_test); } int sip_reqresp_parser_init(void) { #ifdef HAVE_XLOCALE_H c_locale = newlocale(LC_CTYPE_MASK, "C", NULL); if (!c_locale) { return -1; } #endif return 0; } void sip_reqresp_parser_exit(void) { #ifdef HAVE_XLOCALE_H if (c_locale) { freelocale(c_locale); c_locale = NULL; } #endif } asterisk-13.1.0/channels/sip/route.c0000644000000000000000000001070612371207211016007 0ustar rootroot/* * 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. */ /*! * \file * \brief sip_route functions */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 420562 $") #include "asterisk/utils.h" #include "include/route.h" #include "include/reqresp_parser.h" /*! * \brief Traverse route hops */ #define sip_route_traverse(route,elem) AST_LIST_TRAVERSE(&(route)->list, elem, list) #define sip_route_first(route) AST_LIST_FIRST(&(route)->list) /*! * \brief Structure to save a route hop */ struct sip_route_hop { AST_LIST_ENTRY(sip_route_hop) list; char uri[0]; }; 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++; /* ast_calloc is not needed because all fields are initialized in this block */ hop = ast_malloc(sizeof(struct sip_route_hop) + len); if (!hop) { return NULL; } ast_copy_string(hop->uri, uri, len); if (inserthead) { AST_LIST_INSERT_HEAD(&route->list, hop, list); route->type = route_invalidated; } else { if (sip_route_empty(route)) { route->type = route_invalidated; } AST_LIST_INSERT_TAIL(&route->list, hop, list); hop->list.next = NULL; } return hop->uri; } void sip_route_process_header(struct sip_route *route, const char *header, int inserthead) { const char *hop; int len = 0; const char *uri; if (!route) { ast_log(LOG_ERROR, "sip_route_process_header requires non-null route"); ast_do_crash(); return; } while (!get_in_brackets_const(header, &uri, &len)) { header = strchr(header, ','); if (header >= uri && header <= (uri + len)) { /* comma inside brackets */ const char *next_br = strchr(header, '<'); if (next_br && next_br <= (uri + len)) { header++; continue; } continue; } if ((hop = sip_route_add(route, uri, len, inserthead))) { ast_debug(2, "sip_route_process_header: <%s>\n", hop); } header = strchr(uri + len + 1, ','); if (header == NULL) { /* No more field-values, we're done with this header */ break; } /* Advance past comma */ header++; } } 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); sip_route_traverse(src, hop) { const char *uri = sip_route_add(dst, hop->uri, strlen(hop->uri), 0); if (uri) { ast_debug(2, "sip_route_copy: 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, list))) { ast_free(hop); } route->type = route_loose; } void sip_route_dump(const struct sip_route *route) { if (sip_route_empty(route)) { ast_verbose("sip_route_dump: no route/path\n"); } else { struct sip_route_hop *hop; sip_route_traverse(route, hop) { ast_verbose("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 *buf; int i = 0 - skip; buf = ast_str_create(64); if (!buf) { return NULL; } comma = formatcli ? ", " : ","; sip_route_traverse(route, hop) { if (i >= 0) { ast_str_append(&buf, 0, "%s<%s>", i ? comma : "", hop->uri); } i++; } if (formatcli && i <= 0) { ast_str_append(&buf, 0, "N/A"); } return buf; } int sip_route_is_strict(struct sip_route *route) { if (!route) { return 0; } if (route->type == route_invalidated) { struct sip_route_hop *hop = sip_route_first(route); int ret = hop && (strstr(hop->uri, ";lr") == NULL); route->type = ret ? route_strict : route_loose; return ret; } return (route->type == route_strict) ? 1 : 0; } const char *sip_route_first_uri(const struct sip_route *route) { struct sip_route_hop *hop = sip_route_first(route); return hop ? hop->uri : NULL; } asterisk-13.1.0/channels/sip/dialplan_functions.c0000644000000000000000000003351412371207211020527 0ustar rootroot/* * 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. */ /*! * \file * \brief sip channel dialplan functions and unit tests */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 420562 $") #include #include "asterisk/channel.h" #include "asterisk/rtp_engine.h" #include "asterisk/pbx.h" #include "asterisk/acl.h" #include "include/sip.h" #include "include/globals.h" #include "include/dialog.h" #include "include/dialplan_functions.h" #include "include/sip_utils.h" int sip_acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen) { struct sip_pvt *p = ast_channel_tech_pvt(chan); char *parse = ast_strdupa(preparse); 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", funcname); return -1; } AST_STANDARD_APP_ARGS(args, parse); /* Sanity check */ if (!IS_SIP_TECH(ast_channel_tech(chan))) { ast_log(LOG_ERROR, "Cannot call %s on a non-SIP channel\n", funcname); return 0; } memset(buf, 0, buflen); if (p == NULL) { return -1; } if (!strcasecmp(args.param, "peerip")) { ast_copy_string(buf, ast_sockaddr_isnull(&p->sa) ? "" : ast_sockaddr_stringify_addr(&p->sa), buflen); } else if (!strcasecmp(args.param, "recvip")) { ast_copy_string(buf, ast_sockaddr_isnull(&p->recv) ? "" : ast_sockaddr_stringify_addr(&p->recv), buflen); } else if (!strcasecmp(args.param, "recvport")) { ast_copy_string(buf, ast_sockaddr_isnull(&p->recv) ? "" : ast_sockaddr_stringify_port(&p->recv), buflen); } else if (!strcasecmp(args.param, "from")) { ast_copy_string(buf, p->from, buflen); } else if (!strcasecmp(args.param, "uri")) { ast_copy_string(buf, p->uri, buflen); } else if (!strcasecmp(args.param, "useragent")) { ast_copy_string(buf, p->useragent, buflen); } else if (!strcasecmp(args.param, "peername")) { ast_copy_string(buf, p->peername, buflen); } else if (!strcasecmp(args.param, "t38passthrough")) { ast_copy_string(buf, (p->t38.state == 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 = p->rtp; else if (!strcasecmp(args.type, "video")) stream = p->vrtp; else if (!strcasecmp(args.type, "text")) stream = p->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 = p->rtp; else if (!strcasecmp(args.type, "video")) stream = p->vrtp; else if (!strcasecmp(args.type, "text")) stream = p->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 = p->rtp; } else if (!strcasecmp(args.type, "video")) { rtp = p->vrtp; } else if (!strcasecmp(args.type, "text")) { rtp = p->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; }; } lookup[] = { { "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(lookup[i].name); i++) { if (!strcasecmp(args.field, lookup[i].name)) { if (lookup[i].type == INT) { snprintf(buf, buflen, "%u", *lookup[i].i4); } else { snprintf(buf, buflen, "%f", *lookup[i].d8); } return 0; } } ast_log(LOG_WARNING, "Unrecognized argument '%s' to %s\n", preparse, funcname); return -1; } } else if (!strcasecmp(args.param, "secure_signaling")) { snprintf(buf, buflen, "%s", p->socket.type == AST_TRANSPORT_TLS ? "1" : ""); } else if (!strcasecmp(args.param, "secure_media")) { snprintf(buf, buflen, "%s", p->srtp ? "1" : ""); } else { res = -1; } return res; } #ifdef TEST_FRAMEWORK static int test_sip_rtpqos_1_new(struct ast_rtp_instance *instance, struct ast_sched_context *sched, struct ast_sockaddr *addr, void *data) { /* Needed to pass sanity checks */ ast_rtp_instance_set_data(instance, data); return 0; } static int test_sip_rtpqos_1_destroy(struct ast_rtp_instance *instance) { /* Needed to pass sanity checks */ return 0; } static struct ast_frame *test_sip_rtpqos_1_read(struct ast_rtp_instance *instance, int rtcp) { /* Needed to pass sanity checks */ return &ast_null_frame; } static int test_sip_rtpqos_1_write(struct ast_rtp_instance *instance, struct ast_frame *frame) { /* Needed to pass sanity checks */ return 0; } static int test_sip_rtpqos_1_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_instance_stats *stats, enum ast_rtp_instance_stat stat) { struct ast_rtp_instance_stats *s = ast_rtp_instance_get_data(instance); memcpy(stats, s, sizeof(*stats)); return 0; } AST_TEST_DEFINE(test_sip_rtpqos_1) { int i, res = AST_TEST_PASS; struct ast_rtp_engine test_engine = { .name = "test", .new = test_sip_rtpqos_1_new, .destroy = test_sip_rtpqos_1_destroy, .read = test_sip_rtpqos_1_read, .write = test_sip_rtpqos_1_write, .get_stat = test_sip_rtpqos_1_get_stat, }; struct ast_sockaddr sa = { {0, } }; struct ast_rtp_instance_stats mine = { 0, }; struct sip_pvt *p = NULL; struct ast_channel *chan = NULL; struct ast_str *varstr = NULL, *buffer = NULL; struct { const char *name; enum { INT, DBL } type; union { unsigned int *i4; double *d8; }; } lookup[] = { { "txcount", INT, { .i4 = &mine.txcount, }, }, { "rxcount", INT, { .i4 = &mine.rxcount, }, }, { "txjitter", DBL, { .d8 = &mine.txjitter, }, }, { "rxjitter", DBL, { .d8 = &mine.rxjitter, }, }, { "remote_maxjitter", DBL, { .d8 = &mine.remote_maxjitter, }, }, { "remote_minjitter", DBL, { .d8 = &mine.remote_minjitter, }, }, { "remote_normdevjitter", DBL, { .d8 = &mine.remote_normdevjitter, }, }, { "remote_stdevjitter", DBL, { .d8 = &mine.remote_stdevjitter, }, }, { "local_maxjitter", DBL, { .d8 = &mine.local_maxjitter, }, }, { "local_minjitter", DBL, { .d8 = &mine.local_minjitter, }, }, { "local_normdevjitter", DBL, { .d8 = &mine.local_normdevjitter, }, }, { "local_stdevjitter", DBL, { .d8 = &mine.local_stdevjitter, }, }, { "txploss", INT, { .i4 = &mine.txploss, }, }, { "rxploss", INT, { .i4 = &mine.rxploss, }, }, { "remote_maxrxploss", DBL, { .d8 = &mine.remote_maxrxploss, }, }, { "remote_minrxploss", DBL, { .d8 = &mine.remote_minrxploss, }, }, { "remote_normdevrxploss", DBL, { .d8 = &mine.remote_normdevrxploss, }, }, { "remote_stdevrxploss", DBL, { .d8 = &mine.remote_stdevrxploss, }, }, { "local_maxrxploss", DBL, { .d8 = &mine.local_maxrxploss, }, }, { "local_minrxploss", DBL, { .d8 = &mine.local_minrxploss, }, }, { "local_normdevrxploss", DBL, { .d8 = &mine.local_normdevrxploss, }, }, { "local_stdevrxploss", DBL, { .d8 = &mine.local_stdevrxploss, }, }, { "rtt", DBL, { .d8 = &mine.rtt, }, }, { "maxrtt", DBL, { .d8 = &mine.maxrtt, }, }, { "minrtt", DBL, { .d8 = &mine.minrtt, }, }, { "normdevrtt", DBL, { .d8 = &mine.normdevrtt, }, }, { "stdevrtt", DBL, { .d8 = &mine.stdevrtt, }, }, { "local_ssrc", INT, { .i4 = &mine.local_ssrc, }, }, { "remote_ssrc", INT, { .i4 = &mine.remote_ssrc, }, }, { NULL, }, }; switch (cmd) { case TEST_INIT: info->name = "test_sip_rtpqos"; info->category = "/channels/chan_sip/"; info->summary = "Test retrieval of SIP RTP QOS stats"; info->description = "Verify values in the RTP instance structure can be accessed through the dialplan."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } ast_rtp_engine_register2(&test_engine, NULL); /* Have to associate this with a SIP pvt and an ast_channel */ if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, NULL))) { res = AST_TEST_NOT_RUN; goto done; } if (!(p->rtp = ast_rtp_instance_new("test", sched, &bindaddr, &mine))) { res = AST_TEST_NOT_RUN; goto done; } ast_rtp_instance_set_remote_address(p->rtp, &sa); if (!(chan = ast_dummy_channel_alloc())) { res = AST_TEST_NOT_RUN; goto done; } ast_channel_tech_set(chan, &sip_tech); ast_channel_tech_pvt_set(chan, dialog_ref(p, "Give the owner channel a reference to the dialog")); p->owner = chan; varstr = ast_str_create(16); buffer = ast_str_create(16); if (!varstr || !buffer) { res = AST_TEST_NOT_RUN; goto done; } /* Populate "mine" with values, then retrieve them with the CHANNEL dialplan function */ for (i = 0; !ast_strlen_zero(lookup[i].name); i++) { ast_str_set(&varstr, 0, "${CHANNEL(rtpqos,audio,%s)}", lookup[i].name); if (lookup[i].type == INT) { int j; char cmpstr[256]; for (j = 1; j < 25; j++) { *lookup[i].i4 = j; ast_str_substitute_variables(&buffer, 0, chan, ast_str_buffer(varstr)); snprintf(cmpstr, sizeof(cmpstr), "%d", j); if (strcmp(cmpstr, ast_str_buffer(buffer))) { res = AST_TEST_FAIL; ast_test_status_update(test, "%s != %s != %s\n", ast_str_buffer(varstr), cmpstr, ast_str_buffer(buffer)); break; } } } else { double j, cmpdbl = 0.0; for (j = 1.0; j < 10.0; j += 0.3) { *lookup[i].d8 = j; ast_str_substitute_variables(&buffer, 0, chan, ast_str_buffer(varstr)); if (sscanf(ast_str_buffer(buffer), "%lf", &cmpdbl) != 1 || fabs(j - cmpdbl > .05)) { res = AST_TEST_FAIL; ast_test_status_update(test, "%s != %f != %s\n", ast_str_buffer(varstr), j, ast_str_buffer(buffer)); break; } } } } done: ast_free(varstr); ast_free(buffer); /* This unlink and unref will take care of destroying the channel, RTP instance, and SIP pvt */ if (p) { dialog_unlink_all(p); dialog_unref(p, "Destroy test object"); } ast_rtp_engine_unregister(&test_engine); return res; } #endif /*! \brief SIP test registration */ void sip_dialplan_function_register_tests(void) { AST_TEST_REGISTER(test_sip_rtpqos_1); } /*! \brief SIP test registration */ void sip_dialplan_function_unregister_tests(void) { AST_TEST_UNREGISTER(test_sip_rtpqos_1); } asterisk-13.1.0/channels/sip/utils.c0000644000000000000000000000256212371207211016012 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2012, 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. */ /*! * \file * \brief Utility functions for chan_sip * * \author Terry Wilson */ /*** MODULEINFO extended ***/ #include "asterisk.h" #include "asterisk/utils.h" #include "asterisk/cli.h" #include "include/sip.h" #include "include/sip_utils.h" const char *force_rport_string(struct ast_flags *flags) { if (ast_test_flag(&flags[2], SIP_PAGE3_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 *comedia_string(struct ast_flags *flags) { if (ast_test_flag(&flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) { return ast_test_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP) ? "Auto (Yes)" : "Auto (No)"; } return AST_CLI_YESNO(ast_test_flag(&flags[1], SIP_PAGE2_SYMMETRICRTP)); } asterisk-13.1.0/channels/chan_dahdi.c0000644000000000000000000225374212437125770016147 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2008, 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. */ /*! \file * * \brief DAHDI for Pseudo TDM * * \author Mark Spencer * * Connects to the DAHDI telephony library as well as * libpri. Libpri is optional and needed only if you are * going to use ISDN connections. * * You need to install libraries before you attempt to compile * and install the DAHDI channel. * * \ingroup channel_drivers * * \todo Deprecate the "musiconhold" configuration option post 1.4 */ /*! \li \ref chan_dahdi.c uses the configuration file \ref chan_dahdi.conf * \addtogroup configuration_file */ /*! \page chan_dahdi.conf chan_dahdi.conf * \verbinclude chan_dahdi.conf.sample */ /*** MODULEINFO res_smdi dahdi tonezone pri ss7 openr2 core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 428687 $") #if defined(__NetBSD__) || defined(__FreeBSD__) #include #include #else #include #endif #include #include #include "sig_analog.h" /* Analog signaling is currently still present in chan_dahdi for use with * radio. Sig_analog does not currently handle any radio operations. If * radio only uses analog signaling, then the radio handling logic could * be placed in sig_analog and the duplicated code could be removed. */ #if defined(HAVE_PRI) #include "sig_pri.h" #ifndef PRI_RESTART #error "Upgrade your libpri" #endif #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) #include "sig_ss7.h" #if !defined(LIBSS7_ABI_COMPATIBILITY) #error "Upgrade your libss7" #elif LIBSS7_ABI_COMPATIBILITY != 2 #error "Your installed libss7 is not compatible" #endif #endif /* defined(HAVE_SS7) */ #if defined(HAVE_OPENR2) /* put this here until sig_mfcr2 comes along */ #define SIG_MFCR2_MAX_CHANNELS 672 /*!< No more than a DS3 per trunk group */ #endif /* defined(HAVE_OPENR2) */ #include "asterisk/lock.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/file.h" #include "asterisk/ulaw.h" #include "asterisk/alaw.h" #include "asterisk/callerid.h" #include "asterisk/adsi.h" #include "asterisk/cli.h" #include "asterisk/pickup.h" #include "asterisk/features.h" #include "asterisk/musiconhold.h" #include "asterisk/say.h" #include "asterisk/tdd.h" #include "asterisk/app.h" #include "asterisk/dsp.h" #include "asterisk/astdb.h" #include "asterisk/manager.h" #include "asterisk/causes.h" #include "asterisk/term.h" #include "asterisk/utils.h" #include "asterisk/transcap.h" #include "asterisk/stringfields.h" #include "asterisk/abstract_jb.h" #include "asterisk/smdi.h" #include "asterisk/devicestate.h" #include "asterisk/paths.h" #include "asterisk/ccss.h" #include "asterisk/data.h" #include "asterisk/features_config.h" #include "asterisk/bridge.h" #include "asterisk/stasis_channels.h" #include "asterisk/parking.h" #include "asterisk/format_cache.h" #include "chan_dahdi.h" #include "dahdi/bridge_native_dahdi.h" /*** DOCUMENTATION Send digits out of band over a PRI. This application will send the given string of digits in a Keypad Facility IE over the current channel. Send an ISDN call rerouting/deflection facility message. Destination number. Original called number. Diversion reason, if not specified defaults to unknown This application will send an ISDN switch specific call rerouting/deflection facility message over the current channel. Supported switches depend upon the version of libpri in use. Accept an R2 call if its not already accepted (you still need to answer it) Yes or No. Whether you want to accept the call with charge or without charge. This application will Accept the R2 call either with charge or no charge. Transfer DAHDI Channel. DAHDI channel number to transfer. Simulate a flash hook event by the user connected to the channel. Valid only for analog channels. Hangup DAHDI Channel. DAHDI channel number to hangup. Simulate an on-hook event by the user connected to the channel. Valid only for analog channels. Dial over DAHDI channel while offhook. DAHDI channel number to dial digits. Digits to dial. Generate DTMF control frames to the bridged peer. Toggle DAHDI channel Do Not Disturb status ON. DAHDI channel number to set DND on. Equivalent to the CLI command "dahdi set dnd channel on". Feature only supported by analog channels. Toggle DAHDI channel Do Not Disturb status OFF. DAHDI channel number to set DND off. Equivalent to the CLI command "dahdi set dnd channel off". Feature only supported by analog channels. Show status of DAHDI channels. Specify the specific channel number to show. Show all channels if zero or not present. Similar to the CLI command "dahdi show channels". Fully Restart DAHDI channels (terminates calls). Equivalent to the CLI command "dahdi restart". Show status of PRI spans. Specify the specific span to show. Show all spans if zero or not present. Similar to the CLI command "pri show spans". Set PRI debug levels for a span Which span to affect. What debug level to set. May be a numerical value or a text value from the list below Equivalent to the CLI command "pri set debug <level> span <span>". Set the file used for PRI debug message output Path of file to write debug output. Equivalent to the CLI command "pri set debug file <output-file>" Disables file output for PRI debug messages Raised when an alarm is cleared on a DAHDI channel. The DAHDI channel on which the alarm was cleared. This is not an Asterisk channel identifier. Raised when an alarm is cleared on a DAHDI span. The span on which the alarm was cleared. Raised when the Do Not Disturb state is changed on a DAHDI channel. The DAHDI channel on which DND status changed. This is not an Asterisk channel identifier. Raised when an alarm is set on a DAHDI channel. The channel on which the alarm occurred. This is not an Asterisk channel identifier. A textual description of the alarm that occurred. Raised when an alarm is set on a DAHDI span. The span on which the alarm occurred. A textual description of the alarm that occurred. Raised when a DAHDI channel is created or an underlying technology is associated with a DAHDI channel. The DAHDI span associated with this channel. The DAHDI channel associated with this channel. ***/ #define SMDI_MD_WAIT_TIMEOUT 1500 /* 1.5 seconds */ static const char * const lbostr[] = { "0 db (CSU)/0-133 feet (DSX-1)", "133-266 feet (DSX-1)", "266-399 feet (DSX-1)", "399-533 feet (DSX-1)", "533-655 feet (DSX-1)", "-7.5db (CSU)", "-15db (CSU)", "-22.5db (CSU)" }; /*! Global jitterbuffer configuration - by default, jb is disabled * \note Values shown here match the defaults shown in chan_dahdi.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; /*! * \note Define ZHONE_HACK to cause us to go off hook and then back on hook when * the user hangs up to reset the state machine so ring works properly. * This is used to be able to support kewlstart by putting the zhone in * groundstart mode since their forward disconnect supervision is entirely * broken even though their documentation says it isn't and their support * is entirely unwilling to provide any assistance with their channel banks * even though their web site says they support their products for life. */ /* #define ZHONE_HACK */ /*! \brief Typically, how many rings before we should send Caller*ID */ #define DEFAULT_CIDRINGS 1 #define AST_LAW(p) (((p)->law == DAHDI_LAW_ALAW) ? ast_format_alaw : ast_format_ulaw) /*! \brief Signaling types that need to use MF detection should be placed in this macro */ #define NEED_MFDETECT(p) (((p)->sig == SIG_FEATDMF) || ((p)->sig == SIG_FEATDMF_TA) || ((p)->sig == SIG_E911) || ((p)->sig == SIG_FGC_CAMA) || ((p)->sig == SIG_FGC_CAMAMF) || ((p)->sig == SIG_FEATB)) static const char tdesc[] = "DAHDI Telephony" #if defined(HAVE_PRI) || defined(HAVE_SS7) || defined(HAVE_OPENR2) " w/" #if defined(HAVE_PRI) "PRI" #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) #if defined(HAVE_PRI) " & " #endif /* defined(HAVE_PRI) */ "SS7" #endif /* defined(HAVE_SS7) */ #if defined(HAVE_OPENR2) #if defined(HAVE_PRI) || defined(HAVE_SS7) " & " #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ "MFC/R2" #endif /* defined(HAVE_OPENR2) */ #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) || defined(HAVE_OPENR2) */ ; static const char config[] = "chan_dahdi.conf"; #ifdef LOTS_OF_SPANS #define NUM_SPANS DAHDI_MAX_SPANS #else #define NUM_SPANS 32 #endif #define CHAN_PSEUDO -2 #define CALLPROGRESS_PROGRESS 1 #define CALLPROGRESS_FAX_OUTGOING 2 #define CALLPROGRESS_FAX_INCOMING 4 #define CALLPROGRESS_FAX (CALLPROGRESS_FAX_INCOMING | CALLPROGRESS_FAX_OUTGOING) #define NUM_CADENCE_MAX 25 static int num_cadence = 4; static int user_has_defined_cadences = 0; static int has_pseudo; static struct dahdi_ring_cadence cadences[NUM_CADENCE_MAX] = { { { 125, 125, 2000, 4000 } }, /*!< Quick chirp followed by normal ring */ { { 250, 250, 500, 1000, 250, 250, 500, 4000 } }, /*!< British style ring */ { { 125, 125, 125, 125, 125, 4000 } }, /*!< Three short bursts */ { { 1000, 500, 2500, 5000 } }, /*!< Long ring */ }; /*! \brief cidrings says in which pause to transmit the cid information, where the first pause * is 1, the second pause is 2 and so on. */ static int cidrings[NUM_CADENCE_MAX] = { 2, /*!< Right after first long ring */ 4, /*!< Right after long part */ 3, /*!< After third chirp */ 2, /*!< Second spell */ }; /* ETSI EN300 659-1 specifies the ring pulse between 200 and 300 mS */ static struct dahdi_ring_cadence AS_RP_cadence = {{250, 10000}}; #define ISTRUNK(p) ((p->sig == SIG_FXSLS) || (p->sig == SIG_FXSKS) || \ (p->sig == SIG_FXSGS) || (p->sig == SIG_PRI)) #define CANBUSYDETECT(p) (ISTRUNK(p) || (p->sig & (SIG_EM | SIG_EM_E1 | SIG_SF)) /* || (p->sig & __DAHDI_SIG_FXO) */) #define CANPROGRESSDETECT(p) (ISTRUNK(p) || (p->sig & (SIG_EM | SIG_EM_E1 | SIG_SF)) /* || (p->sig & __DAHDI_SIG_FXO) */) static char defaultcic[64] = ""; static char defaultozz[64] = ""; /*! Run this script when the MWI state changes on an FXO line, if mwimonitor is enabled */ static char mwimonitornotify[PATH_MAX] = ""; #ifndef HAVE_DAHDI_LINEREVERSE_VMWI static int mwisend_rpas = 0; #endif static char progzone[10] = ""; static int usedistinctiveringdetection = 0; static int distinctiveringaftercid = 0; static int numbufs = 4; static int mwilevel = 512; static int dtmfcid_level = 256; #define REPORT_CHANNEL_ALARMS 1 #define REPORT_SPAN_ALARMS 2 static int report_alarms = REPORT_CHANNEL_ALARMS; #ifdef HAVE_PRI static int pridebugfd = -1; static char pridebugfilename[1024] = ""; #endif /*! \brief Wait up to 16 seconds for first digit (FXO logic) */ static int firstdigittimeout = 16000; /*! \brief How long to wait for following digits (FXO logic) */ static int gendigittimeout = 8000; /*! \brief How long to wait for an extra digit, if there is an ambiguous match */ static int matchdigittimeout = 3000; /*! \brief Protect the interface list (of dahdi_pvt's) */ AST_MUTEX_DEFINE_STATIC(iflock); static int ifcount = 0; #ifdef HAVE_PRI AST_MUTEX_DEFINE_STATIC(pridebugfdlock); #endif /*! \brief 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(monlock); /*! \brief This is the thread for the monitor which checks for input on the channels which are not currently in use. */ static pthread_t monitor_thread = AST_PTHREADT_NULL; static ast_cond_t ss_thread_complete; AST_MUTEX_DEFINE_STATIC(ss_thread_lock); AST_MUTEX_DEFINE_STATIC(restart_lock); static int ss_thread_count = 0; static int num_restart_pending = 0; static int restart_monitor(void); static int dahdi_sendtext(struct ast_channel *c, const char *text); static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { /* This module does not handle MWI in an event-based manner. However, it * subscribes to MWI for each mailbox that is configured so that the core * knows that we care about it. Then, chan_dahdi will get the MWI from the * event cache instead of checking the mailbox directly. */ } /*! \brief Avoid the silly dahdi_getevent which ignores a bunch of events */ static inline int dahdi_get_event(int fd) { int j; if (ioctl(fd, DAHDI_GETEVENT, &j) == -1) return -1; return j; } /*! \brief Avoid the silly dahdi_waitevent which ignores a bunch of events */ static inline int dahdi_wait_event(int fd) { int i, j = 0; i = DAHDI_IOMUX_SIGEVENT; if (ioctl(fd, DAHDI_IOMUX, &i) == -1) return -1; if (ioctl(fd, DAHDI_GETEVENT, &j) == -1) return -1; return j; } /*! Chunk size to read -- we use 20ms chunks to make things happy. */ #define READ_SIZE 160 #define MASK_AVAIL (1 << 0) /*!< Channel available for PRI use */ #define MASK_INUSE (1 << 1) /*!< Channel currently in use */ #define CALLWAITING_SILENT_SAMPLES ((300 * 8) / READ_SIZE) /*!< 300 ms */ #define CALLWAITING_REPEAT_SAMPLES ((10000 * 8) / READ_SIZE) /*!< 10,000 ms */ #define CALLWAITING_SUPPRESS_SAMPLES ((100 * 8) / READ_SIZE) /*!< 100 ms */ #define CIDCW_EXPIRE_SAMPLES ((500 * 8) / READ_SIZE) /*!< 500 ms */ #define MIN_MS_SINCE_FLASH ((2000) ) /*!< 2000 ms */ #define DEFAULT_RINGT ((8000 * 8) / READ_SIZE) /*!< 8,000 ms */ #define DEFAULT_DIALTONE_DETECT_TIMEOUT ((10000 * 8) / READ_SIZE) /*!< 10,000 ms */ /*! * \brief Configured ring timeout base. * \note Value computed from "ringtimeout" read in from chan_dahdi.conf if it exists. */ static int ringt_base = DEFAULT_RINGT; #if defined(HAVE_SS7) struct dahdi_ss7 { struct sig_ss7_linkset ss7; }; static struct dahdi_ss7 linksets[NUM_SPANS]; static int cur_ss7type = -1; static int cur_slc = -1; static int cur_linkset = -1; static int cur_pointcode = -1; static int cur_cicbeginswith = -1; static int cur_adjpointcode = -1; static int cur_networkindicator = -1; static int cur_defaultdpc = -1; #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 struct dahdi_mfcr2_conf { openr2_variant_t variant; int mfback_timeout; int metering_pulse_timeout; int max_ani; int max_dnis; #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 2 int dtmf_time_on; int dtmf_time_off; #endif #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 3 int dtmf_end_timeout; #endif signed int get_ani_first:2; #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 1 signed int skip_category_request:2; #endif unsigned int call_files:1; unsigned int allow_collect_calls:1; unsigned int charge_calls:1; unsigned int accept_on_offer:1; unsigned int forced_release:1; unsigned int double_answer:1; signed int immediate_accept:2; #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 2 signed int dtmf_dialing:2; signed int dtmf_detection:2; #endif char logdir[OR2_MAX_PATH]; char r2proto_file[OR2_MAX_PATH]; openr2_log_level_t loglevel; openr2_calling_party_category_t category; }; /* MFC-R2 pseudo-link structure */ struct dahdi_mfcr2 { pthread_t r2master; /*!< Thread of master */ openr2_context_t *protocol_context; /*!< OpenR2 context handle */ struct dahdi_pvt *pvts[SIG_MFCR2_MAX_CHANNELS]; /*!< Member channel pvt structs */ int numchans; /*!< Number of channels in this R2 block */ struct dahdi_mfcr2_conf conf; /*!< Configuration used to setup this pseudo-link */ }; /* malloc'd array of malloc'd r2links */ static struct dahdi_mfcr2 **r2links; /* how many r2links have been malloc'd */ static int r2links_count = 0; #endif /* HAVE_OPENR2 */ #ifdef HAVE_PRI struct dahdi_pri { int dchannels[SIG_PRI_NUM_DCHANS]; /*!< What channel are the dchannels on */ int mastertrunkgroup; /*!< What trunk group is our master */ int prilogicalspan; /*!< Logical span number within trunk group */ struct sig_pri_span pri; }; static struct dahdi_pri pris[NUM_SPANS]; #if defined(HAVE_PRI_CCSS) /*! DAHDI PRI CCSS agent and monitor type name. */ static const char dahdi_pri_cc_type[] = "DAHDI/PRI"; #endif /* defined(HAVE_PRI_CCSS) */ #else /*! Shut up the compiler */ struct dahdi_pri; #endif /* Polarity states */ #define POLARITY_IDLE 0 #define POLARITY_REV 1 const char * const subnames[] = { "Real", "Callwait", "Threeway" }; #define DATA_EXPORT_DAHDI_PVT(MEMBER) \ MEMBER(dahdi_pvt, cid_rxgain, AST_DATA_DOUBLE) \ MEMBER(dahdi_pvt, rxgain, AST_DATA_DOUBLE) \ MEMBER(dahdi_pvt, txgain, AST_DATA_DOUBLE) \ MEMBER(dahdi_pvt, txdrc, AST_DATA_DOUBLE) \ MEMBER(dahdi_pvt, rxdrc, AST_DATA_DOUBLE) \ MEMBER(dahdi_pvt, adsi, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, answeronpolarityswitch, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, busydetect, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, callreturn, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, callwaiting, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, callwaitingcallerid, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, cancallforward, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, canpark, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, confirmanswer, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, destroy, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, didtdd, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, dialednone, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, dialing, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, digital, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, dnd, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, echobreak, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, echocanbridged, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, echocanon, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, faxhandled, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, usefaxbuffers, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, bufferoverrideinuse, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, firstradio, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, hanguponpolarityswitch, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, hardwaredtmf, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, hidecallerid, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, hidecalleridname, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, ignoredtmf, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, immediate, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, inalarm, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, mate, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, outgoing, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, permcallwaiting, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, priindication_oob, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, priexclusive, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, pulse, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, pulsedial, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, restartpending, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, restrictcid, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, threewaycalling, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, transfer, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, use_callerid, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, use_callingpres, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, usedistinctiveringdetection, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, dahditrcallerid, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, transfertobusy, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, mwimonitor_neon, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, mwimonitor_fsk, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, mwimonitor_rpas, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, mwimonitoractive, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, mwisendactive, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, inservice, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, locallyblocked, AST_DATA_UNSIGNED_INTEGER) \ MEMBER(dahdi_pvt, remotelyblocked, AST_DATA_UNSIGNED_INTEGER) \ MEMBER(dahdi_pvt, manages_span_alarms, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, use_smdi, AST_DATA_BOOLEAN) \ MEMBER(dahdi_pvt, context, AST_DATA_STRING) \ MEMBER(dahdi_pvt, defcontext, AST_DATA_STRING) \ MEMBER(dahdi_pvt, description, AST_DATA_STRING) \ MEMBER(dahdi_pvt, exten, AST_DATA_STRING) \ MEMBER(dahdi_pvt, language, AST_DATA_STRING) \ MEMBER(dahdi_pvt, mohinterpret, AST_DATA_STRING) \ MEMBER(dahdi_pvt, mohsuggest, AST_DATA_STRING) \ MEMBER(dahdi_pvt, parkinglot, AST_DATA_STRING) AST_DATA_STRUCTURE(dahdi_pvt, DATA_EXPORT_DAHDI_PVT); static struct dahdi_pvt *iflist = NULL; /*!< Main interface list start */ static struct dahdi_pvt *ifend = NULL; /*!< Main interface list end */ #if defined(HAVE_PRI) struct doomed_pri { struct sig_pri_span *pri; AST_LIST_ENTRY(doomed_pri) list; }; static AST_LIST_HEAD_STATIC(doomed_pris, doomed_pri); static void pri_destroy_span(struct sig_pri_span *pri); static struct dahdi_parms_pseudo { int buf_no; /*!< Number of buffers */ int buf_policy; /*!< Buffer policy */ int faxbuf_no; /*!< Number of Fax buffers */ int faxbuf_policy; /*!< Fax buffer policy */ } dahdi_pseudo_parms; #endif /* defined(HAVE_PRI) */ /*! \brief Channel configuration from chan_dahdi.conf . * This struct is used for parsing the [channels] section of chan_dahdi.conf. * Generally there is a field here for every possible configuration item. * * The state of fields is saved along the parsing and whenever a 'channel' * statement is reached, the current dahdi_chan_conf is used to configure the * channel (struct dahdi_pvt) * * \see dahdi_chan_init for the default values. */ struct dahdi_chan_conf { struct dahdi_pvt chan; #ifdef HAVE_PRI struct dahdi_pri pri; #endif #if defined(HAVE_SS7) struct dahdi_ss7 ss7; #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 struct dahdi_mfcr2_conf mfcr2; #endif struct dahdi_params timing; int is_sig_auto; /*!< Use channel signalling from DAHDI? */ /*! Continue configuration even if a channel is not there. */ int ignore_failed_channels; /*! * \brief The serial port to listen for SMDI data on * \note Set from the "smdiport" string read in from chan_dahdi.conf */ char smdi_port[SMDI_MAX_FILENAME_LEN]; /*! * \brief Don't create channels below this number * \note by default is 0 (no limit) */ int wanted_channels_start; /*! * \brief Don't create channels above this number (infinity by default) * \note by default is 0 (special value that means "no limit"). */ int wanted_channels_end; }; /*! returns a new dahdi_chan_conf with default values (by-value) */ static struct dahdi_chan_conf dahdi_chan_conf_default(void) { /* recall that if a field is not included here it is initialized * to 0 or equivalent */ struct dahdi_chan_conf conf = { #ifdef HAVE_PRI .pri.pri = { .nsf = PRI_NSF_NONE, .switchtype = PRI_SWITCH_NI2, .dialplan = PRI_UNKNOWN + 1, .localdialplan = PRI_NATIONAL_ISDN + 1, .nodetype = PRI_CPE, .qsigchannelmapping = DAHDI_CHAN_MAPPING_PHYSICAL, #if defined(HAVE_PRI_CCSS) .cc_ptmp_recall_mode = 1,/* specificRecall */ .cc_qsig_signaling_link_req = 1,/* retain */ .cc_qsig_signaling_link_rsp = 1,/* retain */ #endif /* defined(HAVE_PRI_CCSS) */ .minunused = 2, .idleext = "", .idledial = "", .internationalprefix = "", .nationalprefix = "", .localprefix = "", .privateprefix = "", .unknownprefix = "", .colp_send = SIG_PRI_COLP_UPDATE, .resetinterval = -1, }, #endif #if defined(HAVE_SS7) .ss7.ss7 = { .called_nai = SS7_NAI_NATIONAL, .calling_nai = SS7_NAI_NATIONAL, .internationalprefix = "", .nationalprefix = "", .subscriberprefix = "", .unknownprefix = "", .networkroutedprefix = "" }, #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 .mfcr2 = { .variant = OR2_VAR_ITU, .mfback_timeout = -1, .metering_pulse_timeout = -1, .max_ani = 10, .max_dnis = 4, .get_ani_first = -1, #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 1 .skip_category_request = -1, #endif .call_files = 0, .allow_collect_calls = 0, .charge_calls = 1, .accept_on_offer = 1, .forced_release = 0, .double_answer = 0, .immediate_accept = -1, #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 2 .dtmf_dialing = -1, .dtmf_detection = -1, .dtmf_time_on = OR2_DEFAULT_DTMF_ON, .dtmf_time_off = OR2_DEFAULT_DTMF_OFF, #endif #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 3 .dtmf_end_timeout = -1, #endif .logdir = "", .r2proto_file = "", .loglevel = OR2_LOG_ERROR | OR2_LOG_WARNING, .category = OR2_CALLING_PARTY_CATEGORY_NATIONAL_SUBSCRIBER }, #endif .chan = { .context = "default", .cid_num = "", .cid_name = "", .cid_tag = "", .mohinterpret = "default", .mohsuggest = "", .parkinglot = "", .transfertobusy = 1, .cid_signalling = CID_SIG_BELL, .cid_start = CID_START_RING, .dahditrcallerid = 0, .use_callerid = 1, .sig = -1, .outsigmod = -1, .cid_rxgain = +5.0, .tonezone = -1, .echocancel.head.tap_length = 1, .busycount = 3, .accountcode = "", .mailbox = "", #ifdef HAVE_DAHDI_LINEREVERSE_VMWI .mwisend_fsk = 1, #endif .polarityonanswerdelay = 600, .sendcalleridafter = DEFAULT_CIDRINGS, .buf_policy = DAHDI_POLICY_IMMEDIATE, .buf_no = numbufs, .usefaxbuffers = 0, .cc_params = ast_cc_config_params_init(), }, .timing = { .prewinktime = -1, .preflashtime = -1, .winktime = -1, .flashtime = -1, .starttime = -1, .rxwinktime = -1, .rxflashtime = -1, .debouncetime = -1 }, .is_sig_auto = 1, .ignore_failed_channels = 1, .smdi_port = "/dev/ttyS0", }; return conf; } static struct ast_channel *dahdi_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int dahdi_digit_begin(struct ast_channel *ast, char digit); static int dahdi_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int dahdi_sendtext(struct ast_channel *c, const char *text); static int dahdi_call(struct ast_channel *ast, const char *rdest, int timeout); static int dahdi_hangup(struct ast_channel *ast); static int dahdi_answer(struct ast_channel *ast); static struct ast_frame *dahdi_read(struct ast_channel *ast); static int dahdi_write(struct ast_channel *ast, struct ast_frame *frame); static struct ast_frame *dahdi_exception(struct ast_channel *ast); static int dahdi_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen); static int dahdi_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int dahdi_setoption(struct ast_channel *chan, int option, void *data, int datalen); static int dahdi_queryoption(struct ast_channel *chan, int option, void *data, int *datalen); static int dahdi_func_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len); static int dahdi_func_write(struct ast_channel *chan, const char *function, char *data, const char *value); static int dahdi_devicestate(const char *data); static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback); static struct ast_channel_tech dahdi_tech = { .type = "DAHDI", .description = tdesc, .requester = dahdi_request, .send_digit_begin = dahdi_digit_begin, .send_digit_end = dahdi_digit_end, .send_text = dahdi_sendtext, .call = dahdi_call, .hangup = dahdi_hangup, .answer = dahdi_answer, .read = dahdi_read, .write = dahdi_write, .exception = dahdi_exception, .indicate = dahdi_indicate, .fixup = dahdi_fixup, .setoption = dahdi_setoption, .queryoption = dahdi_queryoption, .func_channel_read = dahdi_func_read, .func_channel_write = dahdi_func_write, .devicestate = dahdi_devicestate, .cc_callback = dahdi_cc_callback, }; #define GET_CHANNEL(p) ((p)->channel) static enum analog_sigtype dahdisig_to_analogsig(int sig) { switch (sig) { case SIG_FXOLS: return ANALOG_SIG_FXOLS; case SIG_FXOGS: return ANALOG_SIG_FXOGS; case SIG_FXOKS: return ANALOG_SIG_FXOKS; case SIG_FXSLS: return ANALOG_SIG_FXSLS; case SIG_FXSGS: return ANALOG_SIG_FXSGS; case SIG_FXSKS: return ANALOG_SIG_FXSKS; case SIG_EMWINK: return ANALOG_SIG_EMWINK; case SIG_EM: return ANALOG_SIG_EM; case SIG_EM_E1: return ANALOG_SIG_EM_E1; case SIG_FEATD: return ANALOG_SIG_FEATD; case SIG_FEATDMF: return ANALOG_SIG_FEATDMF; case SIG_E911: return SIG_E911; case SIG_FGC_CAMA: return ANALOG_SIG_FGC_CAMA; case SIG_FGC_CAMAMF: return ANALOG_SIG_FGC_CAMAMF; case SIG_FEATB: return ANALOG_SIG_FEATB; case SIG_SFWINK: return ANALOG_SIG_SFWINK; case SIG_SF: return ANALOG_SIG_SF; case SIG_SF_FEATD: return ANALOG_SIG_SF_FEATD; case SIG_SF_FEATDMF: return ANALOG_SIG_SF_FEATDMF; case SIG_FEATDMF_TA: return ANALOG_SIG_FEATDMF_TA; case SIG_SF_FEATB: return ANALOG_SIG_FEATB; default: return -1; } } static int analog_tone_to_dahditone(enum analog_tone tone) { switch (tone) { case ANALOG_TONE_RINGTONE: return DAHDI_TONE_RINGTONE; case ANALOG_TONE_STUTTER: return DAHDI_TONE_STUTTER; case ANALOG_TONE_CONGESTION: return DAHDI_TONE_CONGESTION; case ANALOG_TONE_DIALTONE: return DAHDI_TONE_DIALTONE; case ANALOG_TONE_DIALRECALL: return DAHDI_TONE_DIALRECALL; case ANALOG_TONE_INFO: return DAHDI_TONE_INFO; default: return -1; } } static int analogsub_to_dahdisub(enum analog_sub analogsub) { int index; switch (analogsub) { case ANALOG_SUB_REAL: index = SUB_REAL; break; case ANALOG_SUB_CALLWAIT: index = SUB_CALLWAIT; break; case ANALOG_SUB_THREEWAY: index = SUB_THREEWAY; break; default: ast_log(LOG_ERROR, "Unidentified sub!\n"); index = SUB_REAL; } return index; } /*! * \internal * \brief release all members on the doomed pris list * \since 13.0 * * Called priodically by the monitor threads to release spans marked for * removal. */ static void release_doomed_pris(void) { #ifdef HAVE_PRI struct doomed_pri *entry; AST_LIST_LOCK(&doomed_pris); while ((entry = AST_LIST_REMOVE_HEAD(&doomed_pris, list))) { /* The span destruction must be done with this lock not held */ AST_LIST_UNLOCK(&doomed_pris); ast_debug(4, "Destroying span %d from doomed queue.\n", entry->pri->span); pri_destroy_span(entry->pri); ast_free(entry); AST_LIST_LOCK(&doomed_pris); } AST_LIST_UNLOCK(&doomed_pris); #endif } #ifdef HAVE_PRI /*! * \brief Queue a span for destruction * \since 13.0 * * \param pri the span to destroy * * Add a span to the list of spans to be destroyed later on * by the monitor thread. Allows destroying a span while holding its * lock. */ static void pri_queue_for_destruction(struct sig_pri_span *pri) { struct doomed_pri *entry; AST_LIST_LOCK(&doomed_pris); AST_LIST_TRAVERSE(&doomed_pris, entry, list) { if (entry->pri == pri) { AST_LIST_UNLOCK(&doomed_pris); return; } } entry = ast_calloc(sizeof(struct doomed_pri), 1); if (!entry) { /* Nothing useful to do here. Panic? */ ast_log(LOG_WARNING, "Failed allocating memory for a doomed_pri.\n"); AST_LIST_UNLOCK(&doomed_pris); return; } entry->pri = pri; ast_debug(4, "Queue span %d for destruction.\n", pri->span); AST_LIST_INSERT_TAIL(&doomed_pris, entry, list); AST_LIST_UNLOCK(&doomed_pris); } #endif /*! * \internal * \brief Send a dial string to DAHDI. * \since 12.0.0 * * \param pvt DAHDI private pointer * \param operation DAHDI dial operation to do to string * \param dial_str Dial string to send * * \retval 0 on success. * \retval non-zero on error. */ static int dahdi_dial_str(struct dahdi_pvt *pvt, int operation, const char *dial_str) { int res; int offset; const char *pos; struct dahdi_dialoperation zo = { .op = operation, }; /* Convert the W's to ww. */ pos = dial_str; for (offset = 0; offset < sizeof(zo.dialstr) - 1; ++offset) { if (!*pos) { break; } if (*pos == 'W') { /* Convert 'W' to "ww" */ ++pos; if (offset >= sizeof(zo.dialstr) - 3) { /* No room to expand */ break; } zo.dialstr[offset] = 'w'; ++offset; zo.dialstr[offset] = 'w'; continue; } zo.dialstr[offset] = *pos++; } /* The zo initialization has already terminated the dialstr. */ ast_debug(1, "Channel %d: Dial str '%s' expanded to '%s' sent to DAHDI_DIAL.\n", pvt->channel, dial_str, zo.dialstr); res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_DIAL, &zo); if (res) { ast_log(LOG_WARNING, "Channel %d: Couldn't dial '%s': %s\n", pvt->channel, dial_str, strerror(errno)); } return res; } static enum analog_event dahdievent_to_analogevent(int event); static int bump_gains(struct dahdi_pvt *p); static int dahdi_setlinear(int dfd, int linear); static int my_start_cid_detect(void *pvt, int cid_signalling) { struct dahdi_pvt *p = pvt; int index = SUB_REAL; p->cs = callerid_new(cid_signalling); if (!p->cs) { ast_log(LOG_ERROR, "Unable to alloc callerid\n"); return -1; } bump_gains(p); dahdi_setlinear(p->subs[index].dfd, 0); return 0; } static int my_stop_cid_detect(void *pvt) { struct dahdi_pvt *p = pvt; int index = SUB_REAL; if (p->cs) callerid_free(p->cs); dahdi_setlinear(p->subs[index].dfd, p->subs[index].linear); return 0; } static int my_get_callerid(void *pvt, char *namebuf, char *numbuf, enum analog_event *ev, size_t timeout) { struct dahdi_pvt *p = pvt; struct analog_pvt *analog_p = p->sig_pvt; struct pollfd poller; char *name, *num; int index = SUB_REAL; int res; unsigned char buf[256]; int flags; poller.fd = p->subs[SUB_REAL].dfd; poller.events = POLLPRI | POLLIN; poller.revents = 0; res = poll(&poller, 1, timeout); if (poller.revents & POLLPRI) { *ev = dahdievent_to_analogevent(dahdi_get_event(p->subs[SUB_REAL].dfd)); return 1; } if (poller.revents & POLLIN) { /*** NOTES ***/ /* Change API: remove cid_signalling from get_callerid, add a new start_cid_detect and stop_cid_detect function * to enable slin mode and allocate cid detector. get_callerid should be able to be called any number of times until * either a timeout occurs or CID is detected (returns 0). returning 1 should be event received, and -1 should be * a failure and die, and returning 2 means no event was received. */ res = read(p->subs[index].dfd, buf, sizeof(buf)); if (res < 0) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); return -1; } if (analog_p->ringt > 0) { if (!(--analog_p->ringt)) { /* only return if we timeout from a ring event */ return -1; } } if (p->cid_signalling == CID_SIG_V23_JP) { res = callerid_feed_jp(p->cs, buf, res, AST_LAW(p)); } else { res = callerid_feed(p->cs, buf, res, AST_LAW(p)); } if (res < 0) { /* * The previous diagnostic message output likely * explains why it failed. */ ast_log(LOG_WARNING, "Failed to decode CallerID\n"); return -1; } if (res == 1) { callerid_get(p->cs, &name, &num, &flags); if (name) ast_copy_string(namebuf, name, ANALOG_MAX_CID); if (num) ast_copy_string(numbuf, num, ANALOG_MAX_CID); ast_debug(1, "CallerID number: %s, name: %s, flags=%d\n", num, name, flags); return 0; } } *ev = ANALOG_EVENT_NONE; return 2; } static const char *event2str(int event); static int restore_gains(struct dahdi_pvt *p); static int my_distinctive_ring(struct ast_channel *chan, void *pvt, int idx, int *ringdata) { unsigned char buf[256]; int distMatches; int curRingData[RING_PATTERNS]; int receivedRingT; int counter1; int counter; int i; int res; int checkaftercid = 0; struct dahdi_pvt *p = pvt; struct analog_pvt *analog_p = p->sig_pvt; if (ringdata == NULL) { ringdata = curRingData; } else { checkaftercid = 1; } /* We must have a ring by now, so, if configured, lets try to listen for * distinctive ringing */ if ((checkaftercid && distinctiveringaftercid) || !checkaftercid) { /* Clear the current ring data array so we don't have old data in it. */ for (receivedRingT = 0; receivedRingT < RING_PATTERNS; receivedRingT++) ringdata[receivedRingT] = 0; receivedRingT = 0; if (checkaftercid && distinctiveringaftercid) ast_verb(3, "Detecting post-CID distinctive ring\n"); /* Check to see if context is what it should be, if not set to be. */ else if (strcmp(p->context,p->defcontext) != 0) { ast_copy_string(p->context, p->defcontext, sizeof(p->context)); ast_channel_context_set(chan, p->defcontext); } for (;;) { i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT; if ((res = ioctl(p->subs[idx].dfd, DAHDI_IOMUX, &i))) { ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno)); ast_hangup(chan); return 1; } if (i & DAHDI_IOMUX_SIGEVENT) { res = dahdi_get_event(p->subs[idx].dfd); if (res == DAHDI_EVENT_NOALARM) { p->inalarm = 0; analog_p->inalarm = 0; } ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res)); res = 0; /* Let us detect distinctive ring */ ringdata[receivedRingT] = analog_p->ringt; if (analog_p->ringt < analog_p->ringt_base/2) break; /* Increment the ringT counter so we can match it against values in chan_dahdi.conf for distinctive ring */ if (++receivedRingT == RING_PATTERNS) break; } else if (i & DAHDI_IOMUX_READ) { res = read(p->subs[idx].dfd, buf, sizeof(buf)); if (res < 0) { if (errno != ELAST) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); ast_hangup(chan); return 1; } break; } if (analog_p->ringt > 0) { if (!(--analog_p->ringt)) { res = -1; break; } } } } } if ((checkaftercid && usedistinctiveringdetection) || !checkaftercid) { /* this only shows up if you have n of the dring patterns filled in */ ast_verb(3, "Detected ring pattern: %d,%d,%d\n",ringdata[0],ringdata[1],ringdata[2]); for (counter = 0; counter < 3; counter++) { /* Check to see if the rings we received match any of the ones in chan_dahdi.conf for this channel */ distMatches = 0; /* this only shows up if you have n of the dring patterns filled in */ ast_verb(3, "Checking %d,%d,%d\n", p->drings.ringnum[counter].ring[0], p->drings.ringnum[counter].ring[1], p->drings.ringnum[counter].ring[2]); for (counter1 = 0; counter1 < 3; counter1++) { ast_verb(3, "Ring pattern check range: %d\n", p->drings.ringnum[counter].range); if (p->drings.ringnum[counter].ring[counter1] == -1) { ast_verb(3, "Pattern ignore (-1) detected, so matching pattern %d regardless.\n", ringdata[counter1]); distMatches++; } else if (ringdata[counter1] <= (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range) && ringdata[counter1] >= (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range)) { ast_verb(3, "Ring pattern matched in range: %d to %d\n", (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range), (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range)); distMatches++; } } if (distMatches == 3) { /* The ring matches, set the context to whatever is for distinctive ring.. */ ast_copy_string(p->context, S_OR(p->drings.ringContext[counter].contextData, p->defcontext), sizeof(p->context)); ast_channel_context_set(chan, S_OR(p->drings.ringContext[counter].contextData, p->defcontext)); ast_verb(3, "Distinctive Ring matched context %s\n",p->context); break; } } } /* Restore linear mode (if appropriate) for Caller*ID processing */ dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); restore_gains(p); return 0; } static int my_stop_callwait(void *pvt) { struct dahdi_pvt *p = pvt; p->callwaitingrepeat = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; return 0; } static int send_callerid(struct dahdi_pvt *p); static int save_conference(struct dahdi_pvt *p); static int restore_conference(struct dahdi_pvt *p); static int my_callwait(void *pvt) { struct dahdi_pvt *p = pvt; p->callwaitingrepeat = CALLWAITING_REPEAT_SAMPLES; if (p->cidspill) { ast_log(LOG_WARNING, "Spill already exists?!?\n"); ast_free(p->cidspill); } /* * SAS: Subscriber Alert Signal, 440Hz for 300ms * CAS: CPE Alert Signal, 2130Hz * 2750Hz sine waves */ if (!(p->cidspill = ast_malloc(2400 /* SAS */ + 680 /* CAS */ + READ_SIZE * 4))) return -1; save_conference(p); /* Silence */ memset(p->cidspill, 0x7f, 2400 + 600 + READ_SIZE * 4); if (!p->callwaitrings && p->callwaitingcallerid) { ast_gen_cas(p->cidspill, 1, 2400 + 680, AST_LAW(p)); p->callwaitcas = 1; p->cidlen = 2400 + 680 + READ_SIZE * 4; } else { ast_gen_cas(p->cidspill, 1, 2400, AST_LAW(p)); p->callwaitcas = 0; p->cidlen = 2400 + READ_SIZE * 4; } p->cidpos = 0; send_callerid(p); return 0; } static int my_send_callerid(void *pvt, int cwcid, struct ast_party_caller *caller) { struct dahdi_pvt *p = pvt; ast_debug(2, "Starting cid spill\n"); if (p->cidspill) { ast_log(LOG_WARNING, "cidspill already exists??\n"); ast_free(p->cidspill); } if ((p->cidspill = ast_malloc(MAX_CALLERID_SIZE))) { if (cwcid == 0) { p->cidlen = ast_callerid_generate(p->cidspill, caller->id.name.str, caller->id.number.str, AST_LAW(p)); } else { ast_verb(3, "CPE supports Call Waiting Caller*ID. Sending '%s/%s'\n", caller->id.name.str, caller->id.number.str); p->callwaitcas = 0; p->cidcwexpire = 0; p->cidlen = ast_callerid_callwaiting_generate(p->cidspill, caller->id.name.str, caller->id.number.str, AST_LAW(p)); p->cidlen += READ_SIZE * 4; } p->cidpos = 0; p->cid_suppress_expire = 0; send_callerid(p); } return 0; } static int my_dsp_reset_and_flush_digits(void *pvt) { struct dahdi_pvt *p = pvt; if (p->dsp) ast_dsp_digitreset(p->dsp); return 0; } static int my_dsp_set_digitmode(void *pvt, enum analog_dsp_digitmode mode) { struct dahdi_pvt *p = pvt; if (p->channel == CHAN_PSEUDO) ast_log(LOG_ERROR, "You have assumed incorrectly sir!\n"); if (mode == ANALOG_DIGITMODE_DTMF) { /* If we do hardware dtmf, no need for a DSP */ if (p->hardwaredtmf) { if (p->dsp) { ast_dsp_free(p->dsp); p->dsp = NULL; } return 0; } if (!p->dsp) { p->dsp = ast_dsp_new(); if (!p->dsp) { ast_log(LOG_ERROR, "Unable to allocate DSP\n"); return -1; } } ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax); } else if (mode == ANALOG_DIGITMODE_MF) { if (!p->dsp) { p->dsp = ast_dsp_new(); if (!p->dsp) { ast_log(LOG_ERROR, "Unable to allocate DSP\n"); return -1; } } ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_MF | p->dtmfrelax); } return 0; } static int dahdi_wink(struct dahdi_pvt *p, int index); static int my_wink(void *pvt, enum analog_sub sub) { struct dahdi_pvt *p = pvt; int index = analogsub_to_dahdisub(sub); if (index != SUB_REAL) { ast_log(LOG_ERROR, "We used a sub other than SUB_REAL (incorrect assumption sir)\n"); } return dahdi_wink(p, index); } static void wakeup_sub(struct dahdi_pvt *p, int a); static int reset_conf(struct dahdi_pvt *p); static inline int dahdi_confmute(struct dahdi_pvt *p, int muted); static void my_handle_dtmf(void *pvt, struct ast_channel *ast, enum analog_sub analog_index, struct ast_frame **dest) { struct ast_frame *f = *dest; struct dahdi_pvt *p = pvt; int idx = analogsub_to_dahdisub(analog_index); ast_debug(1, "%s DTMF digit: 0x%02X '%c' on %s\n", f->frametype == AST_FRAME_DTMF_BEGIN ? "Begin" : "End", (unsigned)f->subclass.integer, f->subclass.integer, ast_channel_name(ast)); if (f->subclass.integer == 'f') { if (f->frametype == AST_FRAME_DTMF_END) { /* Fax tone -- Handle and return NULL */ if ((p->callprogress & CALLPROGRESS_FAX) && !p->faxhandled) { /* If faxbuffers are configured, use them for the fax transmission */ if (p->usefaxbuffers && !p->bufferoverrideinuse) { struct dahdi_bufferinfo bi = { .txbufpolicy = p->faxbuf_policy, .bufsize = p->bufsize, .numbufs = p->faxbuf_no }; int res; if ((res = ioctl(p->subs[idx].dfd, DAHDI_SET_BUFINFO, &bi)) < 0) { ast_log(LOG_WARNING, "Channel '%s' unable to set buffer policy, reason: %s\n", ast_channel_name(ast), strerror(errno)); } else { p->bufferoverrideinuse = 1; } } p->faxhandled = 1; if (p->dsp) { p->dsp_features &= ~DSP_FEATURE_FAX_DETECT; ast_dsp_set_features(p->dsp, p->dsp_features); ast_debug(1, "Disabling FAX tone detection on %s after tone received\n", ast_channel_name(ast)); } if (strcmp(ast_channel_exten(ast), "fax")) { const char *target_context = S_OR(ast_channel_macrocontext(ast), ast_channel_context(ast)); /* We need to unlock 'ast' here because ast_exists_extension has the * potential to start autoservice on the channel. Such action is prone * to deadlock. */ ast_mutex_unlock(&p->lock); ast_channel_unlock(ast); if (ast_exists_extension(ast, target_context, "fax", 1, S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, NULL))) { ast_channel_lock(ast); ast_mutex_lock(&p->lock); ast_verb(3, "Redirecting %s to fax extension\n", ast_channel_name(ast)); /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */ pbx_builtin_setvar_helper(ast, "FAXEXTEN", ast_channel_exten(ast)); if (ast_async_goto(ast, target_context, "fax", 1)) ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(ast), target_context); } else { ast_channel_lock(ast); ast_mutex_lock(&p->lock); ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n"); } } else { ast_debug(1, "Already in a fax extension, not redirecting\n"); } } else { ast_debug(1, "Fax already handled\n"); } dahdi_confmute(p, 0); } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; *dest = &p->subs[idx].f; } } static void my_lock_private(void *pvt) { struct dahdi_pvt *p = pvt; ast_mutex_lock(&p->lock); } static void my_unlock_private(void *pvt) { struct dahdi_pvt *p = pvt; ast_mutex_unlock(&p->lock); } static void my_deadlock_avoidance_private(void *pvt) { struct dahdi_pvt *p = pvt; DEADLOCK_AVOIDANCE(&p->lock); } static struct ast_manager_event_blob *dahdichannel_to_ami(struct stasis_message *msg) { RAII_VAR(struct ast_str *, channel_string, NULL, ast_free); struct ast_channel_blob *obj = stasis_message_data(msg); struct ast_json *span, *channel; channel_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_string) { return NULL; } span = ast_json_object_get(obj->blob, "span"); channel = ast_json_object_get(obj->blob, "channel"); return ast_manager_event_blob_create(EVENT_FLAG_CALL, "DAHDIChannel", "%s" "DAHDISpan: %u\r\n" "DAHDIChannel: %s\r\n", ast_str_buffer(channel_string), (unsigned int)ast_json_integer_get(span), ast_json_string_get(channel)); } STASIS_MESSAGE_TYPE_DEFN_LOCAL(dahdichannel_type, .to_ami = dahdichannel_to_ami, ); /*! \brief Sends a DAHDIChannel channel blob used to produce DAHDIChannel AMI messages */ static void publish_dahdichannel(struct ast_channel *chan, int span, const char *dahdi_channel) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ast_assert(dahdi_channel != NULL); blob = ast_json_pack("{s: i, s: s}", "span", span, "channel", dahdi_channel); if (!blob) { return; } ast_channel_lock(chan); ast_channel_publish_blob(chan, dahdichannel_type(), blob); ast_channel_unlock(chan); } /*! * \internal * \brief Post an AMI DAHDI channel association event. * \since 1.8 * * \param p DAHDI private pointer * \param chan Channel associated with the private pointer * * \return Nothing */ static void dahdi_ami_channel_event(struct dahdi_pvt *p, struct ast_channel *chan) { char ch_name[20]; if (p->channel < CHAN_PSEUDO) { /* No B channel */ snprintf(ch_name, sizeof(ch_name), "no-media (%d)", p->channel); } else if (p->channel == CHAN_PSEUDO) { /* Pseudo channel */ strcpy(ch_name, "pseudo"); } else { /* Real channel */ snprintf(ch_name, sizeof(ch_name), "%d", p->channel); } publish_dahdichannel(chan, p->span, ch_name); } #ifdef HAVE_PRI /*! * \internal * \brief Post an AMI DAHDI channel association event. * \since 1.8 * * \param pvt DAHDI private pointer * \param chan Channel associated with the private pointer * * \return Nothing */ static void my_ami_channel_event(void *pvt, struct ast_channel *chan) { struct dahdi_pvt *p = pvt; dahdi_ami_channel_event(p, chan); } #endif /* linear_mode = 0 - turn linear mode off, >0 - turn linear mode on * returns the last value of the linear setting */ static int my_set_linear_mode(void *pvt, enum analog_sub sub, int linear_mode) { struct dahdi_pvt *p = pvt; int oldval; int idx = analogsub_to_dahdisub(sub); dahdi_setlinear(p->subs[idx].dfd, linear_mode); oldval = p->subs[idx].linear; p->subs[idx].linear = linear_mode ? 1 : 0; return oldval; } static void my_set_inthreeway(void *pvt, enum analog_sub sub, int inthreeway) { struct dahdi_pvt *p = pvt; int idx = analogsub_to_dahdisub(sub); p->subs[idx].inthreeway = inthreeway; } static int get_alarms(struct dahdi_pvt *p); static void handle_alarms(struct dahdi_pvt *p, int alms); static void my_get_and_handle_alarms(void *pvt) { int res; struct dahdi_pvt *p = pvt; res = get_alarms(p); handle_alarms(p, res); } static void *my_get_sigpvt_bridged_channel(struct ast_channel *chan) { RAII_VAR(struct ast_channel *, bridged, ast_channel_bridge_peer(chan), ast_channel_cleanup); if (bridged && ast_channel_tech(bridged) == &dahdi_tech) { struct dahdi_pvt *p = ast_channel_tech_pvt(bridged); if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { return p->sig_pvt; } } return NULL; } static int my_get_sub_fd(void *pvt, enum analog_sub sub) { struct dahdi_pvt *p = pvt; int dahdi_sub = analogsub_to_dahdisub(sub); return p->subs[dahdi_sub].dfd; } static void my_set_cadence(void *pvt, int *cid_rings, struct ast_channel *ast) { struct dahdi_pvt *p = pvt; /* Choose proper cadence */ if ((p->distinctivering > 0) && (p->distinctivering <= num_cadence)) { if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCADENCE, &cadences[p->distinctivering - 1])) ast_log(LOG_WARNING, "Unable to set distinctive ring cadence %d on '%s': %s\n", p->distinctivering, ast_channel_name(ast), strerror(errno)); *cid_rings = cidrings[p->distinctivering - 1]; } else { if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCADENCE, NULL)) ast_log(LOG_WARNING, "Unable to reset default ring on '%s': %s\n", ast_channel_name(ast), strerror(errno)); *cid_rings = p->sendcalleridafter; } } static void my_set_alarm(void *pvt, int in_alarm) { struct dahdi_pvt *p = pvt; p->inalarm = in_alarm; } static void my_set_dialing(void *pvt, int is_dialing) { struct dahdi_pvt *p = pvt; p->dialing = is_dialing; } static void my_set_outgoing(void *pvt, int is_outgoing) { struct dahdi_pvt *p = pvt; p->outgoing = is_outgoing; } #if defined(HAVE_PRI) || defined(HAVE_SS7) static void my_set_digital(void *pvt, int is_digital) { struct dahdi_pvt *p = pvt; p->digital = is_digital; } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #if defined(HAVE_SS7) static void my_set_inservice(void *pvt, int is_inservice) { struct dahdi_pvt *p = pvt; p->inservice = is_inservice; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static void my_set_locallyblocked(void *pvt, int is_blocked) { struct dahdi_pvt *p = pvt; p->locallyblocked = is_blocked; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static void my_set_remotelyblocked(void *pvt, int is_blocked) { struct dahdi_pvt *p = pvt; p->remotelyblocked = is_blocked; } #endif /* defined(HAVE_SS7) */ static void my_set_ringtimeout(void *pvt, int ringt) { struct dahdi_pvt *p = pvt; p->ringt = ringt; } static void my_set_waitingfordt(void *pvt, struct ast_channel *ast) { struct dahdi_pvt *p = pvt; if (p->waitfordialtone && CANPROGRESSDETECT(p) && p->dsp) { ast_debug(1, "Defer dialing for %dms or dialtone\n", p->waitfordialtone); gettimeofday(&p->waitingfordt, NULL); ast_setstate(ast, AST_STATE_OFFHOOK); } } static int my_check_waitingfordt(void *pvt) { struct dahdi_pvt *p = pvt; if (p->waitingfordt.tv_sec) { return 1; } return 0; } static void my_set_confirmanswer(void *pvt, int flag) { struct dahdi_pvt *p = pvt; p->confirmanswer = flag; } static int my_check_confirmanswer(void *pvt) { struct dahdi_pvt *p = pvt; if (p->confirmanswer) { return 1; } return 0; } static void my_set_callwaiting(void *pvt, int callwaiting_enable) { struct dahdi_pvt *p = pvt; p->callwaiting = callwaiting_enable; } static void my_cancel_cidspill(void *pvt) { struct dahdi_pvt *p = pvt; ast_free(p->cidspill); p->cidspill = NULL; restore_conference(p); } static int my_confmute(void *pvt, int mute) { struct dahdi_pvt *p = pvt; return dahdi_confmute(p, mute); } static void my_set_pulsedial(void *pvt, int flag) { struct dahdi_pvt *p = pvt; p->pulsedial = flag; } static void my_set_new_owner(void *pvt, struct ast_channel *new_owner) { struct dahdi_pvt *p = pvt; p->owner = new_owner; } static const char *my_get_orig_dialstring(void *pvt) { struct dahdi_pvt *p = pvt; return p->dialstring; } static void my_increase_ss_count(void) { ast_mutex_lock(&ss_thread_lock); ss_thread_count++; ast_mutex_unlock(&ss_thread_lock); } static void my_decrease_ss_count(void) { ast_mutex_lock(&ss_thread_lock); ss_thread_count--; ast_cond_signal(&ss_thread_complete); ast_mutex_unlock(&ss_thread_lock); } static void my_all_subchannels_hungup(void *pvt) { struct dahdi_pvt *p = pvt; int res, law; p->faxhandled = 0; p->didtdd = 0; if (p->dsp) { ast_dsp_free(p->dsp); p->dsp = NULL; } p->law = p->law_default; law = p->law_default; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETLAW, &law); if (res < 0) ast_log(LOG_WARNING, "Unable to set law on channel %d to default: %s\n", p->channel, strerror(errno)); dahdi_setlinear(p->subs[SUB_REAL].dfd, 0); #if 1 { int i; p->owner = NULL; /* Cleanup owners here */ for (i = 0; i < 3; i++) { p->subs[i].owner = NULL; } } #endif reset_conf(p); if (num_restart_pending == 0) { restart_monitor(); } } static int conf_del(struct dahdi_pvt *p, struct dahdi_subchannel *c, int index); static int my_conf_del(void *pvt, enum analog_sub sub) { struct dahdi_pvt *p = pvt; int x = analogsub_to_dahdisub(sub); return conf_del(p, &p->subs[x], x); } static int conf_add(struct dahdi_pvt *p, struct dahdi_subchannel *c, int index, int slavechannel); static int my_conf_add(void *pvt, enum analog_sub sub) { struct dahdi_pvt *p = pvt; int x = analogsub_to_dahdisub(sub); return conf_add(p, &p->subs[x], x, 0); } static int isslavenative(struct dahdi_pvt *p, struct dahdi_pvt **out); static int my_complete_conference_update(void *pvt, int needconference) { struct dahdi_pvt *p = pvt; int needconf = needconference; int x; int useslavenative; struct dahdi_pvt *slave = NULL; useslavenative = isslavenative(p, &slave); /* If we have a slave, add him to our conference now. or DAX if this is slave native */ for (x = 0; x < MAX_SLAVES; x++) { if (p->slaves[x]) { if (useslavenative) conf_add(p, &p->slaves[x]->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(p)); else { conf_add(p, &p->slaves[x]->subs[SUB_REAL], SUB_REAL, 0); needconf++; } } } /* If we're supposed to be in there, do so now */ if (p->inconference && !p->subs[SUB_REAL].inthreeway) { if (useslavenative) conf_add(p, &p->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(slave)); else { conf_add(p, &p->subs[SUB_REAL], SUB_REAL, 0); needconf++; } } /* If we have a master, add ourselves to his conference */ if (p->master) { if (isslavenative(p->master, NULL)) { conf_add(p->master, &p->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(p->master)); } else { conf_add(p->master, &p->subs[SUB_REAL], SUB_REAL, 0); } } if (!needconf) { /* Nobody is left (or should be left) in our conference. Kill it. */ p->confno = -1; } return 0; } static int check_for_conference(struct dahdi_pvt *p); static int my_check_for_conference(void *pvt) { struct dahdi_pvt *p = pvt; return check_for_conference(p); } static void my_swap_subchannels(void *pvt, enum analog_sub a, struct ast_channel *ast_a, enum analog_sub b, struct ast_channel *ast_b) { struct dahdi_pvt *p = pvt; int da, db; int tchan; int tinthreeway; da = analogsub_to_dahdisub(a); db = analogsub_to_dahdisub(b); tchan = p->subs[da].chan; p->subs[da].chan = p->subs[db].chan; p->subs[db].chan = tchan; tinthreeway = p->subs[da].inthreeway; p->subs[da].inthreeway = p->subs[db].inthreeway; p->subs[db].inthreeway = tinthreeway; p->subs[da].owner = ast_a; p->subs[db].owner = ast_b; if (ast_a) ast_channel_set_fd(ast_a, 0, p->subs[da].dfd); if (ast_b) ast_channel_set_fd(ast_b, 0, p->subs[db].dfd); wakeup_sub(p, a); wakeup_sub(p, b); return; } /*! * \internal * \brief performs duties of dahdi_new, but also removes and possibly unbinds (if callid_created is 1) before returning * \note this variant of dahdi should only be used in conjunction with ast_callid_threadstorage_auto() * * \param callid_created value returned from ast_callid_threadstorage_auto() */ static struct ast_channel *dahdi_new_callid_clean(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, struct ast_callid *callid, int callid_created); static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, struct ast_callid *callid); static struct ast_channel *my_new_analog_ast_channel(void *pvt, int state, int startpbx, enum analog_sub sub, const struct ast_channel *requestor) { struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); struct dahdi_pvt *p = pvt; int dsub = analogsub_to_dahdisub(sub); return dahdi_new_callid_clean(p, state, startpbx, dsub, 0, NULL, requestor, callid, callid_created); } #if defined(HAVE_PRI) || defined(HAVE_SS7) static int dahdi_setlaw(int dfd, int law) { int res; res = ioctl(dfd, DAHDI_SETLAW, &law); if (res) return res; return 0; } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #if defined(HAVE_PRI) static struct ast_channel *my_new_pri_ast_channel(void *pvt, int state, enum sig_pri_law law, char *exten, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct dahdi_pvt *p = pvt; int audio; int newlaw = -1; struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); switch (p->sig) { case SIG_PRI_LIB_HANDLE_CASES: if (((struct sig_pri_chan *) p->sig_pvt)->no_b_channel) { /* PRI nobch pseudo channel. Does not handle ioctl(DAHDI_AUDIOMODE) */ break; } /* Fall through */ default: /* Set to audio mode at this point */ audio = 1; if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &audio) == -1) { ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d: %s\n", p->channel, audio, strerror(errno)); } break; } if (law != SIG_PRI_DEFLAW) { dahdi_setlaw(p->subs[SUB_REAL].dfd, (law == SIG_PRI_ULAW) ? DAHDI_LAW_MULAW : DAHDI_LAW_ALAW); } ast_copy_string(p->exten, exten, sizeof(p->exten)); switch (law) { case SIG_PRI_DEFLAW: newlaw = 0; break; case SIG_PRI_ALAW: newlaw = DAHDI_LAW_ALAW; break; case SIG_PRI_ULAW: newlaw = DAHDI_LAW_MULAW; break; } return dahdi_new_callid_clean(p, state, 0, SUB_REAL, newlaw, assignedids, requestor, callid, callid_created); } #endif /* defined(HAVE_PRI) */ static int set_actual_gain(int fd, float rxgain, float txgain, float rxdrc, float txdrc, int law); #if defined(HAVE_PRI) || defined(HAVE_SS7) /*! * \internal * \brief Open the PRI/SS7 channel media path. * \since 1.8 * * \param p Channel private control structure. * * \return Nothing */ static void my_pri_ss7_open_media(void *p) { struct dahdi_pvt *pvt = p; int res; int dfd; int set_val; dfd = pvt->subs[SUB_REAL].dfd; /* Open the media path. */ set_val = 1; res = ioctl(dfd, DAHDI_AUDIOMODE, &set_val); if (res < 0) { ast_log(LOG_WARNING, "Unable to enable audio mode on channel %d (%s)\n", pvt->channel, strerror(errno)); } /* Set correct companding law for this call. */ res = dahdi_setlaw(dfd, pvt->law); if (res < 0) { ast_log(LOG_WARNING, "Unable to set law on channel %d\n", pvt->channel); } /* Set correct gain for this call. */ if (pvt->digital) { res = set_actual_gain(dfd, 0, 0, pvt->rxdrc, pvt->txdrc, pvt->law); } else { res = set_actual_gain(dfd, pvt->rxgain, pvt->txgain, pvt->rxdrc, pvt->txdrc, pvt->law); } if (res < 0) { ast_log(LOG_WARNING, "Unable to set gains on channel %d\n", pvt->channel); } if (pvt->dsp_features && pvt->dsp) { ast_dsp_set_features(pvt->dsp, pvt->dsp_features); pvt->dsp_features = 0; } } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #if defined(HAVE_PRI) /*! * \internal * \brief Ask DAHDI to dial the given dial string. * \since 1.8.11 * * \param p Channel private control structure. * \param dial_string String to pass to DAHDI to dial. * * \note The channel private lock needs to be held when calling. * * \return Nothing */ static void my_pri_dial_digits(void *p, const char *dial_string) { char dial_str[DAHDI_MAX_DTMF_BUF]; struct dahdi_pvt *pvt = p; int res; snprintf(dial_str, sizeof(dial_str), "T%s", dial_string); res = dahdi_dial_str(pvt, DAHDI_DIAL_OP_APPEND, dial_str); if (!res) { pvt->dialing = 1; } } #endif /* defined(HAVE_PRI) */ static int unalloc_sub(struct dahdi_pvt *p, int x); static int my_unallocate_sub(void *pvt, enum analog_sub analogsub) { struct dahdi_pvt *p = pvt; return unalloc_sub(p, analogsub_to_dahdisub(analogsub)); } static int alloc_sub(struct dahdi_pvt *p, int x); static int my_allocate_sub(void *pvt, enum analog_sub analogsub) { struct dahdi_pvt *p = pvt; return alloc_sub(p, analogsub_to_dahdisub(analogsub)); } static int has_voicemail(struct dahdi_pvt *p); static int my_has_voicemail(void *pvt) { struct dahdi_pvt *p = pvt; return has_voicemail(p); } static int my_play_tone(void *pvt, enum analog_sub sub, enum analog_tone tone) { struct dahdi_pvt *p = pvt; int index; index = analogsub_to_dahdisub(sub); return tone_zone_play_tone(p->subs[index].dfd, analog_tone_to_dahditone(tone)); } static enum analog_event dahdievent_to_analogevent(int event) { enum analog_event res; switch (event) { case DAHDI_EVENT_ONHOOK: res = ANALOG_EVENT_ONHOOK; break; case DAHDI_EVENT_RINGOFFHOOK: res = ANALOG_EVENT_RINGOFFHOOK; break; case DAHDI_EVENT_WINKFLASH: res = ANALOG_EVENT_WINKFLASH; break; case DAHDI_EVENT_ALARM: res = ANALOG_EVENT_ALARM; break; case DAHDI_EVENT_NOALARM: res = ANALOG_EVENT_NOALARM; break; case DAHDI_EVENT_DIALCOMPLETE: res = ANALOG_EVENT_DIALCOMPLETE; break; case DAHDI_EVENT_RINGERON: res = ANALOG_EVENT_RINGERON; break; case DAHDI_EVENT_RINGEROFF: res = ANALOG_EVENT_RINGEROFF; break; case DAHDI_EVENT_HOOKCOMPLETE: res = ANALOG_EVENT_HOOKCOMPLETE; break; case DAHDI_EVENT_PULSE_START: res = ANALOG_EVENT_PULSE_START; break; case DAHDI_EVENT_POLARITY: res = ANALOG_EVENT_POLARITY; break; case DAHDI_EVENT_RINGBEGIN: res = ANALOG_EVENT_RINGBEGIN; break; case DAHDI_EVENT_EC_DISABLED: res = ANALOG_EVENT_EC_DISABLED; break; case DAHDI_EVENT_REMOVED: res = ANALOG_EVENT_REMOVED; break; case DAHDI_EVENT_NEONMWI_ACTIVE: res = ANALOG_EVENT_NEONMWI_ACTIVE; break; case DAHDI_EVENT_NEONMWI_INACTIVE: res = ANALOG_EVENT_NEONMWI_INACTIVE; break; #ifdef HAVE_DAHDI_ECHOCANCEL_FAX_MODE case DAHDI_EVENT_TX_CED_DETECTED: res = ANALOG_EVENT_TX_CED_DETECTED; break; case DAHDI_EVENT_RX_CED_DETECTED: res = ANALOG_EVENT_RX_CED_DETECTED; break; case DAHDI_EVENT_EC_NLP_DISABLED: res = ANALOG_EVENT_EC_NLP_DISABLED; break; case DAHDI_EVENT_EC_NLP_ENABLED: res = ANALOG_EVENT_EC_NLP_ENABLED; break; #endif case DAHDI_EVENT_PULSEDIGIT: res = ANALOG_EVENT_PULSEDIGIT; break; case DAHDI_EVENT_DTMFDOWN: res = ANALOG_EVENT_DTMFDOWN; break; case DAHDI_EVENT_DTMFUP: res = ANALOG_EVENT_DTMFUP; break; default: switch(event & 0xFFFF0000) { case DAHDI_EVENT_PULSEDIGIT: case DAHDI_EVENT_DTMFDOWN: case DAHDI_EVENT_DTMFUP: /* The event includes a digit number in the low word. * Converting it to a 'enum analog_event' would remove * that information. Thus it is returned as-is. */ return event; } res = ANALOG_EVENT_ERROR; break; } return res; } static inline int dahdi_wait_event(int fd); static int my_wait_event(void *pvt) { struct dahdi_pvt *p = pvt; return dahdi_wait_event(p->subs[SUB_REAL].dfd); } static int my_get_event(void *pvt) { struct dahdi_pvt *p = pvt; int res; if (p->fake_event) { res = p->fake_event; p->fake_event = 0; } else res = dahdi_get_event(p->subs[SUB_REAL].dfd); return dahdievent_to_analogevent(res); } static int my_is_off_hook(void *pvt) { struct dahdi_pvt *p = pvt; int res; struct dahdi_params par; memset(&par, 0, sizeof(par)); if (p->subs[SUB_REAL].dfd > -1) res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &par); else { /* Assume not off hook on CVRS */ res = 0; par.rxisoffhook = 0; } if (res) { ast_log(LOG_WARNING, "Unable to check hook state on channel %d: %s\n", p->channel, strerror(errno)); } if ((p->sig == SIG_FXSKS) || (p->sig == SIG_FXSGS)) { /* When "onhook" that means no battery on the line, and thus it is out of service..., if it's on a TDM card... If it's a channel bank, there is no telling... */ return (par.rxbits > -1) || par.rxisoffhook; } return par.rxisoffhook; } static int my_set_echocanceller(void *pvt, int enable) { struct dahdi_pvt *p = pvt; if (enable) dahdi_ec_enable(p); else dahdi_ec_disable(p); return 0; } static int dahdi_ring_phone(struct dahdi_pvt *p); static int my_ring(void *pvt) { struct dahdi_pvt *p = pvt; return dahdi_ring_phone(p); } static int my_flash(void *pvt) { struct dahdi_pvt *p = pvt; int func = DAHDI_FLASH; return ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &func); } static inline int dahdi_set_hook(int fd, int hs); static int my_off_hook(void *pvt) { struct dahdi_pvt *p = pvt; return dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); } static void my_set_needringing(void *pvt, int value) { struct dahdi_pvt *p = pvt; p->subs[SUB_REAL].needringing = value; } static void my_set_polarity(void *pvt, int value) { struct dahdi_pvt *p = pvt; if (p->channel == CHAN_PSEUDO) { return; } p->polarity = value; ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETPOLARITY, &value); } static void my_start_polarityswitch(void *pvt) { struct dahdi_pvt *p = pvt; if (p->answeronpolarityswitch || p->hanguponpolarityswitch) { my_set_polarity(pvt, 0); } } static void my_answer_polarityswitch(void *pvt) { struct dahdi_pvt *p = pvt; if (!p->answeronpolarityswitch) { return; } my_set_polarity(pvt, 1); } static void my_hangup_polarityswitch(void *pvt) { struct dahdi_pvt *p = pvt; if (!p->hanguponpolarityswitch) { return; } if (p->answeronpolarityswitch) { my_set_polarity(pvt, 0); } else { my_set_polarity(pvt, 1); } } static int my_start(void *pvt) { struct dahdi_pvt *p = pvt; int x = DAHDI_START; return ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); } static int my_dial_digits(void *pvt, enum analog_sub sub, struct analog_dialoperation *dop) { struct dahdi_pvt *p = pvt; if (dop->op != ANALOG_DIAL_OP_REPLACE) { ast_log(LOG_ERROR, "Fix the dial_digits callback!\n"); return -1; } if (sub != ANALOG_SUB_REAL) { ast_log(LOG_ERROR, "Trying to dial_digits '%s' on channel %d subchannel %u\n", dop->dialstr, p->channel, sub); return -1; } return dahdi_dial_str(p, DAHDI_DIAL_OP_REPLACE, dop->dialstr); } static void dahdi_train_ec(struct dahdi_pvt *p); static int my_train_echocanceller(void *pvt) { struct dahdi_pvt *p = pvt; dahdi_train_ec(p); return 0; } static int my_is_dialing(void *pvt, enum analog_sub sub) { struct dahdi_pvt *p = pvt; int index; int x; index = analogsub_to_dahdisub(sub); if (ioctl(p->subs[index].dfd, DAHDI_DIALING, &x)) { ast_debug(1, "DAHDI_DIALING ioctl failed!\n"); return -1; } return x; } static int my_on_hook(void *pvt) { struct dahdi_pvt *p = pvt; return dahdi_set_hook(p->subs[ANALOG_SUB_REAL].dfd, DAHDI_ONHOOK); } #if defined(HAVE_PRI) static void my_pri_fixup_chans(void *chan_old, void *chan_new) { struct dahdi_pvt *old_chan = chan_old; struct dahdi_pvt *new_chan = chan_new; new_chan->owner = old_chan->owner; old_chan->owner = NULL; if (new_chan->owner) { ast_channel_tech_pvt_set(new_chan->owner, new_chan); ast_channel_internal_fd_set(new_chan->owner, 0, new_chan->subs[SUB_REAL].dfd); new_chan->subs[SUB_REAL].owner = old_chan->subs[SUB_REAL].owner; old_chan->subs[SUB_REAL].owner = NULL; } /* Copy any DSP that may be present */ new_chan->dsp = old_chan->dsp; new_chan->dsp_features = old_chan->dsp_features; old_chan->dsp = NULL; old_chan->dsp_features = 0; /* Transfer flags from the old channel. */ new_chan->dialing = old_chan->dialing; new_chan->digital = old_chan->digital; new_chan->outgoing = old_chan->outgoing; old_chan->dialing = 0; old_chan->digital = 0; old_chan->outgoing = 0; /* More stuff to transfer to the new channel. */ new_chan->law = old_chan->law; strcpy(new_chan->dialstring, old_chan->dialstring); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int sig_pri_tone_to_dahditone(enum sig_pri_tone tone) { switch (tone) { case SIG_PRI_TONE_RINGTONE: return DAHDI_TONE_RINGTONE; case SIG_PRI_TONE_STUTTER: return DAHDI_TONE_STUTTER; case SIG_PRI_TONE_CONGESTION: return DAHDI_TONE_CONGESTION; case SIG_PRI_TONE_DIALTONE: return DAHDI_TONE_DIALTONE; case SIG_PRI_TONE_DIALRECALL: return DAHDI_TONE_DIALRECALL; case SIG_PRI_TONE_INFO: return DAHDI_TONE_INFO; case SIG_PRI_TONE_BUSY: return DAHDI_TONE_BUSY; default: return -1; } } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static void my_handle_dchan_exception(struct sig_pri_span *pri, int index) { int x; ioctl(pri->fds[index], DAHDI_GETEVENT, &x); switch (x) { case DAHDI_EVENT_NONE: break; case DAHDI_EVENT_ALARM: case DAHDI_EVENT_NOALARM: if (sig_pri_is_alarm_ignored(pri)) { break; } /* Fall through */ default: ast_log(LOG_NOTICE, "Got DAHDI event: %s (%d) on D-channel of span %d\n", event2str(x), x, pri->span); break; } /* Keep track of alarm state */ switch (x) { case DAHDI_EVENT_ALARM: pri_event_alarm(pri, index, 0); break; case DAHDI_EVENT_NOALARM: pri_event_noalarm(pri, index, 0); break; case DAHDI_EVENT_REMOVED: pri_queue_for_destruction(pri); break; default: break; } } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int my_pri_play_tone(void *pvt, enum sig_pri_tone tone) { struct dahdi_pvt *p = pvt; return tone_zone_play_tone(p->subs[SUB_REAL].dfd, sig_pri_tone_to_dahditone(tone)); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) || defined(HAVE_SS7) /*! * \internal * \brief Set the caller id information. * \since 1.8 * * \param pvt DAHDI private structure * \param caller Caller-id information to set. * * \return Nothing */ static void my_set_callerid(void *pvt, const struct ast_party_caller *caller) { struct dahdi_pvt *p = pvt; ast_copy_string(p->cid_num, S_COR(caller->id.number.valid, caller->id.number.str, ""), sizeof(p->cid_num)); ast_copy_string(p->cid_name, S_COR(caller->id.name.valid, caller->id.name.str, ""), sizeof(p->cid_name)); ast_copy_string(p->cid_subaddr, S_COR(caller->id.subaddress.valid, caller->id.subaddress.str, ""), sizeof(p->cid_subaddr)); p->cid_ton = caller->id.number.plan; p->callingpres = ast_party_id_presentation(&caller->id); if (caller->id.tag) { ast_copy_string(p->cid_tag, caller->id.tag, sizeof(p->cid_tag)); } ast_copy_string(p->cid_ani, S_COR(caller->ani.number.valid, caller->ani.number.str, ""), sizeof(p->cid_ani)); p->cid_ani2 = caller->ani2; } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #if defined(HAVE_PRI) || defined(HAVE_SS7) /*! * \internal * \brief Set the Dialed Number Identifier. * \since 1.8 * * \param pvt DAHDI private structure * \param dnid Dialed Number Identifier string. * * \return Nothing */ static void my_set_dnid(void *pvt, const char *dnid) { struct dahdi_pvt *p = pvt; ast_copy_string(p->dnid, dnid, sizeof(p->dnid)); } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #if defined(HAVE_PRI) /*! * \internal * \brief Set the Redirecting Directory Number Information Service (RDNIS). * \since 1.8 * * \param pvt DAHDI private structure * \param rdnis Redirecting Directory Number Information Service (RDNIS) string. * * \return Nothing */ static void my_set_rdnis(void *pvt, const char *rdnis) { struct dahdi_pvt *p = pvt; ast_copy_string(p->rdnis, rdnis, sizeof(p->rdnis)); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Make a dialstring for native ISDN CC to recall properly. * \since 1.8 * * \param priv Channel private control structure. * \param buf Where to put the modified dialstring. * \param buf_size Size of modified dialstring buffer. * * \details * original dialstring: * DAHDI/[i-](g|G|r|R)[c|r|d][/extension[/options]] * * The modified dialstring will have prefixed the channel-group section * with the ISDN channel restriction. * * buf: * DAHDI/i-(g|G|r|R)[c|r|d][/extension[/options]] * * The routine will check to see if the ISDN channel restriction is already * in the original dialstring. * * \return Nothing */ static void my_pri_make_cc_dialstring(void *priv, char *buf, size_t buf_size) { char *dial; struct dahdi_pvt *pvt; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(tech); /* channel technology token */ AST_APP_ARG(group); /* channel/group token */ //AST_APP_ARG(ext); /* extension token */ //AST_APP_ARG(opts); /* options token */ //AST_APP_ARG(other); /* Any remining unused arguments */ ); pvt = priv; dial = ast_strdupa(pvt->dialstring); AST_NONSTANDARD_APP_ARGS(args, dial, '/'); if (!args.tech) { ast_copy_string(buf, pvt->dialstring, buf_size); return; } if (!args.group) { /* Append the ISDN span channel restriction to the dialstring. */ snprintf(buf, buf_size, "%s/i%d-", args.tech, pvt->pri->span); return; } if (isdigit(args.group[0]) || args.group[0] == 'i' || strchr(args.group, '!')) { /* The ISDN span channel restriction is not needed or already * in the dialstring. */ ast_copy_string(buf, pvt->dialstring, buf_size); return; } /* Insert the ISDN span channel restriction into the dialstring. */ snprintf(buf, buf_size, "%s/i%d-%s", args.tech, pvt->pri->span, args.group); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Reevaluate the PRI span device state. * \since 1.8 * * \param pri Asterisk D channel control structure. * * \return Nothing * * \note Assumes the pri->lock is already obtained. */ static void dahdi_pri_update_span_devstate(struct sig_pri_span *pri) { unsigned idx; unsigned num_b_chans; /* Number of B channels provisioned on the span. */ unsigned in_use; /* Number of B channels in use on the span. */ unsigned in_alarm; /* TRUE if the span is in alarm condition. */ enum ast_device_state new_state; /* Count the number of B channels and the number of B channels in use. */ num_b_chans = 0; in_use = 0; in_alarm = 1; for (idx = pri->numchans; idx--;) { if (pri->pvts[idx] && !pri->pvts[idx]->no_b_channel) { /* This is a B channel interface. */ ++num_b_chans; if (!sig_pri_is_chan_available(pri->pvts[idx])) { ++in_use; } if (!pri->pvts[idx]->inalarm) { /* There is a channel that is not in alarm. */ in_alarm = 0; } } } /* Update the span congestion device state and report any change. */ if (in_alarm) { new_state = AST_DEVICE_UNAVAILABLE; } else { new_state = num_b_chans == in_use ? AST_DEVICE_BUSY : AST_DEVICE_NOT_INUSE; } if (pri->congestion_devstate != new_state) { pri->congestion_devstate = new_state; ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "DAHDI/I%d/congestion", pri->span); } #if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) /* Update the span threshold device state and report any change. */ if (in_alarm) { new_state = AST_DEVICE_UNAVAILABLE; } else if (!in_use) { new_state = AST_DEVICE_NOT_INUSE; } else if (!pri->user_busy_threshold) { new_state = in_use < num_b_chans ? AST_DEVICE_INUSE : AST_DEVICE_BUSY; } else { new_state = in_use < pri->user_busy_threshold ? AST_DEVICE_INUSE : AST_DEVICE_BUSY; } if (pri->threshold_devstate != new_state) { pri->threshold_devstate = new_state; ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "DAHDI/I%d/threshold", pri->span); } #endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Reference this module. * \since 1.8 * * \return Nothing */ static void my_module_ref(void) { ast_module_ref(ast_module_info->self); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Unreference this module. * \since 1.8 * * \return Nothing */ static void my_module_unref(void) { ast_module_unref(ast_module_info->self); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_CALL_WAITING) static void my_pri_init_config(void *priv, struct sig_pri_span *pri); #endif /* defined(HAVE_PRI_CALL_WAITING) */ static int dahdi_new_pri_nobch_channel(struct sig_pri_span *pri); struct sig_pri_callback sig_pri_callbacks = { .handle_dchan_exception = my_handle_dchan_exception, .play_tone = my_pri_play_tone, .set_echocanceller = my_set_echocanceller, .dsp_reset_and_flush_digits = my_dsp_reset_and_flush_digits, .lock_private = my_lock_private, .unlock_private = my_unlock_private, .deadlock_avoidance_private = my_deadlock_avoidance_private, .new_ast_channel = my_new_pri_ast_channel, .fixup_chans = my_pri_fixup_chans, .set_alarm = my_set_alarm, .set_dialing = my_set_dialing, .set_outgoing = my_set_outgoing, .set_digital = my_set_digital, .set_callerid = my_set_callerid, .set_dnid = my_set_dnid, .set_rdnis = my_set_rdnis, .new_nobch_intf = dahdi_new_pri_nobch_channel, #if defined(HAVE_PRI_CALL_WAITING) .init_config = my_pri_init_config, #endif /* defined(HAVE_PRI_CALL_WAITING) */ .get_orig_dialstring = my_get_orig_dialstring, .make_cc_dialstring = my_pri_make_cc_dialstring, .update_span_devstate = dahdi_pri_update_span_devstate, .module_ref = my_module_ref, .module_unref = my_module_unref, .dial_digits = my_pri_dial_digits, .open_media = my_pri_ss7_open_media, .ami_channel_event = my_ami_channel_event, .destroy_later = pri_queue_for_destruction, }; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) /*! * \internal * \brief Handle the SS7 link exception. * \since 1.8 * * \param linkset Controlling linkset for the channel. * \param which Link index of the signaling channel. * * \return Nothing */ static void my_handle_link_exception(struct sig_ss7_linkset *linkset, int which) { int event; if (ioctl(linkset->fds[which], DAHDI_GETEVENT, &event)) { ast_log(LOG_ERROR, "SS7: Error in exception retrieval on span %d/%d!\n", linkset->span, which); return; } switch (event) { case DAHDI_EVENT_NONE: break; case DAHDI_EVENT_ALARM: ast_log(LOG_ERROR, "SS7 got event: %s(%d) on span %d/%d\n", event2str(event), event, linkset->span, which); sig_ss7_link_alarm(linkset, which); break; case DAHDI_EVENT_NOALARM: ast_log(LOG_ERROR, "SS7 got event: %s(%d) on span %d/%d\n", event2str(event), event, linkset->span, which); sig_ss7_link_noalarm(linkset, which); break; default: ast_log(LOG_NOTICE, "SS7 got event: %s(%d) on span %d/%d\n", event2str(event), event, linkset->span, which); break; } } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static void my_ss7_set_loopback(void *pvt, int enable) { struct dahdi_pvt *p = pvt; if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_LOOPBACK, &enable)) { ast_log(LOG_WARNING, "Unable to set loopback on channel %d: %s\n", p->channel, strerror(errno)); } } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) /*! * \internal * \brief Find the linkset to which SS7 belongs. * \since 11.0 * * \param ss7 structure to match on. * * \retval linkset if found. * \retval NULL if not found. */ static struct sig_ss7_linkset *my_ss7_find_linkset(struct ss7 *ss7) { int idx; if (!ss7) { return NULL; } for (idx = 0; idx < NUM_SPANS; ++idx) { if (linksets[idx].ss7.ss7 == ss7) { return &linksets[idx].ss7; } } return NULL; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) /*! * \internal * \brief Create a new asterisk channel structure for SS7. * \since 1.8 * * \param pvt Private channel structure. * \param state Initial state of new channel. * \param law Combanding law to use. * \param exten Dialplan extension for incoming call. * \param requestor Channel requesting this new channel. * * \retval ast_channel on success. * \retval NULL on error. */ static struct ast_channel *my_new_ss7_ast_channel(void *pvt, int state, enum sig_ss7_law law, char *exten, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct dahdi_pvt *p = pvt; int audio; int newlaw; struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); /* Set to audio mode at this point */ audio = 1; if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &audio) == -1) ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d: %s\n", p->channel, audio, strerror(errno)); if (law != SIG_SS7_DEFLAW) { dahdi_setlaw(p->subs[SUB_REAL].dfd, (law == SIG_SS7_ULAW) ? DAHDI_LAW_MULAW : DAHDI_LAW_ALAW); } ast_copy_string(p->exten, exten, sizeof(p->exten)); newlaw = -1; switch (law) { case SIG_SS7_DEFLAW: newlaw = 0; break; case SIG_SS7_ALAW: newlaw = DAHDI_LAW_ALAW; break; case SIG_SS7_ULAW: newlaw = DAHDI_LAW_MULAW; break; } return dahdi_new_callid_clean(p, state, 0, SUB_REAL, newlaw, assignedids, requestor, callid, callid_created); } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static int sig_ss7_tone_to_dahditone(enum sig_ss7_tone tone) { switch (tone) { case SIG_SS7_TONE_RINGTONE: return DAHDI_TONE_RINGTONE; case SIG_SS7_TONE_STUTTER: return DAHDI_TONE_STUTTER; case SIG_SS7_TONE_CONGESTION: return DAHDI_TONE_CONGESTION; case SIG_SS7_TONE_DIALTONE: return DAHDI_TONE_DIALTONE; case SIG_SS7_TONE_DIALRECALL: return DAHDI_TONE_DIALRECALL; case SIG_SS7_TONE_INFO: return DAHDI_TONE_INFO; case SIG_SS7_TONE_BUSY: return DAHDI_TONE_BUSY; default: return -1; } } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static int my_ss7_play_tone(void *pvt, enum sig_ss7_tone tone) { struct dahdi_pvt *p = pvt; return tone_zone_play_tone(p->subs[SUB_REAL].dfd, sig_ss7_tone_to_dahditone(tone)); } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) struct sig_ss7_callback sig_ss7_callbacks = { .lock_private = my_lock_private, .unlock_private = my_unlock_private, .deadlock_avoidance_private = my_deadlock_avoidance_private, .set_echocanceller = my_set_echocanceller, .set_loopback = my_ss7_set_loopback, .new_ast_channel = my_new_ss7_ast_channel, .play_tone = my_ss7_play_tone, .handle_link_exception = my_handle_link_exception, .set_alarm = my_set_alarm, .set_dialing = my_set_dialing, .set_outgoing = my_set_outgoing, .set_digital = my_set_digital, .set_inservice = my_set_inservice, .set_locallyblocked = my_set_locallyblocked, .set_remotelyblocked = my_set_remotelyblocked, .set_callerid = my_set_callerid, .set_dnid = my_set_dnid, .open_media = my_pri_ss7_open_media, .find_linkset = my_ss7_find_linkset, }; #endif /* defined(HAVE_SS7) */ /*! * \brief Send MWI state change * * \param mailbox This is the mailbox associated with the FXO line that the * MWI state has changed on. * \param thereornot This argument should simply be set to 1 or 0, to indicate * whether there are messages waiting or not. * * \return nothing * * This function does two things: * * 1) It generates an internal Asterisk event notifying any other module that * cares about MWI that the state of a mailbox has changed. * * 2) It runs the script specified by the mwimonitornotify option to allow * some custom handling of the state change. */ static void notify_message(char *mailbox, int thereornot) { char s[sizeof(mwimonitornotify) + 80]; if (ast_strlen_zero(mailbox)) { return; } ast_publish_mwi_state(mailbox, NULL, thereornot, thereornot); if (!ast_strlen_zero(mwimonitornotify)) { snprintf(s, sizeof(s), "%s %s %d", mwimonitornotify, mailbox, thereornot); ast_safe_system(s); } } static void my_handle_notify_message(struct ast_channel *chan, void *pvt, int cid_flags, int neon_mwievent) { struct dahdi_pvt *p = pvt; if (neon_mwievent > -1 && !p->mwimonitor_neon) return; if (neon_mwievent == ANALOG_EVENT_NEONMWI_ACTIVE || cid_flags & CID_MSGWAITING) { ast_log(LOG_NOTICE, "MWI: Channel %d message waiting, mailbox %s\n", p->channel, p->mailbox); notify_message(p->mailbox, 1); } else if (neon_mwievent == ANALOG_EVENT_NEONMWI_INACTIVE || cid_flags & CID_NOMSGWAITING) { ast_log(LOG_NOTICE, "MWI: Channel %d no message waiting, mailbox %s\n", p->channel, p->mailbox); notify_message(p->mailbox, 0); } /* If the CID had Message waiting payload, assume that this for MWI only and hangup the call */ /* If generated using Ring Pulse Alert, then ring has been answered as a call and needs to be hungup */ if (neon_mwievent == -1 && p->mwimonitor_rpas) { ast_hangup(chan); return; } } static int my_have_progressdetect(void *pvt) { struct dahdi_pvt *p = pvt; if ((p->callprogress & CALLPROGRESS_PROGRESS) && CANPROGRESSDETECT(p) && p->dsp && p->outgoing) { return 1; } else { /* Don't have progress detection. */ return 0; } } struct analog_callback analog_callbacks = { .play_tone = my_play_tone, .get_event = my_get_event, .wait_event = my_wait_event, .is_off_hook = my_is_off_hook, .set_echocanceller = my_set_echocanceller, .ring = my_ring, .flash = my_flash, .off_hook = my_off_hook, .dial_digits = my_dial_digits, .train_echocanceller = my_train_echocanceller, .on_hook = my_on_hook, .is_dialing = my_is_dialing, .allocate_sub = my_allocate_sub, .unallocate_sub = my_unallocate_sub, .swap_subs = my_swap_subchannels, .has_voicemail = my_has_voicemail, .check_for_conference = my_check_for_conference, .conf_add = my_conf_add, .conf_del = my_conf_del, .complete_conference_update = my_complete_conference_update, .start = my_start, .all_subchannels_hungup = my_all_subchannels_hungup, .lock_private = my_lock_private, .unlock_private = my_unlock_private, .deadlock_avoidance_private = my_deadlock_avoidance_private, .handle_dtmf = my_handle_dtmf, .wink = my_wink, .new_ast_channel = my_new_analog_ast_channel, .dsp_set_digitmode = my_dsp_set_digitmode, .dsp_reset_and_flush_digits = my_dsp_reset_and_flush_digits, .send_callerid = my_send_callerid, .callwait = my_callwait, .stop_callwait = my_stop_callwait, .get_callerid = my_get_callerid, .start_cid_detect = my_start_cid_detect, .stop_cid_detect = my_stop_cid_detect, .handle_notify_message = my_handle_notify_message, .increase_ss_count = my_increase_ss_count, .decrease_ss_count = my_decrease_ss_count, .distinctive_ring = my_distinctive_ring, .set_linear_mode = my_set_linear_mode, .set_inthreeway = my_set_inthreeway, .get_and_handle_alarms = my_get_and_handle_alarms, .get_sigpvt_bridged_channel = my_get_sigpvt_bridged_channel, .get_sub_fd = my_get_sub_fd, .set_cadence = my_set_cadence, .set_alarm = my_set_alarm, .set_dialing = my_set_dialing, .set_outgoing = my_set_outgoing, .set_ringtimeout = my_set_ringtimeout, .set_waitingfordt = my_set_waitingfordt, .check_waitingfordt = my_check_waitingfordt, .set_confirmanswer = my_set_confirmanswer, .check_confirmanswer = my_check_confirmanswer, .set_callwaiting = my_set_callwaiting, .cancel_cidspill = my_cancel_cidspill, .confmute = my_confmute, .set_pulsedial = my_set_pulsedial, .set_new_owner = my_set_new_owner, .get_orig_dialstring = my_get_orig_dialstring, .set_needringing = my_set_needringing, .set_polarity = my_set_polarity, .start_polarityswitch = my_start_polarityswitch, .answer_polarityswitch = my_answer_polarityswitch, .hangup_polarityswitch = my_hangup_polarityswitch, .have_progressdetect = my_have_progressdetect, }; /*! Round robin search locations. */ static struct dahdi_pvt *round_robin[32]; int _dahdi_get_index(struct ast_channel *ast, struct dahdi_pvt *p, int nullok, const char *fname, unsigned long line) { int res; if (p->subs[SUB_REAL].owner == ast) res = 0; else if (p->subs[SUB_CALLWAIT].owner == ast) res = 1; else if (p->subs[SUB_THREEWAY].owner == ast) res = 2; else { res = -1; if (!nullok) ast_log(LOG_WARNING, "Unable to get index for '%s' on channel %d (%s(), line %lu)\n", ast ? ast_channel_name(ast) : "", p->channel, fname, line); } return res; } /*! * \internal * \brief Obtain the specified subchannel owner lock if the owner exists. * * \param pvt Channel private struct. * \param sub_idx Subchannel owner to lock. * * \note Assumes the pvt->lock is already obtained. * * \note * Because deadlock avoidance may have been necessary, you need to confirm * the state of things before continuing. * * \return Nothing */ static void dahdi_lock_sub_owner(struct dahdi_pvt *pvt, int sub_idx) { for (;;) { if (!pvt->subs[sub_idx].owner) { /* No subchannel owner pointer */ break; } if (!ast_channel_trylock(pvt->subs[sub_idx].owner)) { /* Got subchannel owner lock */ break; } /* We must unlock the private to avoid the possibility of a deadlock */ DEADLOCK_AVOIDANCE(&pvt->lock); } } static void wakeup_sub(struct dahdi_pvt *p, int a) { dahdi_lock_sub_owner(p, a); if (p->subs[a].owner) { ast_queue_frame(p->subs[a].owner, &ast_null_frame); ast_channel_unlock(p->subs[a].owner); } } static void dahdi_queue_frame(struct dahdi_pvt *p, struct ast_frame *f) { for (;;) { if (p->owner) { if (ast_channel_trylock(p->owner)) { DEADLOCK_AVOIDANCE(&p->lock); } else { ast_queue_frame(p->owner, f); ast_channel_unlock(p->owner); break; } } else break; } } static void publish_channel_alarm_clear(int channel) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); RAII_VAR(struct ast_str *, dahdi_chan, ast_str_create(32), ast_free); if (!dahdi_chan) { return; } ast_str_set(&dahdi_chan, 0, "%d", channel); ast_log(LOG_NOTICE, "Alarm cleared on channel DAHDI/%d\n", channel); body = ast_json_pack("{s: s}", "DAHDIChannel", ast_str_buffer(dahdi_chan)); if (!body) { return; } ast_manager_publish_event("AlarmClear", EVENT_FLAG_SYSTEM, body); } static void publish_span_alarm_clear(int span) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); ast_log(LOG_NOTICE, "Alarm cleared on span %d\n", span); body = ast_json_pack("{s: i}", "Span", span); if (!body) { return; } ast_manager_publish_event("SpanAlarmClear", EVENT_FLAG_SYSTEM, body); } static void handle_clear_alarms(struct dahdi_pvt *p) { #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig) && sig_pri_is_alarm_ignored(p->pri)) { return; } #endif /* defined(HAVE_PRI) */ if (report_alarms & REPORT_CHANNEL_ALARMS) { publish_channel_alarm_clear(p->channel); } if (report_alarms & REPORT_SPAN_ALARMS && p->manages_span_alarms) { publish_span_alarm_clear(p->span); } } #ifdef HAVE_OPENR2 static int dahdi_r2_answer(struct dahdi_pvt *p) { int res = 0; /* openr2 1.1.0 and older does not even define OR2_LIB_INTERFACE * and does not has support for openr2_chan_answer_call_with_mode * */ #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 1 const char *double_answer = pbx_builtin_getvar_helper(p->owner, "MFCR2_DOUBLE_ANSWER"); int wants_double_answer = ast_true(double_answer) ? 1 : 0; if (!double_answer) { /* this still can result in double answer if the channel context * was configured that way */ res = openr2_chan_answer_call(p->r2chan); } else if (wants_double_answer) { res = openr2_chan_answer_call_with_mode(p->r2chan, OR2_ANSWER_DOUBLE); } else { res = openr2_chan_answer_call_with_mode(p->r2chan, OR2_ANSWER_SIMPLE); } #else res = openr2_chan_answer_call(p->r2chan); #endif return res; } /* should be called with the ast_channel locked */ static openr2_calling_party_category_t dahdi_r2_get_channel_category(struct ast_channel *c) { openr2_calling_party_category_t cat; const char *catstr = pbx_builtin_getvar_helper(c, "MFCR2_CATEGORY"); struct dahdi_pvt *p = ast_channel_tech_pvt(c); if (ast_strlen_zero(catstr)) { ast_debug(1, "No MFC/R2 category specified for chan %s, using default %s\n", ast_channel_name(c), openr2_proto_get_category_string(p->mfcr2_category)); return p->mfcr2_category; } if ((cat = openr2_proto_get_category(catstr)) == OR2_CALLING_PARTY_CATEGORY_UNKNOWN) { ast_log(LOG_WARNING, "Invalid category specified '%s' for chan %s, using default %s\n", catstr, ast_channel_name(c), openr2_proto_get_category_string(p->mfcr2_category)); return p->mfcr2_category; } ast_debug(1, "Using category %s\n", catstr); return cat; } static void dahdi_r2_on_call_init(openr2_chan_t *r2chan) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_mutex_lock(&p->lock); if (p->mfcr2call) { ast_mutex_unlock(&p->lock); /* TODO: This can happen when some other thread just finished dahdi_request requesting this very same interface but has not yet seized the line (dahdi_call), and the far end wins and seize the line, can we avoid this somehow?, at this point when dahdi_call send the seize, it is likely that since the other end will see our seize as a forced release and drop the call, we will see an invalid pattern that will be seen and treated as protocol error. */ ast_log(LOG_ERROR, "Collision of calls on chan %d detected!.\n", openr2_chan_get_number(r2chan)); return; } p->mfcr2call = 1; /* better safe than sorry ... */ p->cid_name[0] = '\0'; p->cid_num[0] = '\0'; p->cid_subaddr[0] = '\0'; p->rdnis[0] = '\0'; p->exten[0] = '\0'; p->mfcr2_ani_index = '\0'; p->mfcr2_dnis_index = '\0'; p->mfcr2_dnis_matched = 0; p->mfcr2_answer_pending = 0; p->mfcr2_call_accepted = 0; ast_mutex_unlock(&p->lock); ast_verbose("New MFC/R2 call detected on chan %d.\n", openr2_chan_get_number(r2chan)); } static void dahdi_r2_on_hardware_alarm(openr2_chan_t *r2chan, int alarm) { int res; struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_mutex_lock(&p->lock); p->inalarm = alarm ? 1 : 0; if (p->inalarm) { res = get_alarms(p); handle_alarms(p, res); } else { handle_clear_alarms(p); } ast_mutex_unlock(&p->lock); } static void dahdi_r2_on_os_error(openr2_chan_t *r2chan, int errorcode) { ast_log(LOG_ERROR, "OS error on chan %d: %s\n", openr2_chan_get_number(r2chan), strerror(errorcode)); } static void dahdi_r2_on_protocol_error(openr2_chan_t *r2chan, openr2_protocol_error_t reason) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_log(LOG_ERROR, "MFC/R2 protocol error on chan %d: %s\n", openr2_chan_get_number(r2chan), openr2_proto_get_error(reason)); if (p->owner) { ast_channel_hangupcause_set(p->owner, AST_CAUSE_PROTOCOL_ERROR); ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); } ast_mutex_lock(&p->lock); p->mfcr2call = 0; ast_mutex_unlock(&p->lock); } static void dahdi_r2_disconnect_call(struct dahdi_pvt *p, openr2_call_disconnect_cause_t cause) { if (openr2_chan_disconnect_call(p->r2chan, cause)) { ast_log(LOG_NOTICE, "Bad! failed to disconnect call on channel %d with reason %s, hope for the best!\n", p->channel, openr2_proto_get_disconnect_string(cause)); /* force the chan to idle and release the call flag now since we will not see a clean on_call_end */ openr2_chan_set_idle(p->r2chan); ast_mutex_lock(&p->lock); p->mfcr2call = 0; ast_mutex_unlock(&p->lock); } } static void dahdi_r2_on_call_offered(openr2_chan_t *r2chan, const char *ani, const char *dnis, openr2_calling_party_category_t category) { struct dahdi_pvt *p; struct ast_channel *c; struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); ast_verbose("MFC/R2 call offered on chan %d. ANI = %s, DNIS = %s, Category = %s\n", openr2_chan_get_number(r2chan), ani ? ani : "(restricted)", dnis, openr2_proto_get_category_string(category)); p = openr2_chan_get_client_data(r2chan); /* if collect calls are not allowed and this is a collect call, reject it! */ if (!p->mfcr2_allow_collect_calls && category == OR2_CALLING_PARTY_CATEGORY_COLLECT_CALL) { ast_log(LOG_NOTICE, "Rejecting MFC/R2 collect call\n"); dahdi_r2_disconnect_call(p, OR2_CAUSE_COLLECT_CALL_REJECTED); goto dahdi_r2_on_call_offered_cleanup; } ast_mutex_lock(&p->lock); p->mfcr2_recvd_category = category; /* if we're not supposed to use CID, clear whatever we have */ if (!p->use_callerid) { ast_debug(1, "No CID allowed in configuration, CID is being cleared!\n"); p->cid_num[0] = 0; p->cid_name[0] = 0; } /* if we're supposed to answer immediately, clear DNIS and set 's' exten */ if (p->immediate || !openr2_context_get_max_dnis(openr2_chan_get_context(r2chan))) { ast_debug(1, "Setting exten => s because of immediate or 0 DNIS configured\n"); p->exten[0] = 's'; p->exten[1] = 0; } ast_mutex_unlock(&p->lock); if (!ast_exists_extension(NULL, p->context, p->exten, 1, p->cid_num)) { ast_log(LOG_NOTICE, "MFC/R2 call on channel %d requested non-existent extension '%s' in context '%s'. Rejecting call.\n", p->channel, p->exten, p->context); dahdi_r2_disconnect_call(p, OR2_CAUSE_UNALLOCATED_NUMBER); goto dahdi_r2_on_call_offered_cleanup; } if (!p->mfcr2_accept_on_offer) { /* The user wants us to start the PBX thread right away without accepting the call first */ c = dahdi_new(p, AST_STATE_RING, 1, SUB_REAL, DAHDI_LAW_ALAW, NULL, NULL, callid); if (c) { /* Done here, don't disable reading now since we still need to generate MF tones to accept the call or reject it and detect the tone off condition of the other end, all of this will be done in the PBX thread now */ goto dahdi_r2_on_call_offered_cleanup; } ast_log(LOG_WARNING, "Unable to create PBX channel in DAHDI channel %d\n", p->channel); dahdi_r2_disconnect_call(p, OR2_CAUSE_OUT_OF_ORDER); } else if (p->mfcr2_charge_calls) { ast_debug(1, "Accepting MFC/R2 call with charge on chan %d\n", p->channel); openr2_chan_accept_call(r2chan, OR2_CALL_WITH_CHARGE); } else { ast_debug(1, "Accepting MFC/R2 call with no charge on chan %d\n", p->channel); openr2_chan_accept_call(r2chan, OR2_CALL_NO_CHARGE); } dahdi_r2_on_call_offered_cleanup: ast_callid_threadstorage_auto_clean(callid, callid_created); } static void dahdi_r2_on_call_end(openr2_chan_t *r2chan) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_verbose("MFC/R2 call end on channel %d\n", p->channel); ast_mutex_lock(&p->lock); p->mfcr2call = 0; ast_mutex_unlock(&p->lock); } static void dahdi_r2_on_call_accepted(openr2_chan_t *r2chan, openr2_call_mode_t mode) { struct dahdi_pvt *p = NULL; struct ast_channel *c = NULL; struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); p = openr2_chan_get_client_data(r2chan); dahdi_ec_enable(p); p->mfcr2_call_accepted = 1; /* if it's an incoming call ... */ if (OR2_DIR_BACKWARD == openr2_chan_get_direction(r2chan)) { ast_verbose("MFC/R2 call has been accepted on backward channel %d\n", openr2_chan_get_number(r2chan)); /* If accept on offer is not set, it means at this point the PBX thread is already launched (was launched in the 'on call offered' handler) and therefore this callback is being executed already in the PBX thread rather than the monitor thread, don't launch any other thread, just disable the openr2 reading and answer the call if needed */ if (!p->mfcr2_accept_on_offer) { openr2_chan_disable_read(r2chan); if (p->mfcr2_answer_pending) { ast_debug(1, "Answering MFC/R2 call after accepting it on chan %d\n", openr2_chan_get_number(r2chan)); dahdi_r2_answer(p); } goto dahdi_r2_on_call_accepted_cleanup; } c = dahdi_new(p, AST_STATE_RING, 1, SUB_REAL, DAHDI_LAW_ALAW, NULL, NULL, callid); if (c) { /* chan_dahdi will take care of reading from now on in the PBX thread, tell the library to forget about it */ openr2_chan_disable_read(r2chan); goto dahdi_r2_on_call_accepted_cleanup; } ast_log(LOG_WARNING, "Unable to create PBX channel in DAHDI channel %d\n", p->channel); /* failed to create the channel, bail out and report it as an out of order line */ dahdi_r2_disconnect_call(p, OR2_CAUSE_OUT_OF_ORDER); goto dahdi_r2_on_call_accepted_cleanup; } /* this is an outgoing call, no need to launch the PBX thread, most likely we're in one already */ ast_verbose("MFC/R2 call has been accepted on forward channel %d\n", p->channel); p->subs[SUB_REAL].needringing = 1; p->dialing = 0; /* chan_dahdi will take care of reading from now on in the PBX thread, tell the library to forget about it */ openr2_chan_disable_read(r2chan); dahdi_r2_on_call_accepted_cleanup: ast_callid_threadstorage_auto_clean(callid, callid_created); } static void dahdi_r2_on_call_answered(openr2_chan_t *r2chan) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_verbose("MFC/R2 call has been answered on channel %d\n", openr2_chan_get_number(r2chan)); p->subs[SUB_REAL].needanswer = 1; } static void dahdi_r2_on_call_read(openr2_chan_t *r2chan, const unsigned char *buf, int buflen) { /*ast_debug(1, "Read data from dahdi channel %d\n", openr2_chan_get_number(r2chan));*/ } static int dahdi_r2_cause_to_ast_cause(openr2_call_disconnect_cause_t cause) { switch (cause) { case OR2_CAUSE_BUSY_NUMBER: return AST_CAUSE_BUSY; case OR2_CAUSE_NETWORK_CONGESTION: return AST_CAUSE_CONGESTION; case OR2_CAUSE_OUT_OF_ORDER: return AST_CAUSE_DESTINATION_OUT_OF_ORDER; case OR2_CAUSE_UNALLOCATED_NUMBER: return AST_CAUSE_UNREGISTERED; case OR2_CAUSE_NO_ANSWER: return AST_CAUSE_NO_ANSWER; case OR2_CAUSE_NORMAL_CLEARING: return AST_CAUSE_NORMAL_CLEARING; case OR2_CAUSE_UNSPECIFIED: default: return AST_CAUSE_NOTDEFINED; } } static void dahdi_r2_on_call_disconnect(openr2_chan_t *r2chan, openr2_call_disconnect_cause_t cause) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); char cause_str[50]; struct ast_control_pvt_cause_code *cause_code; int datalen = sizeof(*cause_code); ast_verbose("MFC/R2 call disconnected on channel %d\n", openr2_chan_get_number(r2chan)); ast_mutex_lock(&p->lock); if (!p->owner) { ast_mutex_unlock(&p->lock); /* no owner, therefore we can't use dahdi_hangup to disconnect, do it right now */ dahdi_r2_disconnect_call(p, OR2_CAUSE_NORMAL_CLEARING); return; } snprintf(cause_str, sizeof(cause_str), "R2 DISCONNECT (%s)", openr2_proto_get_disconnect_string(cause)); datalen += strlen(cause_str); cause_code = ast_alloca(datalen); memset(cause_code, 0, datalen); cause_code->ast_cause = dahdi_r2_cause_to_ast_cause(cause); ast_copy_string(cause_code->chan_name, ast_channel_name(p->owner), AST_CHANNEL_NAME); ast_copy_string(cause_code->code, cause_str, datalen + 1 - sizeof(*cause_code)); ast_queue_control_data(p->owner, AST_CONTROL_PVT_CAUSE_CODE, cause_code, datalen); ast_channel_hangupcause_hash_set(p->owner, cause_code, datalen); /* when we have an owner we don't call dahdi_r2_disconnect_call here, that will be done in dahdi_hangup */ if (ast_channel_state(p->owner) == AST_STATE_UP) { ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); ast_mutex_unlock(&p->lock); } else if (openr2_chan_get_direction(r2chan) == OR2_DIR_FORWARD) { /* being the forward side we must report what happened to the call to whoever requested it */ switch (cause) { case OR2_CAUSE_BUSY_NUMBER: p->subs[SUB_REAL].needbusy = 1; break; case OR2_CAUSE_NETWORK_CONGESTION: case OR2_CAUSE_OUT_OF_ORDER: case OR2_CAUSE_UNALLOCATED_NUMBER: case OR2_CAUSE_NO_ANSWER: case OR2_CAUSE_UNSPECIFIED: case OR2_CAUSE_NORMAL_CLEARING: p->subs[SUB_REAL].needcongestion = 1; break; default: ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV); } ast_mutex_unlock(&p->lock); } else { ast_mutex_unlock(&p->lock); /* being the backward side and not UP yet, we only need to request hangup */ /* TODO: what about doing this same thing when were AST_STATE_UP? */ ast_queue_hangup_with_cause(p->owner, dahdi_r2_cause_to_ast_cause(cause)); } } static void dahdi_r2_write_log(openr2_log_level_t level, char *logmessage) { switch (level) { case OR2_LOG_NOTICE: ast_verbose("%s", logmessage); break; case OR2_LOG_WARNING: ast_log(LOG_WARNING, "%s", logmessage); break; case OR2_LOG_ERROR: ast_log(LOG_ERROR, "%s", logmessage); break; case OR2_LOG_STACK_TRACE: case OR2_LOG_MF_TRACE: case OR2_LOG_CAS_TRACE: case OR2_LOG_DEBUG: case OR2_LOG_EX_DEBUG: ast_debug(1, "%s", logmessage); break; default: ast_log(LOG_WARNING, "We should handle logging level %d here.\n", level); ast_debug(1, "%s", logmessage); break; } } static void dahdi_r2_on_line_blocked(openr2_chan_t *r2chan) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_mutex_lock(&p->lock); p->remotelyblocked = 1; ast_mutex_unlock(&p->lock); ast_log(LOG_NOTICE, "Far end blocked on chan %d\n", openr2_chan_get_number(r2chan)); } static void dahdi_r2_on_line_idle(openr2_chan_t *r2chan) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); ast_mutex_lock(&p->lock); p->remotelyblocked = 0; ast_mutex_unlock(&p->lock); ast_log(LOG_NOTICE, "Far end unblocked on chan %d\n", openr2_chan_get_number(r2chan)); } static void dahdi_r2_on_context_log(openr2_context_t *r2context, openr2_log_level_t level, const char *fmt, va_list ap) __attribute__((format (printf, 3, 0))); static void dahdi_r2_on_context_log(openr2_context_t *r2context, openr2_log_level_t level, const char *fmt, va_list ap) { #define CONTEXT_TAG "Context - " char logmsg[256]; char completemsg[sizeof(logmsg) + sizeof(CONTEXT_TAG) - 1]; vsnprintf(logmsg, sizeof(logmsg), fmt, ap); snprintf(completemsg, sizeof(completemsg), CONTEXT_TAG "%s", logmsg); dahdi_r2_write_log(level, completemsg); #undef CONTEXT_TAG } static void dahdi_r2_on_chan_log(openr2_chan_t *r2chan, openr2_log_level_t level, const char *fmt, va_list ap) __attribute__((format (printf, 3, 0))); static void dahdi_r2_on_chan_log(openr2_chan_t *r2chan, openr2_log_level_t level, const char *fmt, va_list ap) { #define CHAN_TAG "Chan " char logmsg[256]; char completemsg[sizeof(logmsg) + sizeof(CHAN_TAG) - 1]; vsnprintf(logmsg, sizeof(logmsg), fmt, ap); snprintf(completemsg, sizeof(completemsg), CHAN_TAG "%d - %s", openr2_chan_get_number(r2chan), logmsg); dahdi_r2_write_log(level, completemsg); } static int dahdi_r2_on_dnis_digit_received(openr2_chan_t *r2chan, char digit) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); /* if 'immediate' is set, let's stop requesting DNIS */ if (p->immediate) { return 0; } p->exten[p->mfcr2_dnis_index] = digit; p->rdnis[p->mfcr2_dnis_index] = digit; p->mfcr2_dnis_index++; p->exten[p->mfcr2_dnis_index] = 0; p->rdnis[p->mfcr2_dnis_index] = 0; /* if the DNIS is a match and cannot match more, stop requesting DNIS */ if ((p->mfcr2_dnis_matched || (ast_exists_extension(NULL, p->context, p->exten, 1, p->cid_num) && (p->mfcr2_dnis_matched = 1))) && !ast_matchmore_extension(NULL, p->context, p->exten, 1, p->cid_num)) { return 0; } /* otherwise keep going */ return 1; } static void dahdi_r2_on_ani_digit_received(openr2_chan_t *r2chan, char digit) { struct dahdi_pvt *p = openr2_chan_get_client_data(r2chan); p->cid_num[p->mfcr2_ani_index] = digit; p->cid_name[p->mfcr2_ani_index] = digit; p->mfcr2_ani_index++; p->cid_num[p->mfcr2_ani_index] = 0; p->cid_name[p->mfcr2_ani_index] = 0; } static void dahdi_r2_on_billing_pulse_received(openr2_chan_t *r2chan) { ast_verbose("MFC/R2 billing pulse received on channel %d\n", openr2_chan_get_number(r2chan)); } static openr2_event_interface_t dahdi_r2_event_iface = { .on_call_init = dahdi_r2_on_call_init, .on_call_offered = dahdi_r2_on_call_offered, .on_call_accepted = dahdi_r2_on_call_accepted, .on_call_answered = dahdi_r2_on_call_answered, .on_call_disconnect = dahdi_r2_on_call_disconnect, .on_call_end = dahdi_r2_on_call_end, .on_call_read = dahdi_r2_on_call_read, .on_hardware_alarm = dahdi_r2_on_hardware_alarm, .on_os_error = dahdi_r2_on_os_error, .on_protocol_error = dahdi_r2_on_protocol_error, .on_line_blocked = dahdi_r2_on_line_blocked, .on_line_idle = dahdi_r2_on_line_idle, /* cast seems to be needed to get rid of the annoying warning regarding format attribute */ .on_context_log = (openr2_handle_context_logging_func)dahdi_r2_on_context_log, .on_dnis_digit_received = dahdi_r2_on_dnis_digit_received, .on_ani_digit_received = dahdi_r2_on_ani_digit_received, /* so far we do nothing with billing pulses */ .on_billing_pulse_received = dahdi_r2_on_billing_pulse_received }; static inline int16_t dahdi_r2_alaw_to_linear(uint8_t sample) { return AST_ALAW(sample); } static inline uint8_t dahdi_r2_linear_to_alaw(int sample) { return AST_LIN2A(sample); } static openr2_transcoder_interface_t dahdi_r2_transcode_iface = { dahdi_r2_alaw_to_linear, dahdi_r2_linear_to_alaw }; #endif /* HAVE_OPENR2 */ static void swap_subs(struct dahdi_pvt *p, int a, int b) { int tchan; int tinthreeway; struct ast_channel *towner; ast_debug(1, "Swapping %d and %d\n", a, b); tchan = p->subs[a].chan; towner = p->subs[a].owner; tinthreeway = p->subs[a].inthreeway; p->subs[a].chan = p->subs[b].chan; p->subs[a].owner = p->subs[b].owner; p->subs[a].inthreeway = p->subs[b].inthreeway; p->subs[b].chan = tchan; p->subs[b].owner = towner; p->subs[b].inthreeway = tinthreeway; if (p->subs[a].owner) ast_channel_set_fd(p->subs[a].owner, 0, p->subs[a].dfd); if (p->subs[b].owner) ast_channel_set_fd(p->subs[b].owner, 0, p->subs[b].dfd); wakeup_sub(p, a); wakeup_sub(p, b); } static int dahdi_open(char *fn) { int fd; int isnum; int chan = 0; int bs; int x; isnum = 1; for (x = 0; x < strlen(fn); x++) { if (!isdigit(fn[x])) { isnum = 0; break; } } if (isnum) { chan = atoi(fn); if (chan < 1) { ast_log(LOG_WARNING, "Invalid channel number '%s'\n", fn); return -1; } fn = "/dev/dahdi/channel"; } fd = open(fn, O_RDWR | O_NONBLOCK); if (fd < 0) { ast_log(LOG_WARNING, "Unable to open '%s': %s\n", fn, strerror(errno)); return -1; } if (chan) { if (ioctl(fd, DAHDI_SPECIFY, &chan)) { x = errno; close(fd); errno = x; ast_log(LOG_WARNING, "Unable to specify channel %d: %s\n", chan, strerror(errno)); return -1; } } bs = READ_SIZE; if (ioctl(fd, DAHDI_SET_BLOCKSIZE, &bs) == -1) { ast_log(LOG_WARNING, "Unable to set blocksize '%d': %s\n", bs, strerror(errno)); x = errno; close(fd); errno = x; return -1; } return fd; } static void dahdi_close(int fd) { if (fd > 0) close(fd); } static void dahdi_close_sub(struct dahdi_pvt *chan_pvt, int sub_num) { dahdi_close(chan_pvt->subs[sub_num].dfd); chan_pvt->subs[sub_num].dfd = -1; } #if defined(HAVE_PRI) static void dahdi_close_pri_fd(struct dahdi_pri *pri, int fd_num) { dahdi_close(pri->pri.fds[fd_num]); pri->pri.fds[fd_num] = -1; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) static void dahdi_close_ss7_fd(struct dahdi_ss7 *ss7, int fd_num) { dahdi_close(ss7->ss7.fds[fd_num]); ss7->ss7.fds[fd_num] = -1; } #endif /* defined(HAVE_SS7) */ static int dahdi_setlinear(int dfd, int linear) { return ioctl(dfd, DAHDI_SETLINEAR, &linear); } static int alloc_sub(struct dahdi_pvt *p, int x) { struct dahdi_bufferinfo bi; int res; if (p->subs[x].dfd >= 0) { ast_log(LOG_WARNING, "%s subchannel of %d already in use\n", subnames[x], p->channel); return -1; } p->subs[x].dfd = dahdi_open("/dev/dahdi/pseudo"); if (p->subs[x].dfd <= -1) { ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno)); return -1; } res = ioctl(p->subs[x].dfd, DAHDI_GET_BUFINFO, &bi); if (!res) { bi.txbufpolicy = p->buf_policy; bi.rxbufpolicy = p->buf_policy; bi.numbufs = p->buf_no; res = ioctl(p->subs[x].dfd, DAHDI_SET_BUFINFO, &bi); if (res < 0) { ast_log(LOG_WARNING, "Unable to set buffer policy on channel %d: %s\n", x, strerror(errno)); } } else ast_log(LOG_WARNING, "Unable to check buffer policy on channel %d: %s\n", x, strerror(errno)); if (ioctl(p->subs[x].dfd, DAHDI_CHANNO, &p->subs[x].chan) == 1) { ast_log(LOG_WARNING, "Unable to get channel number for pseudo channel on FD %d: %s\n", p->subs[x].dfd, strerror(errno)); dahdi_close_sub(p, x); p->subs[x].dfd = -1; return -1; } ast_debug(1, "Allocated %s subchannel on FD %d channel %d\n", subnames[x], p->subs[x].dfd, p->subs[x].chan); return 0; } static int unalloc_sub(struct dahdi_pvt *p, int x) { if (!x) { ast_log(LOG_WARNING, "Trying to unalloc the real channel %d?!?\n", p->channel); return -1; } ast_debug(1, "Released sub %d of channel %d\n", x, p->channel); dahdi_close_sub(p, x); p->subs[x].linear = 0; p->subs[x].chan = 0; p->subs[x].owner = NULL; p->subs[x].inthreeway = 0; p->polarity = POLARITY_IDLE; memset(&p->subs[x].curconf, 0, sizeof(p->subs[x].curconf)); return 0; } static int digit_to_dtmfindex(char digit) { if (isdigit(digit)) return DAHDI_TONE_DTMF_BASE + (digit - '0'); else if (digit >= 'A' && digit <= 'D') return DAHDI_TONE_DTMF_A + (digit - 'A'); else if (digit >= 'a' && digit <= 'd') return DAHDI_TONE_DTMF_A + (digit - 'a'); else if (digit == '*') return DAHDI_TONE_DTMF_s; else if (digit == '#') return DAHDI_TONE_DTMF_p; else return -1; } static int dahdi_digit_begin(struct ast_channel *chan, char digit) { struct dahdi_pvt *pvt; int idx; int dtmf = -1; int res; pvt = ast_channel_tech_pvt(chan); ast_mutex_lock(&pvt->lock); idx = dahdi_get_index(chan, pvt, 0); if ((idx != SUB_REAL) || !pvt->owner) goto out; #ifdef HAVE_PRI switch (pvt->sig) { case SIG_PRI_LIB_HANDLE_CASES: res = sig_pri_digit_begin(pvt->sig_pvt, chan, digit); if (!res) goto out; break; default: break; } #endif if ((dtmf = digit_to_dtmfindex(digit)) == -1) goto out; if (pvt->pulse || ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_SENDTONE, &dtmf)) { char dial_str[] = { 'T', digit, '\0' }; res = dahdi_dial_str(pvt, DAHDI_DIAL_OP_APPEND, dial_str); if (!res) { pvt->dialing = 1; } } else { ast_debug(1, "Channel %s started VLDTMF digit '%c'\n", ast_channel_name(chan), digit); pvt->dialing = 1; pvt->begindigit = digit; } out: ast_mutex_unlock(&pvt->lock); return 0; } static int dahdi_digit_end(struct ast_channel *chan, char digit, unsigned int duration) { struct dahdi_pvt *pvt; int res = 0; int idx; int x; pvt = ast_channel_tech_pvt(chan); ast_mutex_lock(&pvt->lock); idx = dahdi_get_index(chan, pvt, 0); if ((idx != SUB_REAL) || !pvt->owner || pvt->pulse) goto out; #ifdef HAVE_PRI /* This means that the digit was already sent via PRI signalling */ if (dahdi_sig_pri_lib_handles(pvt->sig) && !pvt->begindigit) { goto out; } #endif if (pvt->begindigit) { x = -1; ast_debug(1, "Channel %s ending VLDTMF digit '%c'\n", ast_channel_name(chan), digit); res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_SENDTONE, &x); pvt->dialing = 0; pvt->begindigit = 0; } out: ast_mutex_unlock(&pvt->lock); return res; } static const char * const events[] = { "No event", "On hook", "Ring/Answered", "Wink/Flash", "Alarm", "No more alarm", "HDLC Abort", "HDLC Overrun", "HDLC Bad FCS", "Dial Complete", "Ringer On", "Ringer Off", "Hook Transition Complete", "Bits Changed", "Pulse Start", "Timer Expired", "Timer Ping", "Polarity Reversal", "Ring Begin", }; static struct { int alarm; char *name; } alarms[] = { { DAHDI_ALARM_RED, "Red Alarm" }, { DAHDI_ALARM_YELLOW, "Yellow Alarm" }, { DAHDI_ALARM_BLUE, "Blue Alarm" }, { DAHDI_ALARM_RECOVER, "Recovering" }, { DAHDI_ALARM_LOOPBACK, "Loopback" }, { DAHDI_ALARM_NOTOPEN, "Not Open" }, { DAHDI_ALARM_NONE, "None" }, }; static char *alarm2str(int alm) { int x; for (x = 0; x < ARRAY_LEN(alarms); x++) { if (alarms[x].alarm & alm) return alarms[x].name; } return alm ? "Unknown Alarm" : "No Alarm"; } static const char *event2str(int event) { static char buf[256]; if ((event < (ARRAY_LEN(events))) && (event > -1)) return events[event]; sprintf(buf, "Event %d", event); /* safe */ return buf; } static char *dahdi_sig2str(int sig) { static char buf[256]; switch (sig) { case SIG_EM: return "E & M Immediate"; case SIG_EMWINK: return "E & M Wink"; case SIG_EM_E1: return "E & M E1"; case SIG_FEATD: return "Feature Group D (DTMF)"; case SIG_FEATDMF: return "Feature Group D (MF)"; case SIG_FEATDMF_TA: return "Feature Groud D (MF) Tandem Access"; case SIG_FEATB: return "Feature Group B (MF)"; case SIG_E911: return "E911 (MF)"; case SIG_FGC_CAMA: return "FGC/CAMA (Dialpulse)"; case SIG_FGC_CAMAMF: return "FGC/CAMA (MF)"; case SIG_FXSLS: return "FXS Loopstart"; case SIG_FXSGS: return "FXS Groundstart"; case SIG_FXSKS: return "FXS Kewlstart"; case SIG_FXOLS: return "FXO Loopstart"; case SIG_FXOGS: return "FXO Groundstart"; case SIG_FXOKS: return "FXO Kewlstart"; case SIG_PRI: return "ISDN PRI"; case SIG_BRI: return "ISDN BRI Point to Point"; case SIG_BRI_PTMP: return "ISDN BRI Point to MultiPoint"; case SIG_SS7: return "SS7"; case SIG_MFCR2: return "MFC/R2"; case SIG_SF: return "SF (Tone) Immediate"; case SIG_SFWINK: return "SF (Tone) Wink"; case SIG_SF_FEATD: return "SF (Tone) with Feature Group D (DTMF)"; case SIG_SF_FEATDMF: return "SF (Tone) with Feature Group D (MF)"; case SIG_SF_FEATB: return "SF (Tone) with Feature Group B (MF)"; case 0: return "Pseudo"; default: snprintf(buf, sizeof(buf), "Unknown signalling %d", sig); return buf; } } #define sig2str dahdi_sig2str static int conf_add(struct dahdi_pvt *p, struct dahdi_subchannel *c, int idx, int slavechannel) { /* If the conference already exists, and we're already in it don't bother doing anything */ struct dahdi_confinfo zi; memset(&zi, 0, sizeof(zi)); zi.chan = 0; if (slavechannel > 0) { /* If we have only one slave, do a digital mon */ zi.confmode = DAHDI_CONF_DIGITALMON; zi.confno = slavechannel; } else { if (!idx) { /* Real-side and pseudo-side both participate in conference */ zi.confmode = DAHDI_CONF_REALANDPSEUDO | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER | DAHDI_CONF_PSEUDO_TALKER | DAHDI_CONF_PSEUDO_LISTENER; } else zi.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER; zi.confno = p->confno; } if ((zi.confno == c->curconf.confno) && (zi.confmode == c->curconf.confmode)) return 0; if (c->dfd < 0) return 0; if (ioctl(c->dfd, DAHDI_SETCONF, &zi)) { ast_log(LOG_WARNING, "Failed to add %d to conference %d/%d: %s\n", c->dfd, zi.confmode, zi.confno, strerror(errno)); return -1; } if (slavechannel < 1) { p->confno = zi.confno; } c->curconf = zi; ast_debug(1, "Added %d to conference %d/%d\n", c->dfd, c->curconf.confmode, c->curconf.confno); return 0; } static int isourconf(struct dahdi_pvt *p, struct dahdi_subchannel *c) { /* If they're listening to our channel, they're ours */ if ((p->channel == c->curconf.confno) && (c->curconf.confmode == DAHDI_CONF_DIGITALMON)) return 1; /* If they're a talker on our (allocated) conference, they're ours */ if ((p->confno > 0) && (p->confno == c->curconf.confno) && (c->curconf.confmode & DAHDI_CONF_TALKER)) return 1; return 0; } static int conf_del(struct dahdi_pvt *p, struct dahdi_subchannel *c, int idx) { struct dahdi_confinfo zi; if (/* Can't delete if there's no dfd */ (c->dfd < 0) || /* Don't delete from the conference if it's not our conference */ !isourconf(p, c) /* Don't delete if we don't think it's conferenced at all (implied) */ ) return 0; memset(&zi, 0, sizeof(zi)); if (ioctl(c->dfd, DAHDI_SETCONF, &zi)) { ast_log(LOG_WARNING, "Failed to drop %d from conference %d/%d: %s\n", c->dfd, c->curconf.confmode, c->curconf.confno, strerror(errno)); return -1; } ast_debug(1, "Removed %d from conference %d/%d\n", c->dfd, c->curconf.confmode, c->curconf.confno); memcpy(&c->curconf, &zi, sizeof(c->curconf)); return 0; } static int isslavenative(struct dahdi_pvt *p, struct dahdi_pvt **out) { int x; int useslavenative; struct dahdi_pvt *slave = NULL; /* Start out optimistic */ useslavenative = 1; /* Update conference state in a stateless fashion */ for (x = 0; x < 3; x++) { /* Any three-way calling makes slave native mode *definitely* out of the question */ if ((p->subs[x].dfd > -1) && p->subs[x].inthreeway) useslavenative = 0; } /* If we don't have any 3-way calls, check to see if we have precisely one slave */ if (useslavenative) { for (x = 0; x < MAX_SLAVES; x++) { if (p->slaves[x]) { if (slave) { /* Whoops already have a slave! No slave native and stop right away */ slave = NULL; useslavenative = 0; break; } else { /* We have one slave so far */ slave = p->slaves[x]; } } } } /* If no slave, slave native definitely out */ if (!slave) useslavenative = 0; else if (slave->law != p->law) { useslavenative = 0; slave = NULL; } if (out) *out = slave; return useslavenative; } static int reset_conf(struct dahdi_pvt *p) { p->confno = -1; memset(&p->subs[SUB_REAL].curconf, 0, sizeof(p->subs[SUB_REAL].curconf)); if (p->subs[SUB_REAL].dfd > -1) { struct dahdi_confinfo zi; memset(&zi, 0, sizeof(zi)); if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCONF, &zi)) ast_log(LOG_WARNING, "Failed to reset conferencing on channel %d: %s\n", p->channel, strerror(errno)); } return 0; } void dahdi_conf_update(struct dahdi_pvt *p) { int needconf = 0; int x; int useslavenative; struct dahdi_pvt *slave = NULL; useslavenative = isslavenative(p, &slave); /* Start with the obvious, general stuff */ for (x = 0; x < 3; x++) { /* Look for three way calls */ if ((p->subs[x].dfd > -1) && p->subs[x].inthreeway) { conf_add(p, &p->subs[x], x, 0); needconf++; } else { conf_del(p, &p->subs[x], x); } } /* If we have a slave, add him to our conference now. or DAX if this is slave native */ for (x = 0; x < MAX_SLAVES; x++) { if (p->slaves[x]) { if (useslavenative) conf_add(p, &p->slaves[x]->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(p)); else { conf_add(p, &p->slaves[x]->subs[SUB_REAL], SUB_REAL, 0); needconf++; } } } /* If we're supposed to be in there, do so now */ if (p->inconference && !p->subs[SUB_REAL].inthreeway) { if (useslavenative) conf_add(p, &p->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(slave)); else { conf_add(p, &p->subs[SUB_REAL], SUB_REAL, 0); needconf++; } } /* If we have a master, add ourselves to his conference */ if (p->master) { if (isslavenative(p->master, NULL)) { conf_add(p->master, &p->subs[SUB_REAL], SUB_REAL, GET_CHANNEL(p->master)); } else { conf_add(p->master, &p->subs[SUB_REAL], SUB_REAL, 0); } } if (!needconf) { /* Nobody is left (or should be left) in our conference. Kill it. */ p->confno = -1; } ast_debug(1, "Updated conferencing on %d, with %d conference users\n", p->channel, needconf); } void dahdi_ec_enable(struct dahdi_pvt *p) { int res; if (!p) return; if (p->echocanon) { ast_debug(1, "Echo cancellation already on\n"); return; } if (p->digital) { ast_debug(1, "Echo cancellation isn't required on digital connection\n"); return; } if (p->echocancel.head.tap_length) { #if defined(HAVE_PRI) || defined(HAVE_SS7) switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: if (((struct sig_pri_chan *) p->sig_pvt)->no_b_channel) { /* * PRI nobch pseudo channel. Does not need ec anyway. * Does not handle ioctl(DAHDI_AUDIOMODE) */ return; } /* Fall through */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: #endif /* defined(HAVE_SS7) */ { int x = 1; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &x); if (res) ast_log(LOG_WARNING, "Unable to enable audio mode on channel %d (%s)\n", p->channel, strerror(errno)); } break; default: break; } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOCANCEL_PARAMS, &p->echocancel); if (res) { ast_log(LOG_WARNING, "Unable to enable echo cancellation on channel %d (%s)\n", p->channel, strerror(errno)); } else { p->echocanon = 1; ast_debug(1, "Enabled echo cancellation on channel %d\n", p->channel); } } else ast_debug(1, "No echo cancellation requested\n"); } static void dahdi_train_ec(struct dahdi_pvt *p) { int x; int res; if (p && p->echocanon && p->echotraining) { x = p->echotraining; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOTRAIN, &x); if (res) ast_log(LOG_WARNING, "Unable to request echo training on channel %d: %s\n", p->channel, strerror(errno)); else ast_debug(1, "Engaged echo training on channel %d\n", p->channel); } else { ast_debug(1, "No echo training requested\n"); } } void dahdi_ec_disable(struct dahdi_pvt *p) { int res; if (p->echocanon) { struct dahdi_echocanparams ecp = { .tap_length = 0 }; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOCANCEL_PARAMS, &ecp); if (res) ast_log(LOG_WARNING, "Unable to disable echo cancellation on channel %d: %s\n", p->channel, strerror(errno)); else ast_debug(1, "Disabled echo cancellation on channel %d\n", p->channel); } p->echocanon = 0; } static int set_hwgain(int fd, float gain, int tx_direction) { struct dahdi_hwgain hwgain; hwgain.newgain = gain * 10.0; hwgain.tx = tx_direction; return ioctl(fd, DAHDI_SET_HWGAIN, &hwgain) < 0; } /* perform a dynamic range compression transform on the given sample */ static int drc_sample(int sample, float drc) { float neg; float shallow, steep; float max = SHRT_MAX; neg = (sample < 0 ? -1 : 1); steep = drc*sample; shallow = neg*(max-max/drc)+(float)sample/drc; if (abs(steep) < abs(shallow)) { sample = steep; } else { sample = shallow; } return sample; } static void fill_txgain(struct dahdi_gains *g, float gain, float drc, int law) { int j; int k; float linear_gain = pow(10.0, gain / 20.0); switch (law) { case DAHDI_LAW_ALAW: for (j = 0; j < ARRAY_LEN(g->txgain); j++) { if (gain || drc) { k = AST_ALAW(j); if (drc) { k = drc_sample(k, drc); } k = (float)k * linear_gain; if (k > 32767) { k = 32767; } else if (k < -32768) { k = -32768; } g->txgain[j] = AST_LIN2A(k); } else { g->txgain[j] = j; } } break; case DAHDI_LAW_MULAW: for (j = 0; j < ARRAY_LEN(g->txgain); j++) { if (gain || drc) { k = AST_MULAW(j); if (drc) { k = drc_sample(k, drc); } k = (float)k * linear_gain; if (k > 32767) { k = 32767; } else if (k < -32768) { k = -32768; } g->txgain[j] = AST_LIN2MU(k); } else { g->txgain[j] = j; } } break; } } static void fill_rxgain(struct dahdi_gains *g, float gain, float drc, int law) { int j; int k; float linear_gain = pow(10.0, gain / 20.0); switch (law) { case DAHDI_LAW_ALAW: for (j = 0; j < ARRAY_LEN(g->rxgain); j++) { if (gain || drc) { k = AST_ALAW(j); if (drc) { k = drc_sample(k, drc); } k = (float)k * linear_gain; if (k > 32767) { k = 32767; } else if (k < -32768) { k = -32768; } g->rxgain[j] = AST_LIN2A(k); } else { g->rxgain[j] = j; } } break; case DAHDI_LAW_MULAW: for (j = 0; j < ARRAY_LEN(g->rxgain); j++) { if (gain || drc) { k = AST_MULAW(j); if (drc) { k = drc_sample(k, drc); } k = (float)k * linear_gain; if (k > 32767) { k = 32767; } else if (k < -32768) { k = -32768; } g->rxgain[j] = AST_LIN2MU(k); } else { g->rxgain[j] = j; } } break; } } static int set_actual_txgain(int fd, float gain, float drc, int law) { struct dahdi_gains g; int res; memset(&g, 0, sizeof(g)); res = ioctl(fd, DAHDI_GETGAINS, &g); if (res) { ast_debug(1, "Failed to read gains: %s\n", strerror(errno)); return res; } fill_txgain(&g, gain, drc, law); return ioctl(fd, DAHDI_SETGAINS, &g); } static int set_actual_rxgain(int fd, float gain, float drc, int law) { struct dahdi_gains g; int res; memset(&g, 0, sizeof(g)); res = ioctl(fd, DAHDI_GETGAINS, &g); if (res) { ast_debug(1, "Failed to read gains: %s\n", strerror(errno)); return res; } fill_rxgain(&g, gain, drc, law); return ioctl(fd, DAHDI_SETGAINS, &g); } static int set_actual_gain(int fd, float rxgain, float txgain, float rxdrc, float txdrc, int law) { return set_actual_txgain(fd, txgain, txdrc, law) | set_actual_rxgain(fd, rxgain, rxdrc, law); } static int bump_gains(struct dahdi_pvt *p) { int res; /* Bump receive gain by value stored in cid_rxgain */ res = set_actual_gain(p->subs[SUB_REAL].dfd, p->rxgain + p->cid_rxgain, p->txgain, p->rxdrc, p->txdrc, p->law); if (res) { ast_log(LOG_WARNING, "Unable to bump gain: %s\n", strerror(errno)); return -1; } return 0; } static int restore_gains(struct dahdi_pvt *p) { int res; res = set_actual_gain(p->subs[SUB_REAL].dfd, p->rxgain, p->txgain, p->rxdrc, p->txdrc, p->law); if (res) { ast_log(LOG_WARNING, "Unable to restore gains: %s\n", strerror(errno)); return -1; } return 0; } static inline int dahdi_set_hook(int fd, int hs) { int x, res; x = hs; res = ioctl(fd, DAHDI_HOOK, &x); if (res < 0) { if (errno == EINPROGRESS) return 0; ast_log(LOG_WARNING, "DAHDI hook failed returned %d (trying %d): %s\n", res, hs, strerror(errno)); /* will expectedly fail if phone is off hook during operation, such as during a restart */ } return res; } static inline int dahdi_confmute(struct dahdi_pvt *p, int muted) { int x, res; x = muted; #if defined(HAVE_PRI) || defined(HAVE_SS7) switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: if (((struct sig_pri_chan *) p->sig_pvt)->no_b_channel) { /* PRI nobch pseudo channel. Does not handle ioctl(DAHDI_AUDIOMODE) */ break; } /* Fall through */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: #endif /* defined(HAVE_SS7) */ { int y = 1; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &y); if (res) ast_log(LOG_WARNING, "Unable to set audio mode on %d: %s\n", p->channel, strerror(errno)); } break; default: break; } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_CONFMUTE, &x); if (res < 0) ast_log(LOG_WARNING, "DAHDI confmute(%d) failed on channel %d: %s\n", muted, p->channel, strerror(errno)); return res; } static int save_conference(struct dahdi_pvt *p) { struct dahdi_confinfo c; int res; if (p->saveconf.confmode) { ast_log(LOG_WARNING, "Can't save conference -- already in use\n"); return -1; } p->saveconf.chan = 0; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GETCONF, &p->saveconf); if (res) { ast_log(LOG_WARNING, "Unable to get conference info: %s\n", strerror(errno)); p->saveconf.confmode = 0; return -1; } memset(&c, 0, sizeof(c)); c.confmode = DAHDI_CONF_NORMAL; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCONF, &c); if (res) { ast_log(LOG_WARNING, "Unable to set conference info: %s\n", strerror(errno)); return -1; } ast_debug(1, "Disabled conferencing\n"); return 0; } static int restore_conference(struct dahdi_pvt *p) { int res; if (p->saveconf.confmode) { res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETCONF, &p->saveconf); p->saveconf.confmode = 0; if (res) { ast_log(LOG_WARNING, "Unable to restore conference info: %s\n", strerror(errno)); return -1; } ast_debug(1, "Restored conferencing\n"); } return 0; } static int send_cwcidspill(struct dahdi_pvt *p) { p->callwaitcas = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; if (!(p->cidspill = ast_malloc(MAX_CALLERID_SIZE))) return -1; p->cidlen = ast_callerid_callwaiting_generate(p->cidspill, p->callwait_name, p->callwait_num, AST_LAW(p)); /* Make sure we account for the end */ p->cidlen += READ_SIZE * 4; p->cidpos = 0; send_callerid(p); ast_verb(3, "CPE supports Call Waiting Caller*ID. Sending '%s/%s'\n", p->callwait_name, p->callwait_num); return 0; } static int has_voicemail(struct dahdi_pvt *p) { int new_msgs; RAII_VAR(struct stasis_message *, mwi_message, NULL, ao2_cleanup); mwi_message = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), p->mailbox); if (mwi_message) { struct ast_mwi_state *mwi_state = stasis_message_data(mwi_message); new_msgs = mwi_state->new_msgs; } else { new_msgs = ast_app_has_voicemail(p->mailbox, NULL); } return new_msgs; } static int send_callerid(struct dahdi_pvt *p) { /* Assumes spill in p->cidspill, p->cidlen in length and we're p->cidpos into it */ int res; /* Take out of linear mode if necessary */ if (p->subs[SUB_REAL].linear) { p->subs[SUB_REAL].linear = 0; dahdi_setlinear(p->subs[SUB_REAL].dfd, 0); } while (p->cidpos < p->cidlen) { res = write(p->subs[SUB_REAL].dfd, p->cidspill + p->cidpos, p->cidlen - p->cidpos); ast_debug(4, "writing callerid at pos %d of %d, res = %d\n", p->cidpos, p->cidlen, res); if (res < 0) { if (errno == EAGAIN) return 0; else { ast_log(LOG_WARNING, "write failed: %s\n", strerror(errno)); return -1; } } if (!res) return 0; p->cidpos += res; } p->cid_suppress_expire = CALLWAITING_SUPPRESS_SAMPLES; ast_free(p->cidspill); p->cidspill = NULL; if (p->callwaitcas) { /* Wait for CID/CW to expire */ p->cidcwexpire = CIDCW_EXPIRE_SAMPLES; p->cid_suppress_expire = p->cidcwexpire; } else restore_conference(p); return 0; } static int dahdi_callwait(struct ast_channel *ast) { struct dahdi_pvt *p = ast_channel_tech_pvt(ast); p->callwaitingrepeat = CALLWAITING_REPEAT_SAMPLES; if (p->cidspill) { ast_log(LOG_WARNING, "Spill already exists?!?\n"); ast_free(p->cidspill); } /* * SAS: Subscriber Alert Signal, 440Hz for 300ms * CAS: CPE Alert Signal, 2130Hz * 2750Hz sine waves */ if (!(p->cidspill = ast_malloc(2400 /* SAS */ + 680 /* CAS */ + READ_SIZE * 4))) return -1; save_conference(p); /* Silence */ memset(p->cidspill, 0x7f, 2400 + 600 + READ_SIZE * 4); if (!p->callwaitrings && p->callwaitingcallerid) { ast_gen_cas(p->cidspill, 1, 2400 + 680, AST_LAW(p)); p->callwaitcas = 1; p->cidlen = 2400 + 680 + READ_SIZE * 4; } else { ast_gen_cas(p->cidspill, 1, 2400, AST_LAW(p)); p->callwaitcas = 0; p->cidlen = 2400 + READ_SIZE * 4; } p->cidpos = 0; send_callerid(p); return 0; } static int dahdi_call(struct ast_channel *ast, const char *rdest, int timeout) { struct dahdi_pvt *p = ast_channel_tech_pvt(ast); int x, res, mysig; char *dest; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(group); /* channel/group token */ AST_APP_ARG(ext); /* extension token */ //AST_APP_ARG(opts); /* options token */ AST_APP_ARG(other); /* Any remining unused arguments */ ); ast_mutex_lock(&p->lock); ast_copy_string(p->dialdest, rdest, sizeof(p->dialdest)); /* Split the dialstring */ dest = ast_strdupa(rdest); AST_NONSTANDARD_APP_ARGS(args, dest, '/'); if (!args.ext) { args.ext = ""; } #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig)) { char *subaddr; sig_pri_extract_called_num_subaddr(p->sig_pvt, rdest, p->exten, sizeof(p->exten)); /* Remove any subaddress for uniformity with incoming calls. */ subaddr = strchr(p->exten, ':'); if (subaddr) { *subaddr = '\0'; } } else #endif /* defined(HAVE_PRI) */ { ast_copy_string(p->exten, args.ext, sizeof(p->exten)); } if ((ast_channel_state(ast) == AST_STATE_BUSY)) { p->subs[SUB_REAL].needbusy = 1; ast_mutex_unlock(&p->lock); return 0; } if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "dahdi_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); ast_mutex_unlock(&p->lock); return -1; } p->waitingfordt.tv_sec = 0; p->dialednone = 0; if ((p->radio || (p->oprmode < 0))) /* if a radio channel, up immediately */ { /* Special pseudo -- automatically up */ ast_setstate(ast, AST_STATE_UP); ast_mutex_unlock(&p->lock); return 0; } x = DAHDI_FLUSH_READ | DAHDI_FLUSH_WRITE; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_FLUSH, &x); if (res) ast_log(LOG_WARNING, "Unable to flush input on channel %d: %s\n", p->channel, strerror(errno)); p->outgoing = 1; if (IS_DIGITAL(ast_channel_transfercapability(ast))){ set_actual_gain(p->subs[SUB_REAL].dfd, 0, 0, p->rxdrc, p->txdrc, p->law); } else { set_actual_gain(p->subs[SUB_REAL].dfd, p->rxgain, p->txgain, p->rxdrc, p->txdrc, p->law); } #ifdef HAVE_PRI if (dahdi_sig_pri_lib_handles(p->sig)) { res = sig_pri_call(p->sig_pvt, ast, rdest, timeout, (p->law == DAHDI_LAW_ALAW) ? PRI_LAYER_1_ALAW : PRI_LAYER_1_ULAW); ast_mutex_unlock(&p->lock); return res; } #endif #if defined(HAVE_SS7) if (p->sig == SIG_SS7) { res = sig_ss7_call(p->sig_pvt, ast, rdest); ast_mutex_unlock(&p->lock); return res; } #endif /* defined(HAVE_SS7) */ /* If this is analog signalling we can exit here */ if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { p->callwaitrings = 0; res = analog_call(p->sig_pvt, ast, rdest, timeout); ast_mutex_unlock(&p->lock); return res; } mysig = p->outsigmod > -1 ? p->outsigmod : p->sig; switch (mysig) { case 0: /* Special pseudo -- automatically up*/ ast_setstate(ast, AST_STATE_UP); break; case SIG_MFCR2: break; default: ast_debug(1, "not yet implemented\n"); ast_mutex_unlock(&p->lock); return -1; } #ifdef HAVE_OPENR2 if (p->mfcr2) { openr2_calling_party_category_t chancat; int callres = 0; char *c, *l; /* We'll get it in a moment -- but use dialdest to store pre-setup_ack digits */ p->dialdest[0] = '\0'; c = args.ext; if (!p->hidecallerid) { l = ast_channel_connected(ast)->id.number.valid ? ast_channel_connected(ast)->id.number.str : NULL; } else { l = NULL; } if (strlen(c) < p->stripmsd) { ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd); ast_mutex_unlock(&p->lock); return -1; } p->dialing = 1; chancat = dahdi_r2_get_channel_category(ast); callres = openr2_chan_make_call(p->r2chan, l, (c + p->stripmsd), chancat); if (-1 == callres) { ast_mutex_unlock(&p->lock); ast_log(LOG_ERROR, "unable to make new MFC/R2 call!\n"); return -1; } p->mfcr2_call_accepted = 0; p->mfcr2_progress_sent = 0; ast_setstate(ast, AST_STATE_DIALING); } #endif /* HAVE_OPENR2 */ ast_mutex_unlock(&p->lock); return 0; } /*! * \internal * \brief Insert the given chan_dahdi interface structure into the interface list. * \since 1.8 * * \param pvt chan_dahdi private interface structure to insert. * * \details * The interface list is a doubly linked list sorted by the chan_dahdi channel number. * Any duplicates are inserted after the existing entries. * * \note The new interface must not already be in the list. * * \return Nothing */ static void dahdi_iflist_insert(struct dahdi_pvt *pvt) { struct dahdi_pvt *cur; pvt->which_iflist = DAHDI_IFLIST_MAIN; /* Find place in middle of list for the new interface. */ for (cur = iflist; cur; cur = cur->next) { if (pvt->channel < cur->channel) { /* New interface goes before the current interface. */ pvt->prev = cur->prev; pvt->next = cur; if (cur->prev) { /* Insert into the middle of the list. */ cur->prev->next = pvt; } else { /* Insert at head of list. */ iflist = pvt; } cur->prev = pvt; return; } } /* New interface goes onto the end of the list */ pvt->prev = ifend; pvt->next = NULL; if (ifend) { ifend->next = pvt; } ifend = pvt; if (!iflist) { /* List was empty */ iflist = pvt; } } /*! * \internal * \brief Extract the given chan_dahdi interface structure from the interface list. * \since 1.8 * * \param pvt chan_dahdi private interface structure to extract. * * \note * The given interface structure can be either in the interface list or a stand alone * structure that has not been put in the list if the next and prev pointers are NULL. * * \return Nothing */ static void dahdi_iflist_extract(struct dahdi_pvt *pvt) { /* Extract from the forward chain. */ if (pvt->prev) { pvt->prev->next = pvt->next; } else if (iflist == pvt) { /* Node is at the head of the list. */ iflist = pvt->next; } /* Extract from the reverse chain. */ if (pvt->next) { pvt->next->prev = pvt->prev; } else if (ifend == pvt) { /* Node is at the end of the list. */ ifend = pvt->prev; } /* Node is no longer in the list. */ pvt->which_iflist = DAHDI_IFLIST_NONE; pvt->prev = NULL; pvt->next = NULL; } #if defined(HAVE_PRI) /*! * \internal * \brief Insert the given chan_dahdi interface structure into the no B channel list. * \since 1.8 * * \param pri sig_pri span control structure holding no B channel list. * \param pvt chan_dahdi private interface structure to insert. * * \details * The interface list is a doubly linked list sorted by the chan_dahdi channel number. * Any duplicates are inserted after the existing entries. * * \note The new interface must not already be in the list. * * \return Nothing */ static void dahdi_nobch_insert(struct sig_pri_span *pri, struct dahdi_pvt *pvt) { struct dahdi_pvt *cur; pvt->which_iflist = DAHDI_IFLIST_NO_B_CHAN; /* Find place in middle of list for the new interface. */ for (cur = pri->no_b_chan_iflist; cur; cur = cur->next) { if (pvt->channel < cur->channel) { /* New interface goes before the current interface. */ pvt->prev = cur->prev; pvt->next = cur; if (cur->prev) { /* Insert into the middle of the list. */ cur->prev->next = pvt; } else { /* Insert at head of list. */ pri->no_b_chan_iflist = pvt; } cur->prev = pvt; return; } } /* New interface goes onto the end of the list */ pvt->prev = pri->no_b_chan_end; pvt->next = NULL; if (pri->no_b_chan_end) { ((struct dahdi_pvt *) pri->no_b_chan_end)->next = pvt; } pri->no_b_chan_end = pvt; if (!pri->no_b_chan_iflist) { /* List was empty */ pri->no_b_chan_iflist = pvt; } } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Extract the given chan_dahdi interface structure from the no B channel list. * \since 1.8 * * \param pri sig_pri span control structure holding no B channel list. * \param pvt chan_dahdi private interface structure to extract. * * \note * The given interface structure can be either in the interface list or a stand alone * structure that has not been put in the list if the next and prev pointers are NULL. * * \return Nothing */ static void dahdi_nobch_extract(struct sig_pri_span *pri, struct dahdi_pvt *pvt) { /* Extract from the forward chain. */ if (pvt->prev) { pvt->prev->next = pvt->next; } else if (pri->no_b_chan_iflist == pvt) { /* Node is at the head of the list. */ pri->no_b_chan_iflist = pvt->next; } /* Extract from the reverse chain. */ if (pvt->next) { pvt->next->prev = pvt->prev; } else if (pri->no_b_chan_end == pvt) { /* Node is at the end of the list. */ pri->no_b_chan_end = pvt->prev; } /* Node is no longer in the list. */ pvt->which_iflist = DAHDI_IFLIST_NONE; pvt->prev = NULL; pvt->next = NULL; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Unlink the channel interface from the PRI private pointer array. * \since 1.8 * * \param pvt chan_dahdi private interface structure to unlink. * * \return Nothing */ static void dahdi_unlink_pri_pvt(struct dahdi_pvt *pvt) { unsigned idx; struct sig_pri_span *pri; pri = pvt->pri; if (!pri) { /* Not PRI signaling so cannot be in a PRI private pointer array. */ return; } ast_mutex_lock(&pri->lock); for (idx = 0; idx < pri->numchans; ++idx) { if (pri->pvts[idx] == pvt->sig_pvt) { pri->pvts[idx] = NULL; ast_mutex_unlock(&pri->lock); return; } } ast_mutex_unlock(&pri->lock); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) /*! * \internal * \brief Unlink the channel interface from the SS7 private pointer array. * \since 1.8 * * \param pvt chan_dahdi private interface structure to unlink. * * \return Nothing */ static void dahdi_unlink_ss7_pvt(struct dahdi_pvt *pvt) { unsigned idx; struct sig_ss7_linkset *ss7; ss7 = pvt->ss7; if (!ss7) { /* Not SS7 signaling so cannot be in a SS7 private pointer array. */ return; } ast_mutex_lock(&ss7->lock); for (idx = 0; idx < ss7->numchans; ++idx) { if (ss7->pvts[idx] == pvt->sig_pvt) { ss7->pvts[idx] = NULL; ast_mutex_unlock(&ss7->lock); return; } } ast_mutex_unlock(&ss7->lock); } #endif /* defined(HAVE_SS7) */ static struct dahdi_pvt *find_next_iface_in_span(struct dahdi_pvt *cur) { if (cur->next && cur->next->span == cur->span) { return cur->next; } else if (cur->prev && cur->prev->span == cur->span) { return cur->prev; } return NULL; } static void destroy_dahdi_pvt(struct dahdi_pvt *pvt) { struct dahdi_pvt *p = pvt; if (p->manages_span_alarms) { struct dahdi_pvt *next = find_next_iface_in_span(p); if (next) { next->manages_span_alarms = 1; } } /* Remove channel from the list */ #if defined(HAVE_PRI) dahdi_unlink_pri_pvt(p); #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) dahdi_unlink_ss7_pvt(p); #endif /* defined(HAVE_SS7) */ switch (pvt->which_iflist) { case DAHDI_IFLIST_NONE: break; case DAHDI_IFLIST_MAIN: dahdi_iflist_extract(p); break; #if defined(HAVE_PRI) case DAHDI_IFLIST_NO_B_CHAN: if (p->pri) { dahdi_nobch_extract(p->pri, p); } break; #endif /* defined(HAVE_PRI) */ } if (p->sig_pvt) { if (dahdi_analog_lib_handles(p->sig, 0, 0)) { analog_delete(p->sig_pvt); } switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: sig_pri_chan_delete(p->sig_pvt); break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: sig_ss7_chan_delete(p->sig_pvt); break; #endif /* defined(HAVE_SS7) */ default: break; } } ast_free(p->cidspill); if (p->use_smdi) { ao2_cleanup(p->smdi_iface); } if (p->mwi_event_sub) { p->mwi_event_sub = stasis_unsubscribe(p->mwi_event_sub); } if (p->vars) { ast_variables_destroy(p->vars); } if (p->cc_params) { ast_cc_config_params_destroy(p->cc_params); } p->named_callgroups = ast_unref_namedgroups(p->named_callgroups); p->named_pickupgroups = ast_unref_namedgroups(p->named_pickupgroups); ast_mutex_destroy(&p->lock); dahdi_close_sub(p, SUB_REAL); if (p->owner) { ast_channel_tech_pvt_set(p->owner, NULL); } ast_free(p); } static void destroy_channel(struct dahdi_pvt *cur, int now) { int i; if (!now) { /* Do not destroy the channel now if it is owned by someone. */ if (cur->owner) { return; } for (i = 0; i < 3; i++) { if (cur->subs[i].owner) { return; } } } destroy_dahdi_pvt(cur); } static void destroy_all_channels(void) { int chan; #if defined(HAVE_PRI) unsigned span; struct sig_pri_span *pri; #endif /* defined(HAVE_PRI) */ struct dahdi_pvt *p; while (num_restart_pending) { usleep(1); } ast_mutex_lock(&iflock); /* Destroy all the interfaces and free their memory */ while (iflist) { p = iflist; chan = p->channel; #if defined(HAVE_PRI_SERVICE_MESSAGES) { char db_chan_name[20]; char db_answer[5]; char state; int why = -1; snprintf(db_chan_name, sizeof(db_chan_name), "%s/%d:%d", dahdi_db, p->span, chan); if (!ast_db_get(db_chan_name, SRVST_DBKEY, db_answer, sizeof(db_answer))) { sscanf(db_answer, "%1c:%30d", &state, &why); } if (!why) { /* SRVST persistence is not required */ ast_db_del(db_chan_name, SRVST_DBKEY); } } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ /* Free associated memory */ destroy_dahdi_pvt(p); ast_verb(3, "Unregistered channel %d\n", chan); } ifcount = 0; ast_mutex_unlock(&iflock); #if defined(HAVE_PRI) /* Destroy all of the no B channel interface lists */ for (span = 0; span < NUM_SPANS; ++span) { if (!pris[span].dchannels[0]) { break; } pri = &pris[span].pri; ast_mutex_lock(&pri->lock); while (pri->no_b_chan_iflist) { p = pri->no_b_chan_iflist; /* Free associated memory */ destroy_dahdi_pvt(p); } ast_mutex_unlock(&pri->lock); } #endif /* defined(HAVE_PRI) */ } #if defined(HAVE_PRI) static char *dahdi_send_keypad_facility_app = "DAHDISendKeypadFacility"; static int dahdi_send_keypad_facility_exec(struct ast_channel *chan, const char *digits) { /* Data will be our digit string */ struct dahdi_pvt *p; if (ast_strlen_zero(digits)) { ast_debug(1, "No digit string sent to application!\n"); return -1; } p = (struct dahdi_pvt *)ast_channel_tech_pvt(chan); if (!p) { ast_debug(1, "Unable to find technology private\n"); return -1; } pri_send_keypad_facility_exec(p->sig_pvt, digits); return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_PROG_W_CAUSE) static char *dahdi_send_callrerouting_facility_app = "DAHDISendCallreroutingFacility"; static int dahdi_send_callrerouting_facility_exec(struct ast_channel *chan, const char *data) { /* Data will be our digit string */ struct dahdi_pvt *pvt; char *parse; int res; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(destination); AST_APP_ARG(original); AST_APP_ARG(reason); ); if (ast_strlen_zero(data)) { ast_debug(1, "No data sent to application!\n"); return -1; } if (ast_channel_tech(chan) != &dahdi_tech) { ast_debug(1, "Only DAHDI technology accepted!\n"); return -1; } pvt = (struct dahdi_pvt *) ast_channel_tech_pvt(chan); if (!pvt) { ast_debug(1, "Unable to find technology private\n"); return -1; } switch (pvt->sig) { case SIG_PRI_LIB_HANDLE_CASES: break; default: ast_debug(1, "callrerouting attempted on non-ISDN channel %s\n", ast_channel_name(chan)); return -1; } parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); if (ast_strlen_zero(args.destination)) { ast_log(LOG_WARNING, "callrerouting facility requires at least destination number argument\n"); return -1; } if (ast_strlen_zero(args.original)) { ast_log(LOG_WARNING, "Callrerouting Facility without original called number argument\n"); args.original = NULL; } if (ast_strlen_zero(args.reason)) { ast_log(LOG_NOTICE, "Callrerouting Facility without diversion reason argument, defaulting to unknown\n"); args.reason = NULL; } res = pri_send_callrerouting_facility_exec(pvt->sig_pvt, ast_channel_state(chan), args.destination, args.original, args.reason); if (!res) { /* * Wait up to 5 seconds for a reply before hanging up this call * leg if the peer does not disconnect first. */ ast_safe_sleep(chan, 5000); } return -1; } #endif /* defined(HAVE_PRI_PROG_W_CAUSE) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_OPENR2) static const char * const dahdi_accept_r2_call_app = "DAHDIAcceptR2Call"; static int dahdi_accept_r2_call_exec(struct ast_channel *chan, const char *data) { /* data is whether to accept with charge or no charge */ openr2_call_mode_t accept_mode; int res, timeout, maxloops; struct ast_frame *f; struct dahdi_pvt *p; char *parse; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(charge); ); if (ast_strlen_zero(data)) { ast_debug(1, "No data sent to application!\n"); return -1; } if (ast_channel_tech(chan) != &dahdi_tech) { ast_debug(1, "Only DAHDI technology accepted!\n"); return -1; } p = (struct dahdi_pvt *)ast_channel_tech_pvt(chan); if (!p) { ast_debug(1, "Unable to find technology private!\n"); return -1; } parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); if (ast_strlen_zero(args.charge)) { ast_log(LOG_WARNING, "DAHDIAcceptR2Call requires 'yes' or 'no' for the charge parameter\n"); return -1; } ast_mutex_lock(&p->lock); if (!p->mfcr2 || !p->mfcr2call) { ast_mutex_unlock(&p->lock); ast_debug(1, "Channel %s does not seems to be an R2 active channel!\n", ast_channel_name(chan)); return -1; } if (p->mfcr2_call_accepted) { ast_mutex_unlock(&p->lock); ast_debug(1, "MFC/R2 call already accepted on channel %s!\n", ast_channel_name(chan)); return 0; } accept_mode = ast_true(args.charge) ? OR2_CALL_WITH_CHARGE : OR2_CALL_NO_CHARGE; if (openr2_chan_accept_call(p->r2chan, accept_mode)) { ast_mutex_unlock(&p->lock); ast_log(LOG_WARNING, "Failed to accept MFC/R2 call!\n"); return -1; } ast_mutex_unlock(&p->lock); res = 0; timeout = 100; maxloops = 50; /* wait up to 5 seconds */ /* we need to read() until the call is accepted */ while (maxloops > 0) { maxloops--; if (ast_check_hangup(chan)) { break; } res = ast_waitfor(chan, timeout); if (res < 0) { ast_debug(1, "ast_waitfor failed on channel %s, going out ...\n", ast_channel_name(chan)); res = -1; break; } if (res == 0) { continue; } res = 0; f = ast_read(chan); if (!f) { ast_debug(1, "No frame read on channel %s, going out ...\n", ast_channel_name(chan)); res = -1; break; } if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_HANGUP) { ast_debug(1, "Got HANGUP frame on channel %s, going out ...\n", ast_channel_name(chan)); ast_frfree(f); res = -1; break; } ast_frfree(f); ast_mutex_lock(&p->lock); if (p->mfcr2_call_accepted) { ast_mutex_unlock(&p->lock); ast_debug(1, "Accepted MFC/R2 call!\n"); break; } ast_mutex_unlock(&p->lock); } if (res == -1) { ast_log(LOG_WARNING, "Failed to accept MFC/R2 call!\n"); } return res; } static openr2_call_disconnect_cause_t dahdi_ast_cause_to_r2_cause(int cause) { openr2_call_disconnect_cause_t r2cause = OR2_CAUSE_NORMAL_CLEARING; switch (cause) { case AST_CAUSE_USER_BUSY: case AST_CAUSE_CALL_REJECTED: case AST_CAUSE_INTERWORKING: /* I don't know wtf is this but is used sometimes when ekiga rejects a call */ r2cause = OR2_CAUSE_BUSY_NUMBER; break; case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION: case AST_CAUSE_SWITCH_CONGESTION: r2cause = OR2_CAUSE_NETWORK_CONGESTION; break; case AST_CAUSE_UNALLOCATED: r2cause = OR2_CAUSE_UNALLOCATED_NUMBER; break; case AST_CAUSE_NETWORK_OUT_OF_ORDER: case AST_CAUSE_DESTINATION_OUT_OF_ORDER: r2cause = OR2_CAUSE_OUT_OF_ORDER; break; case AST_CAUSE_NO_ANSWER: case AST_CAUSE_NO_USER_RESPONSE: r2cause = OR2_CAUSE_NO_ANSWER; break; default: r2cause = OR2_CAUSE_NORMAL_CLEARING; break; } ast_debug(1, "ast cause %d resulted in openr2 cause %d/%s\n", cause, r2cause, openr2_proto_get_disconnect_string(r2cause)); return r2cause; } #endif static int revert_fax_buffers(struct dahdi_pvt *p, struct ast_channel *ast) { if (p->bufferoverrideinuse) { /* faxbuffers are in use, revert them */ struct dahdi_bufferinfo bi = { .txbufpolicy = p->buf_policy, .rxbufpolicy = p->buf_policy, .bufsize = p->bufsize, .numbufs = p->buf_no }; int bpres; if ((bpres = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi)) < 0) { ast_log(LOG_WARNING, "Channel '%s' unable to revert buffer policy: %s\n", ast_channel_name(ast), strerror(errno)); } p->bufferoverrideinuse = 0; return bpres; } return -1; } static int dahdi_hangup(struct ast_channel *ast) { int res = 0; int idx,x; int law; /*static int restore_gains(struct dahdi_pvt *p);*/ struct dahdi_pvt *p = ast_channel_tech_pvt(ast); struct dahdi_params par; ast_debug(1, "dahdi_hangup(%s)\n", ast_channel_name(ast)); if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Asked to hangup channel not connected\n"); return 0; } ast_mutex_lock(&p->lock); p->exten[0] = '\0'; if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { dahdi_confmute(p, 0); restore_gains(p); p->ignoredtmf = 0; p->waitingfordt.tv_sec = 0; res = analog_hangup(p->sig_pvt, ast); revert_fax_buffers(p, ast); goto hangup_out; } else { p->cid_num[0] = '\0'; p->cid_name[0] = '\0'; p->cid_subaddr[0] = '\0'; } #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig)) { x = 1; ast_channel_setoption(ast, AST_OPTION_AUDIO_MODE, &x, sizeof(char), 0); dahdi_confmute(p, 0); p->muting = 0; restore_gains(p); if (p->dsp) { ast_dsp_free(p->dsp); p->dsp = NULL; } p->ignoredtmf = 0; /* Real channel, do some fixup */ p->subs[SUB_REAL].owner = NULL; p->subs[SUB_REAL].needbusy = 0; dahdi_setlinear(p->subs[SUB_REAL].dfd, 0); p->owner = NULL; p->cid_tag[0] = '\0'; p->ringt = 0;/* Probably not used in this mode. Reset anyway. */ p->distinctivering = 0;/* Probably not used in this mode. Reset anyway. */ p->confirmanswer = 0;/* Probably not used in this mode. Reset anyway. */ p->outgoing = 0; p->digital = 0; p->faxhandled = 0; p->pulsedial = 0;/* Probably not used in this mode. Reset anyway. */ revert_fax_buffers(p, ast); p->law = p->law_default; law = p->law_default; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETLAW, &law); if (res < 0) { ast_log(LOG_WARNING, "Unable to set law on channel %d to default: %s\n", p->channel, strerror(errno)); } sig_pri_hangup(p->sig_pvt, ast); tone_zone_play_tone(p->subs[SUB_REAL].dfd, -1); dahdi_ec_disable(p); x = 0; ast_channel_setoption(ast, AST_OPTION_TDD, &x, sizeof(char), 0); p->didtdd = 0;/* Probably not used in this mode. Reset anyway. */ p->rdnis[0] = '\0'; dahdi_conf_update(p); reset_conf(p); /* Restore data mode */ x = 0; ast_channel_setoption(ast, AST_OPTION_AUDIO_MODE, &x, sizeof(char), 0); if (num_restart_pending == 0) { restart_monitor(); } goto hangup_out; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) if (p->sig == SIG_SS7) { x = 1; ast_channel_setoption(ast, AST_OPTION_AUDIO_MODE, &x, sizeof(char), 0); dahdi_confmute(p, 0); p->muting = 0; restore_gains(p); if (p->dsp) { ast_dsp_free(p->dsp); p->dsp = NULL; } p->ignoredtmf = 0; /* Real channel, do some fixup */ p->subs[SUB_REAL].owner = NULL; p->subs[SUB_REAL].needbusy = 0; dahdi_setlinear(p->subs[SUB_REAL].dfd, 0); p->owner = NULL; p->ringt = 0;/* Probably not used in this mode. Reset anyway. */ p->distinctivering = 0;/* Probably not used in this mode. Reset anyway. */ p->confirmanswer = 0;/* Probably not used in this mode. Reset anyway. */ p->outgoing = 0; p->digital = 0; p->faxhandled = 0; p->pulsedial = 0;/* Probably not used in this mode. Reset anyway. */ revert_fax_buffers(p, ast); p->law = p->law_default; law = p->law_default; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETLAW, &law); if (res < 0) { ast_log(LOG_WARNING, "Unable to set law on channel %d to default: %s\n", p->channel, strerror(errno)); } sig_ss7_hangup(p->sig_pvt, ast); tone_zone_play_tone(p->subs[SUB_REAL].dfd, -1); dahdi_ec_disable(p); x = 0; ast_channel_setoption(ast, AST_OPTION_TDD, &x, sizeof(char), 0); p->didtdd = 0;/* Probably not used in this mode. Reset anyway. */ dahdi_conf_update(p); reset_conf(p); /* Restore data mode */ x = 0; ast_channel_setoption(ast, AST_OPTION_AUDIO_MODE, &x, sizeof(char), 0); if (num_restart_pending == 0) { restart_monitor(); } goto hangup_out; } #endif /* defined(HAVE_SS7) */ idx = dahdi_get_index(ast, p, 1); dahdi_confmute(p, 0); p->muting = 0; restore_gains(p); if (p->origcid_num) { ast_copy_string(p->cid_num, p->origcid_num, sizeof(p->cid_num)); ast_free(p->origcid_num); p->origcid_num = NULL; } if (p->origcid_name) { ast_copy_string(p->cid_name, p->origcid_name, sizeof(p->cid_name)); ast_free(p->origcid_name); p->origcid_name = NULL; } if (p->dsp) ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax); ast_debug(1, "Hangup: channel: %d index = %d, normal = %d, callwait = %d, thirdcall = %d\n", p->channel, idx, p->subs[SUB_REAL].dfd, p->subs[SUB_CALLWAIT].dfd, p->subs[SUB_THREEWAY].dfd); p->ignoredtmf = 0; if (idx > -1) { /* Real channel, do some fixup */ p->subs[idx].owner = NULL; p->subs[idx].needanswer = 0; p->subs[idx].needflash = 0; p->subs[idx].needringing = 0; p->subs[idx].needbusy = 0; p->subs[idx].needcongestion = 0; p->subs[idx].linear = 0; p->polarity = POLARITY_IDLE; dahdi_setlinear(p->subs[idx].dfd, 0); if (idx == SUB_REAL) { if ((p->subs[SUB_CALLWAIT].dfd > -1) && (p->subs[SUB_THREEWAY].dfd > -1)) { ast_debug(1, "Normal call hung up with both three way call and a call waiting call in place?\n"); if (p->subs[SUB_CALLWAIT].inthreeway) { /* We had flipped over to answer a callwait and now it's gone */ ast_debug(1, "We were flipped over to the callwait, moving back and unowning.\n"); /* Move to the call-wait, but un-own us until they flip back. */ swap_subs(p, SUB_CALLWAIT, SUB_REAL); unalloc_sub(p, SUB_CALLWAIT); p->owner = NULL; } else { /* The three way hung up, but we still have a call wait */ ast_debug(1, "We were in the threeway and have a callwait still. Ditching the threeway.\n"); swap_subs(p, SUB_THREEWAY, SUB_REAL); unalloc_sub(p, SUB_THREEWAY); if (p->subs[SUB_REAL].inthreeway) { /* This was part of a three way call. Immediately make way for another call */ ast_debug(1, "Call was complete, setting owner to former third call\n"); p->owner = p->subs[SUB_REAL].owner; } else { /* This call hasn't been completed yet... Set owner to NULL */ ast_debug(1, "Call was incomplete, setting owner to NULL\n"); p->owner = NULL; } p->subs[SUB_REAL].inthreeway = 0; } } else if (p->subs[SUB_CALLWAIT].dfd > -1) { /* Move to the call-wait and switch back to them. */ swap_subs(p, SUB_CALLWAIT, SUB_REAL); unalloc_sub(p, SUB_CALLWAIT); p->owner = p->subs[SUB_REAL].owner; if (ast_channel_state(p->owner) != AST_STATE_UP) p->subs[SUB_REAL].needanswer = 1; ast_queue_unhold(p->subs[SUB_REAL].owner); } else if (p->subs[SUB_THREEWAY].dfd > -1) { swap_subs(p, SUB_THREEWAY, SUB_REAL); unalloc_sub(p, SUB_THREEWAY); if (p->subs[SUB_REAL].inthreeway) { /* This was part of a three way call. Immediately make way for another call */ ast_debug(1, "Call was complete, setting owner to former third call\n"); p->owner = p->subs[SUB_REAL].owner; } else { /* This call hasn't been completed yet... Set owner to NULL */ ast_debug(1, "Call was incomplete, setting owner to NULL\n"); p->owner = NULL; } p->subs[SUB_REAL].inthreeway = 0; } } else if (idx == SUB_CALLWAIT) { /* Ditch the holding callwait call, and immediately make it availabe */ if (p->subs[SUB_CALLWAIT].inthreeway) { /* This is actually part of a three way, placed on hold. Place the third part on music on hold now */ if (p->subs[SUB_THREEWAY].owner) { ast_queue_hold(p->subs[SUB_THREEWAY].owner, p->mohsuggest); } p->subs[SUB_THREEWAY].inthreeway = 0; /* Make it the call wait now */ swap_subs(p, SUB_CALLWAIT, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY); } else unalloc_sub(p, SUB_CALLWAIT); } else if (idx == SUB_THREEWAY) { if (p->subs[SUB_CALLWAIT].inthreeway) { /* The other party of the three way call is currently in a call-wait state. Start music on hold for them, and take the main guy out of the third call */ if (p->subs[SUB_CALLWAIT].owner) { ast_queue_hold(p->subs[SUB_CALLWAIT].owner, p->mohsuggest); } p->subs[SUB_CALLWAIT].inthreeway = 0; } p->subs[SUB_REAL].inthreeway = 0; /* If this was part of a three way call index, let us make another three way call */ unalloc_sub(p, SUB_THREEWAY); } else { /* This wasn't any sort of call, but how are we an index? */ ast_log(LOG_WARNING, "Index found but not any type of call?\n"); } } if (!p->subs[SUB_REAL].owner && !p->subs[SUB_CALLWAIT].owner && !p->subs[SUB_THREEWAY].owner) { p->owner = NULL; p->ringt = 0; p->distinctivering = 0; p->confirmanswer = 0; p->outgoing = 0; p->digital = 0; p->faxhandled = 0; p->pulsedial = 0; if (p->dsp) { ast_dsp_free(p->dsp); p->dsp = NULL; } revert_fax_buffers(p, ast); p->law = p->law_default; law = p->law_default; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SETLAW, &law); if (res < 0) ast_log(LOG_WARNING, "Unable to set law on channel %d to default: %s\n", p->channel, strerror(errno)); /* Perform low level hangup if no owner left */ #ifdef HAVE_OPENR2 if (p->mfcr2 && p->mfcr2call && openr2_chan_get_direction(p->r2chan) != OR2_DIR_STOPPED) { ast_debug(1, "disconnecting MFC/R2 call on chan %d\n", p->channel); /* If it's an incoming call, check the mfcr2_forced_release setting */ if (openr2_chan_get_direction(p->r2chan) == OR2_DIR_BACKWARD && p->mfcr2_forced_release) { dahdi_r2_disconnect_call(p, OR2_CAUSE_FORCED_RELEASE); } else { const char *r2causestr = pbx_builtin_getvar_helper(ast, "MFCR2_CAUSE"); int r2cause_user = r2causestr ? atoi(r2causestr) : 0; openr2_call_disconnect_cause_t r2cause = r2cause_user ? dahdi_ast_cause_to_r2_cause(r2cause_user) : dahdi_ast_cause_to_r2_cause(ast_channel_hangupcause(ast)); dahdi_r2_disconnect_call(p, r2cause); } } else if (p->mfcr2call) { ast_debug(1, "Clearing call request on channel %d\n", p->channel); /* since ast_request() was called but not ast_call() we have not yet dialed and the openr2 stack will not call on_call_end callback, we need to unset the mfcr2call flag and bump the monitor count so the monitor thread can take care of this channel events from now on */ p->mfcr2call = 0; } #endif switch (p->sig) { case SIG_SS7: case SIG_MFCR2: case SIG_PRI_LIB_HANDLE_CASES: case 0: break; default: res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_ONHOOK); break; } if (res < 0) { ast_log(LOG_WARNING, "Unable to hangup line %s\n", ast_channel_name(ast)); } switch (p->sig) { case SIG_FXOGS: case SIG_FXOLS: case SIG_FXOKS: memset(&par, 0, sizeof(par)); res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &par); if (!res) { struct analog_pvt *analog_p = p->sig_pvt; #if 0 ast_debug(1, "Hanging up channel %d, offhook = %d\n", p->channel, par.rxisoffhook); #endif /* If they're off hook, try playing congestion */ if ((par.rxisoffhook) && (!(p->radio || (p->oprmode < 0)))) tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); else tone_zone_play_tone(p->subs[SUB_REAL].dfd, -1); analog_p->fxsoffhookstate = par.rxisoffhook; } break; case SIG_FXSGS: case SIG_FXSLS: case SIG_FXSKS: /* Make sure we're not made available for at least two seconds assuming we were actually used for an inbound or outbound call. */ if (ast_channel_state(ast) != AST_STATE_RESERVED) { time(&p->guardtime); p->guardtime += 2; } break; default: tone_zone_play_tone(p->subs[SUB_REAL].dfd, -1); break; } if (p->sig) dahdi_ec_disable(p); x = 0; ast_channel_setoption(ast,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0); ast_channel_setoption(ast,AST_OPTION_TDD,&x,sizeof(char),0); p->didtdd = 0; p->callwaitcas = 0; p->callwaiting = p->permcallwaiting; p->hidecallerid = p->permhidecallerid; p->waitingfordt.tv_sec = 0; p->dialing = 0; p->rdnis[0] = '\0'; dahdi_conf_update(p); reset_conf(p); /* Restore data mode */ switch (p->sig) { case SIG_PRI_LIB_HANDLE_CASES: case SIG_SS7: x = 0; ast_channel_setoption(ast,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0); break; default: break; } if (num_restart_pending == 0) restart_monitor(); } p->callwaitingrepeat = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; p->oprmode = 0; hangup_out: ast_channel_tech_pvt_set(ast, NULL); ast_free(p->cidspill); p->cidspill = NULL; ast_mutex_unlock(&p->lock); ast_verb(3, "Hungup '%s'\n", ast_channel_name(ast)); ast_mutex_lock(&iflock); if (p->restartpending) { num_restart_pending--; } if (p->destroy) { destroy_channel(p, 0); } ast_mutex_unlock(&iflock); ast_module_unref(ast_module_info->self); return 0; } static int dahdi_answer(struct ast_channel *ast) { struct dahdi_pvt *p = ast_channel_tech_pvt(ast); int res = 0; int idx; ast_setstate(ast, AST_STATE_UP);/*! \todo XXX this is redundantly set by the analog and PRI submodules! */ ast_mutex_lock(&p->lock); idx = dahdi_get_index(ast, p, 0); if (idx < 0) idx = SUB_REAL; /* nothing to do if a radio channel */ if ((p->radio || (p->oprmode < 0))) { ast_mutex_unlock(&p->lock); return 0; } if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { res = analog_answer(p->sig_pvt, ast); ast_mutex_unlock(&p->lock); return res; } switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: res = sig_pri_answer(p->sig_pvt, ast); break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: res = sig_ss7_answer(p->sig_pvt, ast); break; #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 case SIG_MFCR2: if (!p->mfcr2_call_accepted) { /* The call was not accepted on offer nor the user, so it must be accepted now before answering, openr2_chan_answer_call will be called when the callback on_call_accepted is executed */ p->mfcr2_answer_pending = 1; if (p->mfcr2_charge_calls) { ast_debug(1, "Accepting MFC/R2 call with charge before answering on chan %d\n", p->channel); openr2_chan_accept_call(p->r2chan, OR2_CALL_WITH_CHARGE); } else { ast_debug(1, "Accepting MFC/R2 call with no charge before answering on chan %d\n", p->channel); openr2_chan_accept_call(p->r2chan, OR2_CALL_NO_CHARGE); } } else { ast_debug(1, "Answering MFC/R2 call on chan %d\n", p->channel); dahdi_r2_answer(p); } break; #endif case 0: ast_mutex_unlock(&p->lock); return 0; default: ast_log(LOG_WARNING, "Don't know how to answer signalling %d (channel %d)\n", p->sig, p->channel); res = -1; break; } ast_mutex_unlock(&p->lock); return res; } void dahdi_dtmf_detect_disable(struct dahdi_pvt *p) { int val = 0; p->ignoredtmf = 1; ioctl(p->subs[SUB_REAL].dfd, DAHDI_TONEDETECT, &val); if (!p->hardwaredtmf && p->dsp) { p->dsp_features &= ~DSP_FEATURE_DIGIT_DETECT; ast_dsp_set_features(p->dsp, p->dsp_features); } } void dahdi_dtmf_detect_enable(struct dahdi_pvt *p) { int val = DAHDI_TONEDETECT_ON | DAHDI_TONEDETECT_MUTE; if (p->channel == CHAN_PSEUDO) return; p->ignoredtmf = 0; ioctl(p->subs[SUB_REAL].dfd, DAHDI_TONEDETECT, &val); if (!p->hardwaredtmf && p->dsp) { p->dsp_features |= DSP_FEATURE_DIGIT_DETECT; ast_dsp_set_features(p->dsp, p->dsp_features); } } static int dahdi_queryoption(struct ast_channel *chan, int option, void *data, int *datalen) { char *cp; struct dahdi_pvt *p = ast_channel_tech_pvt(chan); /* all supported options require data */ if (!p || !data || (*datalen < 1)) { errno = EINVAL; return -1; } switch (option) { case AST_OPTION_DIGIT_DETECT: cp = (char *) data; *cp = p->ignoredtmf ? 0 : 1; ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", ast_channel_name(chan)); break; case AST_OPTION_FAX_DETECT: cp = (char *) data; *cp = (p->dsp_features & DSP_FEATURE_FAX_DETECT) ? 0 : 1; ast_debug(1, "Reporting fax tone detection %sabled on %s\n", *cp ? "en" : "dis", ast_channel_name(chan)); break; case AST_OPTION_CC_AGENT_TYPE: #if defined(HAVE_PRI) #if defined(HAVE_PRI_CCSS) if (dahdi_sig_pri_lib_handles(p->sig)) { ast_copy_string((char *) data, dahdi_pri_cc_type, *datalen); break; } #endif /* defined(HAVE_PRI_CCSS) */ #endif /* defined(HAVE_PRI) */ return -1; default: return -1; } errno = 0; return 0; } static int dahdi_setoption(struct ast_channel *chan, int option, void *data, int datalen) { char *cp; signed char *scp; int x; int idx; struct dahdi_pvt *p = ast_channel_tech_pvt(chan), *pp; struct oprmode *oprmode; /* all supported options require data */ if (!p || !data || (datalen < 1)) { errno = EINVAL; return -1; } switch (option) { case AST_OPTION_TXGAIN: scp = (signed char *) data; idx = dahdi_get_index(chan, p, 0); if (idx < 0) { ast_log(LOG_WARNING, "No index in TXGAIN?\n"); return -1; } ast_debug(1, "Setting actual tx gain on %s to %f\n", ast_channel_name(chan), p->txgain + (float) *scp); return set_actual_txgain(p->subs[idx].dfd, p->txgain + (float) *scp, p->txdrc, p->law); case AST_OPTION_RXGAIN: scp = (signed char *) data; idx = dahdi_get_index(chan, p, 0); if (idx < 0) { ast_log(LOG_WARNING, "No index in RXGAIN?\n"); return -1; } ast_debug(1, "Setting actual rx gain on %s to %f\n", ast_channel_name(chan), p->rxgain + (float) *scp); return set_actual_rxgain(p->subs[idx].dfd, p->rxgain + (float) *scp, p->rxdrc, p->law); case AST_OPTION_TONE_VERIFY: if (!p->dsp) break; cp = (char *) data; switch (*cp) { case 1: ast_debug(1, "Set option TONE VERIFY, mode: MUTECONF(1) on %s\n",ast_channel_name(chan)); ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_MUTECONF | p->dtmfrelax); /* set mute mode if desired */ break; case 2: ast_debug(1, "Set option TONE VERIFY, mode: MUTECONF/MAX(2) on %s\n",ast_channel_name(chan)); ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX | p->dtmfrelax); /* set mute mode if desired */ break; default: ast_debug(1, "Set option TONE VERIFY, mode: OFF(0) on %s\n",ast_channel_name(chan)); ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax); /* set mute mode if desired */ break; } break; case AST_OPTION_TDD: /* turn on or off TDD */ cp = (char *) data; p->mate = 0; if (!*cp) { /* turn it off */ ast_debug(1, "Set option TDD MODE, value: OFF(0) on %s\n",ast_channel_name(chan)); if (p->tdd) tdd_free(p->tdd); p->tdd = 0; break; } ast_debug(1, "Set option TDD MODE, value: %s(%d) on %s\n", (*cp == 2) ? "MATE" : "ON", (int) *cp, ast_channel_name(chan)); dahdi_ec_disable(p); /* otherwise, turn it on */ if (!p->didtdd) { /* if havent done it yet */ unsigned char mybuf[41000];/*! \todo XXX This is an abuse of the stack!! */ unsigned char *buf; int size, res, fd, len; struct pollfd fds[1]; buf = mybuf; memset(buf, 0x7f, sizeof(mybuf)); /* set to silence */ ast_tdd_gen_ecdisa(buf + 16000, 16000); /* put in tone */ len = 40000; idx = dahdi_get_index(chan, p, 0); if (idx < 0) { ast_log(LOG_WARNING, "No index in TDD?\n"); return -1; } fd = p->subs[idx].dfd; while (len) { if (ast_check_hangup(chan)) return -1; size = len; if (size > READ_SIZE) size = READ_SIZE; fds[0].fd = fd; fds[0].events = POLLPRI | POLLOUT; fds[0].revents = 0; res = poll(fds, 1, -1); if (!res) { ast_debug(1, "poll (for write) ret. 0 on channel %d\n", p->channel); continue; } /* if got exception */ if (fds[0].revents & POLLPRI) return -1; if (!(fds[0].revents & POLLOUT)) { ast_debug(1, "write fd not ready on channel %d\n", p->channel); continue; } res = write(fd, buf, size); if (res != size) { if (res == -1) return -1; ast_debug(1, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel); break; } len -= size; buf += size; } p->didtdd = 1; /* set to have done it now */ } if (*cp == 2) { /* Mate mode */ if (p->tdd) tdd_free(p->tdd); p->tdd = 0; p->mate = 1; break; } if (!p->tdd) { /* if we don't have one yet */ p->tdd = tdd_new(); /* allocate one */ } break; case AST_OPTION_RELAXDTMF: /* Relax DTMF decoding (or not) */ if (!p->dsp) break; cp = (char *) data; ast_debug(1, "Set option RELAX DTMF, value: %s(%d) on %s\n", *cp ? "ON" : "OFF", (int) *cp, ast_channel_name(chan)); ast_dsp_set_digitmode(p->dsp, ((*cp) ? DSP_DIGITMODE_RELAXDTMF : DSP_DIGITMODE_DTMF) | p->dtmfrelax); break; case AST_OPTION_AUDIO_MODE: /* Set AUDIO mode (or not) */ #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig) && ((struct sig_pri_chan *) p->sig_pvt)->no_b_channel) { /* PRI nobch pseudo channel. Does not handle ioctl(DAHDI_AUDIOMODE) */ break; } #endif /* defined(HAVE_PRI) */ cp = (char *) data; if (!*cp) { ast_debug(1, "Set option AUDIO MODE, value: OFF(0) on %s\n", ast_channel_name(chan)); x = 0; dahdi_ec_disable(p); } else { ast_debug(1, "Set option AUDIO MODE, value: ON(1) on %s\n", ast_channel_name(chan)); x = 1; } if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &x) == -1) ast_log(LOG_WARNING, "Unable to set audio mode on channel %d to %d: %s\n", p->channel, x, strerror(errno)); break; case AST_OPTION_OPRMODE: /* Operator services mode */ oprmode = (struct oprmode *) data; /* We don't support operator mode across technologies */ if (strcasecmp(ast_channel_tech(chan)->type, ast_channel_tech(oprmode->peer)->type)) { ast_log(LOG_NOTICE, "Operator mode not supported on %s to %s calls.\n", ast_channel_tech(chan)->type, ast_channel_tech(oprmode->peer)->type); errno = EINVAL; return -1; } pp = ast_channel_tech_pvt(oprmode->peer); p->oprmode = pp->oprmode = 0; /* setup peers */ p->oprpeer = pp; pp->oprpeer = p; /* setup modes, if any */ if (oprmode->mode) { pp->oprmode = oprmode->mode; p->oprmode = -oprmode->mode; } ast_debug(1, "Set Operator Services mode, value: %d on %s/%s\n", oprmode->mode, ast_channel_name(chan),ast_channel_name(oprmode->peer)); break; case AST_OPTION_ECHOCAN: cp = (char *) data; if (*cp) { ast_debug(1, "Enabling echo cancellation on %s\n", ast_channel_name(chan)); dahdi_ec_enable(p); } else { ast_debug(1, "Disabling echo cancellation on %s\n", ast_channel_name(chan)); dahdi_ec_disable(p); } break; case AST_OPTION_DIGIT_DETECT: cp = (char *) data; ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", ast_channel_name(chan)); if (*cp) { dahdi_dtmf_detect_enable(p); } else { dahdi_dtmf_detect_disable(p); } break; case AST_OPTION_FAX_DETECT: cp = (char *) data; if (p->dsp) { ast_debug(1, "%sabling fax tone detection on %s\n", *cp ? "En" : "Dis", ast_channel_name(chan)); if (*cp) { p->dsp_features |= DSP_FEATURE_FAX_DETECT; } else { p->dsp_features &= ~DSP_FEATURE_FAX_DETECT; } ast_dsp_set_features(p->dsp, p->dsp_features); } break; default: return -1; } errno = 0; return 0; } static int dahdi_func_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) { struct dahdi_pvt *p = ast_channel_tech_pvt(chan); int res = 0; if (!p) { /* No private structure! */ *buf = '\0'; return -1; } if (!strcasecmp(data, "rxgain")) { ast_mutex_lock(&p->lock); snprintf(buf, len, "%f", p->rxgain); ast_mutex_unlock(&p->lock); } else if (!strcasecmp(data, "txgain")) { ast_mutex_lock(&p->lock); snprintf(buf, len, "%f", p->txgain); ast_mutex_unlock(&p->lock); } else if (!strcasecmp(data, "dahdi_channel")) { ast_mutex_lock(&p->lock); snprintf(buf, len, "%d", p->channel); ast_mutex_unlock(&p->lock); } else if (!strcasecmp(data, "dahdi_span")) { ast_mutex_lock(&p->lock); snprintf(buf, len, "%d", p->span); ast_mutex_unlock(&p->lock); } else if (!strcasecmp(data, "dahdi_type")) { ast_mutex_lock(&p->lock); switch (p->sig) { #if defined(HAVE_OPENR2) case SIG_MFCR2: ast_copy_string(buf, "mfc/r2", len); break; #endif /* defined(HAVE_OPENR2) */ #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: ast_copy_string(buf, "pri", len); break; #endif /* defined(HAVE_PRI) */ case 0: ast_copy_string(buf, "pseudo", len); break; #if defined(HAVE_SS7) case SIG_SS7: ast_copy_string(buf, "ss7", len); break; #endif /* defined(HAVE_SS7) */ default: /* The only thing left is analog ports. */ ast_copy_string(buf, "analog", len); break; } ast_mutex_unlock(&p->lock); #if defined(HAVE_PRI) #if defined(HAVE_PRI_REVERSE_CHARGE) } else if (!strcasecmp(data, "reversecharge")) { ast_mutex_lock(&p->lock); switch (p->sig) { case SIG_PRI_LIB_HANDLE_CASES: snprintf(buf, len, "%d", ((struct sig_pri_chan *) p->sig_pvt)->reverse_charging_indication); break; default: *buf = '\0'; res = -1; break; } ast_mutex_unlock(&p->lock); #endif #if defined(HAVE_PRI_SETUP_KEYPAD) } else if (!strcasecmp(data, "keypad_digits")) { ast_mutex_lock(&p->lock); switch (p->sig) { case SIG_PRI_LIB_HANDLE_CASES: ast_copy_string(buf, ((struct sig_pri_chan *) p->sig_pvt)->keypad_digits, len); break; default: *buf = '\0'; res = -1; break; } ast_mutex_unlock(&p->lock); #endif /* defined(HAVE_PRI_SETUP_KEYPAD) */ } else if (!strcasecmp(data, "no_media_path")) { ast_mutex_lock(&p->lock); switch (p->sig) { case SIG_PRI_LIB_HANDLE_CASES: /* * TRUE if the call is on hold or is call waiting because * there is no media path available. */ snprintf(buf, len, "%d", ((struct sig_pri_chan *) p->sig_pvt)->no_b_channel); break; default: *buf = '\0'; res = -1; break; } ast_mutex_unlock(&p->lock); #endif /* defined(HAVE_PRI) */ } else { *buf = '\0'; res = -1; } return res; } static int parse_buffers_policy(const char *parse, int *num_buffers, int *policy) { int res; char policy_str[21] = ""; if ((res = sscanf(parse, "%30d,%20s", num_buffers, policy_str)) != 2) { ast_log(LOG_WARNING, "Parsing buffer string '%s' failed.\n", parse); return 1; } if (*num_buffers < 0) { ast_log(LOG_WARNING, "Invalid buffer count given '%d'.\n", *num_buffers); return -1; } if (!strcasecmp(policy_str, "full")) { *policy = DAHDI_POLICY_WHEN_FULL; } else if (!strcasecmp(policy_str, "immediate")) { *policy = DAHDI_POLICY_IMMEDIATE; #if defined(HAVE_DAHDI_HALF_FULL) } else if (!strcasecmp(policy_str, "half")) { *policy = DAHDI_POLICY_HALF_FULL; #endif } else { ast_log(LOG_WARNING, "Invalid policy name given '%s'.\n", policy_str); return -1; } return 0; } static int dahdi_func_write(struct ast_channel *chan, const char *function, char *data, const char *value) { struct dahdi_pvt *p = ast_channel_tech_pvt(chan); int res = 0; if (!p) { /* No private structure! */ return -1; } if (!strcasecmp(data, "buffers")) { int num_bufs, policy; if (!(parse_buffers_policy(value, &num_bufs, &policy))) { struct dahdi_bufferinfo bi = { .txbufpolicy = policy, .rxbufpolicy = policy, .bufsize = p->bufsize, .numbufs = num_bufs, }; int bpres; if ((bpres = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi)) < 0) { ast_log(LOG_WARNING, "Channel '%d' unable to override buffer policy: %s\n", p->channel, strerror(errno)); } else { p->bufferoverrideinuse = 1; } } else { res = -1; } } else if (!strcasecmp(data, "echocan_mode")) { if (!strcasecmp(value, "on")) { ast_mutex_lock(&p->lock); dahdi_ec_enable(p); ast_mutex_unlock(&p->lock); } else if (!strcasecmp(value, "off")) { ast_mutex_lock(&p->lock); dahdi_ec_disable(p); ast_mutex_unlock(&p->lock); #ifdef HAVE_DAHDI_ECHOCANCEL_FAX_MODE } else if (!strcasecmp(value, "fax")) { int blah = 1; ast_mutex_lock(&p->lock); if (!p->echocanon) { dahdi_ec_enable(p); } if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOCANCEL_FAX_MODE, &blah)) { ast_log(LOG_WARNING, "Unable to place echocan into fax mode on channel %d: %s\n", p->channel, strerror(errno)); } ast_mutex_unlock(&p->lock); } else if (!strcasecmp(value, "voice")) { int blah = 0; ast_mutex_lock(&p->lock); if (!p->echocanon) { dahdi_ec_enable(p); } if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_ECHOCANCEL_FAX_MODE, &blah)) { ast_log(LOG_WARNING, "Unable to place echocan into voice mode on channel %d: %s\n", p->channel, strerror(errno)); } ast_mutex_unlock(&p->lock); #endif } else { ast_log(LOG_WARNING, "Unsupported value '%s' provided for '%s' item.\n", value, data); res = -1; } } else { res = -1; } return res; } void dahdi_master_slave_unlink(struct dahdi_pvt *slave, struct dahdi_pvt *master, int needlock) { /* Unlink a specific slave or all slaves/masters from a given master */ int x; int hasslaves; if (!master) return; if (needlock) { ast_mutex_lock(&master->lock); if (slave) { while (ast_mutex_trylock(&slave->lock)) { DEADLOCK_AVOIDANCE(&master->lock); } } } hasslaves = 0; for (x = 0; x < MAX_SLAVES; x++) { if (master->slaves[x]) { if (!slave || (master->slaves[x] == slave)) { /* Take slave out of the conference */ ast_debug(1, "Unlinking slave %d from %d\n", master->slaves[x]->channel, master->channel); conf_del(master, &master->slaves[x]->subs[SUB_REAL], SUB_REAL); conf_del(master->slaves[x], &master->subs[SUB_REAL], SUB_REAL); master->slaves[x]->master = NULL; master->slaves[x] = NULL; } else hasslaves = 1; } if (!hasslaves) master->inconference = 0; } if (!slave) { if (master->master) { /* Take master out of the conference */ conf_del(master->master, &master->subs[SUB_REAL], SUB_REAL); conf_del(master, &master->master->subs[SUB_REAL], SUB_REAL); hasslaves = 0; for (x = 0; x < MAX_SLAVES; x++) { if (master->master->slaves[x] == master) master->master->slaves[x] = NULL; else if (master->master->slaves[x]) hasslaves = 1; } if (!hasslaves) master->master->inconference = 0; } master->master = NULL; } dahdi_conf_update(master); if (needlock) { if (slave) ast_mutex_unlock(&slave->lock); ast_mutex_unlock(&master->lock); } } void dahdi_master_slave_link(struct dahdi_pvt *slave, struct dahdi_pvt *master) { int x; if (!slave || !master) { ast_log(LOG_WARNING, "Tried to link to/from NULL??\n"); return; } for (x = 0; x < MAX_SLAVES; x++) { if (!master->slaves[x]) { master->slaves[x] = slave; break; } } if (x >= MAX_SLAVES) { ast_log(LOG_WARNING, "Replacing slave %d with new slave, %d\n", master->slaves[MAX_SLAVES - 1]->channel, slave->channel); master->slaves[MAX_SLAVES - 1] = slave; } if (slave->master) ast_log(LOG_WARNING, "Replacing master %d with new master, %d\n", slave->master->channel, master->channel); slave->master = master; ast_debug(1, "Making %d slave to master %d at %d\n", slave->channel, master->channel, x); } static int dahdi_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct dahdi_pvt *p = ast_channel_tech_pvt(newchan); int x; ast_mutex_lock(&p->lock); ast_debug(1, "New owner for channel %d is %s\n", p->channel, ast_channel_name(newchan)); if (p->owner == oldchan) { p->owner = newchan; } for (x = 0; x < 3; x++) { if (p->subs[x].owner == oldchan) { if (!x) { dahdi_master_slave_unlink(NULL, p, 0); } p->subs[x].owner = newchan; } } if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { analog_fixup(oldchan, newchan, p->sig_pvt); #if defined(HAVE_PRI) } else if (dahdi_sig_pri_lib_handles(p->sig)) { sig_pri_fixup(oldchan, newchan, p->sig_pvt); #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) } else if (p->sig == SIG_SS7) { sig_ss7_fixup(oldchan, newchan, p->sig_pvt); #endif /* defined(HAVE_SS7) */ } dahdi_conf_update(p); ast_mutex_unlock(&p->lock); if (ast_channel_state(newchan) == AST_STATE_RINGING) { dahdi_indicate(newchan, AST_CONTROL_RINGING, NULL, 0); } return 0; } static int dahdi_ring_phone(struct dahdi_pvt *p) { int x; int res; /* Make sure our transmit state is on hook */ x = 0; x = DAHDI_ONHOOK; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); do { x = DAHDI_RING; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); if (res) { switch (errno) { case EBUSY: case EINTR: /* Wait just in case */ usleep(10000); continue; case EINPROGRESS: res = 0; break; default: ast_log(LOG_WARNING, "Couldn't ring the phone: %s\n", strerror(errno)); res = 0; } } } while (res); return res; } static void *analog_ss_thread(void *data); /*! * \internal * \brief Attempt to transfer 3-way call. * * \param p DAHDI private structure. * * \note On entry these locks are held: real-call, private, 3-way call. * \note On exit these locks are held: real-call, private. * * \retval 0 on success. * \retval -1 on error. */ static int attempt_transfer(struct dahdi_pvt *p) { struct ast_channel *owner_real; struct ast_channel *owner_3way; enum ast_transfer_result xfer_res; int res = 0; owner_real = ast_channel_ref(p->subs[SUB_REAL].owner); owner_3way = ast_channel_ref(p->subs[SUB_THREEWAY].owner); ast_verb(3, "TRANSFERRING %s to %s\n", ast_channel_name(owner_3way), ast_channel_name(owner_real)); ast_channel_unlock(owner_real); ast_channel_unlock(owner_3way); ast_mutex_unlock(&p->lock); xfer_res = ast_bridge_transfer_attended(owner_3way, owner_real); if (xfer_res != AST_BRIDGE_TRANSFER_SUCCESS) { ast_softhangup(owner_3way, AST_SOFTHANGUP_DEV); res = -1; } /* Must leave with these locked. */ ast_channel_lock(owner_real); ast_mutex_lock(&p->lock); ast_channel_unref(owner_real); ast_channel_unref(owner_3way); return res; } static int check_for_conference(struct dahdi_pvt *p) { struct dahdi_confinfo ci; /* Fine if we already have a master, etc */ if (p->master || (p->confno > -1)) return 0; memset(&ci, 0, sizeof(ci)); if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_GETCONF, &ci)) { ast_log(LOG_WARNING, "Failed to get conference info on channel %d: %s\n", p->channel, strerror(errno)); return 0; } /* If we have no master and don't have a confno, then if we're in a conference, it's probably a MeetMe room or some such, so don't let us 3-way out! */ if ((p->subs[SUB_REAL].curconf.confno != ci.confno) || (p->subs[SUB_REAL].curconf.confmode != ci.confmode)) { ast_verb(3, "Avoiding 3-way call when in an external conference\n"); return 1; } return 0; } /*! Checks channel for alarms * \param p a channel to check for alarms. * \returns the alarms on the span to which the channel belongs, or alarms on * the channel if no span alarms. */ static int get_alarms(struct dahdi_pvt *p) { int res; struct dahdi_spaninfo zi; struct dahdi_params params; memset(&zi, 0, sizeof(zi)); zi.spanno = p->span; if ((res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SPANSTAT, &zi)) >= 0) { if (zi.alarms != DAHDI_ALARM_NONE) return zi.alarms; } else { ast_log(LOG_WARNING, "Unable to determine alarm on channel %d: %s\n", p->channel, strerror(errno)); return 0; } /* No alarms on the span. Check for channel alarms. */ memset(¶ms, 0, sizeof(params)); if ((res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, ¶ms)) >= 0) return params.chan_alarms; ast_log(LOG_WARNING, "Unable to determine alarm on channel %d\n", p->channel); return DAHDI_ALARM_NONE; } static void dahdi_handle_dtmf(struct ast_channel *ast, int idx, struct ast_frame **dest) { struct dahdi_pvt *p = ast_channel_tech_pvt(ast); struct ast_frame *f = *dest; ast_debug(1, "%s DTMF digit: 0x%02X '%c' on %s\n", f->frametype == AST_FRAME_DTMF_BEGIN ? "Begin" : "End", (unsigned)f->subclass.integer, f->subclass.integer, ast_channel_name(ast)); if (p->confirmanswer) { if (f->frametype == AST_FRAME_DTMF_END) { ast_debug(1, "Confirm answer on %s!\n", ast_channel_name(ast)); /* Upon receiving a DTMF digit, consider this an answer confirmation instead of a DTMF digit */ p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; /* Reset confirmanswer so DTMF's will behave properly for the duration of the call */ p->confirmanswer = 0; } else { p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; } *dest = &p->subs[idx].f; } else if (p->callwaitcas) { if (f->frametype == AST_FRAME_DTMF_END) { if ((f->subclass.integer == 'A') || (f->subclass.integer == 'D')) { ast_debug(1, "Got some DTMF, but it's for the CAS\n"); ast_free(p->cidspill); p->cidspill = NULL; send_cwcidspill(p); } p->callwaitcas = 0; } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; *dest = &p->subs[idx].f; } else if (f->subclass.integer == 'f') { if (f->frametype == AST_FRAME_DTMF_END) { /* Fax tone -- Handle and return NULL */ if ((p->callprogress & CALLPROGRESS_FAX) && !p->faxhandled) { /* If faxbuffers are configured, use them for the fax transmission */ if (p->usefaxbuffers && !p->bufferoverrideinuse) { struct dahdi_bufferinfo bi = { .txbufpolicy = p->faxbuf_policy, .bufsize = p->bufsize, .numbufs = p->faxbuf_no }; int res; if ((res = ioctl(p->subs[idx].dfd, DAHDI_SET_BUFINFO, &bi)) < 0) { ast_log(LOG_WARNING, "Channel '%s' unable to set buffer policy, reason: %s\n", ast_channel_name(ast), strerror(errno)); } else { p->bufferoverrideinuse = 1; } } p->faxhandled = 1; if (p->dsp) { p->dsp_features &= ~DSP_FEATURE_FAX_DETECT; ast_dsp_set_features(p->dsp, p->dsp_features); ast_debug(1, "Disabling FAX tone detection on %s after tone received\n", ast_channel_name(ast)); } if (strcmp(ast_channel_exten(ast), "fax")) { const char *target_context = S_OR(ast_channel_macrocontext(ast), ast_channel_context(ast)); /* We need to unlock 'ast' here because ast_exists_extension has the * potential to start autoservice on the channel. Such action is prone * to deadlock. */ ast_mutex_unlock(&p->lock); ast_channel_unlock(ast); if (ast_exists_extension(ast, target_context, "fax", 1, S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, NULL))) { ast_channel_lock(ast); ast_mutex_lock(&p->lock); ast_verb(3, "Redirecting %s to fax extension\n", ast_channel_name(ast)); /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */ pbx_builtin_setvar_helper(ast, "FAXEXTEN", ast_channel_exten(ast)); if (ast_async_goto(ast, target_context, "fax", 1)) ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(ast), target_context); } else { ast_channel_lock(ast); ast_mutex_lock(&p->lock); ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n"); } } else { ast_debug(1, "Already in a fax extension, not redirecting\n"); } } else { ast_debug(1, "Fax already handled\n"); } dahdi_confmute(p, 0); } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; *dest = &p->subs[idx].f; } } static void publish_span_alarm(int span, const char *alarm_txt) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); body = ast_json_pack("{s: i, s: s}", "Span", span, "Alarm", alarm_txt); if (!body) { return; } ast_manager_publish_event("SpanAlarm", EVENT_FLAG_SYSTEM, body); } static void publish_channel_alarm(int channel, const char *alarm_txt) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); RAII_VAR(struct ast_str *, dahdi_chan, ast_str_create(32), ast_free); if (!dahdi_chan) { return; } ast_str_set(&dahdi_chan, 0, "%d", channel); body = ast_json_pack("{s: s, s: s}", "DAHDIChannel", ast_str_buffer(dahdi_chan), "Alarm", alarm_txt); if (!body) { return; } ast_manager_publish_event("Alarm", EVENT_FLAG_SYSTEM, body); } static void handle_alarms(struct dahdi_pvt *p, int alms) { const char *alarm_str; #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig) && sig_pri_is_alarm_ignored(p->pri)) { return; } #endif /* defined(HAVE_PRI) */ alarm_str = alarm2str(alms); if (report_alarms & REPORT_CHANNEL_ALARMS) { ast_log(LOG_WARNING, "Detected alarm on channel %d: %s\n", p->channel, alarm_str); publish_channel_alarm(p->channel, alarm_str); } if (report_alarms & REPORT_SPAN_ALARMS && p->manages_span_alarms) { ast_log(LOG_WARNING, "Detected alarm on span %d: %s\n", p->span, alarm_str); publish_span_alarm(p->span, alarm_str); } } static struct ast_frame *dahdi_handle_event(struct ast_channel *ast) { int res, x; int idx, mysig; char *c; struct dahdi_pvt *p = ast_channel_tech_pvt(ast); pthread_t threadid; struct ast_channel *chan; struct ast_frame *f; idx = dahdi_get_index(ast, p, 0); if (idx < 0) { return &ast_null_frame; } mysig = p->sig; if (p->outsigmod > -1) mysig = p->outsigmod; p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.datalen = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.src = "dahdi_handle_event"; p->subs[idx].f.data.ptr = NULL; f = &p->subs[idx].f; if (p->fake_event) { res = p->fake_event; p->fake_event = 0; } else res = dahdi_get_event(p->subs[idx].dfd); ast_debug(1, "Got event %s(%d) on channel %d (index %d)\n", event2str(res), res, p->channel, idx); if (res & (DAHDI_EVENT_PULSEDIGIT | DAHDI_EVENT_DTMFUP)) { p->pulsedial = (res & DAHDI_EVENT_PULSEDIGIT) ? 1 : 0; ast_debug(1, "Detected %sdigit '%c'\n", p->pulsedial ? "pulse ": "", res & 0xff); #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig) && ((struct sig_pri_chan *) p->sig_pvt)->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING && p->pri && (p->pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)) { /* absorb event */ } else #endif /* defined(HAVE_PRI) */ { /* Unmute conference */ dahdi_confmute(p, 0); p->subs[idx].f.frametype = AST_FRAME_DTMF_END; p->subs[idx].f.subclass.integer = res & 0xff; dahdi_handle_dtmf(ast, idx, &f); } return f; } if (res & DAHDI_EVENT_DTMFDOWN) { ast_debug(1, "DTMF Down '%c'\n", res & 0xff); #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig) && ((struct sig_pri_chan *) p->sig_pvt)->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING && p->pri && (p->pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)) { /* absorb event */ } else #endif /* defined(HAVE_PRI) */ { /* Mute conference */ dahdi_confmute(p, 1); p->subs[idx].f.frametype = AST_FRAME_DTMF_BEGIN; p->subs[idx].f.subclass.integer = res & 0xff; dahdi_handle_dtmf(ast, idx, &f); } return &p->subs[idx].f; } switch (res) { case DAHDI_EVENT_EC_DISABLED: ast_verb(3, "Channel %d echo canceler disabled.\n", p->channel); p->echocanon = 0; break; #ifdef HAVE_DAHDI_ECHOCANCEL_FAX_MODE case DAHDI_EVENT_TX_CED_DETECTED: ast_verb(3, "Channel %d detected a CED tone towards the network.\n", p->channel); break; case DAHDI_EVENT_RX_CED_DETECTED: ast_verb(3, "Channel %d detected a CED tone from the network.\n", p->channel); break; case DAHDI_EVENT_EC_NLP_DISABLED: ast_verb(3, "Channel %d echo canceler disabled its NLP.\n", p->channel); break; case DAHDI_EVENT_EC_NLP_ENABLED: ast_verb(3, "Channel %d echo canceler enabled its NLP.\n", p->channel); break; #endif case DAHDI_EVENT_BITSCHANGED: #ifdef HAVE_OPENR2 if (p->sig != SIG_MFCR2) { ast_log(LOG_WARNING, "Received bits changed on %s signalling?\n", sig2str(p->sig)); } else { ast_debug(1, "bits changed in chan %d\n", p->channel); openr2_chan_handle_cas(p->r2chan); } #else ast_log(LOG_WARNING, "Received bits changed on %s signalling?\n", sig2str(p->sig)); #endif break; case DAHDI_EVENT_PULSE_START: /* Stop tone if there's a pulse start and the PBX isn't started */ if (!ast_channel_pbx(ast)) tone_zone_play_tone(p->subs[idx].dfd, -1); break; case DAHDI_EVENT_DIALCOMPLETE: /* DAHDI has completed dialing all digits sent using DAHDI_DIAL. */ #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig)) { if (p->inalarm) { break; } if (ioctl(p->subs[idx].dfd, DAHDI_DIALING, &x) == -1) { ast_debug(1, "DAHDI_DIALING ioctl failed on %s: %s\n", ast_channel_name(ast), strerror(errno)); return NULL; } if (x) { /* Still dialing in DAHDI driver */ break; } /* * The ast channel is locked and the private may be locked more * than once. */ sig_pri_dial_complete(p->sig_pvt, ast); break; } #endif /* defined(HAVE_PRI) */ #ifdef HAVE_OPENR2 if ((p->sig & SIG_MFCR2) && p->r2chan && ast_channel_state(ast) != AST_STATE_UP) { /* we don't need to do anything for this event for R2 signaling if the call is being setup */ break; } #endif if (p->inalarm) break; if ((p->radio || (p->oprmode < 0))) break; if (ioctl(p->subs[idx].dfd,DAHDI_DIALING,&x) == -1) { ast_debug(1, "DAHDI_DIALING ioctl failed on %s: %s\n",ast_channel_name(ast), strerror(errno)); return NULL; } if (!x) { /* if not still dialing in driver */ dahdi_ec_enable(p); if (p->echobreak) { dahdi_train_ec(p); ast_copy_string(p->dop.dialstr, p->echorest, sizeof(p->dop.dialstr)); p->dop.op = DAHDI_DIAL_OP_REPLACE; res = dahdi_dial_str(p, p->dop.op, p->dop.dialstr); p->echobreak = 0; } else { p->dialing = 0; if ((mysig == SIG_E911) || (mysig == SIG_FGC_CAMA) || (mysig == SIG_FGC_CAMAMF)) { /* if thru with dialing after offhook */ if (ast_channel_state(ast) == AST_STATE_DIALING_OFFHOOK) { ast_setstate(ast, AST_STATE_UP); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; break; } else { /* if to state wait for offhook to dial rest */ /* we now wait for off hook */ ast_setstate(ast,AST_STATE_DIALING_OFFHOOK); } } if (ast_channel_state(ast) == AST_STATE_DIALING) { if ((p->callprogress & CALLPROGRESS_PROGRESS) && CANPROGRESSDETECT(p) && p->dsp && p->outgoing) { ast_debug(1, "Done dialing, but waiting for progress detection before doing more...\n"); } else if (p->confirmanswer || (!p->dialednone && ((mysig == SIG_EM) || (mysig == SIG_EM_E1) || (mysig == SIG_EMWINK) || (mysig == SIG_FEATD) || (mysig == SIG_FEATDMF_TA) || (mysig == SIG_FEATDMF) || (mysig == SIG_E911) || (mysig == SIG_FGC_CAMA) || (mysig == SIG_FGC_CAMAMF) || (mysig == SIG_FEATB) || (mysig == SIG_SF) || (mysig == SIG_SFWINK) || (mysig == SIG_SF_FEATD) || (mysig == SIG_SF_FEATDMF) || (mysig == SIG_SF_FEATB)))) { ast_setstate(ast, AST_STATE_RINGING); } else if (!p->answeronpolarityswitch) { ast_setstate(ast, AST_STATE_UP); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; /* If aops=0 and hops=1, this is necessary */ p->polarity = POLARITY_REV; } else { /* Start clean, so we can catch the change to REV polarity when party answers */ p->polarity = POLARITY_IDLE; } } } } break; case DAHDI_EVENT_ALARM: switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: sig_pri_chan_alarm_notify(p->sig_pvt, 0); break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: sig_ss7_set_alarm(p->sig_pvt, 1); break; #endif /* defined(HAVE_SS7) */ default: p->inalarm = 1; break; } res = get_alarms(p); handle_alarms(p, res); #ifdef HAVE_PRI if (!p->pri || !p->pri->pri || pri_get_timer(p->pri->pri, PRI_TIMER_T309) < 0) { /* fall through intentionally */ } else { break; } #endif #if defined(HAVE_SS7) if (p->sig == SIG_SS7) break; #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 if (p->sig == SIG_MFCR2) break; #endif case DAHDI_EVENT_ONHOOK: if (p->radio) { p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_RADIO_UNKEY; break; } if (p->oprmode < 0) { if (p->oprmode != -1) break; if ((p->sig == SIG_FXOLS) || (p->sig == SIG_FXOKS) || (p->sig == SIG_FXOGS)) { /* Make sure it starts ringing */ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RINGOFF); dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RING); save_conference(p->oprpeer); tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE); } break; } switch (p->sig) { case SIG_FXOLS: case SIG_FXOGS: case SIG_FXOKS: /* Check for some special conditions regarding call waiting */ if (idx == SUB_REAL) { /* The normal line was hung up */ if (p->subs[SUB_CALLWAIT].owner) { /* There's a call waiting call, so ring the phone, but make it unowned in the mean time */ swap_subs(p, SUB_CALLWAIT, SUB_REAL); ast_verb(3, "Channel %d still has (callwait) call, ringing phone\n", p->channel); unalloc_sub(p, SUB_CALLWAIT); #if 0 p->subs[idx].needanswer = 0; p->subs[idx].needringing = 0; #endif p->callwaitingrepeat = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; p->owner = NULL; /* Don't start streaming audio yet if the incoming call isn't up yet */ if (ast_channel_state(p->subs[SUB_REAL].owner) != AST_STATE_UP) p->dialing = 1; dahdi_ring_phone(p); } else if (p->subs[SUB_THREEWAY].owner) { unsigned int mssinceflash; /* Here we have to retain the lock on both the main channel, the 3-way channel, and the private structure -- not especially easy or clean */ while (p->subs[SUB_THREEWAY].owner && ast_channel_trylock(p->subs[SUB_THREEWAY].owner)) { /* Yuck, didn't get the lock on the 3-way, gotta release everything and re-grab! */ DLA_UNLOCK(&p->lock); CHANNEL_DEADLOCK_AVOIDANCE(ast); /* We can grab ast and p in that order, without worry. We should make sure nothing seriously bad has happened though like some sort of bizarre double masquerade! */ DLA_LOCK(&p->lock); if (p->owner != ast) { ast_log(LOG_WARNING, "This isn't good...\n"); return NULL; } } if (!p->subs[SUB_THREEWAY].owner) { ast_log(LOG_NOTICE, "Whoa, threeway disappeared kinda randomly.\n"); return NULL; } mssinceflash = ast_tvdiff_ms(ast_tvnow(), p->flashtime); ast_debug(1, "Last flash was %u ms ago\n", mssinceflash); if (mssinceflash < MIN_MS_SINCE_FLASH) { /* It hasn't been long enough since the last flashook. This is probably a bounce on hanging up. Hangup both channels now */ if (p->subs[SUB_THREEWAY].owner) ast_queue_hangup_with_cause(p->subs[SUB_THREEWAY].owner, AST_CAUSE_NO_ANSWER); ast_channel_softhangup_internal_flag_add(p->subs[SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); ast_debug(1, "Looks like a bounced flash, hanging up both calls on %d\n", p->channel); ast_channel_unlock(p->subs[SUB_THREEWAY].owner); } else if ((ast_channel_pbx(ast)) || (ast_channel_state(ast) == AST_STATE_UP)) { if (p->transfer) { /* In any case this isn't a threeway call anymore */ p->subs[SUB_REAL].inthreeway = 0; p->subs[SUB_THREEWAY].inthreeway = 0; /* Only attempt transfer if the phone is ringing; why transfer to busy tone eh? */ if (!p->transfertobusy && ast_channel_state(ast) == AST_STATE_BUSY) { ast_channel_unlock(p->subs[SUB_THREEWAY].owner); /* Swap subs and dis-own channel */ swap_subs(p, SUB_THREEWAY, SUB_REAL); p->owner = NULL; /* Ring the phone */ dahdi_ring_phone(p); } else if (!attempt_transfer(p)) { /* * Transfer successful. Don't actually hang up at this point. * Let our channel legs of the calls die off as the transfer * percolates through the core. */ break; } } else { ast_channel_softhangup_internal_flag_add(p->subs[SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); if (p->subs[SUB_THREEWAY].owner) ast_channel_unlock(p->subs[SUB_THREEWAY].owner); } } else { ast_channel_unlock(p->subs[SUB_THREEWAY].owner); /* Swap subs and dis-own channel */ swap_subs(p, SUB_THREEWAY, SUB_REAL); p->owner = NULL; /* Ring the phone */ dahdi_ring_phone(p); } } } else { ast_log(LOG_WARNING, "Got a hangup and my index is %d?\n", idx); } /* Fall through */ default: dahdi_ec_disable(p); return NULL; } break; case DAHDI_EVENT_RINGOFFHOOK: if (p->inalarm) break; if (p->oprmode < 0) { if ((p->sig == SIG_FXOLS) || (p->sig == SIG_FXOKS) || (p->sig == SIG_FXOGS)) { /* Make sure it stops ringing */ dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RINGOFF); tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].dfd, -1); restore_conference(p->oprpeer); } break; } if (p->radio) { p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_RADIO_KEY; break; } /* for E911, its supposed to wait for offhook then dial the second half of the dial string */ if (((mysig == SIG_E911) || (mysig == SIG_FGC_CAMA) || (mysig == SIG_FGC_CAMAMF)) && (ast_channel_state(ast) == AST_STATE_DIALING_OFFHOOK)) { c = strchr(p->dialdest, '/'); if (c) c++; else c = p->dialdest; if (*c) snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*0%s#", c); else ast_copy_string(p->dop.dialstr,"M*2#", sizeof(p->dop.dialstr)); if (strlen(p->dop.dialstr) > 4) { memset(p->echorest, 'w', sizeof(p->echorest) - 1); strcpy(p->echorest + (p->echotraining / 401) + 1, p->dop.dialstr + strlen(p->dop.dialstr) - 2); p->echorest[sizeof(p->echorest) - 1] = '\0'; p->echobreak = 1; p->dop.dialstr[strlen(p->dop.dialstr)-2] = '\0'; } else p->echobreak = 0; if (dahdi_dial_str(p, p->dop.op, p->dop.dialstr)) { x = DAHDI_ONHOOK; ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); return NULL; } p->dialing = 1; return &p->subs[idx].f; } switch (p->sig) { case SIG_FXOLS: case SIG_FXOGS: case SIG_FXOKS: switch (ast_channel_state(ast)) { case AST_STATE_RINGING: dahdi_ec_enable(p); dahdi_train_ec(p); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; /* Make sure it stops ringing */ p->subs[SUB_REAL].needringing = 0; dahdi_set_hook(p->subs[idx].dfd, DAHDI_OFFHOOK); ast_debug(1, "channel %d answered\n", p->channel); /* Cancel any running CallerID spill */ ast_free(p->cidspill); p->cidspill = NULL; restore_conference(p); p->dialing = 0; p->callwaitcas = 0; if (p->confirmanswer) { /* Ignore answer if "confirm answer" is enabled */ p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; } else if (!ast_strlen_zero(p->dop.dialstr)) { /* nick@dccinc.com 4/3/03 - fxo should be able to do deferred dialing */ res = dahdi_dial_str(p, p->dop.op, p->dop.dialstr); if (res) { p->dop.dialstr[0] = '\0'; return NULL; } else { ast_debug(1, "Sent FXO deferred digit string: %s\n", p->dop.dialstr); p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; p->dialing = 1; } p->dop.dialstr[0] = '\0'; ast_setstate(ast, AST_STATE_DIALING); } else ast_setstate(ast, AST_STATE_UP); return &p->subs[idx].f; case AST_STATE_DOWN: ast_setstate(ast, AST_STATE_RING); ast_channel_rings_set(ast, 1); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_OFFHOOK; ast_debug(1, "channel %d picked up\n", p->channel); return &p->subs[idx].f; case AST_STATE_UP: /* Make sure it stops ringing */ dahdi_set_hook(p->subs[idx].dfd, DAHDI_OFFHOOK); /* Okay -- probably call waiting*/ ast_queue_unhold(p->owner); p->subs[idx].needunhold = 1; break; case AST_STATE_RESERVED: /* Start up dialtone */ if (has_voicemail(p)) res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_STUTTER); else res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_DIALTONE); break; default: ast_log(LOG_WARNING, "FXO phone off hook in weird state %u??\n", ast_channel_state(ast)); } break; case SIG_FXSLS: case SIG_FXSGS: case SIG_FXSKS: if (ast_channel_state(ast) == AST_STATE_RING) { p->ringt = p->ringt_base; } /* If we get a ring then we cannot be in * reversed polarity. So we reset to idle */ ast_debug(1, "Setting IDLE polarity due " "to ring. Old polarity was %d\n", p->polarity); p->polarity = POLARITY_IDLE; /* Fall through */ case SIG_EM: case SIG_EM_E1: case SIG_EMWINK: case SIG_FEATD: case SIG_FEATDMF: case SIG_FEATDMF_TA: case SIG_E911: case SIG_FGC_CAMA: case SIG_FGC_CAMAMF: case SIG_FEATB: case SIG_SF: case SIG_SFWINK: case SIG_SF_FEATD: case SIG_SF_FEATDMF: case SIG_SF_FEATB: if (ast_channel_state(ast) == AST_STATE_PRERING) ast_setstate(ast, AST_STATE_RING); if ((ast_channel_state(ast) == AST_STATE_DOWN) || (ast_channel_state(ast) == AST_STATE_RING)) { ast_debug(1, "Ring detected\n"); p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_RING; } else if (p->outgoing && ((ast_channel_state(ast) == AST_STATE_RINGING) || (ast_channel_state(ast) == AST_STATE_DIALING))) { ast_debug(1, "Line answered\n"); if (p->confirmanswer) { p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; } else { p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; ast_setstate(ast, AST_STATE_UP); } } else if (ast_channel_state(ast) != AST_STATE_RING) ast_log(LOG_WARNING, "Ring/Off-hook in strange state %u on channel %d\n", ast_channel_state(ast), p->channel); break; default: ast_log(LOG_WARNING, "Don't know how to handle ring/off hook for signalling %d\n", p->sig); } break; case DAHDI_EVENT_RINGBEGIN: switch (p->sig) { case SIG_FXSLS: case SIG_FXSGS: case SIG_FXSKS: if (ast_channel_state(ast) == AST_STATE_RING) { p->ringt = p->ringt_base; } break; } break; case DAHDI_EVENT_RINGERON: break; case DAHDI_EVENT_NOALARM: switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: sig_pri_chan_alarm_notify(p->sig_pvt, 1); break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: sig_ss7_set_alarm(p->sig_pvt, 0); break; #endif /* defined(HAVE_SS7) */ default: p->inalarm = 0; break; } handle_clear_alarms(p); break; case DAHDI_EVENT_WINKFLASH: if (p->inalarm) break; if (p->radio) break; if (p->oprmode < 0) break; if (p->oprmode > 1) { struct dahdi_params par; memset(&par, 0, sizeof(par)); if (ioctl(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &par) != -1) { if (!par.rxisoffhook) { /* Make sure it stops ringing */ dahdi_set_hook(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_RINGOFF); dahdi_set_hook(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_RING); save_conference(p); tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE); } } break; } /* Remember last time we got a flash-hook */ p->flashtime = ast_tvnow(); switch (mysig) { case SIG_FXOLS: case SIG_FXOGS: case SIG_FXOKS: ast_debug(1, "Winkflash, index: %d, normal: %d, callwait: %d, thirdcall: %d\n", idx, p->subs[SUB_REAL].dfd, p->subs[SUB_CALLWAIT].dfd, p->subs[SUB_THREEWAY].dfd); /* Cancel any running CallerID spill */ ast_free(p->cidspill); p->cidspill = NULL; restore_conference(p); p->callwaitcas = 0; if (idx != SUB_REAL) { ast_log(LOG_WARNING, "Got flash hook with index %d on channel %d?!?\n", idx, p->channel); goto winkflashdone; } if (p->subs[SUB_CALLWAIT].owner) { /* Swap to call-wait */ swap_subs(p, SUB_REAL, SUB_CALLWAIT); tone_zone_play_tone(p->subs[SUB_REAL].dfd, -1); p->owner = p->subs[SUB_REAL].owner; ast_debug(1, "Making %s the new owner\n", ast_channel_name(p->owner)); if (ast_channel_state(p->owner) == AST_STATE_RINGING) { ast_setstate(p->owner, AST_STATE_UP); p->subs[SUB_REAL].needanswer = 1; } p->callwaitingrepeat = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; /* Start music on hold if appropriate */ if (!p->subs[SUB_CALLWAIT].inthreeway) { ast_queue_hold(p->subs[SUB_CALLWAIT].owner, p->mohsuggest); } p->subs[SUB_CALLWAIT].needhold = 1; ast_queue_hold(p->subs[SUB_REAL].owner, p->mohsuggest); p->subs[SUB_REAL].needunhold = 1; } else if (!p->subs[SUB_THREEWAY].owner) { if (!p->threewaycalling) { /* Just send a flash if no 3-way calling */ p->subs[SUB_REAL].needflash = 1; goto winkflashdone; } else if (!check_for_conference(p)) { struct ast_callid *callid = NULL; int callid_created; char cid_num[256]; char cid_name[256]; cid_num[0] = 0; cid_name[0] = 0; if (p->dahditrcallerid && p->owner) { if (ast_channel_caller(p->owner)->id.number.valid && ast_channel_caller(p->owner)->id.number.str) { ast_copy_string(cid_num, ast_channel_caller(p->owner)->id.number.str, sizeof(cid_num)); } if (ast_channel_caller(p->owner)->id.name.valid && ast_channel_caller(p->owner)->id.name.str) { ast_copy_string(cid_name, ast_channel_caller(p->owner)->id.name.str, sizeof(cid_name)); } } /* XXX This section needs much more error checking!!! XXX */ /* Start a 3-way call if feasible */ if (!((ast_channel_pbx(ast)) || (ast_channel_state(ast) == AST_STATE_UP) || (ast_channel_state(ast) == AST_STATE_RING))) { ast_debug(1, "Flash when call not up or ringing\n"); goto winkflashdone; } if (alloc_sub(p, SUB_THREEWAY)) { ast_log(LOG_WARNING, "Unable to allocate three-way subchannel\n"); goto winkflashdone; } callid_created = ast_callid_threadstorage_auto(&callid); /* * Make new channel * * We cannot hold the p or ast locks while creating a new * channel. */ ast_mutex_unlock(&p->lock); ast_channel_unlock(ast); chan = dahdi_new(p, AST_STATE_RESERVED, 0, SUB_THREEWAY, 0, NULL, NULL, callid); ast_channel_lock(ast); ast_mutex_lock(&p->lock); if (p->dahditrcallerid) { if (!p->origcid_num) p->origcid_num = ast_strdup(p->cid_num); if (!p->origcid_name) p->origcid_name = ast_strdup(p->cid_name); ast_copy_string(p->cid_num, cid_num, sizeof(p->cid_num)); ast_copy_string(p->cid_name, cid_name, sizeof(p->cid_name)); } /* Swap things around between the three-way and real call */ swap_subs(p, SUB_THREEWAY, SUB_REAL); /* Disable echo canceller for better dialing */ dahdi_ec_disable(p); res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_DIALRECALL); if (res) ast_log(LOG_WARNING, "Unable to start dial recall tone on channel %d\n", p->channel); p->owner = chan; if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", p->channel); } else if (ast_pthread_create_detached(&threadid, NULL, analog_ss_thread, chan)) { ast_log(LOG_WARNING, "Unable to start simple switch on channel %d\n", p->channel); res = tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); dahdi_ec_enable(p); ast_hangup(chan); } else { ast_verb(3, "Started three way call on channel %d\n", p->channel); /* Start music on hold */ ast_queue_hold(p->subs[SUB_THREEWAY].owner, p->mohsuggest); p->subs[SUB_THREEWAY].needhold = 1; } ast_callid_threadstorage_auto_clean(callid, callid_created); } } else { /* Already have a 3 way call */ if (p->subs[SUB_THREEWAY].inthreeway) { /* Call is already up, drop the last person */ ast_debug(1, "Got flash with three way call up, dropping last call on %d\n", p->channel); /* If the primary call isn't answered yet, use it */ if ((ast_channel_state(p->subs[SUB_REAL].owner) != AST_STATE_UP) && (ast_channel_state(p->subs[SUB_THREEWAY].owner) == AST_STATE_UP)) { /* Swap back -- we're dropping the real 3-way that isn't finished yet*/ swap_subs(p, SUB_THREEWAY, SUB_REAL); p->owner = p->subs[SUB_REAL].owner; } /* Drop the last call and stop the conference */ ast_verb(3, "Dropping three-way call on %s\n", ast_channel_name(p->subs[SUB_THREEWAY].owner)); ast_channel_softhangup_internal_flag_add(p->subs[SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); p->subs[SUB_REAL].inthreeway = 0; p->subs[SUB_THREEWAY].inthreeway = 0; } else { /* Lets see what we're up to */ if (((ast_channel_pbx(ast)) || (ast_channel_state(ast) == AST_STATE_UP)) && (p->transfertobusy || (ast_channel_state(ast) != AST_STATE_BUSY))) { int otherindex = SUB_THREEWAY; ast_verb(3, "Building conference call with %s and %s\n", ast_channel_name(p->subs[SUB_THREEWAY].owner), ast_channel_name(p->subs[SUB_REAL].owner)); /* Put them in the threeway, and flip */ p->subs[SUB_THREEWAY].inthreeway = 1; p->subs[SUB_REAL].inthreeway = 1; if (ast_channel_state(ast) == AST_STATE_UP) { swap_subs(p, SUB_THREEWAY, SUB_REAL); otherindex = SUB_REAL; } if (p->subs[otherindex].owner) { ast_queue_unhold(p->subs[otherindex].owner); } p->subs[otherindex].needunhold = 1; p->owner = p->subs[SUB_REAL].owner; } else { ast_verb(3, "Dumping incomplete call on on %s\n", ast_channel_name(p->subs[SUB_THREEWAY].owner)); swap_subs(p, SUB_THREEWAY, SUB_REAL); ast_channel_softhangup_internal_flag_add(p->subs[SUB_THREEWAY].owner, AST_SOFTHANGUP_DEV); p->owner = p->subs[SUB_REAL].owner; if (p->subs[SUB_REAL].owner) { ast_queue_unhold(p->subs[SUB_REAL].owner); } p->subs[SUB_REAL].needunhold = 1; dahdi_ec_enable(p); } } } winkflashdone: dahdi_conf_update(p); break; case SIG_EM: case SIG_EM_E1: case SIG_FEATD: case SIG_SF: case SIG_SFWINK: case SIG_SF_FEATD: case SIG_FXSLS: case SIG_FXSGS: if (p->dialing) ast_debug(1, "Ignoring wink on channel %d\n", p->channel); else ast_debug(1, "Got wink in weird state %u on channel %d\n", ast_channel_state(ast), p->channel); break; case SIG_FEATDMF_TA: switch (p->whichwink) { case 0: ast_debug(1, "ANI2 set to '%d' and ANI is '%s'\n", ast_channel_caller(p->owner)->ani2, S_COR(ast_channel_caller(p->owner)->ani.number.valid, ast_channel_caller(p->owner)->ani.number.str, "")); snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "M*%d%s#", ast_channel_caller(p->owner)->ani2, S_COR(ast_channel_caller(p->owner)->ani.number.valid, ast_channel_caller(p->owner)->ani.number.str, "")); break; case 1: ast_copy_string(p->dop.dialstr, p->finaldial, sizeof(p->dop.dialstr)); break; case 2: ast_log(LOG_WARNING, "Received unexpected wink on channel of type SIG_FEATDMF_TA\n"); return NULL; } p->whichwink++; /* Fall through */ case SIG_FEATDMF: case SIG_E911: case SIG_FGC_CAMAMF: case SIG_FGC_CAMA: case SIG_FEATB: case SIG_SF_FEATDMF: case SIG_SF_FEATB: case SIG_EMWINK: /* FGD MF and EMWINK *Must* wait for wink */ if (!ast_strlen_zero(p->dop.dialstr)) { res = dahdi_dial_str(p, p->dop.op, p->dop.dialstr); if (res) { p->dop.dialstr[0] = '\0'; return NULL; } else ast_debug(1, "Sent deferred digit string: %s\n", p->dop.dialstr); } p->dop.dialstr[0] = '\0'; break; default: ast_log(LOG_WARNING, "Don't know how to handle ring/off hook for signalling %d\n", p->sig); } break; case DAHDI_EVENT_HOOKCOMPLETE: if (p->inalarm) break; if ((p->radio || (p->oprmode < 0))) break; if (p->waitingfordt.tv_sec) break; switch (mysig) { case SIG_FXSLS: /* only interesting for FXS */ case SIG_FXSGS: case SIG_FXSKS: case SIG_EM: case SIG_EM_E1: case SIG_EMWINK: case SIG_FEATD: case SIG_SF: case SIG_SFWINK: case SIG_SF_FEATD: if (!ast_strlen_zero(p->dop.dialstr)) { res = dahdi_dial_str(p, p->dop.op, p->dop.dialstr); if (res) { p->dop.dialstr[0] = '\0'; return NULL; } else ast_debug(1, "Sent deferred digit string: %s\n", p->dop.dialstr); } p->dop.dialstr[0] = '\0'; p->dop.op = DAHDI_DIAL_OP_REPLACE; break; case SIG_FEATDMF: case SIG_FEATDMF_TA: case SIG_E911: case SIG_FGC_CAMA: case SIG_FGC_CAMAMF: case SIG_FEATB: case SIG_SF_FEATDMF: case SIG_SF_FEATB: ast_debug(1, "Got hook complete in MF FGD, waiting for wink now on channel %d\n",p->channel); break; default: break; } break; case DAHDI_EVENT_POLARITY: /* * If we get a Polarity Switch event, check to see * if we should change the polarity state and * mark the channel as UP or if this is an indication * of remote end disconnect. */ if (p->polarity == POLARITY_IDLE) { p->polarity = POLARITY_REV; if (p->answeronpolarityswitch && ((ast_channel_state(ast) == AST_STATE_DIALING) || (ast_channel_state(ast) == AST_STATE_RINGING))) { ast_debug(1, "Answering on polarity switch!\n"); ast_setstate(p->owner, AST_STATE_UP); if (p->hanguponpolarityswitch) { p->polaritydelaytv = ast_tvnow(); } } else ast_debug(1, "Ignore switch to REVERSED Polarity on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } /* Removed else statement from here as it was preventing hangups from ever happening*/ /* Added AST_STATE_RING in if statement below to deal with calling party hangups that take place when ringing */ if (p->hanguponpolarityswitch && (p->polarityonanswerdelay > 0) && (p->polarity == POLARITY_REV) && ((ast_channel_state(ast) == AST_STATE_UP) || (ast_channel_state(ast) == AST_STATE_RING)) ) { /* Added log_debug information below to provide a better indication of what is going on */ ast_debug(1, "Polarity Reversal event occured - DEBUG 1: channel %d, state %u, pol= %d, aonp= %d, honp= %d, pdelay= %d, tv= %" PRIi64 "\n", p->channel, ast_channel_state(ast), p->polarity, p->answeronpolarityswitch, p->hanguponpolarityswitch, p->polarityonanswerdelay, ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) ); if (ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) > p->polarityonanswerdelay) { ast_debug(1, "Polarity Reversal detected and now Hanging up on channel %d\n", p->channel); ast_softhangup(p->owner, AST_SOFTHANGUP_EXPLICIT); p->polarity = POLARITY_IDLE; } else ast_debug(1, "Polarity Reversal detected but NOT hanging up (too close to answer event) on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } else { p->polarity = POLARITY_IDLE; ast_debug(1, "Ignoring Polarity switch to IDLE on channel %d, state %u\n", p->channel, ast_channel_state(ast)); } /* Added more log_debug information below to provide a better indication of what is going on */ ast_debug(1, "Polarity Reversal event occured - DEBUG 2: channel %d, state %u, pol= %d, aonp= %d, honp= %d, pdelay= %d, tv= %" PRIi64 "\n", p->channel, ast_channel_state(ast), p->polarity, p->answeronpolarityswitch, p->hanguponpolarityswitch, p->polarityonanswerdelay, ast_tvdiff_ms(ast_tvnow(), p->polaritydelaytv) ); break; default: ast_debug(1, "Dunno what to do with event %d on channel %d\n", res, p->channel); } return &p->subs[idx].f; } static struct ast_frame *__dahdi_exception(struct ast_channel *ast) { int res; int idx; struct ast_frame *f; int usedindex = -1; struct dahdi_pvt *p = ast_channel_tech_pvt(ast); if ((idx = dahdi_get_index(ast, p, 0)) < 0) { idx = SUB_REAL; } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.datalen = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.delivery = ast_tv(0,0); p->subs[idx].f.src = "dahdi_exception"; p->subs[idx].f.data.ptr = NULL; if ((!p->owner) && (!(p->radio || (p->oprmode < 0)))) { /* If nobody owns us, absorb the event appropriately, otherwise we loop indefinitely. This occurs when, during call waiting, the other end hangs up our channel so that it no longer exists, but we have neither FLASH'd nor ONHOOK'd to signify our desire to change to the other channel. */ if (p->fake_event) { res = p->fake_event; p->fake_event = 0; } else res = dahdi_get_event(p->subs[SUB_REAL].dfd); /* Switch to real if there is one and this isn't something really silly... */ if ((res != DAHDI_EVENT_RINGEROFF) && (res != DAHDI_EVENT_RINGERON) && (res != DAHDI_EVENT_HOOKCOMPLETE)) { ast_debug(1, "Restoring owner of channel %d on event %d\n", p->channel, res); p->owner = p->subs[SUB_REAL].owner; if (p->owner) { ast_queue_unhold(p->owner); } p->subs[SUB_REAL].needunhold = 1; } switch (res) { case DAHDI_EVENT_ONHOOK: dahdi_ec_disable(p); if (p->owner) { ast_verb(3, "Channel %s still has call, ringing phone\n", ast_channel_name(p->owner)); dahdi_ring_phone(p); p->callwaitingrepeat = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; } else ast_log(LOG_WARNING, "Absorbed on hook, but nobody is left!?!?\n"); dahdi_conf_update(p); break; case DAHDI_EVENT_RINGOFFHOOK: dahdi_ec_enable(p); dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); if (p->owner && (ast_channel_state(p->owner) == AST_STATE_RINGING)) { p->subs[SUB_REAL].needanswer = 1; p->dialing = 0; } break; case DAHDI_EVENT_HOOKCOMPLETE: case DAHDI_EVENT_RINGERON: case DAHDI_EVENT_RINGEROFF: /* Do nothing */ break; case DAHDI_EVENT_WINKFLASH: p->flashtime = ast_tvnow(); if (p->owner) { ast_verb(3, "Channel %d flashed to other channel %s\n", p->channel, ast_channel_name(p->owner)); if (ast_channel_state(p->owner) != AST_STATE_UP) { /* Answer if necessary */ usedindex = dahdi_get_index(p->owner, p, 0); if (usedindex > -1) { p->subs[usedindex].needanswer = 1; } ast_setstate(p->owner, AST_STATE_UP); } p->callwaitingrepeat = 0; p->cidcwexpire = 0; p->cid_suppress_expire = 0; ast_queue_unhold(p->owner); p->subs[SUB_REAL].needunhold = 1; } else ast_log(LOG_WARNING, "Absorbed on hook, but nobody is left!?!?\n"); dahdi_conf_update(p); break; default: ast_log(LOG_WARNING, "Don't know how to absorb event %s\n", event2str(res)); } f = &p->subs[idx].f; return f; } if (!(p->radio || (p->oprmode < 0))) ast_debug(1, "Exception on %d, channel %d\n", ast_channel_fd(ast, 0), p->channel); /* If it's not us, return NULL immediately */ if (ast != p->owner) { if (p->owner) { ast_log(LOG_WARNING, "We're %s, not %s\n", ast_channel_name(ast), ast_channel_name(p->owner)); } f = &p->subs[idx].f; return f; } f = dahdi_handle_event(ast); if (!f) { const char *name = ast_strdupa(ast_channel_name(ast)); /* Tell the CDR this DAHDI device hung up */ ast_mutex_unlock(&p->lock); ast_channel_unlock(ast); ast_set_hangupsource(ast, name, 0); ast_channel_lock(ast); ast_mutex_lock(&p->lock); } return f; } static struct ast_frame *dahdi_exception(struct ast_channel *ast) { struct dahdi_pvt *p = ast_channel_tech_pvt(ast); struct ast_frame *f; ast_mutex_lock(&p->lock); if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { struct analog_pvt *analog_p = p->sig_pvt; f = analog_exception(analog_p, ast); } else { f = __dahdi_exception(ast); } ast_mutex_unlock(&p->lock); return f; } static struct ast_frame *dahdi_read(struct ast_channel *ast) { struct dahdi_pvt *p; int res; int idx; void *readbuf; struct ast_frame *f; /* * For analog channels, we must do deadlock avoidance because * analog ports can have more than one Asterisk channel using * the same private structure. */ p = ast_channel_tech_pvt(ast); while (ast_mutex_trylock(&p->lock)) { CHANNEL_DEADLOCK_AVOIDANCE(ast); /* * Check to see if the channel is still associated with the same * private structure. While the Asterisk channel was unlocked * the following events may have occured: * * 1) A masquerade may have associated the channel with another * technology or private structure. * * 2) For PRI calls, call signaling could change the channel * association to another B channel (private structure). */ if (ast_channel_tech_pvt(ast) != p) { /* The channel is no longer associated. Quit gracefully. */ return &ast_null_frame; } } idx = dahdi_get_index(ast, p, 0); /* Hang up if we don't really exist */ if (idx < 0) { ast_log(LOG_WARNING, "We don't exist?\n"); ast_mutex_unlock(&p->lock); return NULL; } if ((p->radio || (p->oprmode < 0)) && p->inalarm) { ast_mutex_unlock(&p->lock); return NULL; } p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.datalen = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.delivery = ast_tv(0,0); p->subs[idx].f.src = "dahdi_read"; p->subs[idx].f.data.ptr = NULL; /* make sure it sends initial key state as first frame */ if ((p->radio || (p->oprmode < 0)) && (!p->firstradio)) { struct dahdi_params ps; memset(&ps, 0, sizeof(ps)); if (ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &ps) < 0) { ast_mutex_unlock(&p->lock); return NULL; } p->firstradio = 1; p->subs[idx].f.frametype = AST_FRAME_CONTROL; if (ps.rxisoffhook) { p->subs[idx].f.subclass.integer = AST_CONTROL_RADIO_KEY; } else { p->subs[idx].f.subclass.integer = AST_CONTROL_RADIO_UNKEY; } ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } if (p->ringt > 0) { if (!(--p->ringt)) { ast_mutex_unlock(&p->lock); return NULL; } } #ifdef HAVE_OPENR2 if (p->mfcr2) { openr2_chan_process_event(p->r2chan); if (OR2_DIR_FORWARD == openr2_chan_get_direction(p->r2chan)) { struct ast_frame fr = { AST_FRAME_CONTROL, { AST_CONTROL_PROGRESS } }; /* if the call is already accepted and we already delivered AST_CONTROL_RINGING * now enqueue a progress frame to bridge the media up */ if (p->mfcr2_call_accepted && !p->mfcr2_progress_sent && ast_channel_state(ast) == AST_STATE_RINGING) { ast_debug(1, "Enqueuing progress frame after R2 accept in chan %d\n", p->channel); ast_queue_frame(p->owner, &fr); p->mfcr2_progress_sent = 1; } } } #endif if (p->subs[idx].needringing) { /* Send ringing frame if requested */ p->subs[idx].needringing = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_RINGING; ast_setstate(ast, AST_STATE_RINGING); ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } if (p->subs[idx].needbusy) { /* Send busy frame if requested */ p->subs[idx].needbusy = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_BUSY; ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } if (p->subs[idx].needcongestion) { /* Send congestion frame if requested */ p->subs[idx].needcongestion = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_CONGESTION; ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } if (p->subs[idx].needanswer) { /* Send answer frame if requested */ p->subs[idx].needanswer = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_ANSWER; ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } #ifdef HAVE_OPENR2 if (p->mfcr2 && openr2_chan_get_read_enabled(p->r2chan)) { /* openr2 took care of reading and handling any event (needanswer, needbusy etc), if we continue we will read() twice, lets just return a null frame. This should only happen when openr2 is dialing out */ ast_mutex_unlock(&p->lock); return &ast_null_frame; } #endif if (p->subs[idx].needflash) { /* Send answer frame if requested */ p->subs[idx].needflash = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_FLASH; ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } if (p->subs[idx].needhold) { /* Send answer frame if requested */ p->subs[idx].needhold = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_HOLD; ast_mutex_unlock(&p->lock); ast_debug(1, "Sending hold on '%s'\n", ast_channel_name(ast)); return &p->subs[idx].f; } if (p->subs[idx].needunhold) { /* Send answer frame if requested */ p->subs[idx].needunhold = 0; p->subs[idx].f.frametype = AST_FRAME_CONTROL; p->subs[idx].f.subclass.integer = AST_CONTROL_UNHOLD; ast_mutex_unlock(&p->lock); ast_debug(1, "Sending unhold on '%s'\n", ast_channel_name(ast)); return &p->subs[idx].f; } /* * If we have a fake_event, fake an exception to handle it only * if this channel owns the private. */ if (p->fake_event && p->owner == ast) { if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { struct analog_pvt *analog_p = p->sig_pvt; f = analog_exception(analog_p, ast); } else { f = __dahdi_exception(ast); } ast_mutex_unlock(&p->lock); return f; } if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_slin) == AST_FORMAT_CMP_EQUAL) { if (!p->subs[idx].linear) { p->subs[idx].linear = 1; res = dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); if (res) ast_log(LOG_WARNING, "Unable to set channel %d (index %d) to linear mode.\n", p->channel, idx); } } else { if (p->subs[idx].linear) { p->subs[idx].linear = 0; res = dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); if (res) ast_log(LOG_WARNING, "Unable to set channel %d (index %d) to companded mode.\n", p->channel, idx); } } readbuf = ((unsigned char *)p->subs[idx].buffer) + AST_FRIENDLY_OFFSET; CHECK_BLOCKING(ast); res = read(p->subs[idx].dfd, readbuf, p->subs[idx].linear ? READ_SIZE * 2 : READ_SIZE); ast_clear_flag(ast_channel_flags(ast), AST_FLAG_BLOCKING); /* Check for hangup */ if (res < 0) { f = NULL; if (res == -1) { if (errno == EAGAIN) { /* Return "NULL" frame if there is nobody there */ ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } else if (errno == ELAST) { if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { struct analog_pvt *analog_p = p->sig_pvt; f = analog_exception(analog_p, ast); } else { f = __dahdi_exception(ast); } } else ast_log(LOG_WARNING, "dahdi_rec: %s\n", strerror(errno)); } ast_mutex_unlock(&p->lock); return f; } if (res != (p->subs[idx].linear ? READ_SIZE * 2 : READ_SIZE)) { ast_debug(1, "Short read (%d/%d), must be an event...\n", res, p->subs[idx].linear ? READ_SIZE * 2 : READ_SIZE); if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { struct analog_pvt *analog_p = p->sig_pvt; f = analog_exception(analog_p, ast); } else { f = __dahdi_exception(ast); } ast_mutex_unlock(&p->lock); return f; } if (p->tdd) { /* if in TDD mode, see if we receive that */ int c; c = tdd_feed(p->tdd,readbuf,READ_SIZE); if (c < 0) { ast_debug(1,"tdd_feed failed\n"); ast_mutex_unlock(&p->lock); return NULL; } if (c) { /* if a char to return */ p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.frametype = AST_FRAME_TEXT; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = AST_FRIENDLY_OFFSET; p->subs[idx].f.data.ptr = p->subs[idx].buffer + AST_FRIENDLY_OFFSET; p->subs[idx].f.datalen = 1; *((char *) p->subs[idx].f.data.ptr) = c; ast_mutex_unlock(&p->lock); return &p->subs[idx].f; } } if (idx == SUB_REAL) { /* Ensure the CW timers decrement only on a single subchannel */ if (p->cidcwexpire) { if (!--p->cidcwexpire) { /* Expired CID/CW */ ast_verb(3, "CPE does not support Call Waiting Caller*ID.\n"); restore_conference(p); } } if (p->cid_suppress_expire) { --p->cid_suppress_expire; } if (p->callwaitingrepeat) { if (!--p->callwaitingrepeat) { /* Expired, Repeat callwaiting tone */ ++p->callwaitrings; dahdi_callwait(ast); } } } if (p->subs[idx].linear) { p->subs[idx].f.datalen = READ_SIZE * 2; } else p->subs[idx].f.datalen = READ_SIZE; /* Handle CallerID Transmission */ if ((p->owner == ast) && p->cidspill) { send_callerid(p); } p->subs[idx].f.frametype = AST_FRAME_VOICE; p->subs[idx].f.subclass.format = ast_channel_rawreadformat(ast); p->subs[idx].f.samples = READ_SIZE; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = AST_FRIENDLY_OFFSET; p->subs[idx].f.data.ptr = p->subs[idx].buffer + AST_FRIENDLY_OFFSET / sizeof(p->subs[idx].buffer[0]); #if 0 ast_debug(1, "Read %d of voice on %s\n", p->subs[idx].f.datalen, ast->name); #endif if ((p->dialing && !p->waitingfordt.tv_sec) || p->radio || /* Transmitting something */ (idx && (ast_channel_state(ast) != AST_STATE_UP)) || /* Three-way or callwait that isn't up */ ((idx == SUB_CALLWAIT) && !p->subs[SUB_CALLWAIT].inthreeway) /* Inactive and non-confed call-wait */ ) { /* Whoops, we're still dialing, or in a state where we shouldn't transmit.... don't send anything */ p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.data.ptr = NULL; p->subs[idx].f.datalen= 0; } if (p->dsp && (!p->ignoredtmf || p->callwaitcas || p->busydetect || p->callprogress || p->waitingfordt.tv_sec || p->dialtone_detect) && !idx) { /* Perform busy detection etc on the dahdi line */ int mute; f = ast_dsp_process(ast, p->dsp, &p->subs[idx].f); /* Check if DSP code thinks we should be muting this frame and mute the conference if so */ mute = ast_dsp_was_muted(p->dsp); if (p->muting != mute) { p->muting = mute; dahdi_confmute(p, mute); } if (f) { if ((p->dsp_features & DSP_FEATURE_WAITDIALTONE) && (p->dialtone_detect > 0) && !p->outgoing && ast_channel_state(ast) == AST_STATE_UP) { if (++p->dialtone_scanning_time_elapsed >= p->dialtone_detect) { p->dsp_features &= ~DSP_FEATURE_WAITDIALTONE; ast_dsp_set_features(p->dsp, p->dsp_features); } } if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_BUSY)) { if ((ast_channel_state(ast) == AST_STATE_UP) && !p->outgoing) { /* * Treat this as a "hangup" instead of a "busy" on the * assumption that a busy means the incoming call went away. */ ast_frfree(f); f = NULL; } } else if (p->dialtone_detect && !p->outgoing && f->frametype == AST_FRAME_VOICE) { if ((ast_dsp_get_tstate(p->dsp) == DSP_TONE_STATE_DIALTONE) && (ast_dsp_get_tcount(p->dsp) > 9)) { /* Dialtone detected on inbound call; hangup the channel */ ast_frfree(f); f = NULL; } } else if (f->frametype == AST_FRAME_DTMF_BEGIN || f->frametype == AST_FRAME_DTMF_END) { #ifdef HAVE_PRI if (dahdi_sig_pri_lib_handles(p->sig) && ((struct sig_pri_chan *) p->sig_pvt)->call_level < SIG_PRI_CALL_LEVEL_PROCEEDING && p->pri && ((!p->outgoing && (p->pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)) || (p->outgoing && (p->pri->overlapdial & DAHDI_OVERLAPDIAL_OUTGOING)))) { /* Don't accept in-band DTMF when in overlap dial mode */ ast_debug(1, "Absorbing inband %s DTMF digit: 0x%02X '%c' on %s\n", f->frametype == AST_FRAME_DTMF_BEGIN ? "begin" : "end", (unsigned)f->subclass.integer, f->subclass.integer, ast_channel_name(ast)); f->frametype = AST_FRAME_NULL; f->subclass.integer = 0; } #endif /* DSP clears us of being pulse */ p->pulsedial = 0; } else if (p->waitingfordt.tv_sec) { if (ast_tvdiff_ms(ast_tvnow(), p->waitingfordt) >= p->waitfordialtone ) { p->waitingfordt.tv_sec = 0; ast_log(LOG_WARNING, "Never saw dialtone on channel %d\n", p->channel); ast_frfree(f); f = NULL; } else if (f->frametype == AST_FRAME_VOICE) { f->frametype = AST_FRAME_NULL; f->subclass.integer = 0; if ((ast_dsp_get_tstate(p->dsp) == DSP_TONE_STATE_DIALTONE || ast_dsp_get_tstate(p->dsp) == DSP_TONE_STATE_RINGING) && ast_dsp_get_tcount(p->dsp) > 9) { p->waitingfordt.tv_sec = 0; p->dsp_features &= ~DSP_FEATURE_WAITDIALTONE; ast_dsp_set_features(p->dsp, p->dsp_features); ast_debug(1, "Got 10 samples of dialtone!\n"); if (!ast_strlen_zero(p->dop.dialstr)) { /* Dial deferred digits */ res = dahdi_dial_str(p, p->dop.op, p->dop.dialstr); if (res) { p->dop.dialstr[0] = '\0'; ast_mutex_unlock(&p->lock); ast_frfree(f); return NULL; } else { ast_debug(1, "Sent deferred digit string: %s\n", p->dop.dialstr); p->dialing = 1; p->dop.dialstr[0] = '\0'; p->dop.op = DAHDI_DIAL_OP_REPLACE; ast_setstate(ast, AST_STATE_DIALING); } } } } } } } else f = &p->subs[idx].f; if (f) { switch (f->frametype) { case AST_FRAME_DTMF_BEGIN: case AST_FRAME_DTMF_END: if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { analog_handle_dtmf(p->sig_pvt, ast, idx, &f); } else { dahdi_handle_dtmf(ast, idx, &f); } break; case AST_FRAME_VOICE: if (p->cidspill || p->cid_suppress_expire) { /* We are/were sending a caller id spill. Suppress any echo. */ p->subs[idx].f.frametype = AST_FRAME_NULL; p->subs[idx].f.subclass.integer = 0; p->subs[idx].f.samples = 0; p->subs[idx].f.mallocd = 0; p->subs[idx].f.offset = 0; p->subs[idx].f.data.ptr = NULL; p->subs[idx].f.datalen= 0; } break; default: break; } } ast_mutex_unlock(&p->lock); return f; } static int my_dahdi_write(struct dahdi_pvt *p, unsigned char *buf, int len, int idx, int linear) { int sent=0; int size; int res; int fd; fd = p->subs[idx].dfd; while (len) { size = len; if (size > (linear ? READ_SIZE * 2 : READ_SIZE)) size = (linear ? READ_SIZE * 2 : READ_SIZE); res = write(fd, buf, size); if (res != size) { ast_debug(1, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel); return sent; } len -= size; buf += size; } return sent; } static int dahdi_write(struct ast_channel *ast, struct ast_frame *frame) { struct dahdi_pvt *p = ast_channel_tech_pvt(ast); int res; int idx; idx = dahdi_get_index(ast, p, 0); if (idx < 0) { ast_log(LOG_WARNING, "%s doesn't really exist?\n", ast_channel_name(ast)); return -1; } /* Write a frame of (presumably voice) data */ if (frame->frametype != AST_FRAME_VOICE) { if (frame->frametype != AST_FRAME_IMAGE) ast_log(LOG_WARNING, "Don't know what to do with frame type '%u'\n", frame->frametype); return 0; } if (p->dialing) { ast_debug(5, "Dropping frame since I'm still dialing on %s...\n",ast_channel_name(ast)); return 0; } if (!p->owner) { ast_debug(5, "Dropping frame since there is no active owner on %s...\n",ast_channel_name(ast)); return 0; } if (p->cidspill) { ast_debug(5, "Dropping frame since I've still got a callerid spill on %s...\n", ast_channel_name(ast)); return 0; } /* Return if it's not valid data */ if (!frame->data.ptr || !frame->datalen) return 0; if (ast_format_cmp(frame->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) { if (!p->subs[idx].linear) { p->subs[idx].linear = 1; res = dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); if (res) ast_log(LOG_WARNING, "Unable to set linear mode on channel %d\n", p->channel); } res = my_dahdi_write(p, (unsigned char *)frame->data.ptr, frame->datalen, idx, 1); } else if (ast_format_cmp(frame->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL || ast_format_cmp(frame->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { /* x-law already */ if (p->subs[idx].linear) { p->subs[idx].linear = 0; res = dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); if (res) ast_log(LOG_WARNING, "Unable to set companded mode on channel %d\n", p->channel); } res = my_dahdi_write(p, (unsigned char *)frame->data.ptr, frame->datalen, idx, 0); } else { ast_log(LOG_WARNING, "Cannot handle frames in %s format\n", ast_format_get_name(frame->subclass.format)); return -1; } if (res < 0) { ast_log(LOG_WARNING, "write failed: %s\n", strerror(errno)); return -1; } return 0; } static int dahdi_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen) { struct dahdi_pvt *p = ast_channel_tech_pvt(chan); int res=-1; int idx; int func = DAHDI_FLASH; ast_mutex_lock(&p->lock); ast_debug(1, "Requested indication %d on channel %s\n", condition, ast_channel_name(chan)); switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: res = sig_pri_indicate(p->sig_pvt, chan, condition, data, datalen); ast_mutex_unlock(&p->lock); return res; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: res = sig_ss7_indicate(p->sig_pvt, chan, condition, data, datalen); ast_mutex_unlock(&p->lock); return res; #endif /* defined(HAVE_SS7) */ default: break; } #ifdef HAVE_OPENR2 if (p->mfcr2 && !p->mfcr2_call_accepted) { ast_mutex_unlock(&p->lock); /* if this is an R2 call and the call is not yet accepted, we don't want the tone indications to mess up with the MF tones */ return 0; } #endif idx = dahdi_get_index(chan, p, 0); if (idx == SUB_REAL) { switch (condition) { case AST_CONTROL_BUSY: res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_BUSY); break; case AST_CONTROL_RINGING: res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_RINGTONE); if (ast_channel_state(chan) != AST_STATE_UP) { if ((ast_channel_state(chan) != AST_STATE_RING) || ((p->sig != SIG_FXSKS) && (p->sig != SIG_FXSLS) && (p->sig != SIG_FXSGS))) ast_setstate(chan, AST_STATE_RINGING); } break; case AST_CONTROL_INCOMPLETE: ast_debug(1, "Received AST_CONTROL_INCOMPLETE on %s\n", ast_channel_name(chan)); /* act as a progress or proceeding, allowing the caller to enter additional numbers */ res = 0; break; case AST_CONTROL_PROCEEDING: ast_debug(1, "Received AST_CONTROL_PROCEEDING on %s\n", ast_channel_name(chan)); /* don't continue in ast_indicate */ res = 0; break; case AST_CONTROL_PROGRESS: ast_debug(1, "Received AST_CONTROL_PROGRESS on %s\n", ast_channel_name(chan)); /* don't continue in ast_indicate */ res = 0; break; case AST_CONTROL_CONGESTION: /* There are many cause codes that generate an AST_CONTROL_CONGESTION. */ switch (ast_channel_hangupcause(chan)) { case AST_CAUSE_USER_BUSY: case AST_CAUSE_NORMAL_CLEARING: case 0:/* Cause has not been set. */ /* Supply a more appropriate cause. */ ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION); break; default: break; } break; case AST_CONTROL_HOLD: ast_moh_start(chan, data, p->mohinterpret); break; case AST_CONTROL_UNHOLD: ast_moh_stop(chan); break; case AST_CONTROL_RADIO_KEY: if (p->radio) res = dahdi_set_hook(p->subs[idx].dfd, DAHDI_OFFHOOK); res = 0; break; case AST_CONTROL_RADIO_UNKEY: if (p->radio) res = dahdi_set_hook(p->subs[idx].dfd, DAHDI_RINGOFF); res = 0; break; case AST_CONTROL_FLASH: /* flash hookswitch */ if (ISTRUNK(p) && (p->sig != SIG_PRI)) { /* Clear out the dial buffer */ p->dop.dialstr[0] = '\0'; if ((ioctl(p->subs[SUB_REAL].dfd,DAHDI_HOOK,&func) == -1) && (errno != EINPROGRESS)) { ast_log(LOG_WARNING, "Unable to flash external trunk on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } else res = 0; } else res = 0; break; case AST_CONTROL_SRCUPDATE: res = 0; break; case -1: res = tone_zone_play_tone(p->subs[idx].dfd, -1); break; } } else { res = 0; } ast_mutex_unlock(&p->lock); return res; } #if defined(HAVE_PRI) static struct ast_str *create_channel_name(struct dahdi_pvt *i, int is_outgoing, char *address) #else static struct ast_str *create_channel_name(struct dahdi_pvt *i) #endif /* defined(HAVE_PRI) */ { struct ast_str *chan_name; int x, y; /* Create the new channel name tail. */ if (!(chan_name = ast_str_create(32))) { return NULL; } if (i->channel == CHAN_PSEUDO) { ast_str_set(&chan_name, 0, "pseudo-%ld", ast_random()); #if defined(HAVE_PRI) } else if (i->pri) { ast_mutex_lock(&i->pri->lock); y = ++i->pri->new_chan_seq; if (is_outgoing) { ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, address, (unsigned)y); address[0] = '\0'; } else if (ast_strlen_zero(i->cid_subaddr)) { /* Put in caller-id number only since there is no subaddress. */ ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, i->cid_num, (unsigned)y); } else { /* Put in caller-id number and subaddress. */ ast_str_set(&chan_name, 0, "i%d/%s:%s-%x", i->pri->span, i->cid_num, i->cid_subaddr, (unsigned)y); } ast_mutex_unlock(&i->pri->lock); #endif /* defined(HAVE_PRI) */ } else { y = 1; do { ast_str_set(&chan_name, 0, "%d-%d", i->channel, y); for (x = 0; x < 3; ++x) { if (i->subs[x].owner && !strcasecmp(ast_str_buffer(chan_name), ast_channel_name(i->subs[x].owner) + 6)) { break; } } ++y; } while (x < 3); } return chan_name; } static struct ast_channel *dahdi_new_callid_clean(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, struct ast_callid *callid, int callid_created) { struct ast_channel *new_channel = dahdi_new(i, state, startpbx, idx, law, assignedids, requestor, callid); ast_callid_threadstorage_auto_clean(callid, callid_created); return new_channel; } static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, struct ast_callid *callid) { struct ast_channel *tmp; struct ast_format_cap *caps; struct ast_format *deflaw; int x; int features; struct ast_str *chan_name; struct ast_variable *v; char *dashptr; char device_name[AST_CHANNEL_NAME]; if (i->subs[idx].owner) { ast_log(LOG_WARNING, "Channel %d already has a %s call\n", i->channel,subnames[idx]); return NULL; } #if defined(HAVE_PRI) /* * The dnid has been stuffed with the called-number[:subaddress] * by dahdi_request() for outgoing calls. */ chan_name = create_channel_name(i, i->outgoing, i->dnid); #else chan_name = create_channel_name(i); #endif /* defined(HAVE_PRI) */ if (!chan_name) { return NULL; } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { ast_free(chan_name); return NULL; } tmp = ast_channel_alloc(0, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "DAHDI/%s", ast_str_buffer(chan_name)); ast_free(chan_name); if (!tmp) { ao2_ref(caps, -1); return NULL; } ast_channel_stage_snapshot(tmp); if (callid) { ast_channel_callid_set(tmp, callid); } ast_channel_tech_set(tmp, &dahdi_tech); #if defined(HAVE_PRI) if (i->pri) { ast_cc_copy_config_params(i->cc_params, i->pri->cc_params); } #endif /* defined(HAVE_PRI) */ ast_channel_cc_params_init(tmp, i->cc_params); if (law) { i->law = law; if (law == DAHDI_LAW_ALAW) { deflaw = ast_format_alaw; } else { deflaw = ast_format_ulaw; } } else { switch (i->sig) { case SIG_PRI_LIB_HANDLE_CASES: /* Make sure companding law is known. */ i->law = (i->law_default == DAHDI_LAW_ALAW) ? DAHDI_LAW_ALAW : DAHDI_LAW_MULAW; break; default: i->law = i->law_default; break; } if (i->law_default == DAHDI_LAW_ALAW) { deflaw = ast_format_alaw; } else { deflaw = ast_format_ulaw; } } ast_channel_set_fd(tmp, 0, i->subs[idx].dfd); ast_format_cap_append(caps, deflaw, 0); ast_channel_nativeformats_set(tmp, caps); ao2_ref(caps, -1); /* Start out assuming ulaw since it's smaller :) */ ast_channel_set_rawreadformat(tmp, deflaw); ast_channel_set_readformat(tmp, deflaw); ast_channel_set_rawwriteformat(tmp, deflaw); ast_channel_set_writeformat(tmp, deflaw); i->subs[idx].linear = 0; dahdi_setlinear(i->subs[idx].dfd, i->subs[idx].linear); features = 0; if (idx == SUB_REAL) { if (i->busydetect && CANBUSYDETECT(i)) features |= DSP_FEATURE_BUSY_DETECT; if ((i->callprogress & CALLPROGRESS_PROGRESS) && CANPROGRESSDETECT(i)) features |= DSP_FEATURE_CALL_PROGRESS; if ((i->waitfordialtone || i->dialtone_detect) && CANPROGRESSDETECT(i)) features |= DSP_FEATURE_WAITDIALTONE; if ((!i->outgoing && (i->callprogress & CALLPROGRESS_FAX_INCOMING)) || (i->outgoing && (i->callprogress & CALLPROGRESS_FAX_OUTGOING))) { features |= DSP_FEATURE_FAX_DETECT; } x = DAHDI_TONEDETECT_ON | DAHDI_TONEDETECT_MUTE; if (ioctl(i->subs[idx].dfd, DAHDI_TONEDETECT, &x)) { i->hardwaredtmf = 0; features |= DSP_FEATURE_DIGIT_DETECT; } else if (NEED_MFDETECT(i)) { i->hardwaredtmf = 1; features |= DSP_FEATURE_DIGIT_DETECT; } } if (features) { if (i->dsp) { ast_debug(1, "Already have a dsp on %s?\n", ast_channel_name(tmp)); } else { if (i->channel != CHAN_PSEUDO) i->dsp = ast_dsp_new(); else i->dsp = NULL; if (i->dsp) { i->dsp_features = features; #if defined(HAVE_PRI) || defined(HAVE_SS7) /* We cannot do progress detection until receive PROGRESS message */ if (i->outgoing && (dahdi_sig_pri_lib_handles(i->sig) || (i->sig == SIG_SS7))) { /* Remember requested DSP features, don't treat talking as ANSWER */ i->dsp_features = features & ~DSP_PROGRESS_TALK; features = 0; } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ ast_dsp_set_features(i->dsp, features); ast_dsp_set_digitmode(i->dsp, DSP_DIGITMODE_DTMF | i->dtmfrelax); if (!ast_strlen_zero(progzone)) ast_dsp_set_call_progress_zone(i->dsp, progzone); if (i->busydetect && CANBUSYDETECT(i)) { ast_dsp_set_busy_count(i->dsp, i->busycount); ast_dsp_set_busy_pattern(i->dsp, &i->busy_cadence); } } } } i->dialtone_scanning_time_elapsed = 0; if (state == AST_STATE_RING) ast_channel_rings_set(tmp, 1); ast_channel_tech_pvt_set(tmp, i); if ((i->sig == SIG_FXOKS) || (i->sig == SIG_FXOGS) || (i->sig == SIG_FXOLS)) { /* Only FXO signalled stuff can be picked up */ ast_channel_callgroup_set(tmp, i->callgroup); ast_channel_pickupgroup_set(tmp, i->pickupgroup); ast_channel_named_callgroups_set(tmp, i->named_callgroups); ast_channel_named_pickupgroups_set(tmp, i->named_pickupgroups); } if (!ast_strlen_zero(i->parkinglot)) ast_channel_parkinglot_set(tmp, i->parkinglot); if (!ast_strlen_zero(i->language)) ast_channel_language_set(tmp, i->language); if (!i->owner) i->owner = tmp; if (!ast_strlen_zero(i->accountcode)) ast_channel_accountcode_set(tmp, i->accountcode); if (i->amaflags) ast_channel_amaflags_set(tmp, i->amaflags); i->subs[idx].owner = tmp; ast_channel_context_set(tmp, i->context); if (!dahdi_analog_lib_handles(i->sig, i->radio, i->oprmode)) { ast_channel_call_forward_set(tmp, i->call_forward); } /* If we've been told "no ADSI" then enforce it */ if (!i->adsi) ast_channel_adsicpe_set(tmp, AST_ADSI_UNAVAILABLE); if (!ast_strlen_zero(i->exten)) ast_channel_exten_set(tmp, i->exten); if (!ast_strlen_zero(i->rdnis)) { ast_channel_redirecting(tmp)->from.number.valid = 1; ast_channel_redirecting(tmp)->from.number.str = ast_strdup(i->rdnis); } if (!ast_strlen_zero(i->dnid)) { ast_channel_dialed(tmp)->number.str = ast_strdup(i->dnid); } /* Don't use ast_set_callerid() here because it will * generate a needless NewCallerID event */ #if defined(HAVE_PRI) || defined(HAVE_SS7) if (!ast_strlen_zero(i->cid_ani)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_ani); } else if (!ast_strlen_zero(i->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num); } #else if (!ast_strlen_zero(i->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num); } #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ ast_channel_caller(tmp)->id.name.presentation = i->callingpres; ast_channel_caller(tmp)->id.number.presentation = i->callingpres; ast_channel_caller(tmp)->id.number.plan = i->cid_ton; ast_channel_caller(tmp)->ani2 = i->cid_ani2; ast_channel_caller(tmp)->id.tag = ast_strdup(i->cid_tag); /* clear the fake event in case we posted one before we had ast_channel */ i->fake_event = 0; /* Assure there is no confmute on this channel */ dahdi_confmute(i, 0); i->muting = 0; /* Configure the new channel jb */ ast_jb_configure(tmp, &global_jbconf); /* Set initial device state */ ast_copy_string(device_name, ast_channel_name(tmp), sizeof(device_name)); dashptr = strrchr(device_name, '-'); if (dashptr) { *dashptr = '\0'; } ast_set_flag(ast_channel_flags(tmp), AST_FLAG_DISABLE_DEVSTATE_CACHE); ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, device_name); for (v = i->vars ; v ; v = v->next) pbx_builtin_setvar_helper(tmp, v->name, v->value); ast_channel_stage_snapshot_done(tmp); ast_channel_unlock(tmp); ast_module_ref(ast_module_info->self); dahdi_ami_channel_event(i, tmp); if (startpbx) { #ifdef HAVE_OPENR2 if (i->mfcr2call) { pbx_builtin_setvar_helper(tmp, "MFCR2_CATEGORY", openr2_proto_get_category_string(i->mfcr2_recvd_category)); } #endif if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); return NULL; } } return tmp; } static int my_getsigstr(struct ast_channel *chan, char *str, const char *term, int ms) { char c; *str = 0; /* start with empty output buffer */ for (;;) { /* Wait for the first digit (up to specified ms). */ c = ast_waitfordigit(chan, ms); /* if timeout, hangup or error, return as such */ if (c < 1) return c; *str++ = c; *str = 0; if (strchr(term, c)) return 1; } } static int dahdi_wink(struct dahdi_pvt *p, int idx) { int j; dahdi_set_hook(p->subs[idx].dfd, DAHDI_WINK); for (;;) { /* set bits of interest */ j = DAHDI_IOMUX_SIGEVENT; /* wait for some happening */ if (ioctl(p->subs[idx].dfd,DAHDI_IOMUX,&j) == -1) return(-1); /* exit loop if we have it */ if (j & DAHDI_IOMUX_SIGEVENT) break; } /* get the event info */ if (ioctl(p->subs[idx].dfd,DAHDI_GETEVENT,&j) == -1) return(-1); return 0; } static void publish_dnd_state(int channel, const char *status) { RAII_VAR(struct ast_json *, body, NULL, ast_json_unref); RAII_VAR(struct ast_str *, dahdichan, ast_str_create(32), ast_free); if (!dahdichan) { return; } ast_str_set(&dahdichan, 0, "%d", channel); body = ast_json_pack("{s: s, s: s}", "DAHDIChannel", ast_str_buffer(dahdichan), "Status", status); if (!body) { return; } ast_manager_publish_event("DNDState", EVENT_FLAG_SYSTEM, body); } /*! \brief enable or disable the chan_dahdi Do-Not-Disturb mode for a DAHDI channel * \param dahdichan "Physical" DAHDI channel (e.g: DAHDI/5) * \param flag on 1 to enable, 0 to disable, -1 return dnd value * * chan_dahdi has a DND (Do Not Disturb) mode for each dahdichan (physical * DAHDI channel). Use this to enable or disable it. * * \bug the use of the word "channel" for those dahdichans is really confusing. */ static int dahdi_dnd(struct dahdi_pvt *dahdichan, int flag) { if (dahdi_analog_lib_handles(dahdichan->sig, dahdichan->radio, dahdichan->oprmode)) { return analog_dnd(dahdichan->sig_pvt, flag); } if (flag == -1) { return dahdichan->dnd; } /* Do not disturb */ dahdichan->dnd = flag; ast_verb(3, "%s DND on channel %d\n", flag? "Enabled" : "Disabled", dahdichan->channel); publish_dnd_state(dahdichan->channel, flag ? "enabled" : "disabled"); return 0; } static int canmatch_featurecode(const char *pickupexten, const char *exten) { int extlen = strlen(exten); if (!extlen) { return 1; } if (extlen < strlen(pickupexten) && !strncmp(pickupexten, exten, extlen)) { return 1; } /* hardcoded features are *60, *67, *69, *70, *72, *73, *78, *79, *82, *0 */ if (exten[0] == '*' && extlen < 3) { if (extlen == 1) { return 1; } /* "*0" should be processed before it gets here */ switch (exten[1]) { case '6': case '7': case '8': return 1; } } return 0; } static void *analog_ss_thread(void *data) { struct ast_channel *chan = data; struct dahdi_pvt *p = ast_channel_tech_pvt(chan); char exten[AST_MAX_EXTENSION] = ""; char exten2[AST_MAX_EXTENSION] = ""; unsigned char buf[256]; char dtmfcid[300]; char dtmfbuf[300]; struct callerid_state *cs = NULL; char *name = NULL, *number = NULL; int distMatches; int curRingData[3]; int receivedRingT; int counter1; int counter; int samples = 0; struct ast_smdi_md_message *smdi_msg = NULL; int flags = 0; int i; int timeout; int getforward = 0; char *s1, *s2; int len = 0; int res; int idx; RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); const char *pickupexten; ast_mutex_lock(&ss_thread_lock); ss_thread_count++; ast_mutex_unlock(&ss_thread_lock); /* in the bizarre case where the channel has become a zombie before we even get started here, abort safely */ if (!p) { ast_log(LOG_WARNING, "Channel became a zombie before simple switch could be started (%s)\n", ast_channel_name(chan)); ast_hangup(chan); goto quit; } ast_verb(3, "Starting simple switch on '%s'\n", ast_channel_name(chan)); idx = dahdi_get_index(chan, p, 1); if (idx < 0) { ast_log(LOG_WARNING, "Huh?\n"); ast_hangup(chan); goto quit; } ast_channel_lock(chan); pickup_cfg = ast_get_chan_features_pickup_config(chan); 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); } ast_channel_unlock(chan); if (p->dsp) ast_dsp_digitreset(p->dsp); switch (p->sig) { case SIG_FEATD: case SIG_FEATDMF: case SIG_FEATDMF_TA: case SIG_E911: case SIG_FGC_CAMAMF: case SIG_FEATB: case SIG_EMWINK: case SIG_SF_FEATD: case SIG_SF_FEATDMF: case SIG_SF_FEATB: case SIG_SFWINK: if (dahdi_wink(p, idx)) goto quit; /* Fall through */ case SIG_EM: case SIG_EM_E1: case SIG_SF: case SIG_FGC_CAMA: res = tone_zone_play_tone(p->subs[idx].dfd, -1); if (p->dsp) ast_dsp_digitreset(p->dsp); /* set digit mode appropriately */ if (p->dsp) { if (NEED_MFDETECT(p)) ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_MF | p->dtmfrelax); else ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax); } memset(dtmfbuf, 0, sizeof(dtmfbuf)); /* Wait for the first digit only if immediate=no */ if (!p->immediate) /* Wait for the first digit (up to 5 seconds). */ res = ast_waitfordigit(chan, 5000); else res = 0; if (res > 0) { /* save first char */ dtmfbuf[0] = res; switch (p->sig) { case SIG_FEATD: case SIG_SF_FEATD: res = my_getsigstr(chan, dtmfbuf + 1, "*", 3000); if (res > 0) res = my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "*", 3000); if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp); break; case SIG_FEATDMF_TA: res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000); if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp); if (dahdi_wink(p, idx)) goto quit; dtmfbuf[0] = 0; /* Wait for the first digit (up to 5 seconds). */ res = ast_waitfordigit(chan, 5000); if (res <= 0) break; dtmfbuf[0] = res; /* fall through intentionally */ case SIG_FEATDMF: case SIG_E911: case SIG_FGC_CAMAMF: case SIG_SF_FEATDMF: res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000); /* if international caca, do it again to get real ANO */ if ((p->sig == SIG_FEATDMF) && (dtmfbuf[1] != '0') && (strlen(dtmfbuf) != 14)) { if (dahdi_wink(p, idx)) goto quit; dtmfbuf[0] = 0; /* Wait for the first digit (up to 5 seconds). */ res = ast_waitfordigit(chan, 5000); if (res <= 0) break; dtmfbuf[0] = res; res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000); } if (res > 0) { /* if E911, take off hook */ if (p->sig == SIG_E911) dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); res = my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "#", 3000); } if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp); break; case SIG_FEATB: case SIG_SF_FEATB: res = my_getsigstr(chan, dtmfbuf + 1, "#", 3000); if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp); break; case SIG_EMWINK: /* if we received a '*', we are actually receiving Feature Group D dial syntax, so use that mode; otherwise, fall through to normal mode */ if (res == '*') { res = my_getsigstr(chan, dtmfbuf + 1, "*", 3000); if (res > 0) res = my_getsigstr(chan, dtmfbuf + strlen(dtmfbuf), "*", 3000); if ((res < 1) && (p->dsp)) ast_dsp_digitreset(p->dsp); break; } default: /* If we got the first digit, get the rest */ len = 1; dtmfbuf[len] = '\0'; while ((len < AST_MAX_EXTENSION-1) && ast_matchmore_extension(chan, ast_channel_context(chan), dtmfbuf, 1, p->cid_num)) { if (ast_exists_extension(chan, ast_channel_context(chan), dtmfbuf, 1, p->cid_num)) { timeout = matchdigittimeout; } else { timeout = gendigittimeout; } res = ast_waitfordigit(chan, timeout); if (res < 0) { ast_debug(1, "waitfordigit returned < 0...\n"); ast_hangup(chan); goto quit; } else if (res) { dtmfbuf[len++] = res; dtmfbuf[len] = '\0'; } else { break; } } break; } } if (res == -1) { ast_log(LOG_WARNING, "getdtmf on channel %d: %s\n", p->channel, strerror(errno)); ast_hangup(chan); goto quit; } else if (res < 0) { ast_debug(1, "Got hung up before digits finished\n"); ast_hangup(chan); goto quit; } if (p->sig == SIG_FGC_CAMA) { char anibuf[100]; if (ast_safe_sleep(chan,1000) == -1) { ast_hangup(chan); goto quit; } dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_MF | p->dtmfrelax); res = my_getsigstr(chan, anibuf, "#", 10000); if ((res > 0) && (strlen(anibuf) > 2)) { if (anibuf[strlen(anibuf) - 1] == '#') anibuf[strlen(anibuf) - 1] = 0; ast_set_callerid(chan, anibuf + 2, NULL, anibuf + 2); } ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax); } ast_copy_string(exten, dtmfbuf, sizeof(exten)); if (ast_strlen_zero(exten)) ast_copy_string(exten, "s", sizeof(exten)); if (p->sig == SIG_FEATD || p->sig == SIG_EMWINK) { /* Look for Feature Group D on all E&M Wink and Feature Group D trunks */ if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "*"); s2 = strsep(&stringp, "*"); if (s2) { if (!ast_strlen_zero(p->cid_num)) ast_set_callerid(chan, p->cid_num, NULL, p->cid_num); else ast_set_callerid(chan, s1, NULL, s1); ast_copy_string(exten, s2, sizeof(exten)); } else ast_copy_string(exten, s1, sizeof(exten)); } else if (p->sig == SIG_FEATD) ast_log(LOG_WARNING, "Got a non-Feature Group D input on channel %d. Assuming E&M Wink instead\n", p->channel); } if ((p->sig == SIG_FEATDMF) || (p->sig == SIG_FEATDMF_TA)) { if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "#"); s2 = strsep(&stringp, "#"); if (s2) { if (!ast_strlen_zero(p->cid_num)) ast_set_callerid(chan, p->cid_num, NULL, p->cid_num); else if (*(s1 + 2)) ast_set_callerid(chan, s1 + 2, NULL, s1 + 2); ast_copy_string(exten, s2 + 1, sizeof(exten)); } else ast_copy_string(exten, s1 + 2, sizeof(exten)); } else ast_log(LOG_WARNING, "Got a non-Feature Group D input on channel %d. Assuming E&M Wink instead\n", p->channel); } if ((p->sig == SIG_E911) || (p->sig == SIG_FGC_CAMAMF)) { if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "#"); s2 = strsep(&stringp, "#"); if (s2 && (*(s2 + 1) == '0')) { if (*(s2 + 2)) ast_set_callerid(chan, s2 + 2, NULL, s2 + 2); } if (s1) ast_copy_string(exten, s1, sizeof(exten)); else ast_copy_string(exten, "911", sizeof(exten)); } else ast_log(LOG_WARNING, "Got a non-E911/FGC CAMA input on channel %d. Assuming E&M Wink instead\n", p->channel); } if (p->sig == SIG_FEATB) { if (exten[0] == '*') { char *stringp=NULL; ast_copy_string(exten2, exten, sizeof(exten2)); /* Parse out extension and callerid */ stringp=exten2 +1; s1 = strsep(&stringp, "#"); ast_copy_string(exten, exten2 + 1, sizeof(exten)); } else ast_log(LOG_WARNING, "Got a non-Feature Group B input on channel %d. Assuming E&M Wink instead\n", p->channel); } if ((p->sig == SIG_FEATDMF) || (p->sig == SIG_FEATDMF_TA)) { dahdi_wink(p, idx); /* some switches require a minimum guard time between the last FGD wink and something that answers immediately. This ensures it */ if (ast_safe_sleep(chan, 100)) { ast_hangup(chan); goto quit; } } dahdi_ec_enable(p); if (NEED_MFDETECT(p)) { if (p->dsp) { if (!p->hardwaredtmf) ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | p->dtmfrelax); else { ast_dsp_free(p->dsp); p->dsp = NULL; } } } if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { ast_channel_exten_set(chan, exten); if (p->dsp) ast_dsp_digitreset(p->dsp); res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); } goto quit; } else { ast_verb(2, "Unknown extension '%s' in context '%s' requested\n", exten, ast_channel_context(chan)); sleep(2); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_INFO); if (res < 0) ast_log(LOG_WARNING, "Unable to start special tone on %d\n", p->channel); else sleep(1); res = ast_streamfile(chan, "ss-noservice", ast_channel_language(chan)); if (res >= 0) ast_waitstream(chan, ""); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); ast_hangup(chan); goto quit; } break; case SIG_FXOLS: case SIG_FXOGS: case SIG_FXOKS: /* Read the first digit */ timeout = firstdigittimeout; /* If starting a threeway call, never timeout on the first digit so someone can use flash-hook as a "hold" feature */ if (p->subs[SUB_THREEWAY].owner) timeout = 999999; while (len < AST_MAX_EXTENSION-1) { int is_exten_parking = 0; /* Read digit unless it's supposed to be immediate, in which case the only answer is 's' */ if (p->immediate) res = 's'; else res = ast_waitfordigit(chan, timeout); timeout = 0; if (res < 0) { ast_debug(1, "waitfordigit returned < 0...\n"); res = tone_zone_play_tone(p->subs[idx].dfd, -1); ast_hangup(chan); goto quit; } else if (res) { ast_debug(1,"waitfordigit returned '%c' (%d), timeout = %d\n", res, res, timeout); exten[len++]=res; exten[len] = '\0'; } if (!ast_ignore_pattern(ast_channel_context(chan), exten)) { tone_zone_play_tone(p->subs[idx].dfd, -1); } else { tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALTONE); } if (ast_parking_provider_registered()) { is_exten_parking = ast_parking_is_exten_park(ast_channel_context(chan), exten); } if (ast_exists_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num) && !is_exten_parking) { if (!res || !ast_matchmore_extension(chan, ast_channel_context(chan), exten, 1, p->cid_num)) { if (getforward) { /* Record this as the forwarding extension */ ast_copy_string(p->call_forward, exten, sizeof(p->call_forward)); ast_verb(3, "Setting call forward to '%s' on channel %d\n", p->call_forward, p->channel); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); if (res) break; usleep(500000); res = tone_zone_play_tone(p->subs[idx].dfd, -1); sleep(1); memset(exten, 0, sizeof(exten)); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALTONE); len = 0; getforward = 0; } else { res = tone_zone_play_tone(p->subs[idx].dfd, -1); ast_channel_lock(chan); ast_channel_exten_set(chan, exten); if (!ast_strlen_zero(p->cid_num)) { if (!p->hidecallerid) ast_set_callerid(chan, p->cid_num, NULL, p->cid_num); else ast_set_callerid(chan, NULL, NULL, p->cid_num); } if (!ast_strlen_zero(p->cid_name)) { if (!p->hidecallerid) ast_set_callerid(chan, NULL, p->cid_name, NULL); } ast_setstate(chan, AST_STATE_RING); ast_channel_unlock(chan); dahdi_ec_enable(p); res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); } goto quit; } } else { /* It's a match, but they just typed a digit, and there is an ambiguous match, so just set the timeout to matchdigittimeout and wait some more */ timeout = matchdigittimeout; } } else if (res == 0) { ast_debug(1, "not enough digits (and no ambiguous match)...\n"); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); dahdi_wait_event(p->subs[idx].dfd); ast_hangup(chan); goto quit; } else if (p->callwaiting && !strcmp(exten, "*70")) { ast_verb(3, "Disabling call waiting on %s\n", ast_channel_name(chan)); /* Disable call waiting if enabled */ p->callwaiting = 0; res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } len = 0; ioctl(p->subs[idx].dfd,DAHDI_CONFDIAG,&len); memset(exten, 0, sizeof(exten)); timeout = firstdigittimeout; } else if (!strcmp(exten, pickupexten)) { /* Scan all channels and see if there are any * ringing channels that have call groups * that equal this channels pickup group */ if (idx == SUB_REAL) { /* Switch us from Third call to Call Wait */ if (p->subs[SUB_THREEWAY].owner) { /* If you make a threeway call and the *8# a call, it should actually look like a callwait */ alloc_sub(p, SUB_CALLWAIT); swap_subs(p, SUB_CALLWAIT, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY); } dahdi_ec_enable(p); if (ast_pickup_call(chan)) { ast_debug(1, "No call pickup possible...\n"); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); dahdi_wait_event(p->subs[idx].dfd); } ast_hangup(chan); goto quit; } else { ast_log(LOG_WARNING, "Huh? Got *8# on call not on real\n"); ast_hangup(chan); goto quit; } } else if (!p->hidecallerid && !strcmp(exten, "*67")) { ast_verb(3, "Disabling Caller*ID on %s\n", ast_channel_name(chan)); /* Disable Caller*ID if enabled */ p->hidecallerid = 1; ast_party_number_free(&ast_channel_caller(chan)->id.number); ast_party_number_init(&ast_channel_caller(chan)->id.number); ast_party_name_free(&ast_channel_caller(chan)->id.name); ast_party_name_init(&ast_channel_caller(chan)->id.name); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } len = 0; memset(exten, 0, sizeof(exten)); timeout = firstdigittimeout; } else if (p->callreturn && !strcmp(exten, "*69")) { res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); break; } else if (!strcmp(exten, "*78")) { dahdi_dnd(p, 1); /* Do not disturb */ res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; } else if (!strcmp(exten, "*79")) { dahdi_dnd(p, 0); /* Do not disturb */ res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; } else if (p->cancallforward && !strcmp(exten, "*72")) { res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); getforward = 1; memset(exten, 0, sizeof(exten)); len = 0; } else if (p->cancallforward && !strcmp(exten, "*73")) { ast_verb(3, "Cancelling call forwarding on channel %d\n", p->channel); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); memset(p->call_forward, 0, sizeof(p->call_forward)); getforward = 0; memset(exten, 0, sizeof(exten)); len = 0; } else if ((p->transfer || p->canpark) && is_exten_parking && p->subs[SUB_THREEWAY].owner) { struct ast_bridge_channel *bridge_channel; /* * This is a three way call, the main call being a real channel, * and we're parking the first call. */ ast_channel_lock(p->subs[SUB_THREEWAY].owner); bridge_channel = ast_channel_get_bridge_channel(p->subs[SUB_THREEWAY].owner); ast_channel_unlock(p->subs[SUB_THREEWAY].owner); if (bridge_channel) { if (!ast_parking_blind_transfer_park(bridge_channel, ast_channel_context(chan), exten, NULL, NULL)) { /* * Swap things around between the three-way and real call so we * can hear where the channel got parked. */ ast_mutex_lock(&p->lock); p->owner = p->subs[SUB_THREEWAY].owner; swap_subs(p, SUB_THREEWAY, SUB_REAL); ast_mutex_unlock(&p->lock); ast_verb(3, "%s: Parked call\n", ast_channel_name(chan)); ast_hangup(chan); ao2_ref(bridge_channel, -1); goto quit; } ao2_ref(bridge_channel, -1); } break; } else if (p->hidecallerid && !strcmp(exten, "*82")) { ast_verb(3, "Enabling Caller*ID on %s\n", ast_channel_name(chan)); /* Enable Caller*ID if enabled */ p->hidecallerid = 0; ast_set_callerid(chan, p->cid_num, p->cid_name, NULL); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_DIALRECALL); if (res) { ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n", ast_channel_name(chan), strerror(errno)); } len = 0; memset(exten, 0, sizeof(exten)); timeout = firstdigittimeout; } else if (!strcmp(exten, "*0")) { struct ast_channel *nbridge = p->subs[SUB_THREEWAY].owner; struct dahdi_pvt *pbridge = NULL; RAII_VAR(struct ast_channel *, bridged, nbridge ? ast_channel_bridge_peer(nbridge) : NULL, ast_channel_cleanup); /* set up the private struct of the bridged one, if any */ if (nbridge && bridged) { pbridge = ast_channel_tech_pvt(bridged); } if (nbridge && pbridge && (ast_channel_tech(nbridge) == &dahdi_tech) && (ast_channel_tech(bridged) == &dahdi_tech) && ISTRUNK(pbridge)) { int func = DAHDI_FLASH; /* Clear out the dial buffer */ p->dop.dialstr[0] = '\0'; /* flash hookswitch */ if ((ioctl(pbridge->subs[SUB_REAL].dfd,DAHDI_HOOK,&func) == -1) && (errno != EINPROGRESS)) { ast_log(LOG_WARNING, "Unable to flash external trunk on channel %s: %s\n", ast_channel_name(nbridge), strerror(errno)); } swap_subs(p, SUB_REAL, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY); p->owner = p->subs[SUB_REAL].owner; ast_queue_unhold(p->subs[SUB_REAL].owner); ast_hangup(chan); goto quit; } else { tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); dahdi_wait_event(p->subs[idx].dfd); tone_zone_play_tone(p->subs[idx].dfd, -1); swap_subs(p, SUB_REAL, SUB_THREEWAY); unalloc_sub(p, SUB_THREEWAY); p->owner = p->subs[SUB_REAL].owner; ast_hangup(chan); goto quit; } } else if (!ast_canmatch_extension(chan, ast_channel_context(chan), exten, 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) && !canmatch_featurecode(pickupexten, exten)) { ast_debug(1, "Can't match %s from '%s' in context %s\n", exten, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""), ast_channel_context(chan)); break; } if (!timeout) timeout = gendigittimeout; if (len && !ast_ignore_pattern(ast_channel_context(chan), exten)) tone_zone_play_tone(p->subs[idx].dfd, -1); } break; case SIG_FXSLS: case SIG_FXSGS: case SIG_FXSKS: /* check for SMDI messages */ if (p->use_smdi && p->smdi_iface) { smdi_msg = ast_smdi_md_message_wait(p->smdi_iface, SMDI_MD_WAIT_TIMEOUT); if (smdi_msg != NULL) { ast_channel_exten_set(chan, smdi_msg->fwd_st); if (smdi_msg->type == 'B') pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "b"); else if (smdi_msg->type == 'N') pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "u"); ast_debug(1, "Received SMDI message on %s\n", ast_channel_name(chan)); } else { ast_log(LOG_WARNING, "SMDI enabled but no SMDI message present\n"); } } if (p->use_callerid && (p->cid_signalling == CID_SIG_SMDI && smdi_msg)) { number = smdi_msg->calling_st; /* If we want caller id, we're in a prering state due to a polarity reversal * and we're set to use a polarity reversal to trigger the start of caller id, * grab the caller id and wait for ringing to start... */ } else if (p->use_callerid && (ast_channel_state(chan) == AST_STATE_PRERING && (p->cid_start == CID_START_POLARITY || p->cid_start == CID_START_POLARITY_IN || p->cid_start == CID_START_DTMF_NOALERT))) { /* If set to use DTMF CID signalling, listen for DTMF */ if (p->cid_signalling == CID_SIG_DTMF) { int k = 0; int off_ms; struct timeval start = ast_tvnow(); int ms; cs = NULL; ast_debug(1, "Receiving DTMF cid on channel %s\n", ast_channel_name(chan)); dahdi_setlinear(p->subs[idx].dfd, 0); /* * We are the only party interested in the Rx stream since * we have not answered yet. We don't need or even want DTMF * emulation. The DTMF digits can come so fast that emulation * can drop some of them. */ ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY); off_ms = 4000;/* This is a typical OFF time between rings. */ for (;;) { struct ast_frame *f; ms = ast_remaining_ms(start, off_ms); res = ast_waitfor(chan, ms); if (res <= 0) { /* * We do not need to restore the dahdi_setlinear() * or AST_FLAG_END_DTMF_ONLY flag settings since we * are hanging up the channel. */ ast_log(LOG_WARNING, "DTMFCID timed out waiting for ring. " "Exiting simple switch\n"); ast_hangup(chan); goto quit; } f = ast_read(chan); if (!f) break; if (f->frametype == AST_FRAME_DTMF) { if (k < ARRAY_LEN(dtmfbuf) - 1) { dtmfbuf[k++] = f->subclass.integer; } ast_debug(1, "CID got digit '%c'\n", f->subclass.integer); start = ast_tvnow(); } ast_frfree(f); if (ast_channel_state(chan) == AST_STATE_RING || ast_channel_state(chan) == AST_STATE_RINGING) break; /* Got ring */ } ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY); dtmfbuf[k] = '\0'; dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); /* Got cid and ring. */ ast_debug(1, "CID got string '%s'\n", dtmfbuf); callerid_get_dtmf(dtmfbuf, dtmfcid, &flags); ast_debug(1, "CID is '%s', flags %d\n", dtmfcid, flags); /* If first byte is NULL, we have no cid */ if (!ast_strlen_zero(dtmfcid)) number = dtmfcid; else number = NULL; /* If set to use V23 Signalling, launch our FSK gubbins and listen for it */ } else if ((p->cid_signalling == CID_SIG_V23) || (p->cid_signalling == CID_SIG_V23_JP)) { cs = callerid_new(p->cid_signalling); if (cs) { int off_ms; struct timeval start; int ms; samples = 0; #if 1 bump_gains(p); #endif /* Take out of linear mode for Caller*ID processing */ dahdi_setlinear(p->subs[idx].dfd, 0); /* First we wait and listen for the Caller*ID */ for (;;) { i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT; if ((res = ioctl(p->subs[idx].dfd, DAHDI_IOMUX, &i))) { ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } if (i & DAHDI_IOMUX_SIGEVENT) { res = dahdi_get_event(p->subs[idx].dfd); ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res)); if (res == DAHDI_EVENT_NOALARM) { p->inalarm = 0; } if (p->cid_signalling == CID_SIG_V23_JP) { if (res == DAHDI_EVENT_RINGBEGIN) { res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); usleep(1); } } else { res = 0; break; } } else if (i & DAHDI_IOMUX_READ) { res = read(p->subs[idx].dfd, buf, sizeof(buf)); if (res < 0) { if (errno != ELAST) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } break; } samples += res; if (p->cid_signalling == CID_SIG_V23_JP) { res = callerid_feed_jp(cs, buf, res, AST_LAW(p)); } else { res = callerid_feed(cs, buf, res, AST_LAW(p)); } if (res < 0) { /* * The previous diagnostic message output likely * explains why it failed. */ ast_log(LOG_WARNING, "Failed to decode CallerID on channel '%s'\n", ast_channel_name(chan)); break; } else if (res) break; else if (samples > (8000 * 10)) break; } } if (res == 1) { callerid_get(cs, &name, &number, &flags); ast_log(LOG_NOTICE, "CallerID number: %s, name: %s, flags=%d\n", number, name, flags); } if (p->cid_signalling == CID_SIG_V23_JP) { res = dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_ONHOOK); usleep(1); } /* Finished with Caller*ID, now wait for a ring to make sure there really is a call coming */ start = ast_tvnow(); off_ms = 4000;/* This is a typical OFF time between rings. */ for (;;) { struct ast_frame *f; ms = ast_remaining_ms(start, off_ms); res = ast_waitfor(chan, ms); if (res <= 0) { ast_log(LOG_WARNING, "CID timed out waiting for ring. " "Exiting simple switch\n"); ast_hangup(chan); goto quit; } if (!(f = ast_read(chan))) { ast_log(LOG_WARNING, "Hangup received waiting for ring. Exiting simple switch\n"); ast_hangup(chan); goto quit; } ast_frfree(f); if (ast_channel_state(chan) == AST_STATE_RING || ast_channel_state(chan) == AST_STATE_RINGING) break; /* Got ring */ } /* We must have a ring by now, so, if configured, lets try to listen for * distinctive ringing */ if (p->usedistinctiveringdetection) { len = 0; distMatches = 0; /* Clear the current ring data array so we don't have old data in it. */ for (receivedRingT = 0; receivedRingT < ARRAY_LEN(curRingData); receivedRingT++) curRingData[receivedRingT] = 0; receivedRingT = 0; counter = 0; counter1 = 0; /* Check to see if context is what it should be, if not set to be. */ if (strcmp(p->context,p->defcontext) != 0) { ast_copy_string(p->context, p->defcontext, sizeof(p->context)); ast_channel_context_set(chan, p->defcontext); } for (;;) { i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT; if ((res = ioctl(p->subs[idx].dfd, DAHDI_IOMUX, &i))) { ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } if (i & DAHDI_IOMUX_SIGEVENT) { res = dahdi_get_event(p->subs[idx].dfd); ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res)); if (res == DAHDI_EVENT_NOALARM) { p->inalarm = 0; } res = 0; /* Let us detect distinctive ring */ curRingData[receivedRingT] = p->ringt; if (p->ringt < p->ringt_base/2) break; /* Increment the ringT counter so we can match it against values in chan_dahdi.conf for distinctive ring */ if (++receivedRingT == ARRAY_LEN(curRingData)) break; } else if (i & DAHDI_IOMUX_READ) { res = read(p->subs[idx].dfd, buf, sizeof(buf)); if (res < 0) { if (errno != ELAST) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } break; } if (p->ringt > 0) { if (!(--p->ringt)) { res = -1; break; } } } } /* this only shows up if you have n of the dring patterns filled in */ ast_verb(3, "Detected ring pattern: %d,%d,%d\n",curRingData[0],curRingData[1],curRingData[2]); for (counter = 0; counter < 3; counter++) { /* Check to see if the rings we received match any of the ones in chan_dahdi.conf for this channel */ distMatches = 0; for (counter1 = 0; counter1 < 3; counter1++) { ast_verb(3, "Ring pattern check range: %d\n", p->drings.ringnum[counter].range); if (p->drings.ringnum[counter].ring[counter1] == -1) { ast_verb(3, "Pattern ignore (-1) detected, so matching pattern %d regardless.\n", curRingData[counter1]); distMatches++; } else if (curRingData[counter1] <= (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range) && curRingData[counter1] >= (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range)) { ast_verb(3, "Ring pattern matched in range: %d to %d\n", (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range), (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range)); distMatches++; } } if (distMatches == 3) { /* The ring matches, set the context to whatever is for distinctive ring.. */ ast_copy_string(p->context, S_OR(p->drings.ringContext[counter].contextData, p->defcontext), sizeof(p->context)); ast_channel_context_set(chan, S_OR(p->drings.ringContext[counter].contextData, p->defcontext)); ast_verb(3, "Distinctive Ring matched context %s\n",p->context); break; } } } /* Restore linear mode (if appropriate) for Caller*ID processing */ dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); #if 1 restore_gains(p); #endif } else ast_log(LOG_WARNING, "Unable to get caller ID space\n"); } else { ast_log(LOG_WARNING, "Channel %s in prering " "state, but I have nothing to do. " "Terminating simple switch, should be " "restarted by the actual ring.\n", ast_channel_name(chan)); ast_hangup(chan); goto quit; } } else if (p->use_callerid && p->cid_start == CID_START_RING) { if (p->cid_signalling == CID_SIG_DTMF) { int k = 0; int off_ms; struct timeval start; int ms; cs = NULL; dahdi_setlinear(p->subs[idx].dfd, 0); off_ms = 2000; start = ast_tvnow(); for (;;) { struct ast_frame *f; ms = ast_remaining_ms(start, off_ms); res = ast_waitfor(chan, ms); if (res <= 0) { ast_log(LOG_WARNING, "DTMFCID timed out waiting for ring. " "Exiting simple switch\n"); ast_hangup(chan); goto quit; } f = ast_read(chan); if (!f) { /* Hangup received waiting for DTMFCID. Exiting simple switch. */ ast_hangup(chan); goto quit; } if (f->frametype == AST_FRAME_DTMF) { dtmfbuf[k++] = f->subclass.integer; ast_debug(1, "CID got digit '%c'\n", f->subclass.integer); start = ast_tvnow(); } ast_frfree(f); if (p->ringt_base == p->ringt) break; } dtmfbuf[k] = '\0'; dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); /* Got cid and ring. */ callerid_get_dtmf(dtmfbuf, dtmfcid, &flags); ast_debug(1, "CID is '%s', flags %d\n", dtmfcid, flags); /* If first byte is NULL, we have no cid */ if (!ast_strlen_zero(dtmfcid)) number = dtmfcid; else number = NULL; /* If set to use V23 Signalling, launch our FSK gubbins and listen for it */ } else { /* FSK Bell202 callerID */ cs = callerid_new(p->cid_signalling); if (cs) { #if 1 bump_gains(p); #endif samples = 0; len = 0; distMatches = 0; /* Clear the current ring data array so we don't have old data in it. */ for (receivedRingT = 0; receivedRingT < ARRAY_LEN(curRingData); receivedRingT++) curRingData[receivedRingT] = 0; receivedRingT = 0; counter = 0; counter1 = 0; /* Check to see if context is what it should be, if not set to be. */ if (strcmp(p->context,p->defcontext) != 0) { ast_copy_string(p->context, p->defcontext, sizeof(p->context)); ast_channel_context_set(chan, p->defcontext); } /* Take out of linear mode for Caller*ID processing */ dahdi_setlinear(p->subs[idx].dfd, 0); for (;;) { i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT; if ((res = ioctl(p->subs[idx].dfd, DAHDI_IOMUX, &i))) { ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } if (i & DAHDI_IOMUX_SIGEVENT) { res = dahdi_get_event(p->subs[idx].dfd); ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res)); if (res == DAHDI_EVENT_NOALARM) { p->inalarm = 0; } /* If we get a PR event, they hung up while processing calerid */ if ( res == DAHDI_EVENT_POLARITY && p->hanguponpolarityswitch && p->polarity == POLARITY_REV) { ast_debug(1, "Hanging up due to polarity reversal on channel %d while detecting callerid\n", p->channel); p->polarity = POLARITY_IDLE; callerid_free(cs); ast_hangup(chan); goto quit; } res = 0; /* Let us detect callerid when the telco uses distinctive ring */ curRingData[receivedRingT] = p->ringt; if (p->ringt < p->ringt_base/2) break; /* Increment the ringT counter so we can match it against values in chan_dahdi.conf for distinctive ring */ if (++receivedRingT == ARRAY_LEN(curRingData)) break; } else if (i & DAHDI_IOMUX_READ) { res = read(p->subs[idx].dfd, buf, sizeof(buf)); if (res < 0) { if (errno != ELAST) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } break; } if (p->ringt > 0) { if (!(--p->ringt)) { res = -1; break; } } samples += res; res = callerid_feed(cs, buf, res, AST_LAW(p)); if (res < 0) { /* * The previous diagnostic message output likely * explains why it failed. */ ast_log(LOG_WARNING, "Failed to decode CallerID on channel '%s'\n", ast_channel_name(chan)); break; } else if (res) break; else if (samples > (8000 * 10)) break; } } if (res == 1) { callerid_get(cs, &name, &number, &flags); ast_debug(1, "CallerID number: %s, name: %s, flags=%d\n", number, name, flags); } if (distinctiveringaftercid == 1) { /* Clear the current ring data array so we don't have old data in it. */ for (receivedRingT = 0; receivedRingT < 3; receivedRingT++) { curRingData[receivedRingT] = 0; } receivedRingT = 0; ast_verb(3, "Detecting post-CID distinctive ring\n"); for (;;) { i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT; if ((res = ioctl(p->subs[idx].dfd, DAHDI_IOMUX, &i))) { ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } if (i & DAHDI_IOMUX_SIGEVENT) { res = dahdi_get_event(p->subs[idx].dfd); ast_log(LOG_NOTICE, "Got event %d (%s)...\n", res, event2str(res)); if (res == DAHDI_EVENT_NOALARM) { p->inalarm = 0; } res = 0; /* Let us detect callerid when the telco uses distinctive ring */ curRingData[receivedRingT] = p->ringt; if (p->ringt < p->ringt_base/2) break; /* Increment the ringT counter so we can match it against values in chan_dahdi.conf for distinctive ring */ if (++receivedRingT == ARRAY_LEN(curRingData)) break; } else if (i & DAHDI_IOMUX_READ) { res = read(p->subs[idx].dfd, buf, sizeof(buf)); if (res < 0) { if (errno != ELAST) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); callerid_free(cs); ast_hangup(chan); goto quit; } break; } if (p->ringt > 0) { if (!(--p->ringt)) { res = -1; break; } } } } } if (p->usedistinctiveringdetection) { /* this only shows up if you have n of the dring patterns filled in */ ast_verb(3, "Detected ring pattern: %d,%d,%d\n",curRingData[0],curRingData[1],curRingData[2]); for (counter = 0; counter < 3; counter++) { /* Check to see if the rings we received match any of the ones in chan_dahdi.conf for this channel */ /* this only shows up if you have n of the dring patterns filled in */ ast_verb(3, "Checking %d,%d,%d\n", p->drings.ringnum[counter].ring[0], p->drings.ringnum[counter].ring[1], p->drings.ringnum[counter].ring[2]); distMatches = 0; for (counter1 = 0; counter1 < 3; counter1++) { ast_verb(3, "Ring pattern check range: %d\n", p->drings.ringnum[counter].range); if (p->drings.ringnum[counter].ring[counter1] == -1) { ast_verb(3, "Pattern ignore (-1) detected, so matching pattern %d regardless.\n", curRingData[counter1]); distMatches++; } else if (curRingData[counter1] <= (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range) && curRingData[counter1] >= (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range)) { ast_verb(3, "Ring pattern matched in range: %d to %d\n", (p->drings.ringnum[counter].ring[counter1] - p->drings.ringnum[counter].range), (p->drings.ringnum[counter].ring[counter1] + p->drings.ringnum[counter].range)); distMatches++; } } if (distMatches == 3) { /* The ring matches, set the context to whatever is for distinctive ring.. */ ast_copy_string(p->context, S_OR(p->drings.ringContext[counter].contextData, p->defcontext), sizeof(p->context)); ast_channel_context_set(chan, S_OR(p->drings.ringContext[counter].contextData, p->defcontext)); ast_verb(3, "Distinctive Ring matched context %s\n",p->context); break; } } } /* Restore linear mode (if appropriate) for Caller*ID processing */ dahdi_setlinear(p->subs[idx].dfd, p->subs[idx].linear); #if 1 restore_gains(p); #endif if (res < 0) { ast_log(LOG_WARNING, "CallerID returned with error on channel '%s'\n", ast_channel_name(chan)); } } else ast_log(LOG_WARNING, "Unable to get caller ID space\n"); } } else cs = NULL; if (number) ast_shrink_phone_number(number); ast_set_callerid(chan, number, name, number); ao2_cleanup(smdi_msg); if (cs) callerid_free(cs); my_handle_notify_message(chan, p, flags, -1); ast_channel_lock(chan); ast_setstate(chan, AST_STATE_RING); ast_channel_rings_set(chan, 1); ast_channel_unlock(chan); p->ringt = p->ringt_base; res = ast_pbx_run(chan); if (res) { ast_hangup(chan); ast_log(LOG_WARNING, "PBX exited non-zero\n"); } goto quit; default: ast_log(LOG_WARNING, "Don't know how to handle simple switch with signalling %s on channel %d\n", sig2str(p->sig), p->channel); res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); if (res < 0) ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", p->channel); } res = tone_zone_play_tone(p->subs[idx].dfd, DAHDI_TONE_CONGESTION); if (res < 0) ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", p->channel); ast_hangup(chan); quit: ast_mutex_lock(&ss_thread_lock); ss_thread_count--; ast_cond_signal(&ss_thread_complete); ast_mutex_unlock(&ss_thread_lock); return NULL; } struct mwi_thread_data { struct dahdi_pvt *pvt; unsigned char buf[READ_SIZE]; size_t len; }; static int calc_energy(const unsigned char *buf, int len, struct ast_format *law) { int x; int sum = 0; if (!len) return 0; for (x = 0; x < len; x++) sum += abs(law == ast_format_ulaw ? AST_MULAW(buf[x]) : AST_ALAW(buf[x])); return sum / len; } static void *mwi_thread(void *data) { struct mwi_thread_data *mtd = data; struct callerid_state *cs; pthread_t threadid; int samples = 0; char *name, *number; int flags; int i, res; unsigned int spill_done = 0; int spill_result = -1; if (!(cs = callerid_new(mtd->pvt->cid_signalling))) { goto quit_no_clean; } callerid_feed(cs, mtd->buf, mtd->len, AST_LAW(mtd->pvt)); bump_gains(mtd->pvt); for (;;) { i = DAHDI_IOMUX_READ | DAHDI_IOMUX_SIGEVENT; if ((res = ioctl(mtd->pvt->subs[SUB_REAL].dfd, DAHDI_IOMUX, &i))) { ast_log(LOG_WARNING, "I/O MUX failed: %s\n", strerror(errno)); goto quit; } if (i & DAHDI_IOMUX_SIGEVENT) { struct ast_channel *chan; struct ast_callid *callid = NULL; int callid_created; /* If we get an event, screen out events that we do not act on. * Otherwise, cancel and go to the simple switch to let it deal with it. */ res = dahdi_get_event(mtd->pvt->subs[SUB_REAL].dfd); switch (res) { case DAHDI_EVENT_NEONMWI_ACTIVE: case DAHDI_EVENT_NEONMWI_INACTIVE: case DAHDI_EVENT_NONE: case DAHDI_EVENT_BITSCHANGED: break; case DAHDI_EVENT_NOALARM: if (dahdi_analog_lib_handles(mtd->pvt->sig, mtd->pvt->radio, mtd->pvt->oprmode)) { struct analog_pvt *analog_p = mtd->pvt->sig_pvt; analog_p->inalarm = 0; } mtd->pvt->inalarm = 0; handle_clear_alarms(mtd->pvt); break; case DAHDI_EVENT_ALARM: if (dahdi_analog_lib_handles(mtd->pvt->sig, mtd->pvt->radio, mtd->pvt->oprmode)) { struct analog_pvt *analog_p = mtd->pvt->sig_pvt; analog_p->inalarm = 1; } mtd->pvt->inalarm = 1; res = get_alarms(mtd->pvt); handle_alarms(mtd->pvt, res); break; /* What to do on channel alarm ???? -- fall thru intentionally?? */ default: callid_created = ast_callid_threadstorage_auto(&callid); ast_log(LOG_NOTICE, "Got event %d (%s)... Passing along to analog_ss_thread\n", res, event2str(res)); callerid_free(cs); restore_gains(mtd->pvt); mtd->pvt->ringt = mtd->pvt->ringt_base; if ((chan = dahdi_new(mtd->pvt, AST_STATE_RING, 0, SUB_REAL, 0, NULL, NULL, callid))) { int result; if (dahdi_analog_lib_handles(mtd->pvt->sig, mtd->pvt->radio, mtd->pvt->oprmode)) { result = analog_ss_thread_start(mtd->pvt->sig_pvt, chan); } else { result = ast_pthread_create_detached(&threadid, NULL, analog_ss_thread, chan); } if (result) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", mtd->pvt->channel); res = tone_zone_play_tone(mtd->pvt->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); if (res < 0) ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", mtd->pvt->channel); ast_hangup(chan); } } else { ast_log(LOG_WARNING, "Could not create channel to handle call\n"); } ast_callid_threadstorage_auto_clean(callid, callid_created); goto quit_no_clean; } } else if (i & DAHDI_IOMUX_READ) { if ((res = read(mtd->pvt->subs[SUB_REAL].dfd, mtd->buf, sizeof(mtd->buf))) < 0) { if (errno != ELAST) { ast_log(LOG_WARNING, "read returned error: %s\n", strerror(errno)); goto quit; } break; } samples += res; if (!spill_done) { if ((spill_result = callerid_feed(cs, mtd->buf, res, AST_LAW(mtd->pvt))) < 0) { /* * The previous diagnostic message output likely * explains why it failed. */ ast_log(LOG_WARNING, "Failed to decode CallerID\n"); break; } else if (spill_result) { spill_done = 1; } } else { /* keep reading data until the energy level drops below the threshold so we don't get another 'trigger' on the remaining carrier signal */ if (calc_energy(mtd->buf, res, AST_LAW(mtd->pvt)) <= mwilevel) break; } if (samples > (8000 * 4)) /*Termination case - time to give up*/ break; } } if (spill_result == 1) { callerid_get(cs, &name, &number, &flags); if (flags & CID_MSGWAITING) { ast_log(LOG_NOTICE, "mwi: Have Messages on channel %d\n", mtd->pvt->channel); notify_message(mtd->pvt->mailbox, 1); } else if (flags & CID_NOMSGWAITING) { ast_log(LOG_NOTICE, "mwi: No Messages on channel %d\n", mtd->pvt->channel); notify_message(mtd->pvt->mailbox, 0); } else { ast_log(LOG_NOTICE, "mwi: Status unknown on channel %d\n", mtd->pvt->channel); } } quit: callerid_free(cs); restore_gains(mtd->pvt); quit_no_clean: mtd->pvt->mwimonitoractive = 0; ast_free(mtd); return NULL; } /* * The following three functions (mwi_send_init, mwi_send_process_buffer, * mwi_send_process_event) work with the do_monitor thread to generate mwi spills * that are sent out via FXS port on voicemail state change. The execution of * the mwi send is state driven and can either generate a ring pulse prior to * sending the fsk spill or simply send an fsk spill. */ static int mwi_send_init(struct dahdi_pvt * pvt) { int x; #ifdef HAVE_DAHDI_LINEREVERSE_VMWI /* Determine how this spill is to be sent */ if (pvt->mwisend_rpas) { pvt->mwisend_data.mwisend_current = MWI_SEND_SA; pvt->mwisendactive = 1; } else if (pvt->mwisend_fsk) { pvt->mwisend_data.mwisend_current = MWI_SEND_SPILL; pvt->mwisendactive = 1; } else { pvt->mwisendactive = 0; return 0; } #else if (mwisend_rpas) { pvt->mwisend_data.mwisend_current = MWI_SEND_SA; } else { pvt->mwisend_data.mwisend_current = MWI_SEND_SPILL; } pvt->mwisendactive = 1; #endif if (pvt->cidspill) { ast_log(LOG_WARNING, "cidspill already exists when trying to send FSK MWI\n"); ast_free(pvt->cidspill); pvt->cidspill = NULL; pvt->cidpos = 0; pvt->cidlen = 0; } pvt->cidspill = ast_calloc(1, MAX_CALLERID_SIZE); if (!pvt->cidspill) { pvt->mwisendactive = 0; return -1; } x = DAHDI_FLUSH_BOTH; ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_FLUSH, &x); x = 3000; ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_ONHOOKTRANSFER, &x); #ifdef HAVE_DAHDI_LINEREVERSE_VMWI if (pvt->mwisend_fsk) { #endif pvt->cidlen = ast_callerid_vmwi_generate(pvt->cidspill, has_voicemail(pvt), CID_MWI_TYPE_MDMF_FULL, AST_LAW(pvt), pvt->cid_name, pvt->cid_num, 0); pvt->cidpos = 0; #ifdef HAVE_DAHDI_LINEREVERSE_VMWI } #endif return 0; } static int mwi_send_process_buffer(struct dahdi_pvt * pvt, int num_read) { struct timeval now; int res; /* sanity check to catch if this had been interrupted previously * i.e. state says there is more to do but there is no spill allocated */ if (MWI_SEND_DONE != pvt->mwisend_data.mwisend_current && !pvt->cidspill) { pvt->mwisend_data.mwisend_current = MWI_SEND_DONE; } else if (MWI_SEND_DONE != pvt->mwisend_data.mwisend_current) { /* Normal processing -- Perform mwi send action */ switch ( pvt->mwisend_data.mwisend_current) { case MWI_SEND_SA: /* Send the Ring Pulse Signal Alert */ res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_SETCADENCE, &AS_RP_cadence); if (res) { ast_log(LOG_WARNING, "Unable to set RP-AS ring cadence: %s\n", strerror(errno)); goto quit; } res = dahdi_set_hook(pvt->subs[SUB_REAL].dfd, DAHDI_RING); pvt->mwisend_data.mwisend_current = MWI_SEND_SA_WAIT; break; case MWI_SEND_SA_WAIT: /* do nothing until I get RINGEROFF event */ break; case MWI_SEND_PAUSE: /* Wait between alert and spill - min of 500 mS*/ #ifdef HAVE_DAHDI_LINEREVERSE_VMWI if (pvt->mwisend_fsk) { #endif gettimeofday(&now, NULL); if ((int)(now.tv_sec - pvt->mwisend_data.pause.tv_sec) * 1000000 + (int)now.tv_usec - (int)pvt->mwisend_data.pause.tv_usec > 500000) { pvt->mwisend_data.mwisend_current = MWI_SEND_SPILL; } #ifdef HAVE_DAHDI_LINEREVERSE_VMWI } else { /* support for mwisendtype=nofsk */ pvt->mwisend_data.mwisend_current = MWI_SEND_CLEANUP; } #endif break; case MWI_SEND_SPILL: /* We read some number of bytes. Write an equal amount of data */ if (0 < num_read) { if (num_read > pvt->cidlen - pvt->cidpos) { num_read = pvt->cidlen - pvt->cidpos; } res = write(pvt->subs[SUB_REAL].dfd, pvt->cidspill + pvt->cidpos, num_read); if (res > 0) { pvt->cidpos += res; if (pvt->cidpos >= pvt->cidlen) { pvt->mwisend_data.mwisend_current = MWI_SEND_CLEANUP; } } else { ast_log(LOG_WARNING, "MWI FSK Send Write failed: %s\n", strerror(errno)); goto quit; } } break; case MWI_SEND_CLEANUP: /* For now, do nothing */ pvt->mwisend_data.mwisend_current = MWI_SEND_DONE; break; default: /* Should not get here, punt*/ goto quit; } } if (MWI_SEND_DONE == pvt->mwisend_data.mwisend_current) { if (pvt->cidspill) { ast_free(pvt->cidspill); pvt->cidspill = NULL; pvt->cidpos = 0; pvt->cidlen = 0; } pvt->mwisendactive = 0; } return 0; quit: if (pvt->cidspill) { ast_free(pvt->cidspill); pvt->cidspill = NULL; pvt->cidpos = 0; pvt->cidlen = 0; } pvt->mwisendactive = 0; return -1; } static int mwi_send_process_event(struct dahdi_pvt * pvt, int event) { int handled = 0; if (MWI_SEND_DONE != pvt->mwisend_data.mwisend_current) { switch (event) { case DAHDI_EVENT_RINGEROFF: if (pvt->mwisend_data.mwisend_current == MWI_SEND_SA_WAIT) { handled = 1; if (dahdi_set_hook(pvt->subs[SUB_REAL].dfd, DAHDI_RINGOFF) ) { ast_log(LOG_WARNING, "Unable to finish RP-AS: %s mwi send aborted\n", strerror(errno)); ast_free(pvt->cidspill); pvt->cidspill = NULL; pvt->mwisend_data.mwisend_current = MWI_SEND_DONE; pvt->mwisendactive = 0; } else { pvt->mwisend_data.mwisend_current = MWI_SEND_PAUSE; gettimeofday(&pvt->mwisend_data.pause, NULL); } } break; /* Going off hook, I need to punt this spill */ case DAHDI_EVENT_RINGOFFHOOK: if (pvt->cidspill) { ast_free(pvt->cidspill); pvt->cidspill = NULL; pvt->cidpos = 0; pvt->cidlen = 0; } pvt->mwisend_data.mwisend_current = MWI_SEND_DONE; pvt->mwisendactive = 0; break; case DAHDI_EVENT_RINGERON: case DAHDI_EVENT_HOOKCOMPLETE: break; default: break; } } return handled; } /* destroy a range DAHDI channels, identified by their number */ static void dahdi_destroy_channel_range(int start, int end) { struct dahdi_pvt *cur; struct dahdi_pvt *next; int destroyed_first = 0; int destroyed_last = 0; ast_mutex_lock(&iflock); ast_debug(1, "range: %d-%d\n", start, end); for (cur = iflist; cur; cur = next) { next = cur->next; if (cur->channel >= start && cur->channel <= end) { int x = DAHDI_FLASH; if (cur->channel > destroyed_last) { destroyed_last = cur->channel; } if (destroyed_first < 1 || cur->channel < destroyed_first) { destroyed_first = cur->channel; } ast_debug(3, "Destroying %d\n", cur->channel); /* important to create an event for dahdi_wait_event to register so that all analog_ss_threads terminate */ ioctl(cur->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); destroy_channel(cur, 1); ast_module_unref(ast_module_info->self); } } ast_mutex_unlock(&iflock); if (destroyed_first > start || destroyed_last < end) { ast_debug(1, "Asked to destroy %d-%d, destroyed %d-%d,\n", start, end, destroyed_first, destroyed_last); } } static int setup_dahdi(int reload); static int setup_dahdi_int(int reload, struct dahdi_chan_conf *default_conf, struct dahdi_chan_conf *base_conf, struct dahdi_chan_conf *conf); /*! * \internal * \brief create a range of new DAHDI channels * * \param start first channel in the range * \param end last channel in the range * * \retval RESULT_SUCCESS on success. * \retval RESULT_FAILURE on error. */ static int dahdi_create_channel_range(int start, int end) { struct dahdi_pvt *cur; struct dahdi_chan_conf default_conf = dahdi_chan_conf_default(); struct dahdi_chan_conf base_conf = dahdi_chan_conf_default(); struct dahdi_chan_conf conf = dahdi_chan_conf_default(); int ret = RESULT_FAILURE; /* be pessimistic */ ast_debug(1, "channel range caps: %d - %d\n", start, end); ast_mutex_lock(&iflock); for (cur = iflist; cur; cur = cur->next) { if (cur->channel >= start && cur->channel <= end) { ast_log(LOG_ERROR, "channel range %d-%d is occupied\n", start, end); goto out; } } #ifdef HAVE_PRI { int i, x; for (x = 0; x < NUM_SPANS; x++) { struct dahdi_pri *pri = pris + x; if (!pris[x].pri.pvts[0]) { break; } for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { int channo = pri->dchannels[i]; if (!channo) { break; } if (!pri->pri.fds[i]) { break; } if (channo >= start && channo <= end) { ast_log(LOG_ERROR, "channel range %d-%d is occupied by span %d\n", start, end, x + 1); goto out; } } } } #endif if (!default_conf.chan.cc_params || !base_conf.chan.cc_params || !conf.chan.cc_params) { goto out; } default_conf.wanted_channels_start = start; base_conf.wanted_channels_start = start; conf.wanted_channels_start = start; default_conf.wanted_channels_end = end; base_conf.wanted_channels_end = end; conf.wanted_channels_end = end; if (setup_dahdi_int(0, &default_conf, &base_conf, &conf) == 0) { ret = RESULT_SUCCESS; } out: ast_cc_config_params_destroy(default_conf.chan.cc_params); ast_cc_config_params_destroy(base_conf.chan.cc_params); ast_cc_config_params_destroy(conf.chan.cc_params); ast_mutex_unlock(&iflock); return ret; } static struct dahdi_pvt *handle_init_event(struct dahdi_pvt *i, int event) { int res; pthread_t threadid; struct ast_channel *chan; struct ast_callid *callid = NULL; int callid_created; /* Handle an event on a given channel for the monitor thread. */ switch (event) { case DAHDI_EVENT_NONE: case DAHDI_EVENT_BITSCHANGED: break; case DAHDI_EVENT_WINKFLASH: case DAHDI_EVENT_RINGOFFHOOK: if (i->inalarm) break; if (i->radio) break; /* Got a ring/answer. What kind of channel are we? */ switch (i->sig) { case SIG_FXOLS: case SIG_FXOGS: case SIG_FXOKS: res = dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); if (res && (errno == EBUSY)) { break; } callid_created = ast_callid_threadstorage_auto(&callid); /* Cancel VMWI spill */ ast_free(i->cidspill); i->cidspill = NULL; restore_conference(i); if (i->immediate) { dahdi_ec_enable(i); /* The channel is immediately up. Start right away */ res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE); chan = dahdi_new(i, AST_STATE_RING, 1, SUB_REAL, 0, NULL, NULL, callid); if (!chan) { ast_log(LOG_WARNING, "Unable to start PBX on channel %d\n", i->channel); res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); if (res < 0) ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); } } else { /* Check for callerid, digits, etc */ chan = dahdi_new(i, AST_STATE_RESERVED, 0, SUB_REAL, 0, NULL, NULL, callid); if (chan) { if (has_voicemail(i)) res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_STUTTER); else res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_DIALTONE); if (res < 0) ast_log(LOG_WARNING, "Unable to play dialtone on channel %d, do you have defaultzone and loadzone defined?\n", i->channel); if (ast_pthread_create_detached(&threadid, NULL, analog_ss_thread, chan)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); if (res < 0) ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); ast_hangup(chan); } } else ast_log(LOG_WARNING, "Unable to create channel\n"); } ast_callid_threadstorage_auto_clean(callid, callid_created); break; case SIG_FXSLS: case SIG_FXSGS: case SIG_FXSKS: i->ringt = i->ringt_base; /* Fall through */ case SIG_EMWINK: case SIG_FEATD: case SIG_FEATDMF: case SIG_FEATDMF_TA: case SIG_E911: case SIG_FGC_CAMA: case SIG_FGC_CAMAMF: case SIG_FEATB: case SIG_EM: case SIG_EM_E1: case SIG_SFWINK: case SIG_SF_FEATD: case SIG_SF_FEATDMF: case SIG_SF_FEATB: case SIG_SF: /* Check for callerid, digits, etc */ callid_created = ast_callid_threadstorage_auto(&callid); if (i->cid_start == CID_START_POLARITY_IN) { chan = dahdi_new(i, AST_STATE_PRERING, 0, SUB_REAL, 0, NULL, NULL, callid); } else { chan = dahdi_new(i, AST_STATE_RING, 0, SUB_REAL, 0, NULL, NULL, callid); } if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel); } else if (ast_pthread_create_detached(&threadid, NULL, analog_ss_thread, chan)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); if (res < 0) { ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); } ast_hangup(chan); } ast_callid_threadstorage_auto_clean(callid, callid_created); break; default: ast_log(LOG_WARNING, "Don't know how to handle ring/answer with signalling %s on channel %d\n", sig2str(i->sig), i->channel); res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, DAHDI_TONE_CONGESTION); if (res < 0) ast_log(LOG_WARNING, "Unable to play congestion tone on channel %d\n", i->channel); return NULL; } break; case DAHDI_EVENT_NOALARM: switch (i->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: ast_mutex_lock(&i->lock); sig_pri_chan_alarm_notify(i->sig_pvt, 1); ast_mutex_unlock(&i->lock); break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: sig_ss7_set_alarm(i->sig_pvt, 0); break; #endif /* defined(HAVE_SS7) */ default: i->inalarm = 0; break; } handle_clear_alarms(i); break; case DAHDI_EVENT_ALARM: switch (i->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: ast_mutex_lock(&i->lock); sig_pri_chan_alarm_notify(i->sig_pvt, 0); ast_mutex_unlock(&i->lock); break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: sig_ss7_set_alarm(i->sig_pvt, 1); break; #endif /* defined(HAVE_SS7) */ default: i->inalarm = 1; break; } res = get_alarms(i); handle_alarms(i, res); /* fall thru intentionally */ case DAHDI_EVENT_ONHOOK: if (i->radio) break; /* Back on hook. Hang up. */ switch (i->sig) { case SIG_FXOLS: case SIG_FXOGS: case SIG_FEATD: case SIG_FEATDMF: case SIG_FEATDMF_TA: case SIG_E911: case SIG_FGC_CAMA: case SIG_FGC_CAMAMF: case SIG_FEATB: case SIG_EM: case SIG_EM_E1: case SIG_EMWINK: case SIG_SF_FEATD: case SIG_SF_FEATDMF: case SIG_SF_FEATB: case SIG_SF: case SIG_SFWINK: case SIG_FXSLS: case SIG_FXSGS: case SIG_FXSKS: case SIG_FXOKS: dahdi_ec_disable(i); /* Diddle the battery for the zhone */ #ifdef ZHONE_HACK dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_OFFHOOK); usleep(1); #endif res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, -1); dahdi_set_hook(i->subs[SUB_REAL].dfd, DAHDI_ONHOOK); break; case SIG_SS7: case SIG_PRI_LIB_HANDLE_CASES: dahdi_ec_disable(i); res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, -1); break; default: ast_log(LOG_WARNING, "Don't know how to handle on hook with signalling %s on channel %d\n", sig2str(i->sig), i->channel); res = tone_zone_play_tone(i->subs[SUB_REAL].dfd, -1); return NULL; } break; case DAHDI_EVENT_POLARITY: switch (i->sig) { case SIG_FXSLS: case SIG_FXSKS: case SIG_FXSGS: /* We have already got a PR before the channel was created, but it wasn't handled. We need polarity to be REV for remote hangup detection to work. At least in Spain */ callid_created = ast_callid_threadstorage_auto(&callid); if (i->hanguponpolarityswitch) i->polarity = POLARITY_REV; if (i->cid_start == CID_START_POLARITY || i->cid_start == CID_START_POLARITY_IN) { i->polarity = POLARITY_REV; ast_verb(2, "Starting post polarity " "CID detection on channel %d\n", i->channel); chan = dahdi_new(i, AST_STATE_PRERING, 0, SUB_REAL, 0, NULL, NULL, callid); if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel); } else if (ast_pthread_create_detached(&threadid, NULL, analog_ss_thread, chan)) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); ast_hangup(chan); } } ast_callid_threadstorage_auto_clean(callid, callid_created); break; default: ast_log(LOG_WARNING, "handle_init_event detected " "polarity reversal on non-FXO (SIG_FXS) " "interface %d\n", i->channel); } break; case DAHDI_EVENT_REMOVED: /* destroy channel, will actually do so in do_monitor */ ast_log(LOG_NOTICE, "Got DAHDI_EVENT_REMOVED. Destroying channel %d\n", i->channel); return i; case DAHDI_EVENT_NEONMWI_ACTIVE: if (i->mwimonitor_neon) { notify_message(i->mailbox, 1); ast_log(LOG_NOTICE, "NEON MWI set for channel %d, mailbox %s \n", i->channel, i->mailbox); } break; case DAHDI_EVENT_NEONMWI_INACTIVE: if (i->mwimonitor_neon) { notify_message(i->mailbox, 0); ast_log(LOG_NOTICE, "NEON MWI cleared for channel %d, mailbox %s\n", i->channel, i->mailbox); } break; } return NULL; } static void monitor_pfds_clean(void *arg) { struct pollfd **pfds = arg; ast_free(*pfds); } static void *do_monitor(void *data) { int count, res, res2, spoint, pollres=0; struct dahdi_pvt *i; struct dahdi_pvt *last = NULL; struct dahdi_pvt *doomed; time_t thispass = 0, lastpass = 0; int found; char buf[1024]; struct pollfd *pfds=NULL; int lastalloc = -1; /* This thread monitors all the frame relay interfaces which are not yet in use (and thus do not have a separate thread) indefinitely */ /* From here on out, we die whenever asked */ #if 0 if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL)) { ast_log(LOG_WARNING, "Unable to set cancel type to asynchronous\n"); return NULL; } ast_debug(1, "Monitor starting...\n"); #endif pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pthread_cleanup_push(monitor_pfds_clean, &pfds); for (;;) { /* Lock the interface list */ ast_mutex_lock(&iflock); if (!pfds || (lastalloc != ifcount)) { if (pfds) { ast_free(pfds); pfds = NULL; } if (ifcount) { if (!(pfds = ast_calloc(1, ifcount * sizeof(*pfds)))) { ast_mutex_unlock(&iflock); return NULL; } } lastalloc = ifcount; } /* Build the stuff we're going to poll on, that is the socket of every dahdi_pvt that does not have an associated owner channel */ count = 0; for (i = iflist; i; i = i->next) { ast_mutex_lock(&i->lock); if (pfds && (i->subs[SUB_REAL].dfd > -1) && i->sig && (!i->radio) && !(i->sig & SIG_MFCR2)) { if (dahdi_analog_lib_handles(i->sig, i->radio, i->oprmode)) { struct analog_pvt *p = i->sig_pvt; if (!p) { ast_log(LOG_ERROR, "No sig_pvt?\n"); } else if (!p->owner && !p->subs[SUB_REAL].owner) { /* This needs to be watched, as it lacks an owner */ pfds[count].fd = i->subs[SUB_REAL].dfd; pfds[count].events = POLLPRI; pfds[count].revents = 0; /* Message waiting or r2 channels also get watched for reading */ if (i->cidspill || i->mwisendactive || i->mwimonitor_fsk || (i->cid_start == CID_START_DTMF_NOALERT && (i->sig == SIG_FXSLS || i->sig == SIG_FXSGS || i->sig == SIG_FXSKS))) { pfds[count].events |= POLLIN; } count++; } } else { if (!i->owner && !i->subs[SUB_REAL].owner && !i->mwimonitoractive ) { /* This needs to be watched, as it lacks an owner */ pfds[count].fd = i->subs[SUB_REAL].dfd; pfds[count].events = POLLPRI; pfds[count].revents = 0; /* If we are monitoring for VMWI or sending CID, we need to read from the channel as well */ if (i->cidspill || i->mwisendactive || i->mwimonitor_fsk || (i->cid_start == CID_START_DTMF_NOALERT && (i->sig == SIG_FXSLS || i->sig == SIG_FXSGS || i->sig == SIG_FXSKS))) { pfds[count].events |= POLLIN; } count++; } } } ast_mutex_unlock(&i->lock); } /* Okay, now that we know what to do, release the interface lock */ ast_mutex_unlock(&iflock); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); /* Wait at least a second for something to happen */ res = poll(pfds, count, 1000); pthread_testcancel(); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); /* Okay, poll has finished. Let's see what happened. */ if (res < 0) { if ((errno != EAGAIN) && (errno != EINTR)) ast_log(LOG_WARNING, "poll return %d: %s\n", res, strerror(errno)); continue; } /* Alright, lock the interface list again, and let's look and see what has happened */ ast_mutex_lock(&iflock); found = 0; spoint = 0; lastpass = thispass; thispass = time(NULL); doomed = NULL; for (i = iflist;; i = i->next) { if (doomed) { dahdi_destroy_channel_range(doomed->channel, doomed->channel); doomed = NULL; } if (!i) { break; } if (thispass != lastpass) { if (!found && ((i == last) || ((i == iflist) && !last))) { last = i; if (last) { struct analog_pvt *analog_p = last->sig_pvt; /* Only allow MWI to be initiated on a quiescent fxs port */ if (analog_p && !last->mwisendactive && (last->sig & __DAHDI_SIG_FXO) && !analog_p->fxsoffhookstate && !last->owner && !ast_strlen_zero(last->mailbox) && (thispass - analog_p->onhooktime > 3)) { res = has_voicemail(last); if (analog_p->msgstate != res) { /* Set driver resources for signalling VMWI */ res2 = ioctl(last->subs[SUB_REAL].dfd, DAHDI_VMWI, &res); if (res2) { /* TODO: This message will ALWAYS be generated on some cards; any way to restrict it to those cards where it is interesting? */ ast_debug(3, "Unable to control message waiting led on channel %d: %s\n", last->channel, strerror(errno)); } /* If enabled for FSK spill then initiate it */ if (mwi_send_init(last)) { ast_log(LOG_WARNING, "Unable to initiate mwi send sequence on channel %d\n", last->channel); } analog_p->msgstate = res; found ++; } } last = last->next; } } } if ((i->subs[SUB_REAL].dfd > -1) && i->sig) { if (i->radio && !i->owner) { res = dahdi_get_event(i->subs[SUB_REAL].dfd); if (res) { ast_debug(1, "Monitor doohicky got event %s on radio channel %d\n", event2str(res), i->channel); /* Don't hold iflock while handling init events */ ast_mutex_unlock(&iflock); if (dahdi_analog_lib_handles(i->sig, i->radio, i->oprmode)) doomed = (struct dahdi_pvt *) analog_handle_init_event(i->sig_pvt, dahdievent_to_analogevent(res)); else doomed = handle_init_event(i, res); ast_mutex_lock(&iflock); } continue; } pollres = ast_fdisset(pfds, i->subs[SUB_REAL].dfd, count, &spoint); if (pollres & POLLIN) { if (i->owner || i->subs[SUB_REAL].owner) { #ifdef HAVE_PRI if (!i->pri) #endif ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d) in read...\n", i->subs[SUB_REAL].dfd); continue; } if (!i->mwimonitor_fsk && !i->mwisendactive && i->cid_start != CID_START_DTMF_NOALERT) { ast_log(LOG_WARNING, "Whoa.... I'm not looking for MWI or sending MWI but am reading (%d)...\n", i->subs[SUB_REAL].dfd); continue; } res = read(i->subs[SUB_REAL].dfd, buf, sizeof(buf)); if (res > 0) { if (i->mwimonitor_fsk) { if (calc_energy((unsigned char *) buf, res, AST_LAW(i)) > mwilevel) { pthread_attr_t attr; pthread_t threadid; struct mwi_thread_data *mtd; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); ast_debug(1, "Maybe some MWI on port %d!\n", i->channel); if ((mtd = ast_calloc(1, sizeof(*mtd)))) { mtd->pvt = i; memcpy(mtd->buf, buf, res); mtd->len = res; i->mwimonitoractive = 1; if (ast_pthread_create_background(&threadid, &attr, mwi_thread, mtd)) { ast_log(LOG_WARNING, "Unable to start mwi thread on channel %d\n", i->channel); i->mwimonitoractive = 0; ast_free(mtd); } } } /* If configured to check for a DTMF CID spill that comes without alert (e.g no polarity reversal) */ } else if (i->cid_start == CID_START_DTMF_NOALERT) { int energy; struct timeval now; /* State machine dtmfcid_holdoff_state allows for the line to settle * before checking agin for dtmf energy. Presently waits for 500 mS before checking again */ if (1 == i->dtmfcid_holdoff_state) { gettimeofday(&i->dtmfcid_delay, NULL); i->dtmfcid_holdoff_state = 2; } else if (2 == i->dtmfcid_holdoff_state) { gettimeofday(&now, NULL); if ((int)(now.tv_sec - i->dtmfcid_delay.tv_sec) * 1000000 + (int)now.tv_usec - (int)i->dtmfcid_delay.tv_usec > 500000) { i->dtmfcid_holdoff_state = 0; } } else { energy = calc_energy((unsigned char *) buf, res, AST_LAW(i)); if (!i->mwisendactive && energy > dtmfcid_level) { pthread_t threadid; struct ast_channel *chan; ast_mutex_unlock(&iflock); if (dahdi_analog_lib_handles(i->sig, i->radio, i->oprmode)) { /* just in case this event changes or somehow destroys a channel, set doomed here too */ doomed = analog_handle_init_event(i->sig_pvt, ANALOG_EVENT_DTMFCID); i->dtmfcid_holdoff_state = 1; } else { struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); chan = dahdi_new(i, AST_STATE_PRERING, 0, SUB_REAL, 0, NULL, NULL, callid); if (!chan) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %d\n", i->channel); } else { res = ast_pthread_create_detached(&threadid, NULL, analog_ss_thread, chan); if (res) { ast_log(LOG_WARNING, "Unable to start simple switch thread on channel %d\n", i->channel); ast_hangup(chan); } else { i->dtmfcid_holdoff_state = 1; } } ast_callid_threadstorage_auto_clean(callid, callid_created); } ast_mutex_lock(&iflock); } } } if (i->mwisendactive) { mwi_send_process_buffer(i, res); } } else { ast_log(LOG_WARNING, "Read failed with %d: %s\n", res, strerror(errno)); } } if (pollres & POLLPRI) { if (i->owner || i->subs[SUB_REAL].owner) { #ifdef HAVE_PRI if (!i->pri) #endif ast_log(LOG_WARNING, "Whoa.... I'm owned but found (%d)...\n", i->subs[SUB_REAL].dfd); continue; } res = dahdi_get_event(i->subs[SUB_REAL].dfd); ast_debug(1, "Monitor doohicky got event %s on channel %d\n", event2str(res), i->channel); /* Don't hold iflock while handling init events */ ast_mutex_unlock(&iflock); if (0 == i->mwisendactive || 0 == mwi_send_process_event(i, res)) { if (dahdi_analog_lib_handles(i->sig, i->radio, i->oprmode)) doomed = (struct dahdi_pvt *) analog_handle_init_event(i->sig_pvt, dahdievent_to_analogevent(res)); else doomed = handle_init_event(i, res); } ast_mutex_lock(&iflock); } } } ast_mutex_unlock(&iflock); release_doomed_pris(); } /* Never reached */ pthread_cleanup_pop(1); return NULL; } static int restart_monitor(void) { /* If we're supposed to be stopped -- stay stopped */ if (monitor_thread == AST_PTHREADT_STOP) return 0; ast_mutex_lock(&monlock); if (monitor_thread == pthread_self()) { ast_mutex_unlock(&monlock); ast_log(LOG_WARNING, "Cannot kill myself\n"); return -1; } if (monitor_thread != AST_PTHREADT_NULL) { /* Wake up the thread */ pthread_kill(monitor_thread, SIGURG); } else { /* Start a new monitor */ if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) { ast_mutex_unlock(&monlock); ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); return -1; } } ast_mutex_unlock(&monlock); return 0; } #if defined(HAVE_PRI) static int pri_resolve_span(int *span, int channel, int offset, struct dahdi_spaninfo *si) { int x; int trunkgroup; /* Get appropriate trunk group if there is one */ trunkgroup = pris[*span].mastertrunkgroup; if (trunkgroup) { /* Select a specific trunk group */ for (x = 0; x < NUM_SPANS; x++) { if (pris[x].pri.trunkgroup == trunkgroup) { *span = x; return 0; } } ast_log(LOG_WARNING, "Channel %d on span %d configured to use nonexistent trunk group %d\n", channel, *span, trunkgroup); *span = -1; } else { if (pris[*span].pri.trunkgroup) { ast_log(LOG_WARNING, "Unable to use span %d implicitly since it is trunk group %d (please use spanmap)\n", *span, pris[*span].pri.trunkgroup); *span = -1; } else if (pris[*span].mastertrunkgroup) { ast_log(LOG_WARNING, "Unable to use span %d implicitly since it is already part of trunk group %d\n", *span, pris[*span].mastertrunkgroup); *span = -1; } else { if (si->totalchans == 31) { /* E1 */ pris[*span].dchannels[0] = 16 + offset; } else if (si->totalchans == 24) { /* T1 or J1 */ pris[*span].dchannels[0] = 24 + offset; } else if (si->totalchans == 3) { /* BRI */ pris[*span].dchannels[0] = 3 + offset; } else { ast_log(LOG_WARNING, "Unable to use span %d, since the D-channel cannot be located (unexpected span size of %d channels)\n", *span, si->totalchans); *span = -1; return 0; } pris[*span].pri.span = *span + 1; } } return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int pri_create_trunkgroup(int trunkgroup, int *channels) { struct dahdi_spaninfo si; struct dahdi_params p; int fd; int span; int ospan=0; int x,y; for (x = 0; x < NUM_SPANS; x++) { if (pris[x].pri.trunkgroup == trunkgroup) { ast_log(LOG_WARNING, "Trunk group %d already exists on span %d, Primary d-channel %d\n", trunkgroup, x + 1, pris[x].dchannels[0]); return -1; } } for (y = 0; y < SIG_PRI_NUM_DCHANS; y++) { if (!channels[y]) break; memset(&si, 0, sizeof(si)); memset(&p, 0, sizeof(p)); fd = open("/dev/dahdi/channel", O_RDWR); if (fd < 0) { ast_log(LOG_WARNING, "Failed to open channel: %s\n", strerror(errno)); return -1; } x = channels[y]; if (ioctl(fd, DAHDI_SPECIFY, &x)) { ast_log(LOG_WARNING, "Failed to specify channel %d: %s\n", channels[y], strerror(errno)); close(fd); return -1; } if (ioctl(fd, DAHDI_GET_PARAMS, &p)) { ast_log(LOG_WARNING, "Failed to get channel parameters for channel %d: %s\n", channels[y], strerror(errno)); close(fd); return -1; } if (ioctl(fd, DAHDI_SPANSTAT, &si)) { ast_log(LOG_WARNING, "Failed go get span information on channel %d (span %d): %s\n", channels[y], p.spanno, strerror(errno)); close(fd); return -1; } span = p.spanno - 1; if (pris[span].pri.trunkgroup) { ast_log(LOG_WARNING, "Span %d is already provisioned for trunk group %d\n", span + 1, pris[span].pri.trunkgroup); close(fd); return -1; } if (pris[span].pri.pvts[0]) { ast_log(LOG_WARNING, "Span %d is already provisioned with channels (implicit PRI maybe?)\n", span + 1); close(fd); return -1; } if (!y) { pris[span].pri.trunkgroup = trunkgroup; ospan = span; } pris[ospan].dchannels[y] = channels[y]; pris[span].pri.span = span + 1; close(fd); } return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int pri_create_spanmap(int span, int trunkgroup, int logicalspan) { if (pris[span].mastertrunkgroup) { ast_log(LOG_WARNING, "Span %d is already part of trunk group %d, cannot add to trunk group %d\n", span + 1, pris[span].mastertrunkgroup, trunkgroup); return -1; } pris[span].mastertrunkgroup = trunkgroup; pris[span].prilogicalspan = logicalspan; return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) static unsigned int parse_pointcode(const char *pcstring) { unsigned int code1, code2, code3; int numvals; numvals = sscanf(pcstring, "%30d-%30d-%30d", &code1, &code2, &code3); if (numvals == 1) return code1; if (numvals == 3) return (code1 << 16) | (code2 << 8) | code3; return 0; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static struct dahdi_ss7 * ss7_resolve_linkset(int linkset) { if ((linkset < 0) || (linkset >= NUM_SPANS)) return NULL; else return &linksets[linkset - 1]; } #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 static void dahdi_r2_destroy_links(void) { int i = 0; if (!r2links) { return; } for (; i < r2links_count; i++) { if (r2links[i]->r2master != AST_PTHREADT_NULL) { pthread_cancel(r2links[i]->r2master); pthread_join(r2links[i]->r2master, NULL); openr2_context_delete(r2links[i]->protocol_context); } ast_free(r2links[i]); } ast_free(r2links); r2links = NULL; r2links_count = 0; } /* This is an artificial convenient capacity, to keep at most a full E1 of channels in a single thread */ #define R2_LINK_CAPACITY 30 static struct dahdi_mfcr2 *dahdi_r2_get_link(const struct dahdi_chan_conf *conf) { struct dahdi_mfcr2 *new_r2link = NULL; struct dahdi_mfcr2 **new_r2links = NULL; /* Only create a new R2 link if 1. This is the first link requested 2. Configuration changed 3. We got more channels than supported per link */ if (!r2links_count || memcmp(&conf->mfcr2, &r2links[r2links_count - 1]->conf, sizeof(conf->mfcr2)) || (r2links[r2links_count - 1]->numchans == R2_LINK_CAPACITY)) { new_r2link = ast_calloc(1, sizeof(**r2links)); if (!new_r2link) { ast_log(LOG_ERROR, "Cannot allocate R2 link!\n"); return NULL; } new_r2links = ast_realloc(r2links, ((r2links_count + 1) * sizeof(*r2links))); if (!new_r2links) { ast_log(LOG_ERROR, "Cannot allocate R2 link!\n"); ast_free(new_r2link); return NULL; } r2links = new_r2links; new_r2link->r2master = AST_PTHREADT_NULL; r2links[r2links_count] = new_r2link; r2links_count++; ast_debug(1, "Created new R2 link!\n"); } return r2links[r2links_count - 1]; } static int dahdi_r2_set_context(struct dahdi_mfcr2 *r2_link, const struct dahdi_chan_conf *conf) { char tmplogdir[] = "/tmp"; char logdir[OR2_MAX_PATH]; int threshold = 0; int snres = 0; r2_link->protocol_context = openr2_context_new(NULL, &dahdi_r2_event_iface, &dahdi_r2_transcode_iface, conf->mfcr2.variant, conf->mfcr2.max_ani, conf->mfcr2.max_dnis); if (!r2_link->protocol_context) { return -1; } openr2_context_set_log_level(r2_link->protocol_context, conf->mfcr2.loglevel); openr2_context_set_ani_first(r2_link->protocol_context, conf->mfcr2.get_ani_first); #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 1 openr2_context_set_skip_category_request(r2_link->protocol_context, conf->mfcr2.skip_category_request); #endif openr2_context_set_mf_threshold(r2_link->protocol_context, threshold); openr2_context_set_mf_back_timeout(r2_link->protocol_context, conf->mfcr2.mfback_timeout); openr2_context_set_metering_pulse_timeout(r2_link->protocol_context, conf->mfcr2.metering_pulse_timeout); openr2_context_set_double_answer(r2_link->protocol_context, conf->mfcr2.double_answer); openr2_context_set_immediate_accept(r2_link->protocol_context, conf->mfcr2.immediate_accept); #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 2 openr2_context_set_dtmf_dialing(r2_link->protocol_context, conf->mfcr2.dtmf_dialing, conf->mfcr2.dtmf_time_on, conf->mfcr2.dtmf_time_off); openr2_context_set_dtmf_detection(r2_link->protocol_context, conf->mfcr2.dtmf_detection); #endif #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 3 openr2_context_set_dtmf_detection_end_timeout(r2_link->protocol_context, conf->mfcr2.dtmf_end_timeout); #endif if (ast_strlen_zero(conf->mfcr2.logdir)) { if (openr2_context_set_log_directory(r2_link->protocol_context, tmplogdir)) { ast_log(LOG_ERROR, "Failed setting default MFC/R2 log directory %s\n", tmplogdir); } } else { snres = snprintf(logdir, sizeof(logdir), "%s/%s/%s", ast_config_AST_LOG_DIR, "mfcr2", conf->mfcr2.logdir); if (snres >= sizeof(logdir)) { ast_log(LOG_ERROR, "MFC/R2 logging directory truncated, using %s\n", tmplogdir); if (openr2_context_set_log_directory(r2_link->protocol_context, tmplogdir)) { ast_log(LOG_ERROR, "Failed setting default MFC/R2 log directory %s\n", tmplogdir); } } else { if (openr2_context_set_log_directory(r2_link->protocol_context, logdir)) { ast_log(LOG_ERROR, "Failed setting MFC/R2 log directory %s\n", logdir); } } } if (!ast_strlen_zero(conf->mfcr2.r2proto_file)) { if (openr2_context_configure_from_advanced_file(r2_link->protocol_context, conf->mfcr2.r2proto_file)) { ast_log(LOG_ERROR, "Failed to configure r2context from advanced configuration file %s\n", conf->mfcr2.r2proto_file); } } /* Save the configuration used to setup this link */ memcpy(&r2_link->conf, conf, sizeof(r2_link->conf)); return 0; } #endif /* converts a DAHDI sigtype to signalling as can be configured from * chan_dahdi.conf. * While both have basically the same values, this will later be the * place to add filters and sanity checks */ static int sigtype_to_signalling(int sigtype) { return sigtype; } /*! * \internal * \brief Get file name and channel number from (subdir,number) * * \param subdir name of the subdirectory under /dev/dahdi/ * \param channel name of device file under /dev/dahdi// * \param path buffer to put file name in * \param pathlen maximal length of path * * \retval minor number of dahdi channel. * \retval -errno on error. */ static int device2chan(const char *subdir, int channel, char *path, int pathlen) { struct stat stbuf; int num; snprintf(path, pathlen, "/dev/dahdi/%s/%d", subdir, channel); if (stat(path, &stbuf) < 0) { ast_log(LOG_ERROR, "stat(%s) failed: %s\n", path, strerror(errno)); return -errno; } if (!S_ISCHR(stbuf.st_mode)) { ast_log(LOG_ERROR, "%s: Not a character device file\n", path); return -EINVAL; } num = minor(stbuf.st_rdev); ast_debug(1, "%s -> %d\n", path, num); return num; } /*! * \internal * \brief Initialize/create a channel interface. * * \param channel Channel interface number to initialize/create. * \param conf Configuration parameters to initialize interface with. * \param reloading What we are doing now: * 0 - initial module load, * 1 - module reload, * 2 - module restart * * \retval Interface-pointer initialized/created * \retval NULL if error */ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, int reloading) { /* Make a dahdi_pvt structure for this interface */ struct dahdi_pvt *tmp;/*!< Current channel structure initializing */ char fn[80]; struct dahdi_bufferinfo bi; int res; #if defined(HAVE_PRI) int span = 0; #endif /* defined(HAVE_PRI) */ int here = 0;/*!< TRUE if the channel interface already exists. */ int x; struct analog_pvt *analog_p = NULL; struct dahdi_params p; #if defined(HAVE_PRI) struct dahdi_spaninfo si; struct sig_pri_chan *pri_chan = NULL; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) struct sig_ss7_chan *ss7_chan = NULL; #endif /* defined(HAVE_SS7) */ /* Search channel interface list to see if it already exists. */ for (tmp = iflist; tmp; tmp = tmp->next) { if (!tmp->destroy) { if (tmp->channel == channel) { /* The channel interface already exists. */ here = 1; break; } if (tmp->channel > channel) { /* No way it can be in the sorted list. */ tmp = NULL; break; } } } if (!here && reloading != 1) { tmp = ast_calloc(1, sizeof(*tmp)); if (!tmp) { return NULL; } tmp->cc_params = ast_cc_config_params_init(); if (!tmp->cc_params) { ast_free(tmp); return NULL; } ast_mutex_init(&tmp->lock); ifcount++; for (x = 0; x < 3; x++) tmp->subs[x].dfd = -1; tmp->channel = channel; tmp->priindication_oob = conf->chan.priindication_oob; } if (tmp) { int chan_sig = conf->chan.sig; /* If there are variables in tmp before it is updated to match the new config, clear them */ if (reloading && tmp->vars) { ast_variables_destroy(tmp->vars); tmp->vars = NULL; } if (!here) { /* Can only get here if this is a new channel interface being created. */ if ((channel != CHAN_PSEUDO)) { int count = 0; snprintf(fn, sizeof(fn), "%d", channel); /* Open non-blocking */ tmp->subs[SUB_REAL].dfd = dahdi_open(fn); while (tmp->subs[SUB_REAL].dfd < 0 && reloading == 2 && count < 1000) { /* the kernel may not call dahdi_release fast enough for the open flagbit to be cleared in time */ usleep(1); tmp->subs[SUB_REAL].dfd = dahdi_open(fn); count++; } /* Allocate a DAHDI structure */ if (tmp->subs[SUB_REAL].dfd < 0) { ast_log(LOG_ERROR, "Unable to open channel %d: %s\nhere = %d, tmp->channel = %d, channel = %d\n", channel, strerror(errno), here, tmp->channel, channel); destroy_dahdi_pvt(tmp); return NULL; } memset(&p, 0, sizeof(p)); res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &p); if (res < 0) { ast_log(LOG_ERROR, "Unable to get parameters: %s\n", strerror(errno)); destroy_dahdi_pvt(tmp); return NULL; } if (conf->is_sig_auto) chan_sig = sigtype_to_signalling(p.sigtype); if (p.sigtype != (chan_sig & 0x3ffff)) { ast_log(LOG_ERROR, "Signalling requested on channel %d is %s but line is in %s signalling\n", channel, sig2str(chan_sig), sig2str(p.sigtype)); destroy_dahdi_pvt(tmp); return NULL; } tmp->law_default = p.curlaw; tmp->law = p.curlaw; tmp->span = p.spanno; #if defined(HAVE_PRI) span = p.spanno - 1; #endif /* defined(HAVE_PRI) */ } else { chan_sig = 0; } tmp->sig = chan_sig; tmp->outsigmod = conf->chan.outsigmod; if (dahdi_analog_lib_handles(chan_sig, tmp->radio, tmp->oprmode)) { analog_p = analog_new(dahdisig_to_analogsig(chan_sig), tmp); if (!analog_p) { destroy_dahdi_pvt(tmp); return NULL; } tmp->sig_pvt = analog_p; } #if defined(HAVE_SS7) if (chan_sig == SIG_SS7) { struct dahdi_ss7 *ss7; int clear = 0; if (ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &clear)) { ast_log(LOG_ERROR, "Unable to set clear mode on clear channel %d of span %d: %s\n", channel, p.spanno, strerror(errno)); destroy_dahdi_pvt(tmp); return NULL; } ss7 = ss7_resolve_linkset(cur_linkset); if (!ss7) { ast_log(LOG_ERROR, "Unable to find linkset %d\n", cur_linkset); destroy_dahdi_pvt(tmp); return NULL; } ss7->ss7.span = cur_linkset; if (cur_cicbeginswith < 0) { ast_log(LOG_ERROR, "Need to set cicbeginswith for the channels!\n"); destroy_dahdi_pvt(tmp); return NULL; } ss7_chan = sig_ss7_chan_new(tmp, &ss7->ss7); if (!ss7_chan) { destroy_dahdi_pvt(tmp); return NULL; } tmp->sig_pvt = ss7_chan; tmp->ss7 = &ss7->ss7; ss7_chan->channel = tmp->channel; ss7_chan->cic = cur_cicbeginswith++; /* DB: Add CIC's DPC information */ ss7_chan->dpc = cur_defaultdpc; ss7->ss7.pvts[ss7->ss7.numchans++] = ss7_chan; ast_copy_string(ss7->ss7.internationalprefix, conf->ss7.ss7.internationalprefix, sizeof(ss7->ss7.internationalprefix)); ast_copy_string(ss7->ss7.nationalprefix, conf->ss7.ss7.nationalprefix, sizeof(ss7->ss7.nationalprefix)); ast_copy_string(ss7->ss7.subscriberprefix, conf->ss7.ss7.subscriberprefix, sizeof(ss7->ss7.subscriberprefix)); ast_copy_string(ss7->ss7.unknownprefix, conf->ss7.ss7.unknownprefix, sizeof(ss7->ss7.unknownprefix)); ast_copy_string(ss7->ss7.networkroutedprefix, conf->ss7.ss7.networkroutedprefix, sizeof(ss7->ss7.networkroutedprefix)); ss7->ss7.called_nai = conf->ss7.ss7.called_nai; ss7->ss7.calling_nai = conf->ss7.ss7.calling_nai; } #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 if (chan_sig == SIG_MFCR2) { struct dahdi_mfcr2 *r2_link; r2_link = dahdi_r2_get_link(conf); if (!r2_link) { ast_log(LOG_WARNING, "Cannot get another R2 DAHDI context!\n"); destroy_dahdi_pvt(tmp); return NULL; } if (!r2_link->protocol_context && dahdi_r2_set_context(r2_link, conf)) { ast_log(LOG_ERROR, "Cannot create OpenR2 protocol context.\n"); destroy_dahdi_pvt(tmp); return NULL; } if (r2_link->numchans == ARRAY_LEN(r2_link->pvts)) { ast_log(LOG_ERROR, "Cannot add more channels to this link!\n"); destroy_dahdi_pvt(tmp); return NULL; } r2_link->pvts[r2_link->numchans++] = tmp; tmp->r2chan = openr2_chan_new_from_fd(r2_link->protocol_context, tmp->subs[SUB_REAL].dfd, NULL, NULL); if (!tmp->r2chan) { openr2_liberr_t err = openr2_context_get_last_error(r2_link->protocol_context); ast_log(LOG_ERROR, "Cannot create OpenR2 channel: %s\n", openr2_context_error_string(err)); destroy_dahdi_pvt(tmp); return NULL; } tmp->mfcr2 = r2_link; if (conf->mfcr2.call_files) { openr2_chan_enable_call_files(tmp->r2chan); } openr2_chan_set_client_data(tmp->r2chan, tmp); /* cast seems to be needed to get rid of the annoying warning regarding format attribute */ openr2_chan_set_logging_func(tmp->r2chan, (openr2_logging_func_t)dahdi_r2_on_chan_log); openr2_chan_set_log_level(tmp->r2chan, conf->mfcr2.loglevel); tmp->mfcr2_category = conf->mfcr2.category; tmp->mfcr2_charge_calls = conf->mfcr2.charge_calls; tmp->mfcr2_allow_collect_calls = conf->mfcr2.allow_collect_calls; tmp->mfcr2_forced_release = conf->mfcr2.forced_release; tmp->mfcr2_accept_on_offer = conf->mfcr2.accept_on_offer; tmp->mfcr2call = 0; tmp->mfcr2_dnis_index = 0; tmp->mfcr2_ani_index = 0; } #endif #ifdef HAVE_PRI if (dahdi_sig_pri_lib_handles(chan_sig)) { int offset; int matchesdchan; int x,y; int myswitchtype = 0; offset = 0; if (ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_AUDIOMODE, &offset)) { ast_log(LOG_ERROR, "Unable to set clear mode on clear channel %d of span %d: %s\n", channel, p.spanno, strerror(errno)); destroy_dahdi_pvt(tmp); return NULL; } if (span >= NUM_SPANS) { ast_log(LOG_ERROR, "Channel %d does not lie on a span I know of (%d)\n", channel, span); destroy_dahdi_pvt(tmp); return NULL; } else { si.spanno = 0; if (ioctl(tmp->subs[SUB_REAL].dfd,DAHDI_SPANSTAT,&si) == -1) { ast_log(LOG_ERROR, "Unable to get span status: %s\n", strerror(errno)); destroy_dahdi_pvt(tmp); return NULL; } /* Store the logical span first based upon the real span */ tmp->logicalspan = pris[span].prilogicalspan; pri_resolve_span(&span, channel, (channel - p.chanpos), &si); if (span < 0) { ast_log(LOG_WARNING, "Channel %d: Unable to find locate channel/trunk group!\n", channel); destroy_dahdi_pvt(tmp); return NULL; } myswitchtype = conf->pri.pri.switchtype; /* Make sure this isn't a d-channel */ matchesdchan=0; for (x = 0; x < NUM_SPANS; x++) { for (y = 0; y < SIG_PRI_NUM_DCHANS; y++) { if (pris[x].dchannels[y] == tmp->channel) { matchesdchan = 1; break; } } } if (!matchesdchan) { if (pris[span].pri.nodetype && (pris[span].pri.nodetype != conf->pri.pri.nodetype)) { ast_log(LOG_ERROR, "Span %d is already a %s node\n", span + 1, pri_node2str(pris[span].pri.nodetype)); destroy_dahdi_pvt(tmp); return NULL; } if (pris[span].pri.switchtype && (pris[span].pri.switchtype != myswitchtype)) { ast_log(LOG_ERROR, "Span %d is already a %s switch\n", span + 1, pri_switch2str(pris[span].pri.switchtype)); destroy_dahdi_pvt(tmp); return NULL; } if ((pris[span].pri.dialplan) && (pris[span].pri.dialplan != conf->pri.pri.dialplan)) { ast_log(LOG_ERROR, "Span %d is already a %s dialing plan\n", span + 1, pris[span].pri.dialplan == -1 ? "Dynamically set dialplan in ISDN" : pri_plan2str(pris[span].pri.dialplan)); destroy_dahdi_pvt(tmp); return NULL; } if (!ast_strlen_zero(pris[span].pri.idledial) && strcmp(pris[span].pri.idledial, conf->pri.pri.idledial)) { ast_log(LOG_ERROR, "Span %d already has idledial '%s'.\n", span + 1, conf->pri.pri.idledial); destroy_dahdi_pvt(tmp); return NULL; } if (!ast_strlen_zero(pris[span].pri.idleext) && strcmp(pris[span].pri.idleext, conf->pri.pri.idleext)) { ast_log(LOG_ERROR, "Span %d already has idleext '%s'.\n", span + 1, conf->pri.pri.idleext); destroy_dahdi_pvt(tmp); return NULL; } if (pris[span].pri.minunused && (pris[span].pri.minunused != conf->pri.pri.minunused)) { ast_log(LOG_ERROR, "Span %d already has minunused of %d.\n", span + 1, conf->pri.pri.minunused); destroy_dahdi_pvt(tmp); return NULL; } if (pris[span].pri.minidle && (pris[span].pri.minidle != conf->pri.pri.minidle)) { ast_log(LOG_ERROR, "Span %d already has minidle of %d.\n", span + 1, conf->pri.pri.minidle); destroy_dahdi_pvt(tmp); return NULL; } if (pris[span].pri.numchans >= ARRAY_LEN(pris[span].pri.pvts)) { ast_log(LOG_ERROR, "Unable to add channel %d: Too many channels in trunk group %d!\n", channel, pris[span].pri.trunkgroup); destroy_dahdi_pvt(tmp); return NULL; } pri_chan = sig_pri_chan_new(tmp, &pris[span].pri, tmp->logicalspan, p.chanpos, pris[span].mastertrunkgroup); if (!pri_chan) { destroy_dahdi_pvt(tmp); return NULL; } tmp->sig_pvt = pri_chan; tmp->pri = &pris[span].pri; tmp->priexclusive = conf->chan.priexclusive; if (!tmp->pri->cc_params) { tmp->pri->cc_params = ast_cc_config_params_init(); if (!tmp->pri->cc_params) { destroy_dahdi_pvt(tmp); return NULL; } } ast_cc_copy_config_params(tmp->pri->cc_params, conf->chan.cc_params); pris[span].pri.sig = chan_sig; pris[span].pri.nodetype = conf->pri.pri.nodetype; pris[span].pri.switchtype = myswitchtype; pris[span].pri.nsf = conf->pri.pri.nsf; pris[span].pri.dialplan = conf->pri.pri.dialplan; pris[span].pri.localdialplan = conf->pri.pri.localdialplan; pris[span].pri.cpndialplan = conf->pri.pri.cpndialplan; pris[span].pri.pvts[pris[span].pri.numchans++] = tmp->sig_pvt; pris[span].pri.minunused = conf->pri.pri.minunused; pris[span].pri.minidle = conf->pri.pri.minidle; pris[span].pri.overlapdial = conf->pri.pri.overlapdial; pris[span].pri.qsigchannelmapping = conf->pri.pri.qsigchannelmapping; pris[span].pri.discardremoteholdretrieval = conf->pri.pri.discardremoteholdretrieval; #if defined(HAVE_PRI_SERVICE_MESSAGES) pris[span].pri.enable_service_message_support = conf->pri.pri.enable_service_message_support; #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ #ifdef HAVE_PRI_INBANDDISCONNECT pris[span].pri.inbanddisconnect = conf->pri.pri.inbanddisconnect; #endif #if defined(HAVE_PRI_CALL_HOLD) pris[span].pri.hold_disconnect_transfer = conf->pri.pri.hold_disconnect_transfer; #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CCSS) pris[span].pri.cc_ptmp_recall_mode = conf->pri.pri.cc_ptmp_recall_mode; pris[span].pri.cc_qsig_signaling_link_req = conf->pri.pri.cc_qsig_signaling_link_req; pris[span].pri.cc_qsig_signaling_link_rsp = conf->pri.pri.cc_qsig_signaling_link_rsp; #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CALL_WAITING) pris[span].pri.max_call_waiting_calls = conf->pri.pri.max_call_waiting_calls; pris[span].pri.allow_call_waiting_calls = conf->pri.pri.allow_call_waiting_calls; #endif /* defined(HAVE_PRI_CALL_WAITING) */ pris[span].pri.transfer = conf->chan.transfer; pris[span].pri.facilityenable = conf->pri.pri.facilityenable; #if defined(HAVE_PRI_L2_PERSISTENCE) pris[span].pri.l2_persistence = conf->pri.pri.l2_persistence; #endif /* defined(HAVE_PRI_L2_PERSISTENCE) */ pris[span].pri.colp_send = conf->pri.pri.colp_send; #if defined(HAVE_PRI_AOC_EVENTS) pris[span].pri.aoc_passthrough_flag = conf->pri.pri.aoc_passthrough_flag; pris[span].pri.aoce_delayhangup = conf->pri.pri.aoce_delayhangup; #endif /* defined(HAVE_PRI_AOC_EVENTS) */ if (chan_sig == SIG_BRI_PTMP) { pris[span].pri.layer1_ignored = conf->pri.pri.layer1_ignored; } else { /* Option does not apply to this line type. */ pris[span].pri.layer1_ignored = 0; } pris[span].pri.append_msn_to_user_tag = conf->pri.pri.append_msn_to_user_tag; pris[span].pri.inband_on_setup_ack = conf->pri.pri.inband_on_setup_ack; pris[span].pri.inband_on_proceeding = conf->pri.pri.inband_on_proceeding; ast_copy_string(pris[span].pri.initial_user_tag, conf->chan.cid_tag, sizeof(pris[span].pri.initial_user_tag)); ast_copy_string(pris[span].pri.msn_list, conf->pri.pri.msn_list, sizeof(pris[span].pri.msn_list)); #if defined(HAVE_PRI_MWI) ast_copy_string(pris[span].pri.mwi_mailboxes, conf->pri.pri.mwi_mailboxes, sizeof(pris[span].pri.mwi_mailboxes)); ast_copy_string(pris[span].pri.mwi_vm_boxes, conf->pri.pri.mwi_vm_boxes, sizeof(pris[span].pri.mwi_vm_boxes)); ast_copy_string(pris[span].pri.mwi_vm_numbers, conf->pri.pri.mwi_vm_numbers, sizeof(pris[span].pri.mwi_vm_numbers)); #endif /* defined(HAVE_PRI_MWI) */ ast_copy_string(pris[span].pri.idledial, conf->pri.pri.idledial, sizeof(pris[span].pri.idledial)); ast_copy_string(pris[span].pri.idleext, conf->pri.pri.idleext, sizeof(pris[span].pri.idleext)); ast_copy_string(pris[span].pri.internationalprefix, conf->pri.pri.internationalprefix, sizeof(pris[span].pri.internationalprefix)); ast_copy_string(pris[span].pri.nationalprefix, conf->pri.pri.nationalprefix, sizeof(pris[span].pri.nationalprefix)); ast_copy_string(pris[span].pri.localprefix, conf->pri.pri.localprefix, sizeof(pris[span].pri.localprefix)); ast_copy_string(pris[span].pri.privateprefix, conf->pri.pri.privateprefix, sizeof(pris[span].pri.privateprefix)); ast_copy_string(pris[span].pri.unknownprefix, conf->pri.pri.unknownprefix, sizeof(pris[span].pri.unknownprefix)); pris[span].pri.moh_signaling = conf->pri.pri.moh_signaling; pris[span].pri.resetinterval = conf->pri.pri.resetinterval; #if defined(HAVE_PRI_DISPLAY_TEXT) pris[span].pri.display_flags_send = conf->pri.pri.display_flags_send; pris[span].pri.display_flags_receive = conf->pri.pri.display_flags_receive; #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #if defined(HAVE_PRI_MCID) pris[span].pri.mcid_send = conf->pri.pri.mcid_send; #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_DATETIME_SEND) pris[span].pri.datetime_send = conf->pri.pri.datetime_send; #endif /* defined(HAVE_PRI_DATETIME_SEND) */ for (x = 0; x < PRI_MAX_TIMERS; x++) { pris[span].pri.pritimers[x] = conf->pri.pri.pritimers[x]; } #if defined(HAVE_PRI_CALL_WAITING) /* Channel initial config parameters. */ pris[span].pri.ch_cfg.stripmsd = conf->chan.stripmsd; pris[span].pri.ch_cfg.hidecallerid = conf->chan.hidecallerid; pris[span].pri.ch_cfg.hidecalleridname = conf->chan.hidecalleridname; pris[span].pri.ch_cfg.immediate = conf->chan.immediate; pris[span].pri.ch_cfg.priexclusive = conf->chan.priexclusive; pris[span].pri.ch_cfg.priindication_oob = conf->chan.priindication_oob; pris[span].pri.ch_cfg.use_callerid = conf->chan.use_callerid; pris[span].pri.ch_cfg.use_callingpres = conf->chan.use_callingpres; ast_copy_string(pris[span].pri.ch_cfg.context, conf->chan.context, sizeof(pris[span].pri.ch_cfg.context)); ast_copy_string(pris[span].pri.ch_cfg.mohinterpret, conf->chan.mohinterpret, sizeof(pris[span].pri.ch_cfg.mohinterpret)); #endif /* defined(HAVE_PRI_CALL_WAITING) */ } else { ast_log(LOG_ERROR, "Channel %d is reserved for D-channel.\n", p.chanpos); destroy_dahdi_pvt(tmp); return NULL; } } } #endif } else { /* already exists in interface list */ ast_log(LOG_WARNING, "Attempt to configure channel %d with signaling %s ignored because it is already configured to be %s.\n", tmp->channel, dahdi_sig2str(chan_sig), dahdi_sig2str(tmp->sig)); chan_sig = tmp->sig; if (tmp->subs[SUB_REAL].dfd > -1) { memset(&p, 0, sizeof(p)); res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &p); } } /* Adjust starttime on loopstart and kewlstart trunks to reasonable values */ switch (chan_sig) { case SIG_FXSKS: case SIG_FXSLS: case SIG_EM: case SIG_EM_E1: case SIG_EMWINK: case SIG_FEATD: case SIG_FEATDMF: case SIG_FEATDMF_TA: case SIG_FEATB: case SIG_E911: case SIG_SF: case SIG_SFWINK: case SIG_FGC_CAMA: case SIG_FGC_CAMAMF: case SIG_SF_FEATD: case SIG_SF_FEATDMF: case SIG_SF_FEATB: p.starttime = 250; break; } if (tmp->radio) { /* XXX Waiting to hear back from Jim if these should be adjustable XXX */ p.channo = channel; p.rxwinktime = 1; p.rxflashtime = 1; p.starttime = 1; p.debouncetime = 5; } else { p.channo = channel; /* Override timing settings based on config file */ if (conf->timing.prewinktime >= 0) p.prewinktime = conf->timing.prewinktime; if (conf->timing.preflashtime >= 0) p.preflashtime = conf->timing.preflashtime; if (conf->timing.winktime >= 0) p.winktime = conf->timing.winktime; if (conf->timing.flashtime >= 0) p.flashtime = conf->timing.flashtime; if (conf->timing.starttime >= 0) p.starttime = conf->timing.starttime; if (conf->timing.rxwinktime >= 0) p.rxwinktime = conf->timing.rxwinktime; if (conf->timing.rxflashtime >= 0) p.rxflashtime = conf->timing.rxflashtime; if (conf->timing.debouncetime >= 0) p.debouncetime = conf->timing.debouncetime; } /* don't set parms on a pseudo-channel */ if (tmp->subs[SUB_REAL].dfd >= 0) { res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_SET_PARAMS, &p); if (res < 0) { ast_log(LOG_ERROR, "Unable to set parameters: %s\n", strerror(errno)); destroy_dahdi_pvt(tmp); return NULL; } } #if 1 if (!here && (tmp->subs[SUB_REAL].dfd > -1)) { memset(&bi, 0, sizeof(bi)); res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_BUFINFO, &bi); if (!res) { bi.txbufpolicy = conf->chan.buf_policy; bi.rxbufpolicy = conf->chan.buf_policy; bi.numbufs = conf->chan.buf_no; res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi); if (res < 0) { ast_log(LOG_WARNING, "Unable to set buffer policy on channel %d: %s\n", channel, strerror(errno)); } } else { ast_log(LOG_WARNING, "Unable to check buffer policy on channel %d: %s\n", channel, strerror(errno)); } tmp->buf_policy = conf->chan.buf_policy; tmp->buf_no = conf->chan.buf_no; tmp->usefaxbuffers = conf->chan.usefaxbuffers; tmp->faxbuf_policy = conf->chan.faxbuf_policy; tmp->faxbuf_no = conf->chan.faxbuf_no; /* This is not as gnarly as it may first appear. If the ioctl above failed, we'd be setting * tmp->bufsize to zero which would cause subsequent faxbuffer-related ioctl calls to fail. * The reason the ioctl call above failed should to be determined before worrying about the * faxbuffer-related ioctl calls */ tmp->bufsize = bi.bufsize; } #endif tmp->immediate = conf->chan.immediate; tmp->transfertobusy = conf->chan.transfertobusy; if (chan_sig & __DAHDI_SIG_FXS) { tmp->mwimonitor_fsk = conf->chan.mwimonitor_fsk; tmp->mwimonitor_neon = conf->chan.mwimonitor_neon; tmp->mwimonitor_rpas = conf->chan.mwimonitor_rpas; } tmp->ringt_base = ringt_base; tmp->firstradio = 0; if ((chan_sig == SIG_FXOKS) || (chan_sig == SIG_FXOLS) || (chan_sig == SIG_FXOGS)) tmp->permcallwaiting = conf->chan.callwaiting; else tmp->permcallwaiting = 0; /* Flag to destroy the channel must be cleared on new mkif. Part of changes for reload to work */ tmp->destroy = 0; tmp->drings = conf->chan.drings; /* 10 is a nice default. */ if (tmp->drings.ringnum[0].range == 0) tmp->drings.ringnum[0].range = 10; if (tmp->drings.ringnum[1].range == 0) tmp->drings.ringnum[1].range = 10; if (tmp->drings.ringnum[2].range == 0) tmp->drings.ringnum[2].range = 10; tmp->usedistinctiveringdetection = usedistinctiveringdetection; tmp->callwaitingcallerid = conf->chan.callwaitingcallerid; tmp->threewaycalling = conf->chan.threewaycalling; tmp->adsi = conf->chan.adsi; tmp->use_smdi = conf->chan.use_smdi; tmp->permhidecallerid = conf->chan.hidecallerid; tmp->hidecalleridname = conf->chan.hidecalleridname; tmp->callreturn = conf->chan.callreturn; tmp->echocancel = conf->chan.echocancel; tmp->echotraining = conf->chan.echotraining; tmp->pulse = conf->chan.pulse; if (tmp->echocancel.head.tap_length) { tmp->echocanbridged = conf->chan.echocanbridged; } else { if (conf->chan.echocanbridged) ast_log(LOG_NOTICE, "echocancelwhenbridged requires echocancel to be enabled; ignoring\n"); tmp->echocanbridged = 0; } tmp->busydetect = conf->chan.busydetect; tmp->busycount = conf->chan.busycount; tmp->busy_cadence = conf->chan.busy_cadence; tmp->callprogress = conf->chan.callprogress; tmp->waitfordialtone = conf->chan.waitfordialtone; tmp->dialtone_detect = conf->chan.dialtone_detect; tmp->cancallforward = conf->chan.cancallforward; tmp->dtmfrelax = conf->chan.dtmfrelax; tmp->callwaiting = tmp->permcallwaiting; tmp->hidecallerid = tmp->permhidecallerid; tmp->channel = channel; tmp->stripmsd = conf->chan.stripmsd; tmp->use_callerid = conf->chan.use_callerid; tmp->cid_signalling = conf->chan.cid_signalling; tmp->cid_start = conf->chan.cid_start; tmp->dahditrcallerid = conf->chan.dahditrcallerid; tmp->restrictcid = conf->chan.restrictcid; tmp->use_callingpres = conf->chan.use_callingpres; if (tmp->usedistinctiveringdetection) { if (!tmp->use_callerid) { ast_log(LOG_NOTICE, "Distinctive Ring detect requires 'usecallerid' be on\n"); tmp->use_callerid = 1; } } if (tmp->cid_signalling == CID_SIG_SMDI) { if (!tmp->use_smdi) { ast_log(LOG_WARNING, "SMDI callerid requires SMDI to be enabled, enabling...\n"); tmp->use_smdi = 1; } } if (tmp->use_smdi) { tmp->smdi_iface = ast_smdi_interface_find(conf->smdi_port); if (!(tmp->smdi_iface)) { ast_log(LOG_ERROR, "Invalid SMDI port specfied, disabling SMDI support\n"); tmp->use_smdi = 0; } } ast_copy_string(tmp->accountcode, conf->chan.accountcode, sizeof(tmp->accountcode)); tmp->amaflags = conf->chan.amaflags; if (!here) { tmp->confno = -1; tmp->propconfno = -1; } tmp->canpark = conf->chan.canpark; tmp->transfer = conf->chan.transfer; ast_copy_string(tmp->defcontext,conf->chan.context,sizeof(tmp->defcontext)); ast_copy_string(tmp->language, conf->chan.language, sizeof(tmp->language)); ast_copy_string(tmp->mohinterpret, conf->chan.mohinterpret, sizeof(tmp->mohinterpret)); ast_copy_string(tmp->mohsuggest, conf->chan.mohsuggest, sizeof(tmp->mohsuggest)); ast_copy_string(tmp->context, conf->chan.context, sizeof(tmp->context)); ast_copy_string(tmp->description, conf->chan.description, sizeof(tmp->description)); ast_copy_string(tmp->parkinglot, conf->chan.parkinglot, sizeof(tmp->parkinglot)); tmp->cid_ton = 0; if (dahdi_analog_lib_handles(tmp->sig, tmp->radio, tmp->oprmode)) { ast_copy_string(tmp->cid_num, conf->chan.cid_num, sizeof(tmp->cid_num)); ast_copy_string(tmp->cid_name, conf->chan.cid_name, sizeof(tmp->cid_name)); } else { tmp->cid_num[0] = '\0'; tmp->cid_name[0] = '\0'; } #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(tmp->sig)) { tmp->cid_tag[0] = '\0'; } else #endif /* defined(HAVE_PRI) */ { ast_copy_string(tmp->cid_tag, conf->chan.cid_tag, sizeof(tmp->cid_tag)); } tmp->cid_subaddr[0] = '\0'; ast_copy_string(tmp->mailbox, conf->chan.mailbox, sizeof(tmp->mailbox)); if (channel != CHAN_PSEUDO && !ast_strlen_zero(tmp->mailbox)) { struct stasis_topic *mailbox_specific_topic; mailbox_specific_topic = ast_mwi_topic(tmp->mailbox); if (mailbox_specific_topic) { tmp->mwi_event_sub = stasis_subscribe_pool(mailbox_specific_topic, mwi_event_cb, NULL); } } #ifdef HAVE_DAHDI_LINEREVERSE_VMWI tmp->mwisend_setting = conf->chan.mwisend_setting; tmp->mwisend_fsk = conf->chan.mwisend_fsk; tmp->mwisend_rpas = conf->chan.mwisend_rpas; #endif tmp->group = conf->chan.group; tmp->callgroup = conf->chan.callgroup; tmp->pickupgroup= conf->chan.pickupgroup; ast_unref_namedgroups(tmp->named_callgroups); tmp->named_callgroups = ast_ref_namedgroups(conf->chan.named_callgroups); ast_unref_namedgroups(tmp->named_pickupgroups); tmp->named_pickupgroups = ast_ref_namedgroups(conf->chan.named_pickupgroups); if (conf->chan.vars) { struct ast_variable *v, *tmpvar; for (v = conf->chan.vars ; v ; v = v->next) { if ((tmpvar = ast_variable_new(v->name, v->value, v->file))) { tmpvar->next = tmp->vars; tmp->vars = tmpvar; } } } tmp->hwrxgain_enabled = conf->chan.hwrxgain_enabled; tmp->hwtxgain_enabled = conf->chan.hwtxgain_enabled; tmp->hwrxgain = conf->chan.hwrxgain; tmp->hwtxgain = conf->chan.hwtxgain; tmp->cid_rxgain = conf->chan.cid_rxgain; tmp->rxgain = conf->chan.rxgain; tmp->txgain = conf->chan.txgain; tmp->txdrc = conf->chan.txdrc; tmp->rxdrc = conf->chan.rxdrc; tmp->tonezone = conf->chan.tonezone; if (tmp->subs[SUB_REAL].dfd > -1) { if (tmp->hwrxgain_enabled) { tmp->hwrxgain_enabled = !set_hwgain(tmp->subs[SUB_REAL].dfd, tmp->hwrxgain, 0); } if (tmp->hwtxgain_enabled) { tmp->hwtxgain_enabled = !set_hwgain(tmp->subs[SUB_REAL].dfd, tmp->hwtxgain, 1); } set_actual_gain(tmp->subs[SUB_REAL].dfd, tmp->rxgain, tmp->txgain, tmp->rxdrc, tmp->txdrc, tmp->law); if (tmp->dsp) ast_dsp_set_digitmode(tmp->dsp, DSP_DIGITMODE_DTMF | tmp->dtmfrelax); dahdi_conf_update(tmp); if (!here) { switch (chan_sig) { case SIG_PRI_LIB_HANDLE_CASES: case SIG_SS7: case SIG_MFCR2: break; default: /* Hang it up to be sure it's good */ dahdi_set_hook(tmp->subs[SUB_REAL].dfd, DAHDI_ONHOOK); break; } } ioctl(tmp->subs[SUB_REAL].dfd,DAHDI_SETTONEZONE,&tmp->tonezone); if ((res = get_alarms(tmp)) != DAHDI_ALARM_NONE) { /* the dchannel is down so put the channel in alarm */ switch (tmp->sig) { #ifdef HAVE_PRI case SIG_PRI_LIB_HANDLE_CASES: sig_pri_set_alarm(tmp->sig_pvt, 1); break; #endif #if defined(HAVE_SS7) case SIG_SS7: sig_ss7_set_alarm(tmp->sig_pvt, 1); break; #endif /* defined(HAVE_SS7) */ default: /* The only sig submodule left should be sig_analog. */ analog_p = tmp->sig_pvt; if (analog_p) { analog_p->inalarm = 1; } tmp->inalarm = 1; break; } handle_alarms(tmp, res); } } tmp->polarityonanswerdelay = conf->chan.polarityonanswerdelay; tmp->answeronpolarityswitch = conf->chan.answeronpolarityswitch; tmp->hanguponpolarityswitch = conf->chan.hanguponpolarityswitch; tmp->sendcalleridafter = conf->chan.sendcalleridafter; ast_cc_copy_config_params(tmp->cc_params, conf->chan.cc_params); if (!here) { tmp->locallyblocked = 0; tmp->remotelyblocked = 0; switch (tmp->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: tmp->inservice = 1;/* Inservice until actually implemented. */ #if defined(HAVE_PRI_SERVICE_MESSAGES) ((struct sig_pri_chan *) tmp->sig_pvt)->service_status = 0; if (chan_sig == SIG_PRI) { char db_chan_name[20]; char db_answer[5]; /* * Initialize the active out-of-service status * and delete any record if the feature is not enabled. */ snprintf(db_chan_name, sizeof(db_chan_name), "%s/%d:%d", dahdi_db, tmp->span, tmp->channel); if (!ast_db_get(db_chan_name, SRVST_DBKEY, db_answer, sizeof(db_answer))) { unsigned *why; why = &((struct sig_pri_chan *) tmp->sig_pvt)->service_status; if (tmp->pri->enable_service_message_support) { char state; sscanf(db_answer, "%1c:%30u", &state, why); /* Ensure that only the implemented bits could be set.*/ *why &= (SRVST_NEAREND | SRVST_FAREND); } if (!*why) { ast_db_del(db_chan_name, SRVST_DBKEY); } } } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: tmp->inservice = 0; if (tmp->ss7->flags & LINKSET_FLAG_INITIALHWBLO) { tmp->remotelyblocked |= SS7_BLOCKED_HARDWARE; } break; #endif /* defined(HAVE_SS7) */ default: /* We default to in service on protocols that don't have a reset */ tmp->inservice = 1; break; } } switch (tmp->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: if (pri_chan) { pri_chan->channel = tmp->channel; pri_chan->hidecallerid = tmp->hidecallerid; pri_chan->hidecalleridname = tmp->hidecalleridname; pri_chan->immediate = tmp->immediate; pri_chan->inalarm = tmp->inalarm; pri_chan->priexclusive = tmp->priexclusive; pri_chan->priindication_oob = tmp->priindication_oob; pri_chan->use_callerid = tmp->use_callerid; pri_chan->use_callingpres = tmp->use_callingpres; ast_copy_string(pri_chan->context, tmp->context, sizeof(pri_chan->context)); ast_copy_string(pri_chan->mohinterpret, tmp->mohinterpret, sizeof(pri_chan->mohinterpret)); pri_chan->stripmsd = tmp->stripmsd; } break; #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: if (ss7_chan) { ss7_chan->inalarm = tmp->inalarm; ss7_chan->inservice = tmp->inservice; ss7_chan->stripmsd = tmp->stripmsd; ss7_chan->hidecallerid = tmp->hidecallerid; ss7_chan->use_callerid = tmp->use_callerid; ss7_chan->use_callingpres = tmp->use_callingpres; ss7_chan->immediate = tmp->immediate; ss7_chan->locallyblocked = tmp->locallyblocked; ss7_chan->remotelyblocked = tmp->remotelyblocked; ast_copy_string(ss7_chan->context, tmp->context, sizeof(ss7_chan->context)); ast_copy_string(ss7_chan->mohinterpret, tmp->mohinterpret, sizeof(ss7_chan->mohinterpret)); } break; #endif /* defined(HAVE_SS7) */ default: /* The only sig submodule left should be sig_analog. */ analog_p = tmp->sig_pvt; if (analog_p) { analog_p->channel = tmp->channel; analog_p->polarityonanswerdelay = conf->chan.polarityonanswerdelay; analog_p->answeronpolarityswitch = conf->chan.answeronpolarityswitch; analog_p->hanguponpolarityswitch = conf->chan.hanguponpolarityswitch; analog_p->permcallwaiting = conf->chan.callwaiting; /* permcallwaiting possibly modified in analog_config_complete */ analog_p->callreturn = conf->chan.callreturn; analog_p->cancallforward = conf->chan.cancallforward; analog_p->canpark = conf->chan.canpark; analog_p->dahditrcallerid = conf->chan.dahditrcallerid; analog_p->immediate = conf->chan.immediate; analog_p->permhidecallerid = conf->chan.permhidecallerid; analog_p->pulse = conf->chan.pulse; analog_p->threewaycalling = conf->chan.threewaycalling; analog_p->transfer = conf->chan.transfer; analog_p->transfertobusy = conf->chan.transfertobusy; analog_p->use_callerid = tmp->use_callerid; analog_p->use_smdi = tmp->use_smdi; analog_p->smdi_iface = tmp->smdi_iface; analog_p->outsigmod = ANALOG_SIG_NONE; analog_p->echotraining = conf->chan.echotraining; analog_p->cid_signalling = conf->chan.cid_signalling; analog_p->stripmsd = conf->chan.stripmsd; switch (conf->chan.cid_start) { case CID_START_POLARITY: analog_p->cid_start = ANALOG_CID_START_POLARITY; break; case CID_START_POLARITY_IN: analog_p->cid_start = ANALOG_CID_START_POLARITY_IN; break; case CID_START_DTMF_NOALERT: analog_p->cid_start = ANALOG_CID_START_DTMF_NOALERT; break; default: analog_p->cid_start = ANALOG_CID_START_RING; break; } analog_p->callwaitingcallerid = conf->chan.callwaitingcallerid; analog_p->ringt = conf->chan.ringt; analog_p->ringt_base = ringt_base; analog_p->onhooktime = time(NULL); if (chan_sig & __DAHDI_SIG_FXO) { memset(&p, 0, sizeof(p)); res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &p); if (!res) { analog_p->fxsoffhookstate = p.rxisoffhook; } #ifdef HAVE_DAHDI_LINEREVERSE_VMWI res = ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_VMWI_CONFIG, &tmp->mwisend_setting); #endif } analog_p->msgstate = -1; ast_copy_string(analog_p->mohsuggest, conf->chan.mohsuggest, sizeof(analog_p->mohsuggest)); ast_copy_string(analog_p->cid_num, conf->chan.cid_num, sizeof(analog_p->cid_num)); ast_copy_string(analog_p->cid_name, conf->chan.cid_name, sizeof(analog_p->cid_name)); analog_config_complete(analog_p); } break; } #if defined(HAVE_PRI) if (tmp->channel == CHAN_PSEUDO) { /* * Save off pseudo channel buffer policy values for dynamic creation of * no B channel interfaces. */ dahdi_pseudo_parms.buf_no = tmp->buf_no; dahdi_pseudo_parms.buf_policy = tmp->buf_policy; dahdi_pseudo_parms.faxbuf_no = tmp->faxbuf_no; dahdi_pseudo_parms.faxbuf_policy = tmp->faxbuf_policy; } #endif /* defined(HAVE_PRI) */ } if (tmp && !here) { /* Add the new channel interface to the sorted channel interface list. */ dahdi_iflist_insert(tmp); } return tmp; } static int is_group_or_channel_match(struct dahdi_pvt *p, int span, ast_group_t groupmatch, int *groupmatched, int channelmatch, int *channelmatched) { #if defined(HAVE_PRI) if (0 < span) { /* The channel must be on the specified PRI span. */ if (!p->pri || p->pri->span != span) { return 0; } if (!groupmatch && channelmatch == -1) { /* Match any group since it only needs to be on the PRI span. */ *groupmatched = 1; return 1; } } #endif /* defined(HAVE_PRI) */ /* check group matching */ if (groupmatch) { if ((p->group & groupmatch) != groupmatch) /* Doesn't match the specified group, try the next one */ return 0; *groupmatched = 1; } /* Check to see if we have a channel match */ if (channelmatch != -1) { if (p->channel != channelmatch) /* Doesn't match the specified channel, try the next one */ return 0; *channelmatched = 1; } return 1; } static int available(struct dahdi_pvt **pvt, int is_specific_channel) { struct dahdi_pvt *p = *pvt; if (p->inalarm) return 0; if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) return analog_available(p->sig_pvt); switch (p->sig) { #if defined(HAVE_PRI) case SIG_PRI_LIB_HANDLE_CASES: { struct sig_pri_chan *pvt_chan; int res; pvt_chan = p->sig_pvt; res = sig_pri_available(&pvt_chan, is_specific_channel); *pvt = pvt_chan->chan_pvt; return res; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) case SIG_SS7: return sig_ss7_available(p->sig_pvt); #endif /* defined(HAVE_SS7) */ default: break; } if (p->locallyblocked || p->remotelyblocked) { return 0; } /* If no owner definitely available */ if (!p->owner) { #ifdef HAVE_OPENR2 /* Trust MFC/R2 */ if (p->mfcr2) { if (p->mfcr2call) { return 0; } else { return 1; } } #endif return 1; } return 0; } #if defined(HAVE_PRI) #if defined(HAVE_PRI_CALL_WAITING) /*! * \internal * \brief Init the private channel configuration using the span controller. * \since 1.8 * * \param priv Channel to init the configuration. * \param pri sig_pri PRI control structure. * * \note Assumes the pri->lock is already obtained. * * \return Nothing */ static void my_pri_init_config(void *priv, struct sig_pri_span *pri) { struct dahdi_pvt *pvt = priv; pvt->stripmsd = pri->ch_cfg.stripmsd; pvt->hidecallerid = pri->ch_cfg.hidecallerid; pvt->hidecalleridname = pri->ch_cfg.hidecalleridname; pvt->immediate = pri->ch_cfg.immediate; pvt->priexclusive = pri->ch_cfg.priexclusive; pvt->priindication_oob = pri->ch_cfg.priindication_oob; pvt->use_callerid = pri->ch_cfg.use_callerid; pvt->use_callingpres = pri->ch_cfg.use_callingpres; ast_copy_string(pvt->context, pri->ch_cfg.context, sizeof(pvt->context)); ast_copy_string(pvt->mohinterpret, pri->ch_cfg.mohinterpret, sizeof(pvt->mohinterpret)); } #endif /* defined(HAVE_PRI_CALL_WAITING) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) /*! * \internal * \brief Create a no B channel interface. * \since 1.8 * * \param pri sig_pri span controller to add interface. * * \note Assumes the pri->lock is already obtained. * * \retval array-index into private pointer array on success. * \retval -1 on error. */ static int dahdi_new_pri_nobch_channel(struct sig_pri_span *pri) { int pvt_idx; int res; unsigned idx; struct dahdi_pvt *pvt; struct sig_pri_chan *chan; struct dahdi_bufferinfo bi; static int nobch_channel = CHAN_PSEUDO; /* Find spot in the private pointer array for new interface. */ for (pvt_idx = 0; pvt_idx < pri->numchans; ++pvt_idx) { if (!pri->pvts[pvt_idx]) { break; } } if (pri->numchans == pvt_idx) { if (ARRAY_LEN(pri->pvts) <= pvt_idx) { ast_log(LOG_ERROR, "Unable to add a no-B-channel interface!\n"); return -1; } /* Add new spot to the private pointer array. */ pri->pvts[pvt_idx] = NULL; ++pri->numchans; } pvt = ast_calloc(1, sizeof(*pvt)); if (!pvt) { return -1; } pvt->cc_params = ast_cc_config_params_init(); if (!pvt->cc_params) { ast_free(pvt); return -1; } ast_mutex_init(&pvt->lock); for (idx = 0; idx < ARRAY_LEN(pvt->subs); ++idx) { pvt->subs[idx].dfd = -1; } pvt->buf_no = dahdi_pseudo_parms.buf_no; pvt->buf_policy = dahdi_pseudo_parms.buf_policy; pvt->faxbuf_no = dahdi_pseudo_parms.faxbuf_no; pvt->faxbuf_policy = dahdi_pseudo_parms.faxbuf_policy; chan = sig_pri_chan_new(pvt, pri, 0, 0, 0); if (!chan) { destroy_dahdi_pvt(pvt); return -1; } chan->no_b_channel = 1; /* * Pseudo channel companding law. * Needed for outgoing call waiting calls. * XXX May need to make this determined by switchtype or user option. */ pvt->law_default = DAHDI_LAW_ALAW; pvt->sig = pri->sig; pvt->outsigmod = -1; pvt->pri = pri; pvt->sig_pvt = chan; pri->pvts[pvt_idx] = chan; pvt->subs[SUB_REAL].dfd = dahdi_open("/dev/dahdi/pseudo"); if (pvt->subs[SUB_REAL].dfd < 0) { ast_log(LOG_ERROR, "Unable to open no B channel interface pseudo channel: %s\n", strerror(errno)); destroy_dahdi_pvt(pvt); return -1; } memset(&bi, 0, sizeof(bi)); res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_GET_BUFINFO, &bi); if (!res) { pvt->bufsize = bi.bufsize; bi.txbufpolicy = pvt->buf_policy; bi.rxbufpolicy = pvt->buf_policy; bi.numbufs = pvt->buf_no; res = ioctl(pvt->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi); if (res < 0) { ast_log(LOG_WARNING, "Unable to set buffer policy on no B channel interface: %s\n", strerror(errno)); } } else ast_log(LOG_WARNING, "Unable to check buffer policy on no B channel interface: %s\n", strerror(errno)); --nobch_channel; if (CHAN_PSEUDO < nobch_channel) { nobch_channel = CHAN_PSEUDO - 1; } pvt->channel = nobch_channel; pvt->span = pri->span; chan->channel = pvt->channel; dahdi_nobch_insert(pri, pvt); return pvt_idx; } #endif /* defined(HAVE_PRI) */ /* This function can *ONLY* be used for copying pseudo (CHAN_PSEUDO) private structures; it makes no attempt to safely copy regular channel private structures that might contain reference-counted object pointers and other scary bits */ static struct dahdi_pvt *duplicate_pseudo(struct dahdi_pvt *src) { struct dahdi_pvt *p; struct dahdi_bufferinfo bi; int res; p = ast_malloc(sizeof(*p)); if (!p) { return NULL; } *p = *src; /* Must deep copy the cc_params. */ p->cc_params = ast_cc_config_params_init(); if (!p->cc_params) { ast_free(p); return NULL; } ast_cc_copy_config_params(p->cc_params, src->cc_params); p->which_iflist = DAHDI_IFLIST_NONE; p->next = NULL; p->prev = NULL; ast_mutex_init(&p->lock); p->subs[SUB_REAL].dfd = dahdi_open("/dev/dahdi/pseudo"); if (p->subs[SUB_REAL].dfd < 0) { ast_log(LOG_ERROR, "Unable to dup channel: %s\n", strerror(errno)); destroy_dahdi_pvt(p); return NULL; } res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_GET_BUFINFO, &bi); if (!res) { bi.txbufpolicy = src->buf_policy; bi.rxbufpolicy = src->buf_policy; bi.numbufs = src->buf_no; res = ioctl(p->subs[SUB_REAL].dfd, DAHDI_SET_BUFINFO, &bi); if (res < 0) { ast_log(LOG_WARNING, "Unable to set buffer policy on dup channel: %s\n", strerror(errno)); } } else ast_log(LOG_WARNING, "Unable to check buffer policy on dup channel: %s\n", strerror(errno)); p->destroy = 1; dahdi_iflist_insert(p); return p; } struct dahdi_starting_point { /*! Group matching mask. Zero if not specified. */ ast_group_t groupmatch; /*! DAHDI channel to match with. -1 if not specified. */ int channelmatch; /*! Round robin saved search location index. (Valid if roundrobin TRUE) */ int rr_starting_point; /*! ISDN span where channels can be picked (Zero if not specified) */ int span; /*! Analog channel distinctive ring cadance index. */ int cadance; /*! Dialing option. c/r/d if present and valid. */ char opt; /*! TRUE if to search the channel list backwards. */ char backwards; /*! TRUE if search is done with round robin sequence. */ char roundrobin; }; static struct dahdi_pvt *determine_starting_point(const char *data, struct dahdi_starting_point *param) { char *dest; char *s; int x; int res = 0; struct dahdi_pvt *p; char *subdir = NULL; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(group); /* channel/group token */ //AST_APP_ARG(ext); /* extension token */ //AST_APP_ARG(opts); /* options token */ AST_APP_ARG(other); /* Any remining unused arguments */ ); /* * data is ---v * Dial(DAHDI/pseudo[/extension[/options]]) * Dial(DAHDI/[c|r|d][/extension[/options]]) * Dial(DAHDI/![c|r|d][/extension[/options]]) * Dial(DAHDI/i[/extension[/options]]) * Dial(DAHDI/[i-](g|G|r|R)[c|r|d][/extension[/options]]) * * i - ISDN span channel restriction. * Used by CC to ensure that the CC recall goes out the same span. * Also to make ISDN channel names dialable when the sequence number * is stripped off. (Used by DTMF attended transfer feature.) * * g - channel group allocation search forward * G - channel group allocation search backward * r - channel group allocation round robin search forward * R - channel group allocation round robin search backward * * c - Wait for DTMF digit to confirm answer * r - Set distintive ring cadance number * d - Force bearer capability for ISDN/SS7 call to digital. */ if (data) { dest = ast_strdupa(data); } else { ast_log(LOG_WARNING, "Channel requested with no data\n"); return NULL; } AST_NONSTANDARD_APP_ARGS(args, dest, '/'); if (!args.argc || ast_strlen_zero(args.group)) { ast_log(LOG_WARNING, "No channel/group specified\n"); return NULL; } /* Initialize the output parameters */ memset(param, 0, sizeof(*param)); param->channelmatch = -1; if (strchr(args.group, '!') != NULL) { char *prev = args.group; while ((s = strchr(prev, '!')) != NULL) { *s++ = '/'; prev = s; } *(prev - 1) = '\0'; subdir = args.group; args.group = prev; } else if (args.group[0] == 'i') { /* Extract the ISDN span channel restriction specifier. */ res = sscanf(args.group + 1, "%30d", &x); if (res < 1) { ast_log(LOG_WARNING, "Unable to determine ISDN span for data %s\n", data); return NULL; } param->span = x; /* Remove the ISDN span channel restriction specifier. */ s = strchr(args.group, '-'); if (!s) { /* Search all groups since we are ISDN span restricted. */ return iflist; } args.group = s + 1; res = 0; } if (toupper(args.group[0]) == 'G' || toupper(args.group[0])=='R') { /* Retrieve the group number */ s = args.group + 1; res = sscanf(s, "%30d%1c%30d", &x, ¶m->opt, ¶m->cadance); if (res < 1) { ast_log(LOG_WARNING, "Unable to determine group for data %s\n", data); return NULL; } param->groupmatch = ((ast_group_t) 1 << x); if (toupper(args.group[0]) == 'G') { if (args.group[0] == 'G') { param->backwards = 1; p = ifend; } else p = iflist; } else { if (ARRAY_LEN(round_robin) <= x) { ast_log(LOG_WARNING, "Round robin index %d out of range for data %s\n", x, data); return NULL; } if (args.group[0] == 'R') { param->backwards = 1; p = round_robin[x] ? round_robin[x]->prev : ifend; if (!p) p = ifend; } else { p = round_robin[x] ? round_robin[x]->next : iflist; if (!p) p = iflist; } param->roundrobin = 1; param->rr_starting_point = x; } } else { s = args.group; if (!strcasecmp(s, "pseudo")) { /* Special case for pseudo */ x = CHAN_PSEUDO; param->channelmatch = x; } else { res = sscanf(s, "%30d%1c%30d", &x, ¶m->opt, ¶m->cadance); if (res < 1) { ast_log(LOG_WARNING, "Unable to determine channel for data %s\n", data); return NULL; } else { param->channelmatch = x; } } if (subdir) { char path[PATH_MAX]; struct stat stbuf; snprintf(path, sizeof(path), "/dev/dahdi/%s/%d", subdir, param->channelmatch); if (stat(path, &stbuf) < 0) { ast_log(LOG_WARNING, "stat(%s) failed: %s\n", path, strerror(errno)); return NULL; } if (!S_ISCHR(stbuf.st_mode)) { ast_log(LOG_ERROR, "%s: Not a character device file\n", path); return NULL; } param->channelmatch = minor(stbuf.st_rdev); } p = iflist; } if (param->opt == 'r' && res < 3) { ast_log(LOG_WARNING, "Distinctive ring missing identifier in '%s'\n", data); param->opt = '\0'; } return p; } static struct ast_channel *dahdi_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { int callwait = 0; struct dahdi_pvt *p; struct ast_channel *tmp = NULL; struct dahdi_pvt *exitpvt; int channelmatched = 0; int groupmatched = 0; #if defined(HAVE_PRI) || defined(HAVE_SS7) int transcapdigital = 0; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ struct dahdi_starting_point start; struct ast_callid *callid = NULL; int callid_created = ast_callid_threadstorage_auto(&callid); ast_mutex_lock(&iflock); p = determine_starting_point(data, &start); if (!p) { /* We couldn't determine a starting point, which likely means badly-formatted channel name. Abort! */ ast_mutex_unlock(&iflock); ast_callid_threadstorage_auto_clean(callid, callid_created); return NULL; } /* Search for an unowned channel */ exitpvt = p; while (p && !tmp) { if (start.roundrobin) round_robin[start.rr_starting_point] = p; if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched) && available(&p, channelmatched)) { ast_debug(1, "Using channel %d\n", p->channel); callwait = (p->owner != NULL); #ifdef HAVE_OPENR2 if (p->mfcr2) { ast_mutex_lock(&p->lock); if (p->mfcr2call) { ast_mutex_unlock(&p->lock); ast_debug(1, "Yay!, someone just beat us in the race for channel %d.\n", p->channel); goto next; } p->mfcr2call = 1; ast_mutex_unlock(&p->lock); } #endif if (p->channel == CHAN_PSEUDO) { p = duplicate_pseudo(p); if (!p) { break; } } p->distinctivering = 0; /* Make special notes */ switch (start.opt) { case '\0': /* No option present. */ break; case 'c': /* Confirm answer */ p->confirmanswer = 1; break; case 'r': /* Distinctive ring */ p->distinctivering = start.cadance; break; case 'd': #if defined(HAVE_PRI) || defined(HAVE_SS7) /* If this is an ISDN call, make it digital */ transcapdigital = AST_TRANS_CAP_DIGITAL; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ break; default: ast_log(LOG_WARNING, "Unknown option '%c' in '%s'\n", start.opt, data); break; } p->outgoing = 1; if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) { tmp = analog_request(p->sig_pvt, &callwait, requestor); #ifdef HAVE_PRI } else if (dahdi_sig_pri_lib_handles(p->sig)) { /* * We already have the B channel reserved for this call. We * just need to make sure that dahdi_hangup() has completed * cleaning up before continuing. */ ast_mutex_lock(&p->lock); ast_mutex_unlock(&p->lock); sig_pri_extract_called_num_subaddr(p->sig_pvt, data, p->dnid, sizeof(p->dnid)); tmp = sig_pri_request(p->sig_pvt, SIG_PRI_DEFLAW, assignedids, requestor, transcapdigital); #endif #if defined(HAVE_SS7) } else if (p->sig == SIG_SS7) { tmp = sig_ss7_request(p->sig_pvt, SIG_SS7_DEFLAW, assignedids, requestor, transcapdigital); #endif /* defined(HAVE_SS7) */ } else { tmp = dahdi_new(p, AST_STATE_RESERVED, 0, p->owner ? SUB_CALLWAIT : SUB_REAL, 0, assignedids, requestor, callid); } if (!tmp) { p->outgoing = 0; #if defined(HAVE_PRI) switch (p->sig) { case SIG_PRI_LIB_HANDLE_CASES: #if defined(HAVE_PRI_CALL_WAITING) if (((struct sig_pri_chan *) p->sig_pvt)->is_call_waiting) { ((struct sig_pri_chan *) p->sig_pvt)->is_call_waiting = 0; ast_atomic_fetchadd_int(&p->pri->num_call_waiting_calls, -1); } #endif /* defined(HAVE_PRI_CALL_WAITING) */ /* * This should be the last thing to clear when we are done with * the channel. */ ((struct sig_pri_chan *) p->sig_pvt)->allocated = 0; break; default: break; } #endif /* defined(HAVE_PRI) */ } else { snprintf(p->dialstring, sizeof(p->dialstring), "DAHDI/%s", data); } break; } #ifdef HAVE_OPENR2 next: #endif if (start.backwards) { p = p->prev; if (!p) p = ifend; } else { p = p->next; if (!p) p = iflist; } /* stop when you roll to the one that we started from */ if (p == exitpvt) break; } ast_mutex_unlock(&iflock); restart_monitor(); if (cause && !tmp) { if (callwait || channelmatched) { *cause = AST_CAUSE_BUSY; } else if (groupmatched) { *cause = AST_CAUSE_CONGESTION; } else { /* * We did not match any channel requested. * Dialplan error requesting non-existant channel? */ } } ast_callid_threadstorage_auto_clean(callid, callid_created); return tmp; } /*! * \internal * \brief Determine the device state for a given DAHDI device if we can. * \since 1.8 * * \param data DAHDI device name after "DAHDI/". * * \retval device_state enum ast_device_state value. * \retval AST_DEVICE_UNKNOWN if we could not determine the device's state. */ static int dahdi_devicestate(const char *data) { #if defined(HAVE_PRI) const char *device; unsigned span; int res; device = data; if (*device != 'I') { /* The request is not for an ISDN span device. */ return AST_DEVICE_UNKNOWN; } res = sscanf(device, "I%30u", &span); if (res != 1 || !span || NUM_SPANS < span) { /* Bad format for ISDN span device name. */ return AST_DEVICE_UNKNOWN; } device = strchr(device, '/'); if (!device) { /* Bad format for ISDN span device name. */ return AST_DEVICE_UNKNOWN; } /* * Since there are currently no other span devstate's defined, * it must be congestion. */ #if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) ++device; if (!strcmp(device, "congestion")) #endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ { return pris[span - 1].pri.congestion_devstate; } #if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) else if (!strcmp(device, "threshold")) { return pris[span - 1].pri.threshold_devstate; } return AST_DEVICE_UNKNOWN; #endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ #else return AST_DEVICE_UNKNOWN; #endif /* defined(HAVE_PRI) */ } /*! * \brief Callback made when dial failed to get a channel out of dahdi_request(). * \since 1.8 * * \param inbound Incoming asterisk channel. * \param dest Same dial string passed to dahdi_request(). * \param callback Callback into CC core to announce a busy channel available for CC. * * \details * This callback acts like a forked dial with all prongs of the fork busy. * Essentially, for each channel that could have taken the call, indicate that * it is busy. * * \retval 0 on success. * \retval -1 on error. */ static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback) { struct dahdi_pvt *p; struct dahdi_pvt *exitpvt; struct dahdi_starting_point start; int groupmatched = 0; int channelmatched = 0; ast_mutex_lock(&iflock); p = determine_starting_point(dest, &start); if (!p) { ast_mutex_unlock(&iflock); return -1; } exitpvt = p; for (;;) { if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched)) { /* We found a potential match. call the callback */ struct ast_str *device_name; char *dash; const char *monitor_type; char dialstring[AST_CHANNEL_NAME]; char full_device_name[AST_CHANNEL_NAME]; switch (ast_get_cc_monitor_policy(p->cc_params)) { case AST_CC_MONITOR_NEVER: break; case AST_CC_MONITOR_NATIVE: case AST_CC_MONITOR_ALWAYS: case AST_CC_MONITOR_GENERIC: #if defined(HAVE_PRI) if (dahdi_sig_pri_lib_handles(p->sig)) { /* * ISDN is in a trunk busy condition so we need to monitor * the span congestion device state. */ snprintf(full_device_name, sizeof(full_device_name), "DAHDI/I%d/congestion", p->pri->span); } else #endif /* defined(HAVE_PRI) */ { #if defined(HAVE_PRI) device_name = create_channel_name(p, 1, ""); #else device_name = create_channel_name(p); #endif /* defined(HAVE_PRI) */ snprintf(full_device_name, sizeof(full_device_name), "DAHDI/%s", device_name ? ast_str_buffer(device_name) : ""); ast_free(device_name); /* * The portion after the '-' in the channel name is either a random * number, a sequence number, or a subchannel number. None are * necessary so strip them off. */ dash = strrchr(full_device_name, '-'); if (dash) { *dash = '\0'; } } snprintf(dialstring, sizeof(dialstring), "DAHDI/%s", dest); /* * Analog can only do generic monitoring. * ISDN is in a trunk busy condition and any "device" is going * to be busy until a B channel becomes available. The generic * monitor can do this task. */ monitor_type = AST_CC_GENERIC_MONITOR_TYPE; callback(inbound, #if defined(HAVE_PRI) p->pri ? p->pri->cc_params : p->cc_params, #else p->cc_params, #endif /* defined(HAVE_PRI) */ monitor_type, full_device_name, dialstring, NULL); break; } } p = start.backwards ? p->prev : p->next; if (!p) { p = start.backwards ? ifend : iflist; } if (p == exitpvt) { break; } } ast_mutex_unlock(&iflock); return 0; } #if defined(HAVE_SS7) static void dahdi_ss7_message(struct ss7 *ss7, char *s) { int i; if (ss7) { for (i = 0; i < NUM_SPANS; i++) { if (linksets[i].ss7.ss7 == ss7) { ast_verbose_callid(NULL, "[%d] %s", i + 1, s); return; } } } ast_verbose_callid(NULL, "%s", s); } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static void dahdi_ss7_error(struct ss7 *ss7, char *s) { int i; if (ss7) { for (i = 0; i < NUM_SPANS; i++) { if (linksets[i].ss7.ss7 == ss7) { ast_log_callid(LOG_ERROR, NULL, "[%d] %s", i + 1, s); return; } } } ast_log_callid(LOG_ERROR, NULL, "%s", s); } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_OPENR2) static void *mfcr2_monitor(void *data) { struct dahdi_mfcr2 *mfcr2 = data; /* we should be using pthread_key_create and allocate pollers dynamically. I think do_monitor() could be leaking, since it could be cancelled at any time and is not using thread keys, why?, */ struct pollfd pollers[ARRAY_LEN(mfcr2->pvts)]; int res = 0; int i = 0; int oldstate = 0; int quit_loop = 0; int maxsleep = 20; int was_idle = 0; int pollsize = 0; /* now that we're ready to get calls, unblock our side and get current line state */ for (i = 0; i < mfcr2->numchans; i++) { openr2_chan_set_idle(mfcr2->pvts[i]->r2chan); openr2_chan_handle_cas(mfcr2->pvts[i]->r2chan); } while (1) { /* we trust here that the mfcr2 channel list will not ever change once the module is loaded */ pollsize = 0; for (i = 0; i < mfcr2->numchans; i++) { pollers[i].revents = 0; pollers[i].events = 0; if (mfcr2->pvts[i]->owner) { continue; } if (!mfcr2->pvts[i]->r2chan) { ast_debug(1, "Wow, no r2chan on channel %d\n", mfcr2->pvts[i]->channel); quit_loop = 1; break; } openr2_chan_enable_read(mfcr2->pvts[i]->r2chan); pollers[i].events = POLLIN | POLLPRI; pollers[i].fd = mfcr2->pvts[i]->subs[SUB_REAL].dfd; pollsize++; } if (quit_loop) { break; } if (pollsize == 0) { if (!was_idle) { ast_debug(1, "Monitor thread going idle since everybody has an owner\n"); was_idle = 1; } poll(NULL, 0, maxsleep); continue; } was_idle = 0; /* probably poll() is a valid cancel point, lets just be on the safe side by calling pthread_testcancel */ pthread_testcancel(); res = poll(pollers, mfcr2->numchans, maxsleep); pthread_testcancel(); if ((res < 0) && (errno != EINTR)) { ast_log(LOG_ERROR, "going out, poll failed: %s\n", strerror(errno)); break; } /* do we want to allow to cancel while processing events? */ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); for (i = 0; i < mfcr2->numchans; i++) { if (pollers[i].revents & POLLPRI || pollers[i].revents & POLLIN) { openr2_chan_process_event(mfcr2->pvts[i]->r2chan); } } pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate); } ast_log(LOG_NOTICE, "Quitting MFC/R2 monitor thread\n"); return 0; } #endif /* HAVE_OPENR2 */ #if defined(HAVE_PRI) static void dahdi_pri_message(struct pri *pri, char *s) { int x; int y; int dchan = -1; int span = -1; int dchancount = 0; if (pri) { for (x = 0; x < NUM_SPANS; x++) { for (y = 0; y < SIG_PRI_NUM_DCHANS; y++) { if (pris[x].pri.dchans[y]) { dchancount++; } if (pris[x].pri.dchans[y] == pri) { dchan = y; } } if (dchan >= 0) { span = x; break; } dchancount = 0; } if (-1 < span) { if (1 < dchancount) { ast_verbose_callid(NULL, "[PRI Span: %d D-Channel: %d] %s", span + 1, dchan, s); } else { ast_verbose_callid(NULL, "PRI Span: %d %s", span + 1, s); } } else { ast_verbose_callid(NULL, "PRI Span: ? %s", s); } } else { ast_verbose_callid(NULL, "PRI Span: ? %s", s); } ast_mutex_lock(&pridebugfdlock); if (pridebugfd >= 0) { if (write(pridebugfd, s, strlen(s)) < 0) { ast_log_callid(LOG_WARNING, NULL, "write() failed: %s\n", strerror(errno)); } } ast_mutex_unlock(&pridebugfdlock); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static void dahdi_pri_error(struct pri *pri, char *s) { int x; int y; int dchan = -1; int span = -1; int dchancount = 0; if (pri) { for (x = 0; x < NUM_SPANS; x++) { for (y = 0; y < SIG_PRI_NUM_DCHANS; y++) { if (pris[x].pri.dchans[y]) { dchancount++; } if (pris[x].pri.dchans[y] == pri) { dchan = y; } } if (dchan >= 0) { span = x; break; } dchancount = 0; } if (-1 < span) { if (1 < dchancount) { ast_log_callid(LOG_ERROR, NULL, "[PRI Span: %d D-Channel: %d] %s", span + 1, dchan, s); } else { ast_log_callid(LOG_ERROR, NULL, "PRI Span: %d %s", span + 1, s); } } else { ast_log_callid(LOG_ERROR, NULL, "PRI Span: ? %s", s); } } else { ast_log_callid(LOG_ERROR, NULL, "PRI Span: ? %s", s); } ast_mutex_lock(&pridebugfdlock); if (pridebugfd >= 0) { if (write(pridebugfd, s, strlen(s)) < 0) { ast_log_callid(LOG_WARNING, NULL, "write() failed: %s\n", strerror(errno)); } } ast_mutex_unlock(&pridebugfdlock); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int prepare_pri(struct dahdi_pri *pri) { int i, res, x; struct dahdi_params p; struct dahdi_bufferinfo bi; struct dahdi_spaninfo si; for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { if (!pri->dchannels[i]) break; if (pri->pri.fds[i] >= 0) { /* A partial range addition. Not a complete setup. */ break; } pri->pri.fds[i] = open("/dev/dahdi/channel", O_RDWR); if ((pri->pri.fds[i] < 0)) { ast_log(LOG_ERROR, "Unable to open D-channel (fd=%d) (%s)\n", pri->pri.fds[i], strerror(errno)); return -1; } x = pri->dchannels[i]; res = ioctl(pri->pri.fds[i], DAHDI_SPECIFY, &x); if (res) { dahdi_close_pri_fd(pri, i); ast_log(LOG_ERROR, "Unable to SPECIFY channel %d (%s)\n", x, strerror(errno)); return -1; } memset(&p, 0, sizeof(p)); res = ioctl(pri->pri.fds[i], DAHDI_GET_PARAMS, &p); if (res) { dahdi_close_pri_fd(pri, i); ast_log(LOG_ERROR, "Unable to get parameters for D-channel %d (%s)\n", x, strerror(errno)); return -1; } if ((p.sigtype != DAHDI_SIG_HDLCFCS) && (p.sigtype != DAHDI_SIG_HARDHDLC)) { dahdi_close_pri_fd(pri, i); ast_log(LOG_ERROR, "D-channel %d is not in HDLC/FCS mode.\n", x); return -1; } memset(&si, 0, sizeof(si)); res = ioctl(pri->pri.fds[i], DAHDI_SPANSTAT, &si); if (res) { dahdi_close_pri_fd(pri, i); ast_log(LOG_ERROR, "Unable to get span state for D-channel %d (%s)\n", x, strerror(errno)); } if (!si.alarms) { pri_event_noalarm(&pri->pri, i, 1); } else { pri_event_alarm(&pri->pri, i, 1); } memset(&bi, 0, sizeof(bi)); bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE; bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE; bi.numbufs = 32; bi.bufsize = 1024; if (ioctl(pri->pri.fds[i], DAHDI_SET_BUFINFO, &bi)) { ast_log(LOG_ERROR, "Unable to set appropriate buffering on channel %d: %s\n", x, strerror(errno)); dahdi_close_pri_fd(pri, i); return -1; } pri->pri.dchan_logical_span[i] = pris[p.spanno - 1].prilogicalspan; } return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *complete_span_helper(const char *line, const char *word, int pos, int state, int rpos) { int which, span; char *ret = NULL; if (pos != rpos) return ret; for (which = span = 0; span < NUM_SPANS; span++) { if (pris[span].pri.pri && ++which > state) { if (ast_asprintf(&ret, "%d", span + 1) < 0) { /* user indexes start from 1 */ ret = NULL; } break; } } return ret; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *complete_span_4(const char *line, const char *word, int pos, int state) { return complete_span_helper(line,word,pos,state,3); } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_set_debug_file(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int myfd; switch (cmd) { case CLI_INIT: e->command = "pri set debug file"; e->usage = "Usage: pri set debug file [output-file]\n" " Sends PRI debug output to the specified output file\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 5) return CLI_SHOWUSAGE; if (ast_strlen_zero(a->argv[4])) return CLI_SHOWUSAGE; myfd = open(a->argv[4], O_CREAT|O_WRONLY, AST_FILE_MODE); if (myfd < 0) { ast_cli(a->fd, "Unable to open '%s' for writing\n", a->argv[4]); return CLI_SUCCESS; } ast_mutex_lock(&pridebugfdlock); if (pridebugfd >= 0) close(pridebugfd); pridebugfd = myfd; ast_copy_string(pridebugfilename,a->argv[4],sizeof(pridebugfilename)); ast_mutex_unlock(&pridebugfdlock); ast_cli(a->fd, "PRI debug output will be sent to '%s'\n", a->argv[4]); return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int action_pri_debug_file_set(struct mansession *s, const struct message *m) { const char *output_file = astman_get_header(m, "File"); int myfd; if (ast_strlen_zero(output_file)) { astman_send_error(s, m, "Action must define a 'File'"); } myfd = open(output_file, O_CREAT|O_WRONLY, AST_FILE_MODE); if (myfd < 0) { astman_send_error(s, m, "Unable to open requested file for writing"); return 0; } ast_mutex_lock(&pridebugfdlock); if (pridebugfd >= 0) { close(pridebugfd); } pridebugfd = myfd; ast_copy_string(pridebugfilename, output_file, sizeof(pridebugfilename)); ast_mutex_unlock(&pridebugfdlock); astman_send_ack(s, m, "PRI debug output will now be sent to requested file."); return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int action_pri_debug_file_unset(struct mansession *s, const struct message *m) { ast_mutex_lock(&pridebugfdlock); if (pridebugfd >= 0) { close(pridebugfd); } pridebugfd = -1; ast_mutex_unlock(&pridebugfdlock); astman_send_ack(s, m, "PRI Debug output to file disabled"); return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int span; int x; int debugmask = 0; int level = 0; switch (cmd) { case CLI_INIT: e->command = "pri set debug {on|off|hex|intense|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15} span"; e->usage = "Usage: pri set debug {|on|off|hex|intense} span \n" " Enables debugging on a given PRI span\n" " Level is a bitmap of the following values:\n" " 1 General debugging incl. state changes\n" " 2 Decoded Q.931 messages\n" " 4 Decoded Q.921 messages\n" " 8 Raw hex dumps of Q.921 frames\n" " on - equivalent to 3\n" " hex - equivalent to 8\n" " intense - equivalent to 15\n"; return NULL; case CLI_GENERATE: return complete_span_4(a->line, a->word, a->pos, a->n); } if (a->argc < 6) { return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[3], "on")) { level = 3; } else if (!strcasecmp(a->argv[3], "off")) { level = 0; } else if (!strcasecmp(a->argv[3], "intense")) { level = 15; } else if (!strcasecmp(a->argv[3], "hex")) { level = 8; } else { level = atoi(a->argv[3]); } span = atoi(a->argv[5]); if ((span < 1) || (span > NUM_SPANS)) { ast_cli(a->fd, "Invalid span %s. Should be a number %d to %d\n", a->argv[5], 1, NUM_SPANS); return CLI_SUCCESS; } if (!pris[span-1].pri.pri) { ast_cli(a->fd, "No PRI running on span %d\n", span); return CLI_SUCCESS; } if (level & 1) debugmask |= SIG_PRI_DEBUG_NORMAL; if (level & 2) debugmask |= PRI_DEBUG_Q931_DUMP; if (level & 4) debugmask |= PRI_DEBUG_Q921_DUMP; if (level & 8) debugmask |= PRI_DEBUG_Q921_RAW; /* Set debug level in libpri */ for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if (pris[span - 1].pri.dchans[x]) { pri_set_debug(pris[span - 1].pri.dchans[x], debugmask); } } if (level == 0) { /* Close the debugging file if it's set */ ast_mutex_lock(&pridebugfdlock); if (0 <= pridebugfd) { close(pridebugfd); pridebugfd = -1; ast_cli(a->fd, "Disabled PRI debug output to file '%s'\n", pridebugfilename); } ast_mutex_unlock(&pridebugfdlock); } pris[span - 1].pri.debug = (level) ? 1 : 0; ast_cli(a->fd, "%s debugging on span %d\n", (level) ? "Enabled" : "Disabled", span); return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static int action_pri_debug_set(struct mansession *s, const struct message *m) { const char *level = astman_get_header(m, "Level"); const char *span = astman_get_header(m, "Span"); int level_val; int span_val; int x; int debugmask = 0; if (ast_strlen_zero(level)) { astman_send_error(s, m, "'Level' was not specified"); return 0; } if (ast_strlen_zero(span)) { astman_send_error(s, m, "'Span' was not specified"); return 0; } if (!strcasecmp(level, "on")) { level_val = 3; } else if (!strcasecmp(level, "off")) { level_val = 0; } else if (!strcasecmp(level, "intense")) { level_val = 15; } else if (!strcasecmp(level, "hex")) { level_val = 8; } else { if (sscanf(level, "%30d", &level_val) != 1) { astman_send_error(s, m, "Invalid value for 'Level'"); return 0; } } if (sscanf(span, "%30d", &span_val) != 1) { astman_send_error(s, m, "Invalid value for 'Span'"); } if ((span_val < 1) || (span_val > NUM_SPANS)) { const char *id = astman_get_header(m, "ActionID"); char id_text[256] = ""; if (!ast_strlen_zero(id)) { snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id); } astman_append(s, "Response: Error\r\n" "%s" /* id_text */ "Message: Invalid span '%s' - Should be a number from 1 to %d\r\n" "\r\n", id_text, span, NUM_SPANS); return 0; } if (!pris[span_val-1].pri.pri) { astman_send_error(s, m, "No PRI running on requested span"); return 0; } if (level_val & 1) { debugmask |= SIG_PRI_DEBUG_NORMAL; } if (level_val & 2) { debugmask |= PRI_DEBUG_Q931_DUMP; } if (level_val & 4) { debugmask |= PRI_DEBUG_Q921_DUMP; } if (level_val & 8) { debugmask |= PRI_DEBUG_Q921_RAW; } /* Set debug level in libpri */ for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if (pris[span_val - 1].pri.dchans[x]) { pri_set_debug(pris[span_val - 1].pri.dchans[x], debugmask); } } pris[span_val - 1].pri.debug = (level_val) ? 1 : 0; astman_send_ack(s, m, "Debug level set for requested span"); return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_SERVICE_MESSAGES) static char *handle_pri_service_generic(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a, int changestatus) { unsigned *why; int channel; int trunkgroup; int x, y, fd = a->fd; int interfaceid = 0; char db_chan_name[20], db_answer[5]; struct dahdi_pvt *tmp; struct dahdi_pri *pri; if (a->argc < 5 || a->argc > 6) return CLI_SHOWUSAGE; if (strchr(a->argv[4], ':')) { if (sscanf(a->argv[4], "%30d:%30d", &trunkgroup, &channel) != 2) return CLI_SHOWUSAGE; if ((trunkgroup < 1) || (channel < 1)) return CLI_SHOWUSAGE; pri = NULL; for (x=0;xargv[4]); if (a->argc == 6) interfaceid = atoi(a->argv[5]); /* either servicing a D-Channel */ for (x = 0; x < NUM_SPANS; x++) { for (y = 0; y < SIG_PRI_NUM_DCHANS; y++) { if (pris[x].dchannels[y] == channel) { pri = pris + x; if (pri->pri.enable_service_message_support) { ast_mutex_lock(&pri->pri.lock); pri_maintenance_service(pri->pri.pri, interfaceid, -1, changestatus); ast_mutex_unlock(&pri->pri.lock); } else { ast_cli(fd, "\n\tThis operation has not been enabled in chan_dahdi.conf, set 'service_message_support=yes' to use this operation.\n" "\tNote only 4ESS, 5ESS, and NI2 switch types are supported.\n\n"); } return CLI_SUCCESS; } } } /* or servicing a B-Channel */ ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { if (tmp->pri && tmp->channel == channel) { ast_mutex_unlock(&iflock); ast_mutex_lock(&tmp->pri->lock); if (!tmp->pri->enable_service_message_support) { ast_mutex_unlock(&tmp->pri->lock); ast_cli(fd, "\n\tThis operation has not been enabled in chan_dahdi.conf, set 'service_message_support=yes' to use this operation.\n" "\tNote only 4ESS, 5ESS, and NI2 switch types are supported.\n\n"); return CLI_SUCCESS; } snprintf(db_chan_name, sizeof(db_chan_name), "%s/%d:%d", dahdi_db, tmp->span, channel); why = &((struct sig_pri_chan *) tmp->sig_pvt)->service_status; switch(changestatus) { case 0: /* enable */ /* Near end wants to be in service now. */ ast_db_del(db_chan_name, SRVST_DBKEY); *why &= ~SRVST_NEAREND; if (*why) { snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); } else { dahdi_pri_update_span_devstate(tmp->pri); } break; /* case 1: -- loop */ case 2: /* disable */ /* Near end wants to be out-of-service now. */ ast_db_del(db_chan_name, SRVST_DBKEY); *why |= SRVST_NEAREND; snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); dahdi_pri_update_span_devstate(tmp->pri); break; /* case 3: -- continuity */ /* case 4: -- shutdown */ default: ast_log(LOG_WARNING, "Unsupported changestatus: '%d'\n", changestatus); break; } pri_maintenance_bservice(tmp->pri->pri, tmp->sig_pvt, changestatus); ast_mutex_unlock(&tmp->pri->lock); return CLI_SUCCESS; } } ast_mutex_unlock(&iflock); ast_cli(fd, "Unable to find given channel %d, possibly not a PRI\n", channel); return CLI_FAILURE; } static char *handle_pri_service_enable_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "pri service enable channel"; e->usage = "Usage: pri service enable channel []\n" " Send an AT&T / NFAS / CCS ANSI T1.607 maintenance message\n" " to restore a channel to service, with optional interface id\n" " as agreed upon with remote switch operator\n"; return NULL; case CLI_GENERATE: return NULL; } return handle_pri_service_generic(e, cmd, a, 0); } static char *handle_pri_service_disable_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "pri service disable channel"; e->usage = "Usage: pri service disable channel []\n" " Send an AT&T / NFAS / CCS ANSI T1.607 maintenance message\n" " to remove a channel from service, with optional interface id\n" " as agreed upon with remote switch operator\n"; return NULL; case CLI_GENERATE: return NULL; } return handle_pri_service_generic(e, cmd, a, 2); } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int span; switch (cmd) { case CLI_INIT: e->command = "pri show channels"; e->usage = "Usage: pri show channels\n" " Displays PRI channel information such as the current mapping\n" " of DAHDI B channels to Asterisk channel names and which calls\n" " are on hold or call-waiting. Calls on hold or call-waiting\n" " are not associated with any B channel.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; sig_pri_cli_show_channels_header(a->fd); for (span = 0; span < NUM_SPANS; ++span) { if (pris[span].pri.pri) { sig_pri_cli_show_channels(a->fd, &pris[span].pri); } } return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_show_spans(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int span; switch (cmd) { case CLI_INIT: e->command = "pri show spans"; e->usage = "Usage: pri show spans\n" " Displays PRI span information\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; for (span = 0; span < NUM_SPANS; span++) { if (pris[span].pri.pri) { sig_pri_cli_show_spans(a->fd, span + 1, &pris[span].pri); } } return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) /*! * \internal * \brief Destroy a D-Channel of a PRI span * \since 12 * * \param pri the pri span * * Shuts down a span and destroys its D-Channel. Further destruction * of the B-channels using dahdi_destroy_channel() would probably be required * for the B-Channels. */ static void pri_destroy_span(struct sig_pri_span *pri) { int i; int res; int cancel_code; struct dahdi_pri* dahdi_pri; pthread_t master = pri->master; if (!master || (master == AST_PTHREADT_NULL)) { return; } ast_debug(2, "About to destroy DAHDI channels of span %d.\n", pri->span); for (i = 0; i < pri->numchans; i++) { int channel; struct sig_pri_chan *pvt = pri->pvts[i]; if (!pvt) { continue; } channel = pvt->channel; ast_debug(2, "About to destroy B-channel %d.\n", channel); dahdi_destroy_channel_range(channel, channel); } cancel_code = pthread_cancel(master); pthread_kill(master, SIGURG); ast_debug(4, "Waiting to join thread of span %d " "with pid=%p cancel_code=%d\n", pri->span, (void *)master, cancel_code); res = pthread_join(master, NULL); if (res != 0) { ast_log(LOG_NOTICE, "pthread_join failed: %d\n", res); } pri->master = AST_PTHREADT_NULL; /* The 'struct dahdi_pri' that contains our 'struct sig_pri_span' */ dahdi_pri = container_of(pri, struct dahdi_pri, pri); for (i = 0; i < SIG_PRI_NUM_DCHANS; i++) { ast_debug(4, "closing pri_fd %d\n", i); dahdi_close_pri_fd(dahdi_pri, i); dahdi_pri->dchannels[i] = 0; } sig_pri_init_pri(pri); ast_debug(1, "PRI span %d destroyed\n", pri->span); } static char *handle_pri_destroy_span(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int span; int res; struct sig_pri_span *pri; switch (cmd) { case CLI_INIT: e->command = "pri destroy span"; e->usage = "Usage: pri destroy span \n" " Destorys D-channel of span and its B-channels.\n" " DON'T USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING.\n"; return NULL; case CLI_GENERATE: return complete_span_4(a->line, a->word, a->pos, a->n); } if (a->argc < 4) { return CLI_SHOWUSAGE; } res = sscanf(a->argv[3], "%30d", &span); if ((res != 1) || span < 1 || span > NUM_SPANS) { ast_cli(a->fd, "Invalid span '%s'. Should be a number from %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } pri = &pris[span - 1].pri; if (!pri->pri) { ast_cli(a->fd, "No PRI running on span %d\n", span); return CLI_SUCCESS; } pri_destroy_span(pri); return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_show_span(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int span; switch (cmd) { case CLI_INIT: e->command = "pri show span"; e->usage = "Usage: pri show span \n" " Displays PRI Information on a given PRI span\n"; return NULL; case CLI_GENERATE: return complete_span_4(a->line, a->word, a->pos, a->n); } if (a->argc < 4) return CLI_SHOWUSAGE; span = atoi(a->argv[3]); if ((span < 1) || (span > NUM_SPANS)) { ast_cli(a->fd, "Invalid span '%s'. Should be a number from %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } if (!pris[span-1].pri.pri) { ast_cli(a->fd, "No PRI running on span %d\n", span); return CLI_SUCCESS; } sig_pri_cli_show_span(a->fd, pris[span-1].dchannels, &pris[span-1].pri); return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_show_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int x; int span; int count=0; int debug; switch (cmd) { case CLI_INIT: e->command = "pri show debug"; e->usage = "Usage: pri show debug\n" " Show the debug state of pri spans\n"; return NULL; case CLI_GENERATE: return NULL; } for (span = 0; span < NUM_SPANS; span++) { if (pris[span].pri.pri) { for (x = 0; x < SIG_PRI_NUM_DCHANS; x++) { if (pris[span].pri.dchans[x]) { debug = pri_get_debug(pris[span].pri.dchans[x]); ast_cli(a->fd, "Span %d: Debug: %s\tIntense: %s\n", span+1, (debug&PRI_DEBUG_Q931_STATE)? "Yes" : "No" ,(debug&PRI_DEBUG_Q921_RAW)? "Yes" : "No" ); count++; } } } } ast_mutex_lock(&pridebugfdlock); if (pridebugfd >= 0) ast_cli(a->fd, "Logging PRI debug to file %s\n", pridebugfilename); ast_mutex_unlock(&pridebugfdlock); if (!count) ast_cli(a->fd, "No PRI running\n"); return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static char *handle_pri_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "pri show version"; e->usage = "Usage: pri show version\n" "Show libpri version information\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd, "libpri version: %s\n", pri_get_version()); return CLI_SUCCESS; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) static struct ast_cli_entry dahdi_pri_cli[] = { AST_CLI_DEFINE(handle_pri_debug, "Enables PRI debugging on a span"), #if defined(HAVE_PRI_SERVICE_MESSAGES) AST_CLI_DEFINE(handle_pri_service_enable_channel, "Return a channel to service"), AST_CLI_DEFINE(handle_pri_service_disable_channel, "Remove a channel from service"), #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ AST_CLI_DEFINE(handle_pri_show_channels, "Displays PRI channel information"), AST_CLI_DEFINE(handle_pri_show_spans, "Displays PRI span information"), AST_CLI_DEFINE(handle_pri_show_span, "Displays PRI span information"), AST_CLI_DEFINE(handle_pri_destroy_span, "Destroy a PRI span"), AST_CLI_DEFINE(handle_pri_show_debug, "Displays current PRI debug settings"), AST_CLI_DEFINE(handle_pri_set_debug_file, "Sends PRI debug output to the specified file"), AST_CLI_DEFINE(handle_pri_version, "Displays libpri version"), }; #endif /* defined(HAVE_PRI) */ #ifdef HAVE_OPENR2 static char *handle_mfcr2_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "mfcr2 show version"; e->usage = "Usage: mfcr2 show version\n" " Shows the version of the OpenR2 library being used.\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd, "OpenR2 version: %s, revision: %s\n", openr2_get_version(), openr2_get_revision()); return CLI_SUCCESS; } static char *handle_mfcr2_show_variants(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%4s %40s\n" int i = 0; int numvariants = 0; const openr2_variant_entry_t *variants; switch (cmd) { case CLI_INIT: e->command = "mfcr2 show variants"; e->usage = "Usage: mfcr2 show variants\n" " Shows the list of MFC/R2 variants supported.\n"; return NULL; case CLI_GENERATE: return NULL; } if (!(variants = openr2_proto_get_variant_list(&numvariants))) { ast_cli(a->fd, "Failed to get list of variants.\n"); return CLI_FAILURE; } ast_cli(a->fd, FORMAT, "Variant Code", "Country"); for (i = 0; i < numvariants; i++) { ast_cli(a->fd, FORMAT, variants[i].name, variants[i].country); } return CLI_SUCCESS; #undef FORMAT } static char *handle_mfcr2_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%4s %-7.7s %-7.7s %-8.8s %-9.9s %-16.16s %-8.8s %-8.8s\n" int filtertype = 0; int targetnum = 0; char channo[5]; char anino[5]; char dnisno[5]; struct dahdi_pvt *p; openr2_context_t *r2context; openr2_variant_t r2variant; switch (cmd) { case CLI_INIT: e->command = "mfcr2 show channels [group|context]"; e->usage = "Usage: mfcr2 show channels [group | context ]\n" " Shows the DAHDI channels configured with MFC/R2 signaling.\n"; return NULL; case CLI_GENERATE: return NULL; } if (!((a->argc == 3) || (a->argc == 5))) { return CLI_SHOWUSAGE; } if (a->argc == 5) { if (!strcasecmp(a->argv[3], "group")) { targetnum = atoi(a->argv[4]); if ((targetnum < 0) || (targetnum > 63)) return CLI_SHOWUSAGE; targetnum = 1 << targetnum; filtertype = 1; } else if (!strcasecmp(a->argv[3], "context")) { filtertype = 2; } else { return CLI_SHOWUSAGE; } } ast_cli(a->fd, FORMAT, "Chan", "Variant", "Max ANI", "Max DNIS", "ANI First", "Immediate Accept", "Tx CAS", "Rx CAS"); ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (!(p->sig & SIG_MFCR2) || !p->r2chan) { continue; } if (filtertype) { switch(filtertype) { case 1: /* mfcr2 show channels group */ if (p->group != targetnum) { continue; } break; case 2: /* mfcr2 show channels context */ if (strcasecmp(p->context, a->argv[4])) { continue; } break; default: ; } } r2context = openr2_chan_get_context(p->r2chan); r2variant = openr2_context_get_variant(r2context); snprintf(channo, sizeof(channo), "%d", p->channel); snprintf(anino, sizeof(anino), "%d", openr2_context_get_max_ani(r2context)); snprintf(dnisno, sizeof(dnisno), "%d", openr2_context_get_max_dnis(r2context)); ast_cli(a->fd, FORMAT, channo, openr2_proto_get_variant_string(r2variant), anino, dnisno, openr2_context_get_ani_first(r2context) ? "Yes" : "No", openr2_context_get_immediate_accept(r2context) ? "Yes" : "No", openr2_chan_get_tx_cas_string(p->r2chan), openr2_chan_get_rx_cas_string(p->r2chan)); } ast_mutex_unlock(&iflock); return CLI_SUCCESS; #undef FORMAT } static char *handle_mfcr2_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct dahdi_pvt *p = NULL; int channo = 0; char *toklevel = NULL; char *saveptr = NULL; char *logval = NULL; openr2_log_level_t loglevel = OR2_LOG_NOTHING; openr2_log_level_t tmplevel = OR2_LOG_NOTHING; switch (cmd) { case CLI_INIT: e->command = "mfcr2 set debug"; e->usage = "Usage: mfcr2 set debug \n" " Set a new logging level for the specified channel.\n" " If no channel is specified the logging level will be applied to all channels.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 4) { return CLI_SHOWUSAGE; } channo = (a->argc == 5) ? atoi(a->argv[4]) : -1; logval = ast_strdupa(a->argv[3]); toklevel = strtok_r(logval, ",", &saveptr); if (-1 == (tmplevel = openr2_log_get_level(toklevel))) { ast_cli(a->fd, "Invalid MFC/R2 logging level '%s'.\n", a->argv[3]); return CLI_FAILURE; } else if (OR2_LOG_NOTHING == tmplevel) { loglevel = tmplevel; } else { loglevel |= tmplevel; while ((toklevel = strtok_r(NULL, ",", &saveptr))) { if (-1 == (tmplevel = openr2_log_get_level(toklevel))) { ast_cli(a->fd, "Ignoring invalid logging level: '%s'.\n", toklevel); continue; } loglevel |= tmplevel; } } ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (!(p->sig & SIG_MFCR2) || !p->r2chan) { continue; } if ((channo != -1) && (p->channel != channo )) { continue; } openr2_chan_set_log_level(p->r2chan, loglevel); if (channo != -1) { ast_cli(a->fd, "MFC/R2 debugging set to '%s' for channel %d.\n", a->argv[3], p->channel); break; } } if ((channo != -1) && !p) { ast_cli(a->fd, "MFC/R2 channel %d not found.\n", channo); } if (channo == -1) { ast_cli(a->fd, "MFC/R2 debugging set to '%s' for all channels.\n", a->argv[3]); } ast_mutex_unlock(&iflock); return CLI_SUCCESS; } static char *handle_mfcr2_call_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct dahdi_pvt *p = NULL; int channo = 0; switch (cmd) { case CLI_INIT: e->command = "mfcr2 call files [on|off]"; e->usage = "Usage: mfcr2 call files [on|off] \n" " Enable call files creation on the specified channel.\n" " If no channel is specified call files creation policy will be applied to all channels.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 4) { return CLI_SHOWUSAGE; } channo = (a->argc == 5) ? atoi(a->argv[4]) : -1; ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (!(p->sig & SIG_MFCR2) || !p->r2chan) { continue; } if ((channo != -1) && (p->channel != channo )) { continue; } if (ast_true(a->argv[3])) { openr2_chan_enable_call_files(p->r2chan); } else { openr2_chan_disable_call_files(p->r2chan); } if (channo != -1) { if (ast_true(a->argv[3])) { ast_cli(a->fd, "MFC/R2 call files enabled for channel %d.\n", p->channel); } else { ast_cli(a->fd, "MFC/R2 call files disabled for channel %d.\n", p->channel); } break; } } if ((channo != -1) && !p) { ast_cli(a->fd, "MFC/R2 channel %d not found.\n", channo); } if (channo == -1) { if (ast_true(a->argv[3])) { ast_cli(a->fd, "MFC/R2 Call files enabled for all channels.\n"); } else { ast_cli(a->fd, "MFC/R2 Call files disabled for all channels.\n"); } } ast_mutex_unlock(&iflock); return CLI_SUCCESS; } static char *handle_mfcr2_set_idle(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct dahdi_pvt *p = NULL; int channo = 0; switch (cmd) { case CLI_INIT: e->command = "mfcr2 set idle"; e->usage = "Usage: mfcr2 set idle \n" " DON'T USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING.\n" " Force the given channel into IDLE state.\n" " If no channel is specified, all channels will be set to IDLE.\n"; return NULL; case CLI_GENERATE: return NULL; } channo = (a->argc == 4) ? atoi(a->argv[3]) : -1; ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (!(p->sig & SIG_MFCR2) || !p->r2chan) { continue; } if ((channo != -1) && (p->channel != channo )) { continue; } openr2_chan_set_idle(p->r2chan); ast_mutex_lock(&p->lock); p->locallyblocked = 0; p->mfcr2call = 0; ast_mutex_unlock(&p->lock); if (channo != -1) { break; } } if ((channo != -1) && !p) { ast_cli(a->fd, "MFC/R2 channel %d not found.\n", channo); } ast_mutex_unlock(&iflock); return CLI_SUCCESS; } static char *handle_mfcr2_set_blocked(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct dahdi_pvt *p = NULL; int channo = 0; switch (cmd) { case CLI_INIT: e->command = "mfcr2 set blocked"; e->usage = "Usage: mfcr2 set blocked \n" " DON'T USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING.\n" " Force the given channel into BLOCKED state.\n" " If no channel is specified, all channels will be set to BLOCKED.\n"; return NULL; case CLI_GENERATE: return NULL; } channo = (a->argc == 4) ? atoi(a->argv[3]) : -1; ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (!(p->sig & SIG_MFCR2) || !p->r2chan) { continue; } if ((channo != -1) && (p->channel != channo )) { continue; } openr2_chan_set_blocked(p->r2chan); ast_mutex_lock(&p->lock); p->locallyblocked = 1; ast_mutex_unlock(&p->lock); if (channo != -1) { break; } } if ((channo != -1) && !p) { ast_cli(a->fd, "MFC/R2 channel %d not found.\n", channo); } ast_mutex_unlock(&iflock); return CLI_SUCCESS; } static struct ast_cli_entry dahdi_mfcr2_cli[] = { AST_CLI_DEFINE(handle_mfcr2_version, "Show OpenR2 library version"), AST_CLI_DEFINE(handle_mfcr2_show_variants, "Show supported MFC/R2 variants"), AST_CLI_DEFINE(handle_mfcr2_show_channels, "Show MFC/R2 channels"), AST_CLI_DEFINE(handle_mfcr2_set_debug, "Set MFC/R2 channel logging level"), AST_CLI_DEFINE(handle_mfcr2_call_files, "Enable/Disable MFC/R2 call files"), AST_CLI_DEFINE(handle_mfcr2_set_idle, "Reset MFC/R2 channel forcing it to IDLE"), AST_CLI_DEFINE(handle_mfcr2_set_blocked, "Reset MFC/R2 channel forcing it to BLOCKED"), }; #endif /* HAVE_OPENR2 */ static char *dahdi_destroy_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int start; int end; switch (cmd) { case CLI_INIT: e->command = "dahdi destroy channels"; e->usage = "Usage: dahdi destroy channels []\n" " DON'T USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING. Immediately removes a given channel, whether it is in use or not\n"; return NULL; case CLI_GENERATE: return NULL; } if ((a->argc < 4) || a->argc > 5) { return CLI_SHOWUSAGE; } start = atoi(a->argv[3]); if (start < 1) { ast_cli(a->fd, "Invalid starting channel number %s.\n", a->argv[4]); return CLI_FAILURE; } if (a->argc == 5) { end = atoi(a->argv[4]); if (end < 1) { ast_cli(a->fd, "Invalid ending channel number %s.\n", a->argv[4]); return CLI_FAILURE; } } else { end = start; } if (end < start) { ast_cli(a->fd, "range end (%d) is smaller than range start (%d)\n", end, start); return CLI_FAILURE; } dahdi_destroy_channel_range(start, end); return CLI_SUCCESS; } static char *dahdi_create_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int start; int end; int ret; switch (cmd) { case CLI_INIT: e->command = "dahdi create channels"; e->usage = "Usage: dahdi create channels [] - a range of channels\n" " dahdi create channels new - add channels not yet created\n" "For ISDN and SS7 the range should include complete spans.\n"; return NULL; case CLI_GENERATE: return NULL; } if ((a->argc < 4) || a->argc > 5) { return CLI_SHOWUSAGE; } if (a->argc == 4 && !strcmp(a->argv[3], "new")) { ret = dahdi_create_channel_range(0, 0); return (RESULT_SUCCESS == ret) ? CLI_SUCCESS : CLI_FAILURE; } start = atoi(a->argv[3]); if (start <= 0) { ast_cli(a->fd, "Invalid starting channel number '%s'.\n", a->argv[3]); return CLI_FAILURE; } if (a->argc == 5) { end = atoi(a->argv[4]); if (end <= 0) { ast_cli(a->fd, "Invalid ending channel number '%s'.\n", a->argv[4]); return CLI_FAILURE; } } else { end = start; } if (end < start) { ast_cli(a->fd, "range end (%d) is smaller than range start (%d)\n", end, start); return CLI_FAILURE; } ret = dahdi_create_channel_range(start, end); return (RESULT_SUCCESS == ret) ? CLI_SUCCESS : CLI_FAILURE; } static void dahdi_softhangup_all(void) { struct dahdi_pvt *p; retry: ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { ast_mutex_lock(&p->lock); if (p->owner && !p->restartpending) { if (ast_channel_trylock(p->owner)) { if (option_debug > 2) ast_verbose("Avoiding deadlock\n"); /* Avoid deadlock since you're not supposed to lock iflock or pvt before a channel */ ast_mutex_unlock(&p->lock); ast_mutex_unlock(&iflock); goto retry; } if (option_debug > 2) ast_verbose("Softhanging up on %s\n", ast_channel_name(p->owner)); ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_EXPLICIT); p->restartpending = 1; num_restart_pending++; ast_channel_unlock(p->owner); } ast_mutex_unlock(&p->lock); } ast_mutex_unlock(&iflock); } static int dahdi_restart(void) { #if defined(HAVE_PRI) || defined(HAVE_SS7) int i, j; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ int cancel_code; struct dahdi_pvt *p; ast_mutex_lock(&restart_lock); ast_verb(1, "Destroying channels and reloading DAHDI configuration.\n"); dahdi_softhangup_all(); ast_verb(4, "Initial softhangup of all DAHDI channels complete.\n"); #ifdef HAVE_OPENR2 dahdi_r2_destroy_links(); #endif #if defined(HAVE_PRI) for (i = 0; i < NUM_SPANS; i++) { if (pris[i].pri.master && (pris[i].pri.master != AST_PTHREADT_NULL)) { cancel_code = pthread_cancel(pris[i].pri.master); pthread_kill(pris[i].pri.master, SIGURG); ast_debug(4, "Waiting to join thread of span %d with pid=%p, cancel_code=%d\n", i, (void *) pris[i].pri.master, cancel_code); pthread_join(pris[i].pri.master, NULL); ast_debug(4, "Joined thread of span %d\n", i); } } #endif #if defined(HAVE_SS7) for (i = 0; i < NUM_SPANS; i++) { if (linksets[i].ss7.master && (linksets[i].ss7.master != AST_PTHREADT_NULL)) { cancel_code = pthread_cancel(linksets[i].ss7.master); pthread_kill(linksets[i].ss7.master, SIGURG); ast_debug(4, "Waiting to join thread of span %d with pid=%p, cancel_code=%d\n", i, (void *) linksets[i].ss7.master, cancel_code); pthread_join(linksets[i].ss7.master, NULL); ast_debug(4, "Joined thread of span %d\n", i); } } #endif /* defined(HAVE_SS7) */ ast_mutex_lock(&monlock); if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { cancel_code = pthread_cancel(monitor_thread); pthread_kill(monitor_thread, SIGURG); ast_debug(4, "Waiting to join monitor thread with pid=%p, cancel_code=%d\n", (void *) monitor_thread, cancel_code); pthread_join(monitor_thread, NULL); ast_debug(4, "Joined monitor thread\n"); } monitor_thread = AST_PTHREADT_NULL; /* prepare to restart thread in setup_dahdi once channels are reconfigured */ ast_mutex_lock(&ss_thread_lock); while (ss_thread_count > 0) { /* let ss_threads finish and run dahdi_hangup before dahvi_pvts are destroyed */ int x = DAHDI_FLASH; ast_debug(3, "Waiting on %d analog_ss_thread(s) to finish\n", ss_thread_count); ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (p->owner) { /* important to create an event for dahdi_wait_event to register so that all analog_ss_threads terminate */ ioctl(p->subs[SUB_REAL].dfd, DAHDI_HOOK, &x); } } ast_mutex_unlock(&iflock); ast_cond_wait(&ss_thread_complete, &ss_thread_lock); } /* ensure any created channels before monitor threads were stopped are hungup */ dahdi_softhangup_all(); ast_verb(4, "Final softhangup of all DAHDI channels complete.\n"); destroy_all_channels(); memset(round_robin, 0, sizeof(round_robin)); ast_debug(1, "Channels destroyed. Now re-reading config. %d active channels remaining.\n", ast_active_channels()); ast_mutex_unlock(&monlock); #ifdef HAVE_PRI for (i = 0; i < NUM_SPANS; i++) { for (j = 0; j < SIG_PRI_NUM_DCHANS; j++) dahdi_close_pri_fd(&(pris[i]), j); } memset(pris, 0, sizeof(pris)); for (i = 0; i < NUM_SPANS; i++) { sig_pri_init_pri(&pris[i].pri); } pri_set_error(dahdi_pri_error); pri_set_message(dahdi_pri_message); #endif #if defined(HAVE_SS7) for (i = 0; i < NUM_SPANS; i++) { for (j = 0; j < SIG_SS7_NUM_DCHANS; j++) dahdi_close_ss7_fd(&(linksets[i]), j); } memset(linksets, 0, sizeof(linksets)); for (i = 0; i < NUM_SPANS; i++) { sig_ss7_init_linkset(&linksets[i].ss7); } ss7_set_error(dahdi_ss7_error); ss7_set_message(dahdi_ss7_message); ss7_set_hangup(sig_ss7_cb_hangup); ss7_set_notinservice(sig_ss7_cb_notinservice); ss7_set_call_null(sig_ss7_cb_call_null); #endif /* defined(HAVE_SS7) */ if (setup_dahdi(2) != 0) { ast_log(LOG_WARNING, "Reload channels from dahdi config failed!\n"); ast_mutex_unlock(&ss_thread_lock); return 1; } ast_mutex_unlock(&ss_thread_lock); ast_mutex_unlock(&restart_lock); return 0; } static char *dahdi_restart_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "dahdi restart"; e->usage = "Usage: dahdi restart\n" " Restarts the DAHDI channels: destroys them all and then\n" " re-reads them from chan_dahdi.conf.\n" " Note that this will STOP any running CALL on DAHDI channels.\n" ""; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 2) return CLI_SHOWUSAGE; if (dahdi_restart() != 0) return CLI_FAILURE; return CLI_SUCCESS; } static int action_dahdirestart(struct mansession *s, const struct message *m) { if (dahdi_restart() != 0) { astman_send_error(s, m, "Failed rereading DAHDI configuration"); return 1; } astman_send_ack(s, m, "DAHDIRestart: Success"); return 0; } static char *dahdi_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%7s %-15.15s %-15.15s %-10.10s %-20.20s %-10.10s %-10.10s %-32.32s\n" #define FORMAT2 "%7s %-15.15s %-15.15s %-10.10s %-20.20s %-10.10s %-10.10s %-32.32s\n" ast_group_t targetnum = 0; int filtertype = 0; struct dahdi_pvt *tmp = NULL; char tmps[20]; char blockstr[20]; switch (cmd) { case CLI_INIT: e->command = "dahdi show channels [group|context]"; e->usage = "Usage: dahdi show channels [ group | context ]\n" " Shows a list of available channels with optional filtering\n" " must be a number between 0 and 63\n"; return NULL; case CLI_GENERATE: return NULL; } /* syntax: dahdi show channels [ group | context ] */ if (!((a->argc == 3) || (a->argc == 5))) { return CLI_SHOWUSAGE; } if (a->argc == 5) { if (!strcasecmp(a->argv[3], "group")) { targetnum = atoi(a->argv[4]); if (63 < targetnum) { return CLI_SHOWUSAGE; } targetnum = ((ast_group_t) 1) << targetnum; filtertype = 1; } else if (!strcasecmp(a->argv[3], "context")) { filtertype = 2; } } ast_cli(a->fd, FORMAT2, "Chan", "Extension", "Context", "Language", "MOH Interpret", "Blocked", "In Service", "Description"); ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { if (filtertype) { switch(filtertype) { case 1: /* dahdi show channels group */ if (!(tmp->group & targetnum)) { continue; } break; case 2: /* dahdi show channels context */ if (strcasecmp(tmp->context, a->argv[4])) { continue; } break; default: break; } } if (tmp->channel > 0) { snprintf(tmps, sizeof(tmps), "%d", tmp->channel); } else { ast_copy_string(tmps, "pseudo", sizeof(tmps)); } blockstr[0] = tmp->locallyblocked ? 'L' : ' '; blockstr[1] = tmp->remotelyblocked ? 'R' : ' '; blockstr[2] = '\0'; ast_cli(a->fd, FORMAT, tmps, tmp->exten, tmp->context, tmp->language, tmp->mohinterpret, blockstr, tmp->inservice ? "Yes" : "No", tmp->description); } ast_mutex_unlock(&iflock); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } static char *dahdi_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int channel; struct dahdi_pvt *tmp = NULL; struct dahdi_confinfo ci; struct dahdi_params ps; int x; char hwrxgain[15]; char hwtxgain[15]; switch (cmd) { case CLI_INIT: e->command = "dahdi show channel"; e->usage = "Usage: dahdi show channel \n" " Detailed information about a given channel\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) return CLI_SHOWUSAGE; channel = atoi(a->argv[3]); ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { if (tmp->channel == channel) { ast_cli(a->fd, "Channel: %d\n", tmp->channel); ast_cli(a->fd, "Description: %s\n", tmp->description); ast_cli(a->fd, "File Descriptor: %d\n", tmp->subs[SUB_REAL].dfd); ast_cli(a->fd, "Span: %d\n", tmp->span); ast_cli(a->fd, "Extension: %s\n", tmp->exten); ast_cli(a->fd, "Dialing: %s\n", tmp->dialing ? "yes" : "no"); ast_cli(a->fd, "Context: %s\n", tmp->context); ast_cli(a->fd, "Caller ID: %s\n", tmp->cid_num); ast_cli(a->fd, "Calling TON: %d\n", tmp->cid_ton); #if defined(HAVE_PRI) #if defined(HAVE_PRI_SUBADDR) ast_cli(a->fd, "Caller ID subaddress: %s\n", tmp->cid_subaddr); #endif /* defined(HAVE_PRI_SUBADDR) */ #endif /* defined(HAVE_PRI) */ ast_cli(a->fd, "Caller ID name: %s\n", tmp->cid_name); ast_cli(a->fd, "Mailbox: %s\n", S_OR(tmp->mailbox, "none")); if (tmp->vars) { struct ast_variable *v; ast_cli(a->fd, "Variables:\n"); for (v = tmp->vars ; v ; v = v->next) ast_cli(a->fd, " %s = %s\n", v->name, v->value); } ast_cli(a->fd, "Destroy: %d\n", tmp->destroy); ast_cli(a->fd, "InAlarm: %d\n", tmp->inalarm); ast_cli(a->fd, "Signalling Type: %s\n", sig2str(tmp->sig)); ast_cli(a->fd, "Radio: %d\n", tmp->radio); ast_cli(a->fd, "Owner: %s\n", tmp->owner ? ast_channel_name(tmp->owner) : ""); ast_cli(a->fd, "Real: %s%s%s\n", tmp->subs[SUB_REAL].owner ? ast_channel_name(tmp->subs[SUB_REAL].owner) : "", tmp->subs[SUB_REAL].inthreeway ? " (Confed)" : "", tmp->subs[SUB_REAL].linear ? " (Linear)" : ""); ast_cli(a->fd, "Callwait: %s%s%s\n", tmp->subs[SUB_CALLWAIT].owner ? ast_channel_name(tmp->subs[SUB_CALLWAIT].owner) : "", tmp->subs[SUB_CALLWAIT].inthreeway ? " (Confed)" : "", tmp->subs[SUB_CALLWAIT].linear ? " (Linear)" : ""); ast_cli(a->fd, "Threeway: %s%s%s\n", tmp->subs[SUB_THREEWAY].owner ? ast_channel_name(tmp->subs[SUB_THREEWAY].owner) : "", tmp->subs[SUB_THREEWAY].inthreeway ? " (Confed)" : "", tmp->subs[SUB_THREEWAY].linear ? " (Linear)" : ""); ast_cli(a->fd, "Confno: %d\n", tmp->confno); ast_cli(a->fd, "Propagated Conference: %d\n", tmp->propconfno); ast_cli(a->fd, "Real in conference: %d\n", tmp->inconference); ast_cli(a->fd, "DSP: %s\n", tmp->dsp ? "yes" : "no"); ast_cli(a->fd, "Busy Detection: %s\n", tmp->busydetect ? "yes" : "no"); if (tmp->busydetect) { #if defined(BUSYDETECT_TONEONLY) ast_cli(a->fd, " Busy Detector Helper: BUSYDETECT_TONEONLY\n"); #elif defined(BUSYDETECT_COMPARE_TONE_AND_SILENCE) ast_cli(a->fd, " Busy Detector Helper: BUSYDETECT_COMPARE_TONE_AND_SILENCE\n"); #endif #ifdef BUSYDETECT_DEBUG ast_cli(a->fd, " Busy Detector Debug: Enabled\n"); #endif ast_cli(a->fd, " Busy Count: %d\n", tmp->busycount); ast_cli(a->fd, " Busy Pattern: %d,%d,%d,%d\n", tmp->busy_cadence.pattern[0], tmp->busy_cadence.pattern[1], (tmp->busy_cadence.length == 4) ? tmp->busy_cadence.pattern[2] : 0, (tmp->busy_cadence.length == 4) ? tmp->busy_cadence.pattern[3] : 0); } ast_cli(a->fd, "TDD: %s\n", tmp->tdd ? "yes" : "no"); ast_cli(a->fd, "Relax DTMF: %s\n", tmp->dtmfrelax ? "yes" : "no"); ast_cli(a->fd, "Dialing/CallwaitCAS: %d/%d\n", tmp->dialing, tmp->callwaitcas); ast_cli(a->fd, "Default law: %s\n", tmp->law_default == DAHDI_LAW_MULAW ? "ulaw" : tmp->law_default == DAHDI_LAW_ALAW ? "alaw" : "unknown"); ast_cli(a->fd, "Fax Handled: %s\n", tmp->faxhandled ? "yes" : "no"); ast_cli(a->fd, "Pulse phone: %s\n", tmp->pulsedial ? "yes" : "no"); if (tmp->hwrxgain_enabled) { snprintf(hwrxgain, sizeof(hwrxgain), "%.1f", tmp->hwrxgain); } else { ast_copy_string(hwrxgain, "Disabled", sizeof(hwrxgain)); } if (tmp->hwtxgain_enabled) { snprintf(hwtxgain, sizeof(hwtxgain), "%.1f", tmp->hwtxgain); } else { ast_copy_string(hwtxgain, "Disabled", sizeof(hwtxgain)); } ast_cli(a->fd, "HW Gains (RX/TX): %s/%s\n", hwrxgain, hwtxgain); ast_cli(a->fd, "SW Gains (RX/TX): %.2f/%.2f\n", tmp->rxgain, tmp->txgain); ast_cli(a->fd, "Dynamic Range Compression (RX/TX): %.2f/%.2f\n", tmp->rxdrc, tmp->txdrc); ast_cli(a->fd, "DND: %s\n", dahdi_dnd(tmp, -1) ? "yes" : "no"); ast_cli(a->fd, "Echo Cancellation:\n"); if (tmp->echocancel.head.tap_length) { ast_cli(a->fd, "\t%u taps\n", tmp->echocancel.head.tap_length); for (x = 0; x < tmp->echocancel.head.param_count; x++) { ast_cli(a->fd, "\t\t%s: %dd\n", tmp->echocancel.params[x].name, tmp->echocancel.params[x].value); } ast_cli(a->fd, "\t%scurrently %s\n", tmp->echocanbridged ? "" : "(unless TDM bridged) ", tmp->echocanon ? "ON" : "OFF"); } else { ast_cli(a->fd, "\tnone\n"); } ast_cli(a->fd, "Wait for dialtone: %dms\n", tmp->waitfordialtone); if (tmp->master) ast_cli(a->fd, "Master Channel: %d\n", tmp->master->channel); for (x = 0; x < MAX_SLAVES; x++) { if (tmp->slaves[x]) ast_cli(a->fd, "Slave Channel: %d\n", tmp->slaves[x]->channel); } #ifdef HAVE_OPENR2 if (tmp->mfcr2) { char calldir[OR2_MAX_PATH]; openr2_context_t *r2context = openr2_chan_get_context(tmp->r2chan); openr2_variant_t r2variant = openr2_context_get_variant(r2context); ast_cli(a->fd, "MFC/R2 MF State: %s\n", openr2_chan_get_mf_state_string(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 MF Group: %s\n", openr2_chan_get_mf_group_string(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 State: %s\n", openr2_chan_get_r2_state_string(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 Call State: %s\n", openr2_chan_get_call_state_string(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 Call Files Enabled: %s\n", openr2_chan_get_call_files_enabled(tmp->r2chan) ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 Variant: %s\n", openr2_proto_get_variant_string(r2variant)); ast_cli(a->fd, "MFC/R2 Max ANI: %d\n", openr2_context_get_max_ani(r2context)); ast_cli(a->fd, "MFC/R2 Max DNIS: %d\n", openr2_context_get_max_dnis(r2context)); #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 2 ast_cli(a->fd, "MFC/R2 DTMF Dialing: %s\n", openr2_context_get_dtmf_dialing(r2context, NULL, NULL) ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 DTMF Detection: %s\n", openr2_context_get_dtmf_detection(r2context) ? "Yes" : "No"); #endif ast_cli(a->fd, "MFC/R2 Get ANI First: %s\n", openr2_context_get_ani_first(r2context) ? "Yes" : "No"); #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 1 ast_cli(a->fd, "MFC/R2 Skip Category Request: %s\n", openr2_context_get_skip_category_request(r2context) ? "Yes" : "No"); #endif ast_cli(a->fd, "MFC/R2 Immediate Accept: %s\n", openr2_context_get_immediate_accept(r2context) ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 Accept on Offer: %s\n", tmp->mfcr2_accept_on_offer ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 Charge Calls: %s\n", tmp->mfcr2_charge_calls ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 Allow Collect Calls: %s\n", tmp->mfcr2_allow_collect_calls ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 Forced Release: %s\n", tmp->mfcr2_forced_release ? "Yes" : "No"); ast_cli(a->fd, "MFC/R2 MF Back Timeout: %dms\n", openr2_context_get_mf_back_timeout(r2context)); ast_cli(a->fd, "MFC/R2 R2 Metering Pulse Timeout: %dms\n", openr2_context_get_metering_pulse_timeout(r2context)); ast_cli(a->fd, "MFC/R2 Rx CAS: %s\n", openr2_chan_get_rx_cas_string(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 Tx CAS: %s\n", openr2_chan_get_tx_cas_string(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 MF Tx Signal: %d\n", openr2_chan_get_tx_mf_signal(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 MF Rx Signal: %d\n", openr2_chan_get_rx_mf_signal(tmp->r2chan)); ast_cli(a->fd, "MFC/R2 Call Files Directory: %s\n", openr2_context_get_log_directory(r2context, calldir, sizeof(calldir))); } #endif #if defined(HAVE_SS7) if (tmp->ss7) { struct sig_ss7_chan *chan = tmp->sig_pvt; ast_cli(a->fd, "CIC: %d\n", chan->cic); } #endif /* defined(HAVE_SS7) */ #ifdef HAVE_PRI if (tmp->pri) { struct sig_pri_chan *chan = tmp->sig_pvt; ast_cli(a->fd, "PRI Flags: "); if (chan->resetting != SIG_PRI_RESET_IDLE) { ast_cli(a->fd, "Resetting=%u ", chan->resetting); } if (chan->call) ast_cli(a->fd, "Call "); if (chan->allocated) { ast_cli(a->fd, "Allocated "); } ast_cli(a->fd, "\n"); if (tmp->logicalspan) ast_cli(a->fd, "PRI Logical Span: %d\n", tmp->logicalspan); else ast_cli(a->fd, "PRI Logical Span: Implicit\n"); } #endif memset(&ci, 0, sizeof(ci)); ps.channo = tmp->channel; if (tmp->subs[SUB_REAL].dfd > -1) { memset(&ci, 0, sizeof(ci)); if (!ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GETCONF, &ci)) { ast_cli(a->fd, "Actual Confinfo: Num/%d, Mode/0x%04x\n", ci.confno, (unsigned)ci.confmode); } if (!ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GETCONFMUTE, &x)) { ast_cli(a->fd, "Actual Confmute: %s\n", x ? "Yes" : "No"); } memset(&ps, 0, sizeof(ps)); if (ioctl(tmp->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &ps) < 0) { ast_log(LOG_WARNING, "Failed to get parameters on channel %d: %s\n", tmp->channel, strerror(errno)); } else { ast_cli(a->fd, "Hookstate (FXS only): %s\n", ps.rxisoffhook ? "Offhook" : "Onhook"); } } ast_mutex_unlock(&iflock); return CLI_SUCCESS; } } ast_mutex_unlock(&iflock); ast_cli(a->fd, "Unable to find given channel %d\n", channel); return CLI_FAILURE; } static char *handle_dahdi_show_cadences(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int i, j; switch (cmd) { case CLI_INIT: e->command = "dahdi show cadences"; e->usage = "Usage: dahdi show cadences\n" " Shows all cadences currently defined\n"; return NULL; case CLI_GENERATE: return NULL; } for (i = 0; i < num_cadence; i++) { char output[1024]; char tmp[16], tmp2[64]; snprintf(tmp, sizeof(tmp), "r%d: ", i + 1); term_color(output, tmp, COLOR_GREEN, COLOR_BLACK, sizeof(output)); for (j = 0; j < 16; j++) { if (cadences[i].ringcadence[j] == 0) break; snprintf(tmp, sizeof(tmp), "%d", cadences[i].ringcadence[j]); if (cidrings[i] * 2 - 1 == j) term_color(tmp2, tmp, COLOR_MAGENTA, COLOR_BLACK, sizeof(tmp2) - 1); else term_color(tmp2, tmp, COLOR_GREEN, COLOR_BLACK, sizeof(tmp2) - 1); if (j != 0) strncat(output, ",", sizeof(output) - strlen(output) - 1); strncat(output, tmp2, sizeof(output) - strlen(output) - 1); } ast_cli(a->fd,"%s\n",output); } return CLI_SUCCESS; } /* Based on irqmiss.c */ static char *dahdi_show_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%-40.40s %-7.7s %-6d %-6d %-6d %-3.3s %-4.4s %-8.8s %s\n" #define FORMAT2 "%-40.40s %-7.7s %-6.6s %-6.6s %-6.6s %-3.3s %-4.4s %-8.8s %s\n" int span; int res; char alarmstr[50]; int ctl; struct dahdi_spaninfo s; switch (cmd) { case CLI_INIT: e->command = "dahdi show status"; e->usage = "Usage: dahdi show status\n" " Shows a list of DAHDI cards with status\n"; return NULL; case CLI_GENERATE: return NULL; } ctl = open("/dev/dahdi/ctl", O_RDWR); if (ctl < 0) { ast_cli(a->fd, "No DAHDI found. Unable to open /dev/dahdi/ctl: %s\n", strerror(errno)); return CLI_FAILURE; } ast_cli(a->fd, FORMAT2, "Description", "Alarms", "IRQ", "bpviol", "CRC", "Framing", "Coding", "Options", "LBO"); for (span = 1; span < DAHDI_MAX_SPANS; ++span) { s.spanno = span; res = ioctl(ctl, DAHDI_SPANSTAT, &s); if (res) { continue; } alarmstr[0] = '\0'; if (s.alarms > 0) { if (s.alarms & DAHDI_ALARM_BLUE) strcat(alarmstr, "BLU/"); if (s.alarms & DAHDI_ALARM_YELLOW) strcat(alarmstr, "YEL/"); if (s.alarms & DAHDI_ALARM_RED) strcat(alarmstr, "RED/"); if (s.alarms & DAHDI_ALARM_LOOPBACK) strcat(alarmstr, "LB/"); if (s.alarms & DAHDI_ALARM_RECOVER) strcat(alarmstr, "REC/"); if (s.alarms & DAHDI_ALARM_NOTOPEN) strcat(alarmstr, "NOP/"); if (!strlen(alarmstr)) strcat(alarmstr, "UUU/"); if (strlen(alarmstr)) { /* Strip trailing / */ alarmstr[strlen(alarmstr) - 1] = '\0'; } } else { if (s.numchans) strcpy(alarmstr, "OK"); else strcpy(alarmstr, "UNCONFIGURED"); } ast_cli(a->fd, FORMAT, s.desc, alarmstr, s.irqmisses, s.bpvcount, s.crc4count, s.lineconfig & DAHDI_CONFIG_D4 ? "D4" : s.lineconfig & DAHDI_CONFIG_ESF ? "ESF" : s.lineconfig & DAHDI_CONFIG_CCS ? "CCS" : "CAS", s.lineconfig & DAHDI_CONFIG_B8ZS ? "B8ZS" : s.lineconfig & DAHDI_CONFIG_HDB3 ? "HDB3" : s.lineconfig & DAHDI_CONFIG_AMI ? "AMI" : "Unk", s.lineconfig & DAHDI_CONFIG_CRC4 ? s.lineconfig & DAHDI_CONFIG_NOTOPEN ? "CRC4/YEL" : "CRC4" : s.lineconfig & DAHDI_CONFIG_NOTOPEN ? "YEL" : "", lbostr[s.lbo] ); } close(ctl); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } static char *dahdi_show_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int pseudo_fd = -1; struct dahdi_versioninfo vi; switch (cmd) { case CLI_INIT: e->command = "dahdi show version"; e->usage = "Usage: dahdi show version\n" " Shows the DAHDI version in use\n"; return NULL; case CLI_GENERATE: return NULL; } if ((pseudo_fd = open("/dev/dahdi/ctl", O_RDONLY)) < 0) { ast_cli(a->fd, "Failed to open control file to get version.\n"); return CLI_SUCCESS; } strcpy(vi.version, "Unknown"); strcpy(vi.echo_canceller, "Unknown"); if (ioctl(pseudo_fd, DAHDI_GETVERSION, &vi)) ast_cli(a->fd, "Failed to get DAHDI version: %s\n", strerror(errno)); else ast_cli(a->fd, "DAHDI Version: %s Echo Canceller: %s\n", vi.version, vi.echo_canceller); close(pseudo_fd); return CLI_SUCCESS; } static char *dahdi_set_hwgain(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int channel; float gain; int tx; struct dahdi_pvt *tmp = NULL; switch (cmd) { case CLI_INIT: e->command = "dahdi set hwgain {rx|tx}"; e->usage = "Usage: dahdi set hwgain \n" " Sets the hardware gain on a given channel and overrides the\n" " value provided at module loadtime. Changes take effect\n" " immediately whether the channel is in use or not.\n" "\n" " which direction do you want to change (relative to our module)\n" " is the channel number relative to the device\n" " is the gain in dB (e.g. -3.5 for -3.5dB)\n" "\n" " Please note:\n" " * hwgain is only supportable by hardware with analog ports because\n" " hwgain works on the analog side of an analog-digital conversion.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 6) return CLI_SHOWUSAGE; if (!strcasecmp("rx", a->argv[3])) tx = 0; /* rx */ else if (!strcasecmp("tx", a->argv[3])) tx = 1; /* tx */ else return CLI_SHOWUSAGE; channel = atoi(a->argv[4]); gain = atof(a->argv[5]); ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { if (tmp->channel != channel) continue; if (tmp->subs[SUB_REAL].dfd == -1) break; if (set_hwgain(tmp->subs[SUB_REAL].dfd, gain, tx)) { ast_cli(a->fd, "Unable to set the hardware gain for channel %d: %s\n", channel, strerror(errno)); ast_mutex_unlock(&iflock); return CLI_FAILURE; } ast_cli(a->fd, "Hardware %s gain set to %.1f dB on channel %d.\n", tx ? "tx" : "rx", gain, channel); if (tx) { tmp->hwtxgain_enabled = 1; tmp->hwtxgain = gain; } else { tmp->hwrxgain_enabled = 1; tmp->hwrxgain = gain; } break; } ast_mutex_unlock(&iflock); if (tmp) return CLI_SUCCESS; ast_cli(a->fd, "Unable to find given channel %d\n", channel); return CLI_FAILURE; } static char *dahdi_set_swgain(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int channel; float gain; int tx; int res; struct dahdi_pvt *tmp = NULL; switch (cmd) { case CLI_INIT: e->command = "dahdi set swgain {rx|tx}"; e->usage = "Usage: dahdi set swgain \n" " Sets the software gain on a given channel and overrides the\n" " value provided at module loadtime. Changes take effect\n" " immediately whether the channel is in use or not.\n" "\n" " which direction do you want to change (relative to our module)\n" " is the channel number relative to the device\n" " is the gain in dB (e.g. -3.5 for -3.5dB)\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 6) return CLI_SHOWUSAGE; if (!strcasecmp("rx", a->argv[3])) tx = 0; /* rx */ else if (!strcasecmp("tx", a->argv[3])) tx = 1; /* tx */ else return CLI_SHOWUSAGE; channel = atoi(a->argv[4]); gain = atof(a->argv[5]); ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { if (tmp->channel != channel) continue; if (tmp->subs[SUB_REAL].dfd == -1) break; if (tx) res = set_actual_txgain(tmp->subs[SUB_REAL].dfd, gain, tmp->txdrc, tmp->law); else res = set_actual_rxgain(tmp->subs[SUB_REAL].dfd, gain, tmp->rxdrc, tmp->law); if (res) { ast_cli(a->fd, "Unable to set the software gain for channel %d\n", channel); ast_mutex_unlock(&iflock); return CLI_FAILURE; } ast_cli(a->fd, "Software %s gain set to %.2f dB on channel %d.\n", tx ? "tx" : "rx", gain, channel); if (tx) { tmp->txgain = gain; } else { tmp->rxgain = gain; } break; } ast_mutex_unlock(&iflock); if (tmp) return CLI_SUCCESS; ast_cli(a->fd, "Unable to find given channel %d\n", channel); return CLI_FAILURE; } static char *dahdi_set_dnd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int channel; int on; struct dahdi_pvt *dahdi_chan = NULL; switch (cmd) { case CLI_INIT: e->command = "dahdi set dnd"; e->usage = "Usage: dahdi set dnd \n" " Sets/resets DND (Do Not Disturb) mode on a channel.\n" " Changes take effect immediately.\n" " is the channel number\n" " Enable or disable DND mode?\n" ; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 5) return CLI_SHOWUSAGE; if ((channel = atoi(a->argv[3])) <= 0) { ast_cli(a->fd, "Expected channel number, got '%s'\n", a->argv[3]); return CLI_SHOWUSAGE; } if (ast_true(a->argv[4])) on = 1; else if (ast_false(a->argv[4])) on = 0; else { ast_cli(a->fd, "Expected 'on' or 'off', got '%s'\n", a->argv[4]); return CLI_SHOWUSAGE; } ast_mutex_lock(&iflock); for (dahdi_chan = iflist; dahdi_chan; dahdi_chan = dahdi_chan->next) { if (dahdi_chan->channel != channel) continue; /* Found the channel. Actually set it */ dahdi_dnd(dahdi_chan, on); break; } ast_mutex_unlock(&iflock); if (!dahdi_chan) { ast_cli(a->fd, "Unable to find given channel %d\n", channel); return CLI_FAILURE; } return CLI_SUCCESS; } static struct ast_cli_entry dahdi_cli[] = { AST_CLI_DEFINE(handle_dahdi_show_cadences, "List cadences"), AST_CLI_DEFINE(dahdi_show_channels, "Show active DAHDI channels"), AST_CLI_DEFINE(dahdi_show_channel, "Show information on a channel"), AST_CLI_DEFINE(dahdi_destroy_channels, "Destroy channels"), AST_CLI_DEFINE(dahdi_create_channels, "Create channels"), AST_CLI_DEFINE(dahdi_restart_cmd, "Fully restart DAHDI channels"), AST_CLI_DEFINE(dahdi_show_status, "Show all DAHDI cards status"), AST_CLI_DEFINE(dahdi_show_version, "Show the DAHDI version in use"), AST_CLI_DEFINE(dahdi_set_hwgain, "Set hardware gain on a channel"), AST_CLI_DEFINE(dahdi_set_swgain, "Set software gain on a channel"), AST_CLI_DEFINE(dahdi_set_dnd, "Sets/resets DND (Do Not Disturb) mode on a channel"), }; #define TRANSFER 0 #define HANGUP 1 static int dahdi_fake_event(struct dahdi_pvt *p, int mode) { if (p) { switch (mode) { case TRANSFER: p->fake_event = DAHDI_EVENT_WINKFLASH; break; case HANGUP: p->fake_event = DAHDI_EVENT_ONHOOK; break; default: ast_log(LOG_WARNING, "I don't know how to handle transfer event with this: %d on channel %s\n",mode, ast_channel_name(p->owner)); } } return 0; } static struct dahdi_pvt *find_channel(int channel) { struct dahdi_pvt *p; ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (p->channel == channel) { break; } } ast_mutex_unlock(&iflock); return p; } /*! * \internal * \brief Get private struct using given numeric channel string. * * \param channel Numeric channel number string get private struct. * * \retval pvt on success. * \retval NULL on error. */ static struct dahdi_pvt *find_channel_from_str(const char *channel) { int chan_num; if (sscanf(channel, "%30d", &chan_num) != 1) { /* Not numeric string. */ return NULL; } return find_channel(chan_num); } static int action_dahdidndon(struct mansession *s, const struct message *m) { struct dahdi_pvt *p; const char *channel = astman_get_header(m, "DAHDIChannel"); if (ast_strlen_zero(channel)) { astman_send_error(s, m, "No channel specified"); return 0; } p = find_channel_from_str(channel); if (!p) { astman_send_error(s, m, "No such channel"); return 0; } dahdi_dnd(p, 1); astman_send_ack(s, m, "DND Enabled"); return 0; } static int action_dahdidndoff(struct mansession *s, const struct message *m) { struct dahdi_pvt *p; const char *channel = astman_get_header(m, "DAHDIChannel"); if (ast_strlen_zero(channel)) { astman_send_error(s, m, "No channel specified"); return 0; } p = find_channel_from_str(channel); if (!p) { astman_send_error(s, m, "No such channel"); return 0; } dahdi_dnd(p, 0); astman_send_ack(s, m, "DND Disabled"); return 0; } static int action_transfer(struct mansession *s, const struct message *m) { struct dahdi_pvt *p; const char *channel = astman_get_header(m, "DAHDIChannel"); if (ast_strlen_zero(channel)) { astman_send_error(s, m, "No channel specified"); return 0; } p = find_channel_from_str(channel); if (!p) { astman_send_error(s, m, "No such channel"); return 0; } if (!dahdi_analog_lib_handles(p->sig, 0, 0)) { astman_send_error(s, m, "Channel signaling is not analog"); return 0; } dahdi_fake_event(p,TRANSFER); astman_send_ack(s, m, "DAHDITransfer"); return 0; } static int action_transferhangup(struct mansession *s, const struct message *m) { struct dahdi_pvt *p; const char *channel = astman_get_header(m, "DAHDIChannel"); if (ast_strlen_zero(channel)) { astman_send_error(s, m, "No channel specified"); return 0; } p = find_channel_from_str(channel); if (!p) { astman_send_error(s, m, "No such channel"); return 0; } if (!dahdi_analog_lib_handles(p->sig, 0, 0)) { astman_send_error(s, m, "Channel signaling is not analog"); return 0; } dahdi_fake_event(p,HANGUP); astman_send_ack(s, m, "DAHDIHangup"); return 0; } static int action_dahdidialoffhook(struct mansession *s, const struct message *m) { struct dahdi_pvt *p; const char *channel = astman_get_header(m, "DAHDIChannel"); const char *number = astman_get_header(m, "Number"); int i; if (ast_strlen_zero(channel)) { astman_send_error(s, m, "No channel specified"); return 0; } if (ast_strlen_zero(number)) { astman_send_error(s, m, "No number specified"); return 0; } p = find_channel_from_str(channel); if (!p) { astman_send_error(s, m, "No such channel"); return 0; } if (!p->owner) { astman_send_error(s, m, "Channel does not have it's owner"); return 0; } for (i = 0; i < strlen(number); i++) { struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = number[i] }; dahdi_queue_frame(p, &f); } astman_send_ack(s, m, "DAHDIDialOffhook"); return 0; } static int action_dahdishowchannels(struct mansession *s, const struct message *m) { struct dahdi_pvt *tmp = NULL; const char *id = astman_get_header(m, "ActionID"); const char *dahdichannel = astman_get_header(m, "DAHDIChannel"); char idText[256] = ""; int channels = 0; int dahdichanquery; if (!dahdichannel || sscanf(dahdichannel, "%30d", &dahdichanquery) != 1) { /* Not numeric string. */ dahdichanquery = -1; } astman_send_ack(s, m, "DAHDI channel status will follow"); if (!ast_strlen_zero(id)) snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { if (tmp->channel > 0) { int alm; /* If a specific channel is queried for, only deliver status for that channel */ if (dahdichanquery > 0 && tmp->channel != dahdichanquery) continue; alm = get_alarms(tmp); channels++; if (tmp->owner) { /* Add data if we have a current call */ astman_append(s, "Event: DAHDIShowChannels\r\n" "DAHDIChannel: %d\r\n" "Channel: %s\r\n" "Uniqueid: %s\r\n" "AccountCode: %s\r\n" "Signalling: %s\r\n" "SignallingCode: %d\r\n" "Context: %s\r\n" "DND: %s\r\n" "Alarm: %s\r\n" "Description: %s\r\n" "%s" "\r\n", tmp->channel, ast_channel_name(tmp->owner), ast_channel_uniqueid(tmp->owner), ast_channel_accountcode(tmp->owner), sig2str(tmp->sig), tmp->sig, tmp->context, dahdi_dnd(tmp, -1) ? "Enabled" : "Disabled", alarm2str(alm), tmp->description, idText); } else { astman_append(s, "Event: DAHDIShowChannels\r\n" "DAHDIChannel: %d\r\n" "Signalling: %s\r\n" "SignallingCode: %d\r\n" "Context: %s\r\n" "DND: %s\r\n" "Alarm: %s\r\n" "Description: %s\r\n" "%s" "\r\n", tmp->channel, sig2str(tmp->sig), tmp->sig, tmp->context, dahdi_dnd(tmp, -1) ? "Enabled" : "Disabled", alarm2str(alm), tmp->description, idText); } } } ast_mutex_unlock(&iflock); astman_append(s, "Event: DAHDIShowChannelsComplete\r\n" "%s" "Items: %d\r\n" "\r\n", idText, channels); return 0; } #if defined(HAVE_PRI) static int action_prishowspans(struct mansession *s, const struct message *m) { int count; int idx; int span_query; struct dahdi_pri *dspan; const char *id = astman_get_header(m, "ActionID"); const char *span_str = astman_get_header(m, "Span"); char action_id[256]; const char *show_cmd = "PRIShowSpans"; /* NOTE: Asking for span 0 gets all spans. */ if (!ast_strlen_zero(span_str)) { span_query = atoi(span_str); } else { span_query = 0; } if (!ast_strlen_zero(id)) { snprintf(action_id, sizeof(action_id), "ActionID: %s\r\n", id); } else { action_id[0] = '\0'; } astman_send_ack(s, m, "Span status will follow"); count = 0; for (idx = 0; idx < ARRAY_LEN(pris); ++idx) { dspan = &pris[idx]; /* If a specific span is asked for, only deliver status for that span. */ if (0 < span_query && dspan->pri.span != span_query) { continue; } if (dspan->pri.pri) { count += sig_pri_ami_show_spans(s, show_cmd, &dspan->pri, dspan->dchannels, action_id); } } astman_append(s, "Event: %sComplete\r\n" "Items: %d\r\n" "%s" "\r\n", show_cmd, count, action_id); return 0; } #endif /* defined(HAVE_PRI) */ #if defined(HAVE_SS7) static int linkset_addsigchan(int sigchan) { struct dahdi_ss7 *link; int res; int curfd; struct dahdi_params params; struct dahdi_bufferinfo bi; struct dahdi_spaninfo si; if (sigchan < 0) { ast_log(LOG_ERROR, "Invalid sigchan!\n"); return -1; } if (cur_ss7type < 0) { ast_log(LOG_ERROR, "Unspecified or invalid ss7type\n"); return -1; } if (cur_pointcode < 0) { ast_log(LOG_ERROR, "Unspecified pointcode!\n"); return -1; } if (cur_adjpointcode < 0) { ast_log(LOG_ERROR, "Unspecified adjpointcode!\n"); return -1; } if (cur_defaultdpc < 0) { ast_log(LOG_ERROR, "Unspecified defaultdpc!\n"); return -1; } if (cur_networkindicator < 0) { ast_log(LOG_ERROR, "Invalid networkindicator!\n"); return -1; } link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (link->ss7.numsigchans >= SIG_SS7_NUM_DCHANS) { ast_log(LOG_ERROR, "Too many sigchans on linkset %d\n", cur_linkset); return -1; } curfd = link->ss7.numsigchans; /* Open signaling channel */ link->ss7.fds[curfd] = open("/dev/dahdi/channel", O_RDWR, 0600); if (link->ss7.fds[curfd] < 0) { ast_log(LOG_ERROR, "Unable to open SS7 sigchan %d (%s)\n", sigchan, strerror(errno)); return -1; } if (ioctl(link->ss7.fds[curfd], DAHDI_SPECIFY, &sigchan) == -1) { dahdi_close_ss7_fd(link, curfd); ast_log(LOG_ERROR, "Unable to specify SS7 sigchan %d (%s)\n", sigchan, strerror(errno)); return -1; } /* Get signaling channel parameters */ memset(¶ms, 0, sizeof(params)); res = ioctl(link->ss7.fds[curfd], DAHDI_GET_PARAMS, ¶ms); if (res) { dahdi_close_ss7_fd(link, curfd); ast_log(LOG_ERROR, "Unable to get parameters for sigchan %d (%s)\n", sigchan, strerror(errno)); return -1; } if (params.sigtype != DAHDI_SIG_HDLCFCS && params.sigtype != DAHDI_SIG_HARDHDLC && params.sigtype != DAHDI_SIG_MTP2) { dahdi_close_ss7_fd(link, curfd); ast_log(LOG_ERROR, "sigchan %d is not in HDLC/FCS mode.\n", sigchan); return -1; } /* Set signaling channel buffer policy. */ memset(&bi, 0, sizeof(bi)); bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE; bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE; bi.numbufs = 32; bi.bufsize = 512; if (ioctl(link->ss7.fds[curfd], DAHDI_SET_BUFINFO, &bi)) { ast_log(LOG_ERROR, "Unable to set appropriate buffering on channel %d: %s\n", sigchan, strerror(errno)); dahdi_close_ss7_fd(link, curfd); return -1; } /* Get current signaling channel alarm status. */ memset(&si, 0, sizeof(si)); res = ioctl(link->ss7.fds[curfd], DAHDI_SPANSTAT, &si); if (res) { dahdi_close_ss7_fd(link, curfd); ast_log(LOG_ERROR, "Unable to get span state for sigchan %d (%s)\n", sigchan, strerror(errno)); } res = sig_ss7_add_sigchan(&link->ss7, curfd, cur_ss7type, (params.sigtype == DAHDI_SIG_MTP2) ? SS7_TRANSPORT_DAHDIMTP2 : SS7_TRANSPORT_DAHDIDCHAN, si.alarms, cur_networkindicator, cur_pointcode, cur_adjpointcode, cur_slc); if (res) { dahdi_close_ss7_fd(link, curfd); return -1; } ++link->ss7.numsigchans; return 0; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int span; switch (cmd) { case CLI_INIT: e->command = "ss7 set debug {on|off} linkset"; e->usage = "Usage: ss7 set debug {on|off} linkset \n" " Enables debugging on a given SS7 linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 6) { return CLI_SHOWUSAGE; } span = atoi(a->argv[5]); if ((span < 1) || (span > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number from %d to %d\n", a->argv[5], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[span-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", span); } else { if (!strcasecmp(a->argv[3], "on")) { linksets[span - 1].ss7.debug = 1; ss7_set_debug(linksets[span-1].ss7.ss7, SIG_SS7_DEBUG); ast_cli(a->fd, "Enabled debugging on linkset %d\n", span); } else { linksets[span - 1].ss7.debug = 0; ss7_set_debug(linksets[span-1].ss7.ss7, 0); ast_cli(a->fd, "Disabled debugging on linkset %d\n", span); } } return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_cic_blocking(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset, cic; int blocked, i; int do_block = 0; unsigned int dpc; switch (cmd) { case CLI_INIT: e->command = "ss7 {block|unblock} cic"; e->usage = "Usage: ss7 {block|unblock} cic \n" " Sends a remote {blocking|unblocking} request for the given CIC on the specified linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 6) { linkset = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[1], "block")) { do_block = 1; } else if (strcasecmp(a->argv[1], "unblock")) { return CLI_SHOWUSAGE; } if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } cic = atoi(a->argv[5]); if (cic < 1) { ast_cli(a->fd, "Invalid CIC specified!\n"); return CLI_SUCCESS; } dpc = atoi(a->argv[4]); if (dpc < 1) { ast_cli(a->fd, "Invalid DPC specified!\n"); return CLI_SUCCESS; } for (i = 0; i < linksets[linkset-1].ss7.numchans; i++) { if (linksets[linkset-1].ss7.pvts[i] && linksets[linkset-1].ss7.pvts[i]->cic == cic && linksets[linkset-1].ss7.pvts[i]->dpc == dpc) { blocked = linksets[linkset-1].ss7.pvts[i]->locallyblocked; if (!do_block ^ !(blocked & SS7_BLOCKED_MAINTENANCE)) { if (sig_ss7_cic_blocking(&linksets[linkset-1].ss7, do_block, i) < 0) { ast_cli(a->fd, "Unable to allocate new ss7call\n"); } else { ast_cli(a->fd, "Sent %sblocking request for linkset %d on CIC %d DPC %d\n", (do_block) ? "" : "un", linkset, cic, dpc); } } else if (!do_block && blocked) { ast_cli(a->fd, "CIC %d is hardware locally blocked!\n", cic); } else { ast_cli(a->fd, "CIC %d %s locally blocked\n", cic, do_block ? "already" : "is not"); } return CLI_SUCCESS; } } ast_cli(a->fd, "Invalid CIC specified!\n"); return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_linkset_mng(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset, i; enum { DO_BLOCK, DO_UNBLOCK, DO_RESET, } do_what; switch (cmd) { case CLI_INIT: e->command = "ss7 {reset|block|unblock} linkset"; e->usage = "Usage: ss7 {reset|block|unblock} linkset \n" " Sends a remote {reset|blocking|unblocking} request for all CICs on the given linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 4) { linkset = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[1], "block")) { do_what = DO_BLOCK; } else if (!strcasecmp(a->argv[1], "unblock")) { do_what = DO_UNBLOCK; } else if (!strcasecmp(a->argv[1], "reset")) { do_what = DO_RESET; } else { return CLI_SHOWUSAGE; } if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset - 1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } for (i = 0; i < linksets[linkset - 1].ss7.numchans; i++) { /* XXX Should be done with GRS/CGB/CGU instead - see ss7_reset_linkset() */ if (linksets[linkset - 1].ss7.pvts[i]) { switch (do_what) { case DO_BLOCK: case DO_UNBLOCK: if (sig_ss7_cic_blocking(&linksets[linkset - 1].ss7, do_what == DO_BLOCK, i)) { ast_cli(a->fd, "Sent remote %s request on CIC %d\n", (do_what == DO_BLOCK) ? "blocking" : "unblocking", linksets[linkset - 1].ss7.pvts[i]->cic); } break; case DO_RESET: if (sig_ss7_reset_cic(&linksets[linkset - 1].ss7, linksets[linkset - 1].ss7.pvts[i]->cic, linksets[linkset - 1].ss7.pvts[i]->dpc)) { ast_cli(a->fd, "Sent reset request on CIC %d\n", linksets[linkset - 1].ss7.pvts[i]->cic); } break; } } } return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_group_blocking(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset, cic, range, chanpos; int i, dpc, orient = 0; int do_block = 0; unsigned char state[255]; switch (cmd) { case CLI_INIT: e->command = "ss7 {block|unblock} group"; e->usage = "Usage: ss7 {block|unblock} group <1st. CIC> [H]\n" " Sends a remote {blocking|unblocking} request for CIC range on the specified linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 7 || a->argc == 8) { linkset = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[1], "block")) { do_block = 1; } else if (strcasecmp(a->argv[1], "unblock")) { return CLI_SHOWUSAGE; } if (a->argc == 8) { if (!strcasecmp(a->argv[7], "H")) { orient = 1; } else { return CLI_SHOWUSAGE; } } if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[4], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } cic = atoi(a->argv[5]); if (cic < 1) { ast_cli(a->fd, "Invalid CIC specified!\n"); return CLI_SUCCESS; } range = atoi(a->argv[6]); /* ITU-T Q.763 3.43 - range 0 is reserved, which makes a range of 2 CICs a minimum group */ if (range < 1 || range > (linksets[linkset - 1].ss7.type == SS7_ANSI ? 24 : 31)) { ast_cli(a->fd, "Invalid range specified!\n"); return CLI_SUCCESS; } dpc = atoi(a->argv[4]); if (dpc < 1) { ast_cli(a->fd, "Invalid DPC specified!\n"); return CLI_SUCCESS; } ast_mutex_lock(&linksets[linkset-1].ss7.lock); if (!sig_ss7_find_cic_range(&linksets[linkset-1].ss7, cic, cic + range, dpc)) { ast_mutex_unlock(&linksets[linkset-1].ss7.lock); ast_cli(a->fd, "Invalid CIC/RANGE\n"); return CLI_SHOWUSAGE; } memset(state, 0, sizeof(state)); for (i = 0; i <= range; ++i) { state[i] = 1; } /* We are guaranteed to find chanpos because of sig_ss7_find_cic_range() includes it. */ chanpos = sig_ss7_find_cic(&linksets[linkset-1].ss7, cic, dpc); if (sig_ss7_group_blocking(&linksets[linkset-1].ss7, do_block, chanpos, cic + range, state, orient)) { ast_cli(a->fd, "Unable allocate new ss7call\n"); } else { ast_cli(a->fd, "Sending remote%s %sblocking request linkset %d on CIC %d range %d\n", orient ? " hardware" : "", do_block ? "" : "un", linkset, cic, range); } ast_mutex_unlock(&linksets[linkset-1].ss7.lock); /* Break poll on the linkset so it sends our messages */ if (linksets[linkset-1].ss7.master != AST_PTHREADT_NULL) { pthread_kill(linksets[linkset-1].ss7.master, SIGURG); } return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_group_reset(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset, cic, range; unsigned int dpc; switch (cmd) { case CLI_INIT: e->command = "ss7 reset group"; e->usage = "Usage: ss7 reset group <1st CIC> \n" " Send a GRS for the given CIC range on the specified linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 7) { linkset = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[4], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } cic = atoi(a->argv[5]); if (cic < 1) { ast_cli(a->fd, "Invalid CIC specified!\n"); return CLI_SUCCESS; } range = atoi(a->argv[6]); if (range < 1 || range > (linksets[linkset - 1].ss7.type == SS7_ANSI ? 24 : 31)) { ast_cli(a->fd, "Invalid range specified!\n"); return CLI_SUCCESS; } dpc = atoi(a->argv[4]); if (dpc < 1) { ast_cli(a->fd, "Invalid DPC specified!\n"); return CLI_SUCCESS; } ast_mutex_lock(&linksets[linkset-1].ss7.lock); if (!sig_ss7_find_cic_range(&linksets[linkset-1].ss7, cic, cic + range, dpc)) { ast_mutex_unlock(&linksets[linkset-1].ss7.lock); ast_cli(a->fd, "Invalid CIC/RANGE\n"); return CLI_SHOWUSAGE; } if (sig_ss7_reset_group(&linksets[linkset-1].ss7, cic, dpc, range)) { ast_cli(a->fd, "Unable to allocate new ss7call\n"); } else { ast_cli(a->fd, "GRS sent ... \n"); } ast_mutex_unlock(&linksets[linkset-1].ss7.lock); /* Break poll on the linkset so it sends our messages */ if (linksets[linkset-1].ss7.master != AST_PTHREADT_NULL) { pthread_kill(linksets[linkset-1].ss7.master, SIGURG); } return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_show_calls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset; switch (cmd) { case CLI_INIT: e->command = "ss7 show calls"; e->usage = "Usage: ss7 show calls \n" " Show SS7 calls on the specified linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 4) { linkset = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } ast_mutex_lock(&linksets[linkset-1].ss7.lock); isup_show_calls(linksets[linkset-1].ss7.ss7, &ast_cli, a->fd); ast_mutex_unlock(&linksets[linkset-1].ss7.lock); return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_reset_cic(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset, cic, res; unsigned int dpc; switch (cmd) { case CLI_INIT: e->command = "ss7 reset cic"; e->usage = "Usage: ss7 reset cic \n" " Send a RSC for the given CIC on the specified linkset\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 6) { linkset = atoi(a->argv[3]); } else { return CLI_SHOWUSAGE; } if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } cic = atoi(a->argv[5]); if (cic < 1) { ast_cli(a->fd, "Invalid CIC specified!\n"); return CLI_SUCCESS; } dpc = atoi(a->argv[4]); if (dpc < 1) { ast_cli(a->fd, "Invalid DPC specified!\n"); return CLI_SUCCESS; } res = sig_ss7_reset_cic(&linksets[linkset-1].ss7, cic, dpc); ast_cli(a->fd, "%s RSC for linkset %d on CIC %d DPC %d\n", res ? "Sent" : "Failed", linkset, cic, dpc); return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_net_mng(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset; unsigned int slc; unsigned int arg = 0; const char *res; switch (cmd) { case CLI_INIT: e->command = "ss7 mtp3"; e->usage = "Usage: ss7 mtp3 coo|coa|cbd|cba|eco|eca|tfp|tfa|lin|lun|lia|lua|lid|lfu \n" " Send a NET MNG message\n" " WARNING!!! WARNING!!! We are not a STP, just for testing/development purposes\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 5) { return CLI_SHOWUSAGE; } linkset = atoi(a->argv[2]); if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[2], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } slc = atoi(a->argv[3]); if (a->argc == 6) { arg = atoi(a->argv[5]); } ast_mutex_lock(&linksets[linkset-1].ss7.lock); res = mtp3_net_mng(linksets[linkset-1].ss7.ss7, slc, a->argv[4], arg); ast_mutex_unlock(&linksets[linkset-1].ss7.lock); /* Break poll on the linkset so it sends our messages */ if (linksets[linkset-1].ss7.master != AST_PTHREADT_NULL) { pthread_kill(linksets[linkset-1].ss7.master, SIGURG); } ast_cli(a->fd, "%s", res); return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_mtp3_restart(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset; unsigned int slc = 0; switch (cmd) { case CLI_INIT: e->command = "ss7 restart mtp3"; e->usage = "Usage: ss7 restart mtp3 \n" " Restart link\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 5) { return CLI_SHOWUSAGE; } linkset = atoi(a->argv[3]); if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[2], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } slc = atoi(a->argv[4]); ast_mutex_lock(&linksets[linkset-1].ss7.lock); mtp3_init_restart(linksets[linkset-1].ss7.ss7, slc); ast_mutex_unlock(&linksets[linkset-1].ss7.lock); /* Break poll on the linkset so it sends our messages */ if (linksets[linkset-1].ss7.master != AST_PTHREADT_NULL) { pthread_kill(linksets[linkset-1].ss7.master, SIGURG); } return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_show_linkset(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset; struct sig_ss7_linkset *ss7; switch (cmd) { case CLI_INIT: e->command = "ss7 show linkset"; e->usage = "Usage: ss7 show linkset \n" " Shows the status of an SS7 linkset.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 4) { return CLI_SHOWUSAGE; } linkset = atoi(a->argv[3]); if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } ss7 = &linksets[linkset - 1].ss7; if (!ss7->ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } ast_cli(a->fd, "SS7 flags: 0x%x\n", ss7->flags); ast_cli(a->fd, "SS7 linkset %d status: %s\n", linkset, (ss7->state == LINKSET_STATE_UP) ? "Up" : "Down"); ast_cli(a->fd, "SS7 calling nai: %i\n", ss7->calling_nai); ast_cli(a->fd, "SS7 called nai: %i\n", ss7->called_nai); ast_cli(a->fd, "SS7 nationalprefix: %s\n", ss7->nationalprefix); ast_cli(a->fd, "SS7 internationalprefix: %s\n", ss7->internationalprefix); ast_cli(a->fd, "SS7 unknownprefix: %s\n", ss7->unknownprefix); ast_cli(a->fd, "SS7 networkroutedprefix: %s\n", ss7->networkroutedprefix); ast_cli(a->fd, "SS7 subscriberprefix: %s\n", ss7->subscriberprefix); ss7_show_linkset(ss7->ss7, &ast_cli, a->fd); return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int linkset; switch (cmd) { case CLI_INIT: e->command = "ss7 show channels"; e->usage = "Usage: ss7 show channels\n" " Displays SS7 channel information at a glance.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) { return CLI_SHOWUSAGE; } sig_ss7_cli_show_channels_header(a->fd); for (linkset = 0; linkset < NUM_SPANS; ++linkset) { if (linksets[linkset].ss7.ss7) { sig_ss7_cli_show_channels(a->fd, &linksets[linkset].ss7); } } return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_show_cics(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT "%5s %5s %6s %12s %-12s\n" #define FORMAT2 "%5i %5i %6i %12s %-12s\n" int i, linkset, dpc = 0; struct sig_ss7_linkset *ss7; char *state; char blocking[12]; switch (cmd) { case CLI_INIT: e->command = "ss7 show cics"; e->usage = "Usage: ss7 show cics [dpc]\n" " Shows the cics of an SS7 linkset.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 4 || a->argc > 5) { return CLI_SHOWUSAGE; } linkset = atoi(a->argv[3]); if ((linkset < 1) || (linkset > NUM_SPANS)) { ast_cli(a->fd, "Invalid linkset %s. Should be a number %d to %d\n", a->argv[3], 1, NUM_SPANS); return CLI_SUCCESS; } if (!linksets[linkset-1].ss7.ss7) { ast_cli(a->fd, "No SS7 running on linkset %d\n", linkset); return CLI_SUCCESS; } ss7 = &linksets[linkset-1].ss7; if (a->argc == 5) { dpc = atoi(a->argv[4]); if (dpc < 1) { ast_cli(a->fd, "Invalid DPC specified!\n"); return CLI_SUCCESS; } } ast_cli(a->fd, FORMAT, "CIC", "DPC", "DAHDI", "STATE", "BLOCKING"); for (i = 0; i < ss7->numchans; i++) { if (!dpc || (ss7->pvts[i] && ss7->pvts[i]->dpc == dpc)) { struct dahdi_pvt *p = ss7->pvts[i]->chan_pvt; if (ss7->pvts[i]->owner) { state = "Used"; } else if (ss7->pvts[i]->ss7call) { state = "Pending"; } else if (!p->inservice) { state = "NotInServ"; } else { state = "Idle"; } if (p->locallyblocked) { strcpy(blocking, "L:"); if (p->locallyblocked & SS7_BLOCKED_MAINTENANCE) { strcat(blocking, "M"); } else { strcat(blocking, " "); } if (p->locallyblocked & SS7_BLOCKED_HARDWARE) { strcat(blocking, "H"); } else { strcat(blocking, " "); } } else { strcpy(blocking, " "); } if (p->remotelyblocked) { strcat(blocking, " R:"); if (p->remotelyblocked & SS7_BLOCKED_MAINTENANCE) { strcat(blocking, "M"); } else { strcat(blocking, " "); } if (p->remotelyblocked & SS7_BLOCKED_HARDWARE) { strcat(blocking, "H"); } else { strcat(blocking, " "); } } ast_cli(a->fd, FORMAT2, ss7->pvts[i]->cic, ss7->pvts[i]->dpc, ss7->pvts[i]->channel, state, blocking); } } return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static char *handle_ss7_version(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "ss7 show version"; e->usage = "Usage: ss7 show version\n" " Show the libss7 version\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd, "libss7 version: %s\n", ss7_get_version()); return CLI_SUCCESS; } #endif /* defined(HAVE_SS7) */ #if defined(HAVE_SS7) static struct ast_cli_entry dahdi_ss7_cli[] = { AST_CLI_DEFINE(handle_ss7_debug, "Enables SS7 debugging on a linkset"), AST_CLI_DEFINE(handle_ss7_cic_blocking, "Blocks/Unblocks the given CIC"), AST_CLI_DEFINE(handle_ss7_linkset_mng, "Resets/Blocks/Unblocks all CICs on a linkset"), AST_CLI_DEFINE(handle_ss7_group_blocking, "Blocks/Unblocks the given CIC range"), AST_CLI_DEFINE(handle_ss7_reset_cic, "Resets the given CIC"), AST_CLI_DEFINE(handle_ss7_group_reset, "Resets the given CIC range"), AST_CLI_DEFINE(handle_ss7_mtp3_restart, "Restart a link"), AST_CLI_DEFINE(handle_ss7_net_mng, "Send an NET MNG message"), AST_CLI_DEFINE(handle_ss7_show_linkset, "Shows the status of a linkset"), AST_CLI_DEFINE(handle_ss7_show_channels, "Displays SS7 channel information"), AST_CLI_DEFINE(handle_ss7_show_calls, "Show ss7 calls"), AST_CLI_DEFINE(handle_ss7_show_cics, "Show cics on a linkset"), AST_CLI_DEFINE(handle_ss7_version, "Displays libss7 version"), }; #endif /* defined(HAVE_SS7) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief CC agent initialization. * \since 1.8 * * \param agent CC core agent control. * \param chan Original channel the agent will attempt to recall. * * \details * This callback is called when the CC core is initialized. Agents should allocate * any private data necessary for the call and assign it to the private_data * on the agent. Additionally, if any ast_cc_agent_flags are pertinent to the * specific agent type, they should be set in this function as well. * * \retval 0 on success. * \retval -1 on error. */ static int dahdi_pri_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan) { struct dahdi_pvt *pvt; struct sig_pri_chan *pvt_chan; int res; ast_assert(!strcmp(ast_channel_tech(chan)->type, "DAHDI")); pvt = ast_channel_tech_pvt(chan); if (dahdi_sig_pri_lib_handles(pvt->sig)) { pvt_chan = pvt->sig_pvt; } else { pvt_chan = NULL; } if (!pvt_chan) { return -1; } ast_module_ref(ast_module_info->self); res = sig_pri_cc_agent_init(agent, pvt_chan); if (res) { ast_module_unref(ast_module_info->self); } return res; } #endif /* defined(HAVE_PRI_CCSS) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_CCSS) /*! * \internal * \brief Destroy private data on the agent. * \since 1.8 * * \param agent CC core agent control. * * \details * The core will call this function upon completion * or failure of CC. * * \return Nothing */ static void dahdi_pri_cc_agent_destructor(struct ast_cc_agent *agent) { sig_pri_cc_agent_destructor(agent); ast_module_unref(ast_module_info->self); } #endif /* defined(HAVE_PRI_CCSS) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_CCSS) static struct ast_cc_agent_callbacks dahdi_pri_cc_agent_callbacks = { .type = dahdi_pri_cc_type, .init = dahdi_pri_cc_agent_init, .start_offer_timer = sig_pri_cc_agent_start_offer_timer, .stop_offer_timer = sig_pri_cc_agent_stop_offer_timer, .respond = sig_pri_cc_agent_req_rsp, .status_request = sig_pri_cc_agent_status_req, .stop_ringing = sig_pri_cc_agent_stop_ringing, .party_b_free = sig_pri_cc_agent_party_b_free, .start_monitoring = sig_pri_cc_agent_start_monitoring, .callee_available = sig_pri_cc_agent_callee_available, .destructor = dahdi_pri_cc_agent_destructor, }; #endif /* defined(HAVE_PRI_CCSS) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_CCSS) static struct ast_cc_monitor_callbacks dahdi_pri_cc_monitor_callbacks = { .type = dahdi_pri_cc_type, .request_cc = sig_pri_cc_monitor_req_cc, .suspend = sig_pri_cc_monitor_suspend, .unsuspend = sig_pri_cc_monitor_unsuspend, .status_response = sig_pri_cc_monitor_status_rsp, .cancel_available_timer = sig_pri_cc_monitor_cancel_available_timer, .destructor = sig_pri_cc_monitor_destructor, }; #endif /* defined(HAVE_PRI_CCSS) */ #endif /* defined(HAVE_PRI) */ static int __unload_module(void) { struct dahdi_pvt *p; #if defined(HAVE_PRI) || defined(HAVE_SS7) int i, j; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #ifdef HAVE_PRI for (i = 0; i < NUM_SPANS; i++) { if (pris[i].pri.master != AST_PTHREADT_NULL) { pthread_cancel(pris[i].pri.master); pthread_kill(pris[i].pri.master, SIGURG); } } ast_cli_unregister_multiple(dahdi_pri_cli, ARRAY_LEN(dahdi_pri_cli)); ast_unregister_application(dahdi_send_keypad_facility_app); #ifdef HAVE_PRI_PROG_W_CAUSE ast_unregister_application(dahdi_send_callrerouting_facility_app); #endif #endif #if defined(HAVE_SS7) for (i = 0; i < NUM_SPANS; i++) { if (linksets[i].ss7.master != AST_PTHREADT_NULL) { pthread_cancel(linksets[i].ss7.master); pthread_kill(linksets[i].ss7.master, SIGURG); } } ast_cli_unregister_multiple(dahdi_ss7_cli, ARRAY_LEN(dahdi_ss7_cli)); #endif /* defined(HAVE_SS7) */ #if defined(HAVE_OPENR2) dahdi_r2_destroy_links(); ast_cli_unregister_multiple(dahdi_mfcr2_cli, ARRAY_LEN(dahdi_mfcr2_cli)); ast_unregister_application(dahdi_accept_r2_call_app); #endif ast_cli_unregister_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli)); ast_manager_unregister("DAHDIDialOffhook"); ast_manager_unregister("DAHDIHangup"); ast_manager_unregister("DAHDITransfer"); ast_manager_unregister("DAHDIDNDoff"); ast_manager_unregister("DAHDIDNDon"); ast_manager_unregister("DAHDIShowChannels"); ast_manager_unregister("DAHDIRestart"); #if defined(HAVE_PRI) ast_manager_unregister("PRIShowSpans"); ast_manager_unregister("PRIDebugSet"); ast_manager_unregister("PRIDebugFileSet"); ast_manager_unregister("PRIDebugFileUnset"); #endif /* defined(HAVE_PRI) */ ast_data_unregister(NULL); ast_channel_unregister(&dahdi_tech); /* Hangup all interfaces if they have an owner */ ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (p->owner) ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD); } ast_mutex_unlock(&iflock); ast_mutex_lock(&monlock); if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { pthread_cancel(monitor_thread); pthread_kill(monitor_thread, SIGURG); pthread_join(monitor_thread, NULL); } monitor_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&monlock); destroy_all_channels(); #if defined(HAVE_PRI) for (i = 0; i < NUM_SPANS; i++) { if (pris[i].pri.master && (pris[i].pri.master != AST_PTHREADT_NULL)) { pthread_join(pris[i].pri.master, NULL); } for (j = 0; j < SIG_PRI_NUM_DCHANS; j++) { dahdi_close_pri_fd(&(pris[i]), j); } sig_pri_stop_pri(&pris[i].pri); } #if defined(HAVE_PRI_CCSS) ast_cc_agent_unregister(&dahdi_pri_cc_agent_callbacks); ast_cc_monitor_unregister(&dahdi_pri_cc_monitor_callbacks); #endif /* defined(HAVE_PRI_CCSS) */ sig_pri_unload(); #endif #if defined(HAVE_SS7) for (i = 0; i < NUM_SPANS; i++) { if (linksets[i].ss7.master && (linksets[i].ss7.master != AST_PTHREADT_NULL)) { pthread_join(linksets[i].ss7.master, NULL); } for (j = 0; j < SIG_SS7_NUM_DCHANS; j++) { dahdi_close_ss7_fd(&(linksets[i]), j); } if (linksets[i].ss7.ss7) { ss7_destroy(linksets[i].ss7.ss7); linksets[i].ss7.ss7 = NULL; } } #endif /* defined(HAVE_SS7) */ ast_cond_destroy(&ss_thread_complete); dahdi_native_unload(); ao2_cleanup(dahdi_tech.capabilities); dahdi_tech.capabilities = NULL; STASIS_MESSAGE_TYPE_CLEANUP(dahdichannel_type); return 0; } static int unload_module(void) { #if defined(HAVE_PRI) || defined(HAVE_SS7) int y; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ #ifdef HAVE_PRI for (y = 0; y < NUM_SPANS; y++) ast_mutex_destroy(&pris[y].pri.lock); #endif #if defined(HAVE_SS7) for (y = 0; y < NUM_SPANS; y++) ast_mutex_destroy(&linksets[y].ss7.lock); #endif /* defined(HAVE_SS7) */ return __unload_module(); } static void string_replace(char *str, int char1, int char2) { for (; *str; str++) { if (*str == char1) { *str = char2; } } } static char *parse_spanchan(char *chanstr, char **subdir) { char *p; if ((p = strrchr(chanstr, '!')) == NULL) { *subdir = NULL; return chanstr; } *p++ = '\0'; string_replace(chanstr, '!', '/'); *subdir = chanstr; return p; } static int build_channels(struct dahdi_chan_conf *conf, const char *value, int reload, int lineno) { char *c, *chan; char *subdir; int x, start, finish; struct dahdi_pvt *tmp; if ((reload == 0) && (conf->chan.sig < 0) && !conf->is_sig_auto) { ast_log(LOG_ERROR, "Signalling must be specified before any channels are.\n"); return -1; } c = ast_strdupa(value); c = parse_spanchan(c, &subdir); while ((chan = strsep(&c, ","))) { if (sscanf(chan, "%30d-%30d", &start, &finish) == 2) { /* Range */ } else if (sscanf(chan, "%30d", &start)) { /* Just one */ finish = start; } else if (!strcasecmp(chan, "pseudo")) { finish = start = CHAN_PSEUDO; } else { ast_log(LOG_ERROR, "Syntax error parsing '%s' at '%s'\n", value, chan); return -1; } if (finish < start) { ast_log(LOG_WARNING, "Sillyness: %d < %d\n", start, finish); x = finish; finish = start; start = x; } for (x = start; x <= finish; x++) { char fn[PATH_MAX]; int real_channel = x; if (!ast_strlen_zero(subdir)) { real_channel = device2chan(subdir, x, fn, sizeof(fn)); if (real_channel < 0) { if (conf->ignore_failed_channels) { ast_log(LOG_WARNING, "Failed configuring %s!%d, (got %d). But moving on to others.\n", subdir, x, real_channel); continue; } else { ast_log(LOG_ERROR, "Failed configuring %s!%d, (got %d).\n", subdir, x, real_channel); return -1; } } } if (conf->wanted_channels_start && (real_channel < conf->wanted_channels_start || real_channel > conf->wanted_channels_end) ) { continue; } tmp = mkintf(real_channel, conf, reload); if (tmp) { ast_verb(3, "%s channel %d, %s signalling\n", reload ? "Reconfigured" : "Registered", real_channel, sig2str(tmp->sig)); } else { ast_log(LOG_ERROR, "Unable to %s channel '%s'\n", (reload == 1) ? "reconfigure" : "register", value); return -1; } if (real_channel == CHAN_PSEUDO) { has_pseudo = 1; } } } return 0; } /** The length of the parameters list of 'dahdichan'. * \todo Move definition of MAX_CHANLIST_LEN to a proper place. */ #define MAX_CHANLIST_LEN 80 static void process_echocancel(struct dahdi_chan_conf *confp, const char *data, unsigned int line) { char *parse = ast_strdupa(data); char *params[DAHDI_MAX_ECHOCANPARAMS + 1]; unsigned int param_count; unsigned int x; if (!(param_count = ast_app_separate_args(parse, ',', params, ARRAY_LEN(params)))) return; memset(&confp->chan.echocancel, 0, sizeof(confp->chan.echocancel)); /* first parameter is tap length, process it here */ x = ast_strlen_zero(params[0]) ? 0 : atoi(params[0]); if ((x == 32) || (x == 64) || (x == 128) || (x == 256) || (x == 512) || (x == 1024)) confp->chan.echocancel.head.tap_length = x; else if ((confp->chan.echocancel.head.tap_length = ast_true(params[0]))) confp->chan.echocancel.head.tap_length = 128; /* now process any remaining parameters */ for (x = 1; x < param_count; x++) { struct { char *name; char *value; } param; if (ast_app_separate_args(params[x], '=', (char **) ¶m, 2) < 1) { ast_log(LOG_WARNING, "Invalid echocancel parameter supplied at line %u: '%s'\n", line, params[x]); continue; } if (ast_strlen_zero(param.name) || (strlen(param.name) > sizeof(confp->chan.echocancel.params[0].name)-1)) { ast_log(LOG_WARNING, "Invalid echocancel parameter supplied at line %u: '%s'\n", line, param.name); continue; } strcpy(confp->chan.echocancel.params[confp->chan.echocancel.head.param_count].name, param.name); if (param.value) { if (sscanf(param.value, "%30d", &confp->chan.echocancel.params[confp->chan.echocancel.head.param_count].value) != 1) { ast_log(LOG_WARNING, "Invalid echocancel parameter value supplied at line %u: '%s'\n", line, param.value); continue; } } confp->chan.echocancel.head.param_count++; } } #if defined(HAVE_PRI) #if defined(HAVE_PRI_DISPLAY_TEXT) /*! * \internal * \brief Determine the configured display text options. * \since 10.0 * * \param value Configuration value string. * * \return Configured display text option flags. */ static unsigned long dahdi_display_text_option(const char *value) { char *val_str; char *opt_str; unsigned long options; options = 0; val_str = ast_strdupa(value); for (;;) { opt_str = strsep(&val_str, ","); if (!opt_str) { break; } opt_str = ast_strip(opt_str); if (!*opt_str) { continue; } if (!strcasecmp(opt_str, "block")) { options |= PRI_DISPLAY_OPTION_BLOCK; } else if (!strcasecmp(opt_str, "name_initial")) { options |= PRI_DISPLAY_OPTION_NAME_INITIAL; } else if (!strcasecmp(opt_str, "name_update")) { options |= PRI_DISPLAY_OPTION_NAME_UPDATE; } else if (!strcasecmp(opt_str, "name")) { options |= (PRI_DISPLAY_OPTION_NAME_INITIAL | PRI_DISPLAY_OPTION_NAME_UPDATE); } else if (!strcasecmp(opt_str, "text")) { options |= PRI_DISPLAY_OPTION_TEXT; } } return options; } #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #endif /* defined(HAVE_PRI) */ #if defined(HAVE_PRI) #if defined(HAVE_PRI_DATETIME_SEND) /*! * \internal * \brief Determine the configured date/time send policy option. * \since 10.0 * * \param value Configuration value string. * * \return Configured date/time send policy option. */ static int dahdi_datetime_send_option(const char *value) { int option; option = PRI_DATE_TIME_SEND_DEFAULT; if (ast_false(value)) { option = PRI_DATE_TIME_SEND_NO; } else if (!strcasecmp(value, "date")) { option = PRI_DATE_TIME_SEND_DATE; } else if (!strcasecmp(value, "date_hh")) { option = PRI_DATE_TIME_SEND_DATE_HH; } else if (!strcasecmp(value, "date_hhmm")) { option = PRI_DATE_TIME_SEND_DATE_HHMM; } else if (!strcasecmp(value, "date_hhmmss")) { option = PRI_DATE_TIME_SEND_DATE_HHMMSS; } return option; } #endif /* defined(HAVE_PRI_DATETIME_SEND) */ #endif /* defined(HAVE_PRI) */ /*! process_dahdi() - ignore keyword 'channel' and similar */ #define PROC_DAHDI_OPT_NOCHAN (1 << 0) /*! process_dahdi() - No warnings on non-existing cofiguration keywords */ #define PROC_DAHDI_OPT_NOWARN (1 << 1) static void parse_busy_pattern(struct ast_variable *v, struct ast_dsp_busy_pattern *busy_cadence) { int count_pattern = 0; int norval = 0; char *temp = NULL; for (; ;) { /* Scans the string for the next value in the pattern. If none, it checks to see if any have been entered so far. */ if (!sscanf(v->value, "%30d", &norval) && count_pattern == 0) { ast_log(LOG_ERROR, "busypattern= expects either busypattern=tonelength,quietlength or busypattern=t1length, q1length, t2length, q2length at line %d.\n", v->lineno); break; } busy_cadence->pattern[count_pattern] = norval; count_pattern++; if (count_pattern == 4) { break; } temp = strchr(v->value, ','); if (temp == NULL) { break; } v->value = temp + 1; } busy_cadence->length = count_pattern; if (count_pattern % 2 != 0) { /* The pattern length must be divisible by two */ ast_log(LOG_ERROR, "busypattern= expects either busypattern=tonelength,quietlength or busypattern=t1length, q1length, t2length, q2length at line %d.\n", v->lineno); } } static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct ast_variable *v, int reload, int options) { struct dahdi_pvt *tmp; int y; struct ast_variable *dahdichan = NULL; for (; v; v = v->next) { if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) continue; /* Create the interface list */ if (!strcasecmp(v->name, "channel") || !strcasecmp(v->name, "channels")) { if (options & PROC_DAHDI_OPT_NOCHAN) { ast_log(LOG_WARNING, "Channel '%s' ignored.\n", v->value); continue; } if (build_channels(confp, v->value, reload, v->lineno)) { if (confp->ignore_failed_channels) { ast_log(LOG_WARNING, "Channel '%s' failure ignored: ignore_failed_channels.\n", v->value); continue; } else { return -1; } } ast_debug(1, "Channel '%s' configured.\n", v->value); } else if (!strcasecmp(v->name, "ignore_failed_channels")) { confp->ignore_failed_channels = ast_true(v->value); } else if (!strcasecmp(v->name, "buffers")) { if (parse_buffers_policy(v->value, &confp->chan.buf_no, &confp->chan.buf_policy)) { ast_log(LOG_WARNING, "Using default buffer policy.\n"); confp->chan.buf_no = numbufs; confp->chan.buf_policy = DAHDI_POLICY_IMMEDIATE; } } else if (!strcasecmp(v->name, "faxbuffers")) { if (!parse_buffers_policy(v->value, &confp->chan.faxbuf_no, &confp->chan.faxbuf_policy)) { confp->chan.usefaxbuffers = 1; } } else if (!strcasecmp(v->name, "dahdichan")) { /* Only process the last dahdichan value. */ dahdichan = v; } else if (!strcasecmp(v->name, "usedistinctiveringdetection")) { usedistinctiveringdetection = ast_true(v->value); } else if (!strcasecmp(v->name, "distinctiveringaftercid")) { distinctiveringaftercid = ast_true(v->value); } else if (!strcasecmp(v->name, "dring1context")) { ast_copy_string(confp->chan.drings.ringContext[0].contextData,v->value,sizeof(confp->chan.drings.ringContext[0].contextData)); } else if (!strcasecmp(v->name, "dring2context")) { ast_copy_string(confp->chan.drings.ringContext[1].contextData,v->value,sizeof(confp->chan.drings.ringContext[1].contextData)); } else if (!strcasecmp(v->name, "dring3context")) { ast_copy_string(confp->chan.drings.ringContext[2].contextData,v->value,sizeof(confp->chan.drings.ringContext[2].contextData)); } else if (!strcasecmp(v->name, "dring1range")) { confp->chan.drings.ringnum[0].range = atoi(v->value); } else if (!strcasecmp(v->name, "dring2range")) { confp->chan.drings.ringnum[1].range = atoi(v->value); } else if (!strcasecmp(v->name, "dring3range")) { confp->chan.drings.ringnum[2].range = atoi(v->value); } else if (!strcasecmp(v->name, "dring1")) { sscanf(v->value, "%30d,%30d,%30d", &confp->chan.drings.ringnum[0].ring[0], &confp->chan.drings.ringnum[0].ring[1], &confp->chan.drings.ringnum[0].ring[2]); } else if (!strcasecmp(v->name, "dring2")) { sscanf(v->value, "%30d,%30d,%30d", &confp->chan.drings.ringnum[1].ring[0], &confp->chan.drings.ringnum[1].ring[1], &confp->chan.drings.ringnum[1].ring[2]); } else if (!strcasecmp(v->name, "dring3")) { sscanf(v->value, "%30d,%30d,%30d", &confp->chan.drings.ringnum[2].ring[0], &confp->chan.drings.ringnum[2].ring[1], &confp->chan.drings.ringnum[2].ring[2]); } else if (!strcasecmp(v->name, "usecallerid")) { confp->chan.use_callerid = ast_true(v->value); } else if (!strcasecmp(v->name, "cidsignalling")) { if (!strcasecmp(v->value, "bell")) confp->chan.cid_signalling = CID_SIG_BELL; else if (!strcasecmp(v->value, "v23")) confp->chan.cid_signalling = CID_SIG_V23; else if (!strcasecmp(v->value, "dtmf")) confp->chan.cid_signalling = CID_SIG_DTMF; else if (!strcasecmp(v->value, "smdi")) confp->chan.cid_signalling = CID_SIG_SMDI; else if (!strcasecmp(v->value, "v23_jp")) confp->chan.cid_signalling = CID_SIG_V23_JP; else if (ast_true(v->value)) confp->chan.cid_signalling = CID_SIG_BELL; } else if (!strcasecmp(v->name, "cidstart")) { if (!strcasecmp(v->value, "ring")) confp->chan.cid_start = CID_START_RING; else if (!strcasecmp(v->value, "polarity_in")) confp->chan.cid_start = CID_START_POLARITY_IN; else if (!strcasecmp(v->value, "polarity")) confp->chan.cid_start = CID_START_POLARITY; else if (!strcasecmp(v->value, "dtmf")) confp->chan.cid_start = CID_START_DTMF_NOALERT; else if (ast_true(v->value)) confp->chan.cid_start = CID_START_RING; } else if (!strcasecmp(v->name, "threewaycalling")) { confp->chan.threewaycalling = ast_true(v->value); } else if (!strcasecmp(v->name, "cancallforward")) { confp->chan.cancallforward = ast_true(v->value); } else if (!strcasecmp(v->name, "relaxdtmf")) { if (ast_true(v->value)) confp->chan.dtmfrelax = DSP_DIGITMODE_RELAXDTMF; else confp->chan.dtmfrelax = 0; } else if (!strcasecmp(v->name, "mailbox")) { ast_copy_string(confp->chan.mailbox, v->value, sizeof(confp->chan.mailbox)); } else if (!strcasecmp(v->name, "description")) { ast_copy_string(confp->chan.description, v->value, sizeof(confp->chan.description)); } else if (!strcasecmp(v->name, "hasvoicemail")) { if (ast_true(v->value) && ast_strlen_zero(confp->chan.mailbox)) { /* * hasvoicemail is a users.conf legacy voicemail enable method. * hasvoicemail is only going to work for app_voicemail mailboxes. */ if (strchr(cat, '@')) { ast_copy_string(confp->chan.mailbox, cat, sizeof(confp->chan.mailbox)); } else { snprintf(confp->chan.mailbox, sizeof(confp->chan.mailbox), "%s@default", cat); } } } else if (!strcasecmp(v->name, "adsi")) { confp->chan.adsi = ast_true(v->value); } else if (!strcasecmp(v->name, "usesmdi")) { confp->chan.use_smdi = ast_true(v->value); } else if (!strcasecmp(v->name, "smdiport")) { ast_copy_string(confp->smdi_port, v->value, sizeof(confp->smdi_port)); } else if (!strcasecmp(v->name, "transfer")) { confp->chan.transfer = ast_true(v->value); } else if (!strcasecmp(v->name, "canpark")) { confp->chan.canpark = ast_true(v->value); } else if (!strcasecmp(v->name, "echocancelwhenbridged")) { confp->chan.echocanbridged = ast_true(v->value); } else if (!strcasecmp(v->name, "busydetect")) { confp->chan.busydetect = ast_true(v->value); } else if (!strcasecmp(v->name, "busycount")) { confp->chan.busycount = atoi(v->value); } else if (!strcasecmp(v->name, "busypattern")) { parse_busy_pattern(v, &confp->chan.busy_cadence); } else if (!strcasecmp(v->name, "callprogress")) { confp->chan.callprogress &= ~CALLPROGRESS_PROGRESS; if (ast_true(v->value)) confp->chan.callprogress |= CALLPROGRESS_PROGRESS; } else if (!strcasecmp(v->name, "waitfordialtone")) { confp->chan.waitfordialtone = atoi(v->value); } else if (!strcasecmp(v->name, "dialtone_detect")) { if (!strcasecmp(v->value, "always")) { confp->chan.dialtone_detect = -1; } else if (ast_true(v->value)) { confp->chan.dialtone_detect = DEFAULT_DIALTONE_DETECT_TIMEOUT; } else if (ast_false(v->value)) { confp->chan.dialtone_detect = 0; } else { confp->chan.dialtone_detect = ast_strlen_zero(v->value) ? 0 : (8 * atoi(v->value)) / READ_SIZE; } } else if (!strcasecmp(v->name, "faxdetect")) { confp->chan.callprogress &= ~CALLPROGRESS_FAX; if (!strcasecmp(v->value, "incoming")) { confp->chan.callprogress |= CALLPROGRESS_FAX_INCOMING; } else if (!strcasecmp(v->value, "outgoing")) { confp->chan.callprogress |= CALLPROGRESS_FAX_OUTGOING; } else if (!strcasecmp(v->value, "both") || ast_true(v->value)) confp->chan.callprogress |= CALLPROGRESS_FAX_INCOMING | CALLPROGRESS_FAX_OUTGOING; } else if (!strcasecmp(v->name, "echocancel")) { process_echocancel(confp, v->value, v->lineno); } else if (!strcasecmp(v->name, "echotraining")) { if (sscanf(v->value, "%30d", &y) == 1) { if ((y < 10) || (y > 4000)) { ast_log(LOG_WARNING, "Echo training time must be within the range of 10 to 4000 ms at line %d.\n", v->lineno); } else { confp->chan.echotraining = y; } } else if (ast_true(v->value)) { confp->chan.echotraining = 400; } else confp->chan.echotraining = 0; } else if (!strcasecmp(v->name, "hidecallerid")) { confp->chan.hidecallerid = ast_true(v->value); } else if (!strcasecmp(v->name, "hidecalleridname")) { confp->chan.hidecalleridname = ast_true(v->value); } else if (!strcasecmp(v->name, "pulsedial")) { confp->chan.pulse = ast_true(v->value); } else if (!strcasecmp(v->name, "callreturn")) { confp->chan.callreturn = ast_true(v->value); } else if (!strcasecmp(v->name, "callwaiting")) { confp->chan.callwaiting = ast_true(v->value); } else if (!strcasecmp(v->name, "callwaitingcallerid")) { confp->chan.callwaitingcallerid = ast_true(v->value); } else if (!strcasecmp(v->name, "context")) { ast_copy_string(confp->chan.context, v->value, sizeof(confp->chan.context)); } else if (!strcasecmp(v->name, "language")) { ast_copy_string(confp->chan.language, v->value, sizeof(confp->chan.language)); } else if (!strcasecmp(v->name, "progzone")) { ast_copy_string(progzone, v->value, sizeof(progzone)); } else if (!strcasecmp(v->name, "mohinterpret") ||!strcasecmp(v->name, "musiconhold") || !strcasecmp(v->name, "musicclass")) { ast_copy_string(confp->chan.mohinterpret, v->value, sizeof(confp->chan.mohinterpret)); } else if (!strcasecmp(v->name, "mohsuggest")) { ast_copy_string(confp->chan.mohsuggest, v->value, sizeof(confp->chan.mohsuggest)); } else if (!strcasecmp(v->name, "parkinglot")) { ast_copy_string(confp->chan.parkinglot, v->value, sizeof(confp->chan.parkinglot)); } else if (!strcasecmp(v->name, "stripmsd")) { ast_log(LOG_NOTICE, "Configuration option \"%s\" has been deprecated. Please use dialplan instead\n", v->name); confp->chan.stripmsd = atoi(v->value); } else if (!strcasecmp(v->name, "jitterbuffers")) { numbufs = atoi(v->value); } else if (!strcasecmp(v->name, "group")) { confp->chan.group = ast_get_group(v->value); } else if (!strcasecmp(v->name, "callgroup")) { if (!strcasecmp(v->value, "none")) confp->chan.callgroup = 0; else confp->chan.callgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "pickupgroup")) { if (!strcasecmp(v->value, "none")) confp->chan.pickupgroup = 0; else confp->chan.pickupgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "namedcallgroup")) { confp->chan.named_callgroups = ast_get_namedgroups(v->value); } else if (!strcasecmp(v->name, "namedpickupgroup")) { confp->chan.named_pickupgroups = ast_get_namedgroups(v->value); } else if (!strcasecmp(v->name, "setvar")) { char *varname = ast_strdupa(v->value), *varval = NULL; struct ast_variable *tmpvar; if (varname && (varval = strchr(varname, '='))) { *varval++ = '\0'; if ((tmpvar = ast_variable_new(varname, varval, ""))) { tmpvar->next = confp->chan.vars; confp->chan.vars = tmpvar; } } } else if (!strcasecmp(v->name, "immediate")) { confp->chan.immediate = ast_true(v->value); } else if (!strcasecmp(v->name, "transfertobusy")) { confp->chan.transfertobusy = ast_true(v->value); } else if (!strcasecmp(v->name, "mwimonitor")) { confp->chan.mwimonitor_neon = 0; confp->chan.mwimonitor_fsk = 0; confp->chan.mwimonitor_rpas = 0; if (strcasestr(v->value, "fsk")) { confp->chan.mwimonitor_fsk = 1; } if (strcasestr(v->value, "rpas")) { confp->chan.mwimonitor_rpas = 1; } if (strcasestr(v->value, "neon")) { confp->chan.mwimonitor_neon = 1; } /* If set to true or yes, assume that simple fsk is desired */ if (ast_true(v->value)) { confp->chan.mwimonitor_fsk = 1; } } else if (!strcasecmp(v->name, "hwrxgain")) { confp->chan.hwrxgain_enabled = 0; if (strcasecmp(v->value, "disabled")) { if (sscanf(v->value, "%30f", &confp->chan.hwrxgain) == 1) { confp->chan.hwrxgain_enabled = 1; } else { ast_log(LOG_WARNING, "Invalid hwrxgain: %s at line %d.\n", v->value, v->lineno); } } } else if (!strcasecmp(v->name, "hwtxgain")) { confp->chan.hwtxgain_enabled = 0; if (strcasecmp(v->value, "disabled")) { if (sscanf(v->value, "%30f", &confp->chan.hwtxgain) == 1) { confp->chan.hwtxgain_enabled = 1; } else { ast_log(LOG_WARNING, "Invalid hwtxgain: %s at line %d.\n", v->value, v->lineno); } } } else if (!strcasecmp(v->name, "cid_rxgain")) { if (sscanf(v->value, "%30f", &confp->chan.cid_rxgain) != 1) { ast_log(LOG_WARNING, "Invalid cid_rxgain: %s at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "rxgain")) { if (sscanf(v->value, "%30f", &confp->chan.rxgain) != 1) { ast_log(LOG_WARNING, "Invalid rxgain: %s at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "txgain")) { if (sscanf(v->value, "%30f", &confp->chan.txgain) != 1) { ast_log(LOG_WARNING, "Invalid txgain: %s at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "txdrc")) { if (sscanf(v->value, "%f", &confp->chan.txdrc) != 1) { ast_log(LOG_WARNING, "Invalid txdrc: %s\n", v->value); } } else if (!strcasecmp(v->name, "rxdrc")) { if (sscanf(v->value, "%f", &confp->chan.rxdrc) != 1) { ast_log(LOG_WARNING, "Invalid rxdrc: %s\n", v->value); } } else if (!strcasecmp(v->name, "tonezone")) { if (sscanf(v->value, "%30d", &confp->chan.tonezone) != 1) { ast_log(LOG_WARNING, "Invalid tonezone: %s at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "callerid")) { if (!strcasecmp(v->value, "asreceived")) { confp->chan.cid_num[0] = '\0'; confp->chan.cid_name[0] = '\0'; } else { ast_callerid_split(v->value, confp->chan.cid_name, sizeof(confp->chan.cid_name), confp->chan.cid_num, sizeof(confp->chan.cid_num)); } } else if (!strcasecmp(v->name, "fullname")) { ast_copy_string(confp->chan.cid_name, v->value, sizeof(confp->chan.cid_name)); } else if (!strcasecmp(v->name, "cid_number")) { ast_copy_string(confp->chan.cid_num, v->value, sizeof(confp->chan.cid_num)); } else if (!strcasecmp(v->name, "cid_tag")) { ast_copy_string(confp->chan.cid_tag, v->value, sizeof(confp->chan.cid_tag)); } else if (!strcasecmp(v->name, "useincomingcalleridondahditransfer")) { confp->chan.dahditrcallerid = ast_true(v->value); } else if (!strcasecmp(v->name, "restrictcid")) { confp->chan.restrictcid = ast_true(v->value); } else if (!strcasecmp(v->name, "usecallingpres")) { confp->chan.use_callingpres = ast_true(v->value); } else if (!strcasecmp(v->name, "accountcode")) { ast_copy_string(confp->chan.accountcode, v->value, sizeof(confp->chan.accountcode)); } else if (!strcasecmp(v->name, "amaflags")) { y = ast_channel_string2amaflag(v->value); if (y < 0) ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d.\n", v->value, v->lineno); else confp->chan.amaflags = y; } else if (!strcasecmp(v->name, "polarityonanswerdelay")) { confp->chan.polarityonanswerdelay = atoi(v->value); } else if (!strcasecmp(v->name, "answeronpolarityswitch")) { confp->chan.answeronpolarityswitch = ast_true(v->value); } else if (!strcasecmp(v->name, "hanguponpolarityswitch")) { confp->chan.hanguponpolarityswitch = ast_true(v->value); } else if (!strcasecmp(v->name, "sendcalleridafter")) { confp->chan.sendcalleridafter = atoi(v->value); } else if (!strcasecmp(v->name, "mwimonitornotify")) { ast_copy_string(mwimonitornotify, v->value, sizeof(mwimonitornotify)); } else if (ast_cc_is_config_param(v->name)) { ast_cc_set_param(confp->chan.cc_params, v->name, v->value); } else if (!strcasecmp(v->name, "mwisendtype")) { #ifndef HAVE_DAHDI_LINEREVERSE_VMWI /* backward compatibility for older dahdi VMWI implementation */ if (!strcasecmp(v->value, "rpas")) { /* Ring Pulse Alert Signal */ mwisend_rpas = 1; } else { mwisend_rpas = 0; } #else /* Default is fsk, to turn it off you must specify nofsk */ memset(&confp->chan.mwisend_setting, 0, sizeof(confp->chan.mwisend_setting)); if (strcasestr(v->value, "nofsk")) { /* NoFSK */ confp->chan.mwisend_fsk = 0; } else { /* Default FSK */ confp->chan.mwisend_fsk = 1; } if (strcasestr(v->value, "rpas")) { /* Ring Pulse Alert Signal, normally followed by FSK */ confp->chan.mwisend_rpas = 1; } else { confp->chan.mwisend_rpas = 0; } if (strcasestr(v->value, "lrev")) { /* Line Reversal */ confp->chan.mwisend_setting.vmwi_type |= DAHDI_VMWI_LREV; } if (strcasestr(v->value, "hvdc")) { /* HV 90VDC */ confp->chan.mwisend_setting.vmwi_type |= DAHDI_VMWI_HVDC; } if ( (strcasestr(v->value, "neon")) || (strcasestr(v->value, "hvac")) ) { /* 90V DC pulses */ confp->chan.mwisend_setting.vmwi_type |= DAHDI_VMWI_HVAC; } #endif } else if (reload != 1) { if (!strcasecmp(v->name, "signalling") || !strcasecmp(v->name, "signaling")) { int orig_radio = confp->chan.radio; int orig_outsigmod = confp->chan.outsigmod; int orig_auto = confp->is_sig_auto; confp->chan.radio = 0; confp->chan.outsigmod = -1; confp->is_sig_auto = 0; if (!strcasecmp(v->value, "em")) { confp->chan.sig = SIG_EM; } else if (!strcasecmp(v->value, "em_e1")) { confp->chan.sig = SIG_EM_E1; } else if (!strcasecmp(v->value, "em_w")) { confp->chan.sig = SIG_EMWINK; } else if (!strcasecmp(v->value, "fxs_ls")) { confp->chan.sig = SIG_FXSLS; } else if (!strcasecmp(v->value, "fxs_gs")) { confp->chan.sig = SIG_FXSGS; } else if (!strcasecmp(v->value, "fxs_ks")) { confp->chan.sig = SIG_FXSKS; } else if (!strcasecmp(v->value, "fxo_ls")) { confp->chan.sig = SIG_FXOLS; } else if (!strcasecmp(v->value, "fxo_gs")) { confp->chan.sig = SIG_FXOGS; } else if (!strcasecmp(v->value, "fxo_ks")) { confp->chan.sig = SIG_FXOKS; } else if (!strcasecmp(v->value, "fxs_rx")) { confp->chan.sig = SIG_FXSKS; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "fxo_rx")) { confp->chan.sig = SIG_FXOLS; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "fxs_tx")) { confp->chan.sig = SIG_FXSLS; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "fxo_tx")) { confp->chan.sig = SIG_FXOGS; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "em_rx")) { confp->chan.sig = SIG_EM; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "em_tx")) { confp->chan.sig = SIG_EM; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "em_rxtx")) { confp->chan.sig = SIG_EM; confp->chan.radio = 2; } else if (!strcasecmp(v->value, "em_txrx")) { confp->chan.sig = SIG_EM; confp->chan.radio = 2; } else if (!strcasecmp(v->value, "sf")) { confp->chan.sig = SIG_SF; } else if (!strcasecmp(v->value, "sf_w")) { confp->chan.sig = SIG_SFWINK; } else if (!strcasecmp(v->value, "sf_featd")) { confp->chan.sig = SIG_FEATD; } else if (!strcasecmp(v->value, "sf_featdmf")) { confp->chan.sig = SIG_FEATDMF; } else if (!strcasecmp(v->value, "sf_featb")) { confp->chan.sig = SIG_SF_FEATB; } else if (!strcasecmp(v->value, "sf")) { confp->chan.sig = SIG_SF; } else if (!strcasecmp(v->value, "sf_rx")) { confp->chan.sig = SIG_SF; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "sf_tx")) { confp->chan.sig = SIG_SF; confp->chan.radio = 1; } else if (!strcasecmp(v->value, "sf_rxtx")) { confp->chan.sig = SIG_SF; confp->chan.radio = 2; } else if (!strcasecmp(v->value, "sf_txrx")) { confp->chan.sig = SIG_SF; confp->chan.radio = 2; } else if (!strcasecmp(v->value, "featd")) { confp->chan.sig = SIG_FEATD; } else if (!strcasecmp(v->value, "featdmf")) { confp->chan.sig = SIG_FEATDMF; } else if (!strcasecmp(v->value, "featdmf_ta")) { confp->chan.sig = SIG_FEATDMF_TA; } else if (!strcasecmp(v->value, "e911")) { confp->chan.sig = SIG_E911; } else if (!strcasecmp(v->value, "fgccama")) { confp->chan.sig = SIG_FGC_CAMA; } else if (!strcasecmp(v->value, "fgccamamf")) { confp->chan.sig = SIG_FGC_CAMAMF; } else if (!strcasecmp(v->value, "featb")) { confp->chan.sig = SIG_FEATB; #ifdef HAVE_PRI } else if (!strcasecmp(v->value, "pri_net")) { confp->chan.sig = SIG_PRI; confp->pri.pri.nodetype = PRI_NETWORK; } else if (!strcasecmp(v->value, "pri_cpe")) { confp->chan.sig = SIG_PRI; confp->pri.pri.nodetype = PRI_CPE; } else if (!strcasecmp(v->value, "bri_cpe")) { confp->chan.sig = SIG_BRI; confp->pri.pri.nodetype = PRI_CPE; } else if (!strcasecmp(v->value, "bri_net")) { confp->chan.sig = SIG_BRI; confp->pri.pri.nodetype = PRI_NETWORK; } else if (!strcasecmp(v->value, "bri_cpe_ptmp")) { confp->chan.sig = SIG_BRI_PTMP; confp->pri.pri.nodetype = PRI_CPE; } else if (!strcasecmp(v->value, "bri_net_ptmp")) { #if defined(HAVE_PRI_CALL_HOLD) confp->chan.sig = SIG_BRI_PTMP; confp->pri.pri.nodetype = PRI_NETWORK; #else ast_log(LOG_WARNING, "How cool would it be if someone implemented this mode! For now, sucks for you. (line %d)\n", v->lineno); #endif /* !defined(HAVE_PRI_CALL_HOLD) */ #endif #if defined(HAVE_SS7) } else if (!strcasecmp(v->value, "ss7")) { confp->chan.sig = SIG_SS7; #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 } else if (!strcasecmp(v->value, "mfcr2")) { confp->chan.sig = SIG_MFCR2; #endif } else if (!strcasecmp(v->value, "auto")) { confp->is_sig_auto = 1; } else { confp->chan.outsigmod = orig_outsigmod; confp->chan.radio = orig_radio; confp->is_sig_auto = orig_auto; ast_log(LOG_ERROR, "Unknown signalling method '%s' at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "outsignalling") || !strcasecmp(v->name, "outsignaling")) { if (!strcasecmp(v->value, "em")) { confp->chan.outsigmod = SIG_EM; } else if (!strcasecmp(v->value, "em_e1")) { confp->chan.outsigmod = SIG_EM_E1; } else if (!strcasecmp(v->value, "em_w")) { confp->chan.outsigmod = SIG_EMWINK; } else if (!strcasecmp(v->value, "sf")) { confp->chan.outsigmod = SIG_SF; } else if (!strcasecmp(v->value, "sf_w")) { confp->chan.outsigmod = SIG_SFWINK; } else if (!strcasecmp(v->value, "sf_featd")) { confp->chan.outsigmod = SIG_FEATD; } else if (!strcasecmp(v->value, "sf_featdmf")) { confp->chan.outsigmod = SIG_FEATDMF; } else if (!strcasecmp(v->value, "sf_featb")) { confp->chan.outsigmod = SIG_SF_FEATB; } else if (!strcasecmp(v->value, "sf")) { confp->chan.outsigmod = SIG_SF; } else if (!strcasecmp(v->value, "featd")) { confp->chan.outsigmod = SIG_FEATD; } else if (!strcasecmp(v->value, "featdmf")) { confp->chan.outsigmod = SIG_FEATDMF; } else if (!strcasecmp(v->value, "featdmf_ta")) { confp->chan.outsigmod = SIG_FEATDMF_TA; } else if (!strcasecmp(v->value, "e911")) { confp->chan.outsigmod = SIG_E911; } else if (!strcasecmp(v->value, "fgccama")) { confp->chan.outsigmod = SIG_FGC_CAMA; } else if (!strcasecmp(v->value, "fgccamamf")) { confp->chan.outsigmod = SIG_FGC_CAMAMF; } else if (!strcasecmp(v->value, "featb")) { confp->chan.outsigmod = SIG_FEATB; } else { ast_log(LOG_ERROR, "Unknown signalling method '%s' at line %d.\n", v->value, v->lineno); } #ifdef HAVE_PRI } else if (!strcasecmp(v->name, "pridialplan")) { if (!strcasecmp(v->value, "national")) { confp->pri.pri.dialplan = PRI_NATIONAL_ISDN + 1; } else if (!strcasecmp(v->value, "unknown")) { confp->pri.pri.dialplan = PRI_UNKNOWN + 1; } else if (!strcasecmp(v->value, "private")) { confp->pri.pri.dialplan = PRI_PRIVATE + 1; } else if (!strcasecmp(v->value, "international")) { confp->pri.pri.dialplan = PRI_INTERNATIONAL_ISDN + 1; } else if (!strcasecmp(v->value, "local")) { confp->pri.pri.dialplan = PRI_LOCAL_ISDN + 1; } else if (!strcasecmp(v->value, "dynamic")) { confp->pri.pri.dialplan = -1; } else if (!strcasecmp(v->value, "redundant")) { confp->pri.pri.dialplan = -2; } else { ast_log(LOG_WARNING, "Unknown PRI dialplan '%s' at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "prilocaldialplan")) { if (!strcasecmp(v->value, "national")) { confp->pri.pri.localdialplan = PRI_NATIONAL_ISDN + 1; } else if (!strcasecmp(v->value, "unknown")) { confp->pri.pri.localdialplan = PRI_UNKNOWN + 1; } else if (!strcasecmp(v->value, "private")) { confp->pri.pri.localdialplan = PRI_PRIVATE + 1; } else if (!strcasecmp(v->value, "international")) { confp->pri.pri.localdialplan = PRI_INTERNATIONAL_ISDN + 1; } else if (!strcasecmp(v->value, "local")) { confp->pri.pri.localdialplan = PRI_LOCAL_ISDN + 1; } else if (!strcasecmp(v->value, "from_channel")) { confp->pri.pri.localdialplan = 0; } else if (!strcasecmp(v->value, "dynamic")) { confp->pri.pri.localdialplan = -1; } else if (!strcasecmp(v->value, "redundant")) { confp->pri.pri.localdialplan = -2; } else { ast_log(LOG_WARNING, "Unknown PRI localdialplan '%s' at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "pricpndialplan")) { if (!strcasecmp(v->value, "national")) { confp->pri.pri.cpndialplan = PRI_NATIONAL_ISDN + 1; } else if (!strcasecmp(v->value, "unknown")) { confp->pri.pri.cpndialplan = PRI_UNKNOWN + 1; } else if (!strcasecmp(v->value, "private")) { confp->pri.pri.cpndialplan = PRI_PRIVATE + 1; } else if (!strcasecmp(v->value, "international")) { confp->pri.pri.cpndialplan = PRI_INTERNATIONAL_ISDN + 1; } else if (!strcasecmp(v->value, "local")) { confp->pri.pri.cpndialplan = PRI_LOCAL_ISDN + 1; } else if (!strcasecmp(v->value, "from_channel")) { confp->pri.pri.cpndialplan = 0; } else if (!strcasecmp(v->value, "dynamic")) { confp->pri.pri.cpndialplan = -1; } else if (!strcasecmp(v->value, "redundant")) { confp->pri.pri.cpndialplan = -2; } else { ast_log(LOG_WARNING, "Unknown PRI cpndialplan '%s' at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "switchtype")) { if (!strcasecmp(v->value, "national")) confp->pri.pri.switchtype = PRI_SWITCH_NI2; else if (!strcasecmp(v->value, "ni1")) confp->pri.pri.switchtype = PRI_SWITCH_NI1; else if (!strcasecmp(v->value, "dms100")) confp->pri.pri.switchtype = PRI_SWITCH_DMS100; else if (!strcasecmp(v->value, "4ess")) confp->pri.pri.switchtype = PRI_SWITCH_ATT4ESS; else if (!strcasecmp(v->value, "5ess")) confp->pri.pri.switchtype = PRI_SWITCH_LUCENT5E; else if (!strcasecmp(v->value, "euroisdn")) confp->pri.pri.switchtype = PRI_SWITCH_EUROISDN_E1; else if (!strcasecmp(v->value, "qsig")) confp->pri.pri.switchtype = PRI_SWITCH_QSIG; else { ast_log(LOG_ERROR, "Unknown switchtype '%s' at line %d.\n", v->value, v->lineno); return -1; } } else if (!strcasecmp(v->name, "msn")) { ast_copy_string(confp->pri.pri.msn_list, v->value, sizeof(confp->pri.pri.msn_list)); } else if (!strcasecmp(v->name, "nsf")) { if (!strcasecmp(v->value, "sdn")) confp->pri.pri.nsf = PRI_NSF_SDN; else if (!strcasecmp(v->value, "megacom")) confp->pri.pri.nsf = PRI_NSF_MEGACOM; else if (!strcasecmp(v->value, "tollfreemegacom")) confp->pri.pri.nsf = PRI_NSF_TOLL_FREE_MEGACOM; else if (!strcasecmp(v->value, "accunet")) confp->pri.pri.nsf = PRI_NSF_ACCUNET; else if (!strcasecmp(v->value, "none")) confp->pri.pri.nsf = PRI_NSF_NONE; else { ast_log(LOG_WARNING, "Unknown network-specific facility '%s' at line %d.\n", v->value, v->lineno); confp->pri.pri.nsf = PRI_NSF_NONE; } } else if (!strcasecmp(v->name, "priindication")) { if (!strcasecmp(v->value, "outofband")) confp->chan.priindication_oob = 1; else if (!strcasecmp(v->value, "inband")) confp->chan.priindication_oob = 0; else ast_log(LOG_WARNING, "'%s' is not a valid pri indication value, should be 'inband' or 'outofband' at line %d.\n", v->value, v->lineno); } else if (!strcasecmp(v->name, "priexclusive")) { confp->chan.priexclusive = ast_true(v->value); } else if (!strcasecmp(v->name, "internationalprefix")) { ast_copy_string(confp->pri.pri.internationalprefix, v->value, sizeof(confp->pri.pri.internationalprefix)); } else if (!strcasecmp(v->name, "nationalprefix")) { ast_copy_string(confp->pri.pri.nationalprefix, v->value, sizeof(confp->pri.pri.nationalprefix)); } else if (!strcasecmp(v->name, "localprefix")) { ast_copy_string(confp->pri.pri.localprefix, v->value, sizeof(confp->pri.pri.localprefix)); } else if (!strcasecmp(v->name, "privateprefix")) { ast_copy_string(confp->pri.pri.privateprefix, v->value, sizeof(confp->pri.pri.privateprefix)); } else if (!strcasecmp(v->name, "unknownprefix")) { ast_copy_string(confp->pri.pri.unknownprefix, v->value, sizeof(confp->pri.pri.unknownprefix)); } else if (!strcasecmp(v->name, "resetinterval")) { if (!strcasecmp(v->value, "never")) confp->pri.pri.resetinterval = -1; else if (atoi(v->value) >= 60) confp->pri.pri.resetinterval = atoi(v->value); else ast_log(LOG_WARNING, "'%s' is not a valid reset interval, should be >= 60 seconds or 'never' at line %d.\n", v->value, v->lineno); } else if (!strcasecmp(v->name, "minunused")) { confp->pri.pri.minunused = atoi(v->value); } else if (!strcasecmp(v->name, "minidle")) { confp->pri.pri.minidle = atoi(v->value); } else if (!strcasecmp(v->name, "idleext")) { ast_copy_string(confp->pri.pri.idleext, v->value, sizeof(confp->pri.pri.idleext)); } else if (!strcasecmp(v->name, "idledial")) { ast_copy_string(confp->pri.pri.idledial, v->value, sizeof(confp->pri.pri.idledial)); } else if (!strcasecmp(v->name, "overlapdial")) { if (ast_true(v->value)) { confp->pri.pri.overlapdial = DAHDI_OVERLAPDIAL_BOTH; } else if (!strcasecmp(v->value, "incoming")) { confp->pri.pri.overlapdial = DAHDI_OVERLAPDIAL_INCOMING; } else if (!strcasecmp(v->value, "outgoing")) { confp->pri.pri.overlapdial = DAHDI_OVERLAPDIAL_OUTGOING; } else if (!strcasecmp(v->value, "both") || ast_true(v->value)) { confp->pri.pri.overlapdial = DAHDI_OVERLAPDIAL_BOTH; } else { confp->pri.pri.overlapdial = DAHDI_OVERLAPDIAL_NONE; } #ifdef HAVE_PRI_PROG_W_CAUSE } else if (!strcasecmp(v->name, "qsigchannelmapping")) { if (!strcasecmp(v->value, "logical")) { confp->pri.pri.qsigchannelmapping = DAHDI_CHAN_MAPPING_LOGICAL; } else if (!strcasecmp(v->value, "physical")) { confp->pri.pri.qsigchannelmapping = DAHDI_CHAN_MAPPING_PHYSICAL; } else { confp->pri.pri.qsigchannelmapping = DAHDI_CHAN_MAPPING_PHYSICAL; } #endif } else if (!strcasecmp(v->name, "discardremoteholdretrieval")) { confp->pri.pri.discardremoteholdretrieval = ast_true(v->value); #if defined(HAVE_PRI_SERVICE_MESSAGES) } else if (!strcasecmp(v->name, "service_message_support")) { /* assuming switchtype for this channel group has been configured already */ if ((confp->pri.pri.switchtype == PRI_SWITCH_ATT4ESS || confp->pri.pri.switchtype == PRI_SWITCH_LUCENT5E || confp->pri.pri.switchtype == PRI_SWITCH_NI2) && ast_true(v->value)) { confp->pri.pri.enable_service_message_support = 1; } else { confp->pri.pri.enable_service_message_support = 0; } #endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ #ifdef HAVE_PRI_INBANDDISCONNECT } else if (!strcasecmp(v->name, "inbanddisconnect")) { confp->pri.pri.inbanddisconnect = ast_true(v->value); #endif } else if (!strcasecmp(v->name, "pritimer")) { #ifdef PRI_GETSET_TIMERS char tmp[20]; char *timerc; char *c; int timer; int timeridx; ast_copy_string(tmp, v->value, sizeof(tmp)); c = tmp; timerc = strsep(&c, ","); if (!ast_strlen_zero(timerc) && !ast_strlen_zero(c)) { timeridx = pri_timer2idx(timerc); timer = atoi(c); if (timeridx < 0 || PRI_MAX_TIMERS <= timeridx) { ast_log(LOG_WARNING, "'%s' is not a valid ISDN timer at line %d.\n", timerc, v->lineno); } else if (!timer) { ast_log(LOG_WARNING, "'%s' is not a valid value for ISDN timer '%s' at line %d.\n", c, timerc, v->lineno); } else { confp->pri.pri.pritimers[timeridx] = timer; } } else { ast_log(LOG_WARNING, "'%s' is not a valid ISDN timer configuration string at line %d.\n", v->value, v->lineno); } #endif /* PRI_GETSET_TIMERS */ } else if (!strcasecmp(v->name, "facilityenable")) { confp->pri.pri.facilityenable = ast_true(v->value); #if defined(HAVE_PRI_AOC_EVENTS) } else if (!strcasecmp(v->name, "aoc_enable")) { confp->pri.pri.aoc_passthrough_flag = 0; if (strchr(v->value, 's') || strchr(v->value, 'S')) { confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_S; } if (strchr(v->value, 'd') || strchr(v->value, 'D')) { confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_D; } if (strchr(v->value, 'e') || strchr(v->value, 'E')) { confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_E; } } else if (!strcasecmp(v->name, "aoce_delayhangup")) { confp->pri.pri.aoce_delayhangup = ast_true(v->value); #endif /* defined(HAVE_PRI_AOC_EVENTS) */ #if defined(HAVE_PRI_CALL_HOLD) } else if (!strcasecmp(v->name, "hold_disconnect_transfer")) { confp->pri.pri.hold_disconnect_transfer = ast_true(v->value); #endif /* defined(HAVE_PRI_CALL_HOLD) */ } else if (!strcasecmp(v->name, "moh_signaling") || !strcasecmp(v->name, "moh_signalling")) { if (!strcasecmp(v->value, "moh")) { confp->pri.pri.moh_signaling = SIG_PRI_MOH_SIGNALING_MOH; } else if (!strcasecmp(v->value, "notify")) { confp->pri.pri.moh_signaling = SIG_PRI_MOH_SIGNALING_NOTIFY; #if defined(HAVE_PRI_CALL_HOLD) } else if (!strcasecmp(v->value, "hold")) { confp->pri.pri.moh_signaling = SIG_PRI_MOH_SIGNALING_HOLD; #endif /* defined(HAVE_PRI_CALL_HOLD) */ } else { confp->pri.pri.moh_signaling = SIG_PRI_MOH_SIGNALING_MOH; } #if defined(HAVE_PRI_CCSS) } else if (!strcasecmp(v->name, "cc_ptmp_recall_mode")) { if (!strcasecmp(v->value, "global")) { confp->pri.pri.cc_ptmp_recall_mode = 0;/* globalRecall */ } else if (!strcasecmp(v->value, "specific")) { confp->pri.pri.cc_ptmp_recall_mode = 1;/* specificRecall */ } else { confp->pri.pri.cc_ptmp_recall_mode = 1;/* specificRecall */ } } else if (!strcasecmp(v->name, "cc_qsig_signaling_link_req")) { if (!strcasecmp(v->value, "release")) { confp->pri.pri.cc_qsig_signaling_link_req = 0;/* release */ } else if (!strcasecmp(v->value, "retain")) { confp->pri.pri.cc_qsig_signaling_link_req = 1;/* retain */ } else if (!strcasecmp(v->value, "do_not_care")) { confp->pri.pri.cc_qsig_signaling_link_req = 2;/* do-not-care */ } else { confp->pri.pri.cc_qsig_signaling_link_req = 1;/* retain */ } } else if (!strcasecmp(v->name, "cc_qsig_signaling_link_rsp")) { if (!strcasecmp(v->value, "release")) { confp->pri.pri.cc_qsig_signaling_link_rsp = 0;/* release */ } else if (!strcasecmp(v->value, "retain")) { confp->pri.pri.cc_qsig_signaling_link_rsp = 1;/* retain */ } else { confp->pri.pri.cc_qsig_signaling_link_rsp = 1;/* retain */ } #endif /* defined(HAVE_PRI_CCSS) */ #if defined(HAVE_PRI_CALL_WAITING) } else if (!strcasecmp(v->name, "max_call_waiting_calls")) { confp->pri.pri.max_call_waiting_calls = atoi(v->value); if (confp->pri.pri.max_call_waiting_calls < 0) { /* Negative values are not allowed. */ confp->pri.pri.max_call_waiting_calls = 0; } } else if (!strcasecmp(v->name, "allow_call_waiting_calls")) { confp->pri.pri.allow_call_waiting_calls = ast_true(v->value); #endif /* defined(HAVE_PRI_CALL_WAITING) */ #if defined(HAVE_PRI_MWI) } else if (!strcasecmp(v->name, "mwi_mailboxes")) { ast_copy_string(confp->pri.pri.mwi_mailboxes, v->value, sizeof(confp->pri.pri.mwi_mailboxes)); } else if (!strcasecmp(v->name, "mwi_vm_boxes")) { ast_copy_string(confp->pri.pri.mwi_vm_boxes, v->value, sizeof(confp->pri.pri.mwi_vm_boxes)); } else if (!strcasecmp(v->name, "mwi_vm_numbers")) { ast_copy_string(confp->pri.pri.mwi_vm_numbers, v->value, sizeof(confp->pri.pri.mwi_vm_numbers)); #endif /* defined(HAVE_PRI_MWI) */ } else if (!strcasecmp(v->name, "append_msn_to_cid_tag")) { confp->pri.pri.append_msn_to_user_tag = ast_true(v->value); } else if (!strcasecmp(v->name, "inband_on_setup_ack")) { confp->pri.pri.inband_on_setup_ack = ast_true(v->value); } else if (!strcasecmp(v->name, "inband_on_proceeding")) { confp->pri.pri.inband_on_proceeding = ast_true(v->value); #if defined(HAVE_PRI_DISPLAY_TEXT) } else if (!strcasecmp(v->name, "display_send")) { confp->pri.pri.display_flags_send = dahdi_display_text_option(v->value); } else if (!strcasecmp(v->name, "display_receive")) { confp->pri.pri.display_flags_receive = dahdi_display_text_option(v->value); #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #if defined(HAVE_PRI_MCID) } else if (!strcasecmp(v->name, "mcid_send")) { confp->pri.pri.mcid_send = ast_true(v->value); #endif /* defined(HAVE_PRI_MCID) */ #if defined(HAVE_PRI_DATETIME_SEND) } else if (!strcasecmp(v->name, "datetime_send")) { confp->pri.pri.datetime_send = dahdi_datetime_send_option(v->value); #endif /* defined(HAVE_PRI_DATETIME_SEND) */ } else if (!strcasecmp(v->name, "layer1_presence")) { if (!strcasecmp(v->value, "required")) { confp->pri.pri.layer1_ignored = 0; } else if (!strcasecmp(v->value, "ignore")) { confp->pri.pri.layer1_ignored = 1; } else { /* Default */ confp->pri.pri.layer1_ignored = 0; } #if defined(HAVE_PRI_L2_PERSISTENCE) } else if (!strcasecmp(v->name, "layer2_persistence")) { if (!strcasecmp(v->value, "keep_up")) { confp->pri.pri.l2_persistence = PRI_L2_PERSISTENCE_KEEP_UP; } else if (!strcasecmp(v->value, "leave_down")) { confp->pri.pri.l2_persistence = PRI_L2_PERSISTENCE_LEAVE_DOWN; } else { confp->pri.pri.l2_persistence = PRI_L2_PERSISTENCE_DEFAULT; } #endif /* defined(HAVE_PRI_L2_PERSISTENCE) */ } else if (!strcasecmp(v->name, "colp_send")) { if (!strcasecmp(v->value, "block")) { confp->pri.pri.colp_send = SIG_PRI_COLP_BLOCK; } else if (!strcasecmp(v->value, "connect")) { confp->pri.pri.colp_send = SIG_PRI_COLP_CONNECT; } else if (!strcasecmp(v->value, "update")) { confp->pri.pri.colp_send = SIG_PRI_COLP_UPDATE; } else { confp->pri.pri.colp_send = SIG_PRI_COLP_UPDATE; } #endif /* HAVE_PRI */ #if defined(HAVE_SS7) } else if (!strcasecmp(v->name, "ss7type")) { if (!strcasecmp(v->value, "itu")) { cur_ss7type = SS7_ITU; } else if (!strcasecmp(v->value, "ansi")) { cur_ss7type = SS7_ANSI; } else { ast_log(LOG_WARNING, "'%s' is an unknown ss7 switch type at line %d.!\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "slc")) { cur_slc = atoi(v->value); } else if (!strcasecmp(v->name, "linkset")) { cur_linkset = atoi(v->value); } else if (!strcasecmp(v->name, "pointcode")) { cur_pointcode = parse_pointcode(v->value); } else if (!strcasecmp(v->name, "adjpointcode")) { cur_adjpointcode = parse_pointcode(v->value); } else if (!strcasecmp(v->name, "defaultdpc")) { cur_defaultdpc = parse_pointcode(v->value); } else if (!strcasecmp(v->name, "cicbeginswith")) { cur_cicbeginswith = atoi(v->value); } else if (!strcasecmp(v->name, "networkindicator")) { if (!strcasecmp(v->value, "national")) { cur_networkindicator = SS7_NI_NAT; } else if (!strcasecmp(v->value, "national_spare")) { cur_networkindicator = SS7_NI_NAT_SPARE; } else if (!strcasecmp(v->value, "international")) { cur_networkindicator = SS7_NI_INT; } else if (!strcasecmp(v->value, "international_spare")) { cur_networkindicator = SS7_NI_INT_SPARE; } else { cur_networkindicator = -1; } } else if (!strcasecmp(v->name, "ss7_internationalprefix")) { ast_copy_string(confp->ss7.ss7.internationalprefix, v->value, sizeof(confp->ss7.ss7.internationalprefix)); } else if (!strcasecmp(v->name, "ss7_nationalprefix")) { ast_copy_string(confp->ss7.ss7.nationalprefix, v->value, sizeof(confp->ss7.ss7.nationalprefix)); } else if (!strcasecmp(v->name, "ss7_subscriberprefix")) { ast_copy_string(confp->ss7.ss7.subscriberprefix, v->value, sizeof(confp->ss7.ss7.subscriberprefix)); } else if (!strcasecmp(v->name, "ss7_unknownprefix")) { ast_copy_string(confp->ss7.ss7.unknownprefix, v->value, sizeof(confp->ss7.ss7.unknownprefix)); } else if (!strcasecmp(v->name, "ss7_networkroutedprefix")) { ast_copy_string(confp->ss7.ss7.networkroutedprefix, v->value, sizeof(confp->ss7.ss7.networkroutedprefix)); } else if (!strcasecmp(v->name, "ss7_called_nai")) { if (!strcasecmp(v->value, "national")) { confp->ss7.ss7.called_nai = SS7_NAI_NATIONAL; } else if (!strcasecmp(v->value, "international")) { confp->ss7.ss7.called_nai = SS7_NAI_INTERNATIONAL; } else if (!strcasecmp(v->value, "subscriber")) { confp->ss7.ss7.called_nai = SS7_NAI_SUBSCRIBER; } else if (!strcasecmp(v->value, "unknown")) { confp->ss7.ss7.called_nai = SS7_NAI_UNKNOWN; } else if (!strcasecmp(v->value, "dynamic")) { confp->ss7.ss7.called_nai = SS7_NAI_DYNAMIC; } else { ast_log(LOG_WARNING, "Unknown SS7 called_nai '%s' at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "ss7_calling_nai")) { if (!strcasecmp(v->value, "national")) { confp->ss7.ss7.calling_nai = SS7_NAI_NATIONAL; } else if (!strcasecmp(v->value, "international")) { confp->ss7.ss7.calling_nai = SS7_NAI_INTERNATIONAL; } else if (!strcasecmp(v->value, "subscriber")) { confp->ss7.ss7.calling_nai = SS7_NAI_SUBSCRIBER; } else if (!strcasecmp(v->value, "unknown")) { confp->ss7.ss7.calling_nai = SS7_NAI_UNKNOWN; } else if (!strcasecmp(v->value, "dynamic")) { confp->ss7.ss7.calling_nai = SS7_NAI_DYNAMIC; } else { ast_log(LOG_WARNING, "Unknown SS7 calling_nai '%s' at line %d.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "sigchan")) { int sigchan, res; sigchan = atoi(v->value); res = linkset_addsigchan(sigchan); if (res < 0) { return -1; } } else if (!strcasecmp(v->name, "ss7_explicitacm")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (ast_true(v->value)) { link->ss7.flags |= LINKSET_FLAG_EXPLICITACM; } else { link->ss7.flags &= ~LINKSET_FLAG_EXPLICITACM; } } else if (!strcasecmp(v->name, "ss7_autoacm")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (ast_true(v->value)) { link->ss7.flags |= LINKSET_FLAG_AUTOACM; } else { link->ss7.flags &= ~LINKSET_FLAG_AUTOACM; } } else if (!strcasecmp(v->name, "ss7_initialhwblo")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (ast_true(v->value)) { link->ss7.flags |= LINKSET_FLAG_INITIALHWBLO; } else { link->ss7.flags &= ~LINKSET_FLAG_INITIALHWBLO; } } else if (!strcasecmp(v->name, "ss7_use_echocontrol")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (ast_true(v->value)) { link->ss7.flags |= LINKSET_FLAG_USEECHOCONTROL; } else { link->ss7.flags &= ~LINKSET_FLAG_USEECHOCONTROL; } } else if (!strcasecmp(v->name, "ss7_default_echocontrol")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (ast_true(v->value)) { link->ss7.flags |= LINKSET_FLAG_DEFAULTECHOCONTROL; } else { link->ss7.flags &= ~LINKSET_FLAG_DEFAULTECHOCONTROL; } } else if (!strncasecmp(v->name, "isup_timer.", 11)) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (!link->ss7.ss7) { ast_log(LOG_ERROR, "Please specify isup timers after sigchan!\n"); } else if (!ss7_set_isup_timer(link->ss7.ss7, strstr(v->name, ".") + 1, atoi(v->value))) { ast_log(LOG_ERROR, "Invalid isup timer %s\n", v->name); } } else if (!strncasecmp(v->name, "mtp3_timer.", 11)) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (!link->ss7.ss7) { ast_log(LOG_ERROR, "Please specify mtp3 timers after sigchan!\n"); } else if (!ss7_set_mtp3_timer(link->ss7.ss7, strstr(v->name, ".") + 1, atoi(v->value))) { ast_log(LOG_ERROR, "Invalid mtp3 timer %s\n", v->name); } } else if (!strcasecmp(v->name, "inr_if_no_calling")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (!link->ss7.ss7) { ast_log(LOG_ERROR, "Please specify inr_if_no_calling after sigchan!\n"); } else if (ast_true(v->value)) { ss7_set_flags(link->ss7.ss7, SS7_INR_IF_NO_CALLING); } else { ss7_clear_flags(link->ss7.ss7, SS7_INR_IF_NO_CALLING); } } else if (!strcasecmp(v->name, "non_isdn_access")) { struct dahdi_ss7 *link; link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (!link->ss7.ss7) { ast_log(LOG_ERROR, "Please specify non_isdn_access after sigchan!\n"); } else if (ast_true(v->value)) { ss7_clear_flags(link->ss7.ss7, SS7_ISDN_ACCESS_INDICATOR); } else { ss7_set_flags(link->ss7.ss7, SS7_ISDN_ACCESS_INDICATOR); } } else if (!strcasecmp(v->name, "sls_shift")) { struct dahdi_ss7 *link; int sls_shift = atoi(v->value); if (sls_shift < 0 || sls_shift > 7) { ast_log(LOG_ERROR, "Invalid sls_shift value. Must be between 0 and 7\n"); return -1; } link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (!link->ss7.ss7) { ast_log(LOG_ERROR, "Please specify sls_shift after sigchan!\n"); } else { ss7_set_sls_shift(link->ss7.ss7, sls_shift); } } else if (!strcasecmp(v->name, "cause_location")) { struct dahdi_ss7 *link; int cause_location = atoi(v->value); if (cause_location < 0 || cause_location > 15) { ast_log(LOG_ERROR, "Invalid cause_location value. Must be between 0 and 15\n"); return -1; } link = ss7_resolve_linkset(cur_linkset); if (!link) { ast_log(LOG_ERROR, "Invalid linkset number. Must be between 1 and %d\n", NUM_SPANS + 1); return -1; } if (!link->ss7.ss7) { ast_log(LOG_ERROR, "Please specify cause_location after sigchan!\n"); } else { ss7_set_cause_location(link->ss7.ss7, cause_location); } #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 } else if (!strcasecmp(v->name, "mfcr2_advanced_protocol_file")) { ast_copy_string(confp->mfcr2.r2proto_file, v->value, sizeof(confp->mfcr2.r2proto_file)); ast_log(LOG_WARNING, "MFC/R2 Protocol file '%s' will be used, you only should use this if you *REALLY KNOW WHAT YOU ARE DOING*.\n", confp->mfcr2.r2proto_file); } else if (!strcasecmp(v->name, "mfcr2_logdir")) { ast_copy_string(confp->mfcr2.logdir, v->value, sizeof(confp->mfcr2.logdir)); } else if (!strcasecmp(v->name, "mfcr2_variant")) { confp->mfcr2.variant = openr2_proto_get_variant(v->value); if (OR2_VAR_UNKNOWN == confp->mfcr2.variant) { ast_log(LOG_WARNING, "Unknown MFC/R2 variant '%s' at line %d, defaulting to ITU.\n", v->value, v->lineno); confp->mfcr2.variant = OR2_VAR_ITU; } } else if (!strcasecmp(v->name, "mfcr2_mfback_timeout")) { confp->mfcr2.mfback_timeout = atoi(v->value); if (!confp->mfcr2.mfback_timeout) { ast_log(LOG_WARNING, "MF timeout of 0? hum, I will protect you from your ignorance. Setting default.\n"); confp->mfcr2.mfback_timeout = -1; } else if (confp->mfcr2.mfback_timeout > 0 && confp->mfcr2.mfback_timeout < 500) { ast_log(LOG_WARNING, "MF timeout less than 500ms is not recommended, you have been warned!\n"); } } else if (!strcasecmp(v->name, "mfcr2_metering_pulse_timeout")) { confp->mfcr2.metering_pulse_timeout = atoi(v->value); if (confp->mfcr2.metering_pulse_timeout > 500) { ast_log(LOG_WARNING, "Metering pulse timeout greater than 500ms is not recommended, you have been warned!\n"); } #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 2 } else if (!strcasecmp(v->name, "mfcr2_dtmf_detection")) { confp->mfcr2.dtmf_detection = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_dtmf_dialing")) { confp->mfcr2.dtmf_dialing = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_dtmf_time_on")) { confp->mfcr2.dtmf_time_on = atoi(v->value); } else if (!strcasecmp(v->name, "mfcr2_dtmf_time_off")) { confp->mfcr2.dtmf_time_off = atoi(v->value); #endif #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 3 } else if (!strcasecmp(v->name, "mfcr2_dtmf_end_timeout")) { confp->mfcr2.dtmf_end_timeout = atoi(v->value); #endif } else if (!strcasecmp(v->name, "mfcr2_get_ani_first")) { confp->mfcr2.get_ani_first = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_double_answer")) { confp->mfcr2.double_answer = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_charge_calls")) { confp->mfcr2.charge_calls = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_accept_on_offer")) { confp->mfcr2.accept_on_offer = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_allow_collect_calls")) { confp->mfcr2.allow_collect_calls = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_forced_release")) { confp->mfcr2.forced_release = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_immediate_accept")) { confp->mfcr2.immediate_accept = ast_true(v->value) ? 1 : 0; #if defined(OR2_LIB_INTERFACE) && OR2_LIB_INTERFACE > 1 } else if (!strcasecmp(v->name, "mfcr2_skip_category")) { confp->mfcr2.skip_category_request = ast_true(v->value) ? 1 : 0; #endif } else if (!strcasecmp(v->name, "mfcr2_call_files")) { confp->mfcr2.call_files = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "mfcr2_max_ani")) { confp->mfcr2.max_ani = atoi(v->value); if (confp->mfcr2.max_ani >= AST_MAX_EXTENSION) { confp->mfcr2.max_ani = AST_MAX_EXTENSION - 1; } } else if (!strcasecmp(v->name, "mfcr2_max_dnis")) { confp->mfcr2.max_dnis = atoi(v->value); if (confp->mfcr2.max_dnis >= AST_MAX_EXTENSION) { confp->mfcr2.max_dnis = AST_MAX_EXTENSION - 1; } } else if (!strcasecmp(v->name, "mfcr2_category")) { confp->mfcr2.category = openr2_proto_get_category(v->value); if (OR2_CALLING_PARTY_CATEGORY_UNKNOWN == confp->mfcr2.category) { confp->mfcr2.category = OR2_CALLING_PARTY_CATEGORY_NATIONAL_SUBSCRIBER; ast_log(LOG_WARNING, "Invalid MFC/R2 caller category '%s' at line %d. Using national subscriber as default.\n", v->value, v->lineno); } } else if (!strcasecmp(v->name, "mfcr2_logging")) { openr2_log_level_t tmplevel; char *clevel; char *logval = ast_strdupa(v->value); while (logval) { clevel = strsep(&logval,","); if (-1 == (tmplevel = openr2_log_get_level(clevel))) { ast_log(LOG_WARNING, "Ignoring invalid logging level: '%s' at line %d.\n", clevel, v->lineno); continue; } confp->mfcr2.loglevel |= tmplevel; } #endif /* HAVE_OPENR2 */ } else if (!strcasecmp(v->name, "cadence")) { /* setup to scan our argument */ int element_count, c[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int i; struct dahdi_ring_cadence new_cadence; int cid_location = -1; int firstcadencepos = 0; char original_args[80]; int cadence_is_ok = 1; ast_copy_string(original_args, v->value, sizeof(original_args)); /* 16 cadences allowed (8 pairs) */ element_count = sscanf(v->value, "%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d,%30d", &c[0], &c[1], &c[2], &c[3], &c[4], &c[5], &c[6], &c[7], &c[8], &c[9], &c[10], &c[11], &c[12], &c[13], &c[14], &c[15]); /* Cadence must be even (on/off) */ if (element_count % 2 == 1) { ast_log(LOG_ERROR, "Must be a silence duration for each ring duration: %s at line %d.\n", original_args, v->lineno); cadence_is_ok = 0; } /* Ring cadences cannot be negative */ for (i = 0; i < element_count; i++) { if (c[i] == 0) { ast_log(LOG_ERROR, "Ring or silence duration cannot be zero: %s at line %d.\n", original_args, v->lineno); cadence_is_ok = 0; break; } else if (c[i] < 0) { if (i % 2 == 1) { /* Silence duration, negative possibly okay */ if (cid_location == -1) { cid_location = i; c[i] *= -1; } else { ast_log(LOG_ERROR, "CID location specified twice: %s at line %d.\n", original_args, v->lineno); cadence_is_ok = 0; break; } } else { if (firstcadencepos == 0) { firstcadencepos = i; /* only recorded to avoid duplicate specification */ /* duration will be passed negative to the DAHDI driver */ } else { ast_log(LOG_ERROR, "First cadence position specified twice: %s at line %d.\n", original_args, v->lineno); cadence_is_ok = 0; break; } } } } /* Substitute our scanned cadence */ for (i = 0; i < 16; i++) { new_cadence.ringcadence[i] = c[i]; } if (cadence_is_ok) { /* ---we scanned it without getting annoyed; now some sanity checks--- */ if (element_count < 2) { ast_log(LOG_ERROR, "Minimum cadence is ring,pause: %s at line %d.\n", original_args, v->lineno); } else { if (cid_location == -1) { /* user didn't say; default to first pause */ cid_location = 1; } else { /* convert element_index to cidrings value */ cid_location = (cid_location + 1) / 2; } /* ---we like their cadence; try to install it--- */ if (!user_has_defined_cadences++) /* this is the first user-defined cadence; clear the default user cadences */ num_cadence = 0; if ((num_cadence+1) >= NUM_CADENCE_MAX) ast_log(LOG_ERROR, "Already %d cadences; can't add another: %s at line %d.\n", NUM_CADENCE_MAX, original_args, v->lineno); else { cadences[num_cadence] = new_cadence; cidrings[num_cadence++] = cid_location; ast_verb(3, "cadence 'r%d' added: %s\n",num_cadence,original_args); } } } } else if (!strcasecmp(v->name, "ringtimeout")) { ringt_base = (atoi(v->value) * 8) / READ_SIZE; } else if (!strcasecmp(v->name, "prewink")) { confp->timing.prewinktime = atoi(v->value); } else if (!strcasecmp(v->name, "preflash")) { confp->timing.preflashtime = atoi(v->value); } else if (!strcasecmp(v->name, "wink")) { confp->timing.winktime = atoi(v->value); } else if (!strcasecmp(v->name, "flash")) { confp->timing.flashtime = atoi(v->value); } else if (!strcasecmp(v->name, "start")) { confp->timing.starttime = atoi(v->value); } else if (!strcasecmp(v->name, "rxwink")) { confp->timing.rxwinktime = atoi(v->value); } else if (!strcasecmp(v->name, "rxflash")) { confp->timing.rxflashtime = atoi(v->value); } else if (!strcasecmp(v->name, "debounce")) { confp->timing.debouncetime = atoi(v->value); } else if (!strcasecmp(v->name, "toneduration")) { int toneduration; int ctlfd; int res; struct dahdi_dialparams dps; ctlfd = open("/dev/dahdi/ctl", O_RDWR); if (ctlfd == -1) { ast_log(LOG_ERROR, "Unable to open /dev/dahdi/ctl to set toneduration at line %d.\n", v->lineno); return -1; } toneduration = atoi(v->value); if (toneduration > -1) { memset(&dps, 0, sizeof(dps)); dps.dtmf_tonelen = dps.mfv1_tonelen = toneduration; res = ioctl(ctlfd, DAHDI_SET_DIALPARAMS, &dps); if (res < 0) { ast_log(LOG_ERROR, "Invalid tone duration: %d ms at line %d: %s\n", toneduration, v->lineno, strerror(errno)); close(ctlfd); return -1; } } close(ctlfd); } else if (!strcasecmp(v->name, "defaultcic")) { ast_copy_string(defaultcic, v->value, sizeof(defaultcic)); } else if (!strcasecmp(v->name, "defaultozz")) { ast_copy_string(defaultozz, v->value, sizeof(defaultozz)); } else if (!strcasecmp(v->name, "mwilevel")) { mwilevel = atoi(v->value); } else if (!strcasecmp(v->name, "dtmfcidlevel")) { dtmfcid_level = atoi(v->value); } else if (!strcasecmp(v->name, "reportalarms")) { if (!strcasecmp(v->value, "all")) report_alarms = REPORT_CHANNEL_ALARMS | REPORT_SPAN_ALARMS; if (!strcasecmp(v->value, "none")) report_alarms = 0; else if (!strcasecmp(v->value, "channels")) report_alarms = REPORT_CHANNEL_ALARMS; else if (!strcasecmp(v->value, "spans")) report_alarms = REPORT_SPAN_ALARMS; } } else if (!(options & PROC_DAHDI_OPT_NOWARN) ) ast_log(LOG_WARNING, "Ignoring any changes to '%s' (on reload) at line %d.\n", v->name, v->lineno); } /* Since confp has already filled invidual dahdi_pvt objects with channels at this point, clear the variables in confp's pvt. */ if (confp->chan.vars) { ast_variables_destroy(confp->chan.vars); confp->chan.vars = NULL; } if (dahdichan) { /* Process the deferred dahdichan value. */ if (build_channels(confp, dahdichan->value, reload, dahdichan->lineno)) { if (confp->ignore_failed_channels) { ast_log(LOG_WARNING, "Dahdichan '%s' failure ignored: ignore_failed_channels.\n", dahdichan->value); } else { return -1; } } } /* mark the first channels of each DAHDI span to watch for their span alarms */ for (tmp = iflist, y=-1; tmp; tmp = tmp->next) { if (!tmp->destroy && tmp->span != y) { tmp->manages_span_alarms = 1; y = tmp->span; } else { tmp->manages_span_alarms = 0; } } /*< \todo why check for the pseudo in the per-channel section. * Any actual use for manual setup of the pseudo channel? */ if (!has_pseudo && reload != 1 && !(options & PROC_DAHDI_OPT_NOCHAN)) { /* use the default configuration for a channel, so that any settings from real configured channels don't "leak" into the pseudo channel config */ struct dahdi_chan_conf conf = dahdi_chan_conf_default(); if (conf.chan.cc_params) { tmp = mkintf(CHAN_PSEUDO, &conf, reload); } else { tmp = NULL; } if (tmp) { ast_verb(3, "Automatically generated pseudo channel\n"); has_pseudo = 1; } else { ast_log(LOG_WARNING, "Unable to register pseudo channel!\n"); } ast_cc_config_params_destroy(conf.chan.cc_params); } /* Since named callgroup and named pickup group are ref'd to dahdi_pvt at this point, unref container in confp's pvt. */ confp->chan.named_callgroups = ast_unref_namedgroups(confp->chan.named_callgroups); confp->chan.named_pickupgroups = ast_unref_namedgroups(confp->chan.named_pickupgroups); return 0; } /*! * \internal * \brief Deep copy struct dahdi_chan_conf. * \since 1.8 * * \param dest Destination. * \param src Source. * * \return Nothing */ static void deep_copy_dahdi_chan_conf(struct dahdi_chan_conf *dest, const struct dahdi_chan_conf *src) { struct ast_cc_config_params *cc_params; cc_params = dest->chan.cc_params; *dest = *src; dest->chan.cc_params = cc_params; ast_cc_copy_config_params(dest->chan.cc_params, src->chan.cc_params); } /*! * \internal * \brief Setup DAHDI channel driver. * * \param reload enum: load_module(0), reload(1), restart(2). * \param default_conf Default config parameters. So cc_params can be properly destroyed. * \param base_conf Default config parameters per section. So cc_params can be properly destroyed. * \param conf Local config parameters. So cc_params can be properly destroyed. * * \retval 0 on success. * \retval -1 on error. */ static int setup_dahdi_int(int reload, struct dahdi_chan_conf *default_conf, struct dahdi_chan_conf *base_conf, struct dahdi_chan_conf *conf) { struct ast_config *cfg; struct ast_config *ucfg; struct ast_variable *v; struct ast_flags config_flags = { reload == 1 ? CONFIG_FLAG_FILEUNCHANGED : 0 }; const char *chans; const char *cat; int res; #ifdef HAVE_PRI char *c; int spanno; int i; int logicalspan; int trunkgroup; int dchannels[SIG_PRI_NUM_DCHANS]; #endif int have_cfg_now; static int had_cfg_before = 1;/* So initial load will complain if we don't have cfg. */ cfg = ast_config_load(config, config_flags); have_cfg_now = !!cfg; if (!cfg) { /* Error if we have no config file */ if (had_cfg_before) { ast_log(LOG_ERROR, "Unable to load config %s\n", config); ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); } cfg = ast_config_new();/* Dummy config */ if (!cfg) { return 0; } ucfg = ast_config_load("users.conf", config_flags); if (ucfg == CONFIG_STATUS_FILEUNCHANGED) { ast_config_destroy(cfg); return 0; } if (ucfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "File users.conf cannot be parsed. Aborting.\n"); ast_config_destroy(cfg); return 0; } } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { ucfg = ast_config_load("users.conf", config_flags); if (ucfg == CONFIG_STATUS_FILEUNCHANGED) { return 0; } if (ucfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "File users.conf cannot be parsed. Aborting.\n"); return 0; } ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); cfg = ast_config_load(config, config_flags); have_cfg_now = !!cfg; if (!cfg) { if (had_cfg_before) { /* We should have been able to load the config. */ ast_log(LOG_ERROR, "Bad. Unable to load config %s\n", config); ast_config_destroy(ucfg); return 0; } cfg = ast_config_new();/* Dummy config */ if (!cfg) { ast_config_destroy(ucfg); return 0; } } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "File %s cannot be parsed. Aborting.\n", config); ast_config_destroy(ucfg); return 0; } } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "File %s cannot be parsed. Aborting.\n", config); return 0; } else { ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED); ucfg = ast_config_load("users.conf", config_flags); if (ucfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "File users.conf cannot be parsed. Aborting.\n"); ast_config_destroy(cfg); return 0; } } had_cfg_before = have_cfg_now; /* It's a little silly to lock it, but we might as well just to be sure */ ast_mutex_lock(&iflock); #ifdef HAVE_PRI if (reload != 1) { /* Process trunkgroups first */ v = ast_variable_browse(cfg, "trunkgroups"); while (v) { if (!strcasecmp(v->name, "trunkgroup")) { trunkgroup = atoi(v->value); if (trunkgroup > 0) { if ((c = strchr(v->value, ','))) { i = 0; memset(dchannels, 0, sizeof(dchannels)); while (c && (i < SIG_PRI_NUM_DCHANS)) { dchannels[i] = atoi(c + 1); if (dchannels[i] < 0) { ast_log(LOG_WARNING, "D-channel for trunk group %d must be a postiive number at line %d of chan_dahdi.conf\n", trunkgroup, v->lineno); } else i++; c = strchr(c + 1, ','); } if (i) { if (pri_create_trunkgroup(trunkgroup, dchannels)) { ast_log(LOG_WARNING, "Unable to create trunk group %d with Primary D-channel %d at line %d of chan_dahdi.conf\n", trunkgroup, dchannels[0], v->lineno); } else ast_verb(2, "Created trunk group %d with Primary D-channel %d and %d backup%s\n", trunkgroup, dchannels[0], i - 1, (i == 1) ? "" : "s"); } else ast_log(LOG_WARNING, "Trunk group %d lacks any valid D-channels at line %d of chan_dahdi.conf\n", trunkgroup, v->lineno); } else ast_log(LOG_WARNING, "Trunk group %d lacks a primary D-channel at line %d of chan_dahdi.conf\n", trunkgroup, v->lineno); } else ast_log(LOG_WARNING, "Trunk group identifier must be a positive integer at line %d of chan_dahdi.conf\n", v->lineno); } else if (!strcasecmp(v->name, "spanmap")) { spanno = atoi(v->value); if (spanno > 0) { if ((c = strchr(v->value, ','))) { trunkgroup = atoi(c + 1); if (trunkgroup > 0) { if ((c = strchr(c + 1, ','))) logicalspan = atoi(c + 1); else logicalspan = 0; if (logicalspan >= 0) { if (pri_create_spanmap(spanno - 1, trunkgroup, logicalspan)) { ast_log(LOG_WARNING, "Failed to map span %d to trunk group %d (logical span %d)\n", spanno, trunkgroup, logicalspan); } else ast_verb(2, "Mapped span %d to trunk group %d (logical span %d)\n", spanno, trunkgroup, logicalspan); } else ast_log(LOG_WARNING, "Logical span must be a postive number, or '0' (for unspecified) at line %d of chan_dahdi.conf\n", v->lineno); } else ast_log(LOG_WARNING, "Trunk group must be a postive number at line %d of chan_dahdi.conf\n", v->lineno); } else ast_log(LOG_WARNING, "Missing trunk group for span map at line %d of chan_dahdi.conf\n", v->lineno); } else ast_log(LOG_WARNING, "Span number must be a postive integer at line %d of chan_dahdi.conf\n", v->lineno); } else { ast_log(LOG_NOTICE, "Ignoring unknown keyword '%s' in trunkgroups\n", v->name); } v = v->next; } } #endif /* Copy the default jb config over global_jbconf */ memcpy(&global_jbconf, &default_jbconf, sizeof(global_jbconf)); mwimonitornotify[0] = '\0'; v = ast_variable_browse(cfg, "channels"); if ((res = process_dahdi(base_conf, "" /* Must be empty for the channels category. Silly voicemail mailbox. */, v, reload, 0))) { ast_mutex_unlock(&iflock); ast_config_destroy(cfg); if (ucfg) { ast_config_destroy(ucfg); } return res; } /* Now get configuration from all normal sections in chan_dahdi.conf: */ for (cat = ast_category_browse(cfg, NULL); cat ; cat = ast_category_browse(cfg, cat)) { /* [channels] and [trunkgroups] are used. Let's also reserve * [globals] and [general] for future use */ if (!strcasecmp(cat, "general") || !strcasecmp(cat, "trunkgroups") || !strcasecmp(cat, "globals") || !strcasecmp(cat, "channels")) { continue; } chans = ast_variable_retrieve(cfg, cat, "dahdichan"); if (ast_strlen_zero(chans)) { /* Section is useless without a dahdichan value present. */ continue; } /* Copy base_conf to conf. */ deep_copy_dahdi_chan_conf(conf, base_conf); if ((res = process_dahdi(conf, cat, ast_variable_browse(cfg, cat), reload, PROC_DAHDI_OPT_NOCHAN))) { ast_mutex_unlock(&iflock); ast_config_destroy(cfg); if (ucfg) { ast_config_destroy(ucfg); } return res; } } ast_config_destroy(cfg); if (ucfg) { /* Reset base_conf, so things don't leak from chan_dahdi.conf */ deep_copy_dahdi_chan_conf(base_conf, default_conf); process_dahdi(base_conf, "" /* Must be empty for the general category. Silly voicemail mailbox. */, ast_variable_browse(ucfg, "general"), 1, 0); for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) { if (!strcasecmp(cat, "general")) { continue; } chans = ast_variable_retrieve(ucfg, cat, "dahdichan"); if (ast_strlen_zero(chans)) { /* Section is useless without a dahdichan value present. */ continue; } /* Copy base_conf to conf. */ deep_copy_dahdi_chan_conf(conf, base_conf); if ((res = process_dahdi(conf, cat, ast_variable_browse(ucfg, cat), reload, PROC_DAHDI_OPT_NOCHAN | PROC_DAHDI_OPT_NOWARN))) { ast_config_destroy(ucfg); ast_mutex_unlock(&iflock); return res; } } ast_config_destroy(ucfg); } ast_mutex_unlock(&iflock); #ifdef HAVE_PRI if (reload != 1) { int x; for (x = 0; x < NUM_SPANS; x++) { if (pris[x].pri.pvts[0] && pris[x].pri.master == AST_PTHREADT_NULL) { prepare_pri(pris + x); if (sig_pri_start_pri(&pris[x].pri)) { ast_log(LOG_ERROR, "Unable to start D-channel on span %d\n", x + 1); return -1; } else ast_verb(2, "Starting D-Channel on span %d\n", x + 1); } } } #endif #if defined(HAVE_SS7) if (reload != 1) { int x; for (x = 0; x < NUM_SPANS; x++) { if (linksets[x].ss7.ss7) { if (ast_pthread_create(&linksets[x].ss7.master, NULL, ss7_linkset, &linksets[x].ss7)) { ast_log(LOG_ERROR, "Unable to start SS7 linkset on span %d\n", x + 1); return -1; } else ast_verb(2, "Starting SS7 linkset on span %d\n", x + 1); } } } #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 if (reload != 1) { int x; for (x = 0; x < r2links_count; x++) { if (ast_pthread_create(&r2links[x]->r2master, NULL, mfcr2_monitor, r2links[x])) { ast_log(LOG_ERROR, "Unable to start R2 monitor on channel group %d\n", x + 1); return -1; } else { ast_verb(2, "Starting R2 monitor on channel group %d\n", x + 1); } } } #endif /* And start the monitor for the first time */ restart_monitor(); return 0; } /*! * \internal * \brief Setup DAHDI channel driver. * * \param reload enum: load_module(0), reload(1), restart(2). * * \retval 0 on success. * \retval -1 on error. */ static int setup_dahdi(int reload) { int res; struct dahdi_chan_conf default_conf = dahdi_chan_conf_default(); struct dahdi_chan_conf base_conf = dahdi_chan_conf_default(); struct dahdi_chan_conf conf = dahdi_chan_conf_default(); if (default_conf.chan.cc_params && base_conf.chan.cc_params && conf.chan.cc_params) { res = setup_dahdi_int(reload, &default_conf, &base_conf, &conf); } else { res = -1; } ast_cc_config_params_destroy(default_conf.chan.cc_params); ast_cc_config_params_destroy(base_conf.chan.cc_params); ast_cc_config_params_destroy(conf.chan.cc_params); return res; } /*! * \internal * \brief Callback used to generate the dahdi status tree. * \param[in] search The search pattern tree. * \retval NULL on error. * \retval non-NULL The generated tree. */ static int dahdi_status_data_provider_get(const struct ast_data_search *search, struct ast_data *data_root) { int ctl, res, span; struct ast_data *data_span, *data_alarms; struct dahdi_spaninfo s; ctl = open("/dev/dahdi/ctl", O_RDWR); if (ctl < 0) { ast_log(LOG_ERROR, "No DAHDI found. Unable to open /dev/dahdi/ctl: %s\n", strerror(errno)); return -1; } for (span = 1; span < DAHDI_MAX_SPANS; ++span) { s.spanno = span; res = ioctl(ctl, DAHDI_SPANSTAT, &s); if (res) { continue; } data_span = ast_data_add_node(data_root, "span"); if (!data_span) { continue; } ast_data_add_str(data_span, "description", s.desc); /* insert the alarms status */ data_alarms = ast_data_add_node(data_span, "alarms"); if (!data_alarms) { continue; } ast_data_add_bool(data_alarms, "BLUE", s.alarms & DAHDI_ALARM_BLUE); ast_data_add_bool(data_alarms, "YELLOW", s.alarms & DAHDI_ALARM_YELLOW); ast_data_add_bool(data_alarms, "RED", s.alarms & DAHDI_ALARM_RED); ast_data_add_bool(data_alarms, "LOOPBACK", s.alarms & DAHDI_ALARM_LOOPBACK); ast_data_add_bool(data_alarms, "RECOVER", s.alarms & DAHDI_ALARM_RECOVER); ast_data_add_bool(data_alarms, "NOTOPEN", s.alarms & DAHDI_ALARM_NOTOPEN); ast_data_add_int(data_span, "irqmisses", s.irqmisses); ast_data_add_int(data_span, "bpviol", s.bpvcount); ast_data_add_int(data_span, "crc4", s.crc4count); ast_data_add_str(data_span, "framing", s.lineconfig & DAHDI_CONFIG_D4 ? "D4" : s.lineconfig & DAHDI_CONFIG_ESF ? "ESF" : s.lineconfig & DAHDI_CONFIG_CCS ? "CCS" : "CAS"); ast_data_add_str(data_span, "coding", s.lineconfig & DAHDI_CONFIG_B8ZS ? "B8ZS" : s.lineconfig & DAHDI_CONFIG_HDB3 ? "HDB3" : s.lineconfig & DAHDI_CONFIG_AMI ? "AMI" : "Unknown"); ast_data_add_str(data_span, "options", s.lineconfig & DAHDI_CONFIG_CRC4 ? s.lineconfig & DAHDI_CONFIG_NOTOPEN ? "CRC4/YEL" : "CRC4" : s.lineconfig & DAHDI_CONFIG_NOTOPEN ? "YEL" : ""); ast_data_add_str(data_span, "lbo", lbostr[s.lbo]); /* if this span doesn't match remove it. */ if (!ast_data_search_match(search, data_span)) { ast_data_remove_node(data_root, data_span); } } close(ctl); return 0; } /*! * \internal * \brief Callback used to generate the dahdi channels tree. * \param[in] search The search pattern tree. * \retval NULL on error. * \retval non-NULL The generated tree. */ static int dahdi_channels_data_provider_get(const struct ast_data_search *search, struct ast_data *data_root) { struct dahdi_pvt *tmp; struct ast_data *data_channel; ast_mutex_lock(&iflock); for (tmp = iflist; tmp; tmp = tmp->next) { data_channel = ast_data_add_node(data_root, "channel"); if (!data_channel) { continue; } ast_data_add_structure(dahdi_pvt, data_channel, tmp); /* if this channel doesn't match remove it. */ if (!ast_data_search_match(search, data_channel)) { ast_data_remove_node(data_root, data_channel); } } ast_mutex_unlock(&iflock); return 0; } /*! * \internal * \brief Callback used to generate the dahdi channels tree. * \param[in] search The search pattern tree. * \retval NULL on error. * \retval non-NULL The generated tree. */ static int dahdi_version_data_provider_get(const struct ast_data_search *search, struct ast_data *data_root) { int pseudo_fd = -1; struct dahdi_versioninfo vi = { .version = "Unknown", .echo_canceller = "Unknown" }; if ((pseudo_fd = open("/dev/dahdi/ctl", O_RDONLY)) < 0) { ast_log(LOG_ERROR, "Failed to open control file to get version.\n"); return -1; } if (ioctl(pseudo_fd, DAHDI_GETVERSION, &vi)) { ast_log(LOG_ERROR, "Failed to get DAHDI version: %s\n", strerror(errno)); } close(pseudo_fd); ast_data_add_str(data_root, "value", vi.version); ast_data_add_str(data_root, "echocanceller", vi.echo_canceller); return 0; } static const struct ast_data_handler dahdi_status_data_provider = { .version = AST_DATA_HANDLER_VERSION, .get = dahdi_status_data_provider_get }; static const struct ast_data_handler dahdi_channels_data_provider = { .version = AST_DATA_HANDLER_VERSION, .get = dahdi_channels_data_provider_get }; static const struct ast_data_handler dahdi_version_data_provider = { .version = AST_DATA_HANDLER_VERSION, .get = dahdi_version_data_provider_get }; static const struct ast_data_entry dahdi_data_providers[] = { AST_DATA_ENTRY("asterisk/channel/dahdi/status", &dahdi_status_data_provider), AST_DATA_ENTRY("asterisk/channel/dahdi/channels", &dahdi_channels_data_provider), AST_DATA_ENTRY("asterisk/channel/dahdi/version", &dahdi_version_data_provider) }; /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static int load_module(void) { int res; #if defined(HAVE_PRI) || defined(HAVE_SS7) int y; #endif /* defined(HAVE_PRI) || defined(HAVE_SS7) */ if (STASIS_MESSAGE_TYPE_INIT(dahdichannel_type)) { return AST_MODULE_LOAD_FAILURE; } if (!(dahdi_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { return AST_MODULE_LOAD_FAILURE; } ast_format_cap_append(dahdi_tech.capabilities, ast_format_slin, 0); ast_format_cap_append(dahdi_tech.capabilities, ast_format_ulaw, 0); ast_format_cap_append(dahdi_tech.capabilities, ast_format_alaw, 0); if (dahdi_native_load(ast_module_info->self, &dahdi_tech)) { ao2_ref(dahdi_tech.capabilities, -1); return AST_MODULE_LOAD_FAILURE; } #ifdef HAVE_PRI memset(pris, 0, sizeof(pris)); for (y = 0; y < NUM_SPANS; y++) { sig_pri_init_pri(&pris[y].pri); } pri_set_error(dahdi_pri_error); pri_set_message(dahdi_pri_message); ast_register_application_xml(dahdi_send_keypad_facility_app, dahdi_send_keypad_facility_exec); #ifdef HAVE_PRI_PROG_W_CAUSE ast_register_application_xml(dahdi_send_callrerouting_facility_app, dahdi_send_callrerouting_facility_exec); #endif #if defined(HAVE_PRI_CCSS) if (ast_cc_agent_register(&dahdi_pri_cc_agent_callbacks) || ast_cc_monitor_register(&dahdi_pri_cc_monitor_callbacks)) { __unload_module(); return AST_MODULE_LOAD_FAILURE; } #endif /* defined(HAVE_PRI_CCSS) */ if (sig_pri_load( #if defined(HAVE_PRI_CCSS) dahdi_pri_cc_type #else NULL #endif /* defined(HAVE_PRI_CCSS) */ )) { __unload_module(); return AST_MODULE_LOAD_FAILURE; } #endif #if defined(HAVE_SS7) memset(linksets, 0, sizeof(linksets)); for (y = 0; y < NUM_SPANS; y++) { sig_ss7_init_linkset(&linksets[y].ss7); } ss7_set_error(dahdi_ss7_error); ss7_set_message(dahdi_ss7_message); ss7_set_hangup(sig_ss7_cb_hangup); ss7_set_notinservice(sig_ss7_cb_notinservice); ss7_set_call_null(sig_ss7_cb_call_null); #endif /* defined(HAVE_SS7) */ res = setup_dahdi(0); /* Make sure we can register our DAHDI channel type */ if (res) { __unload_module(); return AST_MODULE_LOAD_DECLINE; } if (ast_channel_register(&dahdi_tech)) { ast_log(LOG_ERROR, "Unable to register channel class 'DAHDI'\n"); __unload_module(); return AST_MODULE_LOAD_FAILURE; } #ifdef HAVE_PRI ast_cli_register_multiple(dahdi_pri_cli, ARRAY_LEN(dahdi_pri_cli)); #endif #if defined(HAVE_SS7) ast_cli_register_multiple(dahdi_ss7_cli, ARRAY_LEN(dahdi_ss7_cli)); #endif /* defined(HAVE_SS7) */ #ifdef HAVE_OPENR2 ast_cli_register_multiple(dahdi_mfcr2_cli, ARRAY_LEN(dahdi_mfcr2_cli)); ast_register_application_xml(dahdi_accept_r2_call_app, dahdi_accept_r2_call_exec); #endif ast_cli_register_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli)); /* register all the data providers */ ast_data_register_multiple(dahdi_data_providers, ARRAY_LEN(dahdi_data_providers)); memset(round_robin, 0, sizeof(round_robin)); ast_manager_register_xml("DAHDITransfer", 0, action_transfer); ast_manager_register_xml("DAHDIHangup", 0, action_transferhangup); ast_manager_register_xml("DAHDIDialOffhook", 0, action_dahdidialoffhook); ast_manager_register_xml("DAHDIDNDon", 0, action_dahdidndon); ast_manager_register_xml("DAHDIDNDoff", 0, action_dahdidndoff); ast_manager_register_xml("DAHDIShowChannels", 0, action_dahdishowchannels); ast_manager_register_xml("DAHDIRestart", 0, action_dahdirestart); #if defined(HAVE_PRI) ast_manager_register_xml("PRIShowSpans", 0, action_prishowspans); ast_manager_register_xml("PRIDebugSet", 0, action_pri_debug_set); ast_manager_register_xml("PRIDebugFileSet", EVENT_FLAG_SYSTEM, action_pri_debug_file_set); ast_manager_register_xml("PRIDebugFileUnset", 0, action_pri_debug_file_unset); #endif /* defined(HAVE_PRI) */ ast_cond_init(&ss_thread_complete, NULL); return res; } static int dahdi_sendtext(struct ast_channel *c, const char *text) { #define END_SILENCE_LEN 400 #define HEADER_MS 50 #define TRAILER_MS 5 #define HEADER_LEN ((HEADER_MS + TRAILER_MS) * 8) #define ASCII_BYTES_PER_CHAR 80 unsigned char *buf,*mybuf; struct dahdi_pvt *p = ast_channel_tech_pvt(c); struct pollfd fds[1]; int size,res,fd,len,x; int bytes=0; int idx; /* * Initial carrier (imaginary) * * Note: The following float variables are used by the * PUT_CLID_MARKMS and PUT_CLID() macros. */ float cr = 1.0; float ci = 0.0; float scont = 0.0; if (!text[0]) { return(0); /* if nothing to send, don't */ } idx = dahdi_get_index(c, p, 0); if (idx < 0) { ast_log(LOG_WARNING, "Huh? I don't exist?\n"); return -1; } if ((!p->tdd) && (!p->mate)) { #if defined(HAVE_PRI) #if defined(HAVE_PRI_DISPLAY_TEXT) ast_mutex_lock(&p->lock); if (dahdi_sig_pri_lib_handles(p->sig)) { sig_pri_sendtext(p->sig_pvt, text); } ast_mutex_unlock(&p->lock); #endif /* defined(HAVE_PRI_DISPLAY_TEXT) */ #endif /* defined(HAVE_PRI) */ return(0); /* if not in TDD mode, just return */ } if (p->mate) buf = ast_malloc(((strlen(text) + 1) * ASCII_BYTES_PER_CHAR) + END_SILENCE_LEN + HEADER_LEN); else buf = ast_malloc(((strlen(text) + 1) * TDD_BYTES_PER_CHAR) + END_SILENCE_LEN); if (!buf) return -1; mybuf = buf; if (p->mate) { /* PUT_CLI_MARKMS is a macro and requires a format ptr called codec to be present */ struct ast_format *codec = AST_LAW(p); for (x = 0; x < HEADER_MS; x++) { /* 50 ms of Mark */ PUT_CLID_MARKMS; } /* Put actual message */ for (x = 0; text[x]; x++) { PUT_CLID(text[x]); } for (x = 0; x < TRAILER_MS; x++) { /* 5 ms of Mark */ PUT_CLID_MARKMS; } len = bytes; buf = mybuf; } else { len = tdd_generate(p->tdd, buf, text); if (len < 1) { ast_log(LOG_ERROR, "TDD generate (len %d) failed!!\n", (int)strlen(text)); ast_free(mybuf); return -1; } } memset(buf + len, 0x7f, END_SILENCE_LEN); len += END_SILENCE_LEN; fd = p->subs[idx].dfd; while (len) { if (ast_check_hangup(c)) { ast_free(mybuf); return -1; } size = len; if (size > READ_SIZE) size = READ_SIZE; fds[0].fd = fd; fds[0].events = POLLOUT | POLLPRI; fds[0].revents = 0; res = poll(fds, 1, -1); if (!res) { ast_debug(1, "poll (for write) ret. 0 on channel %d\n", p->channel); continue; } /* if got exception */ if (fds[0].revents & POLLPRI) { ast_free(mybuf); return -1; } if (!(fds[0].revents & POLLOUT)) { ast_debug(1, "write fd not ready on channel %d\n", p->channel); continue; } res = write(fd, buf, size); if (res != size) { if (res == -1) { ast_free(mybuf); return -1; } ast_debug(1, "Write returned %d (%s) on channel %d\n", res, strerror(errno), p->channel); break; } len -= size; buf += size; } ast_free(mybuf); return(0); } static int reload(void) { int res = 0; res = setup_dahdi(1); if (res) { ast_log(LOG_WARNING, "Reload of chan_dahdi.so is unsuccessful!\n"); return -1; } return 0; } /* This is a workaround so that menuselect displays a proper description * AST_MODULE_INFO(, , "DAHDI Telephony" */ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, tdesc, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload, .load_pri = AST_MODPRI_CHANNEL_DRIVER, .nonoptreq = "res_smdi", ); asterisk-13.1.0/channels/console_board.c0000644000000000000000000002306111766660300016676 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright 2007-2008, Marta Carbone, Luigi Rizzo * * 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. * * $Revision: 369013 $ */ /* * Message board implementation. * * A message board is a region of the SDL screen where * messages can be printed, like on a terminal window. * * At the moment we support fix-size font. * * The text is stored in a buffer * of fixed size (rows and cols). A portion of the buffer is * visible on the screen, and the visible window can be moved up and * down by dragging (not yet!) * * TODO: font dynamic allocation * * The region where the text is displayed on the screen is defined * as keypad element, (the name is defined in the `region' variable * so the board geometry can be read from the skin or from the * configuration file). */ /*** MODULEINFO extended ***/ #include "asterisk.h" /* ast_strdupa */ ASTERISK_FILE_VERSION(__FILE__, "$Revision: 369013 $") #include "asterisk/utils.h" /* ast_strdupa */ #include "console_video.h" /* ast_strdupa */ #ifdef HAVE_SDL /* we only use this code if SDL is available */ #include /* Fonts characterization. XXX should be read from the file */ #define FONT_H 20 /* char height, pixels */ #define FONT_W 9 /* char width, pixels */ struct board { int kb_output; /* identity of the board */ /* pointer to the destination surface (on the keypad window) */ SDL_Surface *screen; /* the main screen */ SDL_Rect *p_rect; /* where to write on the main screen */ SDL_Surface *blank; /* original content of the window */ int v_h; /* virtual text height, in lines */ int v_w; /* virtual text width, in lines (probably same as p_w) */ int p_h; /* physical (displayed) text height, in lines * XXX p_h * FONT_H = pixel_height */ int p_w; /* physical (displayed) text width, in characters * XXX p_w * FONT_W = pixel_width */ int cur_col; /* print position (free character) on the last line */ int cur_line; /* first (or last ?) virtual line displayed, * 0 is the line at the bottom, 1 is the one above,... */ SDL_Surface *font; /* points to a surface in the gui structure */ SDL_Rect *font_rects; /* pointer to the font rects */ char *text; /* text buffer, v_h * v_w char. * We make sure the buffer is always full, * print on some position on the last line, * and scroll up when appending new text */ }; /*! \brief Initialize the board. * return 0 on success, 1 on error * TODO, if this is done at reload time, * free resources before allocate new ones * TODO: resource deallocation in case of error. * TODO: move the font load at gui_initialization * TODO: deallocation of the message history */ struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest, SDL_Surface *font, SDL_Rect *font_rects); struct board *board_setup(SDL_Surface *screen, SDL_Rect *dest, SDL_Surface *font, SDL_Rect *font_rects) { struct board *b = ast_calloc(1, sizeof (*b)); SDL_Rect br; if (b == NULL) return NULL; /* font, points to the gui structure */ b->font = font; b->font_rects = font_rects; /* Destination rectangle on the screen - reference is the whole screen */ b->p_rect = dest; b->screen = screen; /* compute physical sizes */ b->p_h = b->p_rect->h/FONT_H; b->p_w = b->p_rect->w/FONT_W; /* virtual sizes */ b->v_h = b->p_h * 10; /* XXX 10 times larger */ b->v_w = b->p_w; /* same width */ /* the rectangle we actually use */ br.h = b->p_h * FONT_H; /* pixel sizes of the background */ br.w = b->p_w * FONT_W; br.x = br.y = 0; /* allocate a buffer for the text */ b->text = ast_calloc(b->v_w*b->v_h + 1, 1); if (b->text == NULL) { ast_log(LOG_WARNING, "Unable to allocate board history memory.\n"); ast_free(b); return NULL; } memset(b->text, ' ', b->v_w * b->v_h); /* fill with spaces */ /* make a copy of the original rectangle, for cleaning up */ b->blank = SDL_CreateRGBSurface(screen->flags, br.w, br.h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask); if (b->blank == NULL) { ast_log(LOG_WARNING, "Unable to allocate board virtual screen: %s\n", SDL_GetError()); ast_free(b->text); ast_free(b); return NULL; } SDL_BlitSurface(screen, b->p_rect, b->blank, &br); /* Set color key, if not alpha channel present */ //colorkey = SDL_MapRGB(b->board_surface->format, 0, 0, 0); //SDL_SetColorKey(b->board_surface, SDL_SRCCOLORKEY, colorkey); b->cur_col = 0; /* current print column */ b->cur_line = 0; /* last line displayed */ if (0) ast_log(LOG_WARNING, "Message board %dx%d@%d,%d successfully initialized\n", b->p_rect->w, b->p_rect->h, b->p_rect->x, b->p_rect->y); return b; } /* Render the text on the board surface. * The first line to render is the one at v_h - p_h - cur_line, * the size is p_h * p_w. * XXX we assume here that p_w = v_w. */ static void render_board(struct board *b) { int first_row = b->v_h - b->p_h - b->cur_line; int first_char = b->v_w * first_row; int last_char = first_char + b->p_h * b->v_w; int i, col; SDL_Rect dst; /* top left char on the physical surface */ dst.w = FONT_W; dst.h = FONT_H; dst.x = b->p_rect->x; dst.y = b->p_rect->y; /* clean the surface board */ SDL_BlitSurface(b->blank, NULL, b->screen, b->p_rect); /* blit all characters */ for (i = first_char, col = 0; i < last_char; i++) { int c = b->text[i] - 32; /* XXX first 32 chars are not printable */ if (c < 0) /* buffer terminator or anything else is a blank */ c = 0; SDL_BlitSurface(b->font, &b->font_rects[c], b->screen, &dst); /* point dst to next char position */ dst.x += dst.w; col++; if (col >= b->v_w) { /* next row */ dst.x = b->p_rect->x; dst.y += dst.h; col = 0; } } SDL_UpdateRects(b->screen, 1, b->p_rect); /* Update the screen */ } void move_message_board(struct board *b, int dy) { int cur = b->cur_line + dy; if (cur < 0) cur = 0; else if (cur >= b->v_h - b->p_h) cur = b->v_h - b->p_h - 1; b->cur_line = cur; render_board(b); } /* return the content of a board */ const char *read_message(const struct board *b) { return b->text; } int reset_board(struct board *b) { memset(b->text, ' ', b->v_w * b->v_h); /* fill with spaces */ b->cur_col = 0; b->cur_line = 0; render_board(b); return 0; } /* Store the message on the history board * and blit on screen if required. * XXX now easy. only regular chars */ int print_message(struct board *b, const char *s) { int i, l, row, col; char *dst; if (ast_strlen_zero(s)) return 0; l = strlen(s); row = 0; col = b->cur_col; /* First, only check how much space we need. * Starting from the current print position, we move * it forward and down (if necessary) according to input * characters (including newlines, tabs, backspaces...). * At the end, row tells us how many rows to scroll, and * col (ignored) is the final print position. */ for (i = 0; i < l; i++) { switch (s[i]) { case '\r': col = 0; break; case '\n': col = 0; row++; break; case '\b': if (col > 0) col--; break; default: if (s[i] < 32) /* signed, so take up to 127 */ break; col++; if (col >= b->v_w) { col -= b->v_w; row++; } break; } } /* scroll the text window */ if (row > 0) { /* need to scroll by 'row' rows */ memcpy(b->text, b->text + row * b->v_w, b->v_w * (b->v_h - row)); /* clean the destination area */ dst = b->text + b->v_w * (b->v_h - row - 1) + b->cur_col; memset(dst, ' ', b->v_w - b->cur_col + b->v_w * row); } /* now do the actual printing. The print position is 'row' lines up * from the bottom of the buffer, start at the same 'cur_col' as before. * dst points to the beginning of the current line. */ dst = b->text + b->v_w * (b->v_h - row - 1); /* start of current line */ col = b->cur_col; for (i = 0; i < l; i++) { switch (s[i]) { case '\r': col = 0; break; case '\n': /* move to beginning of next line */ dst[col] = '\0'; /* mark the rest of the line as empty */ col = 0; dst += b->v_w; break; case '\b': /* one char back */ if (col > 0) col--; dst[col] = ' '; /* delete current char */ break; default: if (s[i] < 32) /* signed, so take up to 127 */ break; /* non printable */ dst[col] = s[i]; /* store character */ col++; if (col >= b->v_w) { col -= b->v_w; dst += b->v_w; } break; } } dst[col] = '\0'; /* the current position is empty */ b->cur_col = col; /* everything is printed now, must do the rendering */ render_board(b); return 1; } /* deletes a board. * we make the free operation on any fields of the board structure allocated * in dynamic memory */ void delete_board(struct board *b) { if (b) { /* deletes the text */ if (b->text) ast_free (b->text); /* deallocates the blank surface */ SDL_FreeSurface(b->blank); /* deallocates the board */ ast_free(b); } } #if 0 /*! \brief refresh the screen, and also grab a bunch of events. */ static int scroll_message(...) { if moving up, scroll text up; if (gui->message_board.screen_cur > 0) gui->message_board.screen_cur--; otherwise scroll text down. if ((b->screen_cur + b->p_line) < b->board_next) { gui->message_board.screen_cur++; #endif /* notyet */ #endif /* HAVE_SDL */ asterisk-13.1.0/channels/chan_vpb.cc0000644000000000000000000025322312363036551016014 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2003, Paul Bagyenda * Paul Bagyenda * Copyright (C) 2004 - 2005, Ben Kramer * Ben Kramer * * Daniel Bichara - Brazilian CallerID detection (c)2004 * * Welber Silveira - welberms@magiclink.com.br - (c)2004 * Copying CLID string to propper structure after detection * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \brief VoiceTronix Interface driver * * \ingroup channel_drivers */ /*! \li \ref chan_vpb.cc uses the configuration file \ref vpb.conf * \addtogroup configuration_file */ /*! \page vpb.conf vpb.conf * \verbinclude vpb.conf.sample */ /* * XXX chan_vpb needs its native bridge code converted to the * new bridge technology scheme. The chan_dahdi native bridge * code can be used as an example. It is unlikely that this * will ever get done. * * The existing native bridge code is marked with the * VPB_NATIVE_BRIDGING conditional. */ /*** MODULEINFO vpb no extended ***/ #include extern "C" { #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419044 $") #include "asterisk/lock.h" #include "asterisk/utils.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/callerid.h" #include "asterisk/dsp.h" #include "asterisk/features.h" #include "asterisk/musiconhold.h" #include "asterisk/format_cache.h" } #include #include #include #include #include #include #include #ifdef pthread_create #undef pthread_create #endif #define DEFAULT_GAIN 0 #define DEFAULT_ECHO_CANCEL 1 #define VPB_SAMPLES 160 #define VPB_MAX_BUF VPB_SAMPLES*4 + AST_FRIENDLY_OFFSET #define VPB_NULL_EVENT 200 #define VPB_WAIT_TIMEOUT 4000 #define MAX_VPB_GAIN 12.0 #define MIN_VPB_GAIN -12.0 #define DTMF_CALLERID #define DTMF_CID_START 'D' #define DTMF_CID_STOP 'C' /**/ #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif /**/ static const char desc[] = "VoiceTronix V6PCI/V12PCI/V4PCI API Support"; static const char tdesc[] = "Standard VoiceTronix API Driver"; static const char config[] = "vpb.conf"; /* Default context for dialtone mode */ static char context[AST_MAX_EXTENSION] = "default"; /* Default language */ static char language[MAX_LANGUAGE] = ""; static int gruntdetect_timeout = 3600000; /* Grunt detect timeout is 1hr. */ /* Protect the interface list (of vpb_pvt's) */ AST_MUTEX_DEFINE_STATIC(iflock); /* 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(monlock); /* This is the thread for the monitor which checks for input on the channels which are not currently in use. */ static pthread_t monitor_thread; static int mthreadactive = -1; /* Flag for monitoring monitorthread.*/ static int restart_monitor(void); /* The private structures of the VPB channels are linked for selecting outgoing channels */ #define MODE_DIALTONE 1 #define MODE_IMMEDIATE 2 #define MODE_FXO 3 /* Pick a country or add your own! */ /* These are the tones that are played to the user */ #define TONES_AU /* #define TONES_USA */ #ifdef TONES_AU static VPB_TONE Dialtone = {440, 440, 440, -10, -10, -10, 5000, 0 }; static VPB_TONE Busytone = {470, 0, 0, -10, -100, -100, 5000, 0 }; static VPB_TONE Ringbacktone = {400, 50, 440, -10, -10, -10, 1400, 800 }; #endif #ifdef TONES_USA static VPB_TONE Dialtone = {350, 440, 0, -16, -16, -100, 10000, 0}; static VPB_TONE Busytone = {480, 620, 0, -10, -10, -100, 500, 500}; static VPB_TONE Ringbacktone = {440, 480, 0, -20, -20, -100, 2000, 4000}; #endif /* grunt tone defn's */ #if 0 static VPB_DETECT toned_grunt = { 3, VPB_GRUNT, 1, 2000, 3000, 0, 0, -40, 0, 0, 0, 40, { { VPB_DELAY, 1000, 0, 0 }, { VPB_RISING, 0, 40, 0 }, { 0, 100, 0, 0 } } }; #endif static VPB_DETECT toned_ungrunt = { 2, VPB_GRUNT, 1, 2000, 1, 0, 0, -40, 0, 0, 30, 40, { { 0, 0, 0, 0 } } }; /* Use loop polarity detection for CID */ static int UsePolarityCID=0; /* Use loop drop detection */ static int UseLoopDrop=1; /* To use or not to use Native bridging */ static int UseNativeBridge=1; /* Use Asterisk Indication or VPB */ static int use_ast_ind=0; /* Use Asterisk DTMF detection or VPB */ static int use_ast_dtmfdet=0; static int relaxdtmf=0; /* Use Asterisk DTMF play back or VPB */ static int use_ast_dtmf=0; /* Break for DTMF on native bridge ? */ static int break_for_dtmf=1; /* Set EC suppression threshold */ static short ec_supp_threshold=-1; /* Inter Digit Delay for collecting DTMF's */ static int dtmf_idd = 3000; #define TIMER_PERIOD_RINGBACK 2000 #define TIMER_PERIOD_BUSY 700 #define TIMER_PERIOD_RING 4000 static int timer_period_ring = TIMER_PERIOD_RING; #define VPB_EVENTS_ALL (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP \ |VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \ |VPB_MRING_OFF|VPB_MDROP|VPB_MSTATION_FLASH) #define VPB_EVENTS_NODROP (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP \ |VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \ |VPB_MRING_OFF|VPB_MSTATION_FLASH) #define VPB_EVENTS_NODTMF (VPB_MRING|VPB_MDIGIT|VPB_MTONEDETECT|VPB_MTIMEREXP \ |VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \ |VPB_MRING_OFF|VPB_MDROP|VPB_MSTATION_FLASH) #define VPB_EVENTS_STAT (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP \ |VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \ |VPB_MRING_OFF|VPB_MSTATION_FLASH) /* Dialing parameters for Australia */ /* #define DIAL_WITH_CALL_PROGRESS */ VPB_TONE_MAP DialToneMap[] = { { VPB_BUSY, VPB_CALL_DISCONNECT, 0 }, { VPB_DIAL, VPB_CALL_DIALTONE, 0 }, { VPB_RINGBACK, VPB_CALL_RINGBACK, 0 }, { VPB_BUSY, VPB_CALL_BUSY, 0 }, { VPB_GRUNT, VPB_CALL_GRUNT, 0 }, { 0, 0, 1 } }; #define VPB_DIALTONE_WAIT 2000 /* Wait up to 2s for a dialtone */ #define VPB_RINGWAIT 4000 /* Wait up to 4s for ring tone after dialing */ #define VPB_CONNECTED_WAIT 4000 /* If no ring tone detected for 4s then consider call connected */ #define TIMER_PERIOD_NOANSWER 120000 /* Let it ring for 120s before deciding theres noone there */ #define MAX_BRIDGES_V4PCI 2 #define MAX_BRIDGES_V12PCI 128 /* port states */ #define VPB_STATE_ONHOOK 0 #define VPB_STATE_OFFHOOK 1 #define VPB_STATE_DIALLING 2 #define VPB_STATE_JOINED 3 #define VPB_STATE_GETDTMF 4 #define VPB_STATE_PLAYDIAL 5 #define VPB_STATE_PLAYBUSY 6 #define VPB_STATE_PLAYRING 7 #define VPB_GOT_RXHWG 1 #define VPB_GOT_TXHWG 2 #define VPB_GOT_RXSWG 4 #define VPB_GOT_TXSWG 8 typedef struct { int inuse; struct ast_channel *c0, *c1, **rc; struct ast_frame **fo; int flags; ast_mutex_t lock; ast_cond_t cond; int endbridge; } vpb_bridge_t; static vpb_bridge_t * bridges; static int max_bridges = MAX_BRIDGES_V4PCI; AST_MUTEX_DEFINE_STATIC(bridge_lock); typedef enum { vpb_model_unknown = 0, vpb_model_v4pci, vpb_model_v12pci } vpb_model_t; static struct vpb_pvt { ast_mutex_t owner_lock; /*!< Protect blocks that expect ownership to remain the same */ struct ast_channel *owner; /*!< Channel who owns us, possibly NULL */ int golock; /*!< Got owner lock ? */ int mode; /*!< fxo/imediate/dialtone */ int handle; /*!< Handle for vpb interface */ int state; /*!< used to keep port state (internal to driver) */ int group; /*!< Which group this port belongs to */ ast_group_t callgroup; /*!< Call group */ ast_group_t pickupgroup; /*!< Pickup group */ char dev[256]; /*!< Device name, eg vpb/1-1 */ vpb_model_t vpb_model; /*!< card model */ struct ast_frame f, fr; /*!< Asterisk frame interface */ char buf[VPB_MAX_BUF]; /*!< Static buffer for reading frames */ int dialtone; /*!< NOT USED */ float txgain, rxgain; /*!< Hardware gain control */ float txswgain, rxswgain; /*!< Software gain control */ int wantdtmf; /*!< Waiting for DTMF. */ char context[AST_MAX_EXTENSION]; /*!< The context for this channel */ char ext[AST_MAX_EXTENSION]; /*!< DTMF buffer for the ext[ens] */ char language[MAX_LANGUAGE]; /*!< language being used */ char callerid[AST_MAX_EXTENSION]; /*!< CallerId used for directly connected phone */ int callerid_type; /*!< Caller ID type: 0=>none 1=>vpb 2=>AstV23 3=>AstBell */ char cid_num[AST_MAX_EXTENSION]; char cid_name[AST_MAX_EXTENSION]; int dtmf_caller_pos; /*!< DTMF CallerID detection (Brazil)*/ int lastoutput; /*!< Holds the last Audio format output'ed */ int lastinput; /*!< Holds the last Audio format input'ed */ int last_ignore_dtmf; void *busy_timer; /*!< Void pointer for busy vpb_timer */ int busy_timer_id; /*!< unique timer ID for busy timer */ void *ringback_timer; /*!< Void pointer for ringback vpb_timer */ int ringback_timer_id; /*!< unique timer ID for ringback timer */ void *ring_timer; /*!< Void pointer for ring vpb_timer */ int ring_timer_id; /*!< unique timer ID for ring timer */ void *dtmfidd_timer; /*!< Void pointer for DTMF IDD vpb_timer */ int dtmfidd_timer_id; /*!< unique timer ID for DTMF IDD timer */ struct ast_dsp *vad; /*!< AST Voice Activation Detection dsp */ struct timeval lastgrunt; /*!< time stamp of last grunt event */ ast_mutex_t lock; /*!< This one just protects bridge ptr below */ vpb_bridge_t *bridge; int stopreads; /*!< Stop reading...*/ int read_state; /*!< Read state */ int chuck_count; /*!< a count of packets weve chucked away!*/ pthread_t readthread; /*!< For monitoring read channel. One per owned channel. */ ast_mutex_t record_lock; /*!< This one prevents reentering a record_buf block */ ast_mutex_t play_lock; /*!< This one prevents reentering a play_buf block */ int play_buf_time; /*!< How long the last play_buf took */ struct timeval lastplay; /*!< Last play time */ ast_mutex_t play_dtmf_lock; char play_dtmf[16]; int faxhandled; /*!< has a fax tone been handled ? */ struct vpb_pvt *next; /*!< Next channel in list */ } *iflist = NULL; static struct ast_channel *vpb_new(struct vpb_pvt *i, enum ast_channel_state state, const char *context, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor); static void *do_chanreads(void *pvt); static struct ast_channel *vpb_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static int vpb_digit_begin(struct ast_channel *ast, char digit); static int vpb_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int vpb_call(struct ast_channel *ast, const char *dest, int timeout); static int vpb_hangup(struct ast_channel *ast); static int vpb_answer(struct ast_channel *ast); static struct ast_frame *vpb_read(struct ast_channel *ast); static int vpb_write(struct ast_channel *ast, struct ast_frame *frame); static int vpb_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen); static int vpb_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static struct ast_channel_tech vpb_tech = { type: "vpb", description: tdesc, capabilities: NULL, properties: 0, requester: vpb_request, devicestate: NULL, send_digit_begin: vpb_digit_begin, send_digit_end: vpb_digit_end, call: vpb_call, hangup: vpb_hangup, answer: vpb_answer, read: vpb_read, write: vpb_write, send_text: NULL, send_image: NULL, send_html: NULL, exception: NULL, early_bridge: NULL, indicate: vpb_indicate, fixup: vpb_fixup, setoption: NULL, queryoption: NULL, transfer: NULL, write_video: NULL, write_text: NULL, func_channel_read: NULL, func_channel_write: NULL, }; static struct ast_channel_tech vpb_tech_indicate = { type: "vpb", description: tdesc, capabilities: NULL, properties: 0, requester: vpb_request, devicestate: NULL, send_digit_begin: vpb_digit_begin, send_digit_end: vpb_digit_end, call: vpb_call, hangup: vpb_hangup, answer: vpb_answer, read: vpb_read, write: vpb_write, send_text: NULL, send_image: NULL, send_html: NULL, exception: NULL, early_bridge: NULL, indicate: NULL, fixup: vpb_fixup, setoption: NULL, queryoption: NULL, transfer: NULL, write_video: NULL, write_text: NULL, func_channel_read: NULL, func_channel_write: NULL, }; #if defined(VPB_NATIVE_BRIDGING) /* Can't get ast_vpb_bridge() working on v4pci without either a horrible * high pitched feedback noise or bad hiss noise depending on gain settings * Get asterisk to do the bridging */ #define BAD_V4PCI_BRIDGE /* This one enables a half duplex bridge which may be required to prevent high pitched * feedback when getting asterisk to do the bridging and when using certain gain settings. */ /* #define HALF_DUPLEX_BRIDGE */ /* This is the Native bridge code, which Asterisk will try before using its own bridging code */ static enum ast_bridge_result ast_vpb_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms) { struct vpb_pvt *p0 = (struct vpb_pvt *)ast_channel_tech_pvt(c0); struct vpb_pvt *p1 = (struct vpb_pvt *)ast_channel_tech_pvt(c1); int i; int res; struct ast_channel *cs[3]; struct ast_channel *who; struct ast_frame *f; cs[0] = c0; cs[1] = c1; #ifdef BAD_V4PCI_BRIDGE if (p0->vpb_model == vpb_model_v4pci) return AST_BRIDGE_FAILED_NOWARN; #endif if (UseNativeBridge != 1) { return AST_BRIDGE_FAILED_NOWARN; } /* ast_mutex_lock(&p0->lock); ast_mutex_lock(&p1->lock); */ /* Bridge channels, check if we can. I believe we always can, so find a slot.*/ ast_mutex_lock(&bridge_lock); for (i = 0; i < max_bridges; i++) if (!bridges[i].inuse) break; if (i < max_bridges) { bridges[i].inuse = 1; bridges[i].endbridge = 0; bridges[i].flags = flags; bridges[i].rc = rc; bridges[i].fo = fo; bridges[i].c0 = c0; bridges[i].c1 = c1; } ast_mutex_unlock(&bridge_lock); if (i == max_bridges) { ast_log(LOG_WARNING, "%s: vpb_bridge: Failed to bridge %s and %s!\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1)); ast_mutex_unlock(&p0->lock); ast_mutex_unlock(&p1->lock); return AST_BRIDGE_FAILED_NOWARN; } else { /* Set bridge pointers. You don't want to take these locks while holding bridge lock.*/ ast_mutex_lock(&p0->lock); p0->bridge = &bridges[i]; ast_mutex_unlock(&p0->lock); ast_mutex_lock(&p1->lock); p1->bridge = &bridges[i]; ast_mutex_unlock(&p1->lock); ast_verb(2, "%s: vpb_bridge: Bridging call entered with [%s, %s]\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1)); } ast_verb(3, "Native bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1)); #ifdef HALF_DUPLEX_BRIDGE ast_debug(2, "%s: vpb_bridge: Starting half-duplex bridge [%s, %s]\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1)); int dir = 0; memset(p0->buf, 0, sizeof(p0->buf)); memset(p1->buf, 0, sizeof(p1->buf)); vpb_record_buf_start(p0->handle, VPB_ALAW); vpb_record_buf_start(p1->handle, VPB_ALAW); vpb_play_buf_start(p0->handle, VPB_ALAW); vpb_play_buf_start(p1->handle, VPB_ALAW); while (!bridges[i].endbridge) { struct vpb_pvt *from, *to; if (++dir % 2) { from = p0; to = p1; } else { from = p1; to = p0; } vpb_record_buf_sync(from->handle, from->buf, VPB_SAMPLES); vpb_play_buf_sync(to->handle, from->buf, VPB_SAMPLES); } vpb_record_buf_finish(p0->handle); vpb_record_buf_finish(p1->handle); vpb_play_buf_finish(p0->handle); vpb_play_buf_finish(p1->handle); ast_debug(2, "%s: vpb_bridge: Finished half-duplex bridge [%s, %s]\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1)); res = VPB_OK; #else res = vpb_bridge(p0->handle, p1->handle, VPB_BRIDGE_ON); if (res == VPB_OK) { /* pthread_cond_wait(&bridges[i].cond, &bridges[i].lock);*/ /* Wait for condition signal. */ while (!bridges[i].endbridge) { /* Are we really ment to be doing nothing ?!?! */ who = ast_waitfor_n(cs, 2, &timeoutms); if (!who) { if (!timeoutms) { res = AST_BRIDGE_RETRY; break; } ast_debug(1, "%s: vpb_bridge: Empty frame read...\n", p0->dev); /* check for hangup / whentohangup */ if (ast_check_hangup(c0) || ast_check_hangup(c1)) break; continue; } f = ast_read(who); if (!f || ((f->frametype == AST_FRAME_DTMF) && (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) || ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) { *fo = f; *rc = who; ast_debug(1, "%s: vpb_bridge: Got a [%s]\n", p0->dev, f ? "digit" : "hangup"); #if 0 if ((c0->tech_pvt == pvt0) && (!ast_check_hangup(c0))) { if (pr0->set_rtp_peer(c0, NULL, NULL, 0)) ast_log(LOG_WARNING, "Channel '%s' failed to revert\n", c0->name); } if ((c1->tech_pvt == pvt1) && (!ast_check_hangup(c1))) { if (pr1->set_rtp_peer(c1, NULL, NULL, 0)) ast_log(LOG_WARNING, "Channel '%s' failed to revert back\n", c1->name); } /* That's all we needed */ return 0; #endif /* Check if we need to break */ if (break_for_dtmf) { break; } else if ((f->frametype == AST_FRAME_DTMF) && ((f->subclass.integer == '#') || (f->subclass.integer == '*'))) { break; } } else { if ((f->frametype == AST_FRAME_DTMF) || (f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_VIDEO)) { /* Forward voice or DTMF frames if they happen upon us */ /* Actually I dont think we want to forward on any frames! if (who == c0) { ast_write(c1, f); } else if (who == c1) { ast_write(c0, f); } */ } ast_frfree(f); } /* Swap priority not that it's a big deal at this point */ cs[2] = cs[0]; cs[0] = cs[1]; cs[1] = cs[2]; }; vpb_bridge(p0->handle, p1->handle, VPB_BRIDGE_OFF); } #endif ast_mutex_lock(&bridge_lock); bridges[i].inuse = 0; ast_mutex_unlock(&bridge_lock); p0->bridge = NULL; p1->bridge = NULL; ast_verb(2, "Bridging call done with [%s, %s] => %d\n", ast_channel_name(c0), ast_channel_name(c1), res); /* ast_mutex_unlock(&p0->lock); ast_mutex_unlock(&p1->lock); */ return (res == VPB_OK) ? AST_BRIDGE_COMPLETE : AST_BRIDGE_FAILED; } #endif /* defined(VPB_NATIVE_BRIDGING) */ /* Caller ID can be located in different positions between the rings depending on your Telco * Australian (Telstra) callerid starts 700ms after 1st ring and finishes 1.5s after first ring * Use ANALYSE_CID to record rings and determine location of callerid */ /* #define ANALYSE_CID */ #define RING_SKIP 300 #define CID_MSECS 2000 static void get_callerid(struct vpb_pvt *p) { short buf[CID_MSECS*8]; /* 8kHz sampling rate */ struct timeval cid_record_time; int rc; struct ast_channel *owner = p->owner; /* char callerid[AST_MAX_EXTENSION] = ""; */ #ifdef ANALYSE_CID void * ws; char * file="cidsams.wav"; #endif if (ast_mutex_trylock(&p->record_lock) == 0) { cid_record_time = ast_tvnow(); ast_verb(4, "CID record - start\n"); /* Skip any trailing ringtone */ if (UsePolarityCID != 1){ vpb_sleep(RING_SKIP); } ast_verb(4, "CID record - skipped %lldms trailing ring\n", (long long int) ast_tvdiff_ms(ast_tvnow(), cid_record_time)); cid_record_time = ast_tvnow(); /* Record bit between the rings which contains the callerid */ vpb_record_buf_start(p->handle, VPB_LINEAR); rc = vpb_record_buf_sync(p->handle, (char*)buf, sizeof(buf)); vpb_record_buf_finish(p->handle); #ifdef ANALYSE_CID vpb_wave_open_write(&ws, file, VPB_LINEAR); vpb_wave_write(ws, (char *)buf, sizeof(buf)); vpb_wave_close_write(ws); #endif ast_verb(4, "CID record - recorded %lldms between rings\n", (long long int) ast_tvdiff_ms(ast_tvnow(), cid_record_time)); ast_mutex_unlock(&p->record_lock); if (rc != VPB_OK) { ast_log(LOG_ERROR, "Failed to record caller id sample on %s\n", p->dev); return; } VPB_CID *cli_struct = new VPB_CID; cli_struct->ra_cldn[0] = 0; cli_struct->ra_cn[0] = 0; /* This decodes FSK 1200baud type callerid */ if ((rc = vpb_cid_decode2(cli_struct, buf, CID_MSECS * 8)) == VPB_OK ) { /* if (owner->cid.cid_num) ast_free(owner->cid.cid_num); owner->cid.cid_num=NULL; if (owner->cid.cid_name) ast_free(owner->cid.cid_name); owner->cid.cid_name=NULL; */ if (cli_struct->ra_cldn[0] == '\0') { /* owner->cid.cid_num = ast_strdup(cli_struct->cldn); owner->cid.cid_name = ast_strdup(cli_struct->cn); */ if (owner) { ast_set_callerid(owner, cli_struct->cldn, cli_struct->cn, cli_struct->cldn); } else { strcpy(p->cid_num, cli_struct->cldn); strcpy(p->cid_name, cli_struct->cn); } ast_verb(4, "CID record - got [%s] [%s]\n", S_COR(ast_channel_caller(owner)->id.number.valid, ast_channel_caller(owner)->id.number.str, ""), S_COR(ast_channel_caller(owner)->id.name.valid, ast_channel_caller(owner)->id.name.str, "")); snprintf(p->callerid, sizeof(p->callerid), "%s %s", cli_struct->cldn, cli_struct->cn); } else { ast_log(LOG_ERROR, "CID record - No caller id avalable on %s \n", p->dev); } } else { ast_log(LOG_ERROR, "CID record - Failed to decode caller id on %s - %d\n", p->dev, rc); ast_copy_string(p->callerid, "unknown", sizeof(p->callerid)); } delete cli_struct; } else ast_log(LOG_ERROR, "CID record - Failed to set record mode for caller id on %s\n", p->dev); } static void get_callerid_ast(struct vpb_pvt *p) { struct callerid_state *cs; char buf[1024]; char *name = NULL, *number = NULL; int flags; int rc = 0, vrc; int sam_count = 0; struct ast_channel *owner = p->owner; int which_cid; /* float old_gain; */ #ifdef ANALYSE_CID void * ws; char * file = "cidsams.wav"; #endif if (p->callerid_type == 1) { ast_verb(4, "Collected caller ID already\n"); return; } else if (p->callerid_type == 2 ) { which_cid = CID_SIG_V23; ast_verb(4, "Collecting Caller ID v23...\n"); } else if (p->callerid_type == 3) { which_cid = CID_SIG_BELL; ast_verb(4, "Collecting Caller ID bell...\n"); } else { ast_verb(4, "Caller ID disabled\n"); return; } /* vpb_sleep(RING_SKIP); */ /* vpb_record_get_gain(p->handle, &old_gain); */ cs = callerid_new(which_cid); if (cs) { #ifdef ANALYSE_CID vpb_wave_open_write(&ws, file, VPB_MULAW); vpb_record_set_gain(p->handle, 3.0); vpb_record_set_hw_gain(p->handle, 12.0); #endif vpb_record_buf_start(p->handle, VPB_MULAW); while ((rc == 0) && (sam_count < 8000 * 3)) { vrc = vpb_record_buf_sync(p->handle, (char*)buf, sizeof(buf)); if (vrc != VPB_OK) ast_log(LOG_ERROR, "%s: Caller ID couldn't read audio buffer!\n", p->dev); rc = callerid_feed(cs, (unsigned char *)buf, sizeof(buf), ast_format_ulaw); #ifdef ANALYSE_CID vpb_wave_write(ws, (char *)buf, sizeof(buf)); #endif sam_count += sizeof(buf); ast_verb(4, "Collecting Caller ID samples [%d][%d]...\n", sam_count, rc); } vpb_record_buf_finish(p->handle); #ifdef ANALYSE_CID vpb_wave_close_write(ws); #endif if (rc == 1) { callerid_get(cs, &name, &number, &flags); ast_debug(1, "%s: Caller ID name [%s] number [%s] flags [%d]\n", p->dev, name, number, flags); } else { ast_log(LOG_ERROR, "%s: Failed to decode Caller ID \n", p->dev); } /* vpb_record_set_gain(p->handle, old_gain); */ /* vpb_record_set_hw_gain(p->handle,6.0); */ } else { ast_log(LOG_ERROR, "%s: Failed to create Caller ID struct\n", p->dev); } ast_party_number_free(&ast_channel_caller(owner)->id.number); ast_party_number_init(&ast_channel_caller(owner)->id.number); ast_party_name_free(&ast_channel_caller(owner)->id.name); ast_party_name_init(&ast_channel_caller(owner)->id.name); if (number) ast_shrink_phone_number(number); ast_set_callerid(owner, number, name, ast_channel_caller(owner)->ani.number.valid ? NULL : number); if (!ast_strlen_zero(name)){ snprintf(p->callerid, sizeof(p->callerid), "%s %s", number, name); } else { ast_copy_string(p->callerid, number, sizeof(p->callerid)); } if (cs) callerid_free(cs); } /* Terminate any tones we are presently playing */ static void stoptone(int handle) { int ret; VPB_EVENT je; while (vpb_playtone_state(handle) != VPB_OK) { vpb_tone_terminate(handle); ret = vpb_get_event_ch_async(handle, &je); if ((ret == VPB_OK) && (je.type != VPB_DIALEND)) { ast_verb(4, "Stop tone collected a wrong event!![%d]\n", je.type); /* vpb_put_event(&je); */ } vpb_sleep(10); } } /* Safe vpb_playtone_async */ static int playtone( int handle, VPB_TONE *tone) { int ret = VPB_OK; stoptone(handle); ast_verb(4, "[%02d]: Playing tone\n", handle); ret = vpb_playtone_async(handle, tone); return ret; } static inline int monitor_handle_owned(struct vpb_pvt *p, VPB_EVENT *e) { struct ast_frame f = {AST_FRAME_CONTROL}; /* default is control, Clear rest. */ int endbridge = 0; ast_verb(4, "%s: handle_owned: got event: [%d=>%d]\n", p->dev, e->type, e->data); f.src = "vpb"; switch (e->type) { case VPB_RING: if (p->mode == MODE_FXO) { f.subclass.integer = AST_CONTROL_RING; vpb_timer_stop(p->ring_timer); vpb_timer_start(p->ring_timer); } else f.frametype = AST_FRAME_NULL; /* ignore ring on station port. */ break; case VPB_RING_OFF: f.frametype = AST_FRAME_NULL; break; case VPB_TIMEREXP: if (e->data == p->busy_timer_id) { playtone(p->handle, &Busytone); p->state = VPB_STATE_PLAYBUSY; vpb_timer_stop(p->busy_timer); vpb_timer_start(p->busy_timer); f.frametype = AST_FRAME_NULL; } else if (e->data == p->ringback_timer_id) { playtone(p->handle, &Ringbacktone); vpb_timer_stop(p->ringback_timer); vpb_timer_start(p->ringback_timer); f.frametype = AST_FRAME_NULL; } else if (e->data == p->ring_timer_id) { /* We didnt get another ring in time! */ if (ast_channel_state(p->owner) != AST_STATE_UP) { /* Assume caller has hung up */ vpb_timer_stop(p->ring_timer); f.subclass.integer = AST_CONTROL_HANGUP; } else { vpb_timer_stop(p->ring_timer); f.frametype = AST_FRAME_NULL; } } else { f.frametype = AST_FRAME_NULL; /* Ignore. */ } break; case VPB_DTMF_DOWN: case VPB_DTMF: if (use_ast_dtmfdet) { f.frametype = AST_FRAME_NULL; } else if (ast_channel_state(p->owner) == AST_STATE_UP) { f.frametype = AST_FRAME_DTMF; f.subclass.integer = e->data; } else f.frametype = AST_FRAME_NULL; break; case VPB_TONEDETECT: if (e->data == VPB_BUSY || e->data == VPB_BUSY_308 || e->data == VPB_BUSY_AUST ) { ast_debug(4, "%s: handle_owned: got event: BUSY\n", p->dev); if (ast_channel_state(p->owner) == AST_STATE_UP) { f.subclass.integer = AST_CONTROL_HANGUP; } else { f.subclass.integer = AST_CONTROL_BUSY; } } else if (e->data == VPB_FAX) { if (!p->faxhandled) { if (strcmp(ast_channel_exten(p->owner), "fax")) { const char *target_context = S_OR(ast_channel_macrocontext(p->owner), ast_channel_context(p->owner)); if (ast_exists_extension(p->owner, target_context, "fax", 1, S_COR(ast_channel_caller(p->owner)->id.number.valid, ast_channel_caller(p->owner)->id.number.str, NULL))) { ast_verb(3, "Redirecting %s to fax extension\n", ast_channel_name(p->owner)); /* Save the DID/DNIS when we transfer the fax call to a "fax" extension */ pbx_builtin_setvar_helper(p->owner, "FAXEXTEN", ast_channel_exten(p->owner)); if (ast_async_goto(p->owner, target_context, "fax", 1)) { ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(p->owner), target_context); } } else { ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n"); } } else { ast_debug(1, "Already in a fax extension, not redirecting\n"); } } else { ast_debug(1, "Fax already handled\n"); } } else if (e->data == VPB_GRUNT) { if (ast_tvdiff_ms(ast_tvnow(), p->lastgrunt) > gruntdetect_timeout) { /* Nothing heard on line for a very long time * Timeout connection */ ast_verb(3, "grunt timeout\n"); ast_log(LOG_NOTICE, "%s: Line hangup due of lack of conversation\n", p->dev); f.subclass.integer = AST_CONTROL_HANGUP; } else { p->lastgrunt = ast_tvnow(); f.frametype = AST_FRAME_NULL; } } else { f.frametype = AST_FRAME_NULL; } break; case VPB_CALLEND: #ifdef DIAL_WITH_CALL_PROGRESS if (e->data == VPB_CALL_CONNECTED) { f.subclass.integer = AST_CONTROL_ANSWER; } else if (e->data == VPB_CALL_NO_DIAL_TONE || e->data == VPB_CALL_NO_RING_BACK) { f.subclass.integer = AST_CONTROL_CONGESTION; } else if (e->data == VPB_CALL_NO_ANSWER || e->data == VPB_CALL_BUSY) { f.subclass.integer = AST_CONTROL_BUSY; } else if (e->data == VPB_CALL_DISCONNECTED) { f.subclass.integer = AST_CONTROL_HANGUP; } #else ast_log(LOG_NOTICE, "%s: Got call progress callback but blind dialing \n", p->dev); f.frametype = AST_FRAME_NULL; #endif break; case VPB_STATION_OFFHOOK: f.subclass.integer = AST_CONTROL_ANSWER; break; case VPB_DROP: if ((p->mode == MODE_FXO) && (UseLoopDrop)) { /* ignore loop drop on stations */ if (ast_channel_state(p->owner) == AST_STATE_UP) { f.subclass.integer = AST_CONTROL_HANGUP; } else { f.frametype = AST_FRAME_NULL; } } break; case VPB_LOOP_ONHOOK: if (ast_channel_state(p->owner) == AST_STATE_UP) { f.subclass.integer = AST_CONTROL_HANGUP; } else { f.frametype = AST_FRAME_NULL; } break; case VPB_STATION_ONHOOK: f.subclass.integer = AST_CONTROL_HANGUP; break; case VPB_STATION_FLASH: f.subclass.integer = AST_CONTROL_FLASH; break; /* Called when dialing has finished and ringing starts * No indication that call has really been answered when using blind dialing */ case VPB_DIALEND: if (p->state < 5) { f.subclass.integer = AST_CONTROL_ANSWER; ast_verb(2, "%s: Dialend\n", p->dev); } else { f.frametype = AST_FRAME_NULL; } break; /* case VPB_PLAY_UNDERFLOW: f.frametype = AST_FRAME_NULL; vpb_reset_play_fifo_alarm(p->handle); break; case VPB_RECORD_OVERFLOW: f.frametype = AST_FRAME_NULL; vpb_reset_record_fifo_alarm(p->handle); break; */ default: f.frametype = AST_FRAME_NULL; break; } /* ast_verb(4, "%s: LOCKING in handle_owned [%d]\n", p->dev,res); res = ast_mutex_lock(&p->lock); ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); */ if (p->bridge) { /* Check what happened, see if we need to report it. */ switch (f.frametype) { case AST_FRAME_DTMF: if ( !(p->bridge->c0 == p->owner && (p->bridge->flags & AST_BRIDGE_DTMF_CHANNEL_0) ) && !(p->bridge->c1 == p->owner && (p->bridge->flags & AST_BRIDGE_DTMF_CHANNEL_1) )) { /* Kill bridge, this is interesting. */ endbridge = 1; } break; case AST_FRAME_CONTROL: endbridge = 1; break; default: break; } if (endbridge) { if (p->bridge->fo) { *p->bridge->fo = ast_frisolate(&f); } if (p->bridge->rc) { *p->bridge->rc = p->owner; } ast_mutex_lock(&p->bridge->lock); p->bridge->endbridge = 1; ast_cond_signal(&p->bridge->cond); ast_mutex_unlock(&p->bridge->lock); } } if (endbridge) { ast_mutex_unlock(&p->lock); /* ast_verb(4, "%s: unLOCKING in handle_owned [%d]\n", p->dev,res); */ return 0; } ast_verb(4, "%s: handle_owned: Prepared frame type[%d]subclass[%d], bridge=%p owner=[%s]\n", p->dev, f.frametype, f.subclass.integer, (void *)p->bridge, ast_channel_name(p->owner)); /* Trylock used here to avoid deadlock that can occur if we * happen to be in here handling an event when hangup is called * Problem is that hangup holds p->owner->lock */ if ((f.frametype >= 0) && (f.frametype != AST_FRAME_NULL) && (p->owner)) { if (ast_channel_trylock(p->owner) == 0) { ast_queue_frame(p->owner, &f); ast_channel_unlock(p->owner); ast_verb(4, "%s: handled_owned: Queued Frame to [%s]\n", p->dev, ast_channel_name(p->owner)); } else { ast_verbose("%s: handled_owned: Missed event %d/%d \n", p->dev, f.frametype, f.subclass.integer); } } ast_mutex_unlock(&p->lock); /* ast_verb(4, "%s: unLOCKING in handle_owned [%d]\n", p->dev,res); */ return 0; } static inline int monitor_handle_notowned(struct vpb_pvt *p, VPB_EVENT *e) { char s[2] = {0}; struct ast_channel *owner = p->owner; char cid_num[256]; char cid_name[256]; /* struct ast_channel *c; */ char str[VPB_MAX_STR]; vpb_translate_event(e, str); ast_verb(4, "%s: handle_notowned: mode=%d, event[%d][%s]=[%d]\n", p->dev, p->mode, e->type,str, e->data); switch (e->type) { case VPB_LOOP_ONHOOK: case VPB_LOOP_POLARITY: if (UsePolarityCID == 1) { ast_verb(4, "Polarity reversal\n"); if (p->callerid_type == 1) { ast_verb(4, "Using VPB Caller ID\n"); get_callerid(p); /* UK CID before 1st ring*/ } /* get_callerid_ast(p); */ /* Caller ID using the ast functions */ } break; case VPB_RING: if (p->mode == MODE_FXO) /* FXO port ring, start * */ { vpb_new(p, AST_STATE_RING, p->context, NULL, NULL); if (UsePolarityCID != 1) { if (p->callerid_type == 1) { ast_verb(4, "Using VPB Caller ID\n"); get_callerid(p); /* Australian CID only between 1st and 2nd ring */ } get_callerid_ast(p); /* Caller ID using the ast functions */ } else { ast_log(LOG_ERROR, "Setting caller ID: %s %s\n", p->cid_num, p->cid_name); ast_set_callerid(p->owner, p->cid_num, p->cid_name, p->cid_num); p->cid_num[0] = 0; p->cid_name[0] = 0; } vpb_timer_stop(p->ring_timer); vpb_timer_start(p->ring_timer); } break; case VPB_RING_OFF: break; case VPB_STATION_OFFHOOK: if (p->mode == MODE_IMMEDIATE) { vpb_new(p,AST_STATE_RING, p->context, NULL, NULL); } else { ast_verb(4, "%s: handle_notowned: playing dialtone\n", p->dev); playtone(p->handle, &Dialtone); p->state = VPB_STATE_PLAYDIAL; p->wantdtmf = 1; p->ext[0] = 0; /* Just to be sure & paranoid.*/ } break; case VPB_DIALEND: if (p->mode == MODE_DIALTONE) { if (p->state == VPB_STATE_PLAYDIAL) { playtone(p->handle, &Dialtone); p->wantdtmf = 1; p->ext[0] = 0; /* Just to be sure & paranoid. */ } #if 0 /* These are not needed as they have timers to restart them */ else if (p->state == VPB_STATE_PLAYBUSY) { playtone(p->handle, &Busytone); p->wantdtmf = 1; p->ext[0] = 0; } else if (p->state == VPB_STATE_PLAYRING) { playtone(p->handle, &Ringbacktone); p->wantdtmf = 1; p->ext[0] = 0; } #endif } else { ast_verb(4, "%s: handle_notowned: Got a DIALEND when not really expected\n",p->dev); } break; case VPB_STATION_ONHOOK: /* clear ext */ stoptone(p->handle); p->wantdtmf = 1 ; p->ext[0] = 0; p->state = VPB_STATE_ONHOOK; break; case VPB_TIMEREXP: if (e->data == p->dtmfidd_timer_id) { if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)){ ast_verb(4, "%s: handle_notowned: DTMF IDD timer out, matching on [%s] in [%s]\n", p->dev, p->ext, p->context); vpb_new(p, AST_STATE_RING, p->context, NULL, NULL); } } else if (e->data == p->ring_timer_id) { /* We didnt get another ring in time! */ if (p->owner) { if (ast_channel_state(p->owner) != AST_STATE_UP) { /* Assume caller has hung up */ vpb_timer_stop(p->ring_timer); } } else { /* No owner any more, Assume caller has hung up */ vpb_timer_stop(p->ring_timer); } } break; case VPB_DTMF: if (p->state == VPB_STATE_ONHOOK){ /* DTMF's being passed while on-hook maybe Caller ID */ if (p->mode == MODE_FXO) { if (e->data == DTMF_CID_START) { /* CallerID Start signal */ p->dtmf_caller_pos = 0; /* Leaves the first digit out */ memset(p->callerid, 0, sizeof(p->callerid)); } else if (e->data == DTMF_CID_STOP) { /* CallerID End signal */ p->callerid[p->dtmf_caller_pos] = '\0'; ast_verb(3, " %s: DTMF CallerID %s\n", p->dev, p->callerid); if (owner) { /* if (owner->cid.cid_num) ast_free(owner->cid.cid_num); owner->cid.cid_num=NULL; if (owner->cid.cid_name) ast_free(owner->cid.cid_name); owner->cid.cid_name=NULL; owner->cid.cid_num = strdup(p->callerid); */ cid_name[0] = '\0'; cid_num[0] = '\0'; ast_callerid_split(p->callerid, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); ast_set_callerid(owner, cid_num, cid_name, cid_num); } else { ast_verb(3, " %s: DTMF CallerID: no owner to assign CID \n", p->dev); } } else if (p->dtmf_caller_pos < AST_MAX_EXTENSION) { if (p->dtmf_caller_pos >= 0) { p->callerid[p->dtmf_caller_pos] = e->data; } p->dtmf_caller_pos++; } } break; } if (p->wantdtmf == 1) { stoptone(p->handle); p->wantdtmf = 0; } p->state = VPB_STATE_GETDTMF; s[0] = e->data; strncat(p->ext, s, sizeof(p->ext) - strlen(p->ext) - 1); #if 0 if (!strcmp(p->ext, ast_pickup_ext())) { /* Call pickup has been dialled! */ if (ast_pickup_call(c)) { /* Call pickup wasnt possible */ } } else #endif if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)) { if (ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)) { ast_verb(4, "%s: handle_notowned: Multiple matches on [%s] in [%s]\n", p->dev, p->ext, p->context); /* Start DTMF IDD timer */ vpb_timer_stop(p->dtmfidd_timer); vpb_timer_start(p->dtmfidd_timer); } else { ast_verb(4, "%s: handle_notowned: Matched on [%s] in [%s]\n", p->dev, p->ext , p->context); vpb_new(p, AST_STATE_UP, p->context, NULL, NULL); } } else if (!ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)) { if (ast_exists_extension(NULL, "default", p->ext, 1, p->callerid)) { vpb_new(p, AST_STATE_UP, "default", NULL, NULL); } else if (!ast_canmatch_extension(NULL, "default", p->ext, 1, p->callerid)) { ast_verb(4, "%s: handle_notowned: can't match anything in %s or default\n", p->dev, p->context); playtone(p->handle, &Busytone); vpb_timer_stop(p->busy_timer); vpb_timer_start(p->busy_timer); p->state = VPB_STATE_PLAYBUSY; } } break; default: /* Ignore.*/ break; } ast_verb(4, "%s: handle_notowned: mode=%d, [%d=>%d]\n", p->dev, p->mode, e->type, e->data); return 0; } static void *do_monitor(void *unused) { /* Monitor thread, doesn't die until explicitly killed. */ ast_verb(2, "Starting vpb monitor thread[%ld]\n", pthread_self()); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); for (;;) { VPB_EVENT e; VPB_EVENT je; char str[VPB_MAX_STR]; struct vpb_pvt *p; /* ast_verb(4, "Monitor waiting for event\n"); */ int res = vpb_get_event_sync(&e, VPB_WAIT_TIMEOUT); if ((res == VPB_NO_EVENTS) || (res == VPB_TIME_OUT)) { /* if (res == VPB_NO_EVENTS) { ast_verb(4, "No events....\n"); } else { ast_verb(4, "No events, timed out....\n"); } */ continue; } if (res != VPB_OK) { ast_log(LOG_ERROR,"Monitor get event error %d\n", res ); ast_verbose("Monitor get event error %d\n", res ); continue; } str[0] = 0; p = NULL; ast_mutex_lock(&monlock); if (e.type == VPB_NULL_EVENT) { ast_verb(4, "Monitor got null event\n"); } else { vpb_translate_event(&e, str); if (*str && *(str + 1)) { str[strlen(str) - 1] = '\0'; } ast_mutex_lock(&iflock); for (p = iflist; p && p->handle != e.handle; p = p->next); ast_mutex_unlock(&iflock); if (p) { ast_verb(4, "%s: Event [%d=>%s]\n", p ? p->dev : "null", e.type, str); } } ast_mutex_unlock(&monlock); if (!p) { if (e.type != VPB_NULL_EVENT) { ast_log(LOG_WARNING, "Got event [%s][%d], no matching iface!\n", str, e.type); ast_verb(4, "vpb/ERR: No interface for Event [%d=>%s] \n", e.type, str); } continue; } /* flush the event from the channel event Q */ vpb_get_event_ch_async(e.handle, &je); vpb_translate_event(&je, str); ast_verb(5, "%s: Flushing event [%d]=>%s\n", p->dev, je.type, str); /* Check for ownership and locks */ if ((p->owner) && (!p->golock)) { /* Need to get owner lock */ /* Safely grab both p->lock and p->owner->lock so that there cannot be a race with something from the other side */ /* ast_mutex_lock(&p->lock); while (ast_mutex_trylock(&p->owner->lock)) { ast_mutex_unlock(&p->lock); usleep(1); ast_mutex_lock(&p->lock); if (!p->owner) break; } if (p->owner) p->golock = 1; */ } /* Two scenarios: Are you owned or not. */ if (p->owner) { monitor_handle_owned(p, &e); } else { monitor_handle_notowned(p, &e); } /* if ((!p->owner)&&(p->golock)) { ast_mutex_unlock(&p->owner->lock); ast_mutex_unlock(&p->lock); } */ } return NULL; } static int restart_monitor(void) { int error = 0; /* If we're supposed to be stopped -- stay stopped */ if (mthreadactive == -2) return 0; ast_verb(4, "Restarting monitor\n"); ast_mutex_lock(&monlock); if (monitor_thread == pthread_self()) { ast_log(LOG_WARNING, "Cannot kill myself\n"); error = -1; ast_verb(4, "Monitor trying to kill monitor\n"); } else { if (mthreadactive != -1) { /* Why do other drivers kill the thread? No need says I, simply awake thread with event. */ VPB_EVENT e; e.handle = 0; e.type = VPB_EVT_NONE; e.data = 0; ast_verb(4, "Trying to reawake monitor\n"); vpb_put_event(&e); } else { /* Start a new monitor */ int pid = ast_pthread_create(&monitor_thread, NULL, do_monitor, NULL); ast_verb(4, "Created new monitor thread %d\n", pid); if (pid < 0) { ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); error = -1; } else { mthreadactive = 0; /* Started the thread!*/ } } } ast_mutex_unlock(&monlock); ast_verb(4, "Monitor restarted\n"); return error; } /* Per board config that must be called after vpb_open() */ static void mkbrd(vpb_model_t model, int echo_cancel) { if (!bridges) { if (model == vpb_model_v4pci) { max_bridges = MAX_BRIDGES_V4PCI; } bridges = (vpb_bridge_t *)ast_calloc(1, max_bridges * sizeof(vpb_bridge_t)); if (!bridges) { ast_log(LOG_ERROR, "Failed to initialize bridges\n"); } else { int i; for (i = 0; i < max_bridges; i++) { ast_mutex_init(&bridges[i].lock); ast_cond_init(&bridges[i].cond, NULL); } } } if (!echo_cancel) { if (model == vpb_model_v4pci) { vpb_echo_canc_disable(); ast_log(LOG_NOTICE, "Voicetronix echo cancellation OFF\n"); } else { /* need to do it port by port for OpenSwitch */ } } else { if (model == vpb_model_v4pci) { vpb_echo_canc_enable(); ast_log(LOG_NOTICE, "Voicetronix echo cancellation ON\n"); if (ec_supp_threshold > -1) { vpb_echo_canc_set_sup_thresh(0, &ec_supp_threshold); ast_log(LOG_NOTICE, "Voicetronix EC Sup Thres set\n"); } } else { /* need to do it port by port for OpenSwitch */ } } } static struct vpb_pvt *mkif(int board, int channel, int mode, int gains, float txgain, float rxgain, float txswgain, float rxswgain, int bal1, int bal2, int bal3, char * callerid, int echo_cancel, int group, ast_group_t callgroup, ast_group_t pickupgroup ) { struct vpb_pvt *tmp; char buf[64]; tmp = (vpb_pvt *)ast_calloc(1, sizeof(*tmp)); if (!tmp) return NULL; tmp->handle = vpb_open(board, channel); if (tmp->handle < 0) { ast_log(LOG_WARNING, "Unable to create channel vpb/%d-%d: %s\n", board, channel, strerror(errno)); ast_free(tmp); return NULL; } snprintf(tmp->dev, sizeof(tmp->dev), "vpb/%d-%d", board, channel); tmp->mode = mode; tmp->group = group; tmp->callgroup = callgroup; tmp->pickupgroup = pickupgroup; /* Initialize dtmf caller ID position variable */ tmp->dtmf_caller_pos = 0; ast_copy_string(tmp->language, language, sizeof(tmp->language)); ast_copy_string(tmp->context, context, sizeof(tmp->context)); tmp->callerid_type = 0; if (callerid) { if (strcasecmp(callerid, "on") == 0) { tmp->callerid_type = 1; ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid)); } else if (strcasecmp(callerid, "v23") == 0) { tmp->callerid_type = 2; ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid)); } else if (strcasecmp(callerid, "bell") == 0) { tmp->callerid_type = 3; ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid)); } else { ast_copy_string(tmp->callerid, callerid, sizeof(tmp->callerid)); } } else { ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid)); } /* check if codec balances have been set in the config file */ if (bal3 >= 0) { if ((bal1>=0) && !(bal1 & 32)) bal1 |= 32; vpb_set_codec_reg(tmp->handle, 0x42, bal3); } if (bal1 >= 0) { vpb_set_codec_reg(tmp->handle, 0x32, bal1); } if (bal2 >= 0) { vpb_set_codec_reg(tmp->handle, 0x3a, bal2); } if (gains & VPB_GOT_TXHWG) { if (txgain > MAX_VPB_GAIN) { tmp->txgain = MAX_VPB_GAIN; } else if (txgain < MIN_VPB_GAIN) { tmp->txgain = MIN_VPB_GAIN; } else { tmp->txgain = txgain; } ast_log(LOG_NOTICE, "VPB setting Tx Hw gain to [%f]\n", tmp->txgain); vpb_play_set_hw_gain(tmp->handle, tmp->txgain); } if (gains & VPB_GOT_RXHWG) { if (rxgain > MAX_VPB_GAIN) { tmp->rxgain = MAX_VPB_GAIN; } else if (rxgain < MIN_VPB_GAIN) { tmp->rxgain = MIN_VPB_GAIN; } else { tmp->rxgain = rxgain; } ast_log(LOG_NOTICE, "VPB setting Rx Hw gain to [%f]\n", tmp->rxgain); vpb_record_set_hw_gain(tmp->handle, tmp->rxgain); } if (gains & VPB_GOT_TXSWG) { tmp->txswgain = txswgain; ast_log(LOG_NOTICE, "VPB setting Tx Sw gain to [%f]\n", tmp->txswgain); vpb_play_set_gain(tmp->handle, tmp->txswgain); } if (gains & VPB_GOT_RXSWG) { tmp->rxswgain = rxswgain; ast_log(LOG_NOTICE, "VPB setting Rx Sw gain to [%f]\n", tmp->rxswgain); vpb_record_set_gain(tmp->handle, tmp->rxswgain); } tmp->vpb_model = vpb_model_unknown; if (vpb_get_model(tmp->handle, buf) == VPB_OK) { if (strcmp(buf, "V12PCI") == 0) { tmp->vpb_model = vpb_model_v12pci; } else if (strcmp(buf, "VPB4") == 0) { tmp->vpb_model = vpb_model_v4pci; } } ast_mutex_init(&tmp->owner_lock); ast_mutex_init(&tmp->lock); ast_mutex_init(&tmp->record_lock); ast_mutex_init(&tmp->play_lock); ast_mutex_init(&tmp->play_dtmf_lock); /* set default read state */ tmp->read_state = 0; tmp->golock = 0; tmp->busy_timer_id = vpb_timer_get_unique_timer_id(); vpb_timer_open(&tmp->busy_timer, tmp->handle, tmp->busy_timer_id, TIMER_PERIOD_BUSY); tmp->ringback_timer_id = vpb_timer_get_unique_timer_id(); vpb_timer_open(&tmp->ringback_timer, tmp->handle, tmp->ringback_timer_id, TIMER_PERIOD_RINGBACK); tmp->ring_timer_id = vpb_timer_get_unique_timer_id(); vpb_timer_open(&tmp->ring_timer, tmp->handle, tmp->ring_timer_id, timer_period_ring); tmp->dtmfidd_timer_id = vpb_timer_get_unique_timer_id(); vpb_timer_open(&tmp->dtmfidd_timer, tmp->handle, tmp->dtmfidd_timer_id, dtmf_idd); if (mode == MODE_FXO){ if (use_ast_dtmfdet) vpb_set_event_mask(tmp->handle, VPB_EVENTS_NODTMF); else vpb_set_event_mask(tmp->handle, VPB_EVENTS_ALL); } else { /* if (use_ast_dtmfdet) vpb_set_event_mask(tmp->handle, VPB_EVENTS_NODTMF); else */ vpb_set_event_mask(tmp->handle, VPB_EVENTS_STAT); } if ((tmp->vpb_model == vpb_model_v12pci) && (echo_cancel)) { vpb_hostecho_on(tmp->handle); } if (use_ast_dtmfdet) { tmp->vad = ast_dsp_new(); ast_dsp_set_features(tmp->vad, DSP_FEATURE_DIGIT_DETECT); ast_dsp_set_digitmode(tmp->vad, DSP_DIGITMODE_DTMF); if (relaxdtmf) ast_dsp_set_digitmode(tmp->vad, DSP_DIGITMODE_DTMF|DSP_DIGITMODE_RELAXDTMF); } else { tmp->vad = NULL; } /* define grunt tone */ vpb_settonedet(tmp->handle,&toned_ungrunt); ast_log(LOG_NOTICE,"Voicetronix %s channel %s initialized (rxsg=%f/txsg=%f/rxhg=%f/txhg=%f)(0x%x/0x%x/0x%x)\n", (tmp->vpb_model == vpb_model_v4pci) ? "V4PCI" : (tmp->vpb_model == vpb_model_v12pci) ? "V12PCI" : "[Unknown model]", tmp->dev, tmp->rxswgain, tmp->txswgain, tmp->rxgain, tmp->txgain, bal1, bal2, bal3); return tmp; } static int vpb_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); int res = 0; if (use_ast_ind == 1) { ast_verb(4, "%s: vpb_indicate called when using Ast Indications !?!\n", p->dev); return 0; } ast_verb(4, "%s: vpb_indicate [%d] state[%d]\n", p->dev, condition,ast_channel_state(ast)); /* if (ast->_state != AST_STATE_UP) { ast_verb(4, "%s: vpb_indicate Not in AST_STATE_UP\n", p->dev, condition,ast->_state); return res; } */ /* ast_verb(4, "%s: LOCKING in indicate \n", p->dev); ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count, p->lock.__m_owner); */ ast_mutex_lock(&p->lock); switch (condition) { case AST_CONTROL_BUSY: case AST_CONTROL_CONGESTION: if (ast_channel_state(ast) == AST_STATE_UP) { playtone(p->handle, &Busytone); p->state = VPB_STATE_PLAYBUSY; vpb_timer_stop(p->busy_timer); vpb_timer_start(p->busy_timer); } break; case AST_CONTROL_RINGING: if (ast_channel_state(ast) == AST_STATE_UP) { playtone(p->handle, &Ringbacktone); p->state = VPB_STATE_PLAYRING; ast_verb(4, "%s: vpb indicate: setting ringback timer [%d]\n", p->dev,p->ringback_timer_id); vpb_timer_stop(p->ringback_timer); vpb_timer_start(p->ringback_timer); } break; case AST_CONTROL_ANSWER: case -1: /* -1 means stop playing? */ vpb_timer_stop(p->ringback_timer); vpb_timer_stop(p->busy_timer); stoptone(p->handle); break; case AST_CONTROL_HANGUP: if (ast_channel_state(ast) == AST_STATE_UP) { playtone(p->handle, &Busytone); p->state = VPB_STATE_PLAYBUSY; vpb_timer_stop(p->busy_timer); vpb_timer_start(p->busy_timer); } break; case AST_CONTROL_HOLD: ast_moh_start(ast, (const char *) data, NULL); break; case AST_CONTROL_UNHOLD: ast_moh_stop(ast); break; case AST_CONTROL_PVT_CAUSE_CODE: res = -1; break; default: res = 0; break; } ast_mutex_unlock(&p->lock); return res; } static int vpb_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(newchan); /* ast_verb(4, "%s: LOCKING in fixup \n", p->dev); ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); */ ast_mutex_lock(&p->lock); ast_debug(1, "New owner for channel %s is %s\n", p->dev, ast_channel_name(newchan)); if (p->owner == oldchan) { p->owner = newchan; } if (ast_channel_state(newchan) == AST_STATE_RINGING){ if (use_ast_ind == 1) { ast_verb(4, "%s: vpb_fixup Calling ast_indicate\n", p->dev); ast_indicate(newchan, AST_CONTROL_RINGING); } else { ast_verb(4, "%s: vpb_fixup Calling vpb_indicate\n", p->dev); vpb_indicate(newchan, AST_CONTROL_RINGING, NULL, 0); } } ast_mutex_unlock(&p->lock); return 0; } static int vpb_digit_begin(struct ast_channel *ast, char digit) { /* XXX Modify this callback to let Asterisk control the length of DTMF */ return 0; } static int vpb_digit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); char s[2]; if (use_ast_dtmf) { ast_verb(4, "%s: vpb_digit: asked to play digit[%c] but we are using asterisk dtmf play back?!\n", p->dev, digit); return 0; } /* ast_verb(4, "%s: LOCKING in digit \n", p->dev); ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); */ ast_mutex_lock(&p->lock); s[0] = digit; s[1] = '\0'; ast_verb(4, "%s: vpb_digit: asked to play digit[%s]\n", p->dev, s); ast_mutex_lock(&p->play_dtmf_lock); strncat(p->play_dtmf, s, sizeof(*p->play_dtmf) - strlen(p->play_dtmf) - 1); ast_mutex_unlock(&p->play_dtmf_lock); ast_mutex_unlock(&p->lock); return 0; } /* Places a call out of a VPB channel */ static int vpb_call(struct ast_channel *ast, const char *dest, int timeout) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); int res = 0, i; const char *s = strrchr(dest, '/'); char dialstring[254] = ""; /* ast_verb(4, "%s: LOCKING in call \n", p->dev); ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); */ ast_mutex_lock(&p->lock); ast_verb(4, "%s: starting call to [%s]\n", p->dev, dest); if (s) s = s + 1; else s = dest; ast_copy_string(dialstring, s, sizeof(dialstring)); for (i = 0; dialstring[i] != '\0'; i++) { if ((dialstring[i] == 'w') || (dialstring[i] == 'W')) dialstring[i] = ','; else if ((dialstring[i] == 'f') || (dialstring[i] == 'F')) dialstring[i] = '&'; } if (ast_channel_state(ast) != AST_STATE_DOWN && ast_channel_state(ast) != AST_STATE_RESERVED) { ast_log(LOG_WARNING, "vpb_call on %s neither down nor reserved!\n", ast_channel_name(ast)); ast_mutex_unlock(&p->lock); return -1; } if (p->mode != MODE_FXO) /* Station port, ring it. */ vpb_ring_station_async(p->handle, 2); else { VPB_CALL call; int j; /* Dial must timeout or it can leave channels unuseable */ if (timeout == 0) { timeout = TIMER_PERIOD_NOANSWER; } else { timeout = timeout * 1000; /* convert from secs to ms. */ } /* These timeouts are only used with call progress dialing */ call.dialtones = 1; /* Number of dialtones to get outside line */ call.dialtone_timeout = VPB_DIALTONE_WAIT; /* Wait this long for dialtone (ms) */ call.ringback_timeout = VPB_RINGWAIT; /* Wait this long for ringing after dialing (ms) */ call.inter_ringback_timeout = VPB_CONNECTED_WAIT; /* If ringing stops for this long consider it connected (ms) */ call.answer_timeout = timeout; /* Time to wait for answer after ringing starts (ms) */ memcpy(&call.tone_map, DialToneMap, sizeof(DialToneMap)); vpb_set_call(p->handle, &call); ast_verb(2, "%s: Calling %s on %s \n",p->dev, dialstring, ast_channel_name(ast)); ast_verb(2, "%s: Dial parms for %s %d/%dms/%dms/%dms/%dms\n", p->dev, ast_channel_name(ast), call.dialtones, call.dialtone_timeout, call.ringback_timeout, call.inter_ringback_timeout, call.answer_timeout); for (j = 0; !call.tone_map[j].terminate; j++) { ast_verb(2, "%s: Dial parms for %s tone %d->%d\n", p->dev, ast_channel_name(ast), call.tone_map[j].tone_id, call.tone_map[j].call_id); } ast_verb(4, "%s: Disabling Loop Drop detection\n", p->dev); vpb_disable_event(p->handle, VPB_MDROP); vpb_sethook_sync(p->handle, VPB_OFFHOOK); p->state = VPB_STATE_OFFHOOK; #ifndef DIAL_WITH_CALL_PROGRESS vpb_sleep(300); ast_verb(4, "%s: Enabling Loop Drop detection\n", p->dev); vpb_enable_event(p->handle, VPB_MDROP); res = vpb_dial_async(p->handle, dialstring); #else ast_verb(4, "%s: Enabling Loop Drop detection\n", p->dev); vpb_enable_event(p->handle, VPB_MDROP); res = vpb_call_async(p->handle, dialstring); #endif if (res != VPB_OK) { ast_debug(1, "Call on %s to %s failed: %d\n", ast_channel_name(ast), s, res); res = -1; } else res = 0; } ast_verb(3, "%s: VPB Calling %s [t=%d] on %s returned %d\n", p->dev , s, timeout, ast_channel_name(ast), res); if (res == 0) { ast_setstate(ast, AST_STATE_RINGING); ast_queue_control(ast, AST_CONTROL_RINGING); } if (!p->readthread) { ast_pthread_create(&p->readthread, NULL, do_chanreads, (void *)p); } ast_mutex_unlock(&p->lock); return res; } static int vpb_hangup(struct ast_channel *ast) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); VPB_EVENT je; char str[VPB_MAX_STR]; /* ast_verb(4, "%s: LOCKING in hangup \n", p->dev); ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); ast_verb(4, "%s: LOCKING pthread_self(%d)\n", p->dev,pthread_self()); ast_mutex_lock(&p->lock); */ ast_verb(2, "%s: Hangup requested\n", ast_channel_name(ast)); if (!ast_channel_tech(ast) || !ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "%s: channel not connected?\n", ast_channel_name(ast)); ast_mutex_unlock(&p->lock); /* Free up ast dsp if we have one */ if (use_ast_dtmfdet && p->vad) { ast_dsp_free(p->vad); p->vad = NULL; } return 0; } /* Stop record */ p->stopreads = 1; if (p->readthread) { pthread_join(p->readthread, NULL); ast_verb(4, "%s: stopped record thread \n", ast_channel_name(ast)); } /* Stop play */ if (p->lastoutput != -1) { ast_verb(2, "%s: Ending play mode \n", ast_channel_name(ast)); vpb_play_terminate(p->handle); ast_mutex_lock(&p->play_lock); vpb_play_buf_finish(p->handle); ast_mutex_unlock(&p->play_lock); } ast_verb(4, "%s: Setting state down\n", ast_channel_name(ast)); ast_setstate(ast, AST_STATE_DOWN); /* ast_verb(4, "%s: LOCKING in hangup \n", p->dev); ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); ast_verb(4, "%s: LOCKING pthread_self(%d)\n", p->dev,pthread_self()); */ ast_mutex_lock(&p->lock); if (p->mode != MODE_FXO) { /* station port. */ vpb_ring_station_async(p->handle, 0); if (p->state != VPB_STATE_ONHOOK) { /* This is causing a "dial end" "play tone" loop playtone(p->handle, &Busytone); p->state = VPB_STATE_PLAYBUSY; ast_verb(5, "%s: Station offhook[%d], playing busy tone\n", ast->name,p->state); */ } else { stoptone(p->handle); } #ifdef VPB_PRI vpb_setloop_async(p->handle, VPB_OFFHOOK); vpb_sleep(100); vpb_setloop_async(p->handle, VPB_ONHOOK); #endif } else { stoptone(p->handle); /* Terminates any dialing */ vpb_sethook_sync(p->handle, VPB_ONHOOK); p->state=VPB_STATE_ONHOOK; } while (VPB_OK == vpb_get_event_ch_async(p->handle, &je)) { vpb_translate_event(&je, str); ast_verb(4, "%s: Flushing event [%d]=>%s\n", ast_channel_name(ast), je.type, str); } p->readthread = 0; p->lastoutput = -1; p->lastinput = -1; p->last_ignore_dtmf = 1; p->ext[0] = 0; p->dialtone = 0; p->owner = NULL; ast_channel_tech_pvt_set(ast, NULL); /* Free up ast dsp if we have one */ if (use_ast_dtmfdet && p->vad) { ast_dsp_free(p->vad); p->vad = NULL; } ast_verb(2, "%s: Hangup complete\n", ast_channel_name(ast)); restart_monitor(); ast_mutex_unlock(&p->lock); return 0; } static int vpb_answer(struct ast_channel *ast) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); /* VPB_EVENT je; int ret; ast_verb(4, "%s: LOCKING in answer \n", p->dev); ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner); */ ast_mutex_lock(&p->lock); ast_verb(4, "%s: Answering channel\n", p->dev); if (p->mode == MODE_FXO) { ast_verb(4, "%s: Disabling Loop Drop detection\n", p->dev); vpb_disable_event(p->handle, VPB_MDROP); } if (ast_channel_state(ast) != AST_STATE_UP) { if (p->mode == MODE_FXO) { vpb_sethook_sync(p->handle, VPB_OFFHOOK); p->state = VPB_STATE_OFFHOOK; /* vpb_sleep(500); ret = vpb_get_event_ch_async(p->handle, &je); if ((ret == VPB_OK) && ((je.type != VPB_DROP)&&(je.type != VPB_RING))){ ast_verb(4, "%s: Answer collected a wrong event!!\n", p->dev); vpb_put_event(&je); } */ } ast_setstate(ast, AST_STATE_UP); ast_verb(2, "%s: Answered call on %s [%s]\n", p->dev, ast_channel_name(ast), (p->mode == MODE_FXO) ? "FXO" : "FXS"); ast_channel_rings_set(ast, 0); if (!p->readthread) { /* res = ast_mutex_unlock(&p->lock); */ /* ast_verbose("%s: unLOCKING in answer [%d]\n", p->dev,res); */ ast_pthread_create(&p->readthread, NULL, do_chanreads, (void *)p); } else { ast_verb(4, "%s: Record thread already running!!\n", p->dev); } } else { ast_verb(4, "%s: Answered state is up\n", p->dev); /* res = ast_mutex_unlock(&p->lock); */ /* ast_verbose("%s: unLOCKING in answer [%d]\n", p->dev,res); */ } vpb_sleep(500); if (p->mode == MODE_FXO) { ast_verb(4, "%s: Re-enabling Loop Drop detection\n", p->dev); vpb_enable_event(p->handle, VPB_MDROP); } ast_mutex_unlock(&p->lock); return 0; } static struct ast_frame *vpb_read(struct ast_channel *ast) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); static struct ast_frame f = { AST_FRAME_NULL }; f.src = "vpb"; ast_log(LOG_NOTICE, "%s: vpb_read: should never be called!\n", p->dev); ast_verbose("%s: vpb_read: should never be called!\n", p->dev); return &f; } static inline AudioCompress ast2vpbformat(struct ast_format *format) { if (ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { return VPB_ALAW; } else if (ast_format_cmp(format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) { return VPB_LINEAR; } else if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { return VPB_MULAW; } else if (ast_format_cmp(format, ast_format_adpcm) == AST_FORMAT_CMP_EQUAL) { return VPB_OKIADPCM; } else { return VPB_RAW; } } static inline const char * ast2vpbformatname(struct ast_format *format) { if (ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { return "AST_FORMAT_ALAW:VPB_ALAW"; } else if (ast_format_cmp(format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) { return "AST_FORMAT_SLINEAR:VPB_LINEAR"; } else if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { return "AST_FORMAT_ULAW:VPB_MULAW"; } else if (ast_format_cmp(format, ast_format_adpcm) == AST_FORMAT_CMP_EQUAL) { return "AST_FORMAT_ADPCM:VPB_OKIADPCM"; } else { return "UNKN:UNKN"; } } static inline int astformatbits(struct ast_format *format) { if (ast_format_cmp(format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) { return 16; } else if (ast_format_cmp(format, ast_format_adpcm) == AST_FORMAT_CMP_EQUAL) { return 4; } else { return 8; } } int a_gain_vector(float g, short *v, int n) { int i; float tmp; for (i = 0; i < n; i++) { tmp = g * v[i]; if (tmp > 32767.0) tmp = 32767.0; if (tmp < -32768.0) tmp = -32768.0; v[i] = (short)tmp; } return i; } /* Writes a frame of voice data to a VPB channel */ static int vpb_write(struct ast_channel *ast, struct ast_frame *frame) { struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast); int res = 0; AudioCompress fmt = VPB_RAW; struct timeval play_buf_time_start; int tdiff; /* ast_mutex_lock(&p->lock); */ ast_verb(6, "%s: vpb_write: Writing to channel\n", p->dev); if (frame->frametype != AST_FRAME_VOICE) { ast_verb(4, "%s: vpb_write: Don't know how to handle from type %d\n", ast_channel_name(ast), frame->frametype); /* ast_mutex_unlock(&p->lock); */ return 0; } else if (ast_channel_state(ast) != AST_STATE_UP) { ast_verb(4, "%s: vpb_write: Attempt to Write frame type[%d]subclass[%s] on not up chan(state[%d])\n", ast_channel_name(ast), frame->frametype, ast_format_get_name(frame->subclass.format), ast_channel_state(ast)); p->lastoutput = -1; /* ast_mutex_unlock(&p->lock); */ return 0; } /* ast_debug(1, "%s: vpb_write: Checked frame type..\n", p->dev); */ fmt = ast2vpbformat(frame->subclass.format); if (fmt < 0) { ast_log(LOG_WARNING, "%s: vpb_write: Cannot handle frames of %s format!\n", ast_channel_name(ast), ast_format_get_name(frame->subclass.format)); return -1; } tdiff = ast_tvdiff_ms(ast_tvnow(), p->lastplay); ast_debug(1, "%s: vpb_write: time since last play(%d) \n", p->dev, tdiff); if (tdiff < (VPB_SAMPLES / 8 - 1)) { ast_debug(1, "%s: vpb_write: Asked to play too often (%d) (%d)\n", p->dev, tdiff, frame->datalen); /* return 0; */ } p->lastplay = ast_tvnow(); /* ast_debug(1, "%s: vpb_write: Checked frame format..\n", p->dev); */ ast_mutex_lock(&p->play_lock); /* ast_debug(1, "%s: vpb_write: Got play lock..\n", p->dev); */ /* Check if we have set up the play_buf */ if (p->lastoutput == -1) { vpb_play_buf_start(p->handle, fmt); ast_verb(2, "%s: vpb_write: Starting play mode (codec=%d)[%s]\n", p->dev, fmt, ast2vpbformatname(frame->subclass.format)); p->lastoutput = fmt; ast_mutex_unlock(&p->play_lock); return 0; } else if (p->lastoutput != fmt) { vpb_play_buf_finish(p->handle); vpb_play_buf_start(p->handle, fmt); ast_verb(2, "%s: vpb_write: Changed play format (%d=>%d)\n", p->dev, p->lastoutput, fmt); ast_mutex_unlock(&p->play_lock); return 0; } p->lastoutput = fmt; /* Apply extra gain ! */ if( p->txswgain > MAX_VPB_GAIN ) a_gain_vector(p->txswgain - MAX_VPB_GAIN , (short*)frame->data.ptr, frame->datalen / sizeof(short)); /* ast_debug(1, "%s: vpb_write: Applied gain..\n", p->dev); */ /* ast_debug(1, "%s: vpb_write: play_buf_time %d\n", p->dev, p->play_buf_time); */ if ((p->read_state == 1) && (p->play_buf_time < 5)){ play_buf_time_start = ast_tvnow(); /* res = vpb_play_buf_sync(p->handle, (char *)frame->data, tdiff * 8 * 2); */ res = vpb_play_buf_sync(p->handle, (char *)frame->data.ptr, frame->datalen); if(res == VPB_OK) { short * data = (short*)frame->data.ptr; ast_verb(6, "%s: vpb_write: Wrote chan (codec=%d) %d %d\n", p->dev, fmt, data[0], data[1]); } p->play_buf_time = ast_tvdiff_ms(ast_tvnow(), play_buf_time_start); } else { p->chuck_count++; ast_debug(1, "%s: vpb_write: Tossed data away, tooooo much data!![%d]\n", p->dev, p->chuck_count); p->play_buf_time = 0; } ast_mutex_unlock(&p->play_lock); /* ast_mutex_unlock(&p->lock); */ ast_verb(6, "%s: vpb_write: Done Writing to channel\n", p->dev); return 0; } /* Read monitor thread function. */ static void *do_chanreads(void *pvt) { struct vpb_pvt *p = (struct vpb_pvt *)pvt; struct ast_frame *fr = &p->fr; char *readbuf = ((char *)p->buf) + AST_FRIENDLY_OFFSET; int bridgerec = 0; struct ast_format *tmpfmt; int readlen, res, trycnt=0; AudioCompress fmt; int ignore_dtmf; const char * getdtmf_var = NULL; fr->frametype = AST_FRAME_VOICE; fr->src = "vpb"; fr->mallocd = 0; fr->delivery.tv_sec = 0; fr->delivery.tv_usec = 0; fr->samples = VPB_SAMPLES; fr->offset = AST_FRIENDLY_OFFSET; memset(p->buf, 0, sizeof p->buf); ast_verb(3, "%s: chanreads: starting thread\n", p->dev); ast_mutex_lock(&p->record_lock); p->stopreads = 0; p->read_state = 1; while (!p->stopreads && p->owner) { ast_verb(5, "%s: chanreads: Starting cycle ...\n", p->dev); ast_verb(5, "%s: chanreads: Checking bridge \n", p->dev); if (p->bridge) { bridgerec = 0; } else { bridgerec = ast_channel_is_bridged(p->owner) ? 1 : 0; } /* if ((p->owner->_state != AST_STATE_UP) || !bridgerec) */ if ((ast_channel_state(p->owner) != AST_STATE_UP)) { if (ast_channel_state(p->owner) != AST_STATE_UP) { ast_verb(5, "%s: chanreads: Im not up[%d]\n", p->dev, ast_channel_state(p->owner)); } else { ast_verb(5, "%s: chanreads: No bridgerec[%d]\n", p->dev, bridgerec); } vpb_sleep(10); continue; } /* Voicetronix DTMF detection can be triggered off ordinary speech * This leads to annoying beeps during the conversation * Avoid this problem by just setting VPB_GETDTMF when you want to listen for DTMF */ /* ignore_dtmf = 1; */ ignore_dtmf = 0; /* set this to 1 to turn this feature on */ getdtmf_var = pbx_builtin_getvar_helper(p->owner, "VPB_GETDTMF"); if (getdtmf_var && strcasecmp(getdtmf_var, "yes") == 0) ignore_dtmf = 0; if ((ignore_dtmf != p->last_ignore_dtmf) && (!use_ast_dtmfdet)){ ast_verb(2, "%s:Now %s DTMF \n", p->dev, ignore_dtmf ? "ignoring" : "listening for"); vpb_set_event_mask(p->handle, ignore_dtmf ? VPB_EVENTS_NODTMF : VPB_EVENTS_ALL ); } p->last_ignore_dtmf = ignore_dtmf; /* Play DTMF digits here to avoid problem you get if playing a digit during * a record operation */ ast_verb(6, "%s: chanreads: Checking dtmf's \n", p->dev); ast_mutex_lock(&p->play_dtmf_lock); if (p->play_dtmf[0]) { /* Try to ignore DTMF event we get after playing digit */ /* This DTMF is played by asterisk and leads to an annoying trailing beep on CISCO phones */ if (!ignore_dtmf) { vpb_set_event_mask(p->handle, VPB_EVENTS_NODTMF ); } if (p->bridge == NULL) { vpb_dial_sync(p->handle, p->play_dtmf); ast_verb(2, "%s: chanreads: Played DTMF %s\n", p->dev, p->play_dtmf); } else { ast_verb(2, "%s: chanreads: Not playing DTMF frame on native bridge\n", p->dev); } p->play_dtmf[0] = '\0'; ast_mutex_unlock(&p->play_dtmf_lock); vpb_sleep(700); /* Long enough to miss echo and DTMF event */ if( !ignore_dtmf) vpb_set_event_mask(p->handle, VPB_EVENTS_ALL); continue; } ast_mutex_unlock(&p->play_dtmf_lock); if (p->owner) { tmpfmt = ast_channel_rawreadformat(p->owner); } else { tmpfmt = ast_format_slin; } fmt = ast2vpbformat(tmpfmt); if (fmt < 0) { ast_log(LOG_WARNING, "%s: Record failure (unsupported format %s)\n", p->dev, ast_format_get_name(tmpfmt)); return NULL; } readlen = VPB_SAMPLES * astformatbits(tmpfmt) / 8; if (p->lastinput == -1) { vpb_record_buf_start(p->handle, fmt); /* vpb_reset_record_fifo_alarm(p->handle); */ p->lastinput = fmt; ast_verb(2, "%s: Starting record mode (codec=%d)[%s]\n", p->dev, fmt, ast2vpbformatname(tmpfmt)); continue; } else if (p->lastinput != fmt) { vpb_record_buf_finish(p->handle); vpb_record_buf_start(p->handle, fmt); p->lastinput = fmt; ast_verb(2, "%s: Changed record format (%d=>%d)\n", p->dev, p->lastinput, fmt); continue; } /* Read only if up and not bridged, or a bridge for which we can read. */ ast_verb(6, "%s: chanreads: getting buffer!\n", p->dev); if( (res = vpb_record_buf_sync(p->handle, readbuf, readlen) ) == VPB_OK ) { ast_verb(6, "%s: chanreads: got buffer!\n", p->dev); /* Apply extra gain ! */ if( p->rxswgain > MAX_VPB_GAIN ) a_gain_vector(p->rxswgain - MAX_VPB_GAIN, (short *)readbuf, readlen / sizeof(short)); ast_verb(6, "%s: chanreads: applied gain\n", p->dev); fr->subclass.format = tmpfmt; fr->data.ptr = readbuf; fr->datalen = readlen; fr->frametype = AST_FRAME_VOICE; if ((use_ast_dtmfdet) && (p->vad)) { fr = ast_dsp_process(p->owner, p->vad, fr); if (fr && (fr->frametype == AST_FRAME_DTMF)) { ast_debug(1, "%s: chanreads: Detected DTMF '%c'\n", p->dev, fr->subclass.integer); } else if (fr->subclass.integer == 'f') { } } /* Using trylock here to prevent deadlock when channel is hungup * (ast_hangup() immediately gets lock) */ if (p->owner && !p->stopreads) { ast_verb(6, "%s: chanreads: queueing buffer on read frame q (state[%d])\n", p->dev, ast_channel_state(p->owner)); do { res = ast_channel_trylock(p->owner); trycnt++; } while ((res !=0 ) && (trycnt < 300)); if (res == 0) { ast_queue_frame(p->owner, fr); ast_channel_unlock(p->owner); } else { ast_verb(5, "%s: chanreads: Couldnt get lock after %d tries!\n", p->dev, trycnt); } trycnt = 0; /* res = ast_mutex_trylock(&p->owner->lock); if (res == 0) { ast_queue_frame(p->owner, fr); ast_mutex_unlock(&p->owner->lock); } else { if (res == EINVAL) ast_verb(5, "%s: chanreads: try owner->lock gave me EINVAL[%d]\n", p->dev, res); else if (res == EBUSY) ast_verb(5, "%s: chanreads: try owner->lock gave me EBUSY[%d]\n", p->dev, res); while (res != 0) { res = ast_mutex_trylock(&p->owner->lock); } if (res == 0) { ast_queue_frame(p->owner, fr); ast_mutex_unlock(&p->owner->lock); } else { if (res == EINVAL) { ast_verb(5, "%s: chanreads: try owner->lock gave me EINVAL[%d]\n", p->dev, res); } else if (res == EBUSY) { ast_verb(5, "%s: chanreads: try owner->lock gave me EBUSY[%d]\n", p->dev, res); } ast_verb(5, "%s: chanreads: Couldnt get lock on owner[%s][%d][%d] channel to send frame!\n", p->dev, p->owner->name, (int)p->owner->lock.__m_owner, (int)p->owner->lock.__m_count); } } */ short *data = (short *)readbuf; ast_verb(7, "%s: Read channel (codec=%d) %d %d\n", p->dev, fmt, data[0], data[1]); } else { ast_verb(5, "%s: p->stopreads[%d] p->owner[%p]\n", p->dev, p->stopreads, (void *)p->owner); } } ast_verb(5, "%s: chanreads: Finished cycle...\n", p->dev); } p->read_state = 0; /* When stopreads seen, go away! */ vpb_record_buf_finish(p->handle); p->read_state = 0; ast_mutex_unlock(&p->record_lock); ast_verb(2, "%s: Ending record mode (%d/%s)\n", p->dev, p->stopreads, p->owner ? "yes" : "no"); return NULL; } static struct ast_channel *vpb_new(struct vpb_pvt *me, enum ast_channel_state state, const char *context, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_channel *tmp; char cid_num[256]; char cid_name[256]; if (me->owner) { ast_log(LOG_WARNING, "Called vpb_new on owned channel (%s) ?!\n", me->dev); return NULL; } ast_verb(4, "%s: New call for context [%s]\n", me->dev, context); tmp = ast_channel_alloc(1, state, 0, 0, "", me->ext, me->context, assignedids, requestor, AST_AMA_NONE, "%s", me->dev); if (tmp) { if (use_ast_ind == 1){ ast_channel_tech_set(tmp, &vpb_tech_indicate); } else { ast_channel_tech_set(tmp, &vpb_tech); } ast_channel_callgroup_set(tmp, me->callgroup); ast_channel_pickupgroup_set(tmp, me->pickupgroup); /* Linear is the preferred format. Although Voicetronix supports other formats * they are all converted to/from linear in the vpb code. Best for us to use * linear since we can then adjust volume in this modules. */ ast_channel_nativeformats_set(tmp, vpb_tech.capabilities); ast_channel_set_rawreadformat(tmp, ast_format_slin); ast_channel_set_rawwriteformat(tmp, ast_format_slin); if (state == AST_STATE_RING) { ast_channel_rings_set(tmp, 1); cid_name[0] = '\0'; cid_num[0] = '\0'; ast_callerid_split(me->callerid, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num)); ast_set_callerid(tmp, cid_num, cid_name, cid_num); } ast_channel_tech_pvt_set(tmp, me); ast_channel_context_set(tmp, context); if (!ast_strlen_zero(me->ext)) ast_channel_exten_set(tmp, me->ext); else ast_channel_exten_set(tmp, "s"); if (!ast_strlen_zero(me->language)) ast_channel_language_set(tmp, me->language); ast_channel_unlock(tmp); me->owner = tmp; me->bridge = NULL; me->lastoutput = -1; me->lastinput = -1; me->last_ignore_dtmf = 1; me->readthread = 0; me->play_dtmf[0] = '\0'; me->faxhandled = 0; me->lastgrunt = ast_tvnow(); /* Assume at least one grunt tone seen now. */ me->lastplay = ast_tvnow(); /* Assume at least one grunt tone seen now. */ if (state != AST_STATE_DOWN) { if ((me->mode != MODE_FXO) && (state != AST_STATE_UP)) { vpb_answer(tmp); } if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); } } } else { ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); } return tmp; } static struct ast_channel *vpb_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause) { struct vpb_pvt *p; struct ast_channel *tmp = NULL; char *sepstr, *name; const char *s; int group = -1; if (!(ast_format_cap_iscompatible_format(cap, ast_format_slin))) { struct ast_str *buf; buf = ast_str_create(256); if (!buf) { return NULL; } ast_log(LOG_NOTICE, "Asked to create a channel for unsupported formats: %s\n", ast_format_cap_get_names(cap, &buf)); ast_free(buf); return NULL; } name = ast_strdup(S_OR(data, "")); sepstr = name; s = strsep(&sepstr, "/"); /* Handle / issues */ if (!s) s = ""; /* Check if we are looking for a group */ if (toupper(name[0]) == 'G' || toupper(name[0]) == 'R') { group = atoi(name + 1); } /* Search for an unowned channel */ ast_mutex_lock(&iflock); for (p = iflist; p; p = p->next) { if (group == -1) { if (strncmp(s, p->dev + 4, sizeof p->dev) == 0) { if (!p->owner) { tmp = vpb_new(p, AST_STATE_DOWN, p->context, assignedids, requestor); break; } } } else { if ((p->group == group) && (!p->owner)) { tmp = vpb_new(p, AST_STATE_DOWN, p->context, assignedids, requestor); break; } } } ast_mutex_unlock(&iflock); ast_verb(2, " %s requested, got: [%s]\n", name, tmp ? ast_channel_name(tmp) : "None"); ast_free(name); restart_monitor(); return tmp; } static float parse_gain_value(const char *gain_type, const char *value) { float gain; /* try to scan number */ if (sscanf(value, "%f", &gain) != 1) { ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n", value, gain_type, config); return DEFAULT_GAIN; } /* percentage? */ /*if (value[strlen(value) - 1] == '%') */ /* return gain / (float)100; */ return gain; } static int unload_module(void) { struct vpb_pvt *p; /* First, take us out of the channel loop */ if (use_ast_ind == 1){ ast_channel_unregister(&vpb_tech_indicate); } else { ast_channel_unregister(&vpb_tech); } ast_mutex_lock(&iflock); /* Hangup all interfaces if they have an owner */ for (p = iflist; p; p = p->next) { if (p->owner) ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD); } iflist = NULL; ast_mutex_unlock(&iflock); ast_mutex_lock(&monlock); if (mthreadactive > -1) { pthread_cancel(monitor_thread); pthread_join(monitor_thread, NULL); } mthreadactive = -2; ast_mutex_unlock(&monlock); ast_mutex_lock(&iflock); /* Destroy all the interfaces and free their memory */ while (iflist) { p = iflist; ast_mutex_destroy(&p->lock); pthread_cancel(p->readthread); ast_mutex_destroy(&p->owner_lock); ast_mutex_destroy(&p->record_lock); ast_mutex_destroy(&p->play_lock); ast_mutex_destroy(&p->play_dtmf_lock); p->readthread = 0; vpb_close(p->handle); iflist = iflist->next; ast_free(p); } iflist = NULL; ast_mutex_unlock(&iflock); if (bridges) { ast_mutex_lock(&bridge_lock); memset(bridges, 0, sizeof bridges); ast_mutex_unlock(&bridge_lock); ast_mutex_destroy(&bridge_lock); for (int i = 0; i < max_bridges; i++) { ast_mutex_destroy(&bridges[i].lock); ast_cond_destroy(&bridges[i].cond); } ast_free(bridges); } ao2_cleanup(vpb_tech.capabilities); vpb_tech.capabilities = NULL; ao2_cleanup(vpb_tech_indicate.capabilities); vpb_tech_indicate.capabilities = NULL; return 0; } /*! * \brief Load the module * * Module loading including tests for configuration or dependencies. * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE, * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the * configuration file or other non-critical problem return * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS. */ static enum ast_module_load_result load_module() { struct ast_config *cfg; struct ast_variable *v; struct ast_flags config_flags = { 0 }; struct vpb_pvt *tmp; int board = 0, group = 0; ast_group_t callgroup = 0; ast_group_t pickupgroup = 0; int mode = MODE_IMMEDIATE; float txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN; float txswgain = 0, rxswgain = 0; int got_gain=0; int first_channel = 1; int echo_cancel = DEFAULT_ECHO_CANCEL; enum ast_module_load_result error = AST_MODULE_LOAD_SUCCESS; /* Error flag */ int bal1 = -1; /* Special value - means do not set */ int bal2 = -1; int bal3 = -1; char * callerid = NULL; int num_cards = 0; vpb_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!vpb_tech.capabilities) { return AST_MODULE_LOAD_DECLINE; } vpb_tech_indicate.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!vpb_tech_indicate.capabilities) { return AST_MODULE_LOAD_DECLINE; } ast_format_cap_append(vpb_tech.capabilities, ast_format_slin, 0); ast_format_cap_append(vpb_tech_indicate.capabilities, ast_format_slin, 0); try { num_cards = vpb_get_num_cards(); } catch (std::exception e) { ast_log(LOG_ERROR, "No Voicetronix cards detected\n"); return AST_MODULE_LOAD_DECLINE; } int ports_per_card[num_cards]; for (int i = 0; i < num_cards; ++i) ports_per_card[i] = vpb_get_ports_per_card(i); cfg = ast_config_load(config, config_flags); /* We *must* have a config file otherwise stop immediately */ if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Unable to load config %s\n", config); return AST_MODULE_LOAD_DECLINE; } ast_mutex_lock(&iflock); v = ast_variable_browse(cfg, "general"); while (v){ if (strcasecmp(v->name, "cards") == 0) { ast_log(LOG_NOTICE, "VPB Driver configured to use [%d] cards\n", atoi(v->value)); } else if (strcasecmp(v->name, "indication") == 0) { use_ast_ind = 1; ast_log(LOG_NOTICE, "VPB driver using Asterisk Indication functions!\n"); } else if (strcasecmp(v->name, "break-for-dtmf") == 0) { if (ast_true(v->value)) { break_for_dtmf = 1; } else { break_for_dtmf = 0; ast_log(LOG_NOTICE, "VPB driver not stopping for DTMF's in native bridge\n"); } } else if (strcasecmp(v->name, "ast-dtmf") == 0) { use_ast_dtmf = 1; ast_log(LOG_NOTICE, "VPB driver using Asterisk DTMF play functions!\n"); } else if (strcasecmp(v->name, "ast-dtmf-det") == 0) { use_ast_dtmfdet = 1; ast_log(LOG_NOTICE, "VPB driver using Asterisk DTMF detection functions!\n"); } else if (strcasecmp(v->name, "relaxdtmf") == 0) { relaxdtmf = 1; ast_log(LOG_NOTICE, "VPB driver using Relaxed DTMF with Asterisk DTMF detections functions!\n"); } else if (strcasecmp(v->name, "timer_period_ring") == 0) { timer_period_ring = atoi(v->value); } else if (strcasecmp(v->name, "ecsuppthres") == 0) { ec_supp_threshold = (short)atoi(v->value); } else if (strcasecmp(v->name, "dtmfidd") == 0) { dtmf_idd = atoi(v->value); ast_log(LOG_NOTICE, "VPB Driver setting DTMF IDD to [%d]ms\n", dtmf_idd); } v = v->next; } v = ast_variable_browse(cfg, "interfaces"); while (v) { /* Create the interface list */ if (strcasecmp(v->name, "board") == 0) { board = atoi(v->value); } else if (strcasecmp(v->name, "group") == 0) { group = atoi(v->value); } else if (strcasecmp(v->name, "callgroup") == 0) { callgroup = ast_get_group(v->value); } else if (strcasecmp(v->name, "pickupgroup") == 0) { pickupgroup = ast_get_group(v->value); } else if (strcasecmp(v->name, "usepolaritycid") == 0) { UsePolarityCID = atoi(v->value); } else if (strcasecmp(v->name, "useloopdrop") == 0) { UseLoopDrop = atoi(v->value); } else if (strcasecmp(v->name, "usenativebridge") == 0) { UseNativeBridge = atoi(v->value); } else if (strcasecmp(v->name, "channel") == 0) { int channel = atoi(v->value); if (board >= num_cards || board < 0 || channel < 0 || channel >= ports_per_card[board]) { ast_log(LOG_ERROR, "Invalid board/channel (%d/%d) for channel '%s'\n", board, channel, v->value); error = AST_MODULE_LOAD_FAILURE; goto done; } tmp = mkif(board, channel, mode, got_gain, txgain, rxgain, txswgain, rxswgain, bal1, bal2, bal3, callerid, echo_cancel,group,callgroup,pickupgroup); if (tmp) { if (first_channel) { mkbrd(tmp->vpb_model, echo_cancel); first_channel = 0; } tmp->next = iflist; iflist = tmp; } else { ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value); error = AST_MODULE_LOAD_FAILURE; goto done; } } else if (strcasecmp(v->name, "language") == 0) { ast_copy_string(language, v->value, sizeof(language)); } else if (strcasecmp(v->name, "callerid") == 0) { callerid = ast_strdup(v->value); } else if (strcasecmp(v->name, "mode") == 0) { if (strncasecmp(v->value, "di", 2) == 0) { mode = MODE_DIALTONE; } else if (strncasecmp(v->value, "im", 2) == 0) { mode = MODE_IMMEDIATE; } else if (strncasecmp(v->value, "fx", 2) == 0) { mode = MODE_FXO; } else { ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value); } } else if (!strcasecmp(v->name, "context")) { ast_copy_string(context, v->value, sizeof(context)); } else if (!strcasecmp(v->name, "echocancel")) { if (!strcasecmp(v->value, "off")) { echo_cancel = 0; } } else if (strcasecmp(v->name, "txgain") == 0) { txswgain = parse_gain_value(v->name, v->value); got_gain |= VPB_GOT_TXSWG; } else if (strcasecmp(v->name, "rxgain") == 0) { rxswgain = parse_gain_value(v->name, v->value); got_gain |= VPB_GOT_RXSWG; } else if (strcasecmp(v->name, "txhwgain") == 0) { txgain = parse_gain_value(v->name, v->value); got_gain |= VPB_GOT_TXHWG; } else if (strcasecmp(v->name, "rxhwgain") == 0) { rxgain = parse_gain_value(v->name, v->value); got_gain |= VPB_GOT_RXHWG; } else if (strcasecmp(v->name, "bal1") == 0) { bal1 = strtol(v->value, NULL, 16); if (bal1 < 0 || bal1 > 255) { ast_log(LOG_WARNING, "Bad bal1 value: %d\n", bal1); bal1 = -1; } } else if (strcasecmp(v->name, "bal2") == 0) { bal2 = strtol(v->value, NULL, 16); if (bal2 < 0 || bal2 > 255) { ast_log(LOG_WARNING, "Bad bal2 value: %d\n", bal2); bal2 = -1; } } else if (strcasecmp(v->name, "bal3") == 0) { bal3 = strtol(v->value, NULL, 16); if (bal3 < 0 || bal3 > 255) { ast_log(LOG_WARNING, "Bad bal3 value: %d\n", bal3); bal3 = -1; } } else if (strcasecmp(v->name, "grunttimeout") == 0) { gruntdetect_timeout = 1000 * atoi(v->value); } v = v->next; } if (gruntdetect_timeout < 1000) gruntdetect_timeout = 1000; done: (void)0; ast_mutex_unlock(&iflock); ast_config_destroy(cfg); if (use_ast_ind == 1) { if (error == AST_MODULE_LOAD_SUCCESS && ast_channel_register(&vpb_tech_indicate) != 0) { ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n"); error = AST_MODULE_LOAD_FAILURE; } else { ast_log(LOG_NOTICE, "VPB driver Registered (w/AstIndication)\n"); } } else { if (error == AST_MODULE_LOAD_SUCCESS && ast_channel_register(&vpb_tech) != 0) { ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n"); error = AST_MODULE_LOAD_FAILURE; } else { ast_log(LOG_NOTICE, "VPB driver Registered )\n"); } } if (error != AST_MODULE_LOAD_SUCCESS) unload_module(); else restart_monitor(); /* And start the monitor for the first time */ return error; } /**/ #if defined(__cplusplus) || defined(c_plusplus) } #endif /**/ AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Voicetronix API driver"); asterisk-13.1.0/channels/vgrabbers.c0000644000000000000000000002211511766660300016041 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright 2007, Luigi Rizzo * * 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. */ /* * Video grabbers used in console_video. * * $Revision: 369013 $ * * Each grabber is implemented through open/read/close calls, * plus an additional move() function used e.g. to change origin * for the X grabber (this may be extended in the future to support * more controls e.g. resolution changes etc.). * * open() should try to open and initialize the grabber, returning NULL on error. * On success it allocates a descriptor for its private data (including * a buffer for the video) and returns a pointer to the descriptor. * read() will return NULL on failure, or a pointer to a buffer with data * on success. * close() should release resources. * move() is optional. * For more details look at the X11 grabber below. * * NOTE: at the moment we expect uncompressed video frames in YUV format, * because this is what current sources supply and also is a convenient * format for display. It is conceivable that one might want to support * an already compressed stream, in which case we should redesign the * pipeline used for the local source, which at the moment is * * .->--[loc_dpy] * [src]-->--[enc_in]--+ * `->--[enc_out] */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 369013 $") #include #include "asterisk/file.h" #include "asterisk/utils.h" /* ast_calloc */ #include "console_video.h" #if defined(HAVE_VIDEO_CONSOLE) #ifdef HAVE_X11 /* A simple X11 grabber, supporting only truecolor formats */ #include /*! \brief internal info used by the X11 grabber */ struct grab_x11_desc { Display *dpy; XImage *image; int screen_width; /* width of X screen */ int screen_height; /* height of X screen */ struct fbuf_t b; /* geometry and pointer into the XImage */ }; static void *grab_x11_close(void *desc); /* forward declaration */ /*! \brief open the grabber. * We use the special name 'X11' to indicate this grabber. */ static void *grab_x11_open(const char *name, struct fbuf_t *geom, int fps) { XImage *im; int screen_num; struct grab_x11_desc *v; struct fbuf_t *b; /* all names starting with X11 identify this grabber */ if (strncasecmp(name, "X11", 3)) return NULL; /* not us */ v = ast_calloc(1, sizeof(*v)); if (v == NULL) return NULL; /* no memory */ /* init the connection with the X server */ v->dpy = XOpenDisplay(NULL); if (v->dpy == NULL) { ast_log(LOG_WARNING, "error opening display\n"); goto error; } v->b = *geom; /* copy geometry */ b = &v->b; /* shorthand */ /* find width and height of the screen */ screen_num = DefaultScreen(v->dpy); v->screen_width = DisplayWidth(v->dpy, screen_num); v->screen_height = DisplayHeight(v->dpy, screen_num); v->image = im = XGetImage(v->dpy, RootWindow(v->dpy, DefaultScreen(v->dpy)), b->x, b->y, b->w, b->h, AllPlanes, ZPixmap); if (v->image == NULL) { ast_log(LOG_WARNING, "error creating Ximage\n"); goto error; } switch (im->bits_per_pixel) { case 32: b->pix_fmt = PIX_FMT_RGBA32; break; case 16: b->pix_fmt = (im->green_mask == 0x7e0) ? PIX_FMT_RGB565 : PIX_FMT_RGB555; break; } ast_log(LOG_NOTICE, "image: data %p %d bpp fmt %d, mask 0x%lx 0x%lx 0x%lx\n", im->data, im->bits_per_pixel, b->pix_fmt, im->red_mask, im->green_mask, im->blue_mask); /* set the pointer but not the size as this is not malloc'ed */ b->data = (uint8_t *)im->data; return v; error: return grab_x11_close(v); } static struct fbuf_t *grab_x11_read(void *desc) { /* read frame from X11 */ struct grab_x11_desc *v = desc; struct fbuf_t *b = &v->b; XGetSubImage(v->dpy, RootWindow(v->dpy, DefaultScreen(v->dpy)), b->x, b->y, b->w, b->h, AllPlanes, ZPixmap, v->image, 0, 0); b->data = (uint8_t *)v->image->data; return b; } static int boundary_checks(int x, int limit) { return (x <= 0) ? 0 : (x > limit ? limit : x); } /*! \brief move the origin for the grabbed area, making sure we do not * overflow the screen. */ static void grab_x11_move(void *desc, int dx, int dy) { struct grab_x11_desc *v = desc; v->b.x = boundary_checks(v->b.x + dx, v->screen_width - v->b.w); v->b.y = boundary_checks(v->b.y + dy, v->screen_height - v->b.h); } /*! \brief disconnect from the server and release memory */ static void *grab_x11_close(void *desc) { struct grab_x11_desc *v = desc; if (v->dpy) XCloseDisplay(v->dpy); v->dpy = NULL; v->image = NULL; ast_free(v); return NULL; } static struct grab_desc grab_x11_desc = { .name = "X11", .open = grab_x11_open, .read = grab_x11_read, .move = grab_x11_move, .close = grab_x11_close, }; #endif /* HAVE_X11 */ #ifdef HAVE_VIDEODEV_H #include /* Video4Linux stuff is only used in grab_v4l1_open() */ struct grab_v4l1_desc { int fd; /* device handle */ struct fbuf_t b; /* buffer (allocated) with grabbed image */ }; /*! \brief * Open the local video source and allocate a buffer * for storing the image. */ static void *grab_v4l1_open(const char *dev, struct fbuf_t *geom, int fps) { struct video_window vw = { 0 }; /* camera attributes */ struct video_picture vp; int fd, i; struct grab_v4l1_desc *v; struct fbuf_t *b; /* name should be something under /dev/ */ if (strncmp(dev, "/dev/", 5)) return NULL; fd = open(dev, O_RDONLY | O_NONBLOCK); if (fd < 0) { ast_log(LOG_WARNING, "error opening camera %s\n", dev); return NULL; } v = ast_calloc(1, sizeof(*v)); if (v == NULL) { ast_log(LOG_WARNING, "no memory for camera %s\n", dev); close(fd); return NULL; /* no memory */ } v->fd = fd; v->b = *geom; b = &v->b; /* shorthand */ i = fcntl(fd, F_GETFL); if (-1 == fcntl(fd, F_SETFL, i | O_NONBLOCK)) { /* non fatal, just emit a warning */ ast_log(LOG_WARNING, "error F_SETFL for %s [%s]\n", dev, strerror(errno)); } /* set format for the camera. * In principle we could retry with a different format if the * one we are asking for is not supported. */ vw.width = b->w; vw.height = b->h; vw.flags = fps << 16; if (ioctl(fd, VIDIOCSWIN, &vw) == -1) { ast_log(LOG_WARNING, "error setting format for %s [%s]\n", dev, strerror(errno)); goto error; } if (ioctl(fd, VIDIOCGPICT, &vp) == -1) { ast_log(LOG_WARNING, "error reading picture info\n"); goto error; } ast_log(LOG_WARNING, "contrast %d bright %d colour %d hue %d white %d palette %d\n", vp.contrast, vp.brightness, vp.colour, vp.hue, vp.whiteness, vp.palette); /* set the video format. Here again, we don't necessary have to * fail if the required format is not supported, but try to use * what the camera gives us. */ b->pix_fmt = vp.palette; vp.palette = VIDEO_PALETTE_YUV420P; if (ioctl(v->fd, VIDIOCSPICT, &vp) == -1) { ast_log(LOG_WARNING, "error setting palette, using %d\n", b->pix_fmt); } else b->pix_fmt = vp.palette; /* allocate the source buffer. * XXX, the code here only handles yuv411, for other formats * we need to look at pix_fmt and set size accordingly */ b->size = (b->w * b->h * 3)/2; /* yuv411 */ ast_log(LOG_WARNING, "videodev %s opened, size %dx%d %d\n", dev, b->w, b->h, b->size); b->data = ast_calloc(1, b->size); if (!b->data) { ast_log(LOG_WARNING, "error allocating buffer %d bytes\n", b->size); goto error; } ast_log(LOG_WARNING, "success opening camera\n"); return v; error: close(v->fd); fbuf_free(b); ast_free(v); return NULL; } /*! \brief read until error, no data or buffer full. * This might be blocking but no big deal since we are in the * display thread. */ static struct fbuf_t *grab_v4l1_read(void *desc) { struct grab_v4l1_desc *v = desc; struct fbuf_t *b = &v->b; for (;;) { int r, l = b->size - b->used; r = read(v->fd, b->data + b->used, l); // ast_log(LOG_WARNING, "read %d of %d bytes from webcam\n", r, l); if (r < 0) /* read error */ break; if (r == 0) /* no data */ break; b->used += r; if (r == l) { b->used = 0; /* prepare for next frame */ return b; } } return NULL; } static void *grab_v4l1_close(void *desc) { struct grab_v4l1_desc *v = desc; close(v->fd); v->fd = -1; fbuf_free(&v->b); ast_free(v); return NULL; } /*! \brief our descriptor. We don't have .move */ static struct grab_desc grab_v4l1_desc = { .name = "v4l1", .open = grab_v4l1_open, .read = grab_v4l1_read, .close = grab_v4l1_close, }; #endif /* HAVE_VIDEODEV_H */ /* * Here you can add more grabbers, e.g. V4L2, firewire, * a file, a still image... */ /*! \brief The list of grabbers supported, with a NULL at the end */ struct grab_desc *console_grabbers[] = { #ifdef HAVE_X11 &grab_x11_desc, #endif #ifdef HAVE_VIDEODEV_H &grab_v4l1_desc, #endif NULL }; #endif /* HAVE_VIDEO_CONSOLE */ asterisk-13.1.0/channels/chan_unistim.c0000644000000000000000000064704412424353027016560 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * UNISTIM channel driver for asterisk * * Copyright (C) 2005 - 2007, Cedric Hans * * Cedric Hans * * Asterisk 1.4 patch by Peter Be * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * * \brief chan_unistim channel driver for Asterisk * \author Cedric Hans * * Unistim (Unified Networks IP Stimulus) channel driver * for Nortel i2002, i2004 and i2050 * * \ingroup channel_drivers */ /*** MODULEINFO extended ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 426668 $") #include #include #if defined(__CYGWIN__) /* * cygwin headers are partly inconsistent. struct iovec is defined in sys/uio.h * which is not included by default by sys/socket.h - in_pktinfo is defined in * w32api/ws2tcpip.h but this probably has compatibility problems with sys/socket.h * So for the time being we simply disable HAVE_PKTINFO when building under cygwin. * This should be done in some common header, but for now this is the only file * using iovec and in_pktinfo so it suffices to apply the fix here. */ #ifdef HAVE_PKTINFO #undef HAVE_PKTINFO #endif #endif /* __CYGWIN__ */ #include "asterisk/paths.h" /* ast_config_AST_LOG_DIR used in (too ?) many places */ #include "asterisk/network.h" #include "asterisk/channel.h" #include "asterisk/config.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/rtp_engine.h" #include "asterisk/netsock2.h" #include "asterisk/acl.h" #include "asterisk/callerid.h" #include "asterisk/cli.h" #include "asterisk/app.h" #include "asterisk/musiconhold.h" #include "asterisk/causes.h" #include "asterisk/indications.h" #include "asterisk/pickup.h" #include "asterisk/astobj2.h" #include "asterisk/astdb.h" #include "asterisk/features_config.h" #include "asterisk/bridge.h" #include "asterisk/stasis_channels.h" #include "asterisk/format_cache.h" #define DEFAULTCONTEXT "default" #define DEFAULTCALLERID "Unknown" #define DEFAULTCALLERNAME " " #define DEFAULTHEIGHT 3 #define USTM_LOG_DIR "unistimHistory" #define USTM_LANG_DIR "unistimLang" /*! Size of the transmit buffer */ #define MAX_BUF_SIZE 64 /*! Number of slots for the transmit queue */ #define MAX_BUF_NUMBER 150 /*! Number of digits displayed on screen */ #define MAX_SCREEN_NUMBER 15 /*! Length of month label size */ #define MONTH_LABEL_SIZE 3 /*! Try x times before removing the phone */ #define NB_MAX_RETRANSMIT 8 /*! Nb of milliseconds waited when no events are scheduled */ #define IDLE_WAIT 1000 /*! Wait x milliseconds before resending a packet */ #define RETRANSMIT_TIMER 2000 /*! How often the mailbox is checked for new messages */ #define TIMER_MWI 5000 /*! Timeout value for entered number being dialed */ #define DEFAULT_INTERDIGIT_TIMER 4000 /*! Not used */ #define DEFAULT_CODEC 0x00 #define SIZE_PAGE 4096 #define DEVICE_NAME_LEN 16 #define AST_CONFIG_MAX_PATH 255 #define MAX_ENTRY_LOG 30 #define SUB_REAL 0 #define SUB_RING 1 #define SUB_THREEWAY 2 #define SUB_ONHOLD 3 struct ast_format_cap *global_cap; enum autoprovision { AUTOPROVISIONING_NO = 0, AUTOPROVISIONING_YES, AUTOPROVISIONING_TN }; enum autoprov_extn { /*! Do not create an extension into the default dialplan */ EXTENSION_NONE = 0, /*! Prompt user for an extension number and register it */ EXTENSION_ASK, /*! Register an extension with the line=> value */ EXTENSION_LINE, /*! Used with AUTOPROVISIONING_TN */ EXTENSION_TN }; #define OUTPUT_HANDSET 0xC0 #define OUTPUT_HEADPHONE 0xC1 #define OUTPUT_SPEAKER 0xC2 #define VOLUME_LOW 0x01 #define VOLUME_LOW_SPEAKER 0x03 #define VOLUME_NORMAL 0x02 #define VOLUME_INSANELY_LOUD 0x07 #define MUTE_OFF 0x00 #define MUTE_ON 0xFF #define MUTE_ON_DISCRET 0xCE #define LED_BAR_OFF 0x00 /* bar off */ #define LED_BAR_ON 0x01 /* bar on */ #define LED_BAR_P2 0x02 /* bar 1s on/1s */ #define LED_BAR_P3 0x03 /* bar 2.5s on/0.5s off */ #define LED_BAR_P4 0x04 /* bar 0.6s on/0.3s off */ #define LED_BAR_P5 0x05 /* bar 0.5s on/0.5s off */ #define LED_BAR_P6 0x06 /* bar 2s on/0.5s off */ #define LED_BAR_P7 0x07 /* bar off */ #define LED_SPEAKER_OFF 0x08 #define LED_SPEAKER_ON 0x09 #define LED_HEADPHONE_OFF 0x010 #define LED_HEADPHONE_ON 0x011 #define LED_MUTE_OFF 0x018 #define LED_MUTE_ON 0x019 #define SIZE_HEADER 6 #define SIZE_MAC_ADDR 17 #define TEXT_LENGTH_MAX 24 #define TEXT_LINE0 0x00 #define TEXT_LINE1 0x20 #define TEXT_LINE2 0x40 #define TEXT_NORMAL 0x05 #define TEXT_INVERSE 0x25 #define STATUS_LENGTH_MAX 28 #define FAV_ICON_NONE 0x00 #define FAV_ICON_ONHOOK_BLACK 0x20 #define FAV_ICON_ONHOOK_WHITE 0x21 #define FAV_ICON_SPEAKER_ONHOOK_BLACK 0x22 #define FAV_ICON_SPEAKER_ONHOOK_WHITE 0x23 #define FAV_ICON_OFFHOOK_BLACK 0x24 #define FAV_ICON_OFFHOOK_WHITE 0x25 #define FAV_ICON_ONHOLD_BLACK 0x26 #define FAV_ICON_ONHOLD_WHITE 0x27 #define FAV_ICON_SPEAKER_OFFHOOK_BLACK 0x28 #define FAV_ICON_SPEAKER_OFFHOOK_WHITE 0x29 #define FAV_ICON_PHONE_BLACK 0x2A #define FAV_ICON_PHONE_WHITE 0x2B #define FAV_ICON_SPEAKER_ONHOLD_BLACK 0x2C #define FAV_ICON_SPEAKER_ONHOLD_WHITE 0x2D #define FAV_ICON_HEADPHONES 0x2E #define FAV_ICON_HEADPHONES_ONHOLD 0x2F #define FAV_ICON_HOME 0x30 #define FAV_ICON_CITY 0x31 #define FAV_ICON_SHARP 0x32 #define FAV_ICON_PAGER 0x33 #define FAV_ICON_CALL_CENTER 0x34 #define FAV_ICON_FAX 0x35 #define FAV_ICON_MAILBOX 0x36 #define FAV_ICON_REFLECT 0x37 #define FAV_ICON_COMPUTER 0x38 #define FAV_ICON_FORWARD 0x39 #define FAV_ICON_LOCKED 0x3A #define FAV_ICON_TRASH 0x3B #define FAV_ICON_INBOX 0x3C #define FAV_ICON_OUTBOX 0x3D #define FAV_ICON_MEETING 0x3E #define FAV_ICON_BOX 0x3F #define FAV_BLINK_FAST 0x20 #define FAV_BLINK_SLOW 0x40 #define FAV_MAX_LENGTH 0x0A #define FAVNUM 6 #define EXPNUM 24 #define FAV_LINE_ICON FAV_ICON_ONHOOK_BLACK static void dummy(char *unused, ...) { return; } /*! \brief Global jitterbuffer configuration - by default, jb is disabled * \note Values shown here match the defaults shown in unistim.conf.sample */ static struct ast_jb_conf default_jbconf = { .flags = 0, .max_size = 200, .resync_threshold = 1000, .impl = "fixed", .target_extra = 40, }; static struct ast_jb_conf global_jbconf; /* #define DUMP_PACKET 1 */ /* #define DEBUG_TIMER ast_verbose */ #define DEBUG_TIMER dummy /*! Enable verbose output. can also be set with the CLI */ static int unistimdebug = 0; static int unistim_port; static enum autoprovision autoprovisioning = AUTOPROVISIONING_NO; static int unistim_keepalive; static int unistimsock = -1; static struct { unsigned int tos; unsigned int tos_audio; unsigned int cos; unsigned int cos_audio; } qos = { 0, 0, 0, 0 }; static struct io_context *io; static struct ast_sched_context *sched; static struct sockaddr_in public_ip = { 0, }; static unsigned char *buff; /*! Receive buffer address */ static int unistim_reloading = 0; AST_MUTEX_DEFINE_STATIC(unistim_reload_lock); /*! This is the thread for the monitor which checks for input on the channels * which are not currently in use. */ static pthread_t monitor_thread = AST_PTHREADT_NULL; /*! 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(monlock); /*! Protect the session list */ AST_MUTEX_DEFINE_STATIC(sessionlock); /*! Protect the device list */ AST_MUTEX_DEFINE_STATIC(devicelock); enum phone_state { STATE_INIT, STATE_AUTHDENY, STATE_MAINPAGE, STATE_EXTENSION, STATE_DIALPAGE, STATE_RINGING, STATE_CALL, STATE_SELECTOPTION, STATE_SELECTCODEC, STATE_SELECTLANGUAGE, STATE_CLEANING, STATE_HISTORY }; enum handset_state { STATE_ONHOOK, STATE_OFFHOOK, }; enum phone_key { KEY_0 = 0x40, KEY_1 = 0x41, KEY_2 = 0x42, KEY_3 = 0x43, KEY_4 = 0x44, KEY_5 = 0x45, KEY_6 = 0x46, KEY_7 = 0x47, KEY_8 = 0x48, KEY_9 = 0x49, KEY_STAR = 0x4a, KEY_SHARP = 0x4b, KEY_UP = 0x4c, KEY_DOWN = 0x4d, KEY_RIGHT = 0x4e, KEY_LEFT = 0x4f, KEY_QUIT = 0x50, KEY_COPY = 0x51, KEY_FUNC1 = 0x54, KEY_FUNC2 = 0x55, KEY_FUNC3 = 0x56, KEY_FUNC4 = 0x57, KEY_ONHOLD = 0x5b, KEY_HANGUP = 0x5c, KEY_MUTE = 0x5d, KEY_HEADPHN = 0x5e, KEY_LOUDSPK = 0x5f, KEY_FAV0 = 0x60, KEY_FAV1 = 0x61, KEY_FAV2 = 0x62, KEY_FAV3 = 0x63, KEY_FAV4 = 0x64, KEY_FAV5 = 0x65, KEY_COMPUTR = 0x7b, KEY_CONF = 0x7c, KEY_SNDHIST = 0x7d, KEY_RCVHIST = 0x7e, KEY_INDEX = 0x7f }; enum charset { LANG_DEFAULT, ISO_8859_1, ISO_8859_2, ISO_8859_4, ISO_8859_5, ISO_2022_JP, }; static const int dtmf_row[] = { 697, 770, 852, 941 }; static const float dtmf_col[] = { 1209, 1336, 1477, 1633 }; struct wsabuf { u_long len; unsigned char *buf; }; struct unistim_subchannel { ast_mutex_t lock; unsigned int subtype; /*! SUB_REAL, SUB_RING, SUB_THREEWAY or SUB_ONHOLD */ struct ast_channel *owner; /*! Asterisk channel used by the subchannel */ struct unistim_line *parent; /*! Unistim line */ struct ast_rtp_instance *rtp; /*! RTP handle */ int softkey; /*! Softkey assigned */ pthread_t ss_thread; /*! unistim_ss thread handle */ int alreadygone; char ringvolume; char ringstyle; int moh; /*!< Music on hold in progress */ AST_LIST_ENTRY(unistim_subchannel) list; }; /*! * \todo Convert to stringfields */ struct unistim_line { ast_mutex_t lock; char name[80]; /*! Like 200 */ char fullname[80]; /*! Like USTM/200\@black */ char exten[AST_MAX_EXTENSION]; /*! Extension where to start */ char cid_num[AST_MAX_EXTENSION]; /*! CallerID Number */ char mailbox[AST_MAX_EXTENSION]; /*! Mailbox for MWI */ char musicclass[MAX_MUSICCLASS]; /*! MusicOnHold class */ ast_group_t callgroup; /*! Call group */ ast_group_t pickupgroup; /*! Pickup group */ char accountcode[AST_MAX_ACCOUNT_CODE]; /*! Account code (for billing) */ int amaflags; /*! AMA flags (for billing) */ struct ast_format_cap *cap; /*! Codec supported */ char parkinglot[AST_MAX_CONTEXT]; /*! Parkinglot */ struct unistim_line *next; struct unistim_device *parent; AST_LIST_ENTRY(unistim_line) list; }; /*! * \brief A device containing one or more lines */ static struct unistim_device { ast_mutex_t lock; int receiver_state; /*!< state of the receiver (see ReceiverState) */ int size_phone_number; /*!< size of the phone number */ char context[AST_MAX_EXTENSION]; /*!< Context to start in */ char phone_number[AST_MAX_EXTENSION]; /*!< the phone number entered by the user */ char redial_number[AST_MAX_EXTENSION]; /*!< the last phone number entered by the user */ char id[18]; /*!< mac address of the current phone in ascii */ char name[DEVICE_NAME_LEN]; /*!< name of the device */ int hasexp; /*!< if device have expansion connected */ char expsoftkeylabel[EXPNUM][11]; /*!< soft key label */ char softkeylabel[FAVNUM][11]; /*!< soft key label */ char softkeynumber[FAVNUM][AST_MAX_EXTENSION]; /*!< number dialed when the soft key is pressed */ char softkeyicon[FAVNUM]; /*!< icon number */ char softkeydevice[FAVNUM][16]; /*!< name of the device monitored */ struct unistim_subchannel *ssub[FAVNUM]; struct unistim_line *sline[FAVNUM]; struct unistim_device *sp[FAVNUM]; /*!< pointer to the device monitored by this soft key */ char language[MAX_LANGUAGE]; /*!< Language for asterisk sounds */ int height; /*!< The number of lines the phone can display */ char maintext0[25]; /*!< when the phone is idle, display this string on line 0 */ char maintext1[25]; /*!< when the phone is idle, display this string on line 1 */ char maintext2[25]; /*!< when the phone is idle, display this string on line 2 */ char titledefault[13]; /*!< title (text before date/time) */ char datetimeformat; /*!< format used for displaying time/date */ char contrast; /*!< contrast */ char country[3]; /*!< country used for dial tone frequency */ struct ast_tone_zone *tz; /*!< Tone zone for res_indications (ring, busy, congestion) */ char ringvolume; /*!< Ring volume */ char ringstyle; /*!< Ring melody */ char cwvolume; /*!< Ring volume on call waiting */ char cwstyle; /*!< Ring melody on call waiting */ int interdigit_timer; /*!< Interdigit timer for dialing number by timeout */ int dtmfduration; /*!< DTMF playback duration */ time_t nextdial; /*!< Timer used for dial by timeout */ int rtp_port; /*!< RTP port used by the phone */ int rtp_method; /*!< Select the unistim data used to establish a RTP session */ int status_method; /*!< Select the unistim packet used for sending status text */ char codec_number; /*!< The current codec used to make calls */ int missed_call; /*!< Number of call unanswered */ int callhistory; /*!< Allowed to record call history */ int sharp_dial; /*!< Execute Dial on '#' or not */ char lst_cid[TEXT_LENGTH_MAX]; /*!< Last callerID received */ char lst_cnm[TEXT_LENGTH_MAX]; /*!< Last callername recevied */ char call_forward[AST_MAX_EXTENSION]; /*!< Forward number */ int output; /*!< Handset, headphone or speaker */ int previous_output; /*!< Previous output */ int volume; /*!< Default volume */ int selected; /*!< softkey selected */ int microphone; /*!< Microphone mode (audio tx) */ int lastmsgssent; /*! Used by MWI */ time_t nextmsgcheck; /*! Used by MWI */ int nat; /*!< Used by the obscure ast_rtp_setnat */ enum autoprov_extn extension; /*!< See ifdef EXTENSION for valid values */ char extension_number[11]; /*!< Extension number entered by the user */ char to_delete; /*!< Used in reload */ struct ast_silence_generator *silence_generator; AST_LIST_HEAD(,unistim_subchannel) subs; /*!< pointer to our current connection, channel... */ AST_LIST_HEAD(,unistim_line) lines; struct ast_ha *ha; struct unistimsession *session; struct unistim_device *next; } *devices = NULL; static struct unistimsession { ast_mutex_t lock; struct sockaddr_in sin; /*!< IP address of the phone */ struct sockaddr_in sout; /*!< IP address of server */ int timeout; /*!< time-out in ticks : resend packet if no ack was received before the timeout occured */ unsigned short seq_phone; /*!< sequence number for the next packet (when we receive a request) */ unsigned short seq_server; /*!< sequence number for the next packet (when we send a request) */ unsigned short last_seq_ack; /*!< sequence number of the last ACK received */ unsigned long tick_next_ping; /*!< time for the next ping */ int last_buf_available; /*!< number of a free slot */ int nb_retransmit; /*!< number of retransmition */ int state; /*!< state of the phone (see phone_state) */ int size_buff_entry; /*!< size of the buffer used to enter datas */ char buff_entry[16]; /*!< Buffer for temporary datas */ char macaddr[18]; /*!< mac adress of the phone (not always available) */ char firmware[8]; /*!< firmware of the phone (not always available) */ struct wsabuf wsabufsend[MAX_BUF_NUMBER]; /*!< Size of each paquet stored in the buffer array & pointer to this buffer */ unsigned char buf[MAX_BUF_NUMBER][MAX_BUF_SIZE]; /*!< Buffer array used to keep the lastest non-acked paquets */ struct unistim_device *device; struct unistimsession *next; } *sessions = NULL; /*! Store on screen phone menu item (label and handler function) */ struct unistim_menu_item { char *label; int state; void (*handle_option)(struct unistimsession *); }; /*! Language item for currently existed translations */ struct unistim_languages { char *label; char *lang_short; int encoding; struct ao2_container *trans; }; /*! * \page Unistim datagram formats * * Format of datagrams : * bytes 0 & 1 : ffff for discovery packet, 0000 for everything else * byte 2 : sequence number (high part) * byte 3 : sequence number (low part) * byte 4 : 2 = ask question or send info, 1 = answer or ACK, 0 = retransmit request * byte 5 : direction, 1 = server to phone, 2 = phone to server arguments */ static const unsigned char packet_rcv_discovery[] = { 0xff, 0xff, 0xff, 0xff, 0x02, 0x02, 0xff, 0xff, 0xff, 0xff, 0x9e, 0x03, 0x08 }; static const unsigned char packet_send_discovery_ack[] = { 0x00, 0x00, /*Initial Seq (2 bytes) */ 0x00, 0x00, 0x00, 0x01 }; static const unsigned char packet_recv_firm_version[] = { 0x00, 0x00, 0x00, 0x13, 0x9a, 0x0a, 0x02 }; static const unsigned char packet_recv_it_type[] = { 0x00, 0x00, 0x00, 0x13, 0x9a, 0x04, 0x03 }; static const unsigned char packet_recv_pressed_key[] = { 0x00, 0x00, 0x00, 0x13, 0x99, 0x04, 0x00 }; static const unsigned char packet_recv_pick_up[] = { 0x00, 0x00, 0x00, 0x13, 0x99, 0x03, 0x04 }; static const unsigned char packet_recv_hangup[] = { 0x00, 0x00, 0x00, 0x13, 0x99, 0x03, 0x03 }; static const unsigned char packet_recv_r2[] = { 0x00, 0x00, 0x00, 0x13, 0x96, 0x03, 0x03 }; /*! Expansion module (i2004 KEM) */ static const unsigned char packet_recv_expansion_pressed_key[] = { 0x00, 0x00, 0x00, 0x13, 0x89, 0x04, 0x59 }; static const unsigned char packet_send_expansion_next[] = { 0x09, 0x03, 0x17 }; static const unsigned char packet_send_expansion_icon[] = { 0x09, 0x06, 0x59, 0x05, /*pos */ 0x47, /*icon */ 0x20 }; /* display an icon in front of the text zone */ static const unsigned char packet_send_expansion_text[] = { 0x09, 0x0f, 0x57, 0x19, /*pos */ 0x47, /*text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 /*end_text */ }; /*! TransportAdapter */ static const unsigned char packet_recv_resume_connection_with_server[] = { 0xff, 0xff, 0xff, 0xff, 0x9e, 0x03, 0x08 }; static const unsigned char packet_recv_mac_addr[] = { 0xff, 0xff, 0xff, 0xff, 0x9a, 0x0d, 0x07 /*MacAddr */ }; static const unsigned char packet_send_date_time3[] = { 0x11, 0x09, 0x02, 0x02, /*Month */ 0x05, /*Day */ 0x06, /*Hour */ 0x07, /*Minutes */ 0x08, 0x32 }; static const unsigned char packet_send_date_time[] = { 0x11, 0x09, 0x02, 0x0a, /*Month */ 0x05, /*Day */ 0x06, /*Hour */ 0x07, /*Minutes */ 0x08, 0x32, 0x17, 0x04, 0x24, 0x07, 0x19, 0x04, 0x07, 0x00, 0x19, 0x05, 0x09, 0x3e, 0x0f, 0x16, 0x05, 0x00, 0x80, 0x00, 0x1e, 0x05, 0x12, 0x00, 0x78 }; static const unsigned char packet_send_no_ring[] = { 0x16, 0x04, 0x1a, 0x00, 0x16, 0x04, 0x11, 0x00 }; static const unsigned char packet_send_s4[] = { 0x16, 0x04, 0x1a, 0x00, 0x16, 0x04, 0x11, 0x00, 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x1c, 0x00, 0x00, 0x17, 0x05, 0x0b, 0x00, 0x00, 0x19, 0x04, 0x00, 0x00, 0x19, 0x04, 0x00, 0x08, 0x19, 0x04, 0x00, 0x10, 0x19, 0x04, 0x00, 0x18, 0x16, 0x05, 0x31, 0x00, 0x00, 0x16, 0x05, 0x04, 0x00, 0x00 }; static const unsigned char packet_send_call[] = { 0x16, 0x04, 0x1a, 0x00, 0x16, 0x04, 0x11, 0x00, 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x1c, 0x00, 0x00, 0x16, 0x0a, 0x38, 0x00, 0x12, 0xca, 0x03, 0xc0, 0xc3, 0xc5, 0x16, 0x16, 0x30, 0x00, 0x00, /*codec */ 0x12, 0x12, /* frames per packet */ 0x01, 0x5c, 0x00, /*port RTP */ 0x0f, 0xa0, /* port RTCP */ 0x9c, 0x41, /*port RTP */ 0x0f, 0xa0, /* port RTCP */ 0x9c, 0x41, /* IP Address */ 0x0a, 0x01, 0x16, 0x66 }; static const unsigned char packet_send_stream_based_tone_off[] = { 0x16, 0x05, 0x1c, 0x00, 0x00 }; static const unsigned char packet_send_mute[] = { 0x16, 0x05, 0x04, 0x00, 0x00 }; static const unsigned char packet_send_CloseAudioStreamRX[] = { 0x16, 0x05, 0x31, 0x00, 0xff }; static const unsigned char packet_send_CloseAudioStreamTX[] = { 0x16, 0x05, 0x31, 0xff, 0x00 }; static const unsigned char packet_send_stream_based_tone_on[] = { 0x16, 0x06, 0x1b, 0x00, 0x00, 0x05 }; static const unsigned char packet_send_stream_based_tone_single_freq[] = { 0x16, 0x06, 0x1d, 0x00, 0x01, 0xb8 }; static const unsigned char packet_send_stream_based_tone_dial_freq[] = { 0x16, 0x08, 0x1d, 0x00, 0x01, 0xb8, 0x01, 0x5e }; static const unsigned char packet_send_select_output[] = { 0x16, 0x06, 0x32, 0xc0, 0x01, 0x00 }; static const unsigned char packet_send_ring[] = { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x1c, 0x00, 0x00, 0x16, 0x04, 0x1a, 0x01, 0x16, 0x05, 0x12, 0x13 /* Ring type 10 to 17 */ , 0x18, 0x16, 0x04, 0x18, /* volume 00, 10, 20... */ 0x20, 0x16, 0x04, 0x10, 0x00 }; //static const unsigned char packet_send_end_call[] = // { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x31, 0x00, 0x00, /* Headset LED off */ 0x19, 0x04, 0x00, //0x10, /* Mute LED off */ 0x19, 0x04, 0x00, 0x18,/* Stream unmute */ 0x16, 0x05, 0x04, 0x00, 0x00, /* Query RTCP */ 0x16, 0x04, 0x37, 0x10 }; static const unsigned char packet_send_end_call[] = { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x16, 0x05, 0x31, 0x00, 0x00, /* Query RTCP */ 0x16, 0x04, 0x37, 0x10 }; static const unsigned char packet_send_s9[] = { 0x16, 0x06, 0x32, 0xdf, 0x00, 0xff, 0x19, 0x04, 0x00, 0x10, 0x16, 0x05, 0x1c, 0x00, 0x00 }; static const unsigned char packet_send_rtp_packet_size[] = { 0x16, 0x08, 0x38, 0x00, 0x00, 0xe0, 0x00, 0xa0 }; static const unsigned char packet_send_jitter_buffer_conf[] = { 0x16, 0x0e, 0x3a, 0x00, /* jitter */ 0x02, /* high water mark */ 0x04, 0x00, 0x00, /* early packet resync 2 bytes */ 0x3e, 0x80, 0x00, 0x00, /* late packet resync 2 bytes */ 0x3e, 0x80 }; /* Duration in ms div 2 (0x20 = 64ms, 0x08 = 16ms) static unsigned char packet_send_StreamBasedToneCad[] = { 0x16, 0x0a, 0x1e, 0x00, duration on 0x0a, duration off 0x0d, duration on 0x0a, duration off 0x0d, duration on 0x0a, duration off 0x2b }; */ static const unsigned char packet_send_open_audio_stream_rx[] = { 0x16, 0x1a, 0x30, 0x00, 0xff, /* Codec */ 0x00, 0x00, 0x01, 0x00, 0xb8, 0xb8, 0x0e, 0x0e, 0x01, /* Port */ 0x14, 0x50, 0x00, 0x00, /* Port */ 0x14, 0x50, 0x00, 0x00, /* Dest IP */ 0x0a, 0x93, 0x69, 0x05 }; static const unsigned char packet_send_open_audio_stream_tx[] = { 0x16, 0x1a, 0x30, 0xff, 0x00, 0x00, /* Codec */ 0x00, 0x01, 0x00, 0xb8, 0xb8, 0x0e, 0x0e, 0x01, /* Local port */ 0x14, 0x50, 0x00, 0x00, /* Rmt Port */ 0x14, 0x50, 0x00, 0x00, /* Dest IP */ 0x0a, 0x93, 0x69, 0x05 }; static const unsigned char packet_send_open_audio_stream_rx3[] = { 0x16, 0x1a, 0x30, 0x00, 0xff, /* Codec */ 0x00, 0x00, 0x02, 0x01, 0xb8, 0xb8, 0x06, 0x06, 0x81, /* RTP Port */ 0x14, 0x50, /* RTCP Port */ 0x14, 0x51, /* RTP Port */ 0x14, 0x50, /* RTCP Port */ 0x00, 0x00, /* Dest IP */ 0x0a, 0x93, 0x69, 0x05 }; static const unsigned char packet_send_open_audio_stream_tx3[] = { 0x16, 0x1a, 0x30, 0xff, 0x00, 0x00, /* Codec */ 0x00, 0x02, 0x01, 0xb8, 0xb8, 0x06, 0x06, 0x81, /* RTP Local port */ 0x14, 0x50, /* RTCP Port */ 0x00, 0x00, /* RTP Rmt Port */ 0x14, 0x50, /* RTCP Port */ 0x00, 0x00, /* Dest IP */ 0x0a, 0x93, 0x69, 0x05 }; static const unsigned char packet_send_arrow[] = { 0x17, 0x04, 0x04, 0x00 }; static const unsigned char packet_send_blink_cursor[] = { 0x17, 0x04, 0x10, 0x86 }; static const unsigned char packet_send_date_time2[] = { 0x17, 0x04, 0x17, 0x3d, 0x11, 0x09, 0x02, 0x0a, /*Month */ 0x05, /*Day */ 0x06, /*Hour */ 0x07, /*Minutes */ 0x08, 0x32 }; static const unsigned char packet_send_Contrast[] = { 0x17, 0x04, 0x24, /*Contrast */ 0x08 }; static const unsigned char packet_send_start_timer[] = { 0x17, 0x05, 0x0b, /*Timer option*/0x05, /* Timer ID */0x00, 0x17, 0x08, 0x16, /* Text */ 'T', 'i', 'm', 'e', 'r' }; static const unsigned char packet_send_stop_timer[] = { 0x17, 0x05, 0x0b, 0x02, 0x00 }; static const unsigned char packet_send_icon[] = { 0x17, 0x05, 0x14, /*pos */ 0x00, /*icon */ 0x25 }; /* display an icon in front of the text zone */ static const unsigned char packet_send_S7[] = { 0x17, 0x06, 0x0f, 0x30, 0x07, 0x07 }; static const unsigned char packet_send_set_pos_cursor[] = { 0x17, 0x06, 0x10, 0x81, 0x04, /*pos */ 0x20 }; static unsigned char monthlabels[] = { 'J', 'a', 'n', 'F', 'e', 'b', 'M', 'a', 'r', 'A', 'p', 'r', 'M', 'a', 'y', 'J', 'u', 'n', 'J', 'u', 'l', 'A', 'u', 'g', 'S', 'e', 'p', 'O', 'c', 't', 'N', 'o', 'v', 'D', 'e', 'c' }; static unsigned char packet_send_monthlabels_download[] = { 0x17, 0x0a, 0x15, /* Month (3 char) */ '-', '-', '-', '-', '-', '-', 0x20 }; static const unsigned char packet_send_favorite[] = { 0x17, 0x0f, 0x19, 0x10, /*pos */ 0x01, /*name */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, /*end_name */ 0x19, 0x05, 0x0f, /*pos */ 0x01, /*icone */ 0x00 }; static const unsigned char packet_send_title[] = { 0x17, 0x10, 0x19, 0x02, /*text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 /*end_text */ }; static const unsigned char packet_send_text[] = { 0x17, 0x1e, 0x1b, 0x04, /*pos */ 0x00, /*inverse */ 0x25, /*text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, /*end_text */ 0x17, 0x04, 0x10, 0x87 }; static const unsigned char packet_send_status[] = { 0x17, 0x20, 0x19, 0x08, /*text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 /*end_text */ }; static const unsigned char packet_send_status2[] = { 0x17, 0x0b, 0x19, /* pos [08|28|48|68] */ 0x00, /* text */ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 /* end_text */ }; /* Multiple character set support */ /* ISO-8859-1 - Western European) */ static const unsigned char packet_send_charset_iso_8859_1[] = { 0x17, 0x08, 0x21, 0x1b, 0x2d, 0x41, 0x1b, 0x00 }; /* ISO-8859-2 - Central European) */ static const unsigned char packet_send_charset_iso_8859_2[] = { 0x17, 0x08, 0x21, 0x1b, 0x2d, 0x42, 0x1b, 0x00 }; /* ISO-8859-4 - Baltic) */ static const unsigned char packet_send_charset_iso_8859_4[] = { 0x17, 0x08, 0x21, 0x1b, 0x2d, 0x44, 0x1b, 0x00 }; /* ISO 8859-5 - cyrilic */ static const unsigned char packet_send_charset_iso_8859_5[] = { 0x17, 0x08, 0x21, 0x1b, 0x2d, 0x4c, 0x1b, 0x00 }; /* Japaneese (ISO-2022-JP ?) */ static const unsigned char packet_send_charset_iso_2022_jp[] = { 0x17, 0x08, 0x21, 0x1b, 0x29, 0x49, 0x1b, 0x7e }; static const unsigned char packet_send_led_update[] = { 0x19, 0x04, 0x00, 0x00 }; static const unsigned char packet_send_query_basic_manager_04[] = { 0x1a, 0x04, 0x01, 0x04 }; static const unsigned char packet_send_query_mac_address[] = { 0x1a, 0x04, 0x01, 0x08 }; static const unsigned char packet_send_query_basic_manager_10[] = { 0x1a, 0x04, 0x01, 0x10 }; static const unsigned char packet_send_S1[] = { 0x1a, 0x07, 0x07, 0x00, 0x00, 0x00, 0x13 }; static unsigned char packet_send_ping[] = { 0x1e, 0x05, 0x12, 0x00, /*Watchdog timer */ 0x78 }; #define BUFFSEND unsigned char buffsend[64] = { 0x00, 0x00, 0xaa, 0xbb, 0x02, 0x01 } static const char tdesc[] = "UNISTIM Channel Driver"; static const char channel_type[] = "USTM"; /*! Protos */ static struct ast_channel *unistim_new(struct unistim_subchannel *sub, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor); static int load_module(void); static int reload(void); static int unload_module(void); static int reload_config(void); static void unistim_set_owner(struct unistim_subchannel *sub, struct ast_channel *chan); static void show_main_page(struct unistimsession *pte); static struct ast_channel *unistim_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); static int unistim_call(struct ast_channel *ast, const char *dest, int timeout); static int unistim_hangup(struct ast_channel *ast); static int unistim_answer(struct ast_channel *ast); static struct ast_frame *unistim_read(struct ast_channel *ast); static int unistim_write(struct ast_channel *ast, struct ast_frame *frame); static int unistim_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen); static int unistim_fixup(struct ast_channel *oldchan, struct ast_channel *newchan); static int unistim_senddigit_begin(struct ast_channel *ast, char digit); static int unistim_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration); static int unistim_sendtext(struct ast_channel *ast, const char *text); static int write_entry_history(struct unistimsession *pte, FILE * f, char c, char *line1); static void change_callerid(struct unistimsession *pte, int type, char *callerid); static struct ast_channel_tech unistim_tech = { .type = channel_type, .description = tdesc, .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, .requester = unistim_request, .call = unistim_call, .hangup = unistim_hangup, .answer = unistim_answer, .read = unistim_read, .write = unistim_write, .indicate = unistim_indicate, .fixup = unistim_fixup, .send_digit_begin = unistim_senddigit_begin, .send_digit_end = unistim_senddigit_end, .send_text = unistim_sendtext, }; static void send_start_rtp(struct unistim_subchannel *); static void send_callerid_screen(struct unistimsession *, struct unistim_subchannel *); static void key_favorite(struct unistimsession *, char); static void handle_select_codec(struct unistimsession *); static void handle_select_language(struct unistimsession *); static int find_language(const char*); static int unistim_free_sub(struct unistim_subchannel *); static struct unistim_menu_item options_menu[] = { {"Change codec", STATE_SELECTCODEC, handle_select_codec}, {"Language", STATE_SELECTLANGUAGE, handle_select_language}, {NULL, 0, NULL} }; static struct unistim_languages options_languages[] = { {"English", "en", ISO_8859_1, NULL}, {"French", "fr", ISO_8859_1, NULL}, {"Russian", "ru", ISO_8859_5, NULL}, {NULL, NULL, 0, NULL} }; static char ustm_strcopy[1024]; struct ustm_lang_entry { const char *str_orig; const char *str_trans; }; static int lang_hash_fn(const void *obj, const int flags) { const struct ustm_lang_entry *entry = obj; return ast_str_hash(entry->str_orig); } static int lang_cmp_fn(void *obj, void *arg, int flags) { struct ustm_lang_entry *entry1 = obj; struct ustm_lang_entry *entry2 = arg; return (!strcmp(entry1->str_orig, entry2->str_orig)) ? (CMP_MATCH | CMP_STOP) : 0; } static const char *ustmtext(const char *str, struct unistimsession *pte) { struct ustm_lang_entry *lang_entry; struct ustm_lang_entry le_search; struct unistim_languages *lang = NULL; int size; if (pte->device) { lang = &options_languages[find_language(pte->device->language)]; } if (!lang) { return str; } /* Check if specified language exists */ if (!lang->trans) { char tmp[1024], *p, *p_orig = NULL, *p_trans = NULL; FILE *f; if (!(lang->trans = ao2_container_alloc(8, lang_hash_fn, lang_cmp_fn))) { ast_log(LOG_ERROR, "Unable to allocate container for translation!\n"); return str; } snprintf(tmp, sizeof(tmp), "%s/%s/%s.po", ast_config_AST_VAR_DIR, USTM_LANG_DIR, lang->lang_short); f = fopen(tmp, "r"); if (!f) { ast_log(LOG_WARNING, "There is no translation file for '%s'\n", lang->lang_short); return str; } while (fgets(tmp, sizeof(tmp), f)) { if (!(p = strchr(tmp, '\n'))) { ast_log(LOG_ERROR, "Too long line found in language file - truncated!\n"); continue; } *p = '\0'; if (!(p = strchr(tmp, '"'))) { continue; } if (tmp == strstr(tmp, "msgid")) { p_orig = ast_strdup(p + 1); p = strchr(p_orig, '"'); } else if (tmp == strstr(tmp, "msgstr")) { p_trans = ast_strdup(p + 1); p = strchr(p_trans, '"'); } else { continue; } *p = '\0'; if (!p_trans || !p_orig) { continue; } if (ast_strlen_zero(p_trans)) { ast_free(p_trans); ast_free(p_orig); p_trans = NULL; p_orig = NULL; continue; } if (!(lang_entry = ao2_alloc(sizeof(*lang_entry), NULL))) { fclose(f); return str; } lang_entry->str_trans = p_trans; lang_entry->str_orig = p_orig; ao2_link(lang->trans, lang_entry); p_trans = NULL; p_orig = NULL; } fclose(f); } le_search.str_orig = str; if ((lang_entry = ao2_find(lang->trans, &le_search, OBJ_POINTER))) { size = strlen(lang_entry->str_trans)+1; if (size > 1024) { size = 1024; } memcpy(ustm_strcopy, lang_entry->str_trans, size); ao2_ref(lang_entry, -1); return ustm_strcopy; } return str; } static void display_last_error(const char *sz_msg) { /* Display the error message */ ast_log(LOG_WARNING, "%s : (%d) %s\n", sz_msg, errno, strerror(errno)); } static unsigned int get_tick_count(void) { struct timeval now = ast_tvnow(); return (now.tv_sec * 1000) + (now.tv_usec / 1000); } /* Send data to a phone without retransmit nor buffering */ static void send_raw_client(int size, const unsigned char *data, struct sockaddr_in *addr_to, const struct sockaddr_in *addr_ourip) { #ifdef HAVE_PKTINFO struct iovec msg_iov; struct msghdr msg; char buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; struct cmsghdr *ip_msg = (struct cmsghdr *) buffer; struct in_pktinfo *pki = (struct in_pktinfo *) CMSG_DATA(ip_msg); /* cast this to a non-const pointer, since the sendmsg() API * does not provide read-only and write-only flavors of the * structures used for its arguments, but in this case we know * the data will not be modified */ msg_iov.iov_base = (char *) data; msg_iov.iov_len = size; msg.msg_name = addr_to; /* optional address */ msg.msg_namelen = sizeof(struct sockaddr_in); /* size of address */ msg.msg_iov = &msg_iov; /* scatter/gather array */ msg.msg_iovlen = 1; /* # elements in msg_iov */ msg.msg_control = ip_msg; /* ancillary data */ msg.msg_controllen = sizeof(buffer); /* ancillary data buffer len */ msg.msg_flags = 0; /* flags on received message */ ip_msg->cmsg_len = CMSG_LEN(sizeof(*pki)); ip_msg->cmsg_level = IPPROTO_IP; ip_msg->cmsg_type = IP_PKTINFO; pki->ipi_ifindex = 0; /* Interface index, 0 = use interface specified in routing table */ pki->ipi_spec_dst.s_addr = addr_ourip->sin_addr.s_addr; /* Local address */ /* pki->ipi_addr = ; Header Destination address - ignored by kernel */ #ifdef DUMP_PACKET if (unistimdebug) { int tmp; ast_verb(0, "\n**> From %s sending %d bytes to %s ***\n", ast_inet_ntoa(addr_ourip->sin_addr), (int) size, ast_inet_ntoa(addr_to->sin_addr)); for (tmp = 0; tmp < size; tmp++) ast_verb(0, "%.2x ", (unsigned char) data[tmp]); ast_verb(0, "\n******************************************\n"); } #endif if (sendmsg(unistimsock, &msg, 0) == -1) { display_last_error("Error sending datas"); } #else if (sendto(unistimsock, data, size, 0, (struct sockaddr *) addr_to, sizeof(*addr_to)) == -1) display_last_error("Error sending datas"); #endif } static void send_client(int size, const unsigned char *data, struct unistimsession *pte) { unsigned int tick; int buf_pos; unsigned short seq = ntohs(++pte->seq_server); ast_mutex_lock(&pte->lock); buf_pos = pte->last_buf_available; if (buf_pos >= MAX_BUF_NUMBER) { ast_log(LOG_WARNING, "Error : send queue overflow\n"); ast_mutex_unlock(&pte->lock); return; } memcpy((void *)data + sizeof(unsigned short), (void *)&seq, sizeof(unsigned short)); pte->wsabufsend[buf_pos].len = size; memcpy(pte->wsabufsend[buf_pos].buf, data, size); tick = get_tick_count(); pte->timeout = tick + RETRANSMIT_TIMER; /*#ifdef DUMP_PACKET */ if (unistimdebug) { ast_verb(0, "Sending datas with seq #0x%.4x Using slot #%d :\n", (unsigned)pte->seq_server, buf_pos); } /*#endif */ send_raw_client(pte->wsabufsend[buf_pos].len, pte->wsabufsend[buf_pos].buf, &(pte->sin), &(pte->sout)); pte->last_buf_available++; ast_mutex_unlock(&pte->lock); } static void send_ping(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending ping\n"); } pte->tick_next_ping = get_tick_count() + unistim_keepalive; memcpy(buffsend + SIZE_HEADER, packet_send_ping, sizeof(packet_send_ping)); send_client(SIZE_HEADER + sizeof(packet_send_ping), buffsend, pte); } static int get_to_address(int fd, struct sockaddr_in *toAddr) { #ifdef HAVE_PKTINFO int err; struct msghdr msg; struct { struct cmsghdr cm; int len; struct in_addr address; } ip_msg; /* Zero out the structures before we use them */ /* This sets several key values to NULL */ memset(&msg, 0, sizeof(msg)); memset(&ip_msg, 0, sizeof(ip_msg)); /* Initialize the message structure */ msg.msg_control = &ip_msg; msg.msg_controllen = sizeof(ip_msg); /* Get info about the incoming packet */ err = recvmsg(fd, &msg, MSG_PEEK); if (err == -1) { ast_log(LOG_WARNING, "recvmsg returned an error: %s\n", strerror(errno)); } memcpy(&toAddr->sin_addr, &ip_msg.address, sizeof(struct in_addr)); return err; #else memcpy(&toAddr, &public_ip, sizeof(&toAddr)); return 0; #endif } /* Allocate memory & initialize structures for a new phone */ /* addr_from : ip address of the phone */ static struct unistimsession *create_client(const struct sockaddr_in *addr_from) { int tmp; struct unistimsession *s; if (!(s = ast_calloc(1, sizeof(*s)))) return NULL; memcpy(&s->sin, addr_from, sizeof(struct sockaddr_in)); get_to_address(unistimsock, &s->sout); s->sout.sin_family = AF_INET; if (unistimdebug) { ast_verb(0, "Creating a new entry for the phone from %s received via server ip %s\n", ast_inet_ntoa(addr_from->sin_addr), ast_inet_ntoa(s->sout.sin_addr)); } ast_mutex_init(&s->lock); ast_mutex_lock(&sessionlock); s->next = sessions; sessions = s; s->timeout = get_tick_count() + RETRANSMIT_TIMER; s->state = STATE_INIT; s->tick_next_ping = get_tick_count() + unistim_keepalive; /* Initialize struct wsabuf */ for (tmp = 0; tmp < MAX_BUF_NUMBER; tmp++) { s->wsabufsend[tmp].buf = s->buf[tmp]; } ast_mutex_unlock(&sessionlock); return s; } static void send_end_call(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending end call\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_end_call, sizeof(packet_send_end_call)); send_client(SIZE_HEADER + sizeof(packet_send_end_call), buffsend, pte); } static void set_ping_timer(struct unistimsession *pte) { unsigned int tick = 0; /* XXX what is this for, anyways */ pte->timeout = pte->tick_next_ping; DEBUG_TIMER("tick = %u next ping at %u tick\n", tick, pte->timeout); return; } /* Checking if our send queue is empty, * if true, setting up a timer for keepalive */ static void check_send_queue(struct unistimsession *pte) { /* Check if our send queue contained only one element */ if (pte->last_buf_available == 1) { if (unistimdebug) { ast_verb(0, "Our single packet was ACKed.\n"); } pte->last_buf_available--; set_ping_timer(pte); return; } /* Check if this ACK catch up our latest packet */ else if (pte->last_seq_ack + 1 == pte->seq_server + 1) { if (unistimdebug) { ast_verb(0, "Our send queue is completely ACKed.\n"); } pte->last_buf_available = 0; /* Purge the send queue */ set_ping_timer(pte); return; } if (unistimdebug) { ast_verb(0, "We still have packets in our send queue\n"); } return; } static void send_start_timer(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending start timer\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_start_timer, sizeof(packet_send_start_timer)); send_client(SIZE_HEADER + sizeof(packet_send_start_timer), buffsend, pte); } static void send_stop_timer(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending stop timer\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_stop_timer, sizeof(packet_send_stop_timer)); send_client(SIZE_HEADER + sizeof(packet_send_stop_timer), buffsend, pte); } static void send_icon(unsigned char pos, unsigned char status, struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending icon pos %d with status 0x%.2x\n", pos, (unsigned)status); } memcpy(buffsend + SIZE_HEADER, packet_send_icon, sizeof(packet_send_icon)); buffsend[9] = pos; buffsend[10] = status; send_client(SIZE_HEADER + sizeof(packet_send_icon), buffsend, pte); } static void send_expansion_next(struct unistimsession *pte) { BUFFSEND; memcpy(buffsend + SIZE_HEADER, packet_send_expansion_next, sizeof(packet_send_expansion_next)); send_client(SIZE_HEADER + sizeof(packet_send_expansion_next), buffsend, pte); } static void send_expansion_icon(unsigned char pos, unsigned char status, struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending expansion icon pos %d with status 0x%.2x\n", pos, (unsigned)status); } memcpy(buffsend + SIZE_HEADER, packet_send_expansion_icon, sizeof(packet_send_expansion_icon)); buffsend[10] = pos; buffsend[11] = status; send_client(SIZE_HEADER + sizeof(packet_send_expansion_icon), buffsend, pte); } /* inverse : TEXT_INVERSE : yes, TEXT_NORMAL : no */ static void send_expansion_text(unsigned char pos, struct unistimsession *pte, const char *text) { int i; BUFFSEND; if (!text) { ast_log(LOG_ERROR, "[expansion] Asked to display NULL text (pos %d)\n", pos); return; } if (unistimdebug) { ast_verb(0, "[expansion] Sending text at pos %d\n", pos); } memcpy(buffsend + SIZE_HEADER, packet_send_expansion_text, sizeof(packet_send_expansion_text)); buffsend[10] = pos; i = strlen(text); if (i > TEXT_LENGTH_MAX) { i = TEXT_LENGTH_MAX; } memcpy(buffsend + 11, text, i); send_client(SIZE_HEADER + sizeof(packet_send_expansion_text), buffsend, pte); } static void send_tone(struct unistimsession *pte, uint16_t tone1, uint16_t tone2) { BUFFSEND; if (!tone1) { if (unistimdebug) { ast_verb(0, "Sending Stream Based Tone Off\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_off, sizeof(packet_send_stream_based_tone_off)); send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_off), buffsend, pte); return; } /* Since most of the world use a continuous tone, it's useless if (unistimdebug) ast_verb(0, "Sending Stream Based Tone Cadence Download\n"); memcpy (buffsend + SIZE_HEADER, packet_send_StreamBasedToneCad, sizeof (packet_send_StreamBasedToneCad)); send_client (SIZE_HEADER + sizeof (packet_send_StreamBasedToneCad), buffsend, pte); */ if (unistimdebug) { ast_verb(0, "Sending Stream Based Tone Frequency Component List Download %d %d\n", tone1, tone2); } tone1 *= 8; if (!tone2) { memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_single_freq, sizeof(packet_send_stream_based_tone_single_freq)); buffsend[10] = (tone1 & 0xff00) >> 8; buffsend[11] = (tone1 & 0x00ff); send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_single_freq), buffsend, pte); } else { tone2 *= 8; memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_dial_freq, sizeof(packet_send_stream_based_tone_dial_freq)); buffsend[10] = (tone1 & 0xff00) >> 8; buffsend[11] = (tone1 & 0x00ff); buffsend[12] = (tone2 & 0xff00) >> 8; buffsend[13] = (tone2 & 0x00ff); send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_dial_freq), buffsend, pte); } if (unistimdebug) { ast_verb(0, "Sending Stream Based Tone On\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_stream_based_tone_on, sizeof(packet_send_stream_based_tone_on)); send_client(SIZE_HEADER + sizeof(packet_send_stream_based_tone_on), buffsend, pte); } /* Positions for favorites |--------------------| | 5 2 | <-- not on screen in i2002 | 4 1 | | 3 0 | KEM Positions |--------------------| | 12 24 | | 11 23 | | 10 22 | | 9 21 | | 8 20 | | 7 19 | | 6 18 | | 5 17 | | 4 16 | | 3 15 | | 2 14 | | 1 13 | */ /* status (icons) : 00 = nothing, 2x/3x = see parser.h, 4x/5x = blink fast, 6x/7x = blink slow */ static void send_favorite(unsigned char pos, unsigned char status, struct unistimsession *pte, const char *text) { BUFFSEND; int i; if (unistimdebug) { ast_verb(0, "Sending favorite pos %d with status 0x%.2x\n", pos, (unsigned)status); } memcpy(buffsend + SIZE_HEADER, packet_send_favorite, sizeof(packet_send_favorite)); buffsend[10] = pos; buffsend[24] = pos; buffsend[25] = status; i = strlen(ustmtext(text, pte)); if (i > FAV_MAX_LENGTH) { i = FAV_MAX_LENGTH; } memcpy(buffsend + FAV_MAX_LENGTH + 1, ustmtext(text, pte), i); send_client(SIZE_HEADER + sizeof(packet_send_favorite), buffsend, pte); } static void send_favorite_short(unsigned char pos, unsigned char status, struct unistimsession *pte) { send_favorite(pos, status, pte, pte->device->softkeylabel[pos]); return; } static void send_favorite_selected(unsigned char status, struct unistimsession *pte) { if (pte->device->selected != -1) { send_favorite(pte->device->selected, status, pte, pte->device->softkeylabel[pte->device->selected]); } return; } static void send_expansion_short(unsigned char pos, unsigned char status, struct unistimsession *pte) { send_expansion_icon(pos, status, pte); send_expansion_text(pos, pte, ustmtext(pte->device->expsoftkeylabel[pos], pte)); send_expansion_next(pte); return; } static int soft_key_visible(struct unistim_device* d, unsigned char num) { if(d->height == 1 && num % 3 == 2) { return 0; } return 1; } static void refresh_all_favorite(struct unistimsession *pte) { unsigned char i = 0; char data[256]; struct unistim_line *line; line = AST_LIST_FIRST(&pte->device->lines); if (unistimdebug) { ast_verb(0, "Refreshing all favorite\n"); } for (i = 0; i < FAVNUM; i++) { unsigned char status = pte->device->softkeyicon[i]; if (!soft_key_visible(pte->device, i)) { continue; } if (!strcasecmp(pte->device->softkeylabel[i], "DND") && line) { if (!ast_db_get("DND", line->name, data, sizeof(data))) { status = FAV_ICON_SPEAKER_ONHOOK_WHITE; } } send_favorite_short(i, status, pte); } if (pte->device->hasexp) { for (i = 0; i < EXPNUM; i++) { send_expansion_short(i, FAV_ICON_NONE, pte); } } } static int is_key_favorite(struct unistim_device *d, int fav) { if ((fav < 0) && (fav > 5)) { return 0; } if (d->sline[fav]) { return 0; } if (d->softkeynumber[fav][0] == '\0') { return 0; } return 1; } static int is_key_line(struct unistim_device *d, int fav) { if ((fav < 0) && (fav > 5)) { return 0; } if (!d->sline[fav]) { return 0; } if (is_key_favorite(d, fav)) { return 0; } return 1; } static int get_active_softkey(struct unistimsession *pte) { return pte->device->selected; } static int get_avail_softkey(struct unistimsession *pte, const char* name) { int i; if (!is_key_line(pte->device, pte->device->selected)) { pte->device->selected = -1; } for (i = 0; i < FAVNUM; i++) { if (pte->device->selected != -1 && pte->device->selected != i) { continue; } if (!soft_key_visible(pte->device, i)) { continue; } if (pte->device->ssub[i]) { continue; } if (is_key_line(pte->device, i)) { if (name && strcmp(name, pte->device->sline[i]->name)) { continue; } if (unistimdebug) { ast_verb(0, "Found softkey %d for device %s\n", i, name); } return i; } } return -1; } /* Change the status for this phone (pte) and update for each phones where pte is bookmarked * use FAV_ICON_*_BLACK constant in status parameters */ static void change_favorite_icon(struct unistimsession *pte, unsigned char status) { struct unistim_device *d = devices; int i; /* Update the current phone line softkey icon */ if (pte->state != STATE_CLEANING) { int softkeylinepos = get_active_softkey(pte); if (softkeylinepos != -1) { send_favorite_short(softkeylinepos, status, pte); } } /* Notify other phones if we're in their bookmark */ while (d) { for (i = 0; i < FAVNUM; i++) { if (d->sp[i] == pte->device) { /* It's us ? */ if (d->softkeyicon[i] != status) { /* Avoid resending the same icon */ d->softkeyicon[i] = status; if (d->session) { send_favorite(i, status + 1, d->session, d->softkeylabel[i]); } } } } d = d->next; } } static int register_extension(const struct unistimsession *pte) { struct unistim_line *line; line = AST_LIST_FIRST(&pte->device->lines); if (unistimdebug) { ast_verb(0, "Trying to register extension '%s' into context '%s' to %s\n", pte->device->extension_number, pte->device->context, line->fullname); } return ast_add_extension(pte->device->context, 0, pte->device->extension_number, 1, NULL, NULL, "Dial", line->fullname, 0, "Unistim"); } static int unregister_extension(const struct unistimsession *pte) { if (unistimdebug) { ast_verb(0, "Trying to unregister extension '%s' context '%s'\n", pte->device->extension_number, pte->device->context); } return ast_context_remove_extension(pte->device->context, pte->device->extension_number, 1, "Unistim"); } /* Free memory allocated for a phone */ static void close_client(struct unistimsession *s) { struct unistim_subchannel *sub = NULL; struct unistimsession *cur, *prev = NULL; ast_mutex_lock(&sessionlock); cur = sessions; /* Looking for the session in the linked chain */ while (cur) { if (cur == s) { break; } prev = cur; cur = cur->next; } if (cur) { /* Session found ? */ if (cur->device) { /* This session was registered ? */ s->state = STATE_CLEANING; if (unistimdebug) { ast_verb(0, "close_client session %p device %p\n", s, s->device); } change_favorite_icon(s, FAV_ICON_NONE); ast_mutex_lock(&s->device->lock); AST_LIST_LOCK(&s->device->subs); AST_LIST_TRAVERSE_SAFE_BEGIN(&s->device->subs, sub, list) { if (!sub) { continue; } if (sub->owner) { /* Call in progress ? */ if (unistimdebug) { ast_verb(0, "Aborting call\n"); } ast_queue_hangup_with_cause(sub->owner, AST_CAUSE_NETWORK_OUT_OF_ORDER); } else { if (unistimdebug) { ast_debug(1, "Released sub %u of channel %s@%s\n", sub->subtype, sub->parent->name, s->device->name); } AST_LIST_REMOVE_CURRENT(list); unistim_free_sub(sub); } } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&s->device->subs); if (!ast_strlen_zero(s->device->extension_number)) { unregister_extension(s); } cur->device->session = NULL; ast_mutex_unlock(&s->device->lock); } else { if (unistimdebug) { ast_verb(0, "Freeing an unregistered client\n"); } } if (prev) { prev->next = cur->next; } else { sessions = cur->next; } ast_mutex_destroy(&s->lock); ast_free(s); } else { ast_log(LOG_WARNING, "Trying to delete non-existent session %p?\n", s); } ast_mutex_unlock(&sessionlock); return; } /* Return 1 if the session chained link was modified */ static int send_retransmit(struct unistimsession *pte) { int i; ast_mutex_lock(&pte->lock); if (++pte->nb_retransmit >= NB_MAX_RETRANSMIT) { if (unistimdebug) { ast_verb(0, "Too many retransmit - freeing client\n"); } ast_mutex_unlock(&pte->lock); close_client(pte); return 1; } pte->timeout = get_tick_count() + RETRANSMIT_TIMER; for (i = pte->last_buf_available - (pte->seq_server - pte->last_seq_ack); i < pte->last_buf_available; i++) { if (i < 0) { ast_log(LOG_WARNING, "Asked to retransmit an ACKed slot ! last_buf_available=%d, seq_server = #0x%.4x last_seq_ack = #0x%.4x\n", pte->last_buf_available, (unsigned)pte->seq_server, (unsigned)pte->last_seq_ack); continue; } if (unistimdebug) { unsigned short *sbuf = (unsigned short *) pte->wsabufsend[i].buf; unsigned short seq; seq = ntohs(sbuf[1]); ast_verb(0, "Retransmit slot #%d (seq=#0x%.4x), last ack was #0x%.4x\n", i, (unsigned)seq, (unsigned)pte->last_seq_ack); } send_raw_client(pte->wsabufsend[i].len, pte->wsabufsend[i].buf, &pte->sin, &pte->sout); } ast_mutex_unlock(&pte->lock); return 0; } /* inverse : TEXT_INVERSE : yes, TEXT_NORMAL : no */ static void send_text(unsigned char pos, unsigned char inverse, struct unistimsession *pte, const char *text) { int i; BUFFSEND; if (!text) { ast_log(LOG_ERROR, "Asked to display NULL text (pos %d, inverse flag %d)\n", pos, inverse); return; } if (pte->device && pte->device->height == 1 && pos != TEXT_LINE0) { return; } if (unistimdebug) { ast_verb(0, "Sending text at pos %d, inverse flag %d\n", pos, inverse); } memcpy(buffsend + SIZE_HEADER, packet_send_text, sizeof(packet_send_text)); buffsend[10] = pos; buffsend[11] = inverse; i = strlen(text); if (i > TEXT_LENGTH_MAX) { i = TEXT_LENGTH_MAX; } memcpy(buffsend + 12, text, i); send_client(SIZE_HEADER + sizeof(packet_send_text), buffsend, pte); } static void send_text_status(struct unistimsession *pte, const char *text) { BUFFSEND; int i; if (unistimdebug) { ast_verb(0, "Sending status text\n"); } if (pte->device) { if (pte->device->status_method == 1) { /* For new firmware and i2050 soft phone */ int n = strlen(text); /* Must send individual button separately */ int j; for (i = 0, j = 0; i < 4; i++, j += 7) { int pos = 0x08 + (i * 0x20); memcpy(buffsend + SIZE_HEADER, packet_send_status2, sizeof(packet_send_status2)); buffsend[9] = pos; memcpy(buffsend + 10, (j < n) ? (text + j) : " ", 7); send_client(SIZE_HEADER + sizeof(packet_send_status2), buffsend, pte); } return; } } memcpy(buffsend + SIZE_HEADER, packet_send_status, sizeof(packet_send_status)); i = strlen(text); if (i > STATUS_LENGTH_MAX) { i = STATUS_LENGTH_MAX; } memcpy(buffsend + 10, text, i); send_client(SIZE_HEADER + sizeof(packet_send_status), buffsend, pte); } static void send_led_update(struct unistimsession *pte, unsigned char led) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending led_update (%x)\n", (unsigned)led); } memcpy(buffsend + SIZE_HEADER, packet_send_led_update, sizeof(packet_send_led_update)); buffsend[9] = led; send_client(SIZE_HEADER + sizeof(packet_send_led_update), buffsend, pte); } static void send_mute(struct unistimsession *pte, unsigned char mute) { /* 0x00 = unmute TX, 0x01 = mute TX 0x20 = unmute RX, 0x21 = mute RX */ BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending mute packet (%x)\n", (unsigned)mute); } memcpy(buffsend + SIZE_HEADER, packet_send_mute, sizeof(packet_send_mute)); buffsend[9] = mute; send_client(SIZE_HEADER + sizeof(packet_send_mute), buffsend, pte); } /* output = OUTPUT_HANDSET, OUTPUT_HEADPHONE or OUTPUT_SPEAKER * volume = VOLUME_LOW, VOLUME_NORMAL, VOLUME_INSANELY_LOUD * mute = MUTE_OFF, MUTE_ON */ static void send_select_output(struct unistimsession *pte, unsigned char output, unsigned char volume, unsigned char mute) { BUFFSEND; int mute_icon = -1; if (unistimdebug) { ast_verb(0, "Sending select output packet output=%x volume=%x mute=%x\n", (unsigned)output, (unsigned)volume, (unsigned)mute); } memcpy(buffsend + SIZE_HEADER, packet_send_select_output, sizeof(packet_send_select_output)); buffsend[9] = output; if (output == OUTPUT_SPEAKER && volume == VOLUME_LOW) { volume = VOLUME_LOW_SPEAKER; } buffsend[10] = volume; if (mute == MUTE_ON_DISCRET) { buffsend[11] = MUTE_ON; } else { buffsend[11] = mute; } send_client(SIZE_HEADER + sizeof(packet_send_select_output), buffsend, pte); if (output == OUTPUT_HANDSET) { mute_icon = (mute == MUTE_ON) ? FAV_ICON_ONHOLD_BLACK : FAV_ICON_OFFHOOK_BLACK; send_led_update(pte, LED_SPEAKER_OFF); send_led_update(pte, LED_HEADPHONE_OFF); } else if (output == OUTPUT_HEADPHONE) { mute_icon = (mute == MUTE_ON)? FAV_ICON_HEADPHONES_ONHOLD : FAV_ICON_HEADPHONES; send_led_update(pte, LED_SPEAKER_OFF); send_led_update(pte, LED_HEADPHONE_ON); } else if (output == OUTPUT_SPEAKER) { send_led_update(pte, LED_SPEAKER_ON); send_led_update(pte, LED_HEADPHONE_OFF); if (pte->device->receiver_state == STATE_OFFHOOK) { mute_icon = (mute == MUTE_ON)? FAV_ICON_SPEAKER_ONHOLD_BLACK : FAV_ICON_SPEAKER_ONHOOK_BLACK; } else { mute_icon = (mute == MUTE_ON)? FAV_ICON_SPEAKER_ONHOLD_BLACK : FAV_ICON_SPEAKER_OFFHOOK_BLACK; } } else { ast_log(LOG_WARNING, "Invalid output (%d)\n", output); } if (mute_icon != -1) { change_favorite_icon(pte, mute_icon); } if (output != pte->device->output) { pte->device->previous_output = pte->device->output; } pte->device->output = output; } static void send_ring(struct unistimsession *pte, char volume, char style) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending ring packet\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_ring, sizeof(packet_send_ring)); buffsend[24] = style + 0x10; buffsend[29] = volume * 0x10; send_client(SIZE_HEADER + sizeof(packet_send_ring), buffsend, pte); } static void send_no_ring(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending no ring packet\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_no_ring, sizeof(packet_send_no_ring)); send_client(SIZE_HEADER + sizeof(packet_send_no_ring), buffsend, pte); } static void send_texttitle(struct unistimsession *pte, const char *text) { BUFFSEND; int i; if (unistimdebug) { ast_verb(0, "Sending title text\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_title, sizeof(packet_send_title)); i = strlen(text); if (i > 12) { i = 12; } memcpy(buffsend + 10, text, i); send_client(SIZE_HEADER + sizeof(packet_send_title), buffsend, pte); } static void send_idle_clock(struct unistimsession *pte) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, ""); } static void send_month_labels(struct unistimsession *pte, int month) { BUFFSEND; char month_name[MONTH_LABEL_SIZE + 1]; int i = 0; if (unistimdebug) { ast_verb(0, "Sending Month Labels\n"); } month_name[MONTH_LABEL_SIZE] = '\0'; memcpy(buffsend + SIZE_HEADER, packet_send_monthlabels_download, sizeof(packet_send_monthlabels_download)); while (i < 2) { memcpy(month_name, &monthlabels[month * MONTH_LABEL_SIZE], MONTH_LABEL_SIZE); memcpy(buffsend + SIZE_HEADER + 3 + i*MONTH_LABEL_SIZE, ustmtext(month_name, pte), MONTH_LABEL_SIZE); ast_log(LOG_WARNING,"%s\n", month_name); ast_log(LOG_WARNING,"%s\n", ustmtext(month_name, pte)); month = (month + 1)%12; i++; } send_client(SIZE_HEADER + sizeof(packet_send_monthlabels_download), buffsend, pte); } static void send_date_time(struct unistimsession *pte) { BUFFSEND; struct timeval now = ast_tvnow(); struct ast_tm atm = { 0, }; if (unistimdebug) { ast_verb(0, "Sending Time & Date\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_date_time, sizeof(packet_send_date_time)); ast_localtime(&now, &atm, NULL); buffsend[10] = (unsigned char) atm.tm_mon + 1; buffsend[11] = (unsigned char) atm.tm_mday; buffsend[12] = (unsigned char) atm.tm_hour; buffsend[13] = (unsigned char) atm.tm_min; send_client(SIZE_HEADER + sizeof(packet_send_date_time), buffsend, pte); send_month_labels(pte, atm.tm_mon); } static void send_date_time2(struct unistimsession *pte) { BUFFSEND; struct timeval now = ast_tvnow(); struct ast_tm atm = { 0, }; if (unistimdebug) { ast_verb(0, "Sending Time & Date #2\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_date_time2, sizeof(packet_send_date_time2)); ast_localtime(&now, &atm, NULL); if (pte->device) { buffsend[9] = pte->device->datetimeformat; } else { buffsend[9] = 61; } buffsend[14] = (unsigned char) atm.tm_mon + 1; buffsend[15] = (unsigned char) atm.tm_mday; buffsend[16] = (unsigned char) atm.tm_hour; buffsend[17] = (unsigned char) atm.tm_min; send_client(SIZE_HEADER + sizeof(packet_send_date_time2), buffsend, pte); } static void send_date_time3(struct unistimsession *pte) { BUFFSEND; struct timeval now = ast_tvnow(); struct ast_tm atm = { 0, }; if (unistimdebug) { ast_verb(0, "Sending Time & Date #3\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_date_time3, sizeof(packet_send_date_time3)); ast_localtime(&now, &atm, NULL); buffsend[10] = (unsigned char) atm.tm_mon + 1; buffsend[11] = (unsigned char) atm.tm_mday; buffsend[12] = (unsigned char) atm.tm_hour; buffsend[13] = (unsigned char) atm.tm_min; send_client(SIZE_HEADER + sizeof(packet_send_date_time3), buffsend, pte); } static void send_blink_cursor(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending set blink\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_blink_cursor, sizeof(packet_send_blink_cursor)); send_client(SIZE_HEADER + sizeof(packet_send_blink_cursor), buffsend, pte); return; } /* pos : 0xab (a=0/2/4 = line ; b = row) */ static void send_cursor_pos(struct unistimsession *pte, unsigned char pos) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending set cursor position\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_set_pos_cursor, sizeof(packet_send_set_pos_cursor)); buffsend[11] = pos; send_client(SIZE_HEADER + sizeof(packet_send_set_pos_cursor), buffsend, pte); return; } static void send_charset_update(struct unistimsession *pte, int charset) { const unsigned char* packet_send_charset; int packet_size; BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending set default charset\n"); } if (charset == LANG_DEFAULT) { charset = options_languages[find_language(pte->device->language)].encoding; } switch (charset) { case ISO_8859_2: packet_send_charset = packet_send_charset_iso_8859_2; packet_size = sizeof(packet_send_charset_iso_8859_2); break; case ISO_8859_4: packet_send_charset = packet_send_charset_iso_8859_4; packet_size = sizeof(packet_send_charset_iso_8859_4); break; case ISO_8859_5: packet_send_charset = packet_send_charset_iso_8859_5; packet_size = sizeof(packet_send_charset_iso_8859_5); break; case ISO_2022_JP: packet_send_charset = packet_send_charset_iso_2022_jp; packet_size = sizeof(packet_send_charset_iso_2022_jp); break; case ISO_8859_1: default: packet_send_charset = packet_send_charset_iso_8859_1; packet_size = sizeof(packet_send_charset_iso_8859_1); } memcpy(buffsend + SIZE_HEADER, packet_send_charset, packet_size); send_client(SIZE_HEADER + packet_size, buffsend, pte); return; } static void rcv_resume_connection_with_server(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "ResumeConnectionWithServer received\n"); ast_verb(0, "Sending packet_send_query_mac_address\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_query_mac_address, sizeof(packet_send_query_mac_address)); send_client(SIZE_HEADER + sizeof(packet_send_query_mac_address), buffsend, pte); return; } static int unistim_register(struct unistimsession *s) { struct unistim_device *d; ast_mutex_lock(&devicelock); d = devices; while (d) { if (!strcasecmp(s->macaddr, d->id)) { /* XXX Deal with IP authentication */ s->device = d; d->session = s; d->codec_number = DEFAULT_CODEC; d->missed_call = 0; d->receiver_state = STATE_ONHOOK; break; } d = d->next; } ast_mutex_unlock(&devicelock); if (!d) { return 0; } return 1; } static void unistim_line_copy(struct unistim_line *dst, struct unistim_line *src) { struct ast_format_cap *tmp = src->cap; memcpy(dst, src, sizeof(*dst)); /* this over writes the cap ptr, so we have to reset it */ src->cap = tmp; ast_format_cap_append_from_cap(src->cap, dst->cap, AST_MEDIA_TYPE_UNKNOWN); } static struct unistim_line *unistim_line_destroy(struct unistim_line *l) { if (!l) { return NULL; } ao2_ref(l->cap, -1); ast_free(l); return NULL; } static struct unistim_line *unistim_line_alloc(void) { struct unistim_line *l; if (!(l = ast_calloc(1, sizeof(*l)))) { return NULL; } if (!(l->cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { ast_free(l); return NULL; } return l; } static int unistim_free_sub(struct unistim_subchannel *sub) { if (unistimdebug) { ast_debug(1, "Released sub %u of channel %s@%s\n", sub->subtype, sub->parent->name, sub->parent->parent->name); } ast_mutex_destroy(&sub->lock); ast_free(sub); return 0; } static struct unistim_subchannel *unistim_alloc_sub(struct unistim_device *d, int x) { struct unistim_subchannel *sub; if (!(sub = ast_calloc(1, sizeof(*sub)))) { return NULL; } if (unistimdebug) { ast_verb(3, "Allocating UNISTIM subchannel #%d on %s ptr=%p\n", x, d->name, sub); } sub->ss_thread = AST_PTHREADT_NULL; sub->subtype = x; AST_LIST_LOCK(&d->subs); AST_LIST_INSERT_TAIL(&d->subs, sub, list); AST_LIST_UNLOCK(&d->subs); ast_mutex_init(&sub->lock); return sub; } static int unistim_unalloc_sub(struct unistim_device *d, struct unistim_subchannel *sub) { struct unistim_subchannel *s; AST_LIST_LOCK(&d->subs); AST_LIST_TRAVERSE_SAFE_BEGIN(&d->subs, s, list) { if (!s) { continue; } if (s != sub) { continue; } AST_LIST_REMOVE_CURRENT(list); unistim_free_sub(sub); } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&d->subs); return 0; } static const char *subtype_tostr(const int type) { switch (type) { case SUB_REAL: return "REAL"; case SUB_ONHOLD: return "ONHOLD"; case SUB_RING: return "RINGING"; case SUB_THREEWAY: return "THREEWAY"; } return "UNKNOWN"; } static const char *ptestate_tostr(const int type) { switch (type) { case STATE_INIT: return "INIT"; case STATE_AUTHDENY: return "AUTHDENY"; case STATE_MAINPAGE: return "MAINPAGE"; case STATE_EXTENSION: return "EXTENSION"; case STATE_DIALPAGE: return "DIALPAGE"; case STATE_RINGING: return "RINGING"; case STATE_CALL: return "CALL"; case STATE_SELECTOPTION: return "SELECTOPTION"; case STATE_SELECTCODEC: return "SELECTCODEC"; case STATE_SELECTLANGUAGE: return "SELECTLANGUAGE"; case STATE_CLEANING: return "CLEARING"; case STATE_HISTORY: return "HISTORY"; } return "UNKNOWN"; } static void rcv_mac_addr(struct unistimsession *pte, const unsigned char *buf) { BUFFSEND; int tmp, i = 0; char addrmac[19]; int res = 0; for (tmp = 15; tmp < 15 + SIZE_HEADER; tmp++) { sprintf(&addrmac[i], "%.2x", (unsigned) buf[tmp]); i += 2; } if (unistimdebug) { ast_verb(0, "MAC Address received: %s\n", addrmac); } strcpy(pte->macaddr, addrmac); res = unistim_register(pte); if (!res) { switch (autoprovisioning) { case AUTOPROVISIONING_NO: ast_log(LOG_WARNING, "No entry found for this phone : %s\n", addrmac); pte->state = STATE_AUTHDENY; break; case AUTOPROVISIONING_YES: { struct unistim_device *d = NULL, *newd = NULL; struct unistim_line *newl = NULL, *l = NULL; if (unistimdebug) { ast_verb(0, "New phone, autoprovisioning on\n"); } /* First : locate the [template] section */ ast_mutex_lock(&devicelock); d = devices; while (d) { if (strcasecmp(d->name, "template")) { d = d->next; continue; } /* Found, cloning this entry */ if (!(newd = ast_malloc(sizeof(*newd)))) { ast_mutex_unlock(&devicelock); return; } memcpy(newd, d, sizeof(*newd)); ast_mutex_init(&newd->lock); newd->lines.first = NULL; newd->lines.last = NULL; AST_LIST_LOCK(&d->lines); AST_LIST_TRAVERSE(&d->lines, l, list) { if (!(newl = unistim_line_alloc())) { break; } unistim_line_copy(l, newl); newl->parent = newd; ast_copy_string(newl->name, l->name, sizeof(newl->name)); snprintf(newl->fullname, sizeof(newl->fullname), "USTM/%s@%s", newl->name, newd->name); snprintf(l->name, sizeof(l->name), "%d", atoi(l->name) + 1); AST_LIST_LOCK(&newd->lines); AST_LIST_INSERT_TAIL(&newd->lines, newl, list); AST_LIST_UNLOCK(&newd->lines); } AST_LIST_UNLOCK(&d->lines); if (!newl) { ast_free(newd); ast_mutex_unlock(&devicelock); } /* Ok, now updating some fields */ ast_copy_string(newd->id, addrmac, sizeof(newd->id)); ast_copy_string(newd->name, addrmac, sizeof(newd->name)); if (newd->extension == EXTENSION_NONE) { newd->extension = EXTENSION_ASK; } newd->receiver_state = STATE_ONHOOK; newd->session = pte; newd->language[0] = '\0'; newd->to_delete = -1; newd->next = NULL; pte->device = newd; /* Go to the end of the linked chain */ while (d->next) { d = d->next; } d->next = newd; d = newd; break; } ast_mutex_unlock(&devicelock); if (!d) { ast_log(LOG_WARNING, "No entry [template] found in unistim.conf\n"); pte->state = STATE_AUTHDENY; } } break; case AUTOPROVISIONING_TN: pte->state = STATE_AUTHDENY; break; default: ast_log(LOG_WARNING, "Internal error : unknown autoprovisioning value = %u\n", autoprovisioning); } } if (pte->state != STATE_AUTHDENY) { struct unistim_line *line; struct unistim_subchannel *sub; ast_verb(3, "Device '%s' successfuly registered\n", pte->device->name); AST_LIST_LOCK(&pte->device->subs); AST_LIST_TRAVERSE_SAFE_BEGIN(&pte->device->subs, sub, list) { if (sub) { ast_log(LOG_ERROR, "Subchannel lost sice reboot. Hanged channel may apear!\n"); AST_LIST_REMOVE_CURRENT(list); ast_free(sub); } } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&pte->device->subs); switch (pte->device->extension) { case EXTENSION_NONE: pte->state = STATE_MAINPAGE; break; case EXTENSION_ASK: /* Checking if we already have an extension number */ if (ast_strlen_zero(pte->device->extension_number)) { pte->state = STATE_EXTENSION; } else { /* Yes, because of a phone reboot. We don't ask again for the TN */ if (register_extension(pte)) { pte->state = STATE_EXTENSION; } else { pte->state = STATE_MAINPAGE; } } break; case EXTENSION_LINE: line = AST_LIST_FIRST(&pte->device->lines); ast_copy_string(pte->device->extension_number, line->name, sizeof(pte->device->extension_number)); if (register_extension(pte)) { pte->state = STATE_EXTENSION; } else { pte->state = STATE_MAINPAGE; } break; case EXTENSION_TN: /* If we are here, it's because of a phone reboot */ pte->state = STATE_MAINPAGE; break; default: ast_log(LOG_WARNING, "Internal error, extension value unknown : %u\n", pte->device->extension); pte->state = STATE_AUTHDENY; break; } } if (pte->state == STATE_EXTENSION) { if (pte->device->extension != EXTENSION_TN) { pte->device->extension = EXTENSION_ASK; } pte->device->extension_number[0] = '\0'; } if (unistimdebug) { ast_verb(0, "\nSending S1\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_S1, sizeof(packet_send_S1)); send_client(SIZE_HEADER + sizeof(packet_send_S1), buffsend, pte); if (unistimdebug) { ast_verb(0, "Sending query_basic_manager_04\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_query_basic_manager_04, sizeof(packet_send_query_basic_manager_04)); send_client(SIZE_HEADER + sizeof(packet_send_query_basic_manager_04), buffsend, pte); if (unistimdebug) { ast_verb(0, "Sending query_basic_manager_10\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_query_basic_manager_10, sizeof(packet_send_query_basic_manager_10)); send_client(SIZE_HEADER + sizeof(packet_send_query_basic_manager_10), buffsend, pte); send_date_time(pte); return; } static int write_entry_history(struct unistimsession *pte, FILE * f, char c, char *line1) { if (fwrite(&c, 1, 1, f) != 1) { display_last_error("Unable to write history log header."); return -1; } if (fwrite(line1, TEXT_LENGTH_MAX, 1, f) != 1) { display_last_error("Unable to write history entry - date."); return -1; } if (fwrite(pte->device->lst_cid, TEXT_LENGTH_MAX, 1, f) != 1) { display_last_error("Unable to write history entry - callerid."); return -1; } if (fwrite(pte->device->lst_cnm, TEXT_LENGTH_MAX, 1, f) != 1) { display_last_error("Unable to write history entry - callername."); return -1; } return 0; } static int write_history(struct unistimsession *pte, char way, char ismissed) { char tmp[AST_CONFIG_MAX_PATH], tmp2[AST_CONFIG_MAX_PATH]; char line1[TEXT_LENGTH_MAX + 1]; char count = 0, *histbuf; int size; FILE *f, *f2; struct timeval now = ast_tvnow(); struct ast_tm atm = { 0, }; if (!pte->device) { return -1; } if (!pte->device->callhistory) { return 0; } if (strchr(pte->device->name, '/') || (pte->device->name[0] == '.')) { ast_log(LOG_WARNING, "Account code '%s' insecure for writing file\n", pte->device->name); return -1; } snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_LOG_DIR, USTM_LOG_DIR); if (ast_mkdir(tmp, 0770)) { ast_log(LOG_WARNING, "Unable to create directory for history\n"); return -1; } ast_localtime(&now, &atm, NULL); if (ismissed) { if (way == 'i') { ast_copy_string(tmp2, ustmtext("Miss", pte), sizeof(tmp2)); } else { ast_copy_string(tmp2, ustmtext("Fail", pte), sizeof(tmp2)); } } else { ast_copy_string(tmp2, ustmtext("Answ", pte), sizeof(tmp2)); } snprintf(line1, sizeof(line1), "%04d/%02d/%02d %02d:%02d:%02d %s", atm.tm_year + 1900, atm.tm_mon + 1, atm.tm_mday, atm.tm_hour, atm.tm_min, atm.tm_sec, tmp2); snprintf(tmp, sizeof(tmp), "%s/%s/%s-%c.csv", ast_config_AST_LOG_DIR, USTM_LOG_DIR, pte->device->name, way); if ((f = fopen(tmp, "r"))) { struct stat bufstat; if (stat(tmp, &bufstat)) { display_last_error("Unable to stat history log."); fclose(f); return -1; } size = 1 + (MAX_ENTRY_LOG * TEXT_LENGTH_MAX * 3); if (bufstat.st_size != size) { ast_log(LOG_WARNING, "History file %s has an incorrect size (%d instead of %d). It will be replaced by a new one.", tmp, (int) bufstat.st_size, size); fclose(f); f = NULL; count = 1; } } /* If we can't open the log file, we create a brand new one */ if (!f) { char c = 1; int i; if ((errno != ENOENT) && (count == 0)) { display_last_error("Unable to open history log."); return -1; } f = fopen(tmp, "w"); if (!f) { display_last_error("Unable to create history log."); return -1; } if (write_entry_history(pte, f, c, line1)) { fclose(f); return -1; } memset(line1, ' ', TEXT_LENGTH_MAX); for (i = 3; i < MAX_ENTRY_LOG * 3; i++) { if (fwrite(line1, TEXT_LENGTH_MAX, 1, f) != 1) { display_last_error("Unable to write history entry - stuffing."); fclose(f); return -1; } } if (fclose(f)) { display_last_error("Unable to close history - creation."); } return 0; } /* We can open the log file, we create a temporary one, we add our entry and copy the rest */ if (fread(&count, 1, 1, f) != 1) { display_last_error("Unable to read history header."); fclose(f); return -1; } if (count > MAX_ENTRY_LOG) { ast_log(LOG_WARNING, "Invalid count in history header of %s (%d max %d)\n", tmp, count, MAX_ENTRY_LOG); fclose(f); return -1; } snprintf(tmp2, sizeof(tmp2), "%s/%s/%s-%c.csv.tmp", ast_config_AST_LOG_DIR, USTM_LOG_DIR, pte->device->name, way); if (!(f2 = fopen(tmp2, "w"))) { display_last_error("Unable to create temporary history log."); fclose(f); return -1; } if (++count > MAX_ENTRY_LOG) { count = MAX_ENTRY_LOG; } if (write_entry_history(pte, f2, count, line1)) { fclose(f); fclose(f2); return -1; } size = (MAX_ENTRY_LOG - 1) * TEXT_LENGTH_MAX * 3; if (!(histbuf = ast_malloc(size))) { fclose(f); fclose(f2); return -1; } if (fread(histbuf, size, 1, f) != 1) { ast_free(histbuf); fclose(f); fclose(f2); display_last_error("Unable to read previous history entries."); return -1; } if (fwrite(histbuf, size, 1, f2) != 1) { ast_free(histbuf); fclose(f); fclose(f2); display_last_error("Unable to write previous history entries."); return -1; } ast_free(histbuf); if (fclose(f)) { display_last_error("Unable to close history log."); } if (fclose(f2)) { display_last_error("Unable to close temporary history log."); } if (unlink(tmp)) { display_last_error("Unable to remove old history log."); } if (rename(tmp2, tmp)) { display_last_error("Unable to rename new history log."); } return 0; } static int attempt_transfer(struct unistim_subchannel *p1, struct unistim_subchannel *p2) { RAII_VAR(struct ast_channel *, chana, NULL, ast_channel_unref); RAII_VAR(struct ast_channel *, chanb, NULL, ast_channel_unref); if (!p1->owner || !p2->owner) { ast_log(LOG_WARNING, "Transfer attempted without dual ownership?\n"); return -1; } chana = ast_channel_ref(p1->owner); chanb = ast_channel_ref(p2->owner); switch (ast_bridge_transfer_attended(chana, chanb)) { case AST_BRIDGE_TRANSFER_INVALID: ast_log(LOG_WARNING, "Transfer failed. Invalid bridge setup\n"); break; case AST_BRIDGE_TRANSFER_NOT_PERMITTED: ast_log(LOG_WARNING, "Transfer not permitted\n"); break; case AST_BRIDGE_TRANSFER_FAIL: ast_log(LOG_WARNING, "Transfer encountered internal error\n"); break; case AST_BRIDGE_TRANSFER_SUCCESS: return 0; } /* Control only reaches this point if transfer has failed */ ast_softhangup_nolock(chana, AST_SOFTHANGUP_DEV); ast_softhangup_nolock(chanb, AST_SOFTHANGUP_DEV); return -1; } void change_callerid(struct unistimsession *pte, int type, char *callerid) { char *data; int size; if (type) { data = pte->device->lst_cnm; } else { data = pte->device->lst_cid; } /* This is very nearly strncpy(), except that the remaining buffer * is padded with ' ', instead of '\0' */ memset(data, ' ', TEXT_LENGTH_MAX); size = strlen(callerid); if (size > TEXT_LENGTH_MAX) { size = TEXT_LENGTH_MAX; } memcpy(data, callerid, size); } static struct unistim_subchannel* get_sub(struct unistim_device *device, int type) { struct unistim_subchannel *sub = NULL; AST_LIST_LOCK(&device->subs); AST_LIST_TRAVERSE(&device->subs, sub, list) { if (!sub) { continue; } if (sub->subtype == type) { break; } } AST_LIST_UNLOCK(&device->subs); return sub; } static void sub_start_silence(struct unistimsession *pte, struct unistim_subchannel *sub) { /* Silence our channel */ if (!pte->device->silence_generator) { pte->device->silence_generator = ast_channel_start_silence_generator(sub->owner); if (pte->device->silence_generator == NULL) { ast_log(LOG_WARNING, "Unable to start a silence generator.\n"); } else if (unistimdebug) { ast_verb(0, "Starting silence generator\n"); } } } static void sub_stop_silence(struct unistimsession *pte, struct unistim_subchannel *sub) { /* Stop the silence generator */ if (pte->device->silence_generator) { if (unistimdebug) { ast_verb(0, "Stopping silence generator\n"); } if (sub->owner) { ast_channel_stop_silence_generator(sub->owner, pte->device->silence_generator); } else { ast_log(LOG_WARNING, "Trying to stop silence generator on a null channel!\n"); } pte->device->silence_generator = NULL; } } static void sub_hold(struct unistimsession *pte, struct unistim_subchannel *sub) { if (!sub) { return; } sub->moh = 1; sub->subtype = SUB_ONHOLD; send_favorite_short(sub->softkey, FAV_ICON_ONHOLD_BLACK + FAV_BLINK_SLOW, pte); send_select_output(pte, pte->device->output, pte->device->volume, MUTE_ON); send_stop_timer(pte); if (sub->owner) { ast_queue_hold(sub->owner, NULL); send_end_call(pte); } return; } static void sub_unhold(struct unistimsession *pte, struct unistim_subchannel *sub) { struct unistim_subchannel *sub_real; sub_real = get_sub(pte->device, SUB_REAL); if (sub_real) { sub_hold(pte, sub_real); } sub->moh = 0; sub->subtype = SUB_REAL; send_favorite_short(sub->softkey, FAV_ICON_OFFHOOK_BLACK, pte); send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF); send_start_timer(pte); if (sub->owner) { ast_queue_unhold(sub->owner); if (sub->rtp) { send_start_rtp(sub); } } return; } static void close_call(struct unistimsession *pte) { struct unistim_subchannel *sub, *sub_transf; sub = get_sub(pte->device, SUB_REAL); sub_transf = get_sub(pte->device, SUB_THREEWAY); send_stop_timer(pte); if (!sub) { ast_log(LOG_WARNING, "Close call without sub\n"); return; } send_favorite_short(sub->softkey, FAV_LINE_ICON, pte); if (sub->owner) { sub->alreadygone = 1; if (sub_transf) { sub_transf->alreadygone = 1; if (attempt_transfer(sub, sub_transf) < 0) { ast_verb(0, "attempt_transfer failed.\n"); } } else { ast_queue_hangup(sub->owner); } } else { if (sub_transf) { if (sub_transf->owner) { ast_queue_hangup_with_cause(sub_transf->owner, AST_CAUSE_NORMAL_CLEARING); } else { ast_log(LOG_WARNING, "threeway sub without owner\n"); } } else { ast_verb(0, "USTM(%s@%s-%d) channel already destroyed\n", sub->parent->name, pte->device->name, sub->softkey); } } change_callerid(pte, 0, pte->device->redial_number); change_callerid(pte, 1, ""); write_history(pte, 'o', pte->device->missed_call); pte->device->missed_call = 0; show_main_page(pte); return; } static void ignore_call(struct unistimsession *pte) { send_no_ring(pte); return; } static void discard_call(struct unistimsession *pte) { struct unistim_subchannel* sub; sub = get_sub(pte->device, SUB_RING); if (!sub) { return; } ast_queue_hangup_with_cause(sub->owner, AST_CAUSE_NORMAL_CLEARING); return; } static void *unistim_ss(void *data) { struct ast_channel *chan = data; struct unistim_subchannel *sub = ast_channel_tech_pvt(chan); struct unistim_line *l = sub->parent; struct unistimsession *s = l->parent->session; int res; if (!s) { return NULL; } ast_verb(3, "Starting switch on '%s@%s-%d' to %s\n", l->name, l->parent->name, sub->softkey, s->device->phone_number); ast_channel_lock(chan); ast_channel_exten_set(chan, s->device->phone_number); ast_setstate(chan, AST_STATE_RING); ast_channel_unlock(chan); ast_copy_string(s->device->redial_number, s->device->phone_number, sizeof(s->device->redial_number)); res = ast_pbx_run(chan); if (res) { ast_log(LOG_WARNING, "PBX exited non-zero\n"); send_tone(s, 1000, 0); } return NULL; } static int find_rtp_port(struct unistim_subchannel *s) { struct unistim_subchannel *sub = NULL; int rtp_start = s->parent->parent->rtp_port; struct ast_sockaddr us_tmp; struct sockaddr_in us = { 0, }; AST_LIST_LOCK(&s->parent->parent->subs); AST_LIST_TRAVERSE(&s->parent->parent->subs, sub, list) { if (!sub) { continue; } if (sub->rtp) { ast_rtp_instance_get_remote_address(sub->rtp, &us_tmp); ast_sockaddr_to_sin(&us_tmp, &us); if (htons(us.sin_port)) { rtp_start = htons(us.sin_port) + 1; break; } } } AST_LIST_UNLOCK(&s->parent->parent->subs); return rtp_start; } static void send_start_rtp(struct unistim_subchannel *sub) { BUFFSEND; int codec; struct sockaddr_in public = { 0, }; struct sockaddr_in us = { 0, }; struct sockaddr_in sin = { 0, }; struct ast_sockaddr us_tmp; struct ast_sockaddr sin_tmp; struct unistimsession *pte; ast_rtp_instance_get_local_address(sub->rtp, &us_tmp); ast_sockaddr_to_sin(&us_tmp, &us); ast_rtp_instance_get_remote_address(sub->rtp, &sin_tmp); ast_sockaddr_to_sin(&sin_tmp, &sin); /* Setting up RTP of the phone */ if (public_ip.sin_family == 0) { /* NAT IP override ? */ memcpy(&public, &us, sizeof(public)); /* No defined, using IP from recvmsg */ } else { memcpy(&public, &public_ip, sizeof(public)); /* override */ } if (unistimdebug) { ast_verb(0, "RTP started : Our IP/port is : %s:%hd with codec %s\n", ast_inet_ntoa(us.sin_addr), htons(us.sin_port), ast_format_get_name(ast_channel_readformat(sub->owner))); ast_verb(0, "Starting phone RTP stack. Our public IP is %s\n", ast_inet_ntoa(public.sin_addr)); } pte = sub->parent->parent->session; codec = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(sub->rtp), 1, ast_channel_readformat(sub->owner), 0); if ((ast_format_cmp(ast_channel_readformat(sub->owner), ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) || (ast_format_cmp(ast_channel_readformat(sub->owner), ast_format_alaw) == AST_FORMAT_CMP_EQUAL)) { if (unistimdebug) { ast_verb(0, "Sending packet_send_rtp_packet_size for codec %d\n", codec); } memcpy(buffsend + SIZE_HEADER, packet_send_rtp_packet_size, sizeof(packet_send_rtp_packet_size)); buffsend[10] = (int) codec & 0xffffffffLL; send_client(SIZE_HEADER + sizeof(packet_send_rtp_packet_size), buffsend, pte); } if (unistimdebug) { ast_verb(0, "Sending Jitter Buffer Parameters Configuration\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_jitter_buffer_conf, sizeof(packet_send_jitter_buffer_conf)); send_client(SIZE_HEADER + sizeof(packet_send_jitter_buffer_conf), buffsend, pte); if (pte->device->rtp_method != 0) { uint16_t rtcpsin_port = htons(us.sin_port) + 1; /* RTCP port is RTP + 1 */ if (unistimdebug) { ast_verb(0, "Sending OpenAudioStreamTX using method #%d\n", pte->device->rtp_method); } if (pte->device->rtp_method == 3) { memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_tx3, sizeof(packet_send_open_audio_stream_tx3)); } else { memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_tx, sizeof(packet_send_open_audio_stream_tx)); } if (pte->device->rtp_method != 2) { memcpy(buffsend + 28, &public.sin_addr, sizeof(public.sin_addr)); buffsend[20] = (htons(sin.sin_port) & 0xff00) >> 8; buffsend[21] = (htons(sin.sin_port) & 0x00ff); buffsend[23] = (rtcpsin_port & 0x00ff); buffsend[22] = (rtcpsin_port & 0xff00) >> 8; buffsend[25] = (us.sin_port & 0xff00) >> 8; buffsend[24] = (us.sin_port & 0x00ff); buffsend[27] = (rtcpsin_port & 0x00ff); buffsend[26] = (rtcpsin_port & 0xff00) >> 8; } else { memcpy(buffsend + 23, &public.sin_addr, sizeof(public.sin_addr)); buffsend[15] = (htons(sin.sin_port) & 0xff00) >> 8; buffsend[16] = (htons(sin.sin_port) & 0x00ff); buffsend[20] = (us.sin_port & 0xff00) >> 8; buffsend[19] = (us.sin_port & 0x00ff); } buffsend[11] = codec; /* rx */ buffsend[12] = codec; /* tx */ send_client(SIZE_HEADER + sizeof(packet_send_open_audio_stream_tx), buffsend, pte); if (unistimdebug) { ast_verb(0, "Sending OpenAudioStreamRX\n"); } if (pte->device->rtp_method == 3) { memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_rx3, sizeof(packet_send_open_audio_stream_rx3)); } else { memcpy(buffsend + SIZE_HEADER, packet_send_open_audio_stream_rx, sizeof(packet_send_open_audio_stream_rx)); } if (pte->device->rtp_method != 2) { memcpy(buffsend + 28, &public.sin_addr, sizeof(public.sin_addr)); buffsend[20] = (htons(sin.sin_port) & 0xff00) >> 8; buffsend[21] = (htons(sin.sin_port) & 0x00ff); buffsend[23] = (rtcpsin_port & 0x00ff); buffsend[22] = (rtcpsin_port & 0xff00) >> 8; buffsend[25] = (us.sin_port & 0xff00) >> 8; buffsend[24] = (us.sin_port & 0x00ff); buffsend[27] = (rtcpsin_port & 0x00ff); buffsend[26] = (rtcpsin_port & 0xff00) >> 8; } else { memcpy(buffsend + 23, &public.sin_addr, sizeof(public.sin_addr)); buffsend[15] = (htons(sin.sin_port) & 0xff00) >> 8; buffsend[16] = (htons(sin.sin_port) & 0x00ff); buffsend[20] = (us.sin_port & 0xff00) >> 8; buffsend[19] = (us.sin_port & 0x00ff); } buffsend[11] = codec; /* rx */ buffsend[12] = codec; /* tx */ send_client(SIZE_HEADER + sizeof(packet_send_open_audio_stream_rx), buffsend, pte); } else { uint16_t rtcpsin_port = htons(us.sin_port) + 1; /* RTCP port is RTP + 1 */ if (unistimdebug) { ast_verb(0, "Sending packet_send_call default method\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_call, sizeof(packet_send_call)); memcpy(buffsend + 53, &public.sin_addr, sizeof(public.sin_addr)); /* Destination port when sending RTP */ buffsend[49] = (us.sin_port & 0x00ff); buffsend[50] = (us.sin_port & 0xff00) >> 8; /* Destination port when sending RTCP */ buffsend[52] = (rtcpsin_port & 0x00ff); buffsend[51] = (rtcpsin_port & 0xff00) >> 8; /* Codec */ buffsend[40] = codec; buffsend[41] = codec; if (ast_format_cmp(ast_channel_readformat(sub->owner), ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { buffsend[42] = 1; /* 1 = 20ms (160 bytes), 2 = 40ms (320 bytes) */ } else if (ast_format_cmp(ast_channel_readformat(sub->owner), ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { buffsend[42] = 1; /* 1 = 20ms (160 bytes), 2 = 40ms (320 bytes) */ } else if (ast_format_cmp(ast_channel_readformat(sub->owner), ast_format_g723) == AST_FORMAT_CMP_EQUAL) { buffsend[42] = 2; /* 1 = 30ms (24 bytes), 2 = 60 ms (48 bytes) */ } else if (ast_format_cmp(ast_channel_readformat(sub->owner), ast_format_g729) == AST_FORMAT_CMP_EQUAL) { buffsend[42] = 2; /* 1 = 10ms (10 bytes), 2 = 20ms (20 bytes) */ } else { ast_log(LOG_WARNING, "Unsupported codec %s!\n", ast_format_get_name(ast_channel_readformat(sub->owner))); } /* Source port for transmit RTP and Destination port for receiving RTP */ buffsend[45] = (htons(sin.sin_port) & 0xff00) >> 8; buffsend[46] = (htons(sin.sin_port) & 0x00ff); buffsend[47] = (rtcpsin_port & 0xff00) >> 8; buffsend[48] = (rtcpsin_port & 0x00ff); send_client(SIZE_HEADER + sizeof(packet_send_call), buffsend, pte); } } static void start_rtp(struct unistim_subchannel *sub) { struct sockaddr_in sin = { 0, }; struct sockaddr_in sout = { 0, }; struct ast_sockaddr sin_tmp; struct ast_sockaddr sout_tmp; /* Sanity checks */ if (!sub) { ast_log(LOG_WARNING, "start_rtp with a null subchannel !\n"); return; } if (!sub->parent) { ast_log(LOG_WARNING, "start_rtp with a null line!\n"); return; } if (!sub->parent->parent) { ast_log(LOG_WARNING, "start_rtp with a null device!\n"); return; } if (!sub->parent->parent->session) { ast_log(LOG_WARNING, "start_rtp with a null session!\n"); return; } if (!sub->owner) { ast_log(LOG_WARNING, "start_rtp with a null asterisk channel!\n"); return; } sout = sub->parent->parent->session->sout; ast_mutex_lock(&sub->lock); /* Allocate the RTP */ if (unistimdebug) { ast_verb(0, "Starting RTP. Bind on %s\n", ast_inet_ntoa(sout.sin_addr)); } ast_sockaddr_from_sin(&sout_tmp, &sout); sub->rtp = ast_rtp_instance_new("asterisk", sched, &sout_tmp, NULL); if (!sub->rtp) { ast_log(LOG_WARNING, "Unable to create RTP session: %s binaddr=%s\n", strerror(errno), ast_inet_ntoa(sout.sin_addr)); ast_mutex_unlock(&sub->lock); return; } ast_rtp_instance_set_prop(sub->rtp, AST_RTP_PROPERTY_RTCP, 1); ast_rtp_instance_set_channel_id(sub->rtp, ast_channel_uniqueid(sub->owner)); ast_channel_internal_fd_set(sub->owner, 0, ast_rtp_instance_fd(sub->rtp, 0)); ast_channel_internal_fd_set(sub->owner, 1, ast_rtp_instance_fd(sub->rtp, 1)); ast_rtp_instance_set_qos(sub->rtp, qos.tos_audio, qos.cos_audio, "UNISTIM RTP"); ast_rtp_instance_set_prop(sub->rtp, AST_RTP_PROPERTY_NAT, sub->parent->parent->nat); /* Create the RTP connection */ sin.sin_family = AF_INET; /* Setting up RTP for our side */ memcpy(&sin.sin_addr, &sub->parent->parent->session->sin.sin_addr, sizeof(sin.sin_addr)); sin.sin_port = htons(find_rtp_port(sub)); ast_sockaddr_from_sin(&sin_tmp, &sin); ast_rtp_instance_set_remote_address(sub->rtp, &sin_tmp); if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(sub->owner), ast_channel_readformat(sub->owner)) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_format *tmpfmt; struct ast_str *cap_buf = ast_str_alloca(64); tmpfmt = ast_format_cap_get_format(ast_channel_nativeformats(sub->owner), 0); ast_log(LOG_WARNING, "Our read/writeformat has been changed to something incompatible: %s, using %s best codec from %s\n", ast_format_get_name(ast_channel_readformat(sub->owner)), ast_format_get_name(tmpfmt), ast_format_cap_get_names(ast_channel_nativeformats(sub->owner), &cap_buf)); ast_channel_set_readformat(sub->owner, tmpfmt); ast_channel_set_writeformat(sub->owner, tmpfmt); ao2_ref(tmpfmt, -1); } send_start_rtp(sub); ast_mutex_unlock(&sub->lock); } static void send_dial_tone(struct unistimsession *pte) { struct ast_tone_zone_sound *ts = NULL; struct ast_tone_zone_part tone_data; char *s = NULL; char *ind; if ((ts = ast_get_indication_tone(pte->device->tz, "dial"))) { ind = ast_strdupa(ts->data); s = strsep(&ind, ","); ast_tone_zone_part_parse(s, &tone_data); send_tone(pte, tone_data.freq1, tone_data.freq2); if (unistimdebug) { ast_verb(0, "Country code found (%s), freq1=%u freq2=%u\n", pte->device->tz->country, tone_data.freq1, tone_data.freq2); } ts = ast_tone_zone_sound_unref(ts); } } static void show_phone_number(struct unistimsession *pte) { char tmp[TEXT_LENGTH_MAX + 1]; const char *tmp_number = ustmtext("Number:", pte); int line, tmp_copy, offset = 0, i; pte->device->phone_number[pte->device->size_phone_number] = '\0'; if (pte->device->size_phone_number > MAX_SCREEN_NUMBER) { offset = pte->device->size_phone_number - MAX_SCREEN_NUMBER - 1; if (offset > strlen(tmp_number)) { offset = strlen(tmp_number); } tmp_copy = strlen(tmp_number) - offset + 1; if (tmp_copy > sizeof(tmp)) { tmp_copy = sizeof(tmp); } memcpy(tmp, tmp_number + offset, tmp_copy); } else { ast_copy_string(tmp, tmp_number, sizeof(tmp)); } offset = (pte->device->size_phone_number >= TEXT_LENGTH_MAX) ? (pte->device->size_phone_number - TEXT_LENGTH_MAX +1) : 0; if (pte->device->size_phone_number) { memcpy(tmp + strlen(tmp), pte->device->phone_number + offset, pte->device->size_phone_number - offset + 1); } offset = strlen(tmp); for (i = strlen(tmp); i < TEXT_LENGTH_MAX; i++) { tmp[i] = '.'; } tmp[i] = '\0'; line = (pte->device->height == 1) ? TEXT_LINE0 : TEXT_LINE2; send_text(line, TEXT_NORMAL, pte, tmp); send_blink_cursor(pte); send_cursor_pos(pte, (unsigned char) (line + offset)); send_led_update(pte, LED_BAR_OFF); } static void handle_dial_page(struct unistimsession *pte) { pte->state = STATE_DIALPAGE; if (pte->device->call_forward[0] == -1) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, ""); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext("Enter forward", pte)); send_text_status(pte, ustmtext("Fwd Cancel BackSp Erase", pte)); if (pte->device->call_forward[1] != 0) { ast_copy_string(pte->device->phone_number, pte->device->call_forward + 1, sizeof(pte->device->phone_number)); show_phone_number(pte); send_led_update(pte, LED_BAR_OFF); return; } } else { if ((pte->device->output == OUTPUT_HANDSET) && (pte->device->receiver_state == STATE_ONHOOK)) { send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF); } else { send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF); } send_dial_tone(pte); if (pte->device->height > 1) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Enter the number to dial", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext("and press Call", pte)); } if (ast_strlen_zero(pte->device->redial_number)) { send_text_status(pte, ustmtext("Call BackSp Erase", pte)); } else { send_text_status(pte, ustmtext("Call Redial BackSp Erase", pte)); } } pte->device->size_phone_number = 0; pte->device->phone_number[0] = 0; show_phone_number(pte); change_favorite_icon(pte, FAV_ICON_PHONE_BLACK); send_icon(TEXT_LINE0, FAV_ICON_NONE, pte); pte->device->missed_call = 0; send_led_update(pte, LED_BAR_OFF); pte->device->lastmsgssent = -1; return; } static void swap_subs(struct unistim_subchannel *a, struct unistim_subchannel *b) { struct ast_rtp_instance *rtp; int fds; if (unistimdebug) { ast_verb(0, "Swapping %p and %p\n", a, b); } if ((!a->owner) || (!b->owner)) { ast_log(LOG_WARNING, "Attempted to swap subchannels with a null owner : sub #%p=%p sub #%p=%p\n", a, a->owner, b, b->owner); return; } rtp = a->rtp; a->rtp = b->rtp; b->rtp = rtp; fds = ast_channel_fd(a->owner, 0); ast_channel_internal_fd_set(a->owner, 0, ast_channel_fd(b->owner, 0)); ast_channel_internal_fd_set(b->owner, 0, fds); fds = ast_channel_fd(a->owner, 1); ast_channel_internal_fd_set(a->owner, 1, ast_channel_fd(b->owner, 1)); ast_channel_internal_fd_set(b->owner, 1, fds); } /* Step 1 : Music On Hold for peer, Dialing screen for us */ static void transfer_call_step1(struct unistimsession *pte) { struct unistim_subchannel *sub /*, *sub_trans */; struct unistim_device *d = pte->device; sub = get_sub(d, SUB_REAL); /* sub_trans = get_sub(d, SUB_THREEWAY); */ if (!sub || !sub->owner) { ast_log(LOG_WARNING, "Unable to find subchannel for music on hold\n"); return; } /* Start music on hold if appropriate */ if (sub->moh) { ast_log(LOG_WARNING, "Transfer with peer already listening music on hold\n"); } else { ast_queue_hold(sub->owner, sub->parent->musicclass); sub->moh = 1; sub->subtype = SUB_THREEWAY; } sub_start_silence(pte, sub); handle_dial_page(pte); } static void transfer_cancel_step2(struct unistimsession *pte) { struct unistim_subchannel *sub, *sub_trans; struct unistim_device *d = pte->device; sub = get_sub(d, SUB_REAL); sub_trans = get_sub(d, SUB_THREEWAY); if (!sub || !sub->owner) { ast_log(LOG_WARNING, "Unable to find subchannel for music on hold\n"); return; } if (sub_trans) { if (unistimdebug) { ast_verb(0, "Transfer canceled, hangup our threeway channel\n"); } if (sub->owner) { swap_subs(sub, sub_trans); ast_queue_unhold(sub_trans->owner); sub_trans->moh = 0; sub_trans->subtype = SUB_REAL; sub->subtype = SUB_THREEWAY; ast_queue_hangup_with_cause(sub->owner, AST_CAUSE_NORMAL_CLEARING); } else { ast_log(LOG_WARNING, "Canceling a threeway channel without owner\n"); } return; } } /* From phone to PBX */ static void handle_call_outgoing(struct unistimsession *s) { struct ast_channel *c; struct unistim_subchannel *sub; int softkey; s->state = STATE_CALL; sub = get_sub(s->device, SUB_THREEWAY); if (sub) { /* If sub for threway call created than we use transfer behaviuor */ struct unistim_subchannel *sub_trans = NULL; struct unistim_device *d = s->device; sub_trans = get_sub(d, SUB_REAL); if (sub_trans) { ast_log(LOG_WARNING, "Can't transfer while active subchannel exists!\n"); return; } if (!sub->owner) { ast_log(LOG_WARNING, "Unable to find subchannel with music on hold\n"); return; } sub_trans = unistim_alloc_sub(d, SUB_REAL); if (!sub_trans) { ast_log(LOG_WARNING, "Unable to allocate three-way subchannel\n"); return; } sub_trans->parent = sub->parent; sub_stop_silence(s, sub); send_tone(s, 0, 0); /* Make new channel */ c = unistim_new(sub_trans, AST_STATE_DOWN, NULL, NULL); if (!c) { ast_log(LOG_WARNING, "Cannot allocate new structure on channel %p\n", sub->parent); return; } /* Swap things around between the three-way and real call */ swap_subs(sub, sub_trans); send_select_output(s, s->device->output, s->device->volume, MUTE_OFF); if (s->device->height == 1) { send_text(TEXT_LINE0, TEXT_NORMAL, s, s->device->phone_number); } else { send_text(TEXT_LINE0, TEXT_NORMAL, s, ustmtext("Calling (pre-transfer)", s)); send_text(TEXT_LINE1, TEXT_NORMAL, s, s->device->phone_number); send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("Dialing...", s)); } send_text_status(s, ustmtext("TransfrCancel", s)); if (ast_pthread_create(&sub->ss_thread, NULL, unistim_ss, c)) { ast_log(LOG_WARNING, "Unable to start simple switch on channel %p\n", c); sub->ss_thread = AST_PTHREADT_NULL; ast_hangup(c); return; } if (unistimdebug) { ast_verb(0, "Started three way call on channel %p (%s) subchan %u\n", sub_trans->owner, ast_channel_name(sub_trans->owner), sub_trans->subtype); } return; } softkey = get_avail_softkey(s, NULL); if (softkey == -1) { ast_log(LOG_WARNING, "Have no avail softkey for calling\n"); return; } sub = get_sub(s->device, SUB_REAL); if (sub) { /* have already call assigned */ sub_hold(s, sub); /* Need to put on hold */ } if (!(sub = unistim_alloc_sub(s->device, SUB_REAL))) { ast_log(LOG_WARNING, "Unable to allocate subchannel!\n"); return; } sub->parent = s->device->sline[softkey]; s->device->ssub[softkey] = sub; sub->softkey = softkey; if (unistimdebug) { ast_verb(0, "Using softkey %d, line %p\n", sub->softkey, sub->parent); } send_favorite_short(sub->softkey, FAV_ICON_OFFHOOK_BLACK, s); s->device->selected = -1; if (!sub->owner) { /* A call is already in progress ? */ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); const char *pickupexten; c = unistim_new(sub, AST_STATE_DOWN, NULL, NULL); /* No, starting a new one */ if (!sub->rtp) { /* Need to start RTP before calling ast_pbx_run */ start_rtp(sub); } if (c) { ast_channel_lock(c); pickup_cfg = ast_get_chan_features_pickup_config(c); 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); } ast_channel_unlock(c); } if (c && !strcmp(s->device->phone_number, pickupexten)) { if (unistimdebug) { ast_verb(0, "Try to pickup in unistim_new\n"); } send_text(TEXT_LINE0, TEXT_NORMAL, s, ""); send_text_status(s, ustmtext(" Transf Hangup", s)); send_start_timer(s); if (ast_pickup_call(c)) { ast_log(LOG_NOTICE, "Nothing to pick up\n"); ast_channel_hangupcause_set(c, AST_CAUSE_CALL_REJECTED); } else { ast_channel_hangupcause_set(c, AST_CAUSE_NORMAL_CLEARING); } ast_hangup(c); c = NULL; } else if (c) { send_select_output(s, s->device->output, s->device->volume, MUTE_OFF); send_tone(s, 0, 0); /* Dialing empty number should also stop dial tone */ if (s->device->height == 1) { if (strlen(s->device->phone_number) > 0) { send_text(TEXT_LINE0, TEXT_NORMAL, s, s->device->phone_number); } else { send_text(TEXT_LINE0, TEXT_NORMAL, s, ustmtext("Calling...", s)); } } else { send_text(TEXT_LINE0, TEXT_NORMAL, s, ustmtext("Calling :", s)); send_text(TEXT_LINE1, TEXT_NORMAL, s, s->device->phone_number); send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("Dialing...", s)); } send_text_status(s, ustmtext(" Hangup", s)); /* start switch */ if (ast_pthread_create(&sub->ss_thread, NULL, unistim_ss, c)) { ast_log(LOG_WARNING, "Unable to create switch thread\n"); sub->ss_thread = AST_PTHREADT_NULL; ast_queue_hangup_with_cause(c, AST_CAUSE_SWITCH_CONGESTION); } } else ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", sub->parent->name, s->device->name); } else { ast_debug(1, "Current sub [%s] already has owner\n", ast_channel_name(sub->owner)); } return; } /* From PBX to phone */ static void handle_call_incoming(struct unistimsession *s) { struct unistim_subchannel *sub = NULL; int i; s->state = STATE_CALL; s->device->missed_call = 0; send_no_ring(s); sub = get_sub(s->device, SUB_RING); /* Put other SUB_REAL call on hold */ if (!sub) { ast_log(LOG_WARNING, "No ringing lines on: %s\n", s->device->name); return; } /* Change icons for all ringing keys */ for (i = 0; i < FAVNUM; i++) { if (!s->device->ssub[i]) { /* No sub assigned - skip */ continue; } if (s->device->ssub[i]->subtype == SUB_REAL) { sub_hold(s, s->device->ssub[i]); } if (s->device->ssub[i] != sub) { continue; } if (sub->softkey == i) { /* If softkey assigned at this moment - do not erase */ continue; } if (sub->softkey < 0) { /* If softkey not defined - first one used */ sub->softkey = i; continue; } send_favorite_short(i, FAV_LINE_ICON, s); s->device->ssub[i] = NULL; } if (sub->softkey < 0) { ast_log(LOG_WARNING, "Can not assign softkey for incoming call on: %s\n", s->device->name); return; } send_favorite_short(sub->softkey, FAV_ICON_OFFHOOK_BLACK, s); sub->parent = s->device->sline[sub->softkey]; sub->subtype = SUB_REAL; if (unistimdebug) { ast_verb(0, "Handle Call Incoming for %s@%s\n", sub->parent->name, s->device->name); } start_rtp(sub); if (!sub->rtp) { ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", sub->parent->name, s->device->name); return; } if (sub->owner) { ast_queue_control(sub->owner, AST_CONTROL_ANSWER); } send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("is on-line", s)); send_text_status(s, ustmtext(" Transf Hangup", s)); send_start_timer(s); if ((s->device->output == OUTPUT_HANDSET) && (s->device->receiver_state == STATE_ONHOOK)) { send_select_output(s, OUTPUT_SPEAKER, s->device->volume, MUTE_OFF); } else { send_select_output(s, s->device->output, s->device->volume, MUTE_OFF); } write_history(s, 'i', 0); return; } static int unistim_do_senddigit(struct unistimsession *pte, char digit) { struct ast_frame f = { .frametype = AST_FRAME_DTMF, .subclass.integer = digit, .src = "unistim" }; struct unistim_subchannel *sub; int row, col; sub = get_sub(pte->device, SUB_REAL); if (!sub || !sub->owner || sub->alreadygone) { ast_log(LOG_WARNING, "Unable to find subchannel in dtmf senddigit\n"); return -1; } /* Send DTMF indication _before_ playing sounds */ ast_queue_frame(sub->owner, &f); if (unistimdebug) { ast_verb(0, "Send Digit %c (%i ms)\n", digit, pte->device->dtmfduration); } if (pte->device->dtmfduration > 0) { row = (digit - '1') % 3; col = (digit - '1' - row) / 3; if (digit >= '1' && digit <='9') { send_tone(pte, dtmf_row[row], dtmf_col[col]); } else if (digit >= 'A' && digit <= 'D') { send_tone(pte, dtmf_row[digit-'A'], dtmf_col[3]); } else if (digit == '*') { send_tone(pte, dtmf_row[3], dtmf_col[0]); } else if (digit == '0') { send_tone(pte, dtmf_row[3], dtmf_col[1]); } else if (digit == '#') { send_tone(pte, dtmf_row[3], dtmf_col[2]); } else { send_tone(pte, 500, 2000); } usleep(pte->device->dtmfduration * 1000); /* XXX Less than perfect, blocking an important thread is not a good idea */ send_tone(pte, 0, 0); } return 0; } static void handle_key_fav(struct unistimsession *pte, char keycode) { int keynum = keycode - KEY_FAV0; struct unistim_subchannel *sub; sub = get_sub(pte->device, SUB_REAL); /* Make an action on selected favorite key */ if (!pte->device->ssub[keynum]) { /* Key have no assigned call */ send_favorite_selected(FAV_LINE_ICON, pte); if (is_key_line(pte->device, keynum)) { if (unistimdebug) { ast_verb(0, "Handle line w/o sub - dialpage\n"); } pte->device->selected = keynum; sub_hold(pte, sub); /* Put active call on hold */ send_stop_timer(pte); handle_dial_page(pte); } else if (is_key_favorite(pte->device, keynum)) { /* Put active call on hold in handle_call_outgoing function, after preparation and checking if lines available for calling */ if (unistimdebug) { ast_verb(0, "Handle favorite w/o sub - dialing\n"); } if ((pte->device->output == OUTPUT_HANDSET) && (pte->device->receiver_state == STATE_ONHOOK)) { send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF); } else { send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF); } key_favorite(pte, keycode); } } else { sub = pte->device->ssub[keynum]; /* Favicon have assigned sub, activate it and put current on hold */ if (sub->subtype == SUB_REAL) { sub_hold(pte, sub); show_main_page(pte); } else if (sub->subtype == SUB_RING) { sub->softkey = keynum; handle_call_incoming(pte); } else if (sub->subtype == SUB_ONHOLD) { if (pte->state == STATE_DIALPAGE){ send_tone(pte, 0, 0); } send_callerid_screen(pte, sub); sub_unhold(pte, sub); pte->state = STATE_CALL; } } } static void key_call(struct unistimsession *pte, char keycode) { struct unistim_subchannel *sub = get_sub(pte->device, SUB_REAL); struct unistim_subchannel *sub_3way = get_sub(pte->device, SUB_THREEWAY); if (!sub) { return; } if ((keycode >= KEY_0) && (keycode <= KEY_SHARP)) { if (keycode == KEY_SHARP) { keycode = '#'; } else if (keycode == KEY_STAR) { keycode = '*'; } else { keycode -= 0x10; } unistim_do_senddigit(pte, keycode); return; } switch (keycode) { case KEY_FUNC1: if (sub->owner && ast_channel_state(sub->owner) == AST_STATE_UP) { if (sub_3way) { close_call(pte); } } break; case KEY_FUNC2: if (sub_3way) { transfer_cancel_step2(pte); } else if (ast_channel_state(sub->owner) == AST_STATE_UP) { transfer_call_step1(pte); } break; case KEY_HANGUP: case KEY_FUNC4: if (!sub_3way) { close_call(pte); } break; case KEY_FAV0: case KEY_FAV1: case KEY_FAV2: case KEY_FAV3: case KEY_FAV4: case KEY_FAV5: handle_key_fav(pte, keycode); break; case KEY_HEADPHN: if (pte->device->output == OUTPUT_HEADPHONE) { send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF); } else { send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF); } break; case KEY_LOUDSPK: if (pte->device->output != OUTPUT_SPEAKER) send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF); else send_select_output(pte, pte->device->previous_output, pte->device->volume, MUTE_OFF); break; case KEY_ONHOLD: if (!sub) { if(pte->device->ssub[pte->device->selected]) { sub_hold(pte, pte->device->ssub[pte->device->selected]); } } else { sub_hold(pte, sub); } break; } return; } static void key_ringing(struct unistimsession *pte, char keycode) { switch (keycode) { case KEY_FAV0: case KEY_FAV1: case KEY_FAV2: case KEY_FAV3: case KEY_FAV4: case KEY_FAV5: handle_key_fav(pte, keycode); break; case KEY_FUNC3: ignore_call(pte); break; case KEY_HANGUP: case KEY_FUNC4: discard_call(pte); break; case KEY_LOUDSPK: pte->device->output = OUTPUT_SPEAKER; handle_call_incoming(pte); break; case KEY_HEADPHN: pte->device->output = OUTPUT_HEADPHONE; handle_call_incoming(pte); break; case KEY_FUNC1: handle_call_incoming(pte); break; } return; } static void key_favorite(struct unistimsession *pte, char keycode) { int fav = keycode - KEY_FAV0; if (!is_key_favorite(pte->device, fav)) { ast_log(LOG_WARNING, "It's not a favorite key\n"); return; } ast_copy_string(pte->device->phone_number, pte->device->softkeynumber[fav], sizeof(pte->device->phone_number)); handle_call_outgoing(pte); return; } static void key_dial_page(struct unistimsession *pte, char keycode) { struct unistim_subchannel *sub = get_sub(pte->device, SUB_THREEWAY); pte->device->nextdial = 0; if (keycode == KEY_FUNC3) { if (pte->device->size_phone_number <= 1) { keycode = KEY_FUNC4; } else { pte->device->size_phone_number -= 2; keycode = pte->device->phone_number[pte->device->size_phone_number] + 0x10; } } if (keycode == KEY_SHARP && pte->device->sharp_dial == 1) { keycode = KEY_FUNC1; } if ((keycode >= KEY_0) && (keycode <= KEY_SHARP)) { int i = pte->device->size_phone_number; if (pte->device->size_phone_number == 0) { send_tone(pte, 0, 0); } if (keycode == KEY_SHARP) { keycode = '#'; } else if (keycode == KEY_STAR) { keycode = '*'; } else { keycode -= 0x10; } pte->device->phone_number[i] = keycode; pte->device->size_phone_number++; pte->device->phone_number[i + 1] = 0; show_phone_number(pte); if (ast_exists_extension(NULL, pte->device->context, pte->device->phone_number, 1, NULL) && !ast_matchmore_extension(NULL, pte->device->context, pte->device->phone_number, 1, NULL)) { keycode = KEY_FUNC1; } else { if (pte->device->interdigit_timer) { pte->device->nextdial = get_tick_count() + pte->device->interdigit_timer; } } } if (keycode == KEY_FUNC4) { pte->device->size_phone_number = 0; show_phone_number(pte); return; } if (pte->device->call_forward[0] == -1) { if (keycode == KEY_FUNC1) { ast_copy_string(pte->device->call_forward, pte->device->phone_number, sizeof(pte->device->call_forward)); show_main_page(pte); } else if ((keycode == KEY_FUNC2) || (keycode == KEY_HANGUP)) { pte->device->call_forward[0] = '\0'; send_led_update(pte, LED_SPEAKER_OFF); send_led_update(pte, LED_HEADPHONE_OFF); show_main_page(pte); } return; } switch (keycode) { case KEY_FUNC2: if (ast_strlen_zero(pte->device->redial_number)) { break; } ast_copy_string(pte->device->phone_number, pte->device->redial_number, sizeof(pte->device->phone_number)); case KEY_FUNC1: handle_call_outgoing(pte); break; case KEY_HANGUP: if (sub && sub->owner) { sub_stop_silence(pte, sub); send_tone(pte, 0, 0); ast_queue_unhold(sub->owner); sub->moh = 0; sub->subtype = SUB_REAL; pte->state = STATE_CALL; send_text_status(pte, ustmtext(" Transf Hangup", pte)); send_callerid_screen(pte, sub); } else { send_led_update(pte, LED_SPEAKER_OFF); send_led_update(pte, LED_HEADPHONE_OFF); show_main_page(pte); } break; case KEY_FAV0: case KEY_FAV1: case KEY_FAV2: case KEY_FAV3: case KEY_FAV4: case KEY_FAV5: send_favorite_selected(FAV_LINE_ICON, pte); pte->device->selected = -1; handle_key_fav(pte, keycode); break; case KEY_LOUDSPK: if (pte->device->output == OUTPUT_SPEAKER) { if (pte->device->receiver_state == STATE_OFFHOOK) { send_select_output(pte, pte->device->previous_output, pte->device->volume, MUTE_OFF); } else { show_main_page(pte); } } else { send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF); } break; case KEY_HEADPHN: if (pte->device->output == OUTPUT_HEADPHONE) { if (pte->device->receiver_state == STATE_OFFHOOK) { send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF); } else { show_main_page(pte); } } else { send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF); } break; } return; } static void handle_select_option(struct unistimsession *pte) { char tmp[128]; if (pte->state != STATE_SELECTOPTION) { pte->state = STATE_SELECTOPTION; pte->size_buff_entry = 1; pte->buff_entry[0] = 0; /* Position in menu */ } snprintf(tmp, sizeof(tmp), "%d. %s", pte->buff_entry[0] + 1, ustmtext(options_menu[(int)pte->buff_entry[0]].label, pte)); send_text(TEXT_LINE0, TEXT_NORMAL, pte, tmp); send_text_status(pte, ustmtext("Select Cancel", pte)); return; } static void key_select_option(struct unistimsession *pte, char keycode) { switch (keycode) { case KEY_DOWN: pte->buff_entry[0]++; if (options_menu[(int)pte->buff_entry[0]].label == NULL) { pte->buff_entry[0]--; } break; case KEY_UP: if (pte->buff_entry[0] > 0) { pte->buff_entry[0]--; } break; case KEY_FUNC1: options_menu[(int)pte->buff_entry[0]].handle_option(pte); return; case KEY_HANGUP: case KEY_FUNC4: show_main_page(pte); return; } handle_select_option(pte); return; } #define SELECTCODEC_START_ENTRY_POS 15 #define SELECTCODEC_MAX_LENGTH 2 #define SELECTCODEC_MSG "Codec number : .." static void handle_select_codec(struct unistimsession *pte) { char buf[30], buf2[5]; pte->state = STATE_SELECTCODEC; ast_copy_string(buf, ustmtext("Using codec", pte), sizeof(buf)); snprintf(buf2, sizeof(buf2), " %d", pte->device->codec_number); strcat(buf, buf2); strcat(buf, " (G711u=0,"); send_text(TEXT_LINE0, TEXT_NORMAL, pte, buf); send_text(TEXT_LINE1, TEXT_NORMAL, pte, "G723=4,G711a=8,G729A=18)"); send_text(TEXT_LINE2, TEXT_INVERSE, pte, SELECTCODEC_MSG); send_blink_cursor(pte); send_cursor_pos(pte, TEXT_LINE2 + SELECTCODEC_START_ENTRY_POS); pte->size_buff_entry = 0; send_text_status(pte, ustmtext("Select BackSp Erase Cancel", pte)); return; } static void key_select_codec(struct unistimsession *pte, char keycode) { if (keycode == KEY_FUNC2) { if (pte->size_buff_entry <= 1) { keycode = KEY_FUNC3; } else { pte->size_buff_entry -= 2; keycode = pte->buff_entry[pte->size_buff_entry] + 0x10; } } if ((keycode >= KEY_0) && (keycode <= KEY_9)) { char tmpbuf[] = SELECTCODEC_MSG; int i = 0; if (pte->size_buff_entry >= SELECTCODEC_MAX_LENGTH) { return; } while (i < pte->size_buff_entry) { tmpbuf[i + SELECTCODEC_START_ENTRY_POS] = pte->buff_entry[i]; i++; } tmpbuf[i + SELECTCODEC_START_ENTRY_POS] = keycode - 0x10; pte->buff_entry[i] = keycode - 0x10; pte->size_buff_entry++; send_text(TEXT_LINE2, TEXT_INVERSE, pte, tmpbuf); send_blink_cursor(pte); send_cursor_pos(pte, (unsigned char) (TEXT_LINE2 + SELECTCODEC_START_ENTRY_POS + 1 + i)); return; } switch (keycode) { case KEY_FUNC1: if (pte->size_buff_entry == 1) { pte->device->codec_number = pte->buff_entry[0] - 48; } else if (pte->size_buff_entry == 2) { pte->device->codec_number = ((pte->buff_entry[0] - 48) * 10) + (pte->buff_entry[1] - 48); } show_main_page(pte); break; case KEY_FUNC3: pte->size_buff_entry = 0; send_text(TEXT_LINE2, TEXT_INVERSE, pte, SELECTCODEC_MSG); send_blink_cursor(pte); send_cursor_pos(pte, TEXT_LINE2 + SELECTCODEC_START_ENTRY_POS); break; case KEY_HANGUP: case KEY_FUNC4: show_main_page(pte); break; } return; } static int find_language(const char* lang) { int i = 0; while (options_languages[i].lang_short != NULL) { if(!strcmp(options_languages[i].lang_short, lang)) { return i; } i++; } return 0; } static void handle_select_language(struct unistimsession *pte) { char tmp_language[40]; struct unistim_languages lang; if (pte->state != STATE_SELECTLANGUAGE) { pte->state = STATE_SELECTLANGUAGE; pte->size_buff_entry = 1; pte->buff_entry[0] = find_language(pte->device->language); } lang = options_languages[(int)pte->buff_entry[0]]; ast_copy_string(tmp_language, pte->device->language, sizeof(tmp_language)); ast_copy_string(pte->device->language, lang.lang_short, sizeof(pte->device->language)); send_charset_update(pte, lang.encoding); send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext(lang.label, pte)); ast_copy_string(pte->device->language, tmp_language, sizeof(pte->device->language)); lang = options_languages[find_language(pte->device->language)]; send_charset_update(pte, lang.encoding); send_text_status(pte, ustmtext("Select Cancel", pte)); return; } static void key_select_language(struct unistimsession *pte, char keycode) { switch (keycode) { case KEY_DOWN: pte->buff_entry[0]++; if (options_languages[(int)pte->buff_entry[0]].label == NULL) { pte->buff_entry[0]--; } break; case KEY_UP: if (pte->buff_entry[0] > 0) { pte->buff_entry[0]--; } break; case KEY_FUNC1: ast_copy_string(pte->device->language, options_languages[(int)pte->buff_entry[0]].lang_short, sizeof(pte->device->language)); send_charset_update(pte, options_languages[(int)pte->buff_entry[0]].encoding); refresh_all_favorite(pte); show_main_page(pte); return; case KEY_HANGUP: case KEY_FUNC4: handle_select_option(pte); return; } handle_select_language(pte); return; } #define SELECTEXTENSION_START_ENTRY_POS 0 #define SELECTEXTENSION_MAX_LENGTH 10 #define SELECTEXTENSION_MSG ".........." static void show_extension_page(struct unistimsession *pte) { pte->state = STATE_EXTENSION; send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Please enter a Terminal", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext("Number (TN) :", pte)); send_text(TEXT_LINE2, TEXT_NORMAL, pte, SELECTEXTENSION_MSG); send_blink_cursor(pte); send_cursor_pos(pte, TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS); send_text_status(pte, ustmtext("Enter BackSpcErase", pte)); pte->size_buff_entry = 0; return; } static void key_select_extension(struct unistimsession *pte, char keycode) { if (keycode == KEY_FUNC2) { if (pte->size_buff_entry <= 1) { keycode = KEY_FUNC3; } else { pte->size_buff_entry -= 2; keycode = pte->buff_entry[pte->size_buff_entry] + 0x10; } } if ((keycode >= KEY_0) && (keycode <= KEY_9)) { char tmpbuf[] = SELECTEXTENSION_MSG; int i = 0; if (pte->size_buff_entry >= SELECTEXTENSION_MAX_LENGTH) { return; } while (i < pte->size_buff_entry) { tmpbuf[i + SELECTEXTENSION_START_ENTRY_POS] = pte->buff_entry[i]; i++; } tmpbuf[i + SELECTEXTENSION_START_ENTRY_POS] = keycode - 0x10; pte->buff_entry[i] = keycode - 0x10; pte->size_buff_entry++; send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmpbuf); send_blink_cursor(pte); send_cursor_pos(pte, (unsigned char) (TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS + 1 + i)); return; } switch (keycode) { case KEY_FUNC1: if (pte->size_buff_entry < 1) { return; } if (autoprovisioning == AUTOPROVISIONING_TN) { struct unistim_device *d; /* First step : looking for this TN in our device list */ ast_mutex_lock(&devicelock); d = devices; pte->buff_entry[pte->size_buff_entry] = '\0'; while (d) { if (d->id[0] == 'T') { /* It's a TN device ? */ /* It's the TN we're looking for ? */ if (!strcmp((d->id) + 1, pte->buff_entry)) { pte->device = d; d->session = pte; d->codec_number = DEFAULT_CODEC; d->missed_call = 0; d->receiver_state = STATE_ONHOOK; strcpy(d->id, pte->macaddr); pte->device->extension_number[0] = 'T'; pte->device->extension = EXTENSION_TN; ast_copy_string((pte->device->extension_number) + 1, pte->buff_entry, pte->size_buff_entry + 1); ast_mutex_unlock(&devicelock); show_main_page(pte); refresh_all_favorite(pte); return; } } d = d->next; } ast_mutex_unlock(&devicelock); send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Invalid Terminal Number.", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext("Please try again :", pte)); send_cursor_pos(pte, (unsigned char) (TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS + pte->size_buff_entry)); send_blink_cursor(pte); } else { ast_copy_string(pte->device->extension_number, pte->buff_entry, pte->size_buff_entry + 1); if (register_extension(pte)) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Invalid extension.", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext("Please try again :", pte)); send_cursor_pos(pte, (unsigned char) (TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS + pte->size_buff_entry)); send_blink_cursor(pte); } else show_main_page(pte); } break; case KEY_FUNC3: pte->size_buff_entry = 0; send_text(TEXT_LINE2, TEXT_NORMAL, pte, SELECTEXTENSION_MSG); send_blink_cursor(pte); send_cursor_pos(pte, TEXT_LINE2 + SELECTEXTENSION_START_ENTRY_POS); break; } return; } static void show_entry_history(struct unistimsession *pte, FILE ** f) { char line[TEXT_LENGTH_MAX + 1], status[STATUS_LENGTH_MAX + 1], func1[10], func2[10], func3[10]; /* Display date/time and call status */ if (fread(line, TEXT_LENGTH_MAX, 1, *f) != 1) { display_last_error("Can't read history date entry"); fclose(*f); return; } line[sizeof(line) - 1] = '\0'; if (pte->device->height == 1) { if (pte->buff_entry[3] == 1) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, line); } } else { send_text(TEXT_LINE0, TEXT_NORMAL, pte, line); } /* Display number */ if (fread(line, TEXT_LENGTH_MAX, 1, *f) != 1) { display_last_error("Can't read callerid entry"); fclose(*f); return; } line[sizeof(line) - 1] = '\0'; ast_copy_string(pte->device->lst_cid, line, sizeof(pte->device->lst_cid)); ast_trim_blanks(pte->device->lst_cid); if (pte->device->height == 1) { if (pte->buff_entry[3] == 2) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, line); } } else { send_text(TEXT_LINE1, TEXT_NORMAL, pte, line); } /* Display name */ if (fread(line, TEXT_LENGTH_MAX, 1, *f) != 1) { display_last_error("Can't read callername entry"); fclose(*f); return; } line[sizeof(line) - 1] = '\0'; if (pte->device->height == 1) { if (pte->buff_entry[3] == 3) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, line); } } else { send_text(TEXT_LINE2, TEXT_NORMAL, pte, line); } fclose(*f); snprintf(line, sizeof(line), "%s %03d/%03d", ustmtext("Call", pte), pte->buff_entry[2], pte->buff_entry[1]); send_texttitle(pte, line); if (pte->buff_entry[2] == 1) { ast_copy_string(func1, " ", sizeof(func1)); } else { ast_copy_string(func1, ustmtext("Prev ", pte), sizeof(func1)); } if (pte->buff_entry[2] >= pte->buff_entry[1]) { ast_copy_string(func2, " ", sizeof(func2)); } else { ast_copy_string(func2, ustmtext("Next ", pte), sizeof(func2)); } if (strlen(pte->device->lst_cid)) { ast_copy_string(func3, ustmtext("Redial ", pte), sizeof(func3)); } else { ast_copy_string(func3, " ", sizeof(func3)); } snprintf(status, sizeof(status), "%s%s%s%s", func1, func2, func3, ustmtext("Cancel", pte)); send_text_status(pte, status); } static char open_history(struct unistimsession *pte, char way, FILE ** f) { char tmp[AST_CONFIG_MAX_PATH]; char count; snprintf(tmp, sizeof(tmp), "%s/%s/%s-%c.csv", ast_config_AST_LOG_DIR, USTM_LOG_DIR, pte->device->name, way); *f = fopen(tmp, "r"); if (!*f) { display_last_error("Unable to open history file"); return 0; } if (fread(&count, 1, 1, *f) != 1) { display_last_error("Unable to read history header - display."); fclose(*f); *f = NULL; return 0; } if (count > MAX_ENTRY_LOG) { ast_log(LOG_WARNING, "Invalid count in history header of %s (%d max %d)\n", tmp, count, MAX_ENTRY_LOG); fclose(*f); *f = NULL; return 0; } return count; } static void show_history(struct unistimsession *pte, char way) { FILE *f; char count; if (!pte->device) { return; } if (!pte->device->callhistory) { return; } count = open_history(pte, way, &f); if (!count) { return; } pte->buff_entry[0] = way; pte->buff_entry[1] = count; pte->buff_entry[2] = 1; pte->buff_entry[3] = 1; show_entry_history(pte, &f); pte->state = STATE_HISTORY; } static void show_main_page(struct unistimsession *pte) { char tmpbuf[TEXT_LENGTH_MAX + 1]; const char *text; if ((pte->device->extension == EXTENSION_ASK) && (ast_strlen_zero(pte->device->extension_number))) { show_extension_page(pte); return; } pte->state = STATE_MAINPAGE; send_led_update(pte, LED_BAR_OFF); pte->device->lastmsgssent = -1; send_tone(pte, 0, 0); send_stop_timer(pte); /* case of holding call */ send_select_output(pte, pte->device->output, pte->device->volume, MUTE_ON_DISCRET); send_led_update(pte, LED_SPEAKER_OFF); send_led_update(pte, LED_HEADPHONE_OFF); if (!ast_strlen_zero(pte->device->call_forward)) { if (pte->device->height == 1) { char tmp_field[100]; snprintf(tmp_field, sizeof(tmp_field), "%s %s", ustmtext("Fwd to:", pte), pte->device->call_forward); send_text(TEXT_LINE0, TEXT_NORMAL, pte, tmp_field); } else { send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Call forwarded to :", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, pte->device->call_forward); } send_icon(TEXT_LINE0, FAV_ICON_REFLECT + FAV_BLINK_SLOW, pte); if (ast_strlen_zero(pte->device->redial_number)) { send_text_status(pte, ustmtext("Dial NoFwd ", pte)); } else { send_text_status(pte, ustmtext("Dial Redial NoFwd ", pte)); } } else { if ((pte->device->extension == EXTENSION_ASK) || (pte->device->extension == EXTENSION_TN)) { if (ast_strlen_zero(pte->device->redial_number)) { send_text_status(pte, ustmtext("Dial Fwd Unregis", pte)); } else { send_text_status(pte, ustmtext("Dial Redial Fwd Unregis", pte)); } } else { if (ast_strlen_zero(pte->device->redial_number)) { send_text_status(pte, ustmtext("Dial Fwd Pickup", pte)); } else { send_text_status(pte, ustmtext("Dial Redial Fwd Pickup", pte)); } } send_text(TEXT_LINE1, TEXT_NORMAL, pte, pte->device->maintext1); if (pte->device->missed_call == 0) { send_date_time2(pte); send_idle_clock(pte); if (strlen(pte->device->maintext0)) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, pte->device->maintext0); } } else { if (pte->device->missed_call == 1) { text = ustmtext("unanswered call", pte); } else { text = ustmtext("unanswered calls", pte); } snprintf(tmpbuf, sizeof(tmpbuf), "%d %s", pte->device->missed_call, text); send_text(TEXT_LINE0, TEXT_NORMAL, pte, tmpbuf); send_icon(TEXT_LINE0, FAV_ICON_CALL_CENTER + FAV_BLINK_SLOW, pte); } } if (pte->device->height > 1) { if (ast_strlen_zero(pte->device->maintext2)) { strcpy(tmpbuf, "IP : "); strcat(tmpbuf, ast_inet_ntoa(pte->sin.sin_addr)); send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmpbuf); } else { send_text(TEXT_LINE2, TEXT_NORMAL, pte, pte->device->maintext2); } } send_texttitle(pte, pte->device->titledefault); change_favorite_icon(pte, FAV_LINE_ICON); } static void key_main_page(struct unistimsession *pte, char keycode) { if (pte->device->missed_call) { send_icon(TEXT_LINE0, FAV_ICON_NONE, pte); pte->device->missed_call = 0; } if ((keycode >= KEY_0) && (keycode <= KEY_SHARP)) { handle_dial_page(pte); key_dial_page(pte, keycode); return; } switch (keycode) { case KEY_FUNC1: pte->device->selected = get_avail_softkey(pte, NULL); handle_dial_page(pte); break; case KEY_FUNC2: if (ast_strlen_zero(pte->device->redial_number)) { break; } if ((pte->device->output == OUTPUT_HANDSET) && (pte->device->receiver_state == STATE_ONHOOK)) { send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF); } else { send_select_output(pte, pte->device->output, pte->device->volume, MUTE_OFF); } ast_copy_string(pte->device->phone_number, pte->device->redial_number, sizeof(pte->device->phone_number)); handle_call_outgoing(pte); break; case KEY_FUNC3: if (!ast_strlen_zero(pte->device->call_forward)) { /* Cancel call forwarding */ memmove(pte->device->call_forward + 1, pte->device->call_forward, sizeof(pte->device->call_forward) - 1); pte->device->call_forward[0] = '\0'; send_icon(TEXT_LINE0, FAV_ICON_NONE, pte); pte->device->output = OUTPUT_HANDSET; /* Seems to be reseted somewhere */ show_main_page(pte); break; } pte->device->call_forward[0] = -1; handle_dial_page(pte); break; case KEY_FUNC4: if (pte->device->extension == EXTENSION_ASK) { unregister_extension(pte); pte->device->extension_number[0] = '\0'; show_extension_page(pte); } else if (pte->device->extension == EXTENSION_TN) { ast_mutex_lock(&devicelock); strcpy(pte->device->id, pte->device->extension_number); pte->buff_entry[0] = '\0'; pte->size_buff_entry = 0; pte->device->session = NULL; pte->device = NULL; ast_mutex_unlock(&devicelock); show_extension_page(pte); } else { /* Pickup function */ /* XXX Is there a way to get a specific channel here? */ RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, ast_get_chan_features_pickup_config(NULL), ao2_cleanup); if (!pickup_cfg) { ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); break; } pte->device->selected = -1; ast_copy_string(pte->device->phone_number, pickup_cfg->pickupexten, sizeof(pte->device->phone_number)); handle_call_outgoing(pte); } break; case KEY_FAV0: case KEY_FAV1: case KEY_FAV2: case KEY_FAV3: case KEY_FAV4: case KEY_FAV5: handle_key_fav(pte, keycode); break; case KEY_CONF: handle_select_option(pte); break; case KEY_LOUDSPK: send_select_output(pte, OUTPUT_SPEAKER, pte->device->volume, MUTE_OFF); handle_dial_page(pte); break; case KEY_HEADPHN: send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF); handle_dial_page(pte); break; case KEY_SNDHIST: show_history(pte, 'o'); break; case KEY_RCVHIST: show_history(pte, 'i'); break; } return; } static void key_history(struct unistimsession *pte, char keycode) { FILE *f; char count; long offset; int flag = 0; switch (keycode) { case KEY_LEFT: if (pte->device->height == 1) { if (pte->buff_entry[3] <= 1) { return; } pte->buff_entry[3]--; flag = 1; break; } case KEY_UP: case KEY_FUNC1: if (pte->buff_entry[2] <= 1) { return; } pte->buff_entry[2]--; flag = 1; break; case KEY_RIGHT: if (pte->device->height == 1) { if (pte->buff_entry[3] == 3) { return; } pte->buff_entry[3]++; flag = 1; break; } case KEY_DOWN: case KEY_FUNC2: if (pte->buff_entry[2] >= pte->buff_entry[1]) { return; } pte->buff_entry[2]++; flag = 1; break; case KEY_FUNC3: if (ast_strlen_zero(pte->device->lst_cid)) { break; } ast_copy_string(pte->device->redial_number, pte->device->lst_cid, sizeof(pte->device->redial_number)); key_main_page(pte, KEY_FUNC2); break; case KEY_FUNC4: case KEY_HANGUP: show_main_page(pte); break; case KEY_SNDHIST: if (pte->buff_entry[0] == 'i') { show_history(pte, 'o'); } else { show_main_page(pte); } break; case KEY_RCVHIST: if (pte->buff_entry[0] == 'i') { show_main_page(pte); } else { show_history(pte, 'i'); } break; } if (flag) { count = open_history(pte, pte->buff_entry[0], &f); if (!count) { return; } offset = ((pte->buff_entry[2] - 1) * TEXT_LENGTH_MAX * 3); if (fseek(f, offset, SEEK_CUR)) { display_last_error("Unable to seek history entry."); fclose(f); return; } show_entry_history(pte, &f); } return; } static void init_phone_step2(struct unistimsession *pte) { BUFFSEND; if (unistimdebug) { ast_verb(0, "Sending S4\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_s4, sizeof(packet_send_s4)); send_client(SIZE_HEADER + sizeof(packet_send_s4), buffsend, pte); send_date_time2(pte); send_date_time3(pte); if (unistimdebug) { ast_verb(0, "Sending S7\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_S7, sizeof(packet_send_S7)); send_client(SIZE_HEADER + sizeof(packet_send_S7), buffsend, pte); if (unistimdebug) { ast_verb(0, "Sending Contrast\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_Contrast, sizeof(packet_send_Contrast)); if (pte->device != NULL) { buffsend[9] = pte->device->contrast; } send_client(SIZE_HEADER + sizeof(packet_send_Contrast), buffsend, pte); if (unistimdebug) { ast_verb(0, "Sending S9\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_s9, sizeof(packet_send_s9)); send_client(SIZE_HEADER + sizeof(packet_send_s9), buffsend, pte); send_no_ring(pte); if (unistimdebug) { ast_verb(0, "Sending S7\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_S7, sizeof(packet_send_S7)); send_client(SIZE_HEADER + sizeof(packet_send_S7), buffsend, pte); send_led_update(pte, LED_BAR_OFF); send_ping(pte); if (unistimdebug) { ast_verb(0, "Sending init language\n"); } if (pte->device) { send_charset_update(pte, options_languages[find_language(pte->device->language)].encoding); } if (pte->state < STATE_MAINPAGE) { if (autoprovisioning == AUTOPROVISIONING_TN) { show_extension_page(pte); return; } else { int i; char tmp[30]; for (i = 1; i < FAVNUM; i++) { send_favorite(i, 0, pte, ""); } send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Phone is not registered", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext("in unistim.conf", pte)); strcpy(tmp, "MAC = "); strcat(tmp, pte->macaddr); send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp); send_text_status(pte, ""); send_texttitle(pte, "UNISTIM for*"); return; } } show_main_page(pte); refresh_all_favorite(pte); if (unistimdebug) { ast_verb(0, "Sending arrow\n"); } memcpy(buffsend + SIZE_HEADER, packet_send_arrow, sizeof(packet_send_arrow)); send_client(SIZE_HEADER + sizeof(packet_send_arrow), buffsend, pte); return; } /* Toggles the state of microphone muting */ static void microphone_mute_toggle(struct unistimsession *pte) { if (pte->device->microphone == MUTE_OFF) { pte->device->microphone = MUTE_ON; send_led_update(pte, LED_MUTE_ON); } else if (pte->device->microphone == MUTE_ON) { pte->device->microphone = MUTE_OFF; send_led_update(pte, LED_MUTE_OFF); } send_mute(pte, (pte->device->microphone & 0x01)); } static void process_request(int size, unsigned char *buf, struct unistimsession *pte) { char tmpbuf[255]; if (memcmp (buf + SIZE_HEADER, packet_recv_resume_connection_with_server, sizeof(packet_recv_resume_connection_with_server)) == 0) { rcv_resume_connection_with_server(pte); return; } if (memcmp(buf + SIZE_HEADER, packet_recv_firm_version, sizeof(packet_recv_firm_version)) == 0) { buf[size] = 0; if (unistimdebug) { ast_verb(0, "Got the firmware version : '%s'\n", buf + 13); } ast_copy_string(pte->firmware, (char *) (buf + 13), sizeof(pte->firmware)); init_phone_step2(pte); return; } if (memcmp(buf + SIZE_HEADER, packet_recv_it_type, sizeof(packet_recv_it_type)) == 0) { char type = buf[13]; if (unistimdebug) { ast_verb(0, "Got the equipment type: '%d'\n", type); } switch (type) { case 0x03: /* i2002 */ if (pte->device) { pte->device->height = 1; } break; } return; } if (memcmp(buf + SIZE_HEADER, packet_recv_mac_addr, sizeof(packet_recv_mac_addr)) == 0) { rcv_mac_addr(pte, buf); return; } if (memcmp(buf + SIZE_HEADER, packet_recv_r2, sizeof(packet_recv_r2)) == 0) { if (unistimdebug) { ast_verb(0, "R2 received\n"); } return; } if (pte->state < STATE_MAINPAGE) { if (unistimdebug) { ast_verb(0, "Request not authorized in this state\n"); } return; } if (!memcmp(buf + SIZE_HEADER, packet_recv_expansion_pressed_key, sizeof(packet_recv_expansion_pressed_key))) { char keycode = buf[13]; if (unistimdebug) { ast_verb(0, "Expansion key pressed: keycode = 0x%.2x - current state: %s\n", (unsigned)keycode, ptestate_tostr(pte->state)); } } if (!memcmp(buf + SIZE_HEADER, packet_recv_pressed_key, sizeof(packet_recv_pressed_key))) { char keycode = buf[13]; if (unistimdebug) { ast_verb(0, "Key pressed: keycode = 0x%.2x - current state: %s\n", (unsigned)keycode, ptestate_tostr(pte->state)); } if (keycode == KEY_MUTE) { microphone_mute_toggle(pte); } switch (pte->state) { case STATE_INIT: if (unistimdebug) { ast_verb(0, "No keys allowed in the init state\n"); } break; case STATE_AUTHDENY: if (unistimdebug) { ast_verb(0, "No keys allowed in authdeny state\n"); } break; case STATE_MAINPAGE: key_main_page(pte, keycode); break; case STATE_DIALPAGE: key_dial_page(pte, keycode); break; case STATE_RINGING: key_ringing(pte, keycode); break; case STATE_CALL: key_call(pte, keycode); break; case STATE_EXTENSION: key_select_extension(pte, keycode); break; case STATE_SELECTOPTION: key_select_option(pte, keycode); break; case STATE_SELECTCODEC: key_select_codec(pte, keycode); break; case STATE_SELECTLANGUAGE: key_select_language(pte, keycode); break; case STATE_HISTORY: key_history(pte, keycode); break; default: ast_log(LOG_WARNING, "Key : Unknown state\n"); } return; } if (memcmp(buf + SIZE_HEADER, packet_recv_pick_up, sizeof(packet_recv_pick_up)) == 0) { if (unistimdebug) { ast_verb(0, "Handset off hook, current state: %s\n", ptestate_tostr(pte->state)); } if (!pte->device) { /* We are not yet registered (asking for a TN in AUTOPROVISIONING_TN) */ return; } pte->device->receiver_state = STATE_OFFHOOK; if (pte->device->output == OUTPUT_HEADPHONE) { send_select_output(pte, OUTPUT_HEADPHONE, pte->device->volume, MUTE_OFF); } else { send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF); } if (pte->state == STATE_RINGING) { handle_call_incoming(pte); } else if ((pte->state == STATE_DIALPAGE) || (pte->state == STATE_CALL)) { send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF); } else if (pte->state == STATE_EXTENSION) { /* We must have a TN before calling */ return; } else { pte->device->selected = get_avail_softkey(pte, NULL); send_select_output(pte, OUTPUT_HANDSET, pte->device->volume, MUTE_OFF); handle_dial_page(pte); } return; } if (memcmp(buf + SIZE_HEADER, packet_recv_hangup, sizeof(packet_recv_hangup)) == 0) { if (unistimdebug) { ast_verb(0, "Handset on hook, current state: %s\n", ptestate_tostr(pte->state)); } if (!pte->device) { return; } pte->device->receiver_state = STATE_ONHOOK; if (pte->state == STATE_CALL) { if (pte->device->output != OUTPUT_SPEAKER) { close_call(pte); } } else if (pte->state == STATE_EXTENSION) { return; } else { pte->device->nextdial = 0; show_main_page(pte); } return; } strcpy(tmpbuf, ast_inet_ntoa(pte->sin.sin_addr)); strcat(tmpbuf, " Unknown request packet\n"); if (unistimdebug) { ast_debug(1, "%s", tmpbuf); } return; } static void parsing(int size, unsigned char *buf, struct unistimsession *pte, struct sockaddr_in *addr_from) { unsigned short *sbuf = (unsigned short *) buf; unsigned short seq; char tmpbuf[255]; strcpy(tmpbuf, ast_inet_ntoa(addr_from->sin_addr)); if (size < 10) { if (size == 0) { ast_log(LOG_WARNING, "%s Read error\n", tmpbuf); } else { ast_log(LOG_NOTICE, "%s Packet too short - ignoring\n", tmpbuf); } return; } if (sbuf[0] == 0xffff) { /* Starting with 0xffff ? *//* Yes, discovery packet ? */ if (size != sizeof(packet_rcv_discovery)) { ast_log(LOG_NOTICE, "%s Invalid size of a discovery packet\n", tmpbuf); } else { if (memcmp(buf, packet_rcv_discovery, sizeof(packet_rcv_discovery)) == 0) { if (unistimdebug) { ast_verb(0, "Discovery packet received - Sending Discovery ACK\n"); } if (pte) { /* A session was already active for this IP ? */ if (pte->state == STATE_INIT) { /* Yes, but it's a dupe */ if (unistimdebug) { ast_verb(1, "Duplicated Discovery packet\n"); } send_raw_client(sizeof(packet_send_discovery_ack), packet_send_discovery_ack, addr_from, &pte->sout); pte->seq_phone = (short) 0x0000; /* reset sequence number */ } else { /* No, probably a reboot, phone side */ close_client(pte); /* Cleanup the previous session */ if (create_client(addr_from)) { send_raw_client(sizeof(packet_send_discovery_ack), packet_send_discovery_ack, addr_from, &pte->sout); } } } else { /* Creating new entry in our phone list */ if ((pte = create_client(addr_from))) { send_raw_client(sizeof(packet_send_discovery_ack), packet_send_discovery_ack, addr_from, &pte->sout); } } return; } ast_log(LOG_NOTICE, "%s Invalid discovery packet\n", tmpbuf); } return; } if (!pte) { if (unistimdebug) { ast_verb(0, "%s Not a discovery packet from an unknown source : ignoring\n", tmpbuf); } return; } if (sbuf[0] != 0) { /* Starting with something else than 0x0000 ? */ ast_log(LOG_NOTICE, "Unknown packet received - ignoring\n"); return; } if (buf[5] != 2) { ast_log(LOG_NOTICE, "%s Wrong direction : got 0x%.2x expected 0x02\n", tmpbuf, (unsigned)buf[5]); return; } seq = ntohs(sbuf[1]); if (buf[4] == 1) { ast_mutex_lock(&pte->lock); if (unistimdebug) { ast_verb(0, "ACK received for packet #0x%.4x\n", (unsigned)seq); } pte->nb_retransmit = 0; if ((pte->last_seq_ack) + 1 == seq) { pte->last_seq_ack++; check_send_queue(pte); ast_mutex_unlock(&pte->lock); return; } if (pte->last_seq_ack > seq) { if (pte->last_seq_ack == 0xffff) { ast_verb(0, "ACK at 0xffff, restarting counter.\n"); pte->last_seq_ack = 0; } else { ast_log(LOG_NOTICE, "%s Warning : ACK received for an already ACKed packet : #0x%.4x we are at #0x%.4x\n", tmpbuf, (unsigned)seq, (unsigned)pte->last_seq_ack); } ast_mutex_unlock(&pte->lock); return; } if (pte->seq_server < seq) { ast_log(LOG_NOTICE, "%s Error : ACK received for a non-existent packet : #0x%.4x\n", tmpbuf, (unsigned)pte->seq_server); ast_mutex_unlock(&pte->lock); return; } if (unistimdebug) { ast_verb(0, "%s ACK gap : Received ACK #0x%.4x, previous was #0x%.4x\n", tmpbuf, (unsigned)seq, (unsigned)pte->last_seq_ack); } pte->last_seq_ack = seq; check_send_queue(pte); ast_mutex_unlock(&pte->lock); return; } if (buf[4] == 2) { if (unistimdebug) { ast_verb(0, "Request received\n"); } if (pte->seq_phone == seq) { /* Send ACK */ buf[4] = 1; buf[5] = 1; send_raw_client(SIZE_HEADER, buf, addr_from, &pte->sout); pte->seq_phone++; process_request(size, buf, pte); return; } if (pte->seq_phone > seq) { ast_log(LOG_NOTICE, "%s Warning : received a retransmitted packet : #0x%.4x (we are at #0x%.4x)\n", tmpbuf, (unsigned)seq, (unsigned)pte->seq_phone); /* BUG ? pte->device->seq_phone = seq; */ /* Send ACK */ buf[4] = 1; buf[5] = 1; send_raw_client(SIZE_HEADER, buf, addr_from, &pte->sout); return; } ast_log(LOG_NOTICE, "%s Warning : we lost a packet : received #0x%.4x (we are at #0x%.4x)\n", tmpbuf, (unsigned)seq, (unsigned)pte->seq_phone); return; } if (buf[4] == 0) { ast_log(LOG_NOTICE, "%s Retransmit request for packet #0x%.4x\n", tmpbuf, (unsigned)seq); if (pte->last_seq_ack > seq) { ast_log(LOG_NOTICE, "%s Error : received a request for an already ACKed packet : #0x%.4x\n", tmpbuf, (unsigned)pte->last_seq_ack); return; } if (pte->seq_server < seq) { ast_log(LOG_NOTICE, "%s Error : received a request for a non-existent packet : #0x%.4x\n", tmpbuf, (unsigned)pte->seq_server); return; } send_retransmit(pte); return; } ast_log(LOG_NOTICE, "%s Unknown request : got 0x%.2x expected 0x00,0x01 or 0x02\n", tmpbuf, (unsigned)buf[4]); return; } static struct unistimsession *channel_to_session(struct ast_channel *ast) { struct unistim_subchannel *sub; if (!ast) { ast_log(LOG_WARNING, "Unistim callback function called with a null channel\n"); return NULL; } if (!ast_channel_tech_pvt(ast)) { ast_log(LOG_WARNING, "Unistim callback function called without a tech_pvt\n"); return NULL; } sub = ast_channel_tech_pvt(ast); if (!sub->parent) { ast_log(LOG_WARNING, "Unistim callback function called without a line\n"); return NULL; } if (!sub->parent->parent) { ast_log(LOG_WARNING, "Unistim callback function called without a device\n"); return NULL; } ast_mutex_lock(&sub->parent->parent->lock); if (!sub->parent->parent->session) { ast_log(LOG_WARNING, "Unistim callback function called without a session\n"); ast_mutex_unlock(&sub->parent->parent->lock); return NULL; } ast_mutex_unlock(&sub->parent->parent->lock); return sub->parent->parent->session; } static void send_callerid_screen(struct unistimsession *pte, struct unistim_subchannel *sub) { char *cidname_str; char *cidnum_str; if (!sub) { return; } if (sub->owner) { if (ast_channel_connected(sub->owner)->id.number.valid && ast_channel_connected(sub->owner)->id.number.str) { cidnum_str = ast_channel_connected(sub->owner)->id.number.str; } else { cidnum_str = DEFAULTCALLERID; } change_callerid(pte, 0, cidnum_str); if (strlen(cidnum_str) == 0) { cidnum_str = DEFAULTCALLERID; } if (ast_channel_connected(sub->owner)->id.name.valid && ast_channel_connected(sub->owner)->id.name.str) { cidname_str = ast_channel_connected(sub->owner)->id.name.str; } else { cidname_str = DEFAULTCALLERNAME; } change_callerid(pte, 1, cidname_str); if (strlen(cidname_str) == 0) { cidname_str = DEFAULTCALLERNAME; } if (pte->device->height == 1) { char tmpstr[256]; snprintf(tmpstr, sizeof(tmpstr), "%s %s", cidnum_str, ustmtext(cidname_str, pte)); send_text(TEXT_LINE0, TEXT_NORMAL, pte, tmpstr); } else { send_text(TEXT_LINE0, TEXT_NORMAL, pte, cidname_str); send_text(TEXT_LINE1, TEXT_NORMAL, pte, ustmtext(cidnum_str, pte)); } } } /*--- unistim_call: Initiate UNISTIM call from PBX ---*/ /* used from the dial() application */ static int unistim_call(struct ast_channel *ast, const char *dest, int timeout) { int res = 0, i; struct unistim_subchannel *sub, *sub_real; struct unistimsession *session; char ringstyle, ringvolume; session = channel_to_session(ast); if (!session) { ast_log(LOG_ERROR, "Device not registered, cannot call %s\n", dest); return -1; } sub = ast_channel_tech_pvt(ast); sub_real = get_sub(session->device, SUB_REAL); if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "unistim_call called on %s, neither down nor reserved\n", ast_channel_name(ast)); return -1; } if (unistimdebug) { ast_verb(3, "unistim_call(%s)\n", ast_channel_name(ast)); } session->state = STATE_RINGING; send_callerid_screen(session, sub); if (ast_strlen_zero(ast_channel_call_forward(ast))) { /* Send ring only if no call forward, otherwise short ring will apear */ send_text(TEXT_LINE2, TEXT_NORMAL, session, ustmtext("is calling you.", session)); send_text_status(session, ustmtext("Accept Ignore Hangup", session)); if (sub_real) { ringstyle = session->device->cwstyle; ringvolume = session->device->cwvolume; } else { ringstyle = (sub->ringstyle == -1) ? session->device->ringstyle : sub->ringstyle; ringvolume = (sub->ringvolume == -1) ? session->device->ringvolume : sub->ringvolume; } send_ring(session, ringvolume, ringstyle); change_favorite_icon(session, FAV_ICON_SPEAKER_ONHOOK_BLACK + FAV_BLINK_FAST); /* Send call identification to all */ for (i = 0; i < FAVNUM; i++) { if (!soft_key_visible(session->device, i)) { continue; } if (session->device->ssub[i]) { continue; } if (is_key_line(session->device, i) && !strcmp(sub->parent->name, session->device->sline[i]->name)) { if (unistimdebug) { ast_verb(0, "Found softkey %d for line %s\n", i, sub->parent->name); } send_favorite_short(i, FAV_ICON_SPEAKER_ONHOOK_BLACK + FAV_BLINK_FAST, session); session->device->ssub[i] = sub; } } } ast_setstate(ast, AST_STATE_RINGING); ast_queue_control(ast, AST_CONTROL_RINGING); return res; } static int unistim_hangup_clean(struct ast_channel *ast, struct unistim_subchannel *sub) { ast_mutex_lock(&sub->lock); ast_channel_tech_pvt_set(ast, NULL); unistim_set_owner(sub, NULL); sub->alreadygone = 0; ast_mutex_unlock(&sub->lock); if (sub->rtp) { if (unistimdebug) { ast_verb(0, "Destroying RTP session\n"); } ast_rtp_instance_destroy(sub->rtp); sub->rtp = NULL; } return 0; } /*--- unistim_hangup: Hangup UNISTIM call */ static int unistim_hangup(struct ast_channel *ast) { struct unistim_subchannel *sub = NULL, *sub_real = NULL, *sub_trans = NULL; struct unistim_line *l; struct unistim_device *d; struct unistimsession *s; int i,end_call = 1; s = channel_to_session(ast); sub = ast_channel_tech_pvt(ast); l = sub->parent; d = l->parent; if (!s) { ast_debug(1, "Asked to hangup channel not connected\n"); unistim_hangup_clean(ast, sub); return 0; } if (unistimdebug) { ast_verb(0, "unistim_hangup(%s) on %s@%s (STATE_%s)\n", ast_channel_name(ast), l->name, l->parent->name, ptestate_tostr(s->state)); } sub_trans = get_sub(d, SUB_THREEWAY); sub_real = get_sub(d, SUB_REAL); if (sub_trans && (sub_trans->owner) && (sub->subtype == SUB_REAL)) { /* 3rd party busy or congested and transfer_cancel_step2 does not called */ if (unistimdebug) { ast_verb(0, "Threeway call disconnected, switching to real call\n"); } ast_queue_unhold(sub_trans->owner); sub_trans->moh = 0; sub_trans->subtype = SUB_REAL; swap_subs(sub_trans, sub); send_text_status(s, ustmtext(" Transf Hangup", s)); send_callerid_screen(s, sub_trans); unistim_hangup_clean(ast, sub); unistim_unalloc_sub(d, sub); return 0; } if (sub_real && (sub_real->owner) && (sub->subtype == SUB_THREEWAY) && (s->state == STATE_CALL)) { /* 3way call cancelled by softkey pressed */ if (unistimdebug) { ast_verb(0, "Real call disconnected, stay in call\n"); } send_text_status(s, ustmtext(" Transf Hangup", s)); send_callerid_screen(s, sub_real); unistim_hangup_clean(ast, sub); unistim_unalloc_sub(d, sub); return 0; } if (sub->subtype == SUB_REAL) { sub_stop_silence(s, sub); } else if (sub->subtype == SUB_RING) { send_no_ring(s); for (i = 0; i < FAVNUM; i++) { if (!soft_key_visible(s->device, i)) { continue; } if (d->ssub[i] != sub) { if (d->ssub[i] != NULL) { /* Found other subchannel active other then hangup'ed one */ end_call = 0; } continue; } if (is_key_line(d, i) && !strcmp(l->name, d->sline[i]->name)) { send_favorite_short(i, FAV_LINE_ICON, s); d->ssub[i] = NULL; continue; } } } if (end_call) { send_end_call(s); /* Send end call packet only if ending active call, in other way sound should be loosed */ } sub->moh = 0; if (sub->softkey >= 0) { send_favorite_short(sub->softkey, FAV_LINE_ICON, s); } /* Delete assign sub to softkey */ for (i = 0; i < FAVNUM; i++) { if (d->ssub[i] == sub) { d->ssub[i] = NULL; break; } } /*refresh_all_favorite(s); */ /* TODO: Update favicons in case of DND keys */ if (s->state == STATE_RINGING && sub->subtype == SUB_RING) { send_no_ring(s); if (ast_channel_hangupcause(ast) != AST_CAUSE_ANSWERED_ELSEWHERE) { d->missed_call++; write_history(s, 'i', 1); } if (!sub_real) { show_main_page(s); } else { /* hangup on a ringing line: reset status to reflect that we're still on an active call */ s->state = STATE_CALL; send_callerid_screen(s, sub_real); send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("is on-line", s)); send_text_status(s, ustmtext(" Transf Hangup", s)); send_favorite_short(sub->softkey, FAV_ICON_OFFHOOK_BLACK, s); } } if (s->state == STATE_CALL && sub->subtype == SUB_REAL) { close_call(s); } sub->softkey = -1; unistim_hangup_clean(ast, sub); unistim_unalloc_sub(d, sub); return 0; } /*--- unistim_answer: Answer UNISTIM call */ static int unistim_answer(struct ast_channel *ast) { int res = 0; struct unistim_subchannel *sub; struct unistim_line *l; struct unistim_device *d; struct unistimsession *s; s = channel_to_session(ast); if (!s) { ast_log(LOG_WARNING, "unistim_answer on a disconnected device ?\n"); return -1; } sub = ast_channel_tech_pvt(ast); l = sub->parent; d = l->parent; if (unistimdebug) { ast_verb(0, "unistim_answer(%s) on %s@%s-%d\n", ast_channel_name(ast), l->name, l->parent->name, sub->softkey); } send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("is now on-line", s)); if (get_sub(d, SUB_THREEWAY)) { send_text_status(s, ustmtext("Transf Cancel", s)); } else { send_text_status(s, ustmtext(" Transf Hangup", s)); } send_start_timer(s); if (ast_channel_state(ast) != AST_STATE_UP) ast_setstate(ast, AST_STATE_UP); return res; } /*--- unistimsock_read: Read data from UNISTIM socket ---*/ /* Successful messages is connected to UNISTIM call and forwarded to parsing() */ static int unistimsock_read(int *id, int fd, short events, void *ignore) { struct sockaddr_in addr_from = { 0, }; struct unistimsession *cur = NULL; int found = 0; int tmp = 0; int dw_num_bytes_rcvd; unsigned int size_addr_from; #ifdef DUMP_PACKET int dw_num_bytes_rcvdd; #endif size_addr_from = sizeof(addr_from); dw_num_bytes_rcvd = recvfrom(unistimsock, buff, SIZE_PAGE, 0, (struct sockaddr *) &addr_from, &size_addr_from); if (dw_num_bytes_rcvd == -1) { if (errno == EAGAIN) { ast_log(LOG_NOTICE, "UNISTIM: Received packet with bad UDP checksum\n"); } else if (errno != ECONNREFUSED) { ast_log(LOG_WARNING, "Recv error %d (%s)\n", errno, strerror(errno)); } return 1; } /* Looking in the phone list if we already have a registration for him */ ast_mutex_lock(&sessionlock); cur = sessions; while (cur) { if (cur->sin.sin_addr.s_addr == addr_from.sin_addr.s_addr) { found = 1; break; } tmp++; cur = cur->next; } ast_mutex_unlock(&sessionlock); #ifdef DUMP_PACKET if (unistimdebug) ast_verb(0, "\n*** Dump %d bytes from %s - phone_table[%d] ***\n", dw_num_bytes_rcvd, ast_inet_ntoa(addr_from.sin_addr), tmp); for (dw_num_bytes_rcvdd = 0; dw_num_bytes_rcvdd < dw_num_bytes_rcvd; dw_num_bytes_rcvdd++) ast_verb(0, "%.2x ", (unsigned char) buff[dw_num_bytes_rcvdd]); ast_verb(0, "\n******************************************\n"); #endif if (!found) { if (unistimdebug) { ast_verb(0, "Received a packet from an unknown source\n"); } parsing(dw_num_bytes_rcvd, buff, NULL, (struct sockaddr_in *) &addr_from); } else { parsing(dw_num_bytes_rcvd, buff, cur, (struct sockaddr_in *) &addr_from); } return 1; } static struct ast_frame *unistim_rtp_read(const struct ast_channel *ast, const struct unistim_subchannel *sub) { /* Retrieve audio/etc from channel. Assumes sub->lock is already held. */ struct ast_frame *f; if (!ast) { ast_log(LOG_WARNING, "Channel NULL while reading\n"); return &ast_null_frame; } if (!sub->rtp) { ast_log(LOG_WARNING, "RTP handle NULL while reading on subchannel %u\n", sub->subtype); return &ast_null_frame; } switch (ast_channel_fdno(ast)) { case 0: f = ast_rtp_instance_read(sub->rtp, 0); /* RTP Audio */ break; case 1: f = ast_rtp_instance_read(sub->rtp, 1); /* RTCP Control Channel */ break; default: f = &ast_null_frame; } if (sub->owner) { /* We already hold the channel lock */ if (f->frametype == AST_FRAME_VOICE) { if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(sub->owner), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *cap_buf = ast_str_alloca(64); struct ast_format_cap *caps; ast_debug(1, "Oooh, format changed from %s to %s\n", ast_format_cap_get_names(ast_channel_nativeformats(sub->owner), &cap_buf), ast_format_get_name(f->subclass.format)); caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (caps) { ast_format_cap_append(caps, f->subclass.format, 0); ast_channel_nativeformats_set(sub->owner, caps); ao2_ref(caps, -1); } ast_set_read_format(sub->owner, ast_channel_readformat(sub->owner)); ast_set_write_format(sub->owner, ast_channel_writeformat(sub->owner)); } } } return f; } static struct ast_frame *unistim_read(struct ast_channel *ast) { struct ast_frame *fr; struct unistim_subchannel *sub = ast_channel_tech_pvt(ast); ast_mutex_lock(&sub->lock); fr = unistim_rtp_read(ast, sub); ast_mutex_unlock(&sub->lock); return fr; } static int unistim_write(struct ast_channel *ast, struct ast_frame *frame) { struct unistim_subchannel *sub = ast_channel_tech_pvt(ast); int res = 0; if (frame->frametype != AST_FRAME_VOICE) { if (frame->frametype == AST_FRAME_IMAGE) { return 0; } else { ast_log(LOG_WARNING, "Can't send %u type frames with unistim_write\n", frame->frametype); return 0; } } else { if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) { struct ast_str *cap_buf = ast_str_alloca(64); 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(ast), &cap_buf), ast_format_get_name(ast_channel_readformat(ast)), ast_format_get_name(ast_channel_writeformat(ast))); return -1; } } if (sub) { ast_mutex_lock(&sub->lock); if (sub->rtp) { res = ast_rtp_instance_write(sub->rtp, frame); } ast_mutex_unlock(&sub->lock); } return res; } static int unistim_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) { struct unistim_subchannel *p = ast_channel_tech_pvt(newchan); struct unistim_line *l = p->parent; ast_mutex_lock(&p->lock); ast_debug(1, "New owner for channel USTM/%s@%s-%u is %s\n", l->name, l->parent->name, p->subtype, ast_channel_name(newchan)); if (p->owner != oldchan) { ast_log(LOG_WARNING, "old channel wasn't %s (%p) but was %s (%p)\n", ast_channel_name(oldchan), oldchan, ast_channel_name(p->owner), p->owner); ast_mutex_unlock(&p->lock); return -1; } unistim_set_owner(p, newchan); ast_mutex_unlock(&p->lock); return 0; } static char *control2str(int ind) { switch (ind) { case AST_CONTROL_HANGUP: return "Other end has hungup"; case AST_CONTROL_RING: return "Local ring"; case AST_CONTROL_RINGING: return "Remote end is ringing"; case AST_CONTROL_ANSWER: return "Remote end has answered"; case AST_CONTROL_BUSY: return "Remote end is busy"; case AST_CONTROL_TAKEOFFHOOK: return "Make it go off hook"; case AST_CONTROL_OFFHOOK: return "Line is off hook"; case AST_CONTROL_CONGESTION: return "Congestion (circuits busy)"; case AST_CONTROL_FLASH: return "Flash hook"; case AST_CONTROL_WINK: return "Wink"; case AST_CONTROL_OPTION: return "Set a low-level option"; case AST_CONTROL_RADIO_KEY: return "Key Radio"; case AST_CONTROL_RADIO_UNKEY: return "Un-Key Radio"; case AST_CONTROL_CONNECTED_LINE: return "Remote end changed"; case AST_CONTROL_SRCCHANGE: return "RTP source updated"; case AST_CONTROL_SRCUPDATE: return "Source of media changed"; case -1: return "Stop tone"; } return "UNKNOWN"; } static void in_band_indication(struct ast_channel *ast, const struct ast_tone_zone *tz, const char *indication) { struct ast_tone_zone_sound *ts = NULL; if ((ts = ast_get_indication_tone(tz, indication))) { ast_playtones_start(ast, 0, ts->data, 1); ts = ast_tone_zone_sound_unref(ts); } else { ast_log(LOG_WARNING, "Unable to get indication tone for %s\n", indication); } } static int unistim_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen) { struct unistim_subchannel *sub; struct unistim_line *l; struct unistimsession *s; if (unistimdebug) { ast_verb(3, "Asked to indicate '%s' (%d) condition on channel %s\n", control2str(ind), ind, ast_channel_name(ast)); } s = channel_to_session(ast); if (!s) { return -1; } sub = ast_channel_tech_pvt(ast); l = sub->parent; switch (ind) { case AST_CONTROL_RINGING: if (ast_channel_state(ast) != AST_STATE_UP) { send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("Ringing...", s)); in_band_indication(ast, l->parent->tz, "ring"); s->device->missed_call = -1; break; } return -1; case AST_CONTROL_BUSY: if (ast_channel_state(ast) != AST_STATE_UP) { sub->alreadygone = 1; send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("Busy", s)); in_band_indication(ast, l->parent->tz, "busy"); s->device->missed_call = -1; break; } return -1; case AST_CONTROL_INCOMPLETE: /* Overlapped dialing is not currently supported for UNIStim. Treat an indication * of incomplete as congestion */ case AST_CONTROL_CONGESTION: if (ast_channel_state(ast) != AST_STATE_UP) { sub->alreadygone = 1; send_text(TEXT_LINE2, TEXT_NORMAL, s, ustmtext("Congestion", s)); in_band_indication(ast, l->parent->tz, "congestion"); s->device->missed_call = -1; break; } return -1; case AST_CONTROL_HOLD: ast_moh_start(ast, data, NULL); break; case AST_CONTROL_UNHOLD: ast_moh_stop(ast); break; case AST_CONTROL_PROGRESS: case AST_CONTROL_SRCUPDATE: case AST_CONTROL_PROCEEDING: case AST_CONTROL_UPDATE_RTP_PEER: break; case -1: ast_playtones_stop(ast); s->device->missed_call = 0; break; case AST_CONTROL_CONNECTED_LINE: ast_log(LOG_NOTICE, "Connected party is now %s <%s>\n", S_COR(ast_channel_connected(ast)->id.name.valid, ast_channel_connected(ast)->id.name.str, ""), S_COR(ast_channel_connected(ast)->id.number.valid, ast_channel_connected(ast)->id.number.str, "")); if (sub->subtype == SUB_REAL) { send_callerid_screen(s, sub); } break; case AST_CONTROL_SRCCHANGE: if (sub->rtp) { ast_rtp_instance_change_source(sub->rtp); } break; default: ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", ind); /* fallthrough */ case AST_CONTROL_PVT_CAUSE_CODE: case AST_CONTROL_MASQUERADE_NOTIFY: return -1; } return 0; } static struct unistim_subchannel *find_subchannel_by_name(const char *dest) { struct unistim_line *l; struct unistim_device *d; struct unistim_subchannel *sub = NULL; char line[256]; char *at; char *device; ast_copy_string(line, dest, sizeof(line)); at = strchr(line, '@'); if (!at) { ast_log(LOG_NOTICE, "Device '%s' has no @ (at) sign!\n", dest); return NULL; } *at = '\0'; at++; device = at; ast_mutex_lock(&devicelock); d = devices; at = strchr(device, '/'); /* Extra options ? */ if (at) { *at = '\0'; } while (d) { if (!strcasecmp(d->name, device)) { if (unistimdebug) { ast_verb(0, "Found device: %s\n", d->name); } /* Found the device */ AST_LIST_LOCK(&d->lines); AST_LIST_TRAVERSE(&d->lines, l, list) { /* Search for the right line */ if (!strcasecmp(l->name, line)) { if (unistimdebug) { ast_verb(0, "Found line: %s\n", l->name); } sub = get_sub(d, SUB_REAL); if (!sub) { sub = unistim_alloc_sub(d, SUB_REAL); } if (sub->owner) { /* Allocate additional channel if asterisk channel already here */ sub = unistim_alloc_sub(d, SUB_ONHOLD); } sub->ringvolume = -1; sub->ringstyle = -1; if (at) { /* Other options ? */ at++; /* Skip slash */ if (*at == 'r') { /* distinctive ring */ at++; if ((*at < '0') || (*at > '7')) { /* ring style */ ast_log(LOG_WARNING, "Invalid ring selection (%s)", at); } else { char ring_volume = -1; char ring_style = *at - '0'; at++; if ((*at >= '0') && (*at <= '3')) { /* ring volume */ ring_volume = *at - '0'; } if (unistimdebug) { ast_verb(0, "Distinctive ring: style #%d volume %d\n", ring_style, ring_volume); } sub->ringvolume = ring_volume; sub->ringstyle = ring_style; } } } sub->parent = l; break; } } AST_LIST_UNLOCK(&d->lines); if (sub) { ast_mutex_unlock(&devicelock); return sub; } } d = d->next; } /* Device not found */ ast_mutex_unlock(&devicelock); return NULL; } static int unistim_senddigit_begin(struct ast_channel *ast, char digit) { struct unistimsession *pte = channel_to_session(ast); if (!pte) { return -1; } return unistim_do_senddigit(pte, digit); } static int unistim_senddigit_end(struct ast_channel *ast, char digit, unsigned int duration) { struct unistimsession *pte = channel_to_session(ast); struct ast_frame f = { 0, }; struct unistim_subchannel *sub; sub = get_sub(pte->device, SUB_REAL); if (!sub || !sub->owner || sub->alreadygone) { ast_log(LOG_WARNING, "Unable to find subchannel in dtmf senddigit_end\n"); return -1; } if (unistimdebug) { ast_verb(0, "Send Digit off %c\n", digit); } if (!pte) { return -1; } send_tone(pte, 0, 0); f.frametype = AST_FRAME_DTMF; f.subclass.integer = digit; f.src = "unistim"; ast_queue_frame(sub->owner, &f); return 0; } /*--- unistim_sendtext: Display a text on the phone screen ---*/ /* Called from PBX core text message functions */ static int unistim_sendtext(struct ast_channel *ast, const char *text) { struct unistimsession *pte = channel_to_session(ast); int size; char tmp[TEXT_LENGTH_MAX + 1]; if (unistimdebug) { ast_verb(0, "unistim_sendtext called\n"); } if (!text) { ast_log(LOG_WARNING, "unistim_sendtext called with a null text\n"); return -1; } if (!pte) { return -1; } size = strlen(text); if (text[0] == '@') { int pos = 0, i = 1, tok = 0, sz = 0; char label[11]; char number[16]; char icon = '\0'; char cur = '\0'; memset(label, 0, 11); memset(number, 0, 16); while (text[i]) { cur = text[i++]; switch (tok) { case 0: if ((cur < '0') && (cur > '5')) { ast_log(LOG_WARNING, "sendtext failed : position must be a number beetween 0 and 5\n"); return 1; } pos = cur - '0'; tok = 1; continue; case 1: if (cur != '@') { ast_log(LOG_WARNING, "sendtext failed : invalid position\n"); return 1; } tok = 2; continue; case 2: if ((cur < '3') && (cur > '6')) { ast_log(LOG_WARNING, "sendtext failed : icon must be a number beetween 32 and 63 (first digit invalid)\n"); return 1; } icon = (cur - '0') * 10; tok = 3; continue; case 3: if ((cur < '0') && (cur > '9')) { ast_log(LOG_WARNING, "sendtext failed : icon must be a number beetween 32 and 63 (second digit invalid)\n"); return 1; } icon += (cur - '0'); tok = 4; continue; case 4: if (cur != '@') { ast_log(LOG_WARNING, "sendtext failed : icon must be a number beetween 32 and 63 (too many digits)\n"); return 1; } tok = 5; continue; case 5: if (cur == '@') { tok = 6; sz = 0; continue; } if (sz > 10) { continue; } label[sz] = cur; sz++; continue; case 6: if (sz > 15) { ast_log(LOG_WARNING, "sendtext failed : extension too long = %d (15 car max)\n", sz); return 1; } number[sz] = cur; sz++; continue; } } if (tok != 6) { ast_log(LOG_WARNING, "sendtext failed : incomplet command\n"); return 1; } if (!pte->device) { ast_log(LOG_WARNING, "sendtext failed : no device ?\n"); return 1; } strcpy(pte->device->softkeylabel[pos], label); strcpy(pte->device->softkeynumber[pos], number); pte->device->softkeyicon[pos] = icon; send_favorite(pos, icon, pte, label); return 0; } if (size <= TEXT_LENGTH_MAX * 2) { if (pte->device->height == 1) { send_text(TEXT_LINE0, TEXT_NORMAL, pte, text); } else { send_text(TEXT_LINE0, TEXT_NORMAL, pte, ustmtext("Message :", pte)); send_text(TEXT_LINE1, TEXT_NORMAL, pte, text); } if (size <= TEXT_LENGTH_MAX) { send_text(TEXT_LINE2, TEXT_NORMAL, pte, ""); return 0; } memcpy(tmp, text + TEXT_LENGTH_MAX, TEXT_LENGTH_MAX); tmp[sizeof(tmp) - 1] = '\0'; send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp); return 0; } send_text(TEXT_LINE0, TEXT_NORMAL, pte, text); memcpy(tmp, text + TEXT_LENGTH_MAX, TEXT_LENGTH_MAX); tmp[sizeof(tmp) - 1] = '\0'; send_text(TEXT_LINE1, TEXT_NORMAL, pte, tmp); memcpy(tmp, text + TEXT_LENGTH_MAX * 2, TEXT_LENGTH_MAX); tmp[sizeof(tmp) - 1] = '\0'; send_text(TEXT_LINE2, TEXT_NORMAL, pte, tmp); return 0; } /*--- unistim_send_mwi_to_peer: Send message waiting indication ---*/ static int unistim_send_mwi_to_peer(struct unistim_line *peer, unsigned int tick) { int new; RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), peer->mailbox); if (msg) { struct ast_mwi_state *mwi_state = stasis_message_data(msg); new = mwi_state->new_msgs; } else { /* Fall back on checking the mailbox directly */ new = ast_app_has_voicemail(peer->mailbox, NULL); } ast_debug(3, "MWI Status for mailbox %s is %d, lastmsgsent:%d\n", peer->mailbox, new, peer->parent->lastmsgssent); peer->parent->nextmsgcheck = tick + TIMER_MWI; /* Return now if it's the same thing we told them last time */ if ((peer->parent->session->state != STATE_MAINPAGE) || (new == peer->parent->lastmsgssent)) { return 0; } peer->parent->lastmsgssent = new; send_led_update(peer->parent->session, (new > 0)?LED_BAR_ON:LED_BAR_OFF); return 0; } /*--- unistim_new: Initiate a call in the UNISTIM channel */ /* called from unistim_request (calls from the pbx ) */ static struct ast_channel *unistim_new(struct unistim_subchannel *sub, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor) { struct ast_format_cap *caps; struct ast_channel *tmp; struct unistim_line *l; struct ast_format *tmpfmt; if (!sub) { ast_log(LOG_WARNING, "subchannel null in unistim_new\n"); return NULL; } if (!sub->parent) { ast_log(LOG_WARNING, "no line for subchannel %p\n", sub); return NULL; } caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!caps) { return NULL; } l = sub->parent; tmp = ast_channel_alloc(1, state, l->cid_num, NULL, l->accountcode, l->exten, l->parent->context, assignedids, requestor, l->amaflags, "USTM/%s@%s-%p", l->name, l->parent->name, sub); if (unistimdebug) { ast_verb(0, "unistim_new sub=%u (%p) chan=%p line=%s\n", sub->subtype, sub, tmp, l->name); } if (!tmp) { ast_log(LOG_WARNING, "Unable to allocate channel structure\n"); ao2_ref(caps, -1); return NULL; } ast_channel_stage_snapshot(tmp); if (ast_format_cap_count(l->cap)) { ast_format_cap_append_from_cap(caps, l->cap, AST_MEDIA_TYPE_UNKNOWN); } else { ast_format_cap_append_from_cap(caps, global_cap, AST_MEDIA_TYPE_UNKNOWN); } ast_channel_nativeformats_set(tmp, caps); ao2_ref(caps, -1); tmpfmt = ast_format_cap_get_format(ast_channel_nativeformats(tmp), 0); if (unistimdebug) { struct ast_str *native_buf = ast_str_alloca(64); struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *global_buf = ast_str_alloca(64); ast_verb(0, "Best codec = %s from nativeformats %s (line cap=%s global=%s)\n", ast_format_get_name(tmpfmt), ast_format_cap_get_names(ast_channel_nativeformats(tmp), &native_buf), ast_format_cap_get_names(l->cap, &cap_buf), ast_format_cap_get_names(global_cap, &global_buf)); } if ((sub->rtp) && (sub->subtype == 0)) { if (unistimdebug) { ast_verb(0, "New unistim channel with a previous rtp handle ?\n"); } ast_channel_internal_fd_set(tmp, 0, ast_rtp_instance_fd(sub->rtp, 0)); ast_channel_internal_fd_set(tmp, 1, ast_rtp_instance_fd(sub->rtp, 1)); } if (sub->rtp) { ast_jb_configure(tmp, &global_jbconf); } /* tmp->type = type; */ ast_setstate(tmp, state); if (state == AST_STATE_RING) { ast_channel_rings_set(tmp, 1); } ast_channel_adsicpe_set(tmp, AST_ADSI_UNAVAILABLE); ast_channel_set_writeformat(tmp, tmpfmt); ast_channel_set_rawwriteformat(tmp, tmpfmt); ast_channel_set_readformat(tmp, tmpfmt); ast_channel_set_rawreadformat(tmp, tmpfmt); ao2_ref(tmpfmt, -1); ast_channel_tech_pvt_set(tmp, sub); ast_channel_tech_set(tmp, &unistim_tech); if (!ast_strlen_zero(l->parent->language)) { ast_channel_language_set(tmp, l->parent->language); } unistim_set_owner(sub, tmp); ast_update_use_count(); ast_channel_callgroup_set(tmp, l->callgroup); ast_channel_pickupgroup_set(tmp, l->pickupgroup); ast_channel_call_forward_set(tmp, l->parent->call_forward); if (!ast_strlen_zero(l->cid_num)) { char *name, *loc, *instr; instr = ast_strdup(l->cid_num); if (instr) { ast_callerid_parse(instr, &name, &loc); ast_channel_caller(tmp)->id.number.valid = 1; ast_free(ast_channel_caller(tmp)->id.number.str); ast_channel_caller(tmp)->id.number.str = ast_strdup(loc); ast_channel_caller(tmp)->id.name.valid = 1; ast_free(ast_channel_caller(tmp)->id.name.str); ast_channel_caller(tmp)->id.name.str = ast_strdup(name); ast_free(instr); } } ast_channel_priority_set(tmp, 1); ast_channel_stage_snapshot_done(tmp); ast_channel_unlock(tmp); if (state != AST_STATE_DOWN) { if (unistimdebug) { ast_verb(0, "Starting pbx in unistim_new\n"); } if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); ast_hangup(tmp); tmp = NULL; } } return tmp; } static void unistim_set_owner(struct unistim_subchannel *sub, struct ast_channel *chan) { sub->owner = chan; if (sub->rtp) { ast_rtp_instance_set_channel_id(sub->rtp, sub->owner ? ast_channel_uniqueid(sub->owner) : ""); } } static void *do_monitor(void *data) { struct unistimsession *cur = NULL; unsigned int dw_timeout = 0; unsigned int tick; int res; int reloading; /* Add an I/O event to our UDP socket */ if (unistimsock > -1) { ast_io_add(io, unistimsock, unistimsock_read, AST_IO_IN, NULL); } /* This thread monitors our UDP socket and timers */ for (;;) { /* This loop is executed at least every IDLE_WAITus (1s) or every time a packet is received */ /* Looking for the smallest time-out value */ tick = get_tick_count(); dw_timeout = UINT_MAX; ast_mutex_lock(&sessionlock); cur = sessions; DEBUG_TIMER("checking timeout for session %p with tick = %u\n", cur, tick); while (cur) { DEBUG_TIMER("checking timeout for session %p timeout = %u\n", cur, cur->timeout); /* Check if we have miss something */ if (cur->timeout <= tick) { DEBUG_TIMER("Event for session %p\n", cur); /* If the queue is empty, send a ping */ if (cur->last_buf_available == 0) { send_ping(cur); } else { if (send_retransmit(cur)) { DEBUG_TIMER("The chained link was modified, restarting...\n"); cur = sessions; dw_timeout = UINT_MAX; continue; } } } if (dw_timeout > cur->timeout - tick) { dw_timeout = cur->timeout - tick; } /* Checking if the phone is logged on for a new MWI */ if (cur->device) { struct unistim_line *l; AST_LIST_LOCK(&cur->device->lines); AST_LIST_TRAVERSE(&cur->device->lines, l, list) { if ((!ast_strlen_zero(l->mailbox)) && (tick >= l->parent->nextmsgcheck)) { DEBUG_TIMER("Checking mailbox for MWI\n"); unistim_send_mwi_to_peer(l, tick); break; } } AST_LIST_UNLOCK(&cur->device->lines); if (cur->device->nextdial && tick >= cur->device->nextdial) { handle_call_outgoing(cur); cur->device->nextdial = 0; } } cur = cur->next; } ast_mutex_unlock(&sessionlock); DEBUG_TIMER("Waiting for %dus\n", dw_timeout); res = dw_timeout; /* We should not wait more than IDLE_WAIT */ if ((res < 0) || (res > IDLE_WAIT)) { res = IDLE_WAIT; } /* Wait for UDP messages for a maximum of res us */ res = ast_io_wait(io, res); /* This function will call unistimsock_read if a packet is received */ /* Check for a reload request */ ast_mutex_lock(&unistim_reload_lock); reloading = unistim_reloading; unistim_reloading = 0; ast_mutex_unlock(&unistim_reload_lock); if (reloading) { ast_verb(1, "Reloading unistim.conf...\n"); reload_config(); } pthread_testcancel(); } /* Never reached */ return NULL; } /*--- restart_monitor: Start the channel monitor thread ---*/ static int restart_monitor(void) { pthread_attr_t attr; /* If we're supposed to be stopped -- stay stopped */ if (monitor_thread == AST_PTHREADT_STOP) { return 0; } if (ast_mutex_lock(&monlock)) { ast_log(LOG_WARNING, "Unable to lock monitor\n"); return -1; } if (monitor_thread == pthread_self()) { ast_mutex_unlock(&monlock); ast_log(LOG_WARNING, "Cannot kill myself\n"); return -1; } if (monitor_thread != AST_PTHREADT_NULL) { /* Wake up the thread */ pthread_kill(monitor_thread, SIGURG); } else { pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); /* Start a new monitor */ if (ast_pthread_create(&monitor_thread, &attr, do_monitor, NULL) < 0) { ast_mutex_unlock(&monlock); ast_log(LOG_ERROR, "Unable to start monitor thread.\n"); return -1; } } ast_mutex_unlock(&monlock); return 0; } /*--- unistim_request: PBX interface function ---*/ /* UNISTIM calls initiated by the PBX arrive here */ static struct ast_channel *unistim_request(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 unistim_subchannel *sub, *sub_ring, *sub_trans; struct unistim_device *d; struct ast_channel *tmpc = NULL; char tmp[256]; if (!(ast_format_cap_iscompatible(cap, global_cap))) { struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *global_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format %s while capability is %s\n", ast_format_cap_get_names(cap, &cap_buf), ast_format_cap_get_names(global_cap, &global_buf)); return NULL; } ast_copy_string(tmp, dest, sizeof(tmp)); if (ast_strlen_zero(tmp)) { ast_log(LOG_NOTICE, "Unistim channels require a device\n"); return NULL; } sub = find_subchannel_by_name(tmp); if (!sub) { ast_log(LOG_NOTICE, "No available lines on: %s\n", dest); *cause = AST_CAUSE_CONGESTION; return NULL; } d = sub->parent->parent; sub_ring = get_sub(d, SUB_RING); sub_trans = get_sub(d, SUB_THREEWAY); /* Another request already in progress */ if (!d->session) { unistim_unalloc_sub(d, sub); *cause = AST_CAUSE_CONGESTION; return NULL; } if (sub_ring || sub_trans) { if (unistimdebug) { ast_verb(0, "Can't create channel, request already in progress: Busy!\n"); } unistim_unalloc_sub(d, sub); *cause = AST_CAUSE_BUSY; return NULL; } if (d->session->state == STATE_DIALPAGE) { if (unistimdebug) { ast_verb(0, "Can't create channel, user on dialpage: Busy!\n"); } unistim_unalloc_sub(d, sub); *cause = AST_CAUSE_BUSY; return NULL; } if (get_avail_softkey(d->session, sub->parent->name) == -1) { if (unistimdebug) { ast_verb(0, "Can't create channel for line %s, all lines busy\n", sub->parent->name); } unistim_unalloc_sub(d, sub); *cause = AST_CAUSE_BUSY; return NULL; } sub->subtype = SUB_RING; sub->softkey = -1; ast_format_cap_append_from_cap(sub->parent->cap, cap, AST_MEDIA_TYPE_UNKNOWN); tmpc = unistim_new(sub, AST_STATE_DOWN, assignedids, requestor); if (!tmpc) { ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp); } if (unistimdebug) { ast_verb(0, "unistim_request owner = %p\n", sub->owner); } restart_monitor(); /* and finish */ return tmpc; } static char *unistim_show_info(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct unistim_device *device = devices; struct unistim_line *line; struct unistim_subchannel *sub; struct unistimsession *s; struct ast_str *cap_buf = ast_str_alloca(64); switch (cmd) { case CLI_INIT: e->command = "unistim show info"; e->usage = "Usage: unistim show info\n" " Dump internal structures.\n\n" " device\n" " ->line\n" " -->sub\n" " ==>key\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } if (a->argc != e->args) { return CLI_SHOWUSAGE; } ast_cli(a->fd, "Dumping internal structures:\n"); ast_mutex_lock(&devicelock); while (device) { int i; ast_cli(a->fd, "\nname=%s id=%s ha=%p sess=%p device=%p selected=%d height=%d\n", device->name, device->id, device->ha, device->session, device, device->selected, device->height); AST_LIST_LOCK(&device->lines); AST_LIST_TRAVERSE(&device->lines,line,list) { ast_cli(a->fd, "->name=%s fullname=%s exten=%s callid=%s cap=%s line=%p\n", line->name, line->fullname, line->exten, line->cid_num, ast_format_cap_get_names(line->cap, &cap_buf), line); } AST_LIST_UNLOCK(&device->lines); AST_LIST_LOCK(&device->subs); AST_LIST_TRAVERSE(&device->subs, sub, list) { if (!sub) { continue; } ast_cli(a->fd, "-->subtype=%s chan=%p rtp=%p line=%p alreadygone=%d softkey=%d\n", subtype_tostr(sub->subtype), sub->owner, sub->rtp, sub->parent, sub->alreadygone, sub->softkey); } AST_LIST_UNLOCK(&device->subs); for (i = 0; i < FAVNUM; i++) { if (!soft_key_visible(device, i)) { continue; } ast_cli(a->fd, "==> %d. dev=%s icon=%#-4x label=%-10s number=%-5s sub=%p line=%p\n", i, device->softkeydevice[i], (unsigned)device->softkeyicon[i], device->softkeylabel[i], device->softkeynumber[i], device->ssub[i], device->sline[i]); } device = device->next; } ast_mutex_unlock(&devicelock); ast_cli(a->fd, "\nSessions:\n"); ast_mutex_lock(&sessionlock); s = sessions; while (s) { ast_cli(a->fd, "sin=%s timeout=%d state=%s macaddr=%s device=%p session=%p\n", ast_inet_ntoa(s->sin.sin_addr), s->timeout, ptestate_tostr(s->state), s->macaddr, s->device, s); s = s->next; } ast_mutex_unlock(&sessionlock); return CLI_SUCCESS; } static char *unistim_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct unistim_device *device = devices; switch (cmd) { case CLI_INIT: e->command = "unistim show devices"; e->usage = "Usage: unistim show devices\n" " Lists all known Unistim devices.\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } if (a->argc != e->args) return CLI_SHOWUSAGE; ast_cli(a->fd, "%-20.20s %-20.20s %-15.15s %-15.15s %s\n", "Name/username", "MAC", "Host", "Firmware", "Status"); ast_mutex_lock(&devicelock); while (device) { ast_cli(a->fd, "%-20.20s %-20.20s %-15.15s %-15.15s %s\n", device->name, device->id, (!device->session) ? "(Unspecified)" : ast_inet_ntoa(device->session->sin.sin_addr), (!device->session) ? "(Unspecified)" : device->session->firmware, (!device->session) ? "UNKNOWN" : "OK"); device = device->next; } ast_mutex_unlock(&devicelock); return CLI_SUCCESS; } static char *unistim_sp(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { BUFFSEND; struct unistim_subchannel *sub; int i, j = 0, len; unsigned char c, cc; char tmp[256]; switch (cmd) { case CLI_INIT: e->command = "unistim send packet"; e->usage = "Usage: unistim send packet USTM/line@name hexa\n" " unistim send packet USTM/1000@hans 19040004\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } if (a->argc < 5) { return CLI_SHOWUSAGE; } if (strlen(a->argv[3]) < 9) { return CLI_SHOWUSAGE; } len = strlen(a->argv[4]); if (len % 2) { return CLI_SHOWUSAGE; } ast_copy_string(tmp, a->argv[3] + 5, sizeof(tmp)); sub = find_subchannel_by_name(tmp); if (!sub) { ast_cli(a->fd, "Can't find '%s'\n", tmp); return CLI_SUCCESS; } if (!sub->parent->parent->session) { ast_cli(a->fd, "'%s' is not connected\n", tmp); return CLI_SUCCESS; } ast_cli(a->fd, "Sending '%s' to %s (%p)\n", a->argv[4], tmp, sub->parent->parent->session); for (i = 0; i < len; i++) { c = a->argv[4][i]; if (c >= 'a') { c -= 'a' - 10; } else { c -= '0'; } i++; cc = a->argv[4][i]; if (cc >= 'a') { cc -= 'a' - 10; } else { cc -= '0'; } tmp[j++] = (c << 4) | cc; } memcpy(buffsend + SIZE_HEADER, tmp, j); send_client(SIZE_HEADER + j, buffsend, sub->parent->parent->session); return CLI_SUCCESS; } static char *unistim_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "unistim set debug {on|off}"; e->usage = "Usage: unistim set debug\n" " Display debug messages.\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } if (a->argc != e->args) { return CLI_SHOWUSAGE; } if (!strcasecmp(a->argv[3], "on")) { unistimdebug = 1; ast_cli(a->fd, "UNISTIM Debugging Enabled\n"); } else if (!strcasecmp(a->argv[3], "off")) { unistimdebug = 0; ast_cli(a->fd, "UNISTIM Debugging Disabled\n"); } else { return CLI_SHOWUSAGE; } return CLI_SUCCESS; } /*! \brief --- unistim_reload: Force reload of module from cli --- * Runs in the asterisk main thread, so don't do anything useful * but setting a flag and waiting for do_monitor to do the job * in our thread */ static char *unistim_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "unistim reload"; e->usage = "Usage: unistim reload\n" " Reloads UNISTIM configuration from unistim.conf\n"; return NULL; case CLI_GENERATE: return NULL; /* no completion */ } if (e && a && a->argc != e->args) { return CLI_SHOWUSAGE; } reload(); return CLI_SUCCESS; } static struct ast_cli_entry unistim_cli[] = { AST_CLI_DEFINE(unistim_reload, "Reload UNISTIM configuration"), AST_CLI_DEFINE(unistim_show_info, "Show UNISTIM info"), AST_CLI_DEFINE(unistim_show_devices, "Show UNISTIM devices"), AST_CLI_DEFINE(unistim_sp, "Send packet (for reverse engineering)"), AST_CLI_DEFINE(unistim_do_debug, "Toggle UNITSTIM debugging"), }; static void unquote(char *out, const char *src, int maxlen) { int len = strlen(src); if (!len) { return; } if ((len > 1) && src[0] == '\"') { /* This is a quoted string */ src++; /* Don't take more than what's there */ len--; if (maxlen > len - 1) { maxlen = len - 1; } memcpy(out, src, maxlen); ((char *) out)[maxlen] = '\0'; } else { memcpy(out, src, maxlen); } return; } static int parse_bookmark(const char *text, struct unistim_device *d) { char line[256]; char *at; char *number; char *icon; int p; int len = strlen(text); ast_copy_string(line, text, sizeof(line)); /* Position specified ? */ if ((len > 2) && (line[1] == '@')) { p = line[0]; if ((p >= '0') && (p <= '5')) { p -= '0'; } else { ast_log(LOG_WARNING, "Invalid position for bookmark : must be between 0 and 5\n"); return 0; } if (d->softkeyicon[p] != 0) { ast_log(LOG_WARNING, "Invalid position %d for bookmark : already used:\n", p); return 0; } memmove(line, line + 2, sizeof(line) - 2); } else { /* No position specified, looking for a free slot */ for (p = 0; p < FAVNUM; p++) { if (!d->softkeyicon[p]) { break; } } if (p == FAVNUM) { ast_log(LOG_WARNING, "No more free bookmark position\n"); return 0; } } at = strchr(line, '@'); if (!at) { ast_log(LOG_NOTICE, "Bookmark entry '%s' has no @ (at) sign!\n", text); return 0; } *at = '\0'; at++; number = at; at = strchr(at, '@'); if (ast_strlen_zero(number)) { ast_log(LOG_NOTICE, "Bookmark entry '%s' has no number\n", text); return 0; } if (ast_strlen_zero(line)) { ast_log(LOG_NOTICE, "Bookmark entry '%s' has no description\n", text); return 0; } at = strchr(number, '@'); if (!at) { d->softkeyicon[p] = FAV_ICON_SHARP; /* default icon */ } else { *at = '\0'; at++; icon = at; if (ast_strlen_zero(icon)) { ast_log(LOG_NOTICE, "Bookmark entry '%s' has no icon value\n", text); return 0; } if (strncmp(icon, "USTM/", 5)) { d->softkeyicon[p] = atoi(icon); } else { d->softkeyicon[p] = 1; ast_copy_string(d->softkeydevice[p], icon + 5, sizeof(d->softkeydevice[p])); } } ast_copy_string(d->softkeylabel[p], line, sizeof(d->softkeylabel[p])); ast_copy_string(d->softkeynumber[p], number, sizeof(d->softkeynumber[p])); if (unistimdebug) { ast_verb(0, "New bookmark at pos %d label='%s' number='%s' icon=%#x\n", p, d->softkeylabel[p], d->softkeynumber[p], (unsigned)d->softkeyicon[p]); } return 1; } /* Looking for dynamic icons entries in bookmarks */ static void finish_bookmark(void) { struct unistim_device *d = devices; int i; ast_mutex_lock(&devicelock); while (d) { for (i = 0; i < 6; i++) { if (d->softkeyicon[i] == 1) { /* Something for us */ struct unistim_device *d2 = devices; while (d2) { if (!strcmp(d->softkeydevice[i], d2->name)) { d->sp[i] = d2; d->softkeyicon[i] = 0; break; } d2 = d2->next; } if (d->sp[i] == NULL) { ast_log(LOG_NOTICE, "Bookmark entry with device %s not found\n", d->softkeydevice[i]); } } } d = d->next; } ast_mutex_unlock(&devicelock); } static struct unistim_line *find_line_by_number(struct unistim_device *d, const char *val) { struct unistim_line *l, *ret = NULL; AST_LIST_LOCK(&d->lines); AST_LIST_TRAVERSE(&d->lines, l, list) { if (!strcmp(l->name, val)) { ret = l; break; } } AST_LIST_UNLOCK(&d->lines); return ret; } static struct unistim_device *build_device(const char *cat, const struct ast_variable *v) { struct unistim_device *d; struct unistim_line *l = NULL, *lt = NULL; int create = 1; int nbsoftkey, dateformat, timeformat, callhistory, sharpdial, linecnt; char linelabel[AST_MAX_EXTENSION]; char ringvolume, ringstyle, cwvolume, cwstyle; /* First, we need to know if we already have this name in our list */ /* Get a lock for the device chained list */ ast_mutex_lock(&devicelock); d = devices; while (d) { if (!strcmp(d->name, cat)) { /* Yep, we alreay have this one */ if (unistimsock < 0) { /* It's a dupe */ ast_log(LOG_WARNING, "Duplicate entry found (%s), ignoring.\n", cat); ast_mutex_unlock(&devicelock); return NULL; } /* we're reloading right now */ create = 0; break; } d = d->next; } ast_mutex_unlock(&devicelock); if (!(lt = ast_calloc(1, sizeof(*lt)))) { return NULL; } if (create) { if (!(d = ast_calloc(1, sizeof(*d)))) { return NULL; } ast_mutex_init(&d->lock); ast_copy_string(d->name, cat, sizeof(d->name)); ast_copy_string(d->context, DEFAULTCONTEXT, sizeof(d->context)); d->contrast = -1; d->output = OUTPUT_HANDSET; d->previous_output = OUTPUT_HANDSET; d->volume = VOLUME_LOW; d->microphone = MUTE_OFF; d->height = DEFAULTHEIGHT; d->selected = -1; d->interdigit_timer = DEFAULT_INTERDIGIT_TIMER; } else { /* Delete existing line information */ AST_LIST_LOCK(&d->lines); AST_LIST_TRAVERSE_SAFE_BEGIN(&d->lines, l, list){ AST_LIST_REMOVE_CURRENT(list); unistim_line_destroy(l); } AST_LIST_TRAVERSE_SAFE_END AST_LIST_UNLOCK(&d->lines); /* reset bookmarks */ memset(d->softkeylabel, 0, sizeof(d->softkeylabel)); memset(d->softkeynumber, 0, sizeof(d->softkeynumber)); memset(d->softkeyicon, 0, sizeof(d->softkeyicon)); memset(d->softkeydevice, 0, sizeof(d->softkeydevice)); memset(d->ssub, 0, sizeof(d->ssub)); memset(d->sline, 0, sizeof(d->sline)); memset(d->sp, 0, sizeof(d->sp)); } linelabel[0] = '\0'; dateformat = 1; timeformat = 1; ringvolume = 2; cwvolume = 1; callhistory = 1; sharpdial = 0; ringstyle = 3; cwstyle = 2; nbsoftkey = 0; linecnt = 0; d->dtmfduration = 0; while (v) { if (!strcasecmp(v->name, "rtp_port")) { d->rtp_port = atoi(v->value); } else if (!strcasecmp(v->name, "rtp_method")) { d->rtp_method = atoi(v->value); } else if (!strcasecmp(v->name, "status_method")) { d->status_method = atoi(v->value); } else if (!strcasecmp(v->name, "device")) { ast_copy_string(d->id, v->value, sizeof(d->id)); } else if (!strcasecmp(v->name, "tn")) { ast_copy_string(d->extension_number, v->value, sizeof(d->extension_number)); } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { d->ha = ast_append_ha(v->name, v->value, d->ha, NULL); } else if (!strcasecmp(v->name, "context")) { ast_copy_string(d->context, v->value, sizeof(d->context)); } else if (!strcasecmp(v->name, "maintext0")) { unquote(d->maintext0, v->value, sizeof(d->maintext0) - 1); } else if (!strcasecmp(v->name, "maintext1")) { unquote(d->maintext1, v->value, sizeof(d->maintext1) - 1); } else if (!strcasecmp(v->name, "maintext2")) { unquote(d->maintext2, v->value, sizeof(d->maintext2) - 1); } else if (!strcasecmp(v->name, "titledefault")) { unquote(d->titledefault, v->value, sizeof(d->titledefault) - 1); } else if (!strcasecmp(v->name, "dateformat")) { dateformat = atoi(v->value); } else if (!strcasecmp(v->name, "timeformat")) { timeformat = atoi(v->value); } else if (!strcasecmp(v->name, "contrast")) { d->contrast = atoi(v->value); if ((d->contrast < 0) || (d->contrast > 15)) { ast_log(LOG_WARNING, "contrast must be beetween 0 and 15\n"); d->contrast = 8; } } else if (!strcasecmp(v->name, "nat")) { d->nat = ast_true(v->value); } else if (!strcasecmp(v->name, "hasexp")) { d->hasexp = ast_true(v->value); } else if (!strcasecmp(v->name, "ringvolume")) { ringvolume = atoi(v->value); } else if (!strcasecmp(v->name, "ringstyle")) { ringstyle = atoi(v->value); } else if (!strcasecmp(v->name, "cwvolume")) { cwvolume = atoi(v->value); } else if (!strcasecmp(v->name, "cwstyle")) { cwstyle = atoi(v->value); } else if (!strcasecmp(v->name, "callhistory")) { callhistory = atoi(v->value); } else if (!strcasecmp(v->name, "sharpdial")) { sharpdial = ast_true(v->value) ? 1 : 0; } else if (!strcasecmp(v->name, "interdigit_timer")) { d->interdigit_timer = atoi(v->value); } else if (!strcasecmp(v->name, "dtmf_duration")) { d->dtmfduration = atoi(v->value); if (d->dtmfduration > 150) { d->dtmfduration = 150; } } else if (!strcasecmp(v->name, "callerid")) { if (!strcasecmp(v->value, "asreceived")) { lt->cid_num[0] = '\0'; } else { ast_copy_string(lt->cid_num, v->value, sizeof(lt->cid_num)); } } else if (!strcasecmp(v->name, "language")) { ast_copy_string(d->language, v->value, sizeof(d->language)); } else if (!strcasecmp(v->name, "country")) { ast_copy_string(d->country, v->value, sizeof(d->country)); } else if (!strcasecmp(v->name, "accountcode")) { ast_copy_string(lt->accountcode, v->value, sizeof(lt->accountcode)); } else if (!strcasecmp(v->name, "amaflags")) { int y; y = ast_channel_string2amaflag(v->value); if (y < 0) { ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno); } else { lt->amaflags = y; } } else if (!strcasecmp(v->name, "musiconhold")) { ast_copy_string(lt->musicclass, v->value, sizeof(lt->musicclass)); } else if (!strcasecmp(v->name, "callgroup")) { lt->callgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "pickupgroup")) { lt->pickupgroup = ast_get_group(v->value); } else if (!strcasecmp(v->name, "mailbox")) { ast_copy_string(lt->mailbox, v->value, sizeof(lt->mailbox)); } else if (!strcasecmp(v->name, "parkinglot")) { ast_copy_string(lt->parkinglot, v->value, sizeof(lt->parkinglot)); } else if (!strcasecmp(v->name, "linelabel")) { unquote(linelabel, v->value, sizeof(linelabel) - 1); } else if (!strcasecmp(v->name, "extension")) { if (!strcasecmp(v->value, "none")) { d->extension = EXTENSION_NONE; } else if (!strcasecmp(v->value, "ask")) { d->extension = EXTENSION_ASK; } else if (!strcasecmp(v->value, "line")) { d->extension = EXTENSION_LINE; } else { ast_log(LOG_WARNING, "Unknown extension option.\n"); } } else if (!strcasecmp(v->name, "bookmark")) { if (nbsoftkey > 5) { ast_log(LOG_WARNING, "More than 6 softkeys defined. Ignoring new entries.\n"); } else { if (parse_bookmark(v->value, d)) { nbsoftkey++; } } } else if (!strcasecmp(v->name, "line")) { int len = strlen(linelabel); int create_line = 0; l = find_line_by_number(d, v->value); if (!l) { /* If line still not exists */ if (!(l = unistim_line_alloc())) { ast_free(d); ast_free(lt); return NULL; } lt->cap = l->cap; memcpy(l, lt, sizeof(*l)); ast_mutex_init(&l->lock); create_line = 1; } d->to_delete = 0; /* Set softkey info for new line*/ d->sline[nbsoftkey] = l; d->softkeyicon[nbsoftkey] = FAV_LINE_ICON; if (!len) { /* label is undefined ? */ ast_copy_string(d->softkeylabel[nbsoftkey], v->value, sizeof(d->softkeylabel[nbsoftkey])); } else { int softkeylinepos = 0; if ((len > 2) && (linelabel[1] == '@')) { softkeylinepos = linelabel[0]; if ((softkeylinepos >= '0') && (softkeylinepos <= '5')) { softkeylinepos -= '0'; d->softkeyicon[nbsoftkey] = FAV_ICON_NONE; } else { ast_log(LOG_WARNING, "Invalid position for linelabel : must be between 0 and 5\n"); } ast_copy_string(d->softkeylabel[softkeylinepos], linelabel + 2, sizeof(d->softkeylabel[softkeylinepos])); d->softkeyicon[softkeylinepos] = FAV_LINE_ICON; } else { ast_copy_string(d->softkeylabel[nbsoftkey], linelabel, sizeof(d->softkeylabel[nbsoftkey])); } } nbsoftkey++; if (create_line) { ast_copy_string(l->name, v->value, sizeof(l->name)); snprintf(l->fullname, sizeof(l->fullname), "USTM/%s@%s", l->name, d->name); if (!ast_strlen_zero(l->mailbox)) { if (unistimdebug) { ast_verb(3, "Setting mailbox '%s' on %s@%s\n", l->mailbox, d->name, l->name); } } ast_format_cap_append_from_cap(l->cap, global_cap, AST_MEDIA_TYPE_UNKNOWN); l->parent = d; linecnt++; AST_LIST_LOCK(&d->lines); AST_LIST_INSERT_TAIL(&d->lines, l, list); AST_LIST_UNLOCK(&d->lines); } } else if (!strcasecmp(v->name, "height")) { /* Allow the user to lower the expected display lines on the phone * For example the Nortel i2001 and i2002 only have one ! */ d->height = atoi(v->value); } else ast_log(LOG_WARNING, "Don't know keyword '%s' at line %d\n", v->name, v->lineno); v = v->next; } ast_free(lt); if (linecnt == 0) { ast_log(LOG_ERROR, "An Unistim device must have at least one line!\n"); ast_free(d); return NULL; } d->ringvolume = ringvolume; d->ringstyle = ringstyle; d->cwvolume = cwvolume; d->cwstyle = cwstyle; d->callhistory = callhistory; d->sharp_dial = sharpdial; d->tz = ast_get_indication_zone(d->country); if ((d->tz == NULL) && !ast_strlen_zero(d->country)) { ast_log(LOG_WARNING, "Country '%s' was not found in indications.conf\n", d->country); } d->datetimeformat = 48 + (dateformat * 4); d->datetimeformat += timeformat; if ((autoprovisioning == AUTOPROVISIONING_TN) && (!ast_strlen_zero(d->extension_number))) { d->extension = EXTENSION_TN; if (!ast_strlen_zero(d->id)) { ast_log(LOG_WARNING, "tn= and device= can't be used together. Ignoring device= entry\n"); } d->id[0] = 'T'; /* magic : this is a tn entry */ ast_copy_string((d->id) + 1, d->extension_number, sizeof(d->id) - 1); d->extension_number[0] = '\0'; } else if (ast_strlen_zero(d->id)) { if (strcmp(d->name, "template")) { ast_log(LOG_ERROR, "You must specify the mac address with device=\n"); if (d->tz) { d->tz = ast_tone_zone_unref(d->tz); } ast_free(d); return NULL; } else { strcpy(d->id, "000000000000"); } } if (!d->rtp_port) { d->rtp_port = 10000; } if (d->contrast == -1) { d->contrast = 8; } if (ast_strlen_zero(d->maintext1)) { strcpy(d->maintext1, d->name); } if (ast_strlen_zero(d->titledefault)) { struct ast_tm tm = { 0, }; struct timeval cur_time = ast_tvnow(); if ((ast_localtime(&cur_time, &tm, 0)) == 0 || ast_strlen_zero(tm.tm_zone)) { ast_log(LOG_WARNING, "Error in ast_localtime()\n"); ast_copy_string(d->titledefault, "UNISTIM for*", 12); } else { if (strlen(tm.tm_zone) < 4) { strcpy(d->titledefault, "TimeZone "); strcat(d->titledefault, tm.tm_zone); } else if (strlen(tm.tm_zone) < 9) { strcpy(d->titledefault, "TZ "); strcat(d->titledefault, tm.tm_zone); } else { ast_copy_string(d->titledefault, tm.tm_zone, 12); } } } /* Update the chained link if it's a new device */ if (create) { ast_mutex_lock(&devicelock); d->next = devices; devices = d; ast_mutex_unlock(&devicelock); ast_verb(3, "Added device '%s'\n", d->name); } else { ast_verb(3, "Device '%s' reloaded\n", d->name); } return d; } /*--- reload_config: Re-read unistim.conf config file ---*/ static int reload_config(void) { struct ast_config *cfg; struct ast_variable *v; struct ast_hostent ahp; struct hostent *hp; struct sockaddr_in bindaddr = { 0, }; char *config = "unistim.conf"; char *cat; struct unistim_device *d; const int reuseFlag = 1; struct unistimsession *s; struct ast_flags config_flags = { 0, }; cfg = ast_config_load(config, config_flags); /* We *must* have a config file otherwise stop immediately */ if (!cfg) { ast_log(LOG_ERROR, "Unable to load config %s\n", config); return -1; } else if (cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_ERROR, "Config file %s is in an invalid format. Aborting.\n", config); return -1; } /* Copy the default jb config over global_jbconf */ memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf)); unistim_keepalive = 120; unistim_port = 0; v = ast_variable_browse(cfg, "general"); while (v) { /* handle jb conf */ if (!ast_jb_read_conf(&global_jbconf, v->name, v->value)) { continue; } if (!strcasecmp(v->name, "keepalive")) { unistim_keepalive = atoi(v->value); } else if (!strcasecmp(v->name, "port")) { unistim_port = atoi(v->value); } else if (!strcasecmp(v->name, "tos")) { if (ast_str2tos(v->value, &qos.tos)) { ast_log(LOG_WARNING, "Invalid tos value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "tos_audio")) { if (ast_str2tos(v->value, &qos.tos_audio)) { ast_log(LOG_WARNING, "Invalid tos_audio value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "cos")) { if (ast_str2cos(v->value, &qos.cos)) { ast_log(LOG_WARNING, "Invalid cos value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "cos_audio")) { if (ast_str2cos(v->value, &qos.cos_audio)) { ast_log(LOG_WARNING, "Invalid cos_audio value at line %d, refer to QoS documentation\n", v->lineno); } } else if (!strcasecmp(v->name, "debug")) { if (!strcasecmp(v->value, "no")) { unistimdebug = 0; } else if (!strcasecmp(v->value, "yes")) { unistimdebug = 1; } } else if (!strcasecmp(v->name, "autoprovisioning")) { if (!strcasecmp(v->value, "no")) { autoprovisioning = AUTOPROVISIONING_NO; } else if (!strcasecmp(v->value, "yes")) { autoprovisioning = AUTOPROVISIONING_YES; } else if (!strcasecmp(v->value, "tn")) { autoprovisioning = AUTOPROVISIONING_TN; } else { ast_log(LOG_WARNING, "Unknown autoprovisioning option.\n"); } } else if (!strcasecmp(v->name, "public_ip")) { if (!ast_strlen_zero(v->value)) { if (!(hp = ast_gethostbyname(v->value, &ahp))) { ast_log(LOG_WARNING, "Invalid address: %s\n", v->value); } else { memcpy(&public_ip.sin_addr, hp->h_addr, sizeof(public_ip.sin_addr)); public_ip.sin_family = AF_INET; } } } v = v->next; } if ((unistim_keepalive < 10) || (unistim_keepalive > 255 - (((NB_MAX_RETRANSMIT + 1) * RETRANSMIT_TIMER) / 1000))) { ast_log(LOG_ERROR, "keepalive is invalid in %s\n", config); ast_config_destroy(cfg); return -1; } packet_send_ping[4] = unistim_keepalive + (((NB_MAX_RETRANSMIT + 1) * RETRANSMIT_TIMER) / 1000); if ((unistim_port < 1) || (unistim_port > 65535)) { ast_log(LOG_ERROR, "port is not set or invalid in %s\n", config); ast_config_destroy(cfg); return -1; } unistim_keepalive *= 1000; ast_mutex_lock(&devicelock); d = devices; while (d) { if (d->to_delete >= 0) { d->to_delete = 1; } d = d->next; } ast_mutex_unlock(&devicelock); /* load the device sections */ cat = ast_category_browse(cfg, NULL); while (cat) { if (strcasecmp(cat, "general")) { d = build_device(cat, ast_variable_browse(cfg, cat)); } cat = ast_category_browse(cfg, cat); } ast_mutex_lock(&devicelock); d = devices; while (d) { if (d->to_delete) { struct unistim_line *l; struct unistim_subchannel *sub; if (unistimdebug) { ast_verb(0, "Removing device '%s'\n", d->name); } AST_LIST_LOCK(&d->subs); AST_LIST_TRAVERSE_SAFE_BEGIN(&d->subs, sub, list){ if (sub->subtype == SUB_REAL) { if (!sub) { ast_log(LOG_ERROR, "Device '%s' without a subchannel !, aborting\n", d->name); ast_config_destroy(cfg); return 0; } if (sub->owner) { ast_log(LOG_WARNING, "Device '%s' was not deleted : a call is in progress. Try again later.\n", d->name); d = d->next; continue; } } if (sub->subtype == SUB_THREEWAY) { ast_log(LOG_WARNING, "Device '%s' with threeway call subchannels allocated, aborting.\n", d->name); break; } AST_LIST_REMOVE_CURRENT(list); ast_mutex_destroy(&sub->lock); ast_free(sub); } AST_LIST_TRAVERSE_SAFE_END AST_LIST_UNLOCK(&d->subs); AST_LIST_LOCK(&d->lines); AST_LIST_TRAVERSE_SAFE_BEGIN(&d->lines, l, list){ AST_LIST_REMOVE_CURRENT(list); ast_mutex_destroy(&l->lock); unistim_line_destroy(l); } AST_LIST_TRAVERSE_SAFE_END AST_LIST_UNLOCK(&d->lines); if (d->session) { if (sessions == d->session) { sessions = d->session->next; } else { s = sessions; while (s) { if (s->next == d->session) { s->next = d->session->next; break; } s = s->next; } } ast_mutex_destroy(&d->session->lock); ast_free(d->session); } if (devices == d) { devices = d->next; } else { struct unistim_device *d2 = devices; while (d2) { if (d2->next == d) { d2->next = d->next; break; } d2 = d2->next; } } if (d->tz) { d->tz = ast_tone_zone_unref(d->tz); } ast_mutex_destroy(&d->lock); ast_free(d); d = devices; continue; } d = d->next; } finish_bookmark(); ast_mutex_unlock(&devicelock); ast_config_destroy(cfg); ast_mutex_lock(&sessionlock); s = sessions; while (s) { if (s->device) { refresh_all_favorite(s); if (ast_strlen_zero(s->device->language)) { struct unistim_languages lang; lang = options_languages[find_language(s->device->language)]; send_charset_update(s, lang.encoding); } } s = s->next; } ast_mutex_unlock(&sessionlock); /* We don't recreate a socket when reloading (locks would be necessary). */ if (unistimsock > -1) { return 0; } bindaddr.sin_addr.s_addr = INADDR_ANY; bindaddr.sin_port = htons(unistim_port); bindaddr.sin_family = AF_INET; unistimsock = socket(AF_INET, SOCK_DGRAM, 0); if (unistimsock < 0) { ast_log(LOG_WARNING, "Unable to create UNISTIM socket: %s\n", strerror(errno)); return -1; } #ifdef HAVE_PKTINFO { const int pktinfoFlag = 1; setsockopt(unistimsock, IPPROTO_IP, IP_PKTINFO, &pktinfoFlag, sizeof(pktinfoFlag)); } #else if (public_ip.sin_family == 0) { ast_log(LOG_WARNING, "Your OS does not support IP_PKTINFO, you must set public_ip.\n"); unistimsock = -1; return -1; } #endif setsockopt(unistimsock, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuseFlag, sizeof(reuseFlag)); if (bind(unistimsock, (struct sockaddr *) &bindaddr, sizeof(bindaddr)) < 0) { ast_log(LOG_WARNING, "Failed to bind to %s:%d: %s\n", ast_inet_ntoa(bindaddr.sin_addr), htons(bindaddr.sin_port), strerror(errno)); close(unistimsock); unistimsock = -1; } else { ast_verb(2, "UNISTIM Listening on %s:%d\n", ast_inet_ntoa(bindaddr.sin_addr), htons(bindaddr.sin_port)); ast_set_qos(unistimsock, qos.tos, qos.cos, "UNISTIM"); } return 0; } static enum ast_rtp_glue_result unistim_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance) { struct unistim_subchannel *sub = ast_channel_tech_pvt(chan); if (!sub) { return AST_RTP_GLUE_RESULT_FORBID; } if (!sub->rtp) { return AST_RTP_GLUE_RESULT_FORBID; } ao2_ref(sub->rtp, +1); *instance = sub->rtp; return AST_RTP_GLUE_RESULT_LOCAL; } static int unistim_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp, struct ast_rtp_instance *trtp, const struct ast_format_cap *codecs, int nat_active) { struct unistim_subchannel *sub = ast_channel_tech_pvt(chan); struct sockaddr_in them = { 0, }; struct sockaddr_in us = { 0, }; if (!rtp) { return 0; } sub = (struct unistim_subchannel *) ast_channel_tech_pvt(chan); if (!sub) { ast_log(LOG_ERROR, "No Private Structure, this is bad\n"); return -1; } { struct ast_sockaddr tmp; ast_rtp_instance_get_remote_address(rtp, &tmp); ast_sockaddr_to_sin(&tmp, &them); ast_rtp_instance_get_local_address(rtp, &tmp); ast_sockaddr_to_sin(&tmp, &us); } /* TODO: Set rtp on phone in case of direct rtp (not implemented) */ return 0; } static struct ast_rtp_glue unistim_rtp_glue = { .type = channel_type, .get_rtp_info = unistim_get_rtp_peer, .update_peer = unistim_set_rtp_peer, }; /*--- load_module: PBX load module - initialization ---*/ int load_module(void) { int res; if (!(global_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { goto buff_failed; } if (!(unistim_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { goto buff_failed; } ast_format_cap_append(global_cap, ast_format_ulaw, 0); ast_format_cap_append(global_cap, ast_format_alaw, 0); ast_format_cap_append_from_cap(unistim_tech.capabilities, global_cap, AST_MEDIA_TYPE_AUDIO); if (!(buff = ast_malloc(SIZE_PAGE))) { goto buff_failed; } io = io_context_create(); if (!io) { ast_log(LOG_ERROR, "Failed to allocate IO context\n"); goto io_failed; } sched = ast_sched_context_create(); if (!sched) { ast_log(LOG_ERROR, "Failed to allocate scheduler context\n"); goto sched_failed; } res = reload_config(); if (res) { ao2_ref(unistim_tech.capabilities, -1); ao2_ref(global_cap, -1); ast_sched_context_destroy(sched); io_context_destroy(io); return AST_MODULE_LOAD_DECLINE; } /* Make sure we can register our unistim channel type */ if (ast_channel_register(&unistim_tech)) { ast_log(LOG_ERROR, "Unable to register channel type '%s'\n", channel_type); goto chanreg_failed; } ast_rtp_glue_register(&unistim_rtp_glue); ast_cli_register_multiple(unistim_cli, ARRAY_LEN(unistim_cli)); restart_monitor(); return AST_MODULE_LOAD_SUCCESS; chanreg_failed: /*! XXX \todo Leaking anything allocated by reload_config() ... */ ast_sched_context_destroy(sched); sched = NULL; sched_failed: io_context_destroy(io); io = NULL; io_failed: ast_free(buff); buff = NULL; buff_failed: ao2_cleanup(global_cap); global_cap = NULL; ao2_cleanup(unistim_tech.capabilities); unistim_tech.capabilities = NULL; return AST_MODULE_LOAD_FAILURE; } static int unload_module(void) { /* First, take us out of the channel loop */ if (sched) { ast_sched_context_destroy(sched); } ast_cli_unregister_multiple(unistim_cli, ARRAY_LEN(unistim_cli)); ast_channel_unregister(&unistim_tech); ao2_cleanup(unistim_tech.capabilities); ast_rtp_glue_unregister(&unistim_rtp_glue); ast_mutex_lock(&monlock); if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { pthread_cancel(monitor_thread); pthread_kill(monitor_thread, SIGURG); pthread_join(monitor_thread, NULL); } monitor_thread = AST_PTHREADT_STOP; ast_mutex_unlock(&monlock); if (buff) { ast_free(buff); } if (unistimsock > -1) { close(unistimsock); } ao2_ref(global_cap, -1); return 0; } /*! reload: Part of Asterisk module interface ---*/ int reload(void) { if (unistimdebug) { ast_verb(0, "reload unistim\n"); } ast_mutex_lock(&unistim_reload_lock); if (!unistim_reloading) { unistim_reloading = 1; } ast_mutex_unlock(&unistim_reload_lock); restart_monitor(); return 0; } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "UNISTIM Protocol (USTM)", .support_level = AST_MODULE_SUPPORT_EXTENDED, .load = load_module, .unload = unload_module, .reload = reload, ); asterisk-13.1.0/channels/chan_iax2.c0000644000000000000000000170525012437125770015734 0ustar rootroot/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2006, 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. */ /*! \file * * \brief Implementation of Inter-Asterisk eXchange Version 2 * as specified in RFC 5456 * * \author Mark Spencer * * \par See also * \arg \ref Config_iax * * \ingroup channel_drivers * * \todo Implement musicclass settings for IAX2 devices */ /*! \li \ref chan_iax2.c uses the configuration file \ref iax.conf * \addtogroup configuration_file */ /*! \page iax.conf iax.conf * \verbinclude iax.conf.sample */ /*! * \todo XXX The IAX2 channel driver needs its native bridge * code converted to the new bridge technology scheme. * * \note The chan_dahdi native bridge code can be used as an * example. It also appears that chan_iax2 also has a native * transfer check like chan_dahdi to eliminate tromboned calls. * * \note The existing native bridge code is marked with the * IAX2_NATIVE_BRIDGING conditional. */ /*** MODULEINFO crypto core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 428687 $") #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asterisk/paths.h" #include "asterisk/lock.h" #include "asterisk/frame.h" #include "asterisk/channel.h" #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/sched.h" #include "asterisk/io.h" #include "asterisk/config.h" #include "asterisk/cli.h" #include "asterisk/translate.h" #include "asterisk/md5.h" #include "asterisk/crypto.h" #include "asterisk/acl.h" #include "asterisk/manager.h" #include "asterisk/callerid.h" #include "asterisk/app.h" #include "asterisk/astdb.h" #include "asterisk/musiconhold.h" #include "asterisk/features.h" #include "asterisk/utils.h" #include "asterisk/causes.h" #include "asterisk/localtime.h" #include "asterisk/dnsmgr.h" #include "asterisk/devicestate.h" #include "asterisk/netsock.h" #include "asterisk/stringfields.h" #include "asterisk/linkedlists.h" #include "asterisk/astobj2.h" #include "asterisk/timing.h" #include "asterisk/taskprocessor.h" #include "asterisk/test.h" #include "asterisk/data.h" #include "asterisk/security_events.h" #include "asterisk/stasis_endpoints.h" #include "asterisk/bridge.h" #include "asterisk/stasis.h" #include "asterisk/stasis_system.h" #include "asterisk/stasis_channels.h" #include "asterisk/format_cache.h" #include "asterisk/format_compatibility.h" #include "asterisk/format_cap.h" #include "iax2/include/iax2.h" #include "iax2/include/firmware.h" #include "iax2/include/parser.h" #include "iax2/include/provision.h" #include "iax2/include/codec_pref.h" #include "iax2/include/format_compatibility.h" #include "jitterbuf.h" /*** DOCUMENTATION Provision a calling IAXy with a given template. If not specified, defaults to default. Provisions the calling IAXy (assuming the calling entity is in fact an IAXy) with the given template. Returns -1 on error or 0 on success. Gets IAX peer information. If peername is specified to this value, return the IP address of the endpoint of the current channel If peername is specified, valid items are: (default) The IP address. The peer's status (if qualify=yes) 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 codecs. Preferred codec index number x (beginning with 0) Gets information associated with the specified IAX2 peer. SIPPEER Sets or retrieves a remote variable. Gets or sets a variable that is sent to a remote IAX2 peer during call setup. List IAX peers. List IAX Peers. List all the IAX peers. Show IAX Netstats. Show IAX channels network statistics. Show IAX registrations. Show IAX registrations. ***/ /* Define SCHED_MULTITHREADED to run the scheduler in a special multithreaded mode. */ #define SCHED_MULTITHREADED /* Define DEBUG_SCHED_MULTITHREADED to keep track of where each thread is actually doing. */ #define DEBUG_SCHED_MULTITHREAD #ifdef SO_NO_CHECK static int nochecksums = 0; #endif #define PTR_TO_CALLNO(a) ((unsigned short)(unsigned long)(a)) #define CALLNO_TO_PTR(a) ((void *)(unsigned long)(a)) #define DEFAULT_THREAD_COUNT 10 #define DEFAULT_MAX_THREAD_COUNT 100 #define DEFAULT_RETRY_TIME 1000 #define MEMORY_SIZE 100 #define DEFAULT_DROP 3 #define DEBUG_SUPPORT #define MIN_REUSE_TIME 60 /* Don't reuse a call number within 60 seconds */ /* Sample over last 100 units to determine historic jitter */ #define GAMMA (0.01) static struct iax2_codec_pref prefs_global; static const char tdesc[] = "Inter Asterisk eXchange Driver (Ver 2)"; /*! \brief Maximum transmission unit for the UDP packet in the trunk not to be fragmented. This is based on 1516 - ethernet - ip - udp - iax minus one g711 frame = 1240 */ #define MAX_TRUNK_MTU 1240 static int global_max_trunk_mtu; /*!< Maximum MTU, 0 if not used */ static int trunk_timed, trunk_untimed, trunk_maxmtu, trunk_nmaxmtu ; /*!< Trunk MTU statistics */ #define DEFAULT_CONTEXT "default" static char default_parkinglot[AST_MAX_CONTEXT]; static char language[MAX_LANGUAGE] = ""; static char regcontext[AST_MAX_CONTEXT] = ""; static struct stasis_subscription *network_change_sub; /*!< subscription id for network change events */ static struct stasis_subscription *acl_change_sub; /*!< subscription id for ACL change events */ static int network_change_sched_id = -1; static int maxauthreq = 3; static int max_retries = 4; static int ping_time = 21; static int lagrq_time = 10; static int maxjitterbuffer=1000; static int resyncthreshold=1000; static int maxjitterinterps=10; static int jittertargetextra = 40; /* number of milliseconds the new jitter buffer adds on to its size */ #define MAX_TRUNKDATA 640 * 200 /*!< 40ms, uncompressed linear * 200 channels */ static int trunkfreq = 20; static int trunkmaxsize = MAX_TRUNKDATA; static int authdebug = 0; static int autokill = 0; static int iaxcompat = 0; static int last_authmethod = 0; static int iaxdefaultdpcache=10 * 60; /* Cache dialplan entries for 10 minutes by default */ static int iaxdefaulttimeout = 5; /* Default to wait no more than 5 seconds for a reply to come back */ static struct { unsigned int tos; unsigned int cos; } qos = { 0, 0 }; static int min_reg_expire; static int max_reg_expire; static int srvlookup = 0; static struct ast_timer *timer; /* Timer for trunking */ static struct ast_netsock_list *netsock; static struct ast_netsock_list *outsock; /*!< used if sourceaddress specified and bindaddr == INADDR_ANY */ static int defaultsockfd = -1; static int (*iax2_regfunk)(const char *username, int onoff) = NULL; /* Ethernet, etc */ #define IAX_CAPABILITY_FULLBANDWIDTH 0xFFFF /* T1, maybe ISDN */ #define IAX_CAPABILITY_MEDBANDWIDTH (IAX_CAPABILITY_FULLBANDWIDTH & \ ~AST_FORMAT_SLIN & \ ~AST_FORMAT_SLIN16 & \ ~AST_FORMAT_SIREN7 & \ ~AST_FORMAT_SIREN14 & \ ~AST_FORMAT_G719 & \ ~AST_FORMAT_ULAW & \ ~AST_FORMAT_ALAW & \ ~AST_FORMAT_G722) /* A modem */ #define IAX_CAPABILITY_LOWBANDWIDTH (IAX_CAPABILITY_MEDBANDWIDTH & \ ~AST_FORMAT_G726 & \ ~AST_FORMAT_G726_AAL2 & \ ~AST_FORMAT_ADPCM) #define IAX_CAPABILITY_LOWFREE (IAX_CAPABILITY_LOWBANDWIDTH & \ ~AST_FORMAT_G723) #define DEFAULT_MAXMS 2000 /* Must be faster than 2 seconds by default */ #define DEFAULT_FREQ_OK 60 * 1000 /* How often to check for the host to be up */ #define DEFAULT_FREQ_NOTOK 10 * 1000 /* How often to check, if the host is down... */ /* if a pvt has encryption setup done and is running on the call */ #define IAX_CALLENCRYPTED(pvt) \ (ast_test_flag64(pvt, IAX_ENCRYPTED) && ast_test_flag64(pvt, IAX_KEYPOPULATED)) #define IAX_DEBUGDIGEST(msg, key) do { \ int idx; \ char digest[33] = ""; \ \ if (!iaxdebug) \ break; \ \ for (idx = 0; idx < 16; idx++) \ sprintf(digest + (idx << 1), "%2.2x", (unsigned) key[idx]); \ \ ast_log(LOG_NOTICE, msg " IAX_COMMAND_RTKEY to rotate key to '%s'\n", digest); \ } while(0) static struct io_context *io; static struct ast_sched_context *sched; #define DONT_RESCHEDULE -2 static iax2_format iax2_capability = IAX_CAPABILITY_FULLBANDWIDTH; static int iaxdebug = 0; static int iaxtrunkdebug = 0; static int test_losspct = 0; #ifdef IAXTESTS static int test_late = 0; static int test_resync = 0; static int test_jit = 0; static int test_jitpct = 0; #endif /* IAXTESTS */ static char accountcode[AST_MAX_ACCOUNT_CODE]; static char mohinterpret[MAX_MUSICCLASS]; static char mohsuggest[MAX_MUSICCLASS]; static int amaflags = 0; static int adsi = 0; static int delayreject = 0; static int iax2_encryption = 0; static struct ast_flags64 globalflags = { 0 }; static pthread_t netthreadid = AST_PTHREADT_NULL; enum iax2_state { IAX_STATE_STARTED = (1 << 0), IAX_STATE_AUTHENTICATED = (1 << 1), IAX_STATE_TBD = (1 << 2), }; struct iax2_context { char context[AST_MAX_CONTEXT]; struct iax2_context *next; }; #define IAX_HASCALLERID (uint64_t)(1 << 0) /*!< CallerID has been specified */ #define IAX_DELME (uint64_t)(1 << 1) /*!< Needs to be deleted */ #define IAX_TEMPONLY (uint64_t)(1 << 2) /*!< Temporary (realtime) */ #define IAX_TRUNK (uint64_t)(1 << 3) /*!< Treat as a trunk */ #define IAX_NOTRANSFER (uint64_t)(1 << 4) /*!< Don't native bridge */ #define IAX_USEJITTERBUF (uint64_t)(1 << 5) /*!< Use jitter buffer */ #define IAX_DYNAMIC (uint64_t)(1 << 6) /*!< dynamic peer */ #define IAX_SENDANI (uint64_t)(1 << 7) /*!< Send ANI along with CallerID */ #define IAX_RTSAVE_SYSNAME (uint64_t)(1 << 8) /*!< Save Systname on Realtime Updates */ #define IAX_ALREADYGONE (uint64_t)(1 << 9) /*!< Already disconnected */ #define IAX_PROVISION (uint64_t)(1 << 10) /*!< This is a provisioning request */ #define IAX_QUELCH (uint64_t)(1 << 11) /*!< Whether or not we quelch audio */ #define IAX_ENCRYPTED (uint64_t)(1 << 12) /*!< Whether we should assume encrypted tx/rx */ #define IAX_KEYPOPULATED (uint64_t)(1 << 13) /*!< Whether we have a key populated */ #define IAX_CODEC_USER_FIRST (uint64_t)(1 << 14) /*!< are we willing to let the other guy choose the codec? */ #define IAX_CODEC_NOPREFS (uint64_t)(1 << 15) /*!< Force old behaviour by turning off prefs */ #define IAX_CODEC_NOCAP (uint64_t)(1 << 16) /*!< only consider requested format and ignore capabilities*/ #define IAX_RTCACHEFRIENDS (uint64_t)(1 << 17) /*!< let realtime stay till your reload */ #define IAX_RTUPDATE (uint64_t)(1 << 18) /*!< Send a realtime update */ #define IAX_RTAUTOCLEAR (uint64_t)(1 << 19) /*!< erase me on expire */ #define IAX_FORCEJITTERBUF (uint64_t)(1 << 20) /*!< Force jitterbuffer, even when bridged to a channel that can take jitter */ #define IAX_RTIGNOREREGEXPIRE (uint64_t)(1 << 21) /*!< When using realtime, ignore registration expiration */ #define IAX_TRUNKTIMESTAMPS (uint64_t)(1 << 22) /*!< Send trunk timestamps */ #define IAX_TRANSFERMEDIA (uint64_t)(1 << 23) /*!< When doing IAX2 transfers, transfer media only */ #define IAX_MAXAUTHREQ (uint64_t)(1 << 24) /*!< Maximum outstanding AUTHREQ restriction is in place */ #define IAX_DELAYPBXSTART (uint64_t)(1 << 25) /*!< Don't start a PBX on the channel until the peer sends us a response, so that we've achieved a three-way handshake with them before sending voice or anything else */ #define IAX_ALLOWFWDOWNLOAD (uint64_t)(1 << 26) /*!< Allow the FWDOWNL command? */ #define IAX_IMMEDIATE (uint64_t)(1 << 27) /*!< Allow immediate off-hook to extension s */ #define IAX_SENDCONNECTEDLINE (uint64_t)(1 << 28) /*!< Allow sending of connected line updates */ #define IAX_RECVCONNECTEDLINE (uint64_t)(1 << 29) /*!< Allow receiving of connected line updates */ #define IAX_FORCE_ENCRYPT (uint64_t)(1 << 30) /*!< Forces call encryption, if encryption not possible hangup */ #define IAX_SHRINKCALLERID (uint64_t)(1 << 31) /*!< Turn on and off caller id shrinking */ static int global_rtautoclear = 120; static int reload_config(int forced_reload); /*! * \brief Call token validation settings. */ enum calltoken_peer_enum { /*! \brief Default calltoken required unless the ip is in the ignorelist */ CALLTOKEN_DEFAULT = 0, /*! \brief Require call token validation. */ CALLTOKEN_YES = 1, /*! \brief Require call token validation after a successful registration * using call token validation occurs. */ CALLTOKEN_AUTO = 2, /*! \brief Do not require call token validation. */ CALLTOKEN_NO = 3, }; struct iax2_user { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(name); AST_STRING_FIELD(secret); AST_STRING_FIELD(dbsecret); AST_STRING_FIELD(accountcode); AST_STRING_FIELD(mohinterpret); AST_STRING_FIELD(mohsuggest); AST_STRING_FIELD(inkeys); /*!< Key(s) this user can use to authenticate to us */ AST_STRING_FIELD(language); AST_STRING_FIELD(cid_num); AST_STRING_FIELD(cid_name); AST_STRING_FIELD(parkinglot); /*!< Default parkinglot for device */ ); int authmethods; int encmethods; int amaflags; int adsi; uint64_t flags; iax2_format capability; int maxauthreq; /*!< Maximum allowed outstanding AUTHREQs */ int curauthreq; /*!< Current number of outstanding AUTHREQs */ struct iax2_codec_pref prefs; struct ast_acl_list *acl; struct iax2_context *contexts; struct ast_variable *vars; enum calltoken_peer_enum calltoken_required; /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */ }; struct iax2_peer { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(name); AST_STRING_FIELD(username); AST_STRING_FIELD(description); /*!< Description of the peer */ AST_STRING_FIELD(secret); AST_STRING_FIELD(dbsecret); AST_STRING_FIELD(outkey); /*!< What key we use to talk to this peer */ AST_STRING_FIELD(regexten); /*!< Extension to register (if regcontext is used) */ AST_STRING_FIELD(context); /*!< For transfers only */ AST_STRING_FIELD(peercontext); /*!< Context to pass to peer */ AST_STRING_FIELD(mailbox); /*!< Mailbox */ AST_STRING_FIELD(mohinterpret); AST_STRING_FIELD(mohsuggest); AST_STRING_FIELD(inkeys); /*!< Key(s) this peer can use to authenticate to us */ /* Suggested caller id if registering */ AST_STRING_FIELD(cid_num); /*!< Default context (for transfer really) */ AST_STRING_FIELD(cid_name); /*!< Default context (for transfer really) */ AST_STRING_FIELD(zonetag); /*!< Time Zone */ AST_STRING_FIELD(parkinglot); /*!< Default parkinglot for device */ ); struct iax2_codec_pref prefs; struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager */ struct ast_sockaddr addr; int formats; int sockfd; /*!< Socket to use for transmission */ struct ast_sockaddr mask; int adsi; uint64_t flags; /* Dynamic Registration fields */ struct ast_sockaddr defaddr; /*!< Default address if there is one */ int authmethods; /*!< Authentication methods (IAX_AUTH_*) */ int encmethods; /*!< Encryption methods (IAX_ENCRYPT_*) */ int expire; /*!< Schedule entry for expiry */ int expiry; /*!< How soon to expire */ iax2_format capability; /*!< Capability */ /* Qualification */ int callno; /*!< Call number of POKE request */ int pokeexpire; /*!< Scheduled qualification-related task (ie iax2_poke_peer_s or iax2_poke_noanswer) */ int lastms; /*!< How long last response took (in ms), or -1 for no response */ int maxms; /*!< Max ms we will accept for the host to be up, 0 to not monitor */ int pokefreqok; /*!< How often to check if the host is up */ int pokefreqnotok; /*!< How often to check when the host has been determined to be down */ int historicms; /*!< How long recent average responses took */ int smoothing; /*!< Sample over how many units to determine historic ms */ uint16_t maxcallno; /*!< Max call number limit for this peer. Set on registration */ struct stasis_subscription *mwi_event_sub; /*!< This subscription lets pollmailboxes know which mailboxes need to be polled */ struct ast_acl_list *acl; enum calltoken_peer_enum calltoken_required; /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */ struct ast_endpoint *endpoint; /*!< Endpoint structure for this peer */ }; #define IAX2_TRUNK_PREFACE (sizeof(struct iax_frame) + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr)) struct iax2_trunk_peer { ast_mutex_t lock; int sockfd; struct ast_sockaddr addr; struct timeval txtrunktime; /*!< Transmit trunktime */ struct timeval rxtrunktime; /*!< Receive trunktime */ struct timeval lasttxtime; /*!< Last transmitted trunktime */ struct timeval trunkact; /*!< Last trunk activity */ unsigned int lastsent; /*!< Last sent time */ /* Trunk data and length */ unsigned char *trunkdata; unsigned int trunkdatalen; unsigned int trunkdataalloc; int trunkmaxmtu; int trunkerror; int calls; AST_LIST_ENTRY(iax2_trunk_peer) list; }; static AST_LIST_HEAD_STATIC(tpeers, iax2_trunk_peer); enum iax_reg_state { REG_STATE_UNREGISTERED = 0, REG_STATE_REGSENT, REG_STATE_AUTHSENT, REG_STATE_REGISTERED, REG_STATE_REJECTED, REG_STATE_TIMEOUT, REG_STATE_NOAUTH }; enum iax_transfer_state { TRANSFER_NONE = 0, TRANSFER_BEGIN, TRANSFER_READY, TRANSFER_RELEASED, TRANSFER_PASSTHROUGH, TRANSFER_MBEGIN, TRANSFER_MREADY, TRANSFER_MRELEASED, TRANSFER_MPASSTHROUGH, TRANSFER_MEDIA, TRANSFER_MEDIAPASS }; struct iax2_registry { struct ast_sockaddr addr; /*!< Who we connect to for registration purposes */ char username[80]; char secret[80]; /*!< Password or key name in []'s */ int expire; /*!< Sched ID of expiration */ int refresh; /*!< How often to refresh */ enum iax_reg_state regstate; int messages; /*!< Message count, low 8 bits = new, high 8 bits = old */ int callno; /*!< Associated call number if applicable */ struct ast_sockaddr us; /*!< Who the server thinks we are */ struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager */ AST_LIST_ENTRY(iax2_registry) entry; int port; char hostname[]; }; static AST_LIST_HEAD_STATIC(registrations, iax2_registry); /* Don't retry more frequently than every 10 ms, or less frequently than every 5 seconds */ #define MIN_RETRY_TIME 100 #define MAX_RETRY_TIME 10000 #define MAX_JITTER_BUFFER 50 #define MIN_JITTER_BUFFER 10 #define DEFAULT_TRUNKDATA 640 * 10 /*!< 40ms, uncompressed linear * 10 channels */ #define MAX_TIMESTAMP_SKEW 160 /*!< maximum difference between actual and predicted ts for sending */ /* If consecutive voice frame timestamps jump by more than this many milliseconds, then jitter buffer will resync */ #define TS_GAP_FOR_JB_RESYNC 5000 /* used for first_iax_message and last_iax_message. If this bit is set it was TX, else RX */ #define MARK_IAX_SUBCLASS_TX 0x8000 static int iaxthreadcount = DEFAULT_THREAD_COUNT; static int iaxmaxthreadcount = DEFAULT_MAX_THREAD_COUNT; static int iaxdynamicthreadcount = 0; static int iaxdynamicthreadnum = 0; static int iaxactivethreadcount = 0; struct iax_rr { int jitter; int losspct; int losscnt; int packets; int delay; int dropped; int ooo; }; struct iax2_pvt_ref; /* We use the high order bit as the validated flag, and the lower 15 as the * actual call number */ typedef uint16_t callno_entry; struct chan_iax2_pvt { /*! Socket to send/receive on for this call */ int sockfd; /*! ast_callid bound to dialog */ struct ast_callid *callid; /*! Last received voice format */ iax2_format voiceformat; /*! Last received video format */ iax2_format videoformat; /*! Last sent voice format */ iax2_format svoiceformat; /*! Last sent video format */ iax2_format svideoformat; /*! What we are capable of sending */ iax2_format capability; /*! Last received timestamp */ unsigned int last; /*! Last sent timestamp - never send the same timestamp twice in a single call */ unsigned int lastsent; /*! Timestamp of the last video frame sent */ unsigned int lastvsent; /*! Next outgoing timestamp if everything is good */ unsigned int nextpred; /*! iax frame subclass that began iax2_pvt entry. 0x8000 bit is set on TX */ int first_iax_message; /*! Last iax frame subclass sent or received for a iax2_pvt. 0x8000 bit is set on TX */ int last_iax_message; /*! True if the last voice we transmitted was not silence/CNG */ unsigned int notsilenttx:1; /*! Ping time */ unsigned int pingtime; /*! Max time for initial response */ int maxtime; /*! Peer Address */ struct ast_sockaddr addr; /*! Actual used codec preferences */ struct iax2_codec_pref prefs; /*! Requested codec preferences */ struct iax2_codec_pref rprefs; /*! Our call number */ unsigned short callno; /*! Our callno_entry entry */ callno_entry callno_entry; /*! Peer callno */ unsigned short peercallno; /*! Negotiated format, this is only used to remember what format was chosen for an unauthenticated call so that the channel can get created later using the right format */ iax2_format chosenformat; /*! Peer selected format */ iax2_format peerformat; /*! Peer capability */ iax2_format peercapability; /*! timeval that we base our transmission on */ struct timeval offset; /*! timeval that we base our delivery on */ struct timeval rxcore; /*! The jitterbuffer */ jitterbuf *jb; /*! active jb read scheduler id */ int jbid; /*! LAG */ int lag; /*! Error, as discovered by the manager */ int error; /*! Owner if we have one */ struct ast_channel *owner; /*! What's our state? */ struct ast_flags state; /*! Expiry (optional) */ int expiry; /*! Next outgoing sequence number */ unsigned char oseqno; /*! Next sequence number they have not yet acknowledged */ unsigned char rseqno; /*! Next incoming sequence number */ unsigned char iseqno; /*! Last incoming sequence number we have acknowledged */ unsigned char aseqno; AST_DECLARE_STRING_FIELDS( /*! Peer name */ AST_STRING_FIELD(peer); /*! Default Context */ AST_STRING_FIELD(context); /*! Caller ID if available */ AST_STRING_FIELD(cid_num); AST_STRING_FIELD(cid_name); /*! Hidden Caller ID (i.e. ANI) if appropriate */ AST_STRING_FIELD(ani); /*! DNID */ AST_STRING_FIELD(dnid); /*! RDNIS */ AST_STRING_FIELD(rdnis); /*! Requested Extension */ AST_STRING_FIELD(exten); /*! Expected Username */ AST_STRING_FIELD(username); /*! Expected Secret */ AST_STRING_FIELD(secret); /*! MD5 challenge */ AST_STRING_FIELD(challenge); /*! Public keys permitted keys for incoming authentication */ AST_STRING_FIELD(inkeys); /*! Private key for outgoing authentication */ AST_STRING_FIELD(outkey); /*! Preferred language */ AST_STRING_FIELD(language); /*! Hostname/peername for naming purposes */ AST_STRING_FIELD(host); AST_STRING_FIELD(dproot); AST_STRING_FIELD(accountcode); AST_STRING_FIELD(mohinterpret); AST_STRING_FIELD(mohsuggest); /*! received OSP token */ AST_STRING_FIELD(osptoken); /*! Default parkinglot */ AST_STRING_FIELD(parkinglot); ); /*! AUTHREJ all AUTHREP frames */ int authrej; /*! permitted authentication methods */ int authmethods; /*! permitted encryption methods */ int encmethods; /*! Encryption AES-128 Key */ ast_aes_encrypt_key ecx; /*! Decryption AES-128 Key corresponding to ecx */ ast_aes_decrypt_key mydcx; /*! Decryption AES-128 Key used to decrypt peer frames */ ast_aes_decrypt_key dcx; /*! scheduler id associated with iax_key_rotate * for encrypted calls*/ int keyrotateid; /*! 32 bytes of semi-random data */ unsigned char semirand[32]; /*! Associated registry */ struct iax2_registry *reg; /*! Associated peer for poking */ struct iax2_peer *peerpoke; /*! IAX_ flags */ uint64_t flags; int adsi; /*! Transferring status */ enum iax_transfer_state transferring; /*! Transfer identifier */ int transferid; /*! Who we are IAX transferring to */ struct ast_sockaddr transfer; /*! What's the new call number for the transfer */ unsigned short transfercallno; /*! Transfer encrypt AES-128 Key */ ast_aes_encrypt_key tdcx; /*! Status of knowledge of peer ADSI capability */ int peeradsicpe; /*! Callno of native bridge peer. (Valid if nonzero) */ unsigned short bridgecallno; int pingid; /*!< Transmit PING request */ int lagid; /*!< Retransmit lag request */ int autoid; /*!< Auto hangup for Dialplan requestor */ int authid; /*!< Authentication rejection ID */ int authfail; /*!< Reason to report failure */ int initid; /*!< Initial peer auto-congest ID (based on qualified peers) */ int calling_ton; int calling_tns; int calling_pres; int amaflags; AST_LIST_HEAD_NOLOCK(, iax2_dpcache) dpentries; /*! variables inherited from the user definition */ struct ast_variable *vars; /*! variables transmitted in a NEW packet */ struct ast_variable *iaxvars; /*! last received remote rr */ struct iax_rr remote_rr; /*! Current base time: (just for stats) */ int min; /*! Dropped frame count: (just for stats) */ int frames_dropped; /*! received frame count: (just for stats) */ int frames_received; /*! num bytes used for calltoken ie, even an empty ie should contain 2 */ unsigned char calltoken_ie_len; /*! hold all signaling frames from the pbx thread until we have a destination callno */ char hold_signaling; /*! frame queue for signaling frames from pbx thread waiting for destination callno */ AST_LIST_HEAD_NOLOCK(signaling_queue, signaling_queue_entry) signaling_queue; }; struct signaling_queue_entry { struct ast_frame f; AST_LIST_ENTRY(signaling_queue_entry) next; }; enum callno_type { CALLNO_TYPE_NORMAL, CALLNO_TYPE_TRUNK, }; #define PTR_TO_CALLNO_ENTRY(a) ((uint16_t)(unsigned long)(a)) #define CALLNO_ENTRY_TO_PTR(a) ((void *)(unsigned long)(a)) #define CALLNO_ENTRY_SET_VALIDATED(a) ((a) |= 0x8000) #define CALLNO_ENTRY_IS_VALIDATED(a) ((a) & 0x8000) #define CALLNO_ENTRY_GET_CALLNO(a) ((a) & 0x7FFF) struct call_number_pool { size_t capacity; size_t available; callno_entry numbers[IAX_MAX_CALLS / 2 + 1]; }; AST_MUTEX_DEFINE_STATIC(callno_pool_lock); /*! table of available call numbers */ static struct call_number_pool callno_pool; /*! table of available trunk call numbers */ static struct call_number_pool callno_pool_trunk; /*! * \brief a list of frames that may need to be retransmitted * * \note The contents of this list do not need to be explicitly destroyed * on module unload. This is because all active calls are destroyed, and * all frames in this queue will get destroyed as a part of that process. * * \note Contents protected by the iaxsl[] locks */ static AST_LIST_HEAD_NOLOCK(, iax_frame) frame_queue[IAX_MAX_CALLS]; static struct ast_taskprocessor *transmit_processor; static int randomcalltokendata; static const time_t MAX_CALLTOKEN_DELAY = 10; /*! * This module will get much higher performance when doing a lot of * user and peer lookups if the number of buckets is increased from 1. * However, to maintain old behavior for Asterisk 1.4, these are set to * 1 by default. When using multiple buckets, search order through these * containers is considered random, so you will not be able to depend on * the order the entires are specified in iax.conf for matching order. */ #ifdef LOW_MEMORY #define MAX_PEER_BUCKETS 17 #else #define MAX_PEER_BUCKETS 563 #endif static struct ao2_container *peers; #define MAX_USER_BUCKETS MAX_PEER_BUCKETS static struct ao2_container *users; /*! Table containing peercnt objects for every ip address consuming a callno */ static struct ao2_container *peercnts; /*! Table containing custom callno limit rules for a range of ip addresses. */ static struct ao2_container *callno_limits; /*! Table containing ip addresses not requiring calltoken validation */ static struct ao2_container *calltoken_ignores; static uint16_t DEFAULT_MAXCALLNO_LIMIT = 2048; static uint16_t DEFAULT_MAXCALLNO_LIMIT_NONVAL = 8192; static uint16_t global_maxcallno; /*! Total num of call numbers allowed to be allocated without calltoken validation */ static uint16_t global_maxcallno_nonval; static uint16_t total_nonval_callno_used = 0; /*! peer connection private, keeps track of all the call numbers * consumed by a single ip address */ struct peercnt { /*! ip address consuming call numbers */ struct ast_sockaddr addr; /*! Number of call numbers currently used by this ip address */ uint16_t cur; /*! Max call numbers allowed for this ip address */ uint16_t limit; /*! Specifies whether limit is set by a registration or not, if so normal * limit setting rules do not apply to this address. */ unsigned char reg; }; /*! used by both callno_limits and calltoken_ignores containers */ struct addr_range { /*! ip address range for custom callno limit rule */ struct ast_ha ha; /*! callno limit for this ip address range, only used in callno_limits container */ uint16_t limit; /*! delete me marker for reloads */ unsigned char delme; }; enum { /*! Extension exists */ CACHE_FLAG_EXISTS = (1 << 0), /*! Extension is nonexistent */ CACHE_FLAG_NONEXISTENT = (1 << 1), /*! Extension can exist */ CACHE_FLAG_CANEXIST = (1 << 2), /*! Waiting to hear back response */ CACHE_FLAG_PENDING = (1 << 3), /*! Timed out */ CACHE_FLAG_TIMEOUT = (1 << 4), /*! Request transmitted */ CACHE_FLAG_TRANSMITTED = (1 << 5), /*! Timeout */ CACHE_FLAG_UNKNOWN = (1 << 6), /*! Matchmore */ CACHE_FLAG_MATCHMORE = (1 << 7), }; struct iax2_dpcache { char peercontext[AST_MAX_CONTEXT]; char exten[AST_MAX_EXTENSION]; struct timeval orig; struct timeval expiry; int flags; unsigned short callno; int waiters[256]; AST_LIST_ENTRY(iax2_dpcache) cache_list; AST_LIST_ENTRY(iax2_dpcache) peer_list; }; static AST_LIST_HEAD_STATIC(dpcache, iax2_dpcache); static void reg_source_db(struct iax2_peer *p); static struct iax2_peer *realtime_peer(const char *peername, struct ast_sockaddr *addr); static struct iax2_user *realtime_user(const char *username, struct ast_sockaddr *addr); static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt); static char *complete_iax2_peers(const char *line, const char *word, int pos, int state, uint64_t flags); static char *complete_iax2_unregister(const char *line, const char *word, int pos, int state); enum iax2_thread_iostate { IAX_IOSTATE_IDLE, IAX_IOSTATE_READY, IAX_IOSTATE_PROCESSING, IAX_IOSTATE_SCHEDREADY, }; enum iax2_thread_type { IAX_THREAD_TYPE_POOL, IAX_THREAD_TYPE_DYNAMIC, }; struct iax2_pkt_buf { AST_LIST_ENTRY(iax2_pkt_buf) entry; size_t len; unsigned char buf[1]; }; struct iax2_thread { AST_LIST_ENTRY(iax2_thread) list; enum iax2_thread_type type; enum iax2_thread_iostate iostate; #ifdef SCHED_MULTITHREADED void (*schedfunc)(const void *); const void *scheddata; #endif #ifdef DEBUG_SCHED_MULTITHREAD char curfunc[80]; #endif int actions; pthread_t threadid; int threadnum; struct ast_sockaddr ioaddr; unsigned char readbuf[4096]; unsigned char *buf; ssize_t buf_len; size_t buf_size; int iofd; time_t checktime; ast_mutex_t lock; ast_cond_t cond; ast_mutex_t init_lock; ast_cond_t init_cond; /*! if this thread is processing a full frame, some information about that frame will be stored here, so we can avoid dispatching any more full frames for that callno to other threads */ struct { unsigned short callno; struct ast_sockaddr addr; unsigned char type; unsigned char csub; } ffinfo; /*! Queued up full frames for processing. If more full frames arrive for * a call which this thread is already processing a full frame for, they * are queued up here. */ AST_LIST_HEAD_NOLOCK(, iax2_pkt_buf) full_frames; unsigned char stop; }; /* Thread lists */ static AST_LIST_HEAD_STATIC(idle_list, iax2_thread); static AST_LIST_HEAD_STATIC(active_list, iax2_thread); static AST_LIST_HEAD_STATIC(dynamic_list, iax2_thread); static void *iax2_process_thread(void *data); static void iax2_destroy(int callno); static void signal_condition(ast_mutex_t *lock, ast_cond_t *cond) { ast_mutex_lock(lock); ast_cond_signal(cond); ast_mutex_unlock(lock); } /*! * \brief an array of iax2 pvt structures * * The container for active chan_iax2_pvt structures is implemented as an * array for extremely quick direct access to the correct pvt structure * based on the local call number. The local call number is used as the * index into the array where the associated pvt structure is stored. */ static struct chan_iax2_pvt *iaxs[IAX_MAX_CALLS]; static struct ast_callid *iax_pvt_callid_get(int callno) { if (iaxs[callno]->callid) { return ast_callid_ref(iaxs[callno]->callid); } return NULL; } static void iax_pvt_callid_set(int callno, struct ast_callid *callid) { if (iaxs[callno]->callid) { ast_callid_unref(iaxs[callno]->callid); } ast_callid_ref(callid); iaxs[callno]->callid = callid; } static void iax_pvt_callid_new(int callno) { struct ast_callid *callid = ast_create_callid(); char buffer[AST_CALLID_BUFFER_LENGTH]; ast_callid_strnprint(buffer, sizeof(buffer), callid); iax_pvt_callid_set(callno, callid); ast_callid_unref(callid); } /*! * \brief Another container of iax2_pvt structures * * Active IAX2 pvt structs are also stored in this container, if they are a part * of an active call where we know the remote side's call number. The reason * for this is that incoming media frames do not contain our call number. So, * instead of having to iterate the entire iaxs array, we use this container to * look up calls where the remote side is using a given call number. */ static struct ao2_container *iax_peercallno_pvts; /*! * \brief chan_iax2_pvt structure locks * * These locks are used when accessing a pvt structure in the iaxs array. * The index used here is the same as used in the iaxs array. It is the * local call number for the associated pvt struct. */ static ast_mutex_t iaxsl[ARRAY_LEN(iaxs)]; /*! * * \brief Another container of iax2_pvt structures * * Active IAX2 pvt stucts used during transfering a call are stored here. */ static struct ao2_container *iax_transfercallno_pvts; /* Flag to use with trunk calls, keeping these calls high up. It halves our effective use but keeps the division between trunked and non-trunked better. */ #define TRUNK_CALL_START (IAX_MAX_CALLS / 2) /* Debug routines... */ static struct ast_sockaddr debugaddr; static void iax_outputframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, struct ast_sockaddr *addr, int datalen) { if (iaxdebug || (addr && !ast_sockaddr_isnull(&debugaddr) && (!ast_sockaddr_port(&debugaddr) || ast_sockaddr_port(&debugaddr) == ast_sockaddr_port(addr)) && !ast_sockaddr_cmp_addr(&debugaddr, addr))) { if (iaxdebug) { iax_showframe(f, fhi, rx, addr, datalen); } else { iaxdebug = 1; iax_showframe(f, fhi, rx, addr, datalen); iaxdebug = 0; } } } static void iax_debug_output(const char *data) { if (iaxdebug) ast_verbose("%s", data); } static void iax_error_output(const char *data) { ast_log(LOG_WARNING, "%s", data); } static void __attribute__((format(printf, 1, 2))) jb_error_output(const char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); ast_log(LOG_ERROR, "%s", buf); } static void __attribute__((format(printf, 1, 2))) jb_warning_output(const char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); ast_log(LOG_WARNING, "%s", buf); } static void __attribute__((format(printf, 1, 2))) jb_debug_output(const char *fmt, ...) { va_list args; char buf[1024]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); ast_verbose("%s", buf); } static int expire_registry(const void *data); static int iax2_answer(struct ast_channel *c); static int iax2_call(struct ast_channel *c, const char *dest, int timeout); static int iax2_devicestate(const char *data); static int iax2_digit_begin(struct ast_channel *c, char digit); static int iax2_digit_end(struct ast_channel *c, char digit, unsigned int duration); static int iax2_do_register(struct iax2_registry *reg); static int iax2_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan); static int iax2_hangup(struct ast_channel *c); static int iax2_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen); static int iax2_poke_peer(struct iax2_peer *peer, int heldcall); static int iax2_provision(struct ast_sockaddr *end, int sockfd, const char *dest, const char *template, int force); static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final); static int iax2_sendhtml(struct ast_channel *c, int subclass, const char *data, int datalen); static int iax2_sendimage(struct ast_channel *c, struct ast_frame *img); static int iax2_sendtext(struct ast_channel *c, const char *text); static int iax2_setoption(struct ast_channel *c, int option, void *data, int datalen); static int iax2_queryoption(struct ast_channel *c, int option, void *data, int *datalen); static int iax2_transfer(struct ast_channel *c, const char *dest); static int iax2_write(struct ast_channel *c, struct ast_frame *f); static int iax2_sched_add(struct ast_sched_context *sched, int when, ast_sched_cb callback, const void *data); static int send_trunk(struct iax2_trunk_peer *tpeer, struct timeval *now); static int send_command(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int, int); static int send_command_final(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int, int); static int send_command_immediate(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int, int); static int send_command_locked(unsigned short callno, char, int, unsigned int, const unsigned char *, int, int); static int send_command_transfer(struct chan_iax2_pvt *, char, int, unsigned int, const unsigned char *, int); static struct ast_channel *iax2_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause); static struct ast_frame *iax2_read(struct ast_channel *c); static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly); static struct iax2_user *build_user(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly); static void realtime_update_peer(const char *peername, struct ast_sockaddr *sockaddr, time_t regtime); static void *iax2_dup_variable_datastore(void *); static void prune_peers(void); static void prune_users(void); static void iax2_free_variable_datastore(void *); static int acf_channel_read(struct ast_channel *chan, const char *funcname, char *preparse, char *buf, size_t buflen); static int decode_frame(ast_aes_decrypt_key *dcx, struct ast_iax2_full_hdr *fh, struct ast_frame *f, int *datalen); static int encrypt_frame(ast_aes_encrypt_key *ecx, struct ast_iax2_full_hdr *fh, unsigned char *poo, int *datalen); static void build_ecx_key(const unsigned char *digest, struct chan_iax2_pvt *pvt); static void build_rand_pad(unsigned char *buf, ssize_t len); static int get_unused_callno(enum callno_type type, int validated, callno_entry *entry); static int replace_callno(const void *obj); static void sched_delay_remove(struct ast_sockaddr *addr, callno_entry entry); static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); static struct ast_channel_tech iax2_tech = { .type = "IAX2", .description = tdesc, .properties = AST_CHAN_TP_WANTSJITTER, .requester = iax2_request, .devicestate = iax2_devicestate, .send_digit_begin = iax2_digit_begin, .send_digit_end = iax2_digit_end, .send_text = iax2_sendtext, .send_image = iax2_sendimage, .send_html = iax2_sendhtml, .call = iax2_call, .hangup = iax2_hangup, .answer = iax2_answer, .read = iax2_read, .write = iax2_write, .write_video = iax2_write, .indicate = iax2_indicate, .setoption = iax2_setoption, .queryoption = iax2_queryoption, .transfer = iax2_transfer, .fixup = iax2_fixup, .func_channel_read = acf_channel_read, }; /*! * \internal * \brief Obtain the owner channel lock if the owner exists. * * \param callno IAX2 call id. * * \note Assumes the iaxsl[callno] lock is already obtained. * * \note * IMPORTANT NOTE!!! Any time this function is used, even if * iaxs[callno] was valid before calling it, it may no longer be * valid after calling it. This function may unlock and lock * the mutex associated with this callno, meaning that another * thread may grab it and destroy the call. * * \return Nothing */ static void iax2_lock_owner(int callno) { for (;;) { if (!iaxs[callno] || !iaxs[callno]->owner) { /* There is no owner lock to get. */ break; } if (!ast_channel_trylock(iaxs[callno]->owner)) { /* We got the lock */ break; } /* Avoid deadlock by pausing and trying again */ DEADLOCK_AVOIDANCE(&iaxsl[callno]); } } /*! * \internal * \brief Check if a control subtype is allowed on the wire. * * \param subtype Control frame subtype to check if allowed to/from the wire. * * \retval non-zero if allowed. */ static int iax2_is_control_frame_allowed(int subtype) { enum ast_control_frame_type control = subtype; int is_allowed; /* * Note: If we compare the enumeration type, which does not have any * negative constants, the compiler may optimize this code away. * Therefore, we must perform an integer comparison here. */ if (subtype == -1) { return -1; } /* Default to not allowing control frames to pass. */ is_allowed = 0; /* * The switch default is not present in order to take advantage * of the compiler complaining of a missing enum case. */ switch (control) { /* * These control frames make sense to send/receive across the link. */ case AST_CONTROL_HANGUP: case AST_CONTROL_RING: case AST_CONTROL_RINGING: case AST_CONTROL_ANSWER: case AST_CONTROL_BUSY: case AST_CONTROL_TAKEOFFHOOK: case AST_CONTROL_OFFHOOK: case AST_CONTROL_CONGESTION: case AST_CONTROL_FLASH: case AST_CONTROL_WINK: case AST_CONTROL_OPTION: case AST_CONTROL_RADIO_KEY: case AST_CONTROL_RADIO_UNKEY: case AST_CONTROL_PROGRESS: case AST_CONTROL_PROCEEDING: case AST_CONTROL_HOLD: case AST_CONTROL_UNHOLD: case AST_CONTROL_VIDUPDATE: case AST_CONTROL_CONNECTED_LINE: case AST_CONTROL_REDIRECTING: case AST_CONTROL_T38_PARAMETERS: case AST_CONTROL_AOC: case AST_CONTROL_INCOMPLETE: case AST_CONTROL_MCID: is_allowed = -1; break; /* * These control frames do not make sense to send/receive across the link. */ case _XXX_AST_CONTROL_T38: /* The control value is deprecated in favor of AST_CONTROL_T38_PARAMETERS. */ case AST_CONTROL_SRCUPDATE: /* Across an IAX link the source is still the same. */ case AST_CONTROL_TRANSFER: /* A success/fail status report from calling ast_transfer() on this machine. */ case AST_CONTROL_CC: /* The payload contains pointers that are valid for the sending machine only. */ case AST_CONTROL_SRCCHANGE: /* Across an IAX link the source is still the same. */ case AST_CONTROL_READ_ACTION: /* The action can only be done by the sending machine. */ case AST_CONTROL_END_OF_Q: /* This frame would cause the call to unexpectedly hangup. */ case AST_CONTROL_UPDATE_RTP_PEER: /* Only meaningful across a bridge on this machine for direct-media exchange. */ case AST_CONTROL_PVT_CAUSE_CODE: /* Intended only for the sending machine's local channel structure. */ case AST_CONTROL_MASQUERADE_NOTIFY: /* Intended only for masquerades when calling ast_indicate_data(). */ case AST_CONTROL_STREAM_STOP: case AST_CONTROL_STREAM_SUSPEND: case AST_CONTROL_STREAM_RESTART: case AST_CONTROL_STREAM_REVERSE: case AST_CONTROL_STREAM_FORWARD: /* None of these playback stream control frames should go across the link. */ case AST_CONTROL_RECORD_CANCEL: case AST_CONTROL_RECORD_STOP: case AST_CONTROL_RECORD_SUSPEND: case AST_CONTROL_RECORD_MUTE: /* None of these media recording control frames should go across the link. */ break; } return is_allowed; } static void mwi_event_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg) { /* The MWI subscriptions exist just so the core knows we care about those * mailboxes. However, we just grab the events out of the cache when it * is time to send MWI, since it is only sent with a REGACK. */ } static void network_change_stasis_subscribe(void) { if (!network_change_sub) { network_change_sub = stasis_subscribe(ast_system_topic(), network_change_stasis_cb, NULL); } } static void network_change_stasis_unsubscribe(void) { network_change_sub = stasis_unsubscribe_and_join(network_change_sub); } static void acl_change_stasis_subscribe(void) { if (!acl_change_sub) { acl_change_sub = stasis_subscribe(ast_security_topic(), acl_change_stasis_cb, NULL); } } static void acl_change_stasis_unsubscribe(void) { acl_change_sub = stasis_unsubscribe_and_join(acl_change_sub); } static int network_change_sched_cb(const void *data) { struct iax2_registry *reg; network_change_sched_id = -1; AST_LIST_LOCK(®istrations); AST_LIST_TRAVERSE(®istrations, reg, entry) { iax2_do_register(reg); } AST_LIST_UNLOCK(®istrations); return 0; } static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { /* This callback is only concerned with network change messages from the system topic. */ if (stasis_message_type(message) != ast_network_change_type()) { return; } ast_verb(1, "IAX, got a network change message, renewing all IAX registrations.\n"); if (network_change_sched_id == -1) { network_change_sched_id = iax2_sched_add(sched, 1000, network_change_sched_cb, NULL); } } static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { if (stasis_message_type(message) != ast_named_acl_change_type()) { return; } ast_log(LOG_NOTICE, "Reloading chan_iax2 in response to ACL change event.\n"); reload_config(1); } static const struct ast_datastore_info iax2_variable_datastore_info = { .type = "IAX2_VARIABLE", .duplicate = iax2_dup_variable_datastore, .destroy = iax2_free_variable_datastore, }; static void *iax2_dup_variable_datastore(void *old) { AST_LIST_HEAD(, ast_var_t) *oldlist = old, *newlist; struct ast_var_t *oldvar, *newvar; newlist = ast_calloc(sizeof(*newlist), 1); if (!newlist) { ast_log(LOG_ERROR, "Unable to duplicate iax2 variables\n"); return NULL; } AST_LIST_HEAD_INIT(newlist); AST_LIST_LOCK(oldlist); AST_LIST_TRAVERSE(oldlist, oldvar, entries) { newvar = ast_var_assign(ast_var_name(oldvar), ast_var_value(oldvar)); if (newvar) AST_LIST_INSERT_TAIL(newlist, newvar, entries); else ast_log(LOG_ERROR, "Unable to duplicate iax2 variable '%s'\n", ast_var_name(oldvar)); } AST_LIST_UNLOCK(oldlist); return newlist; } static void iax2_free_variable_datastore(void *old) { AST_LIST_HEAD(, ast_var_t) *oldlist = old; struct ast_var_t *oldvar; AST_LIST_LOCK(oldlist); while ((oldvar = AST_LIST_REMOVE_HEAD(oldlist, entries))) { ast_free(oldvar); } AST_LIST_UNLOCK(oldlist); AST_LIST_HEAD_DESTROY(oldlist); ast_free(oldlist); } /* WARNING: insert_idle_thread should only ever be called within the * context of an iax2_process_thread() thread. */ static void insert_idle_thread(struct iax2_thread *thread) { if (thread->type == IAX_THREAD_TYPE_DYNAMIC) { AST_LIST_LOCK(&dynamic_list); AST_LIST_INSERT_TAIL(&dynamic_list, thread, list); AST_LIST_UNLOCK(&dynamic_list); } else { AST_LIST_LOCK(&idle_list); AST_LIST_INSERT_TAIL(&idle_list, thread, list); AST_LIST_UNLOCK(&idle_list); } return; } static struct iax2_thread *find_idle_thread(void) { struct iax2_thread *thread = NULL; /* Pop the head of the idle list off */ AST_LIST_LOCK(&idle_list); thread = AST_LIST_REMOVE_HEAD(&idle_list, list); AST_LIST_UNLOCK(&idle_list); /* If we popped a thread off the idle list, just return it */ if (thread) { memset(&thread->ffinfo, 0, sizeof(thread->ffinfo)); return thread; } /* Pop the head of the dynamic list off */ AST_LIST_LOCK(&dynamic_list); thread = AST_LIST_REMOVE_HEAD(&dynamic_list, list); AST_LIST_UNLOCK(&dynamic_list); /* If we popped a thread off the dynamic list, just return it */ if (thread) { memset(&thread->ffinfo, 0, sizeof(thread->ffinfo)); return thread; } /* If we can't create a new dynamic thread for any reason, return no thread at all */ if (iaxdynamicthreadcount >= iaxmaxthreadcount || !(thread = ast_calloc(1, sizeof(*thread)))) return NULL; /* Set default values */ ast_atomic_fetchadd_int(&iaxdynamicthreadcount, 1); thread->threadnum = ast_atomic_fetchadd_int(&iaxdynamicthreadnum, 1); thread->type = IAX_THREAD_TYPE_DYNAMIC; /* Initialize lock and condition */ ast_mutex_init(&thread->lock); ast_cond_init(&thread->cond, NULL); ast_mutex_init(&thread->init_lock); ast_cond_init(&thread->init_cond, NULL); ast_mutex_lock(&thread->init_lock); /* Create thread and send it on it's way */ if (ast_pthread_create_background(&thread->threadid, NULL, iax2_process_thread, thread)) { ast_cond_destroy(&thread->cond); ast_mutex_destroy(&thread->lock); ast_mutex_unlock(&thread->init_lock); ast_cond_destroy(&thread->init_cond); ast_mutex_destroy(&thread->init_lock); ast_free(thread); return NULL; } /* this thread is not processing a full frame (since it is idle), so ensure that the field for the full frame call number is empty */ memset(&thread->ffinfo, 0, sizeof(thread->ffinfo)); /* Wait for the thread to be ready before returning it to the caller */ ast_cond_wait(&thread->init_cond, &thread->init_lock); /* Done with init_lock */ ast_mutex_unlock(&thread->init_lock); return thread; } #ifdef SCHED_MULTITHREADED static int __schedule_action(void (*func)(const void *data), const void *data, const char *funcname) { struct iax2_thread *thread; static time_t lasterror; time_t t; thread = find_idle_thread(); if (thread != NULL) { thread->schedfunc = func; thread->scheddata = data; thread->iostate = IAX_IOSTATE_SCHEDREADY; #ifdef DEBUG_SCHED_MULTITHREAD ast_copy_string(thread->curfunc, funcname, sizeof(thread->curfunc)); #endif signal_condition(&thread->lock, &thread->cond); return 0; } time(&t); if (t != lasterror) { lasterror = t; ast_debug(1, "Out of idle IAX2 threads for scheduling! (%s)\n", funcname); } return -1; } #define schedule_action(func, data) __schedule_action(func, data, __PRETTY_FUNCTION__) #endif static int iax2_sched_replace(int id, struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) { return ast_sched_replace(id, con, when, callback, data); } static int iax2_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) { return ast_sched_add(con, when, callback, data); } static int send_ping(const void *data); static void __send_ping(const void *data) { int callno = (long) data; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { if (iaxs[callno]->peercallno) { send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_PING, 0, NULL, 0, -1); if (iaxs[callno]->pingid != DONT_RESCHEDULE) { iaxs[callno]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, data); } } } else { ast_debug(1, "I was supposed to send a PING with callno %d, but no such call exists.\n", callno); } ast_mutex_unlock(&iaxsl[callno]); } static int send_ping(const void *data) { int callno = (long) data; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno] && iaxs[callno]->pingid != DONT_RESCHEDULE) { iaxs[callno]->pingid = -1; } ast_mutex_unlock(&iaxsl[callno]); #ifdef SCHED_MULTITHREADED if (schedule_action(__send_ping, data)) #endif __send_ping(data); return 0; } static void encmethods_to_str(int e, struct ast_str **buf) { ast_str_set(buf, 0, "("); if (e & IAX_ENCRYPT_AES128) { ast_str_append(buf, 0, "aes128"); } if (e & IAX_ENCRYPT_KEYROTATE) { ast_str_append(buf, 0, ",keyrotate"); } if (ast_str_strlen(*buf) > 1) { ast_str_append(buf, 0, ")"); } else { ast_str_set(buf, 0, "No"); } } static int get_encrypt_methods(const char *s) { int e; if (!strcasecmp(s, "aes128")) e = IAX_ENCRYPT_AES128 | IAX_ENCRYPT_KEYROTATE; else if (ast_true(s)) e = IAX_ENCRYPT_AES128 | IAX_ENCRYPT_KEYROTATE; else e = 0; return e; } static int send_lagrq(const void *data); static void __send_lagrq(const void *data) { int callno = (long) data; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { if (iaxs[callno]->peercallno) { send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_LAGRQ, 0, NULL, 0, -1); if (iaxs[callno]->lagid != DONT_RESCHEDULE) { iaxs[callno]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, data); } } } else { ast_debug(1, "I was supposed to send a LAGRQ with callno %d, but no such call exists.\n", callno); } ast_mutex_unlock(&iaxsl[callno]); } static int send_lagrq(const void *data) { int callno = (long) data; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno] && iaxs[callno]->lagid != DONT_RESCHEDULE) { iaxs[callno]->lagid = -1; } ast_mutex_unlock(&iaxsl[callno]); #ifdef SCHED_MULTITHREADED if (schedule_action(__send_lagrq, data)) #endif __send_lagrq(data); return 0; } static unsigned char compress_subclass(iax2_format subclass) { int x; int power=-1; /* If it's 64 or smaller, just return it */ if (subclass < IAX_FLAG_SC_LOG) return subclass; /* Otherwise find its power */ for (x = 0; x < IAX_MAX_SHIFT; x++) { if (subclass & (1LL << x)) { if (power > -1) { ast_log(LOG_WARNING, "Can't compress subclass %lld\n", (long long) subclass); return 0; } else power = x; } } return power | IAX_FLAG_SC_LOG; } static iax2_format uncompress_subclass(unsigned char csub) { /* If the SC_LOG flag is set, return 2^csub otherwise csub */ if (csub & IAX_FLAG_SC_LOG) { /* special case for 'compressed' -1 */ if (csub == 0xff) return -1; else return 1LL << (csub & ~IAX_FLAG_SC_LOG & IAX_MAX_SHIFT); } else return csub; } static struct ast_format *codec_choose_from_prefs(struct iax2_codec_pref *pref, struct ast_format_cap *cap) { int x; struct ast_format *found_format = NULL; for (x = 0; x < ARRAY_LEN(pref->order); ++x) { struct ast_format *pref_format; uint64_t pref_bitfield; pref_bitfield = iax2_codec_pref_order_value_to_format_bitfield(pref->order[x]); if (!pref_bitfield) { break; } pref_format = ast_format_compatibility_bitfield2format(pref_bitfield); if (!pref_format) { /* The bitfield is not associated with any format. */ continue; } found_format = ast_format_cap_get_compatible_format(cap, pref_format); if (found_format) { break; } } if (found_format && (ast_format_get_type(found_format) == AST_MEDIA_TYPE_AUDIO)) { return found_format; } ast_debug(4, "Could not find preferred codec - Returning zero codec.\n"); ao2_cleanup(found_format); return NULL; } static iax2_format iax2_codec_choose(struct iax2_codec_pref *pref, iax2_format formats) { struct ast_format_cap *cap; struct ast_format *tmpfmt; iax2_format format = 0; if ((cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { iax2_format_compatibility_bitfield2cap(formats, cap); tmpfmt = codec_choose_from_prefs(pref, cap); if (!tmpfmt) { ao2_ref(cap, -1); return 0; } format = ast_format_compatibility_format2bitfield(tmpfmt); ao2_ref(tmpfmt, -1); ao2_ref(cap, -1); } return format; } const char *iax2_getformatname(iax2_format format) { struct ast_format *tmpfmt; tmpfmt = ast_format_compatibility_bitfield2format(format); if (!tmpfmt) { return "Unknown"; } return ast_format_get_name(tmpfmt); } static const char *iax2_getformatname_multiple(iax2_format format, struct ast_str **codec_buf) { struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!cap) { return "(Nothing)"; } iax2_format_compatibility_bitfield2cap(format, cap); ast_format_cap_get_names(cap, codec_buf); ao2_ref(cap, -1); return ast_str_buffer(*codec_buf); } static int iax2_parse_allow_disallow(struct iax2_codec_pref *pref, iax2_format *formats, const char *list, int allowing) { int res, i; struct ast_format_cap *cap; /* We want to add the formats to the cap in the preferred order */ cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!cap || iax2_codec_pref_to_cap(pref, cap)) { ao2_cleanup(cap); return 1; } res = ast_format_cap_update_by_allow_disallow(cap, list, allowing); /* Adjust formats bitfield and pref list to match. */ *formats = iax2_format_compatibility_cap2bitfield(cap); iax2_codec_pref_remove_missing(pref, *formats); for (i = 0; i < ast_format_cap_count(cap); i++) { struct ast_format *fmt = ast_format_cap_get_format(cap, i); iax2_codec_pref_append(pref, fmt, ast_format_cap_get_format_framing(cap, fmt)); ao2_ref(fmt, -1); } ao2_ref(cap, -1); return res; } static int iax2_data_add_codecs(struct ast_data *root, const char *node_name, iax2_format formats) { int res; struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!cap) { return -1; } iax2_format_compatibility_bitfield2cap(formats, cap); res = ast_data_add_codecs(root, node_name, cap); ao2_ref(cap, -1); return res; } /*! * \note The only member of the peer passed here guaranteed to be set is the name field */ static int peer_hash_cb(const void *obj, const int flags) { const struct iax2_peer *peer = obj; const char *name = obj; return ast_str_hash(flags & OBJ_KEY ? name : peer->name); } /*! * \note The only member of the peer passed here guaranteed to be set is the name field */ static int peer_cmp_cb(void *obj, void *arg, int flags) { struct iax2_peer *peer = obj, *peer2 = arg; const char *name = arg; return !strcmp(peer->name, flags & OBJ_KEY ? name : peer2->name) ? CMP_MATCH | CMP_STOP : 0; } /*! * \note The only member of the user passed here guaranteed to be set is the name field */ static int user_hash_cb(const void *obj, const int flags) { const struct iax2_user *user = obj; const char *name = obj; return ast_str_hash(flags & OBJ_KEY ? name : user->name); } /*! * \note The only member of the user passed here guaranteed to be set is the name field */ static int user_cmp_cb(void *obj, void *arg, int flags) { struct iax2_user *user = obj, *user2 = arg; const char *name = arg; return !strcmp(user->name, flags & OBJ_KEY ? name : user2->name) ? CMP_MATCH | CMP_STOP : 0; } /*! * \note This funtion calls realtime_peer -> reg_source_db -> iax2_poke_peer -> find_callno, * so do not call it with a pvt lock held. */ static struct iax2_peer *find_peer(const char *name, int realtime) { struct iax2_peer *peer = NULL; peer = ao2_find(peers, name, OBJ_KEY); /* Now go for realtime if applicable */ if (!peer && realtime) { peer = realtime_peer(name, NULL); } return peer; } static struct iax2_peer *peer_ref(struct iax2_peer *peer) { ao2_ref(peer, +1); return peer; } static inline struct iax2_peer *peer_unref(struct iax2_peer *peer) { ao2_ref(peer, -1); return NULL; } static struct iax2_user *find_user(const char *name) { return ao2_find(users, name, OBJ_KEY); } static inline struct iax2_user *user_ref(struct iax2_user *user) { ao2_ref(user, +1); return user; } static inline struct iax2_user *user_unref(struct iax2_user *user) { ao2_ref(user, -1); return NULL; } static int iax2_getpeername(struct ast_sockaddr addr, char *host, int len) { struct iax2_peer *peer = NULL; int res = 0; struct ao2_iterator i; i = ao2_iterator_init(peers, 0); while ((peer = ao2_iterator_next(&i))) { if (!ast_sockaddr_cmp(&peer->addr, &addr)) { ast_copy_string(host, peer->name, len); peer_unref(peer); res = 1; break; } peer_unref(peer); } ao2_iterator_destroy(&i); if (!peer) { peer = realtime_peer(NULL, &addr); if (peer) { ast_copy_string(host, peer->name, len); peer_unref(peer); res = 1; } } return res; } /*!\note Assumes the lock on the pvt is already held, when * iax2_destroy_helper() is called. */ static void iax2_destroy_helper(struct chan_iax2_pvt *pvt) { /* Decrement AUTHREQ count if needed */ if (ast_test_flag64(pvt, IAX_MAXAUTHREQ)) { struct iax2_user *user; user = ao2_find(users, pvt->username, OBJ_KEY); if (user) { ast_atomic_fetchadd_int(&user->curauthreq, -1); user_unref(user); } ast_clear_flag64(pvt, IAX_MAXAUTHREQ); } /* No more pings or lagrq's */ AST_SCHED_DEL_SPINLOCK(sched, pvt->pingid, &iaxsl[pvt->callno]); pvt->pingid = DONT_RESCHEDULE; AST_SCHED_DEL_SPINLOCK(sched, pvt->lagid, &iaxsl[pvt->callno]); pvt->lagid = DONT_RESCHEDULE; AST_SCHED_DEL(sched, pvt->autoid); AST_SCHED_DEL(sched, pvt->authid); AST_SCHED_DEL(sched, pvt->initid); AST_SCHED_DEL(sched, pvt->jbid); AST_SCHED_DEL(sched, pvt->keyrotateid); } static void iax2_frame_free(struct iax_frame *fr) { AST_SCHED_DEL(sched, fr->retrans); iax_frame_free(fr); } static int scheduled_destroy(const void *vid) { unsigned short callno = PTR_TO_CALLNO(vid); ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { ast_debug(1, "Really destroying %d now...\n", callno); iax2_destroy(callno); } ast_mutex_unlock(&iaxsl[callno]); return 0; } static void free_signaling_queue_entry(struct signaling_queue_entry *s) { if (s->f.datalen) { ast_free(s->f.data.ptr); } ast_free(s); } /*! \brief This function must be called once we are sure the other side has * given us a call number. All signaling is held here until that point. */ static void send_signaling(struct chan_iax2_pvt *pvt) { struct signaling_queue_entry *s = NULL; while ((s = AST_LIST_REMOVE_HEAD(&pvt->signaling_queue, next))) { iax2_send(pvt, &s->f, 0, -1, 0, 0, 0); free_signaling_queue_entry(s); } pvt->hold_signaling = 0; } /*! \brief All frames other than that of type AST_FRAME_IAX must be held until * we have received a destination call number. */ static int queue_signalling(struct chan_iax2_pvt *pvt, struct ast_frame *f) { struct signaling_queue_entry *qe; if (f->frametype == AST_FRAME_IAX || !pvt->hold_signaling) { return 1; /* do not queue this frame */ } else if (!(qe = ast_calloc(1, sizeof(struct signaling_queue_entry)))) { return -1; /* out of memory */ } /* copy ast_frame into our queue entry */ qe->f = *f; if (qe->f.datalen) { /* if there is data in this frame copy it over as well */ if (!(qe->f.data.ptr = ast_malloc(qe->f.datalen))) { free_signaling_queue_entry(qe); return -1; } memcpy(qe->f.data.ptr, f->data.ptr, qe->f.datalen); } AST_LIST_INSERT_TAIL(&pvt->signaling_queue, qe, next); return 0; } static void pvt_destructor(void *obj) { struct chan_iax2_pvt *pvt = obj; struct iax_frame *cur = NULL; struct signaling_queue_entry *s = NULL; ast_mutex_lock(&iaxsl[pvt->callno]); iax2_destroy_helper(pvt); sched_delay_remove(&pvt->addr, pvt->callno_entry); pvt->callno_entry = 0; /* Already gone */ ast_set_flag64(pvt, IAX_ALREADYGONE); AST_LIST_TRAVERSE(&frame_queue[pvt->callno], cur, list) { /* Cancel any pending transmissions */ cur->retries = -1; } ast_mutex_unlock(&iaxsl[pvt->callno]); while ((s = AST_LIST_REMOVE_HEAD(&pvt->signaling_queue, next))) { free_signaling_queue_entry(s); } if (pvt->reg) { pvt->reg->callno = 0; } if (!pvt->owner) { jb_frame frame; if (pvt->vars) { ast_variables_destroy(pvt->vars); pvt->vars = NULL; } while (jb_getall(pvt->jb, &frame) == JB_OK) { iax2_frame_free(frame.data); } jb_destroy(pvt->jb); ast_string_field_free_memory(pvt); } if (pvt->callid) { ast_callid_unref(pvt->callid); } } static struct chan_iax2_pvt *new_iax(struct ast_sockaddr *addr, const char *host) { struct chan_iax2_pvt *tmp; jb_conf jbconf; if (!(tmp = ao2_alloc(sizeof(*tmp), pvt_destructor))) { return NULL; } if (ast_string_field_init(tmp, 32)) { ao2_ref(tmp, -1); tmp = NULL; return NULL; } tmp->prefs = prefs_global; tmp->pingid = -1; tmp->lagid = -1; tmp->autoid = -1; tmp->authid = -1; tmp->initid = -1; tmp->keyrotateid = -1; ast_string_field_set(tmp,exten, "s"); ast_string_field_set(tmp,host, host); tmp->jb = jb_new(); tmp->jbid = -1; jbconf.max_jitterbuf = maxjitterbuffer; jbconf.resync_threshold = resyncthreshold; jbconf.max_contig_interp = maxjitterinterps; jbconf.target_extra = jittertargetextra; jb_setconf(tmp->jb,&jbconf); AST_LIST_HEAD_INIT_NOLOCK(&tmp->dpentries); tmp->hold_signaling = 1; AST_LIST_HEAD_INIT_NOLOCK(&tmp->signaling_queue); return tmp; } static struct iax_frame *iaxfrdup2(struct iax_frame *fr) { struct iax_frame *new = iax_frame_new(DIRECTION_INGRESS, fr->af.datalen, fr->cacheable); if (new) { size_t afdatalen = new->afdatalen; memcpy(new, fr, sizeof(*new)); iax_frame_wrap(new, &fr->af); new->afdatalen = afdatalen; new->data = NULL; new->datalen = 0; new->direction = DIRECTION_INGRESS; new->retrans = -1; } return new; } /* keep these defined in this order. They are used in find_callno to * determine whether or not a new call number should be allowed. */ enum { /* do not allow a new call number, only search ones in use for match */ NEW_PREVENT = 0, /* search for match first, then allow a new one to be allocated */ NEW_ALLOW = 1, /* do not search for match, force a new call number */ NEW_FORCE = 2, /* do not search for match, force a new call number. Signifies call number * has been calltoken validated */ NEW_ALLOW_CALLTOKEN_VALIDATED = 3, }; static int match(struct ast_sockaddr *addr, unsigned short callno, unsigned short dcallno, const struct chan_iax2_pvt *cur, int check_dcallno) { if (!ast_sockaddr_cmp(&cur->addr, addr)) { /* This is the main host */ if ( (cur->peercallno == 0 || cur->peercallno == callno) && (check_dcallno ? dcallno == cur->callno : 1) ) { /* That's us. Be sure we keep track of the peer call number */ return 1; } } if (!ast_sockaddr_cmp(&cur->transfer, addr) && cur->transferring) { /* We're transferring */ if ((dcallno == cur->callno) || (cur->transferring == TRANSFER_MEDIAPASS && cur->transfercallno == callno)) return 1; } return 0; } static int make_trunk(unsigned short callno, int locked) { int x; int res= 0; callno_entry entry; if (iaxs[callno]->oseqno) { ast_log(LOG_WARNING, "Can't make trunk once a call has started!\n"); return -1; } if (callno >= TRUNK_CALL_START) { ast_log(LOG_WARNING, "Call %d is already a trunk\n", callno); return -1; } if (get_unused_callno( CALLNO_TYPE_TRUNK, CALLNO_ENTRY_IS_VALIDATED(iaxs[callno]->callno_entry), &entry)) { ast_log(LOG_WARNING, "Unable to trunk call: Insufficient space\n"); return -1; } x = CALLNO_ENTRY_GET_CALLNO(entry); ast_mutex_lock(&iaxsl[x]); /*! * \note We delete these before switching the slot, because if * they fire in the meantime, they will generate a warning. */ AST_SCHED_DEL(sched, iaxs[callno]->pingid); AST_SCHED_DEL(sched, iaxs[callno]->lagid); iaxs[callno]->lagid = iaxs[callno]->pingid = -1; iaxs[x] = iaxs[callno]; iaxs[x]->callno = x; /* since we copied over the pvt from a different callno, make sure the old entry is replaced * before assigning the new one */ if (iaxs[x]->callno_entry) { iax2_sched_add( sched, MIN_REUSE_TIME * 1000, replace_callno, CALLNO_ENTRY_TO_PTR(iaxs[x]->callno_entry)); } iaxs[x]->callno_entry = entry; iaxs[callno] = NULL; /* Update the two timers that should have been started */ iaxs[x]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x); iaxs[x]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x); if (locked) ast_mutex_unlock(&iaxsl[callno]); res = x; if (!locked) ast_mutex_unlock(&iaxsl[x]); /* We moved this call from a non-trunked to a trunked call */ ast_debug(1, "Made call %d into trunk call %d\n", callno, x); return res; } static void store_by_transfercallno(struct chan_iax2_pvt *pvt) { if (!pvt->transfercallno) { ast_log(LOG_ERROR, "This should not be called without a transfer call number.\n"); return; } ao2_link(iax_transfercallno_pvts, pvt); } static void remove_by_transfercallno(struct chan_iax2_pvt *pvt) { if (!pvt->transfercallno) { ast_log(LOG_ERROR, "This should not be called without a transfer call number.\n"); return; } ao2_unlink(iax_transfercallno_pvts, pvt); } static void store_by_peercallno(struct chan_iax2_pvt *pvt) { if (!pvt->peercallno) { ast_log(LOG_ERROR, "This should not be called without a peer call number.\n"); return; } ao2_link(iax_peercallno_pvts, pvt); } static void remove_by_peercallno(struct chan_iax2_pvt *pvt) { if (!pvt->peercallno) { ast_log(LOG_ERROR, "This should not be called without a peer call number.\n"); return; } ao2_unlink(iax_peercallno_pvts, pvt); } static int addr_range_delme_cb(void *obj, void *arg, int flags) { struct addr_range *lim = obj; lim->delme = 1; return 0; } static int addr_range_hash_cb(const void *obj, const int flags) { const struct addr_range *lim = obj; return abs(ast_sockaddr_hash(&lim->ha.addr)); } static int addr_range_cmp_cb(void *obj, void *arg, int flags) { struct addr_range *lim1 = obj, *lim2 = arg; return (!(ast_sockaddr_cmp_addr(&lim1->ha.addr, &lim2->ha.addr)) && !(ast_sockaddr_cmp_addr(&lim1->ha.netmask, &lim2->ha.netmask))) ? CMP_MATCH | CMP_STOP : 0; } static int peercnt_hash_cb(const void *obj, const int flags) { const struct peercnt *peercnt = obj; if (ast_sockaddr_isnull(&peercnt->addr)) { return 0; } return ast_sockaddr_hash(&peercnt->addr); } static int peercnt_cmp_cb(void *obj, void *arg, int flags) { struct peercnt *peercnt1 = obj, *peercnt2 = arg; return !ast_sockaddr_cmp_addr(&peercnt1->addr, &peercnt2->addr) ? CMP_MATCH | CMP_STOP : 0; } static int addr_range_match_address_cb(void *obj, void *arg, int flags) { struct addr_range *addr_range = obj; struct ast_sockaddr *addr = arg; struct ast_sockaddr tmp_addr; ast_sockaddr_apply_netmask(addr, &addr_range->ha.netmask, &tmp_addr); if (!ast_sockaddr_cmp_addr(&tmp_addr, &addr_range->ha.addr)) { return CMP_MATCH | CMP_STOP; } return 0; } /*! * \internal * * \brief compares addr to calltoken_ignores table to determine if validation is required. */ static int calltoken_required(struct ast_sockaddr *addr, const char *name, int subclass) { struct addr_range *addr_range; struct iax2_peer *peer = NULL; struct iax2_user *user = NULL; /* if no username is given, check for guest accounts */ const char *find = S_OR(name, "guest"); int res = 1; /* required by default */ int optional = 0; enum calltoken_peer_enum calltoken_required = CALLTOKEN_DEFAULT; /* There are only two cases in which calltoken validation is not required. * Case 1. sin falls within the list of address ranges specified in the calltoken optional table and * the peer definition has not set the requirecalltoken option. * Case 2. Username is a valid peer/user, and that peer has requirecalltoken set either auto or no. */ /* ----- Case 1 ----- */ if ((addr_range = ao2_callback(calltoken_ignores, 0, addr_range_match_address_cb, addr))) { ao2_ref(addr_range, -1); optional = 1; } /* ----- Case 2 ----- */ if ((subclass == IAX_COMMAND_NEW) && (user = find_user(find))) { calltoken_required = user->calltoken_required; } else if ((subclass == IAX_COMMAND_NEW) && (user = realtime_user(find, addr))) { calltoken_required = user->calltoken_required; } else if ((subclass != IAX_COMMAND_NEW) && (peer = find_peer(find, 0))) { calltoken_required = peer->calltoken_required; } else if ((subclass != IAX_COMMAND_NEW) && (peer = realtime_peer(find, addr))) { calltoken_required = peer->calltoken_required; } if (peer) { peer_unref(peer); } if (user) { user_unref(user); } ast_debug(1, "Determining if address %s with username %s requires calltoken validation. Optional = %d calltoken_required = %u \n", ast_sockaddr_stringify_addr(addr), name, optional, calltoken_required); if (((calltoken_required == CALLTOKEN_NO) || (calltoken_required == CALLTOKEN_AUTO)) || (optional && (calltoken_required == CALLTOKEN_DEFAULT))) { res = 0; } return res; } /*! * \internal * * \brief set peercnt callno limit. * * \details * First looks in custom definitions. If not found, global limit * is used. Entries marked as reg already have * a custom limit set by a registration and are not modified. */ static void set_peercnt_limit(struct peercnt *peercnt) { uint16_t limit = global_maxcallno; struct addr_range *addr_range; struct ast_sockaddr addr; ast_sockaddr_copy(&addr, &peercnt->addr); if (peercnt->reg && peercnt->limit) { return; /* this peercnt has a custom limit set by a registration */ } if ((addr_range = ao2_callback(callno_limits, 0, addr_range_match_address_cb, &addr))) { limit = addr_range->limit; ast_debug(1, "custom addr_range %d found for %s\n", limit, ast_sockaddr_stringify(&addr)); ao2_ref(addr_range, -1); } peercnt->limit = limit; } /*! * \internal * \brief sets limits for all peercnts in table. done on reload to reflect changes in conf. */ static int set_peercnt_limit_all_cb(void *obj, void *arg, int flags) { struct peercnt *peercnt = obj; set_peercnt_limit(peercnt); ast_debug(1, "Reset limits for peercnts table\n"); return 0; } /*! * \internal * \brief returns match if delme is set. */ static int prune_addr_range_cb(void *obj, void *arg, int flags) { struct addr_range *addr_range = obj; return addr_range->delme ? CMP_MATCH : 0; } /*! * \internal * \brief modifies peercnt entry in peercnts table. Used to set custom limit or mark a registered ip */ static void peercnt_modify(unsigned char reg, uint16_t limit, struct ast_sockaddr *sockaddr) { /* this function turns off and on custom callno limits set by peer registration */ struct peercnt *peercnt; struct peercnt tmp; ast_sockaddr_copy(&tmp.addr, sockaddr); if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) { peercnt->reg = reg; if (limit) { peercnt->limit = limit; } else { set_peercnt_limit(peercnt); } ast_debug(1, "peercnt entry %s modified limit:%d registered:%d", ast_sockaddr_stringify_addr(sockaddr), peercnt->limit, peercnt->reg); ao2_ref(peercnt, -1); /* decrement ref from find */ } } /*! * \internal * \brief adds an ip to the peercnts table, increments connection count if it already exists * * \details First searches for the address in the peercnts table. If found * the current count is incremented. If not found a new peercnt is allocated * and linked into the peercnts table with a call number count of 1. */ static int peercnt_add(struct ast_sockaddr *addr) { struct peercnt *peercnt; int res = 0; struct peercnt tmp; ast_sockaddr_copy(&tmp.addr, addr); /* Reasoning for peercnts container lock: Two identical ip addresses * could be added by different threads at the "same time". Without the container * lock, both threads could alloc space for the same object and attempt * to link to table. With the lock, one would create the object and link * to table while the other would find the already created peercnt object * rather than creating a new one. */ ao2_lock(peercnts); if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) { ao2_lock(peercnt); } else if ((peercnt = ao2_alloc(sizeof(*peercnt), NULL))) { ao2_lock(peercnt); /* create and set defaults */ ast_sockaddr_copy(&peercnt->addr, addr); set_peercnt_limit(peercnt); /* guarantees it does not go away after unlocking table * ao2_find automatically adds this */ ao2_link(peercnts, peercnt); } else { ao2_unlock(peercnts); return -1; } /* check to see if the address has hit its callno limit. If not increment cur. */ if (peercnt->limit > peercnt->cur) { peercnt->cur++; ast_debug(1, "ip callno count incremented to %d for %s\n", peercnt->cur, ast_sockaddr_stringify_addr(addr)); } else { /* max num call numbers for this peer has been reached! */ ast_log(LOG_ERROR, "maxcallnumber limit of %d for %s has been reached!\n", peercnt->limit, ast_sockaddr_stringify_addr(addr)); res = -1; } /* clean up locks and ref count */ ao2_unlock(peercnt); ao2_unlock(peercnts); ao2_ref(peercnt, -1); /* decrement ref from find/alloc, only the container ref remains. */ return res; } /*! * \internal * \brief decrements a peercnts table entry */ static void peercnt_remove(struct peercnt *peercnt) { struct ast_sockaddr addr; ast_sockaddr_copy(&addr, &peercnt->addr); /* * Container locked here since peercnt may be unlinked from * list. If left unlocked, peercnt_add could try and grab this * entry from the table and modify it at the "same time" this * thread attemps to unlink it. */ ao2_lock(peercnts); peercnt->cur--; ast_debug(1, "ip callno count decremented to %d for %s\n", peercnt->cur, ast_sockaddr_stringify_addr(&addr)); /* if this was the last connection from the peer remove it from table */ if (peercnt->cur == 0) { ao2_unlink(peercnts, peercnt);/* decrements ref from table, last ref is left to scheduler */ } ao2_unlock(peercnts); } /*! * \internal * \brief called by scheduler to decrement object */ static int peercnt_remove_cb(const void *obj) { struct peercnt *peercnt = (struct peercnt *) obj; peercnt_remove(peercnt); ao2_ref(peercnt, -1); /* decrement ref from scheduler */ return 0; } /*! * \internal * \brief decrements peercnts connection count, finds by addr */ static int peercnt_remove_by_addr(struct ast_sockaddr *addr) { struct peercnt *peercnt; struct peercnt tmp; ast_sockaddr_copy(&tmp.addr, addr); if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) { peercnt_remove(peercnt); ao2_ref(peercnt, -1); /* decrement ref from find */ } return 0; } /*! * \internal * \brief Create callno_limit entry based on configuration */ static void build_callno_limits(struct ast_variable *v) { struct addr_range *addr_range = NULL; struct addr_range tmp; struct ast_ha *ha; int limit; int error; int found; for (; v; v = v->next) { limit = -1; error = 0; found = 0; ha = ast_append_ha("permit", v->name, NULL, &error); /* check for valid config information */ if (error) { ast_log(LOG_ERROR, "Call number limit for %s could not be added, Invalid address range\n.", v->name); continue; } else if ((sscanf(v->value, "%d", &limit) != 1) || (limit < 0)) { ast_log(LOG_ERROR, "Call number limit for %s could not be added. Invalid limit %s\n.", v->name, v->value); ast_free_ha(ha); continue; } ast_copy_ha(ha, &tmp.ha); /* find or create the addr_range */ if ((addr_range = ao2_find(callno_limits, &tmp, OBJ_POINTER))) { ao2_lock(addr_range); found = 1; } else if (!(addr_range = ao2_alloc(sizeof(*addr_range), NULL))) { ast_free_ha(ha); return; /* out of memory */ } /* copy over config data into addr_range object */ ast_copy_ha(ha, &addr_range->ha); /* this is safe because only one ha is possible for each limit */ ast_free_ha(ha); /* cleanup the tmp ha */ addr_range->limit = limit; addr_range->delme = 0; /* cleanup */ if (found) { ao2_unlock(addr_range); } else { ao2_link(callno_limits, addr_range); } ao2_ref(addr_range, -1); /* decrement ref from ao2_find and ao2_alloc, only container ref remains */ } } /*! * \internal * \brief Create calltoken_ignores entry based on configuration */ static int add_calltoken_ignore(const char *addr) { struct addr_range tmp; struct addr_range *addr_range = NULL; struct ast_ha *ha = NULL; int error = 0; if (ast_strlen_zero(addr)) { ast_log(LOG_WARNING, "invalid calltokenoptional %s\n", addr); return -1; } ha = ast_append_ha("permit", addr, NULL, &error); /* check for valid config information */ if (error) { ast_log(LOG_WARNING, "Error %d creating calltokenoptional entry %s\n", error, addr); return -1; } ast_copy_ha(ha, &tmp.ha); /* find or create the addr_range */ if ((addr_range = ao2_find(calltoken_ignores, &tmp, OBJ_POINTER))) { ao2_lock(addr_range); addr_range->delme = 0; ao2_unlock(addr_range); } else if ((addr_range = ao2_alloc(sizeof(*addr_range), NULL))) { /* copy over config data into addr_range object */ ast_copy_ha(ha, &addr_range->ha); /* this is safe because only one ha is possible */ ao2_link(calltoken_ignores, addr_range); } else { ast_free_ha(ha); return -1; } ast_free_ha(ha); ao2_ref(addr_range, -1); /* decrement ref from ao2_find and ao2_alloc, only container ref remains */ return 0; } static char *handle_cli_iax2_show_callno_limits(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_iterator i; struct peercnt *peercnt; struct ast_sockaddr addr; int found = 0; switch (cmd) { case CLI_INIT: e->command = "iax2 show callnumber usage"; e->usage = "Usage: iax2 show callnumber usage [IP address]\n" " Shows current IP addresses which are consuming iax2 call numbers\n"; return NULL; case CLI_GENERATE: return NULL; case CLI_HANDLER: if (a->argc < 4 || a->argc > 5) return CLI_SHOWUSAGE; if (a->argc == 4) { ast_cli(a->fd, "%-45s %-12s %-12s\n", "Address", "Callno Usage", "Callno Limit"); } i = ao2_iterator_init(peercnts, 0); while ((peercnt = ao2_iterator_next(&i))) { ast_sockaddr_copy(&addr, &peercnt->addr); if (a->argc == 5) { if (!strcasecmp(a->argv[4], ast_sockaddr_stringify(&addr))) { ast_cli(a->fd, "%-45s %-12s %-12s\n", "Address", "Callno Usage", "Callno Limit"); ast_cli(a->fd, "%-45s %-12d %-12d\n", ast_sockaddr_stringify(&addr), peercnt->cur, peercnt->limit); ao2_ref(peercnt, -1); found = 1; break; } } else { ast_cli(a->fd, "%-45s %-12d %-12d\n", ast_sockaddr_stringify(&addr), peercnt->cur, peercnt->limit); } ao2_ref(peercnt, -1); } ao2_iterator_destroy(&i); if (a->argc == 4) { size_t pool_avail = callno_pool.available; size_t trunk_pool_avail = callno_pool_trunk.available; ast_cli(a->fd, "\nNon-CallToken Validation Callno Limit: %d\n" "Non-CallToken Validated Callno Used: %d\n", global_maxcallno_nonval, total_nonval_callno_used); ast_cli(a->fd, "Total Available Callno: %zu\n" "Regular Callno Available: %zu\n" "Trunk Callno Available: %zu\n", pool_avail + trunk_pool_avail, pool_avail, trunk_pool_avail); } else if (a->argc == 5 && !found) { ast_cli(a->fd, "No call number table entries for %s found\n", a->argv[4] ); } return CLI_SUCCESS; default: return NULL; } } static int get_unused_callno(enum callno_type type, int validated, callno_entry *entry) { struct call_number_pool *pool = NULL; callno_entry swap; size_t choice; switch (type) { case CALLNO_TYPE_NORMAL: pool = &callno_pool; break; case CALLNO_TYPE_TRUNK: pool = &callno_pool_trunk; break; default: ast_assert(0); break; } /* If we fail, make sure this has a defined value */ *entry = 0; /* We lock here primarily to ensure thread safety of the * total_nonval_callno_used check and increment */ ast_mutex_lock(&callno_pool_lock); /* Bail out if we don't have any available call numbers */ if (!pool->available) { ast_log(LOG_WARNING, "Out of call numbers\n"); ast_mutex_unlock(&callno_pool_lock); return 1; } /* Only a certain number of non-validated call numbers should be allocated. * If there ever is an attack, this separates the calltoken validating users * from the non-calltoken validating users. */ if (!validated && total_nonval_callno_used >= global_maxcallno_nonval) { ast_log(LOG_WARNING, "NON-CallToken callnumber limit is reached. Current: %d Max: %d\n", total_nonval_callno_used, global_maxcallno_nonval); ast_mutex_unlock(&callno_pool_lock); return 1; } /* We use a modified Fisher-Yates-Durstenfeld Shuffle to maintain a list of * available call numbers. The array of call numbers begins as an ordered * list from 1 -> n, and we keep a running tally of how many remain unclaimed * - let's call that x. When a call number is needed we pick a random index * into the array between 0 and x and use that as our call number. In a * typical FYD shuffle, we would swap the value that we are extracting with * the number at x, but in our case we swap and don't touch the value at x * because it is effectively invisible. We rely on the rest of the IAX2 core * to return the number to us at some point. Finally, we decrement x by 1 * which establishes our new unused range. * * When numbers are returned to the pool, we put them just past x and bump x * by 1 so that this number is now available for re-use. */ choice = ast_random() % pool->available; *entry = pool->numbers[choice]; swap = pool->numbers[pool->available - 1]; pool->numbers[choice] = swap; pool->available--; if (validated) { CALLNO_ENTRY_SET_VALIDATED(*entry); } else { total_nonval_callno_used++; } ast_mutex_unlock(&callno_pool_lock); return 0; } static int replace_callno(const void *obj) { callno_entry entry = PTR_TO_CALLNO_ENTRY(obj); struct call_number_pool *pool; /* We lock here primarily to ensure thread safety of the * total_nonval_callno_used check and decrement */ ast_mutex_lock(&callno_pool_lock); if (!CALLNO_ENTRY_IS_VALIDATED(entry)) { if (total_nonval_callno_used) { total_nonval_callno_used--; } else { ast_log(LOG_ERROR, "Attempted to decrement total non calltoken validated " "callnumbers below zero. Callno is: %d\n", CALLNO_ENTRY_GET_CALLNO(entry)); } } if (CALLNO_ENTRY_GET_CALLNO(entry) < TRUNK_CALL_START) { pool = &callno_pool; } else { pool = &callno_pool_trunk; } ast_assert(pool->capacity > pool->available); /* This clears the validated flag */ entry = CALLNO_ENTRY_GET_CALLNO(entry); pool->numbers[pool->available] = entry; pool->available++; ast_mutex_unlock(&callno_pool_lock); return 0; } static int create_callno_pools(void) { uint16_t i; callno_pool.available = callno_pool_trunk.available = 0; /* We start at 2. 0 and 1 are reserved. */ for (i = 2; i < TRUNK_CALL_START; i++) { callno_pool.numbers[callno_pool.available] = i; callno_pool.available++; } for (i = TRUNK_CALL_START; i < IAX_MAX_CALLS; i++) { callno_pool_trunk.numbers[callno_pool_trunk.available] = i; callno_pool_trunk.available++; } callno_pool.capacity = callno_pool.available; callno_pool_trunk.capacity = callno_pool_trunk.available; ast_assert(callno_pool.capacity && callno_pool_trunk.capacity); return 0; } /*! * \internal * \brief Schedules delayed removal of iax2_pvt call number data * * \note After MIN_REUSE_TIME has passed for a destroyed iax2_pvt, the callno is * available again, and the address from the previous connection must be decremented * from the peercnts table. This function schedules these operations to take place. */ static void sched_delay_remove(struct ast_sockaddr *addr, callno_entry entry) { int i; struct peercnt *peercnt; struct peercnt tmp; ast_sockaddr_copy(&tmp.addr, addr); if ((peercnt = ao2_find(peercnts, &tmp, OBJ_POINTER))) { /* refcount is incremented with ao2_find. keep that ref for the scheduler */ ast_debug(1, "schedule decrement of callno used for %s in %d seconds\n", ast_sockaddr_stringify_addr(addr), MIN_REUSE_TIME); i = iax2_sched_add(sched, MIN_REUSE_TIME * 1000, peercnt_remove_cb, peercnt); if (i == -1) { ao2_ref(peercnt, -1); } } iax2_sched_add( sched, MIN_REUSE_TIME * 1000, replace_callno, CALLNO_ENTRY_TO_PTR(entry)); } /*! * \internal * \brief returns whether or not a frame is capable of starting a new IAX2 dialog. * * \note For this implementation, inbound pokes should _NOT_ be capable of allocating * a new callno. */ static inline int attribute_pure iax2_allow_new(int frametype, int subclass, int inbound) { if (frametype != AST_FRAME_IAX) { return 0; } switch (subclass) { case IAX_COMMAND_NEW: case IAX_COMMAND_REGREQ: case IAX_COMMAND_FWDOWNL: case IAX_COMMAND_REGREL: return 1; case IAX_COMMAND_POKE: if (!inbound) { return 1; } break; } return 0; } /* * \note Calling this function while holding another pvt lock can cause a deadlock. */ static int __find_callno(unsigned short callno, unsigned short dcallno, struct ast_sockaddr *addr, int new, int sockfd, int return_locked, int check_dcallno) { int res = 0; int x; /* this call is calltoken validated as long as it is either NEW_FORCE * or NEW_ALLOW_CALLTOKEN_VALIDATED */ int validated = (new > NEW_ALLOW) ? 1 : 0; char host[80]; if (new <= NEW_ALLOW) { if (callno) { struct chan_iax2_pvt *pvt; struct chan_iax2_pvt tmp_pvt = { .callno = dcallno, .peercallno = callno, .transfercallno = callno, /* hack!! */ .frames_received = check_dcallno, }; ast_sockaddr_copy(&tmp_pvt.addr, addr); /* this works for finding normal call numbers not involving transfering */ if ((pvt = ao2_find(iax_peercallno_pvts, &tmp_pvt, OBJ_POINTER))) { if (return_locked) { ast_mutex_lock(&iaxsl[pvt->callno]); } res = pvt->callno; ao2_ref(pvt, -1); pvt = NULL; return res; } /* this searches for transfer call numbers that might not get caught otherwise */ memset(&tmp_pvt.addr, 0, sizeof(tmp_pvt.addr)); ast_sockaddr_copy(&tmp_pvt.transfer, addr); if ((pvt = ao2_find(iax_transfercallno_pvts, &tmp_pvt, OBJ_POINTER))) { if (return_locked) { ast_mutex_lock(&iaxsl[pvt->callno]); } res = pvt->callno; ao2_ref(pvt, -1); pvt = NULL; return res; } } /* This will occur on the first response to a message that we initiated, * such as a PING. */ if (dcallno) { ast_mutex_lock(&iaxsl[dcallno]); } if (callno && dcallno && iaxs[dcallno] && !iaxs[dcallno]->peercallno && match(addr, callno, dcallno, iaxs[dcallno], check_dcallno)) { iaxs[dcallno]->peercallno = callno; res = dcallno; store_by_peercallno(iaxs[dcallno]); if (!res || !return_locked) { ast_mutex_unlock(&iaxsl[dcallno]); } return res; } if (dcallno) { ast_mutex_unlock(&iaxsl[dcallno]); } } if (!res && (new >= NEW_ALLOW)) { callno_entry entry; /* It may seem odd that we look through the peer list for a name for * this *incoming* call. Well, it is weird. However, users don't * have an IP address/port number that we can match against. So, * this is just checking for a peer that has that IP/port and * assuming that we have a user of the same name. This isn't always * correct, but it will be changed if needed after authentication. */ if (!iax2_getpeername(*addr, host, sizeof(host))) snprintf(host, sizeof(host), "%s", ast_sockaddr_stringify(addr)); if (peercnt_add(addr)) { /* This address has hit its callnumber limit. When the limit * is reached, the connection is not added to the peercnts table.*/ return 0; } if (get_unused_callno(CALLNO_TYPE_NORMAL, validated, &entry)) { /* since we ran out of space, remove the peercnt * entry we added earlier */ peercnt_remove_by_addr(addr); ast_log(LOG_WARNING, "No more space\n"); return 0; } x = CALLNO_ENTRY_GET_CALLNO(entry); ast_mutex_lock(&iaxsl[x]); iaxs[x] = new_iax(addr, host); if (iaxs[x]) { if (iaxdebug) ast_debug(1, "Creating new call structure %d\n", x); iaxs[x]->callno_entry = entry; iaxs[x]->sockfd = sockfd; ast_sockaddr_copy(&iaxs[x]->addr, addr); iaxs[x]->peercallno = callno; iaxs[x]->callno = x; iaxs[x]->pingtime = DEFAULT_RETRY_TIME; iaxs[x]->expiry = min_reg_expire; iaxs[x]->pingid = iax2_sched_add(sched, ping_time * 1000, send_ping, (void *)(long)x); iaxs[x]->lagid = iax2_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)(long)x); iaxs[x]->amaflags = amaflags; ast_copy_flags64(iaxs[x], &globalflags, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_SENDCONNECTEDLINE | IAX_RECVCONNECTEDLINE | IAX_FORCE_ENCRYPT); ast_string_field_set(iaxs[x], accountcode, accountcode); ast_string_field_set(iaxs[x], mohinterpret, mohinterpret); ast_string_field_set(iaxs[x], mohsuggest, mohsuggest); ast_string_field_set(iaxs[x], parkinglot, default_parkinglot); if (iaxs[x]->peercallno) { store_by_peercallno(iaxs[x]); } } else { ast_log(LOG_WARNING, "Out of resources\n"); ast_mutex_unlock(&iaxsl[x]); replace_callno(CALLNO_ENTRY_TO_PTR(entry)); return 0; } if (!return_locked) ast_mutex_unlock(&iaxsl[x]); res = x; } return res; } static int find_callno(unsigned short callno, unsigned short dcallno, struct ast_sockaddr *addr, int new, int sockfd, int full_frame) { return __find_callno(callno, dcallno, addr, new, sockfd, 0, full_frame); } static int find_callno_locked(unsigned short callno, unsigned short dcallno, struct ast_sockaddr *addr, int new, int sockfd, int full_frame) { return __find_callno(callno, dcallno, addr, new, sockfd, 1, full_frame); } /*! * \brief Queue a frame to a call's owning asterisk channel * * \pre This function assumes that iaxsl[callno] is locked when called. * * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno] * was valid before calling it, it may no longer be valid after calling it. * This function may unlock and lock the mutex associated with this callno, * meaning that another thread may grab it and destroy the call. */ static int iax2_queue_frame(int callno, struct ast_frame *f) { iax2_lock_owner(callno); if (iaxs[callno] && iaxs[callno]->owner) { ast_queue_frame(iaxs[callno]->owner, f); ast_channel_unlock(iaxs[callno]->owner); } return 0; } /*! * \brief Queue a hold frame on the ast_channel owner * * This function queues a hold frame on the owner of the IAX2 pvt struct that * is active for the given call number. * * \pre Assumes lock for callno is already held. * * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno] * was valid before calling it, it may no longer be valid after calling it. * This function may unlock and lock the mutex associated with this callno, * meaning that another thread may grab it and destroy the call. */ static int iax2_queue_hold(int callno, const char *musicclass) { iax2_lock_owner(callno); if (iaxs[callno] && iaxs[callno]->owner) { ast_queue_hold(iaxs[callno]->owner, musicclass); ast_channel_unlock(iaxs[callno]->owner); } return 0; } /*! * \brief Queue an unhold frame on the ast_channel owner * * This function queues an unhold frame on the owner of the IAX2 pvt struct that * is active for the given call number. * * \pre Assumes lock for callno is already held. * * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno] * was valid before calling it, it may no longer be valid after calling it. * This function may unlock and lock the mutex associated with this callno, * meaning that another thread may grab it and destroy the call. */ static int iax2_queue_unhold(int callno) { iax2_lock_owner(callno); if (iaxs[callno] && iaxs[callno]->owner) { ast_queue_unhold(iaxs[callno]->owner); ast_channel_unlock(iaxs[callno]->owner); } return 0; } /*! * \brief Queue a hangup frame on the ast_channel owner * * This function queues a hangup frame on the owner of the IAX2 pvt struct that * is active for the given call number. * * \pre Assumes lock for callno is already held. * * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno] * was valid before calling it, it may no longer be valid after calling it. * This function may unlock and lock the mutex associated with this callno, * meaning that another thread may grab it and destroy the call. */ static int iax2_queue_hangup(int callno) { iax2_lock_owner(callno); if (iaxs[callno] && iaxs[callno]->owner) { ast_queue_hangup(iaxs[callno]->owner); ast_channel_unlock(iaxs[callno]->owner); } return 0; } /*! * \note This function assumes that iaxsl[callno] is locked when called. * * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno] * was valid before calling it, it may no longer be valid after calling it. * This function calls iax2_queue_frame(), which may unlock and lock the mutex * associated with this callno, meaning that another thread may grab it and destroy the call. */ static int __do_deliver(void *data) { /* Just deliver the packet by using queueing. This is called by the IAX thread with the iaxsl lock held. */ struct iax_frame *fr = data; fr->retrans = -1; ast_clear_flag(&fr->af, AST_FRFLAG_HAS_TIMING_INFO); if (iaxs[fr->callno] && !ast_test_flag64(iaxs[fr->callno], IAX_ALREADYGONE)) iax2_queue_frame(fr->callno, &fr->af); /* Free our iax frame */ iax2_frame_free(fr); /* And don't run again */ return 0; } static int handle_error(void) { /* XXX Ideally we should figure out why an error occurred and then abort those rather than continuing to try. Unfortunately, the published interface does not seem to work XXX */ #if 0 struct sockaddr_in *sin; int res; struct msghdr m; struct sock_extended_err e; m.msg_name = NULL; m.msg_namelen = 0; m.msg_iov = NULL; m.msg_control = &e; m.msg_controllen = sizeof(e); m.msg_flags = 0; res = recvmsg(netsocket, &m, MSG_ERRQUEUE); if (res < 0) ast_log(LOG_WARNING, "Error detected, but unable to read error: %s\n", strerror(errno)); else { if (m.msg_controllen) { sin = (struct sockaddr_in *)SO_EE_OFFENDER(&e); if (sin) ast_log(LOG_WARNING, "Receive error from %s\n", ast_inet_ntoa(sin->sin_addr)); else ast_log(LOG_WARNING, "No address detected??\n"); } else { ast_log(LOG_WARNING, "Local error: %s\n", strerror(e.ee_errno)); } } #endif return 0; } static int transmit_trunk(struct iax_frame *f, struct ast_sockaddr *addr, int sockfd) { int res; res = ast_sendto(sockfd, f->data, f->datalen, 0, addr); if (res < 0) { ast_debug(1, "Received error: %s\n", strerror(errno)); handle_error(); } else res = 0; return res; } static int send_packet(struct iax_frame *f) { int res; int callno = f->callno; /* Don't send if there was an error, but return error instead */ if (!callno || !iaxs[callno] || iaxs[callno]->error) return -1; /* Called with iaxsl held */ if (iaxdebug) { ast_debug(3, "Sending %u on %d/%d to %s\n", f->ts, callno, iaxs[callno]->peercallno, ast_sockaddr_stringify(&iaxs[callno]->addr)); } if (f->transfer) { iax_outputframe(f, NULL, 0, &iaxs[callno]->transfer, f->datalen - sizeof(struct ast_iax2_full_hdr)); res = ast_sendto(iaxs[callno]->sockfd, f->data, f->datalen, 0, &iaxs[callno]->transfer); } else { iax_outputframe(f, NULL, 0, &iaxs[callno]->addr, f->datalen - sizeof(struct ast_iax2_full_hdr)); res = ast_sendto(iaxs[callno]->sockfd, f->data, f->datalen, 0, &iaxs[callno]->addr); } if (res < 0) { if (iaxdebug) ast_debug(1, "Received error: %s\n", strerror(errno)); handle_error(); } else res = 0; return res; } /*! * \note Since this function calls iax2_queue_hangup(), the pvt struct * for the given call number may disappear during its execution. */ static int iax2_predestroy(int callno) { struct ast_channel *c = NULL; struct chan_iax2_pvt *pvt = iaxs[callno]; if (!pvt) return -1; if (!ast_test_flag64(pvt, IAX_ALREADYGONE)) { iax2_destroy_helper(pvt); ast_set_flag64(pvt, IAX_ALREADYGONE); } if ((c = pvt->owner)) { ast_channel_tech_pvt_set(c, NULL); iax2_queue_hangup(callno); pvt->owner = NULL; ast_module_unref(ast_module_info->self); } return 0; } static void iax2_destroy(int callno) { struct chan_iax2_pvt *pvt = NULL; struct ast_channel *owner = NULL; retry: if ((pvt = iaxs[callno])) { #if 0 /* iax2_destroy_helper gets called from this function later on. When * called twice, we get the (previously) familiar FRACK! errors in * devmode, from the scheduler. An alternative to this approach is to * reset the scheduler entries to -1 when they're deleted in * iax2_destroy_helper(). That approach was previously decided to be * "wrong" because "the memory is going to be deallocated anyway. Why * should we be resetting those values?" */ iax2_destroy_helper(pvt); #endif } owner = pvt ? pvt->owner : NULL; if (owner) { if (ast_channel_trylock(owner)) { ast_debug(3, "Avoiding IAX destroy deadlock\n"); DEADLOCK_AVOIDANCE(&iaxsl[callno]); goto retry; } } if (!owner) { iaxs[callno] = NULL; } if (pvt) { if (!owner) { pvt->owner = NULL; } else { /* If there's an owner, prod it to give up */ /* It is ok to use ast_queue_hangup() here instead of iax2_queue_hangup() * because we already hold the owner channel lock. */ ast_queue_hangup(owner); } if (pvt->peercallno) { remove_by_peercallno(pvt); } if (pvt->transfercallno) { remove_by_transfercallno(pvt); } if (!owner) { ao2_ref(pvt, -1); pvt = NULL; } } if (owner) { ast_channel_unlock(owner); } } static int update_packet(struct iax_frame *f) { /* Called with iaxsl lock held, and iaxs[callno] non-NULL */ struct ast_iax2_full_hdr *fh = f->data; struct ast_frame af; /* if frame is encrypted. decrypt before updating it. */ if (f->encmethods) { decode_frame(&f->mydcx, fh, &af, &f->datalen); } /* Mark this as a retransmission */ fh->dcallno = ntohs(IAX_FLAG_RETRANS | f->dcallno); /* Update iseqno */ f->iseqno = iaxs[f->callno]->iseqno; fh->iseqno = f->iseqno; /* Now re-encrypt the frame */ if (f->encmethods) { /* since this is a retransmit frame, create a new random padding * before re-encrypting. */ build_rand_pad(f->semirand, sizeof(f->semirand)); encrypt_frame(&f->ecx, fh, f->semirand, &f->datalen); } return 0; } static int attempt_transmit(const void *data); static void __attempt_transmit(const void *data) { /* Attempt to transmit the frame to the remote peer... Called without iaxsl held. */ struct iax_frame *f = (struct iax_frame *)data; int freeme = 0; int callno = f->callno; /* Make sure this call is still active */ if (callno) ast_mutex_lock(&iaxsl[callno]); if (callno && iaxs[callno]) { if (f->retries < 0) { /* Already ACK'd */ freeme = 1; } else if (f->retries >= max_retries) { /* Too many attempts. Record an error. */ if (f->transfer) { /* Transfer timeout */ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_TXREJ, 0, NULL, 0, -1); } else if (f->final) { iax2_destroy(callno); } else { if (iaxs[callno]->owner) { ast_log(LOG_WARNING, "Max retries exceeded to host %s on %s (type = %u, subclass = %d, ts=%u, seqno=%d)\n", ast_sockaddr_stringify_addr(&iaxs[f->callno]->addr), ast_channel_name(iaxs[f->callno]->owner), f->af.frametype, f->af.subclass.integer, f->ts, f->oseqno); } iaxs[callno]->error = ETIMEDOUT; if (iaxs[callno]->owner) { struct ast_frame fr = { AST_FRAME_CONTROL, { AST_CONTROL_HANGUP }, .data.uint32 = AST_CAUSE_DESTINATION_OUT_OF_ORDER }; /* Hangup the fd */ iax2_queue_frame(callno, &fr); /* XXX */ /* Remember, owner could disappear */ if (iaxs[callno] && iaxs[callno]->owner) ast_channel_hangupcause_set(iaxs[callno]->owner, AST_CAUSE_DESTINATION_OUT_OF_ORDER); } else { if (iaxs[callno]->reg) { memset(&iaxs[callno]->reg->us, 0, sizeof(iaxs[callno]->reg->us)); iaxs[callno]->reg->regstate = REG_STATE_TIMEOUT; iaxs[callno]->reg->refresh = IAX_DEFAULT_REG_EXPIRE; } iax2_destroy(callno); } } freeme = 1; } else { /* Update it if it needs it */ update_packet(f); /* Attempt transmission */ send_packet(f); f->retries++; /* Try again later after 10 times as long */ f->retrytime *= 10; if (f->retrytime > MAX_RETRY_TIME) f->retrytime = MAX_RETRY_TIME; /* Transfer messages max out at one second */ if (f->transfer && (f->retrytime > 1000)) f->retrytime = 1000; f->retrans = iax2_sched_add(sched, f->retrytime, attempt_transmit, f); } } else { /* Make sure it gets freed */ f->retries = -1; freeme = 1; } if (freeme) { /* Don't attempt delivery, just remove it from the queue */ AST_LIST_REMOVE(&frame_queue[callno], f, list); ast_mutex_unlock(&iaxsl[callno]); f->retrans = -1; /* this is safe because this is the scheduled function */ /* Free the IAX frame */ iax2_frame_free(f); } else if (callno) { ast_mutex_unlock(&iaxsl[callno]); } } static int attempt_transmit(const void *data) { #ifdef SCHED_MULTITHREADED if (schedule_action(__attempt_transmit, data)) #endif __attempt_transmit(data); return 0; } static char *handle_cli_iax2_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct iax2_peer *peer = NULL; struct iax2_user *user = NULL; static const char * const choices[] = { "all", NULL }; char *cmplt; switch (cmd) { case CLI_INIT: e->command = "iax2 prune realtime"; e->usage = "Usage: iax2 prune realtime [|all]\n" " Prunes object(s) from the cache\n"; return NULL; case CLI_GENERATE: if (a->pos == 3) { cmplt = ast_cli_complete(a->word, choices, a->n); if (!cmplt) cmplt = complete_iax2_peers(a->line, a->word, a->pos, a->n - sizeof(choices), IAX_RTCACHEFRIENDS); return cmplt; } return NULL; } if (a->argc != 4) return CLI_SHOWUSAGE; if (!strcmp(a->argv[3], "all")) { prune_users(); prune_peers(); ast_cli(a->fd, "Cache flushed successfully.\n"); return CLI_SUCCESS; } peer = find_peer(a->argv[3], 0); user = find_user(a->argv[3]); if (peer || user) { if (peer) { if (ast_test_flag64(peer, IAX_RTCACHEFRIENDS)) { ast_set_flag64(peer, IAX_RTAUTOCLEAR); expire_registry(peer_ref(peer)); ast_cli(a->fd, "Peer %s was removed from the cache.\n", a->argv[3]); } else { ast_cli(a->fd, "Peer %s is not eligible for this operation.\n", a->argv[3]); } peer_unref(peer); } if (user) { if (ast_test_flag64(user, IAX_RTCACHEFRIENDS)) { ast_set_flag64(user, IAX_RTAUTOCLEAR); ast_cli(a->fd, "User %s was removed from the cache.\n", a->argv[3]); } else { ast_cli(a->fd, "User %s is not eligible for this operation.\n", a->argv[3]); } ao2_unlink(users,user); user_unref(user); } } else { ast_cli(a->fd, "%s was not found in the cache.\n", a->argv[3]); } return CLI_SUCCESS; } static char *handle_cli_iax2_test_losspct(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 test losspct"; e->usage = "Usage: iax2 test losspct \n" " For testing, throws away percent of incoming packets\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) return CLI_SHOWUSAGE; test_losspct = atoi(a->argv[3]); return CLI_SUCCESS; } #ifdef IAXTESTS static char *handle_cli_iax2_test_late(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 test late"; e->usage = "Usage: iax2 test late \n" " For testing, count the next frame as ms late\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) return CLI_SHOWUSAGE; test_late = atoi(a->argv[3]); return CLI_SUCCESS; } static char *handle_cli_iax2_test_resync(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 test resync"; e->usage = "Usage: iax2 test resync \n" " For testing, adjust all future frames by ms\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) return CLI_SHOWUSAGE; test_resync = atoi(a->argv[3]); return CLI_SUCCESS; } static char *handle_cli_iax2_test_jitter(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 test jitter"; e->usage = "Usage: iax2 test jitter \n" " For testing, simulate maximum jitter of +/- on \n" " percentage of packets. If is not specified, adds\n" " jitter to all packets.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc < 4 || a->argc > 5) return CLI_SHOWUSAGE; test_jit = atoi(a->argv[3]); if (a->argc == 5) test_jitpct = atoi(a->argv[4]); return CLI_SUCCESS; } #endif /* IAXTESTS */ /*! \brief peer_status: Report Peer status in character string */ /* returns 1 if peer is online, -1 if unmonitored */ static int peer_status(struct iax2_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); res = -1; } return res; } /*! \brief Show one peer in detail */ static char *handle_cli_iax2_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char status[30]; char cbuf[256]; struct iax2_peer *peer; struct ast_str *codec_buf = ast_str_alloca(256); struct ast_str *encmethods = ast_str_alloca(256); int load_realtime = 0; switch (cmd) { case CLI_INIT: e->command = "iax2 show peer"; e->usage = "Usage: iax2 show peer \n" " Display details on specific IAX peer\n"; return NULL; case CLI_GENERATE: if (a->pos == 3) return complete_iax2_peers(a->line, a->word, a->pos, a->n, 0); return NULL; } if (a->argc < 4) return CLI_SHOWUSAGE; load_realtime = (a->argc == 5 && !strcmp(a->argv[4], "load")) ? 1 : 0; peer = find_peer(a->argv[3], load_realtime); if (peer) { char *str_addr, *str_defaddr; char *str_port, *str_defport; str_addr = ast_strdupa(ast_sockaddr_stringify_addr(&peer->addr)); str_port = ast_strdupa(ast_sockaddr_stringify_port(&peer->addr)); str_defaddr = ast_strdupa(ast_sockaddr_stringify_addr(&peer->defaddr)); str_defport = ast_strdupa(ast_sockaddr_stringify_port(&peer->defaddr)); encmethods_to_str(peer->encmethods, &encmethods); ast_cli(a->fd, "\n\n"); ast_cli(a->fd, " * Name : %s\n", peer->name); ast_cli(a->fd, " Description : %s\n", peer->description); ast_cli(a->fd, " Secret : %s\n", ast_strlen_zero(peer->secret) ? "" : ""); ast_cli(a->fd, " Context : %s\n", peer->context); ast_cli(a->fd, " Parking lot : %s\n", peer->parkinglot); ast_cli(a->fd, " Mailbox : %s\n", peer->mailbox); ast_cli(a->fd, " Dynamic : %s\n", ast_test_flag64(peer, IAX_DYNAMIC) ? "Yes" : "No"); ast_cli(a->fd, " Callnum limit: %d\n", peer->maxcallno); ast_cli(a->fd, " Calltoken req: %s\n", (peer->calltoken_required == CALLTOKEN_YES) ? "Yes" : ((peer->calltoken_required == CALLTOKEN_AUTO) ? "Auto" : "No")); ast_cli(a->fd, " Trunk : %s\n", ast_test_flag64(peer, IAX_TRUNK) ? "Yes" : "No"); ast_cli(a->fd, " Encryption : %s\n", peer->encmethods ? ast_str_buffer(encmethods) : "No"); ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "")); ast_cli(a->fd, " Expire : %d\n", peer->expire); ast_cli(a->fd, " ACL : %s\n", (ast_acl_list_is_empty(peer->acl) ? "No" : "Yes")); ast_cli(a->fd, " Addr->IP : %s Port %s\n", str_addr ? str_addr : "(Unspecified)", str_port); ast_cli(a->fd, " Defaddr->IP : %s Port %s\n", str_defaddr, str_defport); ast_cli(a->fd, " Username : %s\n", peer->username); ast_cli(a->fd, " Codecs : %s\n", iax2_getformatname_multiple(peer->capability, &codec_buf)); if (iax2_codec_pref_string(&peer->prefs, cbuf, sizeof(cbuf)) < 0) { strcpy(cbuf, "Error"); /* Safe */ } ast_cli(a->fd, " Codec Order : %s\n", cbuf); peer_status(peer, status, sizeof(status)); ast_cli(a->fd, " Status : %s\n", status); ast_cli(a->fd, " Qualify : every %dms when OK, every %dms when UNREACHABLE (sample smoothing %s)\n", peer->pokefreqok, peer->pokefreqnotok, peer->smoothing ? "On" : "Off"); ast_cli(a->fd, "\n"); peer_unref(peer); } else { ast_cli(a->fd, "Peer %s not found.\n", a->argv[3]); ast_cli(a->fd, "\n"); } return CLI_SUCCESS; } static char *complete_iax2_peers(const char *line, const char *word, int pos, int state, uint64_t flags) { int which = 0; struct iax2_peer *peer; char *res = NULL; int wordlen = strlen(word); struct ao2_iterator i; i = ao2_iterator_init(peers, 0); while ((peer = ao2_iterator_next(&i))) { if (!strncasecmp(peer->name, word, wordlen) && ++which > state && (!flags || ast_test_flag64(peer, flags))) { res = ast_strdup(peer->name); peer_unref(peer); break; } peer_unref(peer); } ao2_iterator_destroy(&i); return res; } static char *handle_cli_iax2_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct iax_frame *cur; int cnt = 0, dead = 0, final = 0, i = 0; switch (cmd) { case CLI_INIT: e->command = "iax2 show stats"; e->usage = "Usage: iax2 show stats\n" " Display statistics on IAX channel driver.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; for (i = 0; i < ARRAY_LEN(frame_queue); i++) { ast_mutex_lock(&iaxsl[i]); AST_LIST_TRAVERSE(&frame_queue[i], cur, list) { if (cur->retries < 0) dead++; if (cur->final) final++; cnt++; } ast_mutex_unlock(&iaxsl[i]); } ast_cli(a->fd, " IAX Statistics\n"); ast_cli(a->fd, "---------------------\n"); ast_cli(a->fd, "Outstanding frames: %d (%d ingress, %d egress)\n", iax_get_frames(), iax_get_iframes(), iax_get_oframes()); ast_cli(a->fd, "%d timed and %d untimed transmits; MTU %d/%d/%d\n", trunk_timed, trunk_untimed, trunk_maxmtu, trunk_nmaxmtu, global_max_trunk_mtu); ast_cli(a->fd, "Packets in transmit queue: %d dead, %d final, %d total\n\n", dead, final, cnt); trunk_timed = trunk_untimed = 0; if (trunk_maxmtu > trunk_nmaxmtu) trunk_nmaxmtu = trunk_maxmtu; return CLI_SUCCESS; } /*! \brief Set trunk MTU from CLI */ static char *handle_cli_iax2_set_mtu(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int mtuv; switch (cmd) { case CLI_INIT: e->command = "iax2 set mtu"; e->usage = "Usage: iax2 set mtu \n" " Set the system-wide IAX IP mtu to bytes net or\n" " zero to disable. Disabling means that the operating system\n" " must handle fragmentation of UDP packets when the IAX2 trunk\n" " packet exceeds the UDP payload size. This is substantially\n" " below the IP mtu. Try 1240 on ethernets. Must be 172 or\n" " greater for G.711 samples.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 4) return CLI_SHOWUSAGE; if (strncasecmp(a->argv[3], "default", strlen(a->argv[3])) == 0) mtuv = MAX_TRUNK_MTU; else mtuv = atoi(a->argv[3]); if (mtuv == 0) { ast_cli(a->fd, "Trunk MTU control disabled (mtu was %d)\n", global_max_trunk_mtu); global_max_trunk_mtu = 0; return CLI_SUCCESS; } if (mtuv < 172 || mtuv > 4000) { ast_cli(a->fd, "Trunk MTU must be between 172 and 4000\n"); return CLI_SHOWUSAGE; } ast_cli(a->fd, "Trunk MTU changed from %d to %d\n", global_max_trunk_mtu, mtuv); global_max_trunk_mtu = mtuv; return CLI_SUCCESS; } static char *handle_cli_iax2_show_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct iax2_dpcache *dp = NULL; char tmp[1024], *pc = NULL; int s, x, y; struct timeval now = ast_tvnow(); switch (cmd) { case CLI_INIT: e->command = "iax2 show cache"; e->usage = "Usage: iax2 show cache\n" " Display currently cached IAX Dialplan results.\n"; return NULL; case CLI_GENERATE: return NULL; } AST_LIST_LOCK(&dpcache); ast_cli(a->fd, "%-20.20s %-12.12s %-9.9s %-8.8s %s\n", "Peer/Context", "Exten", "Exp.", "Wait.", "Flags"); AST_LIST_TRAVERSE(&dpcache, dp, cache_list) { s = dp->expiry.tv_sec - now.tv_sec; tmp[0] = '\0'; if (dp->flags & CACHE_FLAG_EXISTS) strncat(tmp, "EXISTS|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_NONEXISTENT) strncat(tmp, "NONEXISTENT|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_CANEXIST) strncat(tmp, "CANEXIST|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_PENDING) strncat(tmp, "PENDING|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_TIMEOUT) strncat(tmp, "TIMEOUT|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_TRANSMITTED) strncat(tmp, "TRANSMITTED|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_MATCHMORE) strncat(tmp, "MATCHMORE|", sizeof(tmp) - strlen(tmp) - 1); if (dp->flags & CACHE_FLAG_UNKNOWN) strncat(tmp, "UNKNOWN|", sizeof(tmp) - strlen(tmp) - 1); /* Trim trailing pipe */ if (!ast_strlen_zero(tmp)) { tmp[strlen(tmp) - 1] = '\0'; } else { ast_copy_string(tmp, "(none)", sizeof(tmp)); } y = 0; pc = strchr(dp->peercontext, '@'); if (!pc) { pc = dp->peercontext; } else { pc++; } for (x = 0; x < ARRAY_LEN(dp->waiters); x++) { if (dp->waiters[x] > -1) y++; } if (s > 0) { ast_cli(a->fd, "%-20.20s %-12.12s %-9d %-8d %s\n", pc, dp->exten, s, y, tmp); } else { ast_cli(a->fd, "%-20.20s %-12.12s %-9.9s %-8d %s\n", pc, dp->exten, "(expired)", y, tmp); } } AST_LIST_UNLOCK(&dpcache); return CLI_SUCCESS; } static unsigned int calc_rxstamp(struct chan_iax2_pvt *p, unsigned int offset); static void unwrap_timestamp(struct iax_frame *fr) { /* Video mini frames only encode the lower 15 bits of the session * timestamp, but other frame types (e.g. audio) encode 16 bits. */ const int ts_shift = (fr->af.frametype == AST_FRAME_VIDEO) ? 15 : 16; const int lower_mask = (1 << ts_shift) - 1; const int upper_mask = ~lower_mask; const int last_upper = iaxs[fr->callno]->last & upper_mask; if ( (fr->ts & upper_mask) == last_upper ) { const int x = fr->ts - iaxs[fr->callno]->last; const int threshold = (ts_shift == 15) ? 25000 : 50000; if (x < -threshold) { /* Sudden big jump backwards in timestamp: What likely happened here is that miniframe timestamp has circled but we haven't gotten the update from the main packet. We'll just pretend that we did, and update the timestamp appropriately. */ fr->ts = (last_upper + (1 << ts_shift)) | (fr->ts & lower_mask); if (iaxdebug) ast_debug(1, "schedule_delivery: pushed forward timestamp\n"); } else if (x > threshold) { /* Sudden apparent big jump forwards in timestamp: What's likely happened is this is an old miniframe belonging to the previous top 15 or 16-bit timestamp that has turned up out of order. Adjust the timestamp appropriately. */ fr->ts = (last_upper - (1 << ts_shift)) | (fr->ts & lower_mask); if (iaxdebug) ast_debug(1, "schedule_delivery: pushed back timestamp\n"); } } } static int get_from_jb(const void *p); static void update_jbsched(struct chan_iax2_pvt *pvt) { int when; when = ast_tvdiff_ms(ast_tvnow(), pvt->rxcore); when = jb_next(pvt->jb) - when; if (when <= 0) { /* XXX should really just empty until when > 0.. */ when = 1; } pvt->jbid = iax2_sched_replace(pvt->jbid, sched, when, get_from_jb, CALLNO_TO_PTR(pvt->callno)); } static void __get_from_jb(const void *p) { int callno = PTR_TO_CALLNO(p); struct chan_iax2_pvt *pvt = NULL; struct iax_frame *fr; jb_frame frame; int ret; long ms; long next; struct timeval now = ast_tvnow(); /* Make sure we have a valid private structure before going on */ ast_mutex_lock(&iaxsl[callno]); pvt = iaxs[callno]; if (!pvt) { /* No go! */ ast_mutex_unlock(&iaxsl[callno]); return; } pvt->jbid = -1; /* round up a millisecond since ast_sched_runq does; */ /* prevents us from spinning while waiting for our now */ /* to catch up with runq's now */ now.tv_usec += 1000; ms = ast_tvdiff_ms(now, pvt->rxcore); if(ms >= (next = jb_next(pvt->jb))) { struct ast_format *voicefmt; voicefmt = ast_format_compatibility_bitfield2format(pvt->voiceformat); ret = jb_get(pvt->jb, &frame, ms, voicefmt ? ast_format_get_default_ms(voicefmt) : 20); switch(ret) { case JB_OK: fr = frame.data; __do_deliver(fr); /* __do_deliver() can cause the call to disappear */ pvt = iaxs[callno]; break; case JB_INTERP: { struct ast_frame af = { 0, }; /* create an interpolation frame */ af.frametype = AST_FRAME_VOICE; af.subclass.format = voicefmt; af.samples = frame.ms * (ast_format_get_sample_rate(voicefmt) / 1000); af.src = "IAX2 JB interpolation"; af.delivery = ast_tvadd(pvt->rxcore, ast_samp2tv(next, 1000)); af.offset = AST_FRIENDLY_OFFSET; /* queue the frame: For consistency, we would call __do_deliver here, but __do_deliver wants an iax_frame, * which we'd need to malloc, and then it would free it. That seems like a drag */ if (!ast_test_flag64(iaxs[callno], IAX_ALREADYGONE)) { iax2_queue_frame(callno, &af); /* iax2_queue_frame() could cause the call to disappear */ pvt = iaxs[callno]; } } break; case JB_DROP: iax2_frame_free(frame.data); break; case JB_NOFRAME: case JB_EMPTY: /* do nothing */ break; default: /* shouldn't happen */ break; } } if (pvt) update_jbsched(pvt); ast_mutex_unlock(&iaxsl[callno]); } static int get_from_jb(const void *data) { #ifdef SCHED_MULTITHREADED if (schedule_action(__get_from_jb, data)) #endif __get_from_jb(data); return 0; } /*! * \note This function assumes fr->callno is locked * * \note IMPORTANT NOTE!!! Any time this function is used, even if iaxs[callno] * was valid before calling it, it may no longer be valid after calling it. */ static int schedule_delivery(struct iax_frame *fr, int updatehistory, int fromtrunk, unsigned int *tsout) { int type, len; int ret; int needfree = 0; struct ast_channel *owner = NULL; RAII_VAR(struct ast_channel *, bridge, NULL, ast_channel_cleanup); /* * Clear fr->af.data if there is no data in the buffer. Things * like AST_CONTROL_HOLD without a suggested music class must * have a NULL pointer. */ if (!fr->af.datalen) { memset(&fr->af.data, 0, sizeof(fr->af.data)); } /* Attempt to recover wrapped timestamps */ unwrap_timestamp(fr); /* delivery time is sender's sent timestamp converted back into absolute time according to our clock */ if ( !fromtrunk && !ast_tvzero(iaxs[fr->callno]->rxcore)) fr->af.delivery = ast_tvadd(iaxs[fr->callno]->rxcore, ast_samp2tv(fr->ts, 1000)); else { #if 0 ast_debug(1, "schedule_delivery: set delivery to 0 as we don't have an rxcore yet, or frame is from trunk.\n"); #endif fr->af.delivery = ast_tv(0,0); } type = JB_TYPE_CONTROL; len = 0; if(fr->af.frametype == AST_FRAME_VOICE) { type = JB_TYPE_VOICE; len = ast_codec_samples_count(&fr->af) / (ast_format_get_sample_rate(fr->af.subclass.format) / 1000); } else if(fr->af.frametype == AST_FRAME_CNG) { type = JB_TYPE_SILENCE; } if ( (!ast_test_flag64(iaxs[fr->callno], IAX_USEJITTERBUF)) ) { if (tsout) *tsout = fr->ts; __do_deliver(fr); return -1; } iax2_lock_owner(fr->callno); if (!iaxs[fr->callno]) { /* The call dissappeared so discard this frame that we could not send. */ iax2_frame_free(fr); return -1; } if ((owner = iaxs[fr->callno]->owner)) { bridge = ast_channel_bridge_peer(owner); } /* if the user hasn't requested we force the use of the jitterbuffer, and we're bridged to * a channel that can accept jitter, then flush and suspend the jb, and send this frame straight through */ if ( (!ast_test_flag64(iaxs[fr->callno], IAX_FORCEJITTERBUF)) && owner && bridge && (ast_channel_tech(bridge)->properties & AST_CHAN_TP_WANTSJITTER) ) { jb_frame frame; ast_channel_unlock(owner); /* deliver any frames in the jb */ while (jb_getall(iaxs[fr->callno]->jb, &frame) == JB_OK) { __do_deliver(frame.data); /* __do_deliver() can make the call disappear */ if (!iaxs[fr->callno]) return -1; } jb_reset(iaxs[fr->callno]->jb); AST_SCHED_DEL(sched, iaxs[fr->callno]->jbid); /* deliver this frame now */ if (tsout) *tsout = fr->ts; __do_deliver(fr); return -1; } if (owner) { ast_channel_unlock(owner); } /* insert into jitterbuffer */ /* TODO: Perhaps we could act immediately if it's not droppable and late */ ret = jb_put(iaxs[fr->callno]->jb, fr, type, len, fr->ts, calc_rxstamp(iaxs[fr->callno],fr->ts)); if (ret == JB_DROP) { needfree++; } else if (ret == JB_SCHED) { update_jbsched(iaxs[fr->callno]); } if (tsout) *tsout = fr->ts; if (needfree) { /* Free our iax frame */ iax2_frame_free(fr); return -1; } return 0; } static int transmit_frame(void *data) { struct iax_frame *fr = data; ast_mutex_lock(&iaxsl[fr->callno]); fr->sentyet = 1; if (iaxs[fr->callno]) { send_packet(fr); } if (fr->retries < 0) { ast_mutex_unlock(&iaxsl[fr->callno]); /* No retransmit requested */ iax_frame_free(fr); } else { /* We need reliable delivery. Schedule a retransmission */ AST_LIST_INSERT_TAIL(&frame_queue[fr->callno], fr, list); fr->retries++; fr->retrans = iax2_sched_add(sched, fr->retrytime, attempt_transmit, fr); ast_mutex_unlock(&iaxsl[fr->callno]); } return 0; } static int iax2_transmit(struct iax_frame *fr) { fr->sentyet = 0; return ast_taskprocessor_push(transmit_processor, transmit_frame, fr); } static int iax2_digit_begin(struct ast_channel *c, char digit) { return send_command_locked(PTR_TO_CALLNO(ast_channel_tech_pvt(c)), AST_FRAME_DTMF_BEGIN, digit, 0, NULL, 0, -1); } static int iax2_digit_end(struct ast_channel *c, char digit, unsigned int duration) { return send_command_locked(PTR_TO_CALLNO(ast_channel_tech_pvt(c)), AST_FRAME_DTMF_END, digit, 0, NULL, 0, -1); } static int iax2_sendtext(struct ast_channel *c, const char *text) { return send_command_locked(PTR_TO_CALLNO(ast_channel_tech_pvt(c)), AST_FRAME_TEXT, 0, 0, (unsigned char *)text, strlen(text) + 1, -1); } static int iax2_sendimage(struct ast_channel *c, struct ast_frame *img) { return send_command_locked(PTR_TO_CALLNO(ast_channel_tech_pvt(c)), AST_FRAME_IMAGE, img->subclass.integer, 0, img->data.ptr, img->datalen, -1); } static int iax2_sendhtml(struct ast_channel *c, int subclass, const char *data, int datalen) { return send_command_locked(PTR_TO_CALLNO(ast_channel_tech_pvt(c)), AST_FRAME_HTML, subclass, 0, (unsigned char *)data, datalen, -1); } static int iax2_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan) { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(newchan)); ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) iaxs[callno]->owner = newchan; else ast_log(LOG_WARNING, "Uh, this isn't a good sign...\n"); ast_mutex_unlock(&iaxsl[callno]); return 0; } /*! * \note This function calls reg_source_db -> iax2_poke_peer -> find_callno, * so do not call this with a pvt lock held. */ static struct iax2_peer *realtime_peer(const char *peername, struct ast_sockaddr *addr) { struct ast_variable *var = NULL; struct ast_variable *tmp; struct iax2_peer *peer=NULL; time_t regseconds = 0, nowtime; int dynamic=0; char *str_addr, *str_port; str_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); str_port = ast_strdupa(ast_sockaddr_stringify_port(addr)); if (peername) { var = ast_load_realtime("iaxpeers", "name", peername, "host", "dynamic", SENTINEL); if (!var && !ast_sockaddr_isnull(addr)) { var = ast_load_realtime("iaxpeers", "name", peername, "host", str_addr, SENTINEL); } } else if (!ast_sockaddr_isnull(addr)) { var = ast_load_realtime("iaxpeers", "ipaddr", str_addr, "port", str_port, SENTINEL); if (var) { /* We'll need the peer name in order to build the structure! */ for (tmp = var; tmp; tmp = tmp->next) { if (!strcasecmp(tmp->name, "name")) peername = tmp->value; } } } if (!var && peername) { /* Last ditch effort */ var = ast_load_realtime("iaxpeers", "name", peername, SENTINEL); /*!\note * 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 (var && !ast_sockaddr_isnull(addr)) { for (tmp = var; tmp; tmp = tmp->next) { if (!strcasecmp(tmp->name, "host")) { struct ast_sockaddr *hostaddr; if (!ast_sockaddr_resolve(&hostaddr, tmp->value, PARSE_PORT_FORBID, AST_AF_UNSPEC) || ast_sockaddr_cmp_addr(hostaddr, addr)) { /* No match */ ast_variables_destroy(var); var = NULL; } break; } } } } if (!var) return NULL; peer = build_peer(peername, var, NULL, ast_test_flag64((&globalflags), IAX_RTCACHEFRIENDS) ? 0 : 1); if (!peer) { ast_variables_destroy(var); return NULL; } for (tmp = var; tmp; tmp = tmp->next) { /* Make sure it's not a user only... */ if (!strcasecmp(tmp->name, "type")) { if (strcasecmp(tmp->value, "friend") && strcasecmp(tmp->value, "peer")) { /* Whoops, we weren't supposed to exist! */ peer = peer_unref(peer); break; } } else if (!strcasecmp(tmp->name, "regseconds")) { ast_get_time_t(tmp->value, ®seconds, 0, NULL); } else if (!strcasecmp(tmp->name, "ipaddr")) { int setport = ast_sockaddr_port(&peer->addr); if (ast_parse_arg(tmp->value, PARSE_ADDR | PARSE_PORT_FORBID, NULL)) { ast_log(LOG_WARNING, "Failed to parse sockaddr '%s' for ipaddr of realtime peer '%s'\n", tmp->value, tmp->name); } else { ast_sockaddr_parse(&peer->addr, tmp->value, 0); } ast_sockaddr_set_port(&peer->addr, setport); } else if (!strcasecmp(tmp->name, "port")) { int bindport; if (ast_parse_arg(tmp->value, PARSE_UINT32 | PARSE_IN_RANGE, &bindport, 0, 65535)) { bindport = IAX_DEFAULT_PORTNO; } ast_sockaddr_set_port(&peer->addr, bindport); } else if (!strcasecmp(tmp->name, "host")) { if (!strcasecmp(tmp->value, "dynamic")) dynamic = 1; } } ast_variables_destroy(var); if (ast_test_flag64((&globalflags), IAX_RTCACHEFRIENDS)) { ast_copy_flags64(peer, &globalflags, IAX_RTAUTOCLEAR|IAX_RTCACHEFRIENDS); if (ast_test_flag64(peer, IAX_RTAUTOCLEAR)) { if (peer->expire > -1) { if (!AST_SCHED_DEL(sched, peer->expire)) { peer->expire = -1; peer_unref(peer); } } peer->expire = iax2_sched_add(sched, (global_rtautoclear) * 1000, expire_registry, peer_ref(peer)); if (peer->expire == -1) peer_unref(peer); } ao2_link(peers, peer); if (ast_test_flag64(peer, IAX_DYNAMIC)) reg_source_db(peer); } else { ast_set_flag64(peer, IAX_TEMPONLY); } if (!ast_test_flag64(&globalflags, IAX_RTIGNOREREGEXPIRE) && dynamic) { time(&nowtime); if ((nowtime - regseconds) > IAX_DEFAULT_REG_EXPIRE) { memset(&peer->addr, 0, sizeof(peer->addr)); realtime_update_peer(peer->name, &peer->addr, 0); ast_debug(1, "realtime_peer: Bah, '%s' is expired (%d/%d/%d)!\n", peername, (int)(nowtime - regseconds), (int)regseconds, (int)nowtime); } else { ast_debug(1, "realtime_peer: Registration for '%s' still active (%d/%d/%d)!\n", peername, (int)(nowtime - regseconds), (int)regseconds, (int)nowtime); } } return peer; } static struct iax2_user *realtime_user(const char *username, struct ast_sockaddr *addr) { struct ast_variable *var; struct ast_variable *tmp; struct iax2_user *user=NULL; char *str_addr, *str_port; str_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); str_port = ast_strdupa(ast_sockaddr_stringify_port(addr)); var = ast_load_realtime("iaxusers", "name", username, "host", "dynamic", SENTINEL); if (!var) var = ast_load_realtime("iaxusers", "name", username, "host", str_addr, SENTINEL); if (!var && !ast_sockaddr_isnull(addr)) { var = ast_load_realtime("iaxusers", "name", username, "ipaddr", str_addr, "port", str_port, SENTINEL); if (!var) var = ast_load_realtime("iaxusers", "ipaddr", str_addr, "port", str_port, SENTINEL); } if (!var) { /* Last ditch effort */ var = ast_load_realtime("iaxusers", "name", username, SENTINEL); /*!\note * 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 (var) { for (tmp = var; tmp; tmp = tmp->next) { if (!strcasecmp(tmp->name, "host")) { struct ast_sockaddr *hostaddr; if (!ast_sockaddr_resolve(&hostaddr, tmp->value, PARSE_PORT_FORBID, AST_AF_UNSPEC) || ast_sockaddr_cmp_addr(hostaddr, addr)) { /* No match */ ast_variables_destroy(var); var = NULL; } break; } } } } if (!var) return NULL; tmp = var; while(tmp) { /* Make sure it's not a peer only... */ if (!strcasecmp(tmp->name, "type")) { if (strcasecmp(tmp->value, "friend") && strcasecmp(tmp->value, "user")) { return NULL; } } tmp = tmp->next; } user = build_user(username, var, NULL, !ast_test_flag64((&globalflags), IAX_RTCACHEFRIENDS)); ast_variables_destroy(var); if (!user) return NULL; if (ast_test_flag64((&globalflags), IAX_RTCACHEFRIENDS)) { ast_set_flag64(user, IAX_RTCACHEFRIENDS); ao2_link(users, user); } else { ast_set_flag64(user, IAX_TEMPONLY); } return user; } static void realtime_update_peer(const char *peername, struct ast_sockaddr *sockaddr, time_t regtime) { char regseconds[20]; const char *sysname = ast_config_AST_SYSTEM_NAME; char *syslabel = NULL; char *port; if (ast_strlen_zero(sysname)) /* No system name, disable this */ sysname = NULL; else if (ast_test_flag64(&globalflags, IAX_RTSAVE_SYSNAME)) syslabel = "regserver"; snprintf(regseconds, sizeof(regseconds), "%d", (int)regtime); port = ast_strdupa(ast_sockaddr_stringify_port(sockaddr)); ast_update_realtime("iaxpeers", "name", peername, "ipaddr", ast_sockaddr_isnull(sockaddr) ? "" : ast_sockaddr_stringify_addr(sockaddr), "port", ast_sockaddr_isnull(sockaddr) ? "" : port, "regseconds", regseconds, syslabel, sysname, SENTINEL); /* note syslable can be NULL */ } struct create_addr_info { iax2_format capability; uint64_t flags; struct iax2_codec_pref prefs; int maxtime; int encmethods; int found; int sockfd; int adsi; char username[80]; char secret[80]; char outkey[80]; char timezone[80]; char cid_num[80]; char cid_name[80]; char context[AST_MAX_CONTEXT]; char peercontext[AST_MAX_CONTEXT]; char mohinterpret[MAX_MUSICCLASS]; char mohsuggest[MAX_MUSICCLASS]; }; static int create_addr(const char *peername, struct ast_channel *c, struct ast_sockaddr *addr, struct create_addr_info *cai) { struct iax2_peer *peer; int res = -1; ast_clear_flag64(cai, IAX_SENDANI | IAX_TRUNK); cai->sockfd = defaultsockfd; cai->maxtime = 0; if (!(peer = find_peer(peername, 1))) { struct ast_sockaddr peer_addr; peer_addr.ss.ss_family = AST_AF_UNSPEC; cai->found = 0; if (ast_get_ip_or_srv(&peer_addr, peername, srvlookup ? "_iax._udp" : NULL)) { ast_log(LOG_WARNING, "No such host: %s\n", peername); return -1; } if (!ast_sockaddr_port(&peer_addr)) { ast_sockaddr_set_port(&peer_addr, IAX_DEFAULT_PORTNO); } ast_sockaddr_copy(addr, &peer_addr); /* * Use The global iax prefs for unknown peer/user. * However, move the calling channel's native codec to * the top of the preference list. */ cai->prefs = prefs_global; if (c) { int i; for (i = 0; i < ast_format_cap_count(ast_channel_nativeformats(c)); i++) { struct ast_format *format = ast_format_cap_get_format( ast_channel_nativeformats(c), i); iax2_codec_pref_prepend(&cai->prefs, format, ast_format_cap_get_format_framing(ast_channel_nativeformats(c), format), 1); ao2_ref(format, -1); } } return 0; } cai->found = 1; /* if the peer has no address (current or default), return failure */ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { goto return_unref; } /* if the peer is being monitored and is currently unreachable, return failure */ if (peer->maxms && ((peer->lastms > peer->maxms) || (peer->lastms < 0))) goto return_unref; ast_copy_flags64(cai, peer, IAX_SENDANI | IAX_TRUNK | IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_SENDCONNECTEDLINE | IAX_RECVCONNECTEDLINE | IAX_FORCE_ENCRYPT); cai->maxtime = peer->maxms; cai->capability = peer->capability; cai->encmethods = peer->encmethods; cai->sockfd = peer->sockfd; cai->adsi = peer->adsi; cai->prefs = peer->prefs; /* Move the calling channel's native codec to the top of the preference list */ if (c) { int i; for (i = 0; i < ast_format_cap_count(ast_channel_nativeformats(c)); i++) { struct ast_format *tmpfmt = ast_format_cap_get_format( ast_channel_nativeformats(c), i); iax2_codec_pref_prepend(&cai->prefs, tmpfmt, ast_format_cap_get_format_framing(ast_channel_nativeformats(c), tmpfmt), 1); ao2_ref(tmpfmt, -1); } } ast_copy_string(cai->context, peer->context, sizeof(cai->context)); ast_copy_string(cai->peercontext, peer->peercontext, sizeof(cai->peercontext)); ast_copy_string(cai->username, peer->username, sizeof(cai->username)); ast_copy_string(cai->timezone, peer->zonetag, sizeof(cai->timezone)); ast_copy_string(cai->outkey, peer->outkey, sizeof(cai->outkey)); ast_copy_string(cai->cid_num, peer->cid_num, sizeof(cai->cid_num)); ast_copy_string(cai->cid_name, peer->cid_name, sizeof(cai->cid_name)); ast_copy_string(cai->mohinterpret, peer->mohinterpret, sizeof(cai->mohinterpret)); ast_copy_string(cai->mohsuggest, peer->mohsuggest, sizeof(cai->mohsuggest)); if (ast_strlen_zero(peer->dbsecret)) { ast_copy_string(cai->secret, peer->secret, sizeof(cai->secret)); } else { char *family; char *key = NULL; family = ast_strdupa(peer->dbsecret); key = strchr(family, '/'); if (key) *key++ = '\0'; if (!key || ast_db_get(family, key, cai->secret, sizeof(cai->secret))) { ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", peer->dbsecret); goto return_unref; } } if (!ast_sockaddr_isnull(&peer->addr)) { ast_sockaddr_copy(addr, &peer->addr); } else { ast_sockaddr_copy(addr, &peer->defaddr); } res = 0; return_unref: peer_unref(peer); return res; } static void __auto_congest(const void *nothing) { int callno = PTR_TO_CALLNO(nothing); struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_CONGESTION } }; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { iaxs[callno]->initid = -1; iax2_queue_frame(callno, &f); ast_log(LOG_NOTICE, "Auto-congesting call due to slow response\n"); } ast_mutex_unlock(&iaxsl[callno]); } static int auto_congest(const void *data) { #ifdef SCHED_MULTITHREADED if (schedule_action(__auto_congest, data)) #endif __auto_congest(data); return 0; } static unsigned int iax2_datetime(const char *tz) { struct timeval t = ast_tvnow(); struct ast_tm tm; unsigned int tmp; ast_localtime(&t, &tm, ast_strlen_zero(tz) ? NULL : tz); tmp = (tm.tm_sec >> 1) & 0x1f; /* 5 bits of seconds */ tmp |= (tm.tm_min & 0x3f) << 5; /* 6 bits of minutes */ tmp |= (tm.tm_hour & 0x1f) << 11; /* 5 bits of hours */ tmp |= (tm.tm_mday & 0x1f) << 16; /* 5 bits of day of month */ tmp |= ((tm.tm_mon + 1) & 0xf) << 21; /* 4 bits of month */ tmp |= ((tm.tm_year - 100) & 0x7f) << 25; /* 7 bits of year */ return tmp; } struct parsed_dial_string { char *username; char *password; char *key; char *peer; char *port; char *exten; char *context; char *options; }; static int send_apathetic_reply(unsigned short callno, unsigned short dcallno, struct ast_sockaddr *addr, int command, int ts, unsigned char seqno, int sockfd, struct iax_ie_data *ied) { struct { struct ast_iax2_full_hdr f; struct iax_ie_data ied; } data; size_t size = sizeof(struct ast_iax2_full_hdr); if (ied) { size += ied->pos; memcpy(&data.ied, ied->buf, ied->pos); } data.f.scallno = htons(0x8000 | callno); data.f.dcallno = htons(dcallno & ~IAX_FLAG_RETRANS); data.f.ts = htonl(ts); data.f.iseqno = seqno; data.f.oseqno = 0; data.f.type = AST_FRAME_IAX; data.f.csub = compress_subclass(command); iax_outputframe(NULL, &data.f, 0, addr, size - sizeof(struct ast_iax2_full_hdr)); return ast_sendto(sockfd, &data, size, 0, addr); } static void add_empty_calltoken_ie(struct chan_iax2_pvt *pvt, struct iax_ie_data *ied) { /* first make sure their are two empty bytes left in ied->buf */ if (pvt && ied && (2 < ((int) sizeof(ied->buf) - ied->pos))) { ied->buf[ied->pos++] = IAX_IE_CALLTOKEN; /* type */ ied->buf[ied->pos++] = 0; /* data size, ZERO in this case */ pvt->calltoken_ie_len = 2; } } static void resend_with_token(int callno, struct iax_frame *f, const char *newtoken) { struct chan_iax2_pvt *pvt = iaxs[callno]; int frametype = f->af.frametype; int subclass = f->af.subclass.integer; struct { struct ast_iax2_full_hdr fh; struct iax_ie_data ied; } data = { .ied.buf = { 0 }, .ied.pos = 0, }; /* total len - header len gives us the frame's IE len */ int ie_data_pos = f->datalen - sizeof(struct ast_iax2_full_hdr); if (!pvt) { return; /* this should not be possible if called from socket_process() */ } /* * Check to make sure last frame sent is valid for call token resend * 1. Frame should _NOT_ be encrypted since it starts the IAX dialog * 2. Frame should _NOT_ already have a destination callno * 3. Frame must be a valid iax_frame subclass capable of starting dialog * 4. Pvt must have a calltoken_ie_len which represents the number of * bytes at the end of the frame used for the previous calltoken ie. * 5. Pvt's calltoken_ie_len must be _LESS_ than the total IE length * 6. Total length of f->data must be _LESS_ than size of our data struct * because f->data must be able to fit within data. */ if (f->encmethods || f->dcallno || !iax2_allow_new(frametype, subclass, 0) || !pvt->calltoken_ie_len || (pvt->calltoken_ie_len > ie_data_pos) || (f->datalen > sizeof(data))) { return; /* ignore resend, token was not valid for the dialog */ } /* token is valid * 1. Copy frame data over * 2. Redo calltoken IE, it will always be the last ie in the frame. * NOTE: Having the ie always be last is not protocol specified, * it is only an implementation choice. Since we only expect the ie to * be last for frames we have sent, this can no way be affected by * another end point. * 3. Remove frame from queue * 4. Free old frame * 5. Clear previous seqnos * 6. Resend with CALLTOKEN ie. */ /* ---1.--- */ memcpy(&data, f->data, f->datalen); data.ied.pos = ie_data_pos; /* ---2.--- */ /* move to the beginning of the calltoken ie so we can write over it */ data.ied.pos -= pvt->calltoken_ie_len; iax_ie_append_str(&data.ied, IAX_IE_CALLTOKEN, newtoken); /* make sure to update token length incase it ever has to be stripped off again */ pvt->calltoken_ie_len = data.ied.pos - ie_data_pos; /* new pos minus old pos tells how big token ie is */ /* ---3.--- */ AST_LIST_REMOVE(&frame_queue[callno], f, list); /* ---4.--- */ iax2_frame_free(f); /* ---5.--- */ pvt->oseqno = 0; pvt->rseqno = 0; pvt->iseqno = 0; pvt->aseqno = 0; if (pvt->peercallno) { remove_by_peercallno(pvt); pvt->peercallno = 0; } /* ---6.--- */ send_command(pvt, AST_FRAME_IAX, subclass, 0, data.ied.buf, data.ied.pos, -1); } static void requirecalltoken_mark_auto(const char *name, int subclass) { struct iax2_user *user = NULL; struct iax2_peer *peer = NULL; if (ast_strlen_zero(name)) { return; /* no username given */ } if ((subclass == IAX_COMMAND_NEW) && (user = find_user(name)) && (user->calltoken_required == CALLTOKEN_AUTO)) { user->calltoken_required = CALLTOKEN_YES; } else if ((subclass != IAX_COMMAND_NEW) && (peer = find_peer(name, 1)) && (peer->calltoken_required == CALLTOKEN_AUTO)) { peer->calltoken_required = CALLTOKEN_YES; } if (peer) { peer_unref(peer); } if (user) { user_unref(user); } } /*! * \internal * * \brief handles calltoken logic for a received iax_frame. * * \note frametype must be AST_FRAME_IAX. * * \note * Three different cases are possible here. * Case 1. An empty calltoken is provided. This means the client supports * calltokens but has not yet received one from us. In this case * a full calltoken IE is created and sent in a calltoken fullframe. * Case 2. A full calltoken is received and must be checked for validity. * Case 3. No calltoken is received indicating that the client does not * support calltokens. In this case it is up to the configuration * to decide how this should be handled (reject or permit without calltoken) */ static int handle_call_token(struct ast_iax2_full_hdr *fh, struct iax_ies *ies, struct ast_sockaddr *addr, int fd) { #define CALLTOKEN_HASH_FORMAT "%s%u%d" /* address + port + ts + randomcalldata */ #define CALLTOKEN_IE_FORMAT "%u?%s" /* time + ? + (40 char hash) */ struct ast_str *buf = ast_str_alloca(256); time_t t = time(NULL); char hash[41]; /* 40 char sha1 hash */ int subclass = uncompress_subclass(fh->csub); /* ----- Case 1 ----- */ if (ies->calltoken && !ies->calltokendata) { /* empty calltoken is provided, client supports calltokens */ struct iax_ie_data ied = { .buf = { 0 }, .pos = 0, }; /* create the hash with their address data and our timestamp */ ast_str_set(&buf, 0, CALLTOKEN_HASH_FORMAT, ast_sockaddr_stringify(addr), (unsigned int) t, randomcalltokendata); ast_sha1_hash(hash, ast_str_buffer(buf)); ast_str_set(&buf, 0, CALLTOKEN_IE_FORMAT, (unsigned int) t, hash); iax_ie_append_str(&ied, IAX_IE_CALLTOKEN, ast_str_buffer(buf)); send_apathetic_reply(1, ntohs(fh->scallno), addr, IAX_COMMAND_CALLTOKEN, ntohl(fh->ts), fh->iseqno + 1, fd, &ied); return 1; /* ----- Case 2 ----- */ } else if (ies->calltoken && ies->calltokendata) { /* calltoken received, check to see if it is valid */ char *rec_hash = NULL; /* the received hash, make sure it matches with ours. */ char *rec_ts = NULL; /* received timestamp */ unsigned int rec_time; /* received time_t */ /* split the timestamp from the hash data */ rec_hash = strchr((char *) ies->calltokendata, '?'); if (rec_hash) { *rec_hash++ = '\0'; rec_ts = (char *) ies->calltokendata; } /* check that we have valid data before we do any comparisons */ if (!rec_hash || !rec_ts) { goto reject; } else if (sscanf(rec_ts, "%u", &rec_time) != 1) { goto reject; } /* create a hash with their address and the _TOKEN'S_ timestamp */ ast_str_set(&buf, 0, CALLTOKEN_HASH_FORMAT, ast_sockaddr_stringify(addr), (unsigned int) rec_time, randomcalltokendata); ast_sha1_hash(hash, ast_str_buffer(buf)); /* compare hashes and then check timestamp delay */ if (strcmp(hash, rec_hash)) { ast_log(LOG_WARNING, "Address %s failed CallToken hash inspection\n", ast_sockaddr_stringify(addr)); goto reject; /* received hash does not match ours, reject */ } else if ((t < rec_time) || ((t - rec_time) >= MAX_CALLTOKEN_DELAY)) { ast_log(LOG_WARNING, "Too much delay in IAX2 calltoken timestamp from address %s\n", ast_sockaddr_stringify(addr)); goto reject; /* too much delay, reject */ } /* at this point the call token is valid, returning 0 * will allow socket_process to continue as usual */ requirecalltoken_mark_auto(ies->username, subclass); return 0; /* ----- Case 3 ----- */ } else { /* calltokens are not supported for this client, how do we respond? */ if (calltoken_required(addr, ies->username, subclass)) { ast_log(LOG_ERROR, "Call rejected, CallToken Support required. If unexpected, resolve by placing address %s in the calltokenoptional list or setting user %s requirecalltoken=no\n", ast_sockaddr_stringify(addr), S_OR(ies->username, "guest")); goto reject; } return 0; /* calltoken is not required for this addr, so permit it. */ } reject: /* received frame has failed calltoken inspection, send apathetic reject messages */ if (subclass == IAX_COMMAND_REGREQ || subclass == IAX_COMMAND_REGREL) { send_apathetic_reply(1, ntohs(fh->scallno), addr, IAX_COMMAND_REGREJ, ntohl(fh->ts), fh->iseqno + 1, fd, NULL); } else { send_apathetic_reply(1, ntohs(fh->scallno), addr, IAX_COMMAND_REJECT, ntohl(fh->ts), fh->iseqno + 1, fd, NULL); } return 1; } /*! * \brief Parses an IAX dial string into its component parts. * \param data the string to be parsed * \param pds pointer to a \c struct \c parsed_dial_string to be filled in * \return nothing * * This function parses the string and fills the structure * with pointers to its component parts. The input string * will be modified. * * \note This function supports both plaintext passwords and RSA * key names; if the password string is formatted as '[keyname]', * then the keyname will be placed into the key field, and the * password field will be set to NULL. * * \note The dial string format is: * [username[:password]@]peer[:port][/exten[@context]][/options] */ static void parse_dial_string(char *data, struct parsed_dial_string *pds) { if (ast_strlen_zero(data)) return; pds->peer = strsep(&data, "/"); pds->exten = strsep(&data, "/"); pds->options = data; if (pds->exten) { data = pds->exten; pds->exten = strsep(&data, "@"); pds->context = data; } if (strchr(pds->peer, '@')) { data = pds->peer; pds->username = strsep(&data, "@"); pds->peer = data; } if (pds->username) { data = pds->username; pds->username = strsep(&data, ":"); pds->password = data; } data = pds->peer; pds->peer = strsep(&data, ":"); pds->port = data; /* * Check for a key name wrapped in [] in the password position. * If found, move it to the key field instead. */ if (pds->password && (pds->password[0] == '[')) { pds->key = ast_strip_quoted(pds->password, "[", "]"); pds->password = NULL; } } static int iax2_call(struct ast_channel *c, const char *dest, int timeout) { struct ast_sockaddr addr; char *l=NULL, *n=NULL, *tmpstr; struct iax_ie_data ied; char *defaultrdest = "s"; unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); struct parsed_dial_string pds; struct create_addr_info cai; struct ast_var_t *var; struct ast_datastore *variablestore = ast_channel_datastore_find(c, &iax2_variable_datastore_info, NULL); const char* osp_token_ptr; unsigned int osp_token_length; unsigned char osp_block_index; unsigned int osp_block_length; unsigned char osp_buffer[256]; char encoded_prefs[32]; iax2_format iax2_tmpfmt; if ((ast_channel_state(c) != AST_STATE_DOWN) && (ast_channel_state(c) != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "Channel is already in use (%s)?\n", ast_channel_name(c)); return -1; } memset(&cai, 0, sizeof(cai)); cai.encmethods = iax2_encryption; memset(&pds, 0, sizeof(pds)); tmpstr = ast_strdupa(dest); parse_dial_string(tmpstr, &pds); if (ast_strlen_zero(pds.peer)) { ast_log(LOG_WARNING, "No peer provided in the IAX2 dial string '%s'\n", dest); return -1; } if (!pds.exten) { pds.exten = defaultrdest; } if (create_addr(pds.peer, c, &addr, &cai)) { ast_log(LOG_WARNING, "No address associated with '%s'\n", pds.peer); return -1; } if (ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT) && !cai.encmethods) { ast_log(LOG_WARNING, "Encryption forced for call, but not enabled\n"); ast_channel_hangupcause_set(c, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); return -1; } if (ast_strlen_zero(cai.secret) && ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT)) { ast_log(LOG_WARNING, "Call terminated. No secret given and force encrypt enabled\n"); return -1; } if (!pds.username && !ast_strlen_zero(cai.username)) pds.username = cai.username; if (!pds.password && !ast_strlen_zero(cai.secret)) pds.password = cai.secret; if (!pds.key && !ast_strlen_zero(cai.outkey)) pds.key = cai.outkey; if (!pds.context && !ast_strlen_zero(cai.peercontext)) pds.context = cai.peercontext; /* Keep track of the context for outgoing calls too */ ast_channel_context_set(c, cai.context); if (pds.port) { int bindport; if (ast_parse_arg(pds.port, PARSE_UINT32 | PARSE_IN_RANGE, &bindport, 0, 65535)) { ast_sockaddr_set_port(&addr, bindport); } } l = ast_channel_connected(c)->id.number.valid ? ast_channel_connected(c)->id.number.str : NULL; n = ast_channel_connected(c)->id.name.valid ? ast_channel_connected(c)->id.name.str : NULL; /* Now build request */ memset(&ied, 0, sizeof(ied)); /* On new call, first IE MUST be IAX version of caller */ iax_ie_append_short(&ied, IAX_IE_VERSION, IAX_PROTO_VERSION); iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, pds.exten); if (pds.options && strchr(pds.options, 'a')) { /* Request auto answer */ iax_ie_append(&ied, IAX_IE_AUTOANSWER); } /* WARNING: this breaks down at 190 bits! */ iax2_codec_pref_convert(&cai.prefs, encoded_prefs, sizeof(encoded_prefs), 1); iax_ie_append_str(&ied, IAX_IE_CODEC_PREFS, encoded_prefs); if (l) { iax_ie_append_str(&ied, IAX_IE_CALLING_NUMBER, l); iax_ie_append_byte(&ied, IAX_IE_CALLINGPRES, ast_party_id_presentation(&ast_channel_connected(c)->id)); } else if (n) { iax_ie_append_byte(&ied, IAX_IE_CALLINGPRES, ast_party_id_presentation(&ast_channel_connected(c)->id)); } else { iax_ie_append_byte(&ied, IAX_IE_CALLINGPRES, AST_PRES_NUMBER_NOT_AVAILABLE); } iax_ie_append_byte(&ied, IAX_IE_CALLINGTON, ast_channel_connected(c)->id.number.plan); iax_ie_append_short(&ied, IAX_IE_CALLINGTNS, ast_channel_dialed(c)->transit_network_select); if (n) iax_ie_append_str(&ied, IAX_IE_CALLING_NAME, n); if (ast_test_flag64(iaxs[callno], IAX_SENDANI) && ast_channel_connected(c)->ani.number.valid && ast_channel_connected(c)->ani.number.str) { iax_ie_append_str(&ied, IAX_IE_CALLING_ANI, ast_channel_connected(c)->ani.number.str); } if (!ast_strlen_zero(ast_channel_language(c))) iax_ie_append_str(&ied, IAX_IE_LANGUAGE, ast_channel_language(c)); if (!ast_strlen_zero(ast_channel_dialed(c)->number.str)) { iax_ie_append_str(&ied, IAX_IE_DNID, ast_channel_dialed(c)->number.str); } if (ast_channel_redirecting(c)->from.number.valid && !ast_strlen_zero(ast_channel_redirecting(c)->from.number.str)) { iax_ie_append_str(&ied, IAX_IE_RDNIS, ast_channel_redirecting(c)->from.number.str); } if (pds.context) iax_ie_append_str(&ied, IAX_IE_CALLED_CONTEXT, pds.context); if (pds.username) iax_ie_append_str(&ied, IAX_IE_USERNAME, pds.username); if (cai.encmethods) iax_ie_append_short(&ied, IAX_IE_ENCRYPTION, cai.encmethods); ast_mutex_lock(&iaxsl[callno]); if (!ast_strlen_zero(ast_channel_context(c))) ast_string_field_set(iaxs[callno], context, ast_channel_context(c)); if (pds.username) ast_string_field_set(iaxs[callno], username, pds.username); iaxs[callno]->encmethods = cai.encmethods; iaxs[callno]->adsi = cai.adsi; ast_string_field_set(iaxs[callno], mohinterpret, cai.mohinterpret); ast_string_field_set(iaxs[callno], mohsuggest, cai.mohsuggest); if (pds.key) ast_string_field_set(iaxs[callno], outkey, pds.key); if (pds.password) ast_string_field_set(iaxs[callno], secret, pds.password); iax2_tmpfmt = iax2_format_compatibility_cap2bitfield(ast_channel_nativeformats(c)); iax_ie_append_int(&ied, IAX_IE_FORMAT, (int) iax2_tmpfmt); iax_ie_append_versioned_uint64(&ied, IAX_IE_FORMAT2, 0, iax2_tmpfmt); iax_ie_append_int(&ied, IAX_IE_CAPABILITY, (int) iaxs[callno]->capability); iax_ie_append_versioned_uint64(&ied, IAX_IE_CAPABILITY2, 0, iaxs[callno]->capability); iax_ie_append_short(&ied, IAX_IE_ADSICPE, ast_channel_adsicpe(c)); iax_ie_append_int(&ied, IAX_IE_DATETIME, iax2_datetime(cai.timezone)); if (iaxs[callno]->maxtime) { /* Initialize pingtime and auto-congest time */ iaxs[callno]->pingtime = iaxs[callno]->maxtime / 2; iaxs[callno]->initid = iax2_sched_add(sched, iaxs[callno]->maxtime * 2, auto_congest, CALLNO_TO_PTR(callno)); } else if (autokill) { iaxs[callno]->pingtime = autokill / 2; iaxs[callno]->initid = iax2_sched_add(sched, autokill * 2, auto_congest, CALLNO_TO_PTR(callno)); } /* Check if there is an OSP token */ osp_token_ptr = pbx_builtin_getvar_helper(c, "IAX2OSPTOKEN"); if (!ast_strlen_zero(osp_token_ptr)) { if ((osp_token_length = strlen(osp_token_ptr)) <= IAX_MAX_OSPTOKEN_SIZE) { osp_block_index = 0; while (osp_token_length > 0) { osp_block_length = IAX_MAX_OSPBLOCK_SIZE < osp_token_length ? IAX_MAX_OSPBLOCK_SIZE : osp_token_length; osp_buffer[0] = osp_block_index; memcpy(osp_buffer + 1, osp_token_ptr, osp_block_length); iax_ie_append_raw(&ied, IAX_IE_OSPTOKEN, osp_buffer, osp_block_length + 1); osp_block_index++; osp_token_ptr += osp_block_length; osp_token_length -= osp_block_length; } } else ast_log(LOG_WARNING, "OSP token is too long\n"); } else if (iaxdebug) ast_debug(1, "OSP token is undefined\n"); /* send the command using the appropriate socket for this peer */ iaxs[callno]->sockfd = cai.sockfd; /* Add remote vars */ if (variablestore) { AST_LIST_HEAD(, ast_var_t) *variablelist = variablestore->data; ast_debug(1, "Found an IAX variable store on this channel\n"); AST_LIST_LOCK(variablelist); AST_LIST_TRAVERSE(variablelist, var, entries) { char tmp[256]; int i; ast_debug(1, "Found IAXVAR '%s' with value '%s' (to transmit)\n", ast_var_name(var), ast_var_value(var)); /* Automatically divide the value up into sized chunks */ for (i = 0; i < strlen(ast_var_value(var)); i += 255 - (strlen(ast_var_name(var)) + 1)) { snprintf(tmp, sizeof(tmp), "%s=%s", ast_var_name(var), ast_var_value(var) + i); iax_ie_append_str(&ied, IAX_IE_VARIABLE, tmp); } } AST_LIST_UNLOCK(variablelist); } /* Transmit the string in a "NEW" request */ add_empty_calltoken_ie(iaxs[callno], &ied); /* this _MUST_ be the last ie added */ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_NEW, 0, ied.buf, ied.pos, -1); ast_mutex_unlock(&iaxsl[callno]); ast_setstate(c, AST_STATE_RINGING); return 0; } static int iax2_hangup(struct ast_channel *c) { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); struct iax_ie_data ied; int alreadygone; memset(&ied, 0, sizeof(ied)); ast_mutex_lock(&iaxsl[callno]); if (callno && iaxs[callno]) { ast_debug(1, "We're hanging up %s now...\n", ast_channel_name(c)); alreadygone = ast_test_flag64(iaxs[callno], IAX_ALREADYGONE); /* Send the hangup unless we have had a transmission error or are already gone */ iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, (unsigned char)ast_channel_hangupcause(c)); if (!iaxs[callno]->error && !alreadygone) { if (send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_HANGUP, 0, ied.buf, ied.pos, -1)) { ast_log(LOG_WARNING, "No final packet could be sent for callno %d\n", callno); } if (!iaxs[callno]) { ast_mutex_unlock(&iaxsl[callno]); return 0; } } /* Explicitly predestroy it */ iax2_predestroy(callno); /* If we were already gone to begin with, destroy us now */ if (iaxs[callno] && alreadygone) { ast_debug(1, "Really destroying %s now...\n", ast_channel_name(c)); iax2_destroy(callno); } else if (iaxs[callno]) { if (ast_sched_add(sched, 10000, scheduled_destroy, CALLNO_TO_PTR(callno)) < 0) { ast_log(LOG_ERROR, "Unable to schedule iax2 callno %d destruction?!! Destroying immediately.\n", callno); iax2_destroy(callno); } } } else if (ast_channel_tech_pvt(c)) { /* If this call no longer exists, but the channel still * references it we need to set the channel's tech_pvt to null * to avoid ast_channel_free() trying to free it. */ ast_channel_tech_pvt_set(c, NULL); } ast_mutex_unlock(&iaxsl[callno]); ast_verb(3, "Hungup '%s'\n", ast_channel_name(c)); return 0; } /*! * \note expects the pvt to be locked */ static int wait_for_peercallno(struct chan_iax2_pvt *pvt) { unsigned short callno = pvt->callno; if (!pvt->peercallno) { /* We don't know the remote side's call number, yet. :( */ int count = 10; while (count-- && pvt && !pvt->peercallno) { DEADLOCK_AVOIDANCE(&iaxsl[callno]); pvt = iaxs[callno]; } if (!pvt || !pvt->peercallno) { return -1; } } return 0; } static int iax2_setoption(struct ast_channel *c, int option, void *data, int datalen) { struct ast_option_header *h; int res; switch (option) { case AST_OPTION_TXGAIN: case AST_OPTION_RXGAIN: /* these two cannot be sent, because they require a result */ errno = ENOSYS; return -1; case AST_OPTION_OPRMODE: errno = EINVAL; return -1; case AST_OPTION_SECURE_SIGNALING: case AST_OPTION_SECURE_MEDIA: { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); ast_mutex_lock(&iaxsl[callno]); if ((*(int *) data)) { ast_set_flag64(iaxs[callno], IAX_FORCE_ENCRYPT); } else { ast_clear_flag64(iaxs[callno], IAX_FORCE_ENCRYPT); } ast_mutex_unlock(&iaxsl[callno]); return 0; } /* These options are sent to the other side across the network where * they will be passed to whatever channel is bridged there. Don't * do anything silly like pass an option that transmits pointers to * memory on this machine to a remote machine to use */ case AST_OPTION_TONE_VERIFY: case AST_OPTION_TDD: case AST_OPTION_RELAXDTMF: case AST_OPTION_AUDIO_MODE: case AST_OPTION_DIGIT_DETECT: case AST_OPTION_FAX_DETECT: { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); struct chan_iax2_pvt *pvt; ast_mutex_lock(&iaxsl[callno]); pvt = iaxs[callno]; if (wait_for_peercallno(pvt)) { ast_mutex_unlock(&iaxsl[callno]); return -1; } ast_mutex_unlock(&iaxsl[callno]); if (!(h = ast_malloc(datalen + sizeof(*h)))) { return -1; } h->flag = AST_OPTION_FLAG_REQUEST; h->option = htons(option); memcpy(h->data, data, datalen); res = send_command_locked(PTR_TO_CALLNO(ast_channel_tech_pvt(c)), AST_FRAME_CONTROL, AST_CONTROL_OPTION, 0, (unsigned char *) h, datalen + sizeof(*h), -1); ast_free(h); return res; } default: return -1; } /* Just in case someone does a break instead of a return */ return -1; } static int iax2_queryoption(struct ast_channel *c, int option, void *data, int *datalen) { switch (option) { case AST_OPTION_SECURE_SIGNALING: case AST_OPTION_SECURE_MEDIA: { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); ast_mutex_lock(&iaxsl[callno]); *((int *) data) = ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT) ? 1 : 0; ast_mutex_unlock(&iaxsl[callno]); return 0; } default: return -1; } } static struct ast_frame *iax2_read(struct ast_channel *c) { ast_debug(1, "I should never be called!\n"); return &ast_null_frame; } static int iax2_key_rotate(const void *vpvt) { int res = 0; struct chan_iax2_pvt *pvt = (void *) vpvt; struct MD5Context md5; char key[17] = ""; struct iax_ie_data ied = { .pos = 0, }; ast_mutex_lock(&iaxsl[pvt->callno]); pvt->keyrotateid = ast_sched_add(sched, 120000 + (ast_random() % 180001), iax2_key_rotate, vpvt); snprintf(key, sizeof(key), "%lX", (unsigned long)ast_random()); MD5Init(&md5); MD5Update(&md5, (unsigned char *) key, strlen(key)); MD5Final((unsigned char *) key, &md5); IAX_DEBUGDIGEST("Sending", key); iax_ie_append_raw(&ied, IAX_IE_CHALLENGE, key, 16); res = send_command(pvt, AST_FRAME_IAX, IAX_COMMAND_RTKEY, 0, ied.buf, ied.pos, -1); build_ecx_key((unsigned char *) key, pvt); ast_mutex_unlock(&iaxsl[pvt->callno]); return res; } #if defined(IAX2_NATIVE_BRIDGING) static int iax2_start_transfer(unsigned short callno0, unsigned short callno1, int mediaonly) { int res; struct iax_ie_data ied0; struct iax_ie_data ied1; unsigned int transferid = (unsigned int)ast_random(); if (IAX_CALLENCRYPTED(iaxs[callno0]) || IAX_CALLENCRYPTED(iaxs[callno1])) { ast_debug(1, "transfers are not supported for encrypted calls at this time\n"); ast_set_flag64(iaxs[callno0], IAX_NOTRANSFER); ast_set_flag64(iaxs[callno1], IAX_NOTRANSFER); return 0; } memset(&ied0, 0, sizeof(ied0)); iax_ie_append_addr(&ied0, IAX_IE_APPARENT_ADDR, &iaxs[callno1]->addr); iax_ie_append_short(&ied0, IAX_IE_CALLNO, iaxs[callno1]->peercallno); iax_ie_append_int(&ied0, IAX_IE_TRANSFERID, transferid); memset(&ied1, 0, sizeof(ied1)); iax_ie_append_addr(&ied1, IAX_IE_APPARENT_ADDR, &iaxs[callno0]->addr); iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[callno0]->peercallno); iax_ie_append_int(&ied1, IAX_IE_TRANSFERID, transferid); res = send_command(iaxs[callno0], AST_FRAME_IAX, IAX_COMMAND_TXREQ, 0, ied0.buf, ied0.pos, -1); if (res) return -1; res = send_command(iaxs[callno1], AST_FRAME_IAX, IAX_COMMAND_TXREQ, 0, ied1.buf, ied1.pos, -1); if (res) return -1; iaxs[callno0]->transferring = mediaonly ? TRANSFER_MBEGIN : TRANSFER_BEGIN; iaxs[callno1]->transferring = mediaonly ? TRANSFER_MBEGIN : TRANSFER_BEGIN; return 0; } #endif /* defined(IAX2_NATIVE_BRIDGING) */ #if defined(IAX2_NATIVE_BRIDGING) static void lock_both(unsigned short callno0, unsigned short callno1) { ast_mutex_lock(&iaxsl[callno0]); while (ast_mutex_trylock(&iaxsl[callno1])) { DEADLOCK_AVOIDANCE(&iaxsl[callno0]); } } #endif /* defined(IAX2_NATIVE_BRIDGING) */ #if defined(IAX2_NATIVE_BRIDGING) static void unlock_both(unsigned short callno0, unsigned short callno1) { ast_mutex_unlock(&iaxsl[callno1]); ast_mutex_unlock(&iaxsl[callno0]); } #endif /* defined(IAX2_NATIVE_BRIDGING) */ #if defined(IAX2_NATIVE_BRIDGING) static enum ast_bridge_result iax2_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms) { struct ast_channel *cs[3]; struct ast_channel *who, *other; int to = -1; int res = -1; int transferstarted=0; struct ast_frame *f; unsigned short callno0 = PTR_TO_CALLNO(ast_channel_tech_pvt(c0)); unsigned short callno1 = PTR_TO_CALLNO(ast_channel_tech_pvt(c1)); struct timeval waittimer = {0, 0}; /* We currently do not support native bridging if a timeoutms value has been provided */ if (timeoutms > 0) { return AST_BRIDGE_FAILED; } timeoutms = -1; lock_both(callno0, callno1); if (!iaxs[callno0] || !iaxs[callno1]) { unlock_both(callno0, callno1); return AST_BRIDGE_FAILED; } /* Put them in native bridge mode */ if (!(flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1))) { iaxs[callno0]->bridgecallno = callno1; iaxs[callno1]->bridgecallno = callno0; } unlock_both(callno0, callno1); /* If not, try to bridge until we can execute a transfer, if we can */ cs[0] = c0; cs[1] = c1; for (/* ever */;;) { /* Check in case we got masqueraded into */ if ((ast_channel_tech(c0) != &iax2_tech) || (ast_channel_tech(c1) != &iax2_tech)) { ast_verb(3, "Can't masquerade, we're different...\n"); /* Remove from native mode */ if (ast_channel_tech(c0) == &iax2_tech) { ast_mutex_lock(&iaxsl[callno0]); iaxs[callno0]->bridgecallno = 0; ast_mutex_unlock(&iaxsl[callno0]); } if (ast_channel_tech(c1) == &iax2_tech) { ast_mutex_lock(&iaxsl[callno1]); iaxs[callno1]->bridgecallno = 0; ast_mutex_unlock(&iaxsl[callno1]); } return AST_BRIDGE_FAILED_NOWARN; } if (!(ast_format_cap_identical(ast_channel_nativeformats(c0), ast_channel_nativeformats(c1)))) { struct ast_str *c0_buf = ast_str_alloca(64); struct ast_str *c1_buf = ast_str_alloca(64); ast_verb(3, "Operating with different codecs [%s] [%s] , can't native bridge...\n", ast_format_cap_get_names(ast_channel_nativeformats(c0), &c0_buf), ast_format_cap_get_names(ast_channel_nativeformats(c1), &c1_buf)); /* Remove from native mode */ lock_both(callno0, callno1); if (iaxs[callno0]) iaxs[callno0]->bridgecallno = 0; if (iaxs[callno1]) iaxs[callno1]->bridgecallno = 0; unlock_both(callno0, callno1); return AST_BRIDGE_FAILED_NOWARN; } /* check if transferred and if we really want native bridging */ if (!transferstarted && !ast_test_flag64(iaxs[callno0], IAX_NOTRANSFER) && !ast_test_flag64(iaxs[callno1], IAX_NOTRANSFER)) { /* Try the transfer */ if (iax2_start_transfer(callno0, callno1, (flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1)) || ast_test_flag64(iaxs[callno0], IAX_TRANSFERMEDIA) | ast_test_flag64(iaxs[callno1], IAX_TRANSFERMEDIA))) ast_log(LOG_WARNING, "Unable to start the transfer\n"); transferstarted = 1; } if ((iaxs[callno0]->transferring == TRANSFER_RELEASED) && (iaxs[callno1]->transferring == TRANSFER_RELEASED)) { /* Call has been transferred. We're no longer involved */ struct timeval now = ast_tvnow(); if (ast_tvzero(waittimer)) { waittimer = now; } else if (now.tv_sec - waittimer.tv_sec > IAX_LINGER_TIMEOUT) { ast_channel_softhangup_internal_flag_add(c0, AST_SOFTHANGUP_DEV); ast_channel_softhangup_internal_flag_add(c1, AST_SOFTHANGUP_DEV); *fo = NULL; *rc = c0; res = AST_BRIDGE_COMPLETE; break; } } to = 1000; who = ast_waitfor_n(cs, 2, &to); /* XXX This will need to be updated to calculate * timeout correctly once timeoutms is allowed to be * > 0. Right now, this can go badly if the waitfor * times out in less than a millisecond */ if (timeoutms > -1) { timeoutms -= (1000 - to); if (timeoutms < 0) timeoutms = 0; } if (!who) { if (!timeoutms) { res = AST_BRIDGE_RETRY; break; } if (ast_check_hangup(c0) || ast_check_hangup(c1)) { res = AST_BRIDGE_FAILED; break; } continue; } f = ast_read(who); if (!f) { *fo = NULL; *rc = who; res = AST_BRIDGE_COMPLETE; break; } other = (who == c0) ? c1 : c0; /* the 'other' channel */ if (f->frametype == AST_FRAME_CONTROL) { switch (f->subclass.integer) { case AST_CONTROL_VIDUPDATE: case AST_CONTROL_SRCUPDATE: case AST_CONTROL_SRCCHANGE: case AST_CONTROL_T38_PARAMETERS: ast_write(other, f); break; case AST_CONTROL_PVT_CAUSE_CODE: ast_channel_hangupcause_hash_set(other, f->data.ptr, f->datalen); break; default: *fo = f; *rc = who; res = AST_BRIDGE_COMPLETE; break; } if (res == AST_BRIDGE_COMPLETE) { break; } } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_TEXT || f->frametype == AST_FRAME_VIDEO || f->frametype == AST_FRAME_IMAGE) { ast_write(other, f); } else if (f->frametype == AST_FRAME_DTMF) { /* monitored dtmf take out of the bridge. * check if we monitor the specific source. */ int monitored_source = (who == c0) ? AST_BRIDGE_DTMF_CHANNEL_0 : AST_BRIDGE_DTMF_CHANNEL_1; if (flags & monitored_source) { *rc = who; *fo = f; res = AST_BRIDGE_COMPLETE; /* Remove from native mode */ break; } ast_write(other, f); } ast_frfree(f); /* Swap who gets priority */ cs[2] = cs[0]; cs[0] = cs[1]; cs[1] = cs[2]; } lock_both(callno0, callno1); if(iaxs[callno0]) iaxs[callno0]->bridgecallno = 0; if(iaxs[callno1]) iaxs[callno1]->bridgecallno = 0; unlock_both(callno0, callno1); return res; } #endif /* defined(IAX2_NATIVE_BRIDGING) */ static int iax2_answer(struct ast_channel *c) { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); ast_debug(1, "Answering IAX2 call\n"); return send_command_locked(callno, AST_FRAME_CONTROL, AST_CONTROL_ANSWER, 0, NULL, 0, -1); } static int iax2_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen) { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); struct chan_iax2_pvt *pvt; int res = 0; if (iaxdebug) ast_debug(1, "Indicating condition %d\n", condition); ast_mutex_lock(&iaxsl[callno]); pvt = iaxs[callno]; if (wait_for_peercallno(pvt)) { res = -1; goto done; } switch (condition) { case AST_CONTROL_HOLD: if (strcasecmp(pvt->mohinterpret, "passthrough")) { ast_moh_start(c, data, pvt->mohinterpret); goto done; } break; case AST_CONTROL_UNHOLD: if (strcasecmp(pvt->mohinterpret, "passthrough")) { ast_moh_stop(c); goto done; } break; case AST_CONTROL_CONNECTED_LINE: case AST_CONTROL_REDIRECTING: if (!ast_test_flag64(pvt, IAX_SENDCONNECTEDLINE)) { /* We are not configured to allow sending these updates. */ ast_debug(2, "Callno %d: Config blocked sending control frame %d.\n", callno, condition); goto done; } break; case AST_CONTROL_PVT_CAUSE_CODE: case AST_CONTROL_MASQUERADE_NOTIFY: res = -1; goto done; } res = send_command(pvt, AST_FRAME_CONTROL, condition, 0, data, datalen, -1); done: ast_mutex_unlock(&iaxsl[callno]); return res; } static int iax2_transfer(struct ast_channel *c, const char *dest) { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); struct iax_ie_data ied = { "", }; char tmp[256], *context; enum ast_control_transfer message = AST_TRANSFER_SUCCESS; ast_copy_string(tmp, dest, sizeof(tmp)); context = strchr(tmp, '@'); if (context) { *context = '\0'; context++; } iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, tmp); if (context) iax_ie_append_str(&ied, IAX_IE_CALLED_CONTEXT, context); ast_debug(1, "Transferring '%s' to '%s'\n", ast_channel_name(c), dest); ast_queue_control_data(c, AST_CONTROL_TRANSFER, &message, sizeof(message)); return send_command_locked(callno, AST_FRAME_IAX, IAX_COMMAND_TRANSFER, 0, ied.buf, ied.pos, -1); } static int iax2_getpeertrunk(struct ast_sockaddr addr) { struct iax2_peer *peer; int res = 0; struct ao2_iterator i; i = ao2_iterator_init(peers, 0); while ((peer = ao2_iterator_next(&i))) { if (!ast_sockaddr_cmp(&peer->addr, &addr)) { res = ast_test_flag64(peer, IAX_TRUNK); peer_unref(peer); break; } peer_unref(peer); } ao2_iterator_destroy(&i); return res; } /*! \brief Create new call, interface with the PBX core */ static struct ast_channel *ast_iax2_new(int callno, int state, iax2_format capability, struct iax2_codec_pref *prefs, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, unsigned int cachable) { struct ast_channel *tmp = NULL; struct chan_iax2_pvt *i; struct iax2_peer *peer; struct ast_variable *v = NULL; struct ast_format_cap *native; struct ast_format *tmpfmt; struct ast_callid *callid; char *peer_name = NULL; if (!(i = iaxs[callno])) { ast_log(LOG_WARNING, "No IAX2 pvt found for callno '%d' !\n", callno); return NULL; } if (!capability) { ast_log(LOG_WARNING, "No formats specified for call to: IAX2/%s-%d\n", i->host, i->callno); return NULL; } native = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!native) { return NULL; } if (iax2_codec_pref_best_bitfield2cap(capability, prefs, native) || !ast_format_cap_count(native)) { ast_log(LOG_WARNING, "No requested formats available for call to: IAX2/%s-%d\n", i->host, i->callno); ao2_ref(native, -1); return NULL; } if (!ast_strlen_zero(i->peer)) { peer_name = ast_strdupa(i->peer); } else if (!ast_strlen_zero(i->host)) { peer_name = ast_strdupa(i->host); } /* Don't hold call lock while making a channel or looking up a peer */ ast_mutex_unlock(&iaxsl[callno]); if (!ast_strlen_zero(peer_name)) { peer = find_peer(peer_name, 1); if (peer && peer->endpoint) { tmp = ast_channel_alloc_with_endpoint(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, peer->endpoint, "IAX2/%s-%d", i->host, i->callno); } ao2_cleanup(peer); } if (!tmp) { tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "IAX2/%s-%d", i->host, i->callno); } ast_mutex_lock(&iaxsl[callno]); if (i != iaxs[callno]) { if (tmp) { /* unlock and relock iaxsl[callno] to preserve locking order */ ast_mutex_unlock(&iaxsl[callno]); ast_channel_unlock(tmp); tmp = ast_channel_release(tmp); ast_mutex_lock(&iaxsl[callno]); } ao2_ref(native, -1); return NULL; } if (!tmp) { ao2_ref(native, -1); return NULL; } ast_channel_stage_snapshot(tmp); if ((callid = iaxs[callno]->callid)) { ast_channel_callid_set(tmp, callid); } ast_channel_tech_set(tmp, &iax2_tech); /* We can support any format by default, until we get restricted */ ast_channel_nativeformats_set(tmp, native); tmpfmt = ast_format_cap_get_format(native, 0); ast_channel_set_readformat(tmp, tmpfmt); ast_channel_set_rawreadformat(tmp, tmpfmt); ast_channel_set_writeformat(tmp, tmpfmt); ast_channel_set_rawwriteformat(tmp, tmpfmt); ao2_ref(tmpfmt, -1); ao2_ref(native, -1); ast_channel_tech_pvt_set(tmp, CALLNO_TO_PTR(i->callno)); if (!ast_strlen_zero(i->parkinglot)) ast_channel_parkinglot_set(tmp, i->parkinglot); /* Don't use ast_set_callerid() here because it will * generate a NewCallerID event before the NewChannel event */ if (!ast_strlen_zero(i->ani)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->ani); } else if (!ast_strlen_zero(i->cid_num)) { ast_channel_caller(tmp)->ani.number.valid = 1; ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num); } ast_channel_dialed(tmp)->number.str = ast_strdup(i->dnid); if (!ast_strlen_zero(i->rdnis)) { ast_channel_redirecting(tmp)->from.number.valid = 1; ast_channel_redirecting(tmp)->from.number.str = ast_strdup(i->rdnis); } ast_channel_caller(tmp)->id.name.presentation = i->calling_pres; ast_channel_caller(tmp)->id.number.presentation = i->calling_pres; ast_channel_caller(tmp)->id.number.plan = i->calling_ton; ast_channel_dialed(tmp)->transit_network_select = i->calling_tns; if (!ast_strlen_zero(i->language)) ast_channel_language_set(tmp, i->language); if (!ast_strlen_zero(i->accountcode)) ast_channel_accountcode_set(tmp, i->accountcode); if (i->amaflags) ast_channel_amaflags_set(tmp, i->amaflags); ast_channel_context_set(tmp, i->context); ast_channel_exten_set(tmp, i->exten); if (i->adsi) ast_channel_adsicpe_set(tmp, i->peeradsicpe); else ast_channel_adsicpe_set(tmp, AST_ADSI_UNAVAILABLE); i->owner = tmp; i->capability = capability; if (!cachable) { ast_set_flag(ast_channel_flags(tmp), AST_FLAG_DISABLE_DEVSTATE_CACHE); } /* Set inherited variables */ if (i->vars) { for (v = i->vars ; v ; v = v->next) pbx_builtin_setvar_helper(tmp, v->name, v->value); } if (i->iaxvars) { struct ast_datastore *variablestore; struct ast_variable *var, *prev = NULL; AST_LIST_HEAD(, ast_var_t) *varlist; ast_debug(1, "Loading up the channel with IAXVARs\n"); varlist = ast_calloc(1, sizeof(*varlist)); variablestore = ast_datastore_alloc(&iax2_variable_datastore_info, NULL); if (variablestore && varlist) { variablestore->data = varlist; variablestore->inheritance = DATASTORE_INHERIT_FOREVER; AST_LIST_HEAD_INIT(varlist); for (var = i->iaxvars; var; var = var->next) { struct ast_var_t *newvar = ast_var_assign(var->name, var->value); if (prev) ast_free(prev); prev = var; if (!newvar) { /* Don't abort list traversal, as this would leave i->iaxvars in an inconsistent state. */ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); } else { AST_LIST_INSERT_TAIL(varlist, newvar, entries); } } if (prev) ast_free(prev); i->iaxvars = NULL; ast_channel_datastore_add(i->owner, variablestore); } else { if (variablestore) { ast_datastore_free(variablestore); } if (varlist) { ast_free(varlist); } } } ast_channel_stage_snapshot_done(tmp); ast_channel_unlock(tmp); if (state != AST_STATE_DOWN) { if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp)); /* unlock and relock iaxsl[callno] to preserve locking order */ ast_mutex_unlock(&iaxsl[callno]); ast_hangup(tmp); ast_mutex_lock(&iaxsl[callno]); return NULL; } } ast_module_ref(ast_module_info->self); return tmp; } static unsigned int calc_txpeerstamp(struct iax2_trunk_peer *tpeer, int sampms, struct timeval *now) { unsigned long int mssincetx; /* unsigned to handle overflows */ long int ms, pred; tpeer->trunkact = *now; mssincetx = ast_tvdiff_ms(*now, tpeer->lasttxtime); if (mssincetx > 5000 || ast_tvzero(tpeer->txtrunktime)) { /* If it's been at least 5 seconds since the last time we transmitted on this trunk, reset our timers */ tpeer->txtrunktime = *now; tpeer->lastsent = 999999; } /* Update last transmit time now */ tpeer->lasttxtime = *now; /* Calculate ms offset */ ms = ast_tvdiff_ms(*now, tpeer->txtrunktime); /* Predict from last value */ pred = tpeer->lastsent + sampms; if (abs(ms - pred) < MAX_TIMESTAMP_SKEW) ms = pred; /* We never send the same timestamp twice, so fudge a little if we must */ if (ms == tpeer->lastsent) ms = tpeer->lastsent + 1; tpeer->lastsent = ms; return ms; } static unsigned int fix_peerts(struct timeval *rxtrunktime, int callno, unsigned int ts) { long ms; /* NOT unsigned */ if (ast_tvzero(iaxs[callno]->rxcore)) { /* Initialize rxcore time if appropriate */ iaxs[callno]->rxcore = ast_tvnow(); /* Round to nearest 20ms so traces look pretty */ iaxs[callno]->rxcore.tv_usec -= iaxs[callno]->rxcore.tv_usec % 20000; } /* Calculate difference between trunk and channel */ ms = ast_tvdiff_ms(*rxtrunktime, iaxs[callno]->rxcore); /* Return as the sum of trunk time and the difference between trunk and real time */ return ms + ts; } static unsigned int calc_timestamp(struct chan_iax2_pvt *p, unsigned int ts, struct ast_frame *f) { int ms; int voice = 0; int genuine = 0; int adjust; int rate = 0; struct timeval *delivery = NULL; /* What sort of frame do we have?: voice is self-explanatory "genuine" means an IAX frame - things like LAGRQ/RP, PING/PONG, ACK non-genuine frames are CONTROL frames [ringing etc], DTMF The "genuine" distinction is needed because genuine frames must get a clock-based timestamp, the others need a timestamp slaved to the voice frames so that they go in sequence */ if (f->frametype == AST_FRAME_VOICE) { voice = 1; rate = ast_format_get_sample_rate(f->subclass.format) / 1000; delivery = &f->delivery; } else if (f->frametype == AST_FRAME_IAX) { genuine = 1; } else if (f->frametype == AST_FRAME_CNG) { p->notsilenttx = 0; } if (ast_tvzero(p->offset)) { p->offset = ast_tvnow(); /* Round to nearest 20ms for nice looking traces */ p->offset.tv_usec -= p->offset.tv_usec % 20000; } /* If the timestamp is specified, just send it as is */ if (ts) return ts; /* If we have a time that the frame arrived, always use it to make our timestamp */ if (delivery && !ast_tvzero(*delivery)) { ms = ast_tvdiff_ms(*delivery, p->offset); if (ms < 0) { ms = 0; } if (iaxdebug) ast_debug(3, "calc_timestamp: call %d/%d: Timestamp slaved to delivery time\n", p->callno, iaxs[p->callno]->peercallno); } else { ms = ast_tvdiff_ms(ast_tvnow(), p->offset); if (ms < 0) ms = 0; if (voice) { /* On a voice frame, use predicted values if appropriate */ if (p->notsilenttx && abs(ms - p->nextpred) <= MAX_TIMESTAMP_SKEW) { /* Adjust our txcore, keeping voice and non-voice synchronized */ /* AN EXPLANATION: When we send voice, we usually send "calculated" timestamps worked out on the basis of the number of samples sent. When we send other frames, we usually send timestamps worked out from the real clock. The problem is that they can tend to drift out of step because the source channel's clock and our clock may not be exactly at the same rate. We fix this by continuously "tweaking" p->offset. p->offset is "time zero" for this call. Moving it adjusts timestamps for non-voice frames. We make the adjustment in the style of a moving average. Each time we adjust p->offset by 10% of the difference between our clock-derived timestamp and the predicted timestamp. That's why you see "10000" below even though IAX2 timestamps are in milliseconds. The use of a moving average avoids offset moving too radically. Generally, "adjust" roams back and forth around 0, with offset hardly changing at all. But if a consistent different starts to develop it will be eliminated over the course of 10 frames (200-300msecs) */ adjust = (ms - p->nextpred); if (adjust < 0) p->offset = ast_tvsub(p->offset, ast_samp2tv(abs(adjust), 10000)); else if (adjust > 0) p->offset = ast_tvadd(p->offset, ast_samp2tv(adjust, 10000)); if (!p->nextpred) { p->nextpred = ms; /*f->samples / rate;*/ if (p->nextpred <= p->lastsent) p->nextpred = p->lastsent + 3; } ms = p->nextpred; } else { /* in this case, just use the actual * time, since we're either way off * (shouldn't happen), or we're ending a * silent period -- and seed the next * predicted time. Also, round ms to the * next multiple of frame size (so our * silent periods are multiples of * frame size too) */ if (iaxdebug && abs(ms - p->nextpred) > MAX_TIMESTAMP_SKEW ) ast_debug(1, "predicted timestamp skew (%d) > max (%d), using real ts instead.\n", abs(ms - p->nextpred), MAX_TIMESTAMP_SKEW); if (f->samples >= rate) /* check to make sure we don't core dump */ { int diff = ms % (f->samples / rate); if (diff) ms += f->samples/rate - diff; } p->nextpred = ms; p->notsilenttx = 1; } } else if ( f->frametype == AST_FRAME_VIDEO ) { /* * IAX2 draft 03 says that timestamps MUST be in order. * It does not say anything about several frames having the same timestamp * When transporting video, we can have a frame that spans multiple iax packets * (so called slices), so it would make sense to use the same timestamp for all of * them * We do want to make sure that frames don't go backwards though */ if ( (unsigned int)ms < p->lastsent ) ms = p->lastsent; } else { /* On a dataframe, use last value + 3 (to accomodate jitter buffer shrinking) if appropriate unless it's a genuine frame */ if (genuine) { /* genuine (IAX LAGRQ etc) must keep their clock-based stamps */ if (ms <= p->lastsent) ms = p->lastsent + 3; } else if (abs(ms - p->lastsent) <= MAX_TIMESTAMP_SKEW) { /* non-genuine frames (!?) (DTMF, CONTROL) should be pulled into the predicted stream stamps */ ms = p->lastsent + 3; } } } p->lastsent = ms; if (voice) { p->nextpred = p->nextpred + f->samples / rate; } return ms; } static unsigned int calc_rxstamp(struct chan_iax2_pvt *p, unsigned int offset) { /* Returns where in "receive time" we are. That is, how many ms since we received (or would have received) the frame with timestamp 0 */ int ms; #ifdef IAXTESTS int jit; #endif /* IAXTESTS */ /* Setup rxcore if necessary */ if (ast_tvzero(p->rxcore)) { p->rxcore = ast_tvnow(); if (iaxdebug) ast_debug(1, "calc_rxstamp: call=%d: rxcore set to %d.%6.6d - %ums\n", p->callno, (int)(p->rxcore.tv_sec), (int)(p->rxcore.tv_usec), offset); p->rxcore = ast_tvsub(p->rxcore, ast_samp2tv(offset, 1000)); #if 1 if (iaxdebug) ast_debug(1, "calc_rxstamp: call=%d: works out as %d.%6.6d\n", p->callno, (int)(p->rxcore.tv_sec),(int)( p->rxcore.tv_usec)); #endif } ms = ast_tvdiff_ms(ast_tvnow(), p->rxcore); #ifdef IAXTESTS if (test_jit) { if (!test_jitpct || ((100.0 * ast_random() / (RAND_MAX + 1.0)) < test_jitpct)) { jit = (int)((float)test_jit * ast_random() / (RAND_MAX + 1.0)); if ((int)(2.0 * ast_random() / (RAND_MAX + 1.0))) jit = -jit; ms += jit; } } if (test_late) { ms += test_late; test_late = 0; } #endif /* IAXTESTS */ return ms; } static struct iax2_trunk_peer *find_tpeer(struct ast_sockaddr *addr, int fd) { struct iax2_trunk_peer *tpeer = NULL; /* Finds and locks trunk peer */ AST_LIST_LOCK(&tpeers); AST_LIST_TRAVERSE(&tpeers, tpeer, list) { if (!ast_sockaddr_cmp(&tpeer->addr, addr)) { ast_mutex_lock(&tpeer->lock); break; } } if (!tpeer) { if ((tpeer = ast_calloc(1, sizeof(*tpeer)))) { ast_mutex_init(&tpeer->lock); tpeer->lastsent = 9999; ast_sockaddr_copy(&tpeer->addr, addr); tpeer->trunkact = ast_tvnow(); ast_mutex_lock(&tpeer->lock); tpeer->sockfd = fd; #ifdef SO_NO_CHECK setsockopt(tpeer->sockfd, SOL_SOCKET, SO_NO_CHECK, &nochecksums, sizeof(nochecksums)); #endif ast_debug(1, "Created trunk peer for '%s'\n", ast_sockaddr_stringify(&tpeer->addr)); AST_LIST_INSERT_TAIL(&tpeers, tpeer, list); } } AST_LIST_UNLOCK(&tpeers); return tpeer; } static int iax2_trunk_queue(struct chan_iax2_pvt *pvt, struct iax_frame *fr) { struct ast_frame *f; struct iax2_trunk_peer *tpeer; void *tmp, *ptr; struct timeval now; struct ast_iax2_meta_trunk_entry *met; struct ast_iax2_meta_trunk_mini *mtm; f = &fr->af; tpeer = find_tpeer(&pvt->addr, pvt->sockfd); if (tpeer) { if (tpeer->trunkdatalen + f->datalen + 4 >= tpeer->trunkdataalloc) { /* Need to reallocate space */ if (tpeer->trunkdataalloc < trunkmaxsize) { if (!(tmp = ast_realloc(tpeer->trunkdata, tpeer->trunkdataalloc + DEFAULT_TRUNKDATA + IAX2_TRUNK_PREFACE))) { ast_mutex_unlock(&tpeer->lock); return -1; } tpeer->trunkdataalloc += DEFAULT_TRUNKDATA; tpeer->trunkdata = tmp; ast_debug(1, "Expanded trunk '%s' to %u bytes\n", ast_sockaddr_stringify(&tpeer->addr), tpeer->trunkdataalloc); } else { ast_log(LOG_WARNING, "Maximum trunk data space exceeded to %s\n", ast_sockaddr_stringify(&tpeer->addr)); ast_mutex_unlock(&tpeer->lock); return -1; } } /* Append to meta frame */ ptr = tpeer->trunkdata + IAX2_TRUNK_PREFACE + tpeer->trunkdatalen; if (ast_test_flag64(&globalflags, IAX_TRUNKTIMESTAMPS)) { mtm = (struct ast_iax2_meta_trunk_mini *)ptr; mtm->len = htons(f->datalen); mtm->mini.callno = htons(pvt->callno); mtm->mini.ts = htons(0xffff & fr->ts); ptr += sizeof(struct ast_iax2_meta_trunk_mini); tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_mini); } else { met = (struct ast_iax2_meta_trunk_entry *)ptr; /* Store call number and length in meta header */ met->callno = htons(pvt->callno); met->len = htons(f->datalen); /* Advance pointers/decrease length past trunk entry header */ ptr += sizeof(struct ast_iax2_meta_trunk_entry); tpeer->trunkdatalen += sizeof(struct ast_iax2_meta_trunk_entry); } /* Copy actual trunk data */ memcpy(ptr, f->data.ptr, f->datalen); tpeer->trunkdatalen += f->datalen; tpeer->calls++; /* track the largest mtu we actually have sent */ if (tpeer->trunkdatalen + f->datalen + 4 > trunk_maxmtu) trunk_maxmtu = tpeer->trunkdatalen + f->datalen + 4 ; /* if we have enough for a full MTU, ship it now without waiting */ if (global_max_trunk_mtu > 0 && tpeer->trunkdatalen + f->datalen + 4 >= global_max_trunk_mtu) { now = ast_tvnow(); send_trunk(tpeer, &now); trunk_untimed ++; } ast_mutex_unlock(&tpeer->lock); } return 0; } /* IAX2 encryption requires 16 to 32 bytes of random padding to be present * before the encryption data. This function randomizes that data. */ static void build_rand_pad(unsigned char *buf, ssize_t len) { long tmp; for (tmp = ast_random(); len > 0; tmp = ast_random()) { memcpy(buf, (unsigned char *) &tmp, (len > sizeof(tmp)) ? sizeof(tmp) : len); buf += sizeof(tmp); len -= sizeof(tmp); } } static void build_encryption_keys(const unsigned char *digest, struct chan_iax2_pvt *pvt) { build_ecx_key(digest, pvt); ast_aes_set_decrypt_key(digest, &pvt->dcx); } static void build_ecx_key(const unsigned char *digest, struct chan_iax2_pvt *pvt) { /* it is required to hold the corresponding decrypt key to our encrypt key * in the pvt struct because queued frames occasionally need to be decrypted and * re-encrypted when updated for a retransmission */ build_rand_pad(pvt->semirand, sizeof(pvt->semirand)); ast_aes_set_encrypt_key(digest, &pvt->ecx); ast_aes_set_decrypt_key(digest, &pvt->mydcx); } static void memcpy_decrypt(unsigned char *dst, const unsigned char *src, int len, ast_aes_decrypt_key *dcx) { #if 0 /* Debug with "fake encryption" */ int x; if (len % 16) ast_log(LOG_WARNING, "len should be multiple of 16, not %d!\n", len); for (x=0;x 0) { ast_aes_decrypt(src, dst, dcx); for (x=0;x<16;x++) dst[x] ^= lastblock[x]; memcpy(lastblock, src, sizeof(lastblock)); dst += 16; src += 16; len -= 16; } #endif } static void memcpy_encrypt(unsigned char *dst, const unsigned char *src, int len, ast_aes_encrypt_key *ecx) { #if 0 /* Debug with "fake encryption" */ int x; if (len % 16) ast_log(LOG_WARNING, "len should be multiple of 16, not %d!\n", len); for (x=0;x 0) { for (x=0;x<16;x++) curblock[x] ^= src[x]; ast_aes_encrypt(curblock, dst, ecx); memcpy(curblock, dst, sizeof(curblock)); dst += 16; src += 16; len -= 16; } #endif } static int decode_frame(ast_aes_decrypt_key *dcx, struct ast_iax2_full_hdr *fh, struct ast_frame *f, int *datalen) { int padding; unsigned char *workspace; workspace = ast_alloca(*datalen); memset(f, 0, sizeof(*f)); if (ntohs(fh->scallno) & IAX_FLAG_FULL) { struct ast_iax2_full_enc_hdr *efh = (struct ast_iax2_full_enc_hdr *)fh; if (*datalen < 16 + sizeof(struct ast_iax2_full_hdr)) return -1; /* Decrypt */ memcpy_decrypt(workspace, efh->encdata, *datalen - sizeof(struct ast_iax2_full_enc_hdr), dcx); padding = 16 + (workspace[15] & 0x0f); if (iaxdebug) ast_debug(1, "Decoding full frame with length %d (padding = %d) (15=%02x)\n", *datalen, padding, (unsigned)workspace[15]); if (*datalen < padding + sizeof(struct ast_iax2_full_hdr)) return -1; *datalen -= padding; memcpy(efh->encdata, workspace + padding, *datalen - sizeof(struct ast_iax2_full_enc_hdr)); f->frametype = fh->type; if (f->frametype == AST_FRAME_VIDEO) { f->subclass.format = ast_format_compatibility_bitfield2format(uncompress_subclass(fh->csub & ~0x40) | ((fh->csub >> 6) & 0x1)); } else if (f->frametype == AST_FRAME_VOICE) { f->subclass.format = ast_format_compatibility_bitfield2format(uncompress_subclass(fh->csub)); } else { f->subclass.integer = uncompress_subclass(fh->csub); } } else { struct ast_iax2_mini_enc_hdr *efh = (struct ast_iax2_mini_enc_hdr *)fh; if (iaxdebug) ast_debug(1, "Decoding mini with length %d\n", *datalen); if (*datalen < 16 + sizeof(struct ast_iax2_mini_hdr)) return -1; /* Decrypt */ memcpy_decrypt(workspace, efh->encdata, *datalen - sizeof(struct ast_iax2_mini_enc_hdr), dcx); padding = 16 + (workspace[15] & 0x0f); if (*datalen < padding + sizeof(struct ast_iax2_mini_hdr)) return -1; *datalen -= padding; memcpy(efh->encdata, workspace + padding, *datalen - sizeof(struct ast_iax2_mini_enc_hdr)); } return 0; } static int encrypt_frame(ast_aes_encrypt_key *ecx, struct ast_iax2_full_hdr *fh, unsigned char *poo, int *datalen) { int padding; unsigned char *workspace; workspace = ast_alloca(*datalen + 32); if (ntohs(fh->scallno) & IAX_FLAG_FULL) { struct ast_iax2_full_enc_hdr *efh = (struct ast_iax2_full_enc_hdr *)fh; if (iaxdebug) ast_debug(1, "Encoding full frame %d/%d with length %d\n", fh->type, fh->csub, *datalen); padding = 16 - ((*datalen - sizeof(struct ast_iax2_full_enc_hdr)) % 16); padding = 16 + (padding & 0xf); memcpy(workspace, poo, padding); memcpy(workspace + padding, efh->encdata, *datalen - sizeof(struct ast_iax2_full_enc_hdr)); workspace[15] &= 0xf0; workspace[15] |= (padding & 0xf); if (iaxdebug) ast_debug(1, "Encoding full frame %d/%d with length %d + %d padding (15=%02x)\n", fh->type, fh->csub, *datalen, padding, (unsigned)workspace[15]); *datalen += padding; memcpy_encrypt(efh->encdata, workspace, *datalen - sizeof(struct ast_iax2_full_enc_hdr), ecx); if (*datalen >= 32 + sizeof(struct ast_iax2_full_enc_hdr)) memcpy(poo, workspace + *datalen - 32, 32); } else { struct ast_iax2_mini_enc_hdr *efh = (struct ast_iax2_mini_enc_hdr *)fh; if (iaxdebug) ast_debug(1, "Encoding mini frame with length %d\n", *datalen); padding = 16 - ((*datalen - sizeof(struct ast_iax2_mini_enc_hdr)) % 16); padding = 16 + (padding & 0xf); memcpy(workspace, poo, padding); memcpy(workspace + padding, efh->encdata, *datalen - sizeof(struct ast_iax2_mini_enc_hdr)); workspace[15] &= 0xf0; workspace[15] |= (padding & 0x0f); *datalen += padding; memcpy_encrypt(efh->encdata, workspace, *datalen - sizeof(struct ast_iax2_mini_enc_hdr), ecx); if (*datalen >= 32 + sizeof(struct ast_iax2_mini_enc_hdr)) memcpy(poo, workspace + *datalen - 32, 32); } return 0; } static int decrypt_frame(int callno, struct ast_iax2_full_hdr *fh, struct ast_frame *f, int *datalen) { int res=-1; if (!ast_test_flag64(iaxs[callno], IAX_KEYPOPULATED)) { /* Search for possible keys, given secrets */ struct MD5Context md5; unsigned char digest[16]; char *tmppw, *stringp; tmppw = ast_strdupa(iaxs[callno]->secret); stringp = tmppw; while ((tmppw = strsep(&stringp, ";"))) { MD5Init(&md5); MD5Update(&md5, (unsigned char *)iaxs[callno]->challenge, strlen(iaxs[callno]->challenge)); MD5Update(&md5, (unsigned char *)tmppw, strlen(tmppw)); MD5Final(digest, &md5); build_encryption_keys(digest, iaxs[callno]); res = decode_frame(&iaxs[callno]->dcx, fh, f, datalen); if (!res) { ast_set_flag64(iaxs[callno], IAX_KEYPOPULATED); break; } } } else res = decode_frame(&iaxs[callno]->dcx, fh, f, datalen); return res; } static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final) { /* Queue a packet for delivery on a given private structure. Use "ts" for timestamp, or calculate if ts is 0. Send immediately without retransmission or delayed, with retransmission */ struct ast_iax2_full_hdr *fh; struct ast_iax2_mini_hdr *mh; struct ast_iax2_video_hdr *vh; struct { struct iax_frame fr2; unsigned char buffer[4096]; } frb; struct iax_frame *fr; int res; int sendmini=0; unsigned int lastsent; unsigned int fts; frb.fr2.afdatalen = sizeof(frb.buffer); if (!pvt) { ast_log(LOG_WARNING, "No private structure for packet?\n"); return -1; } lastsent = pvt->lastsent; /* Calculate actual timestamp */ fts = calc_timestamp(pvt, ts, f); /* Bail here if this is an "interp" frame; we don't want or need to send these placeholders out * (the endpoint should detect the lost packet itself). But, we want to do this here, so that we * increment the "predicted timestamps" for voice, if we're predicting */ if(f->frametype == AST_FRAME_VOICE && f->datalen == 0) return 0; #if 0 ast_log(LOG_NOTICE, "f->frametype %c= AST_FRAME_VOICE, %sencrypted, %srotation scheduled...\n", *("=!" + (f->frametype == AST_FRAME_VOICE)), IAX_CALLENCRYPTED(pvt) ? "" : "not ", pvt->keyrotateid != -1 ? "" : "no " ); #endif if (pvt->keyrotateid == -1 && f->frametype == AST_FRAME_VOICE && IAX_CALLENCRYPTED(pvt)) { iax2_key_rotate(pvt); } if ((ast_test_flag64(pvt, IAX_TRUNK) || (((fts & 0xFFFF0000L) == (lastsent & 0xFFFF0000L)) || ((fts & 0xFFFF0000L) == ((lastsent + 0x10000) & 0xFFFF0000L)))) /* High two bytes are the same on timestamp, or sending on a trunk */ && (f->frametype == AST_FRAME_VOICE) /* is a voice frame */ && (ast_format_cmp(f->subclass.format, ast_format_compatibility_bitfield2format(pvt->svoiceformat)) == AST_FORMAT_CMP_EQUAL) /* is the same type */ ) { /* Force immediate rather than delayed transmission */ now = 1; /* Mark that mini-style frame is appropriate */ sendmini = 1; } if ( f->frametype == AST_FRAME_VIDEO ) { /* * If the lower 15 bits of the timestamp roll over, or if * the video format changed then send a full frame. * Otherwise send a mini video frame */ if (((fts & 0xFFFF8000L) == (pvt->lastvsent & 0xFFFF8000L)) && (ast_format_cmp(f->subclass.format, ast_format_compatibility_bitfield2format(pvt->svideoformat)) == AST_FORMAT_CMP_EQUAL) ) { now = 1; sendmini = 1; } else { now = 0; sendmini = 0; } pvt->lastvsent = fts; } if (f->frametype == AST_FRAME_IAX) { /* 0x8000 marks this message as TX:, this bit will be stripped later */ pvt->last_iax_message = f->subclass.integer | MARK_IAX_SUBCLASS_TX; if (!pvt->first_iax_message) { pvt->first_iax_message = pvt->last_iax_message; } } /* Allocate an iax_frame */ if (now) { fr = &frb.fr2; } else fr = iax_frame_new(DIRECTION_OUTGRESS, ast_test_flag64(pvt, IAX_ENCRYPTED) ? f->datalen + 32 : f->datalen, (f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_VIDEO)); if (!fr) { ast_log(LOG_WARNING, "Out of memory\n"); return -1; } /* Copy our prospective frame into our immediate or retransmitted wrapper */ iax_frame_wrap(fr, f); fr->ts = fts; fr->callno = pvt->callno; fr->transfer = transfer; fr->final = final; fr->encmethods = 0; if (!sendmini) { /* We need a full frame */ if (seqno > -1) fr->oseqno = seqno; else fr->oseqno = pvt->oseqno++; fr->iseqno = pvt->iseqno; fh = (struct ast_iax2_full_hdr *)(fr->af.data.ptr - sizeof(struct ast_iax2_full_hdr)); fh->scallno = htons(fr->callno | IAX_FLAG_FULL); fh->ts = htonl(fr->ts); fh->oseqno = fr->oseqno; if (transfer) { fh->iseqno = 0; } else fh->iseqno = fr->iseqno; /* Keep track of the last thing we've acknowledged */ if (!transfer) pvt->aseqno = fr->iseqno; fh->type = fr->af.frametype & 0xFF; if (fr->af.frametype == AST_FRAME_VIDEO) { iax2_format tmpfmt = ast_format_compatibility_format2bitfield(fr->af.subclass.format); tmpfmt |= fr->af.subclass.frame_ending ? 0x1LL : 0; fh->csub = compress_subclass(tmpfmt | ((tmpfmt & 0x1LL) << 6)); } else if (fr->af.frametype == AST_FRAME_VOICE) { fh->csub = compress_subclass(ast_format_compatibility_format2bitfield(fr->af.subclass.format)); } else { fh->csub = compress_subclass(fr->af.subclass.integer); } if (transfer) { fr->dcallno = pvt->transfercallno; } else fr->dcallno = pvt->peercallno; fh->dcallno = htons(fr->dcallno); fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_full_hdr); fr->data = fh; fr->retries = 0; /* Retry after 2x the ping time has passed */ fr->retrytime = pvt->pingtime * 2; if (fr->retrytime < MIN_RETRY_TIME) fr->retrytime = MIN_RETRY_TIME; if (fr->retrytime > MAX_RETRY_TIME) fr->retrytime = MAX_RETRY_TIME; /* Acks' don't get retried */ if ((f->frametype == AST_FRAME_IAX) && (f->subclass.integer == IAX_COMMAND_ACK)) fr->retries = -1; else if (f->frametype == AST_FRAME_VOICE) pvt->svoiceformat = ast_format_compatibility_format2bitfield(f->subclass.format); else if (f->frametype == AST_FRAME_VIDEO) pvt->svideoformat = ast_format_compatibility_format2bitfield(f->subclass.format); if (ast_test_flag64(pvt, IAX_ENCRYPTED)) { if (ast_test_flag64(pvt, IAX_KEYPOPULATED)) { if (fr->transfer) iax_outputframe(fr, NULL, 2, &pvt->transfer, fr->datalen - sizeof(struct ast_iax2_full_hdr)); else iax_outputframe(fr, NULL, 2, &pvt->addr, fr->datalen - sizeof(struct ast_iax2_full_hdr)); encrypt_frame(&pvt->ecx, fh, pvt->semirand, &fr->datalen); fr->encmethods = pvt->encmethods; fr->ecx = pvt->ecx; fr->mydcx = pvt->mydcx; memcpy(fr->semirand, pvt->semirand, sizeof(fr->semirand)); } else ast_log(LOG_WARNING, "Supposed to send packet encrypted, but no key?\n"); } if (now) { res = send_packet(fr); } else res = iax2_transmit(fr); } else { if (ast_test_flag64(pvt, IAX_TRUNK)) { iax2_trunk_queue(pvt, fr); res = 0; } else if (fr->af.frametype == AST_FRAME_VIDEO) { /* Video frame have no sequence number */ fr->oseqno = -1; fr->iseqno = -1; vh = (struct ast_iax2_video_hdr *)(fr->af.data.ptr - sizeof(struct ast_iax2_video_hdr)); vh->zeros = 0; vh->callno = htons(0x8000 | fr->callno); vh->ts = htons((fr->ts & 0x7FFF) | (fr->af.subclass.frame_ending ? 0x8000 : 0)); fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_video_hdr); fr->data = vh; fr->retries = -1; res = send_packet(fr); } else { /* Mini-frames have no sequence number */ fr->oseqno = -1; fr->iseqno = -1; /* Mini frame will do */ mh = (struct ast_iax2_mini_hdr *)(fr->af.data.ptr - sizeof(struct ast_iax2_mini_hdr)); mh->callno = htons(fr->callno); mh->ts = htons(fr->ts & 0xFFFF); fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_mini_hdr); fr->data = mh; fr->retries = -1; if (pvt->transferring == TRANSFER_MEDIAPASS) fr->transfer = 1; if (ast_test_flag64(pvt, IAX_ENCRYPTED)) { if (ast_test_flag64(pvt, IAX_KEYPOPULATED)) { encrypt_frame(&pvt->ecx, (struct ast_iax2_full_hdr *)mh, pvt->semirand, &fr->datalen); } else ast_log(LOG_WARNING, "Supposed to send packet encrypted, but no key?\n"); } res = send_packet(fr); } } return res; } static char *handle_cli_iax2_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { regex_t regexbuf; int havepattern = 0; #define FORMAT "%-15.15s %-20.20s %-15.15s %-15.15s %-5.5s %-5.10s\n" #define FORMAT2 "%-15.15s %-20.20s %-15.15d %-15.15s %-5.5s %-5.10s\n" struct iax2_user *user = NULL; char auth[90]; char *pstr = ""; struct ao2_iterator i; switch (cmd) { case CLI_INIT: e->command = "iax2 show users [like]"; e->usage = "Usage: iax2 show users [like ]\n" " Lists all known IAX2 users.\n" " Optional regular expression pattern is used to filter the user list.\n"; return NULL; case CLI_GENERATE: return NULL; } switch (a->argc) { case 5: if (!strcasecmp(a->argv[3], "like")) { if (regcomp(®exbuf, a->argv[4], REG_EXTENDED | REG_NOSUB)) return CLI_SHOWUSAGE; havepattern = 1; } else return CLI_SHOWUSAGE; case 3: break; default: return CLI_SHOWUSAGE; } ast_cli(a->fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C","Codec Pref"); i = ao2_iterator_init(users, 0); for (; (user = ao2_iterator_next(&i)); user_unref(user)) { if (havepattern && regexec(®exbuf, user->name, 0, NULL, 0)) continue; if (!ast_strlen_zero(user->secret)) { ast_copy_string(auth,user->secret, sizeof(auth)); } else if (!ast_strlen_zero(user->inkeys)) { snprintf(auth, sizeof(auth), "Key: %-15.15s ", user->inkeys); } else ast_copy_string(auth, "-no secret-", sizeof(auth)); if(ast_test_flag64(user, IAX_CODEC_NOCAP)) pstr = "REQ Only"; else if(ast_test_flag64(user, IAX_CODEC_NOPREFS)) pstr = "Disabled"; else pstr = ast_test_flag64(user, IAX_CODEC_USER_FIRST) ? "Caller" : "Host"; ast_cli(a->fd, FORMAT2, user->name, auth, user->authmethods, user->contexts ? user->contexts->context : DEFAULT_CONTEXT, ast_acl_list_is_empty(user->acl) ? "No" : "Yes", pstr); } ao2_iterator_destroy(&i); if (havepattern) regfree(®exbuf); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } struct show_peers_context { regex_t regexbuf; int havepattern; char idtext[256]; int registeredonly; int peerlist; int total_peers; int online_peers; int offline_peers; int unmonitored_peers; }; #define PEERS_FORMAT2 "%-15.15s %-40.40s %s %-40.40s %-9s %s %-11s %-32.32s\n" #define PEERS_FORMAT "%-15.15s %-40.40s %s %-40.40s %-6s%s %s %-11s %-32.32s\n" static void _iax2_show_peers_one(int fd, struct mansession *s, struct show_peers_context *cont, struct iax2_peer *peer) { char name[256] = ""; char status[20]; int retstatus; struct ast_str *encmethods = ast_str_alloca(256); char *tmp_host, *tmp_mask, *tmp_port; tmp_host = ast_strdupa(ast_sockaddr_stringify_addr(&peer->addr)); tmp_mask = ast_strdupa(ast_sockaddr_stringify_addr(&peer->mask)); tmp_port = ast_strdupa(ast_sockaddr_stringify_port(&peer->addr)); 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)); } encmethods_to_str(peer->encmethods, &encmethods); retstatus = peer_status(peer, status, sizeof(status)); if (retstatus > 0) { cont->online_peers++; } else if (!retstatus) { cont->offline_peers++; } else { cont->unmonitored_peers++; } if (s) { if (cont->peerlist) { /* IAXpeerlist */ astman_append(s, "Event: PeerEntry\r\n%s" "Channeltype: IAX\r\n", cont->idtext); if (!ast_strlen_zero(peer->username)) { astman_append(s, "ObjectName: %s\r\n" "ObjectUsername: %s\r\n", peer->name, peer->username); } else { astman_append(s, "ObjectName: %s\r\n", name); } } else { /* IAXpeers */ astman_append(s, "Event: PeerEntry\r\n%s" "Channeltype: IAX2\r\n" "ObjectName: %s\r\n", cont->idtext, name); } astman_append(s, "ChanObjectType: peer\r\n" "IPaddress: %s\r\n", tmp_host); if (cont->peerlist) { /* IAXpeerlist */ astman_append(s, "Mask: %s\r\n" "Port: %s\r\n", tmp_mask, tmp_port); } else { /* IAXpeers */ astman_append(s, "IPport: %s\r\n", tmp_port); } astman_append(s, "Dynamic: %s\r\n" "Trunk: %s\r\n" "Encryption: %s\r\n" "Status: %s\r\n", ast_test_flag64(peer, IAX_DYNAMIC) ? "yes" : "no", ast_test_flag64(peer, IAX_TRUNK) ? "yes" : "no", peer->encmethods ? ast_str_buffer(encmethods) : "no", status); if (cont->peerlist) { /* IAXpeerlist */ astman_append(s, "\r\n"); } else { /* IAXpeers */ astman_append(s, "Description: %s\r\n\r\n", peer->description); } } else { ast_cli(fd, PEERS_FORMAT, name, tmp_host, ast_test_flag64(peer, IAX_DYNAMIC) ? "(D)" : "(S)", tmp_mask, tmp_port, ast_test_flag64(peer, IAX_TRUNK) ? "(T)" : " ", peer->encmethods ? "(E)" : " ", status, peer->description); } cont->total_peers++; } static int __iax2_show_peers(int fd, int *total, struct mansession *s, const int argc, const char * const argv[]) { struct show_peers_context cont = { .havepattern = 0, .idtext = "", .registeredonly = 0, .peerlist = 0, .total_peers = 0, .online_peers = 0, .offline_peers = 0, .unmonitored_peers = 0, }; struct ao2_iterator i; struct iax2_peer *peer = NULL; switch (argc) { case 6: if (!strcasecmp(argv[3], "registered")) cont.registeredonly = 1; else return RESULT_SHOWUSAGE; if (!strcasecmp(argv[4], "like")) { if (regcomp(&cont.regexbuf, argv[5], REG_EXTENDED | REG_NOSUB)) return RESULT_SHOWUSAGE; cont.havepattern = 1; } else return RESULT_SHOWUSAGE; break; case 5: if (!strcasecmp(argv[3], "like")) { if (regcomp(&cont.regexbuf, argv[4], REG_EXTENDED | REG_NOSUB)) return RESULT_SHOWUSAGE; cont.havepattern = 1; } else return RESULT_SHOWUSAGE; break; case 4: if (!strcasecmp(argv[3], "registered")) { cont.registeredonly = 1; } else { return RESULT_SHOWUSAGE; } break; case 3: break; default: return RESULT_SHOWUSAGE; } if (!s) { ast_cli(fd, PEERS_FORMAT2, "Name/Username", "Host", " ", "Mask", "Port", " ", "Status", "Description"); } i = ao2_iterator_init(peers, 0); for (; (peer = ao2_iterator_next(&i)); peer_unref(peer)) { if (cont.registeredonly && ast_sockaddr_isnull(&peer->addr)) { continue; } if (cont.havepattern && regexec(&cont.regexbuf, peer->name, 0, NULL, 0)) { continue; } _iax2_show_peers_one(fd, s, &cont, peer); } ao2_iterator_destroy(&i); if (!s) { ast_cli(fd,"%d iax2 peers [%d online, %d offline, %d unmonitored]\n", cont.total_peers, cont.online_peers, cont.offline_peers, cont.unmonitored_peers); } if (cont.havepattern) { regfree(&cont.regexbuf); } if (total) { *total = cont.total_peers; } return RESULT_SUCCESS; } #undef PEERS_FORMAT2 #undef PEERS_FORMAT static char *handle_cli_iax2_show_threads(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct iax2_thread *thread = NULL; time_t t; int threadcount = 0, dynamiccount = 0; char type; switch (cmd) { case CLI_INIT: e->command = "iax2 show threads"; e->usage = "Usage: iax2 show threads\n" " Lists status of IAX helper threads\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, "IAX2 Thread Information\n"); time(&t); ast_cli(a->fd, "Idle Threads:\n"); AST_LIST_LOCK(&idle_list); AST_LIST_TRAVERSE(&idle_list, thread, list) { #ifdef DEBUG_SCHED_MULTITHREAD ast_cli(a->fd, "Thread %d: state=%u, update=%d, actions=%d, func='%s'\n", thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions, thread->curfunc); #else ast_cli(a->fd, "Thread %d: state=%u, update=%d, actions=%d\n", thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions); #endif threadcount++; } AST_LIST_UNLOCK(&idle_list); ast_cli(a->fd, "Active Threads:\n"); AST_LIST_LOCK(&active_list); AST_LIST_TRAVERSE(&active_list, thread, list) { if (thread->type == IAX_THREAD_TYPE_DYNAMIC) type = 'D'; else type = 'P'; #ifdef DEBUG_SCHED_MULTITHREAD ast_cli(a->fd, "Thread %c%d: state=%u, update=%d, actions=%d, func='%s'\n", type, thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions, thread->curfunc); #else ast_cli(a->fd, "Thread %c%d: state=%u, update=%d, actions=%d\n", type, thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions); #endif threadcount++; } AST_LIST_UNLOCK(&active_list); ast_cli(a->fd, "Dynamic Threads:\n"); AST_LIST_LOCK(&dynamic_list); AST_LIST_TRAVERSE(&dynamic_list, thread, list) { #ifdef DEBUG_SCHED_MULTITHREAD ast_cli(a->fd, "Thread %d: state=%u, update=%d, actions=%d, func='%s'\n", thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions, thread->curfunc); #else ast_cli(a->fd, "Thread %d: state=%u, update=%d, actions=%d\n", thread->threadnum, thread->iostate, (int)(t - thread->checktime), thread->actions); #endif dynamiccount++; } AST_LIST_UNLOCK(&dynamic_list); ast_cli(a->fd, "%d of %d threads accounted for with %d dynamic threads\n", threadcount, iaxthreadcount, dynamiccount); return CLI_SUCCESS; } static char *handle_cli_iax2_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct iax2_peer *p; switch (cmd) { case CLI_INIT: e->command = "iax2 unregister"; e->usage = "Usage: iax2 unregister \n" " Unregister (force expiration) an IAX2 peer from the registry.\n"; return NULL; case CLI_GENERATE: return complete_iax2_unregister(a->line, a->word, a->pos, a->n); } if (a->argc != 3) return CLI_SHOWUSAGE; p = find_peer(a->argv[2], 1); if (p) { if (p->expire > 0) { struct iax2_peer *peer; peer = ao2_find(peers, a->argv[2], OBJ_KEY); if (peer) { expire_registry(peer_ref(peer)); /* will release its own reference when done */ peer_unref(peer); /* ref from ao2_find() */ ast_cli(a->fd, "Peer %s unregistered\n", a->argv[2]); } else { ast_cli(a->fd, "Peer %s not found\n", a->argv[2]); } } else { ast_cli(a->fd, "Peer %s not registered\n", a->argv[2]); } peer_unref(p); } else { ast_cli(a->fd, "Peer unknown: %s. Not unregistered\n", a->argv[2]); } return CLI_SUCCESS; } static char *complete_iax2_unregister(const char *line, const char *word, int pos, int state) { int which = 0; struct iax2_peer *p = NULL; char *res = NULL; int wordlen = strlen(word); /* 0 - iax2; 1 - unregister; 2 - */ if (pos == 2) { struct ao2_iterator i = ao2_iterator_init(peers, 0); while ((p = ao2_iterator_next(&i))) { if (!strncasecmp(p->name, word, wordlen) && ++which > state && p->expire > 0) { res = ast_strdup(p->name); peer_unref(p); break; } peer_unref(p); } ao2_iterator_destroy(&i); } return res; } static char *handle_cli_iax2_show_peers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 show peers"; e->usage = "Usage: iax2 show peers [registered] [like ]\n" " Lists all known IAX2 peers.\n" " Optional 'registered' argument lists only peers with known addresses.\n" " Optional regular expression pattern is used to filter the peer list.\n"; return NULL; case CLI_GENERATE: return NULL; } switch (__iax2_show_peers(a->fd, NULL, NULL, a->argc, a->argv)) { case RESULT_SHOWUSAGE: return CLI_SHOWUSAGE; case RESULT_FAILURE: return CLI_FAILURE; default: return CLI_SUCCESS; } } static int manager_iax2_show_netstats(struct mansession *s, const struct message *m) { ast_cli_netstats(s, -1, 0); astman_append(s, "\r\n"); return RESULT_SUCCESS; } static int firmware_show_callback(struct ast_iax2_firmware_header *header, void *user_data) { int *fd = user_data; ast_cli(*fd, "%-15.15s %-15d %-15d\n", header->devname, ntohs(header->version), (int) ntohl(header->datalen)); return 0; } static char *handle_cli_iax2_show_firmware(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 show firmware"; e->usage = "Usage: iax2 show firmware\n" " Lists all known IAX firmware images.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3 && a->argc != 4) return CLI_SHOWUSAGE; ast_cli(a->fd, "%-15.15s %-15.15s %-15.15s\n", "Device", "Version", "Size"); iax_firmware_traverse( a->argc == 3 ? NULL : a->argv[3], firmware_show_callback, (void *) &a->fd); return CLI_SUCCESS; } /*! \brief callback to display iax peers in manager */ static int manager_iax2_show_peers(struct mansession *s, const struct message *m) { static const char * const a[] = { "iax2", "show", "peers" }; const char *id = astman_get_header(m,"ActionID"); char idtext[256] = ""; int total = 0; if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); astman_send_listack(s, m, "Peer status list will follow", "start"); /* List the peers in separate manager events */ __iax2_show_peers(-1, &total, s, 3, a); /* Send final confirmation */ astman_append(s, "Event: PeerlistComplete\r\n" "EventList: Complete\r\n" "ListItems: %d\r\n" "%s" "\r\n", total, idtext); return 0; } /*! \brief callback to display iax peers in manager format */ static int manager_iax2_show_peer_list(struct mansession *s, const struct message *m) { struct show_peers_context cont = { .havepattern = 0, .idtext = "", .registeredonly = 0, .peerlist = 1, .total_peers = 0, .online_peers = 0, .offline_peers = 0, .unmonitored_peers = 0, }; struct iax2_peer *peer = NULL; struct ao2_iterator i; const char *id = astman_get_header(m,"ActionID"); if (!ast_strlen_zero(id)) { snprintf(cont.idtext, sizeof(cont.idtext), "ActionID: %s\r\n", id); } astman_append(s, "Response: Success\r\n" "%sMessage: IAX Peer status list will follow\r\n\r\n", cont.idtext); i = ao2_iterator_init(peers, 0); for (; (peer = ao2_iterator_next(&i)); peer_unref(peer)) { _iax2_show_peers_one(-1, s, &cont, peer); } ao2_iterator_destroy(&i); astman_append(s, "Event: PeerlistComplete\r\n" "%sListItems: %d\r\n\r\n", cont.idtext, cont.total_peers); return RESULT_SUCCESS; } static char *regstate2str(int regstate) { switch(regstate) { case REG_STATE_UNREGISTERED: return "Unregistered"; case REG_STATE_REGSENT: return "Request Sent"; case REG_STATE_AUTHSENT: return "Auth. Sent"; case REG_STATE_REGISTERED: return "Registered"; case REG_STATE_REJECTED: return "Rejected"; case REG_STATE_TIMEOUT: return "Timeout"; case REG_STATE_NOAUTH: return "No Authentication"; default: return "Unknown"; } } static char *handle_cli_iax2_show_registry(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT2 "%-45.45s %-6.6s %-10.10s %-45.45s %8.8s %s\n" #define FORMAT "%-45.45s %-6.6s %-10.10s %-45.45s %8d %s\n" struct iax2_registry *reg = NULL; char host[80]; char perceived[80]; int counter = 0; switch (cmd) { case CLI_INIT: e->command = "iax2 show registry"; e->usage = "Usage: iax2 show registry\n" " Lists all registration requests and status.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, FORMAT2, "Host", "dnsmgr", "Username", "Perceived", "Refresh", "State"); AST_LIST_LOCK(®istrations); AST_LIST_TRAVERSE(®istrations, reg, entry) { snprintf(host, sizeof(host), "%s", ast_sockaddr_stringify(®->addr)); snprintf(perceived, sizeof(perceived), "%s", ast_sockaddr_isnull(®->addr) ? "" : ast_sockaddr_stringify(®->addr)); ast_cli(a->fd, FORMAT, host, (reg->dnsmgr) ? "Y" : "N", reg->username, perceived, reg->refresh, regstate2str(reg->regstate)); counter++; } AST_LIST_UNLOCK(®istrations); ast_cli(a->fd, "%d IAX2 registrations.\n", counter); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 } static int manager_iax2_show_registry(struct mansession *s, const struct message *m) { const char *id = astman_get_header(m, "ActionID"); struct iax2_registry *reg = NULL; char idtext[256] = ""; char host[80] = ""; char perceived[80] = ""; int total = 0; if (!ast_strlen_zero(id)) snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id); astman_send_listack(s, m, "Registrations will follow", "start"); AST_LIST_LOCK(®istrations); AST_LIST_TRAVERSE(®istrations, reg, entry) { snprintf(host, sizeof(host), "%s", ast_sockaddr_stringify(®->addr)); snprintf(perceived, sizeof(perceived), "%s", ast_sockaddr_isnull(®->addr) ? "" : ast_sockaddr_stringify(®->addr)); astman_append(s, "Event: RegistryEntry\r\n" "%s" "Host: %s\r\n" "DNSmanager: %s\r\n" "Username: %s\r\n" "Perceived: %s\r\n" "Refresh: %d\r\n" "State: %s\r\n" "\r\n", idtext, host, (reg->dnsmgr) ? "Y" : "N", reg->username, perceived, reg->refresh, regstate2str(reg->regstate)); total++; } AST_LIST_UNLOCK(®istrations); astman_append(s, "Event: RegistrationsComplete\r\n" "EventList: Complete\r\n" "ListItems: %d\r\n" "%s" "\r\n", total, idtext); return 0; } static char *handle_cli_iax2_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { #define FORMAT2 "%-20.20s %-40.40s %-10.10s %-11.11s %-11.11s %-7.7s %-6.6s %-6.6s %s %s %9s\n" #define FORMAT "%-20.20s %-40.40s %-10.10s %5.5d/%5.5d %5.5d/%5.5d %-5.5dms %-4.4dms %-4.4dms %-6.6s %s%s %3s%s\n" #define FORMATB "%-20.20s %-40.40s %-10.10s %5.5d/%5.5d %5.5d/%5.5d [Native Bridged to ID=%5.5d]\n" int x; int numchans = 0; char first_message[10] = { 0, }; char last_message[10] = { 0, }; switch (cmd) { case CLI_INIT: e->command = "iax2 show channels"; e->usage = "Usage: iax2 show channels\n" " Lists all currently active IAX channels.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, FORMAT2, "Channel", "Peer", "Username", "ID (Lo/Rem)", "Seq (Tx/Rx)", "Lag", "Jitter", "JitBuf", "Format", "FirstMsg", "LastMsg"); for (x = 0; x < ARRAY_LEN(iaxs); x++) { ast_mutex_lock(&iaxsl[x]); if (iaxs[x]) { int lag, jitter, localdelay; jb_info jbinfo; if (ast_test_flag64(iaxs[x], IAX_USEJITTERBUF)) { jb_getinfo(iaxs[x]->jb, &jbinfo); jitter = jbinfo.jitter; localdelay = jbinfo.current - jbinfo.min; } else { jitter = -1; localdelay = 0; } iax_frame_subclass2str(iaxs[x]->first_iax_message & ~MARK_IAX_SUBCLASS_TX, first_message, sizeof(first_message)); iax_frame_subclass2str(iaxs[x]->last_iax_message & ~MARK_IAX_SUBCLASS_TX, last_message, sizeof(last_message)); lag = iaxs[x]->remote_rr.delay; ast_cli(a->fd, FORMAT, iaxs[x]->owner ? ast_channel_name(iaxs[x]->owner) : "(None)", ast_sockaddr_stringify_addr(&iaxs[x]->addr), S_OR(iaxs[x]->username, "(None)"), iaxs[x]->callno, iaxs[x]->peercallno, iaxs[x]->oseqno, iaxs[x]->iseqno, lag, jitter, localdelay, iax2_getformatname(iaxs[x]->voiceformat), (iaxs[x]->first_iax_message & MARK_IAX_SUBCLASS_TX) ? "Tx:" : "Rx:", first_message, (iaxs[x]->last_iax_message & MARK_IAX_SUBCLASS_TX) ? "Tx:" : "Rx:", last_message); numchans++; } ast_mutex_unlock(&iaxsl[x]); } ast_cli(a->fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : ""); return CLI_SUCCESS; #undef FORMAT #undef FORMAT2 #undef FORMATB } static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt) { int x; int numchans = 0; char first_message[10] = { 0, }; char last_message[10] = { 0, }; #define ACN_FORMAT1 "%-20.25s %4u %4d %4d %5d %3d %5d %4d %6d %4d %4d %5d %3d %5d %4d %6d %s%s %4s%s\n" #define ACN_FORMAT2 "%s %u %d %d %d %d %d %d %d %d %d %d %d %d %d %d %s%s %s%s\n" for (x = 0; x < ARRAY_LEN(iaxs); x++) { ast_mutex_lock(&iaxsl[x]); if (iaxs[x]) { int localjitter, localdelay, locallost, locallosspct, localdropped, localooo; jb_info jbinfo; iax_frame_subclass2str(iaxs[x]->first_iax_message & ~MARK_IAX_SUBCLASS_TX, first_message, sizeof(first_message)); iax_frame_subclass2str(iaxs[x]->last_iax_message & ~MARK_IAX_SUBCLASS_TX, last_message, sizeof(last_message)); if(ast_test_flag64(iaxs[x], IAX_USEJITTERBUF)) { jb_getinfo(iaxs[x]->jb, &jbinfo); localjitter = jbinfo.jitter; localdelay = jbinfo.current - jbinfo.min; locallost = jbinfo.frames_lost; locallosspct = jbinfo.losspct/1000; localdropped = jbinfo.frames_dropped; localooo = jbinfo.frames_ooo; } else { localjitter = -1; localdelay = 0; locallost = -1; locallosspct = -1; localdropped = 0; localooo = -1; } if (s) astman_append(s, limit_fmt ? ACN_FORMAT1 : ACN_FORMAT2, iaxs[x]->owner ? ast_channel_name(iaxs[x]->owner) : "(None)", iaxs[x]->pingtime, localjitter, localdelay, locallost, locallosspct, localdropped, localooo, iaxs[x]->frames_received/1000, iaxs[x]->remote_rr.jitter, iaxs[x]->remote_rr.delay, iaxs[x]->remote_rr.losscnt, iaxs[x]->remote_rr.losspct, iaxs[x]->remote_rr.dropped, iaxs[x]->remote_rr.ooo, iaxs[x]->remote_rr.packets/1000, (iaxs[x]->first_iax_message & MARK_IAX_SUBCLASS_TX) ? "Tx:" : "Rx:", first_message, (iaxs[x]->last_iax_message & MARK_IAX_SUBCLASS_TX) ? "Tx:" : "Rx:", last_message); else ast_cli(fd, limit_fmt ? ACN_FORMAT1 : ACN_FORMAT2, iaxs[x]->owner ? ast_channel_name(iaxs[x]->owner) : "(None)", iaxs[x]->pingtime, localjitter, localdelay, locallost, locallosspct, localdropped, localooo, iaxs[x]->frames_received/1000, iaxs[x]->remote_rr.jitter, iaxs[x]->remote_rr.delay, iaxs[x]->remote_rr.losscnt, iaxs[x]->remote_rr.losspct, iaxs[x]->remote_rr.dropped, iaxs[x]->remote_rr.ooo, iaxs[x]->remote_rr.packets/1000, (iaxs[x]->first_iax_message & MARK_IAX_SUBCLASS_TX) ? "Tx:" : "Rx:", first_message, (iaxs[x]->last_iax_message & MARK_IAX_SUBCLASS_TX) ? "Tx:" : "Rx:", last_message); numchans++; } ast_mutex_unlock(&iaxsl[x]); } return numchans; } static char *handle_cli_iax2_show_netstats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int numchans = 0; switch (cmd) { case CLI_INIT: e->command = "iax2 show netstats"; e->usage = "Usage: iax2 show netstats\n" " Lists network status for all currently active IAX channels.\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) return CLI_SHOWUSAGE; ast_cli(a->fd, " -------- LOCAL --------------------- -------- REMOTE --------------------\n"); ast_cli(a->fd, "Channel RTT Jit Del Lost %% Drop OOO Kpkts Jit Del Lost %% Drop OOO Kpkts FirstMsg LastMsg\n"); numchans = ast_cli_netstats(NULL, a->fd, 1); ast_cli(a->fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : ""); return CLI_SUCCESS; } static char *handle_cli_iax2_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 set debug {on|off|peer}"; e->usage = "Usage: iax2 set debug {on|off|peer peername}\n" " Enables/Disables dumping of IAX packets for debugging purposes.\n"; return NULL; case CLI_GENERATE: if (a->pos == 4 && !strcasecmp(a->argv[3], "peer")) return complete_iax2_peers(a->line, a->word, a->pos, a->n, 0); return NULL; } if (a->argc < e->args || a->argc > e->args + 1) return CLI_SHOWUSAGE; if (!strcasecmp(a->argv[3], "peer")) { struct iax2_peer *peer; if (a->argc != e->args + 1) return CLI_SHOWUSAGE; peer = find_peer(a->argv[4], 1); if (!peer) { ast_cli(a->fd, "IAX2 peer '%s' does not exist\n", a->argv[e->args-1]); return CLI_FAILURE; } ast_sockaddr_copy(&debugaddr, &peer->addr); ast_cli(a->fd, "IAX2 Debugging Enabled for IP: %s\n", ast_sockaddr_stringify_port(&debugaddr)); ao2_ref(peer, -1); } else if (!strncasecmp(a->argv[3], "on", 2)) { iaxdebug = 1; ast_cli(a->fd, "IAX2 Debugging Enabled\n"); } else { iaxdebug = 0; memset(&debugaddr, 0, sizeof(debugaddr)); ast_cli(a->fd, "IAX2 Debugging Disabled\n"); } return CLI_SUCCESS; } static char *handle_cli_iax2_set_debug_trunk(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 set debug trunk {on|off}"; e->usage = "Usage: iax2 set debug trunk {on|off}\n" " Enables/Disables debugging of IAX trunking\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != e->args) return CLI_SHOWUSAGE; if (!strncasecmp(a->argv[e->args - 1], "on", 2)) { iaxtrunkdebug = 1; ast_cli(a->fd, "IAX2 Trunk Debugging Enabled\n"); } else { iaxtrunkdebug = 0; ast_cli(a->fd, "IAX2 Trunk Debugging Disabled\n"); } return CLI_SUCCESS; } static char *handle_cli_iax2_set_debug_jb(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "iax2 set debug jb {on|off}"; e->usage = "Usage: iax2 set debug jb {on|off}\n" " Enables/Disables jitterbuffer debugging information\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != e->args) return CLI_SHOWUSAGE; if (!strncasecmp(a->argv[e->args -1], "on", 2)) { jb_setoutput(jb_error_output, jb_warning_output, jb_debug_output); ast_cli(a->fd, "IAX2 Jitterbuffer Debugging Enabled\n"); } else { jb_setoutput(jb_error_output, jb_warning_output, NULL); ast_cli(a->fd, "IAX2 Jitterbuffer Debugging Disabled\n"); } return CLI_SUCCESS; } static int iax2_write(struct ast_channel *c, struct ast_frame *f) { unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(c)); int res = -1; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { /* If there's an outstanding error, return failure now */ if (!iaxs[callno]->error) { if (ast_test_flag64(iaxs[callno], IAX_ALREADYGONE)) res = 0; /* Don't waste bandwidth sending null frames */ else if (f->frametype == AST_FRAME_NULL) res = 0; else if ((f->frametype == AST_FRAME_VOICE) && ast_test_flag64(iaxs[callno], IAX_QUELCH)) res = 0; else if (!ast_test_flag(&iaxs[callno]->state, IAX_STATE_STARTED)) res = 0; else /* Simple, just queue for transmission */ res = iax2_send(iaxs[callno], f, 0, -1, 0, 0, 0); } else { ast_debug(1, "Write error: %s\n", strerror(errno)); } } /* If it's already gone, just return */ ast_mutex_unlock(&iaxsl[callno]); return res; } static int __send_command(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno, int now, int transfer, int final) { struct ast_frame f = { 0, }; int res = 0; f.frametype = type; f.subclass.integer = command; f.datalen = datalen; f.src = __FUNCTION__; f.data.ptr = (void *) data; if ((res = queue_signalling(i, &f)) <= 0) { return res; } return iax2_send(i, &f, ts, seqno, now, transfer, final); } static int send_command(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno) { if (type == AST_FRAME_CONTROL && !iax2_is_control_frame_allowed(command)) { /* Control frame should not go out on the wire. */ ast_debug(2, "Callno %d: Blocked sending control frame %d.\n", i->callno, command); return 0; } return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 0); } static int send_command_locked(unsigned short callno, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno) { int res; ast_mutex_lock(&iaxsl[callno]); res = send_command(iaxs[callno], type, command, ts, data, datalen, seqno); ast_mutex_unlock(&iaxsl[callno]); return res; } /*! * \note Since this function calls iax2_predestroy() -> iax2_queue_hangup(), * the pvt struct for the given call number may disappear during its * execution. */ static int send_command_final(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno) { int call_num = i->callno; /* It is assumed that the callno has already been locked */ iax2_predestroy(i->callno); if (!iaxs[call_num]) return -1; return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 1); } static int send_command_immediate(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen, int seqno) { return __send_command(i, type, command, ts, data, datalen, seqno, 1, 0, 0); } static int send_command_transfer(struct chan_iax2_pvt *i, char type, int command, unsigned int ts, const unsigned char *data, int datalen) { return __send_command(i, type, command, ts, data, datalen, 0, 0, 1, 0); } static int apply_context(struct iax2_context *con, const char *context) { while(con) { if (!strcmp(con->context, context) || !strcmp(con->context, "*")) return -1; con = con->next; } return 0; } static int check_access(int callno, struct ast_sockaddr *addr, struct iax_ies *ies) { /* Start pessimistic */ int res = -1; int version = 2; struct iax2_user *user = NULL, *best = NULL; int bestscore = 0; int gotcapability = 0; struct ast_variable *v = NULL, *tmpvar = NULL; struct ao2_iterator i; if (!iaxs[callno]) return res; if (ies->called_number) ast_string_field_set(iaxs[callno], exten, ies->called_number); if (ies->calling_number) { if (ast_test_flag64(&globalflags, IAX_SHRINKCALLERID)) { ast_shrink_phone_number(ies->calling_number); } ast_string_field_set(iaxs[callno], cid_num, ies->calling_number); } if (ies->calling_name) ast_string_field_set(iaxs[callno], cid_name, ies->calling_name); if (ies->calling_ani) ast_string_field_set(iaxs[callno], ani, ies->calling_ani); if (ies->dnid) ast_string_field_set(iaxs[callno], dnid, ies->dnid); if (ies->rdnis) ast_string_field_set(iaxs[callno], rdnis, ies->rdnis); if (ies->called_context) ast_string_field_set(iaxs[callno], context, ies->called_context); if (ies->language) ast_string_field_set(iaxs[callno], language, ies->language); if (ies->username) ast_string_field_set(iaxs[callno], username, ies->username); if (ies->calling_ton > -1) iaxs[callno]->calling_ton = ies->calling_ton; if (ies->calling_tns > -1) iaxs[callno]->calling_tns = ies->calling_tns; if (ies->calling_pres > -1) iaxs[callno]->calling_pres = ies->calling_pres; if (ies->format) iaxs[callno]->peerformat = ies->format; if (ies->adsicpe) iaxs[callno]->peeradsicpe = ies->adsicpe; if (ies->capability) { gotcapability = 1; iaxs[callno]->peercapability = ies->capability; } if (ies->version) version = ies->version; /* Use provided preferences until told otherwise for actual preferences */ if (ies->codec_prefs) { iax2_codec_pref_convert(&iaxs[callno]->rprefs, ies->codec_prefs, 32, 0); } else { memset(&iaxs[callno]->rprefs, 0, sizeof(iaxs[callno]->rprefs)); } iaxs[callno]->prefs = iaxs[callno]->rprefs; if (!gotcapability) { iaxs[callno]->peercapability = iaxs[callno]->peerformat; } if (version > IAX_PROTO_VERSION) { ast_log(LOG_WARNING, "Peer '%s' has too new a protocol version (%d) for me\n", ast_sockaddr_stringify_addr(addr), version); return res; } /* Search the userlist for a compatible entry, and fill in the rest */ i = ao2_iterator_init(users, 0); while ((user = ao2_iterator_next(&i))) { if ((ast_strlen_zero(iaxs[callno]->username) || /* No username specified */ !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */ && (ast_apply_acl(user->acl, addr, "IAX2 user ACL: ") == AST_SENSE_ALLOW) /* Access is permitted from this IP */ && (ast_strlen_zero(iaxs[callno]->context) || /* No context specified */ apply_context(user->contexts, iaxs[callno]->context))) { /* Context is permitted */ if (!ast_strlen_zero(iaxs[callno]->username)) { /* Exact match, stop right now. */ if (best) user_unref(best); best = user; break; } else if (ast_strlen_zero(user->secret) && ast_strlen_zero(user->dbsecret) && ast_strlen_zero(user->inkeys)) { /* No required authentication */ if (user->acl) { /* There was host authentication and we passed, bonus! */ if (bestscore < 4) { bestscore = 4; if (best) user_unref(best); best = user; continue; } } else { /* No host access, but no secret, either, not bad */ if (bestscore < 3) { bestscore = 3; if (best) user_unref(best); best = user; continue; } } } else { if (user->acl) { /* Authentication, but host access too, eh, it's something.. */ if (bestscore < 2) { bestscore = 2; if (best) user_unref(best); best = user; continue; } } else { /* Authentication and no host access... This is our baseline */ if (bestscore < 1) { bestscore = 1; if (best) user_unref(best); best = user; continue; } } } } user_unref(user); } ao2_iterator_destroy(&i); user = best; if (!user && !ast_strlen_zero(iaxs[callno]->username)) { user = realtime_user(iaxs[callno]->username, addr); if (user && (ast_apply_acl(user->acl, addr, "IAX2 user ACL: ") == AST_SENSE_DENY /* Access is denied from this IP */ || (!ast_strlen_zero(iaxs[callno]->context) && /* No context specified */ !apply_context(user->contexts, iaxs[callno]->context)))) { /* Context is permitted */ user = user_unref(user); } } if (user) { /* We found our match (use the first) */ /* copy vars */ for (v = user->vars ; v ; v = v->next) { if((tmpvar = ast_variable_new(v->name, v->value, v->file))) { tmpvar->next = iaxs[callno]->vars; iaxs[callno]->vars = tmpvar; } } /* If a max AUTHREQ restriction is in place, activate it */ if (user->maxauthreq > 0) ast_set_flag64(iaxs[callno], IAX_MAXAUTHREQ); iaxs[callno]->prefs = user->prefs; ast_copy_flags64(iaxs[callno], user, IAX_CODEC_USER_FIRST | IAX_IMMEDIATE | IAX_CODEC_NOPREFS | IAX_CODEC_NOCAP | IAX_FORCE_ENCRYPT); iaxs[callno]->encmethods = user->encmethods; /* Store the requested username if not specified */ if (ast_strlen_zero(iaxs[callno]->username)) ast_string_field_set(iaxs[callno], username, user->name); /* Store whether this is a trunked call, too, of course, and move if appropriate */ ast_copy_flags64(iaxs[callno], user, IAX_TRUNK); iaxs[callno]->capability = user->capability; /* And use the default context */ if (ast_strlen_zero(iaxs[callno]->context)) { if (user->contexts) ast_string_field_set(iaxs[callno], context, user->contexts->context); else ast_string_field_set(iaxs[callno], context, DEFAULT_CONTEXT); } /* And any input keys */ ast_string_field_set(iaxs[callno], inkeys, user->inkeys); /* And the permitted authentication methods */ iaxs[callno]->authmethods = user->authmethods; iaxs[callno]->adsi = user->adsi; /* If the user has callerid, override the remote caller id. */ if (ast_test_flag64(user, IAX_HASCALLERID)) { iaxs[callno]->calling_tns = 0; iaxs[callno]->calling_ton = 0; ast_string_field_set(iaxs[callno], cid_num, user->cid_num); ast_string_field_set(iaxs[callno], cid_name, user->cid_name); ast_string_field_set(iaxs[callno], ani, user->cid_num); iaxs[callno]->calling_pres = AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN; } else if (ast_strlen_zero(iaxs[callno]->cid_num) && ast_strlen_zero(iaxs[callno]->cid_name)) { iaxs[callno]->calling_pres = AST_PRES_NUMBER_NOT_AVAILABLE; } /* else user is allowed to set their own CID settings */ if (!ast_strlen_zero(user->accountcode)) ast_string_field_set(iaxs[callno], accountcode, user->accountcode); if (!ast_strlen_zero(user->mohinterpret)) ast_string_field_set(iaxs[callno], mohinterpret, user->mohinterpret); if (!ast_strlen_zero(user->mohsuggest)) ast_string_field_set(iaxs[callno], mohsuggest, user->mohsuggest); if (!ast_strlen_zero(user->parkinglot)) ast_string_field_set(iaxs[callno], parkinglot, user->parkinglot); if (user->amaflags) iaxs[callno]->amaflags = user->amaflags; if (!ast_strlen_zero(user->language)) ast_string_field_set(iaxs[callno], language, user->language); ast_copy_flags64(iaxs[callno], user, IAX_NOTRANSFER | IAX_TRANSFERMEDIA | IAX_USEJITTERBUF | IAX_FORCEJITTERBUF | IAX_SENDCONNECTEDLINE | IAX_RECVCONNECTEDLINE); /* Keep this check last */ if (!ast_strlen_zero(user->dbsecret)) { char *family, *key=NULL; char buf[80]; family = ast_strdupa(user->dbsecret); key = strchr(family, '/'); if (key) { *key = '\0'; key++; } if (!key || ast_db_get(family, key, buf, sizeof(buf))) ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", user->dbsecret); else ast_string_field_set(iaxs[callno], secret, buf); } else ast_string_field_set(iaxs[callno], secret, user->secret); res = 0; user = user_unref(user); } else { /* user was not found, but we should still fake an AUTHREQ. * Set authmethods to the last known authmethod used by the system * Set a fake secret, it's not looked at, just required to attempt authentication. * Set authrej so the AUTHREP is rejected without even looking at its contents */ iaxs[callno]->authmethods = last_authmethod ? last_authmethod : (IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT); ast_string_field_set(iaxs[callno], secret, "badsecret"); iaxs[callno]->authrej = 1; if (!ast_strlen_zero(iaxs[callno]->username)) { /* only send the AUTHREQ if a username was specified. */ res = 0; } } ast_set2_flag64(iaxs[callno], iax2_getpeertrunk(*addr), IAX_TRUNK); return res; } static int raw_hangup(struct ast_sockaddr *addr, unsigned short src, unsigned short dst, int sockfd) { struct ast_iax2_full_hdr fh; fh.scallno = htons(src | IAX_FLAG_FULL); fh.dcallno = htons(dst); fh.ts = 0; fh.oseqno = 0; fh.iseqno = 0; fh.type = AST_FRAME_IAX; fh.csub = compress_subclass(IAX_COMMAND_INVAL); iax_outputframe(NULL, &fh, 0, addr, 0); ast_debug(1, "Raw Hangup %s, src=%d, dst=%d\n", ast_sockaddr_stringify(addr), src, dst); return ast_sendto(sockfd, &fh, sizeof(fh), 0, addr); } static void merge_encryption(struct chan_iax2_pvt *p, unsigned int enc) { /* Select exactly one common encryption if there are any */ p->encmethods &= enc; if (p->encmethods) { if (!(p->encmethods & IAX_ENCRYPT_KEYROTATE)){ /* if key rotation is not supported, turn off keyrotation. */ p->keyrotateid = -2; } if (p->encmethods & IAX_ENCRYPT_AES128) p->encmethods = IAX_ENCRYPT_AES128; else p->encmethods = 0; } } /*! * \pre iaxsl[call_num] is locked * * \note Since this function calls send_command_final(), the pvt struct for the given * call number may disappear while executing this function. */ static int authenticate_request(int call_num) { struct iax_ie_data ied; int res = -1, authreq_restrict = 0; char challenge[10]; struct chan_iax2_pvt *p = iaxs[call_num]; memset(&ied, 0, sizeof(ied)); /* If an AUTHREQ restriction is in place, make sure we can send an AUTHREQ back */ if (ast_test_flag64(p, IAX_MAXAUTHREQ)) { struct iax2_user *user; user = ao2_find(users, p->username, OBJ_KEY); if (user) { if (user->curauthreq == user->maxauthreq) authreq_restrict = 1; else user->curauthreq++; user = user_unref(user); } } /* If the AUTHREQ limit test failed, send back an error */ if (authreq_restrict) { iax_ie_append_str(&ied, IAX_IE_CAUSE, "Unauthenticated call limit reached"); iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_CALL_REJECTED); send_command_final(p, AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied.buf, ied.pos, -1); return 0; } iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, p->authmethods); if (p->authmethods & (IAX_AUTH_MD5 | IAX_AUTH_RSA)) { snprintf(challenge, sizeof(challenge), "%d", (int)ast_random()); ast_string_field_set(p, challenge, challenge); /* snprintf(p->challenge, sizeof(p->challenge), "%d", (int)ast_random()); */ iax_ie_append_str(&ied, IAX_IE_CHALLENGE, p->challenge); } if (p->encmethods) iax_ie_append_short(&ied, IAX_IE_ENCRYPTION, p->encmethods); iax_ie_append_str(&ied,IAX_IE_USERNAME, p->username); res = send_command(p, AST_FRAME_IAX, IAX_COMMAND_AUTHREQ, 0, ied.buf, ied.pos, -1); if (p->encmethods) ast_set_flag64(p, IAX_ENCRYPTED); return res; } static int authenticate_verify(struct chan_iax2_pvt *p, struct iax_ies *ies) { char requeststr[256]; char md5secret[256] = ""; char secret[256] = ""; char rsasecret[256] = ""; int res = -1; int x; struct iax2_user *user; if (p->authrej) { return res; } user = ao2_find(users, p->username, OBJ_KEY); if (user) { if (ast_test_flag64(p, IAX_MAXAUTHREQ)) { ast_atomic_fetchadd_int(&user->curauthreq, -1); ast_clear_flag64(p, IAX_MAXAUTHREQ); } ast_string_field_set(p, host, user->name); user = user_unref(user); } if (ast_test_flag64(p, IAX_FORCE_ENCRYPT) && !p->encmethods) { ast_log(LOG_NOTICE, "Call Terminated, Incoming call is unencrypted while force encrypt is enabled.\n"); return res; } if (!ast_test_flag(&p->state, IAX_STATE_AUTHENTICATED)) return res; if (ies->password) ast_copy_string(secret, ies->password, sizeof(secret)); if (ies->md5_result) ast_copy_string(md5secret, ies->md5_result, sizeof(md5secret)); if (ies->rsa_result) ast_copy_string(rsasecret, ies->rsa_result, sizeof(rsasecret)); if ((p->authmethods & IAX_AUTH_RSA) && !ast_strlen_zero(rsasecret) && !ast_strlen_zero(p->inkeys)) { struct ast_key *key; char *keyn; char *tmpkey; char *stringp=NULL; if (!(tmpkey = ast_strdup(p->inkeys))) { ast_log(LOG_ERROR, "Unable to create a temporary string for parsing stored 'inkeys'\n"); return res; } stringp = tmpkey; keyn = strsep(&stringp, ":"); while(keyn) { key = ast_key_get(keyn, AST_KEY_PUBLIC); if (key && !ast_check_signature(key, p->challenge, rsasecret)) { res = 0; break; } else if (!key) ast_log(LOG_WARNING, "requested inkey '%s' for RSA authentication does not exist\n", keyn); keyn = strsep(&stringp, ":"); } ast_free(tmpkey); } else if (p->authmethods & IAX_AUTH_MD5) { struct MD5Context md5; unsigned char digest[16]; char *tmppw, *stringp; tmppw = ast_strdupa(p->secret); stringp = tmppw; while((tmppw = strsep(&stringp, ";"))) { MD5Init(&md5); MD5Update(&md5, (unsigned char *)p->challenge, strlen(p->challenge)); MD5Update(&md5, (unsigned char *)tmppw, strlen(tmppw)); MD5Final(digest, &md5); /* If they support md5, authenticate with it. */ for (x=0;x<16;x++) sprintf(requeststr + (x << 1), "%2.2x", (unsigned)digest[x]); /* safe */ if (!strcasecmp(requeststr, md5secret)) { res = 0; break; } } } else if (p->authmethods & IAX_AUTH_PLAINTEXT) { if (!strcmp(secret, p->secret)) res = 0; } return res; } /*! \brief Verify inbound registration */ static int register_verify(int callno, struct ast_sockaddr *addr, struct iax_ies *ies) { char requeststr[256] = ""; char peer[256] = ""; char md5secret[256] = ""; char rsasecret[256] = ""; char secret[256] = ""; struct iax2_peer *p = NULL; struct ast_key *key; char *keyn; int x; int expire = 0; int res = -1; ast_clear_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED); /* iaxs[callno]->peer[0] = '\0'; not necc. any more-- stringfield is pre-inited to null string */ if (ies->username) ast_copy_string(peer, ies->username, sizeof(peer)); if (ies->password) ast_copy_string(secret, ies->password, sizeof(secret)); if (ies->md5_result) ast_copy_string(md5secret, ies->md5_result, sizeof(md5secret)); if (ies->rsa_result) ast_copy_string(rsasecret, ies->rsa_result, sizeof(rsasecret)); if (ies->refresh) expire = ies->refresh; if (ast_strlen_zero(peer)) { ast_log(LOG_NOTICE, "Empty registration from %s\n", ast_sockaddr_stringify_addr(addr)); return -1; } /* SLD: first call to lookup peer during registration */ ast_mutex_unlock(&iaxsl[callno]); p = find_peer(peer, 1); ast_mutex_lock(&iaxsl[callno]); if (!p || !iaxs[callno]) { if (iaxs[callno]) { int plaintext = ((last_authmethod & IAX_AUTH_PLAINTEXT) | (iaxs[callno]->authmethods & IAX_AUTH_PLAINTEXT)); /* Anything, as long as it's non-blank */ ast_string_field_set(iaxs[callno], secret, "badsecret"); /* An AUTHREQ must be sent in response to a REGREQ of an invalid peer unless * 1. A challenge already exists indicating a AUTHREQ was already sent out. * 2. A plaintext secret is present in ie as result of a previous AUTHREQ requesting it. * 3. A plaintext secret is present in the ie and the last_authmethod used by a peer happened * to be plaintext, indicating it is an authmethod used by other peers on the system. * * If none of these cases exist, res will be returned as 0 without authentication indicating * an AUTHREQ needs to be sent out. */ if (ast_strlen_zero(iaxs[callno]->challenge) && !(!ast_strlen_zero(secret) && plaintext)) { /* by setting res to 0, an REGAUTH will be sent */ res = 0; } } if (authdebug && !p) ast_log(LOG_NOTICE, "No registration for peer '%s' (from %s)\n", peer, ast_sockaddr_stringify_addr(addr)); goto return_unref; } if (!ast_test_flag64(p, IAX_DYNAMIC)) { if (authdebug) ast_log(LOG_NOTICE, "Peer '%s' is not dynamic (from %s)\n", peer, ast_sockaddr_stringify_addr(addr)); goto return_unref; } if (!ast_apply_acl(p->acl, addr, "IAX2 Peer ACL: ")) { if (authdebug) ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_sockaddr_stringify_addr(addr), p->name); goto return_unref; } ast_string_field_set(iaxs[callno], secret, p->secret); ast_string_field_set(iaxs[callno], inkeys, p->inkeys); /* Check secret against what we have on file */ if (!ast_strlen_zero(rsasecret) && (p->authmethods & IAX_AUTH_RSA) && !ast_strlen_zero(iaxs[callno]->challenge)) { if (!ast_strlen_zero(p->inkeys)) { char *tmpkey; char *stringp=NULL; if (!(tmpkey = ast_strdup(p->inkeys))) { ast_log(LOG_ERROR, "Unable to create a temporary string for parsing stored 'inkeys'\n"); goto return_unref; } stringp = tmpkey; keyn = strsep(&stringp, ":"); while(keyn) { key = ast_key_get(keyn, AST_KEY_PUBLIC); if (key && !ast_check_signature(key, iaxs[callno]->challenge, rsasecret)) { ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED); break; } else if (!key) ast_log(LOG_WARNING, "requested inkey '%s' does not exist\n", keyn); keyn = strsep(&stringp, ":"); } ast_free(tmpkey); if (!keyn) { if (authdebug) ast_log(LOG_NOTICE, "Host %s failed RSA authentication with inkeys '%s'\n", peer, p->inkeys); goto return_unref; } } else { if (authdebug) ast_log(LOG_NOTICE, "Host '%s' trying to do RSA authentication, but we have no inkeys\n", peer); goto return_unref; } } else if (!ast_strlen_zero(md5secret) && (p->authmethods & IAX_AUTH_MD5) && !ast_strlen_zero(iaxs[callno]->challenge)) { struct MD5Context md5; unsigned char digest[16]; char *tmppw, *stringp; tmppw = ast_strdupa(p->secret); stringp = tmppw; while((tmppw = strsep(&stringp, ";"))) { MD5Init(&md5); MD5Update(&md5, (unsigned char *)iaxs[callno]->challenge, strlen(iaxs[callno]->challenge)); MD5Update(&md5, (unsigned char *)tmppw, strlen(tmppw)); MD5Final(digest, &md5); for (x=0;x<16;x++) sprintf(requeststr + (x << 1), "%2.2x", (unsigned)digest[x]); /* safe */ if (!strcasecmp(requeststr, md5secret)) break; } if (tmppw) { ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED); } else { if (authdebug) ast_log(LOG_NOTICE, "Host %s failed MD5 authentication for '%s' (%s != %s)\n", ast_sockaddr_stringify_addr(addr), p->name, requeststr, md5secret); goto return_unref; } } else if (!ast_strlen_zero(secret) && (p->authmethods & IAX_AUTH_PLAINTEXT)) { /* They've provided a plain text password and we support that */ if (strcmp(secret, p->secret)) { if (authdebug) ast_log(LOG_NOTICE, "Host %s did not provide proper plaintext password for '%s'\n", ast_sockaddr_stringify_addr(addr), p->name); goto return_unref; } else ast_set_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED); } else if (!ast_strlen_zero(iaxs[callno]->challenge) && ast_strlen_zero(md5secret) && ast_strlen_zero(rsasecret)) { /* if challenge has been sent, but no challenge response if given, reject. */ goto return_unref; } ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */ /* either Authentication has taken place, or a REGAUTH must be sent before verifying registration */ res = 0; return_unref: if (iaxs[callno]) { ast_string_field_set(iaxs[callno], peer, peer); /* Choose lowest expiry number */ if (expire && (expire < iaxs[callno]->expiry)) { iaxs[callno]->expiry = expire; } } if (p) { peer_unref(p); } return res; } static int authenticate(const char *challenge, const char *secret, const char *keyn, int authmethods, struct iax_ie_data *ied, struct ast_sockaddr *addr, struct chan_iax2_pvt *pvt) { int res = -1; int x; if (!ast_strlen_zero(keyn)) { if (!(authmethods & IAX_AUTH_RSA)) { if (ast_strlen_zero(secret)) { ast_log(LOG_NOTICE, "Asked to authenticate to %s with an RSA key, but they don't allow RSA authentication\n", ast_sockaddr_stringify_addr(addr)); } } else if (ast_strlen_zero(challenge)) { ast_log(LOG_NOTICE, "No challenge provided for RSA authentication to %s\n", ast_sockaddr_stringify_addr(addr)); } else { char sig[256]; struct ast_key *key; key = ast_key_get(keyn, AST_KEY_PRIVATE); if (!key) { ast_log(LOG_NOTICE, "Unable to find private key '%s'\n", keyn); } else { if (ast_sign(key, (char*)challenge, sig)) { ast_log(LOG_NOTICE, "Unable to sign challenge with key\n"); res = -1; } else { iax_ie_append_str(ied, IAX_IE_RSA_RESULT, sig); res = 0; } } } } /* Fall back */ if (res && !ast_strlen_zero(secret)) { if ((authmethods & IAX_AUTH_MD5) && !ast_strlen_zero(challenge)) { struct MD5Context md5; unsigned char digest[16]; char digres[128]; MD5Init(&md5); MD5Update(&md5, (unsigned char *)challenge, strlen(challenge)); MD5Update(&md5, (unsigned char *)secret, strlen(secret)); MD5Final(digest, &md5); /* If they support md5, authenticate with it. */ for (x=0;x<16;x++) sprintf(digres + (x << 1), "%2.2x", (unsigned)digest[x]); /* safe */ if (pvt) { build_encryption_keys(digest, pvt); } iax_ie_append_str(ied, IAX_IE_MD5_RESULT, digres); res = 0; } else if (authmethods & IAX_AUTH_PLAINTEXT) { iax_ie_append_str(ied, IAX_IE_PASSWORD, secret); res = 0; } else ast_log(LOG_NOTICE, "No way to send secret to peer '%s' (their methods: %d)\n", ast_sockaddr_stringify_addr(addr), authmethods); } return res; } /*! * \note This function calls realtime_peer -> reg_source_db -> iax2_poke_peer -> find_callno, * so do not call this function with a pvt lock held. */ static int authenticate_reply(struct chan_iax2_pvt *p, struct ast_sockaddr *addr, struct iax_ies *ies, const char *override, const char *okey) { struct iax2_peer *peer = NULL; /* Start pessimistic */ int res = -1; int authmethods = 0; struct iax_ie_data ied; uint16_t callno = p->callno; memset(&ied, 0, sizeof(ied)); if (ies->username) ast_string_field_set(p, username, ies->username); if (ies->challenge) ast_string_field_set(p, challenge, ies->challenge); if (ies->authmethods) authmethods = ies->authmethods; if (authmethods & IAX_AUTH_MD5) merge_encryption(p, ies->encmethods); else p->encmethods = 0; /* Check for override RSA authentication first */ if (!ast_strlen_zero(override) || !ast_strlen_zero(okey)) { /* Normal password authentication */ res = authenticate(p->challenge, override, okey, authmethods, &ied, addr, p); } else { struct ao2_iterator i = ao2_iterator_init(peers, 0); while ((peer = ao2_iterator_next(&i))) { struct ast_sockaddr peer_addr; struct ast_sockaddr tmp_sockaddr1; struct ast_sockaddr tmp_sockaddr2; ast_sockaddr_copy(&peer_addr, &peer->addr); ast_sockaddr_apply_netmask(addr, &peer->mask, &tmp_sockaddr1); ast_sockaddr_apply_netmask(&peer_addr, &peer->mask, &tmp_sockaddr2); if ((ast_strlen_zero(p->peer) || !strcmp(p->peer, peer->name)) /* No peer specified at our end, or this is the peer */ && (ast_strlen_zero(peer->username) || (!strcmp(peer->username, p->username))) /* No username specified in peer rule, or this is the right username */ && (ast_sockaddr_isnull(&peer_addr) || !(ast_sockaddr_cmp_addr(&tmp_sockaddr1, &tmp_sockaddr2))) /* No specified host, or this is our host */ ) { res = authenticate(p->challenge, peer->secret, peer->outkey, authmethods, &ied, addr, p); if (!res) { peer_unref(peer); break; } } peer_unref(peer); } ao2_iterator_destroy(&i); if (!peer) { /* We checked our list and didn't find one. It's unlikely, but possible, that we're trying to authenticate *to* a realtime peer */ const char *peer_name = ast_strdupa(p->peer); ast_mutex_unlock(&iaxsl[callno]); if ((peer = realtime_peer(peer_name, NULL))) { ast_mutex_lock(&iaxsl[callno]); if (!(p = iaxs[callno])) { peer_unref(peer); return -1; } res = authenticate(p->challenge, peer->secret,peer->outkey, authmethods, &ied, addr, p); peer_unref(peer); } if (!peer) { ast_mutex_lock(&iaxsl[callno]); if (!(p = iaxs[callno])) return -1; } } } if (ies->encmethods) { ast_set_flag64(p, IAX_ENCRYPTED | IAX_KEYPOPULATED); } else if (ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT)) { ast_log(LOG_NOTICE, "Call initiated without encryption while forceencryption=yes option is set\n"); return -1; /* if force encryption is yes, and no encryption methods, then return -1 to hangup */ } if (!res) { struct ast_datastore *variablestore; struct ast_variable *var, *prev = NULL; AST_LIST_HEAD(, ast_var_t) *varlist; varlist = ast_calloc(1, sizeof(*varlist)); variablestore = ast_datastore_alloc(&iax2_variable_datastore_info, NULL); if (variablestore && varlist && p->owner) { variablestore->data = varlist; variablestore->inheritance = DATASTORE_INHERIT_FOREVER; AST_LIST_HEAD_INIT(varlist); for (var = ies->vars; var; var = var->next) { struct ast_var_t *newvar = ast_var_assign(var->name, var->value); if (prev) ast_free(prev); prev = var; if (!newvar) { /* Don't abort list traversal, as this would leave ies->vars in an inconsistent state. */ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); } else { AST_LIST_INSERT_TAIL(varlist, newvar, entries); } } if (prev) ast_free(prev); ies->vars = NULL; ast_channel_datastore_add(p->owner, variablestore); } else { if (p->owner) ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); if (variablestore) ast_datastore_free(variablestore); if (varlist) ast_free(varlist); } } if (!res) res = send_command(p, AST_FRAME_IAX, IAX_COMMAND_AUTHREP, 0, ied.buf, ied.pos, -1); return res; } static int iax2_do_register(struct iax2_registry *reg); static void __iax2_do_register_s(const void *data) { struct iax2_registry *reg = (struct iax2_registry *)data; if (ast_sockaddr_isnull(®->addr)) { reg->addr.ss.ss_family = AST_AF_UNSPEC; ast_dnsmgr_lookup(reg->hostname, ®->addr, ®->dnsmgr, srvlookup ? "_iax._udp" : NULL); if (!ast_sockaddr_port(®->addr)) { ast_sockaddr_set_port(®->addr, reg->port); } else { reg->port = ast_sockaddr_port(®->addr); } } reg->expire = -1; iax2_do_register(reg); } static int iax2_do_register_s(const void *data) { #ifdef SCHED_MULTITHREADED if (schedule_action(__iax2_do_register_s, data)) #endif __iax2_do_register_s(data); return 0; } static int try_transfer(struct chan_iax2_pvt *pvt, struct iax_ies *ies) { int newcall = 0; struct iax_ie_data ied; struct ast_sockaddr new; memset(&ied, 0, sizeof(ied)); if (!ast_sockaddr_isnull(&ies->apparent_addr)) { ast_sockaddr_copy(&new, &ies->apparent_addr); } if (ies->callno) { newcall = ies->callno; } if (!newcall || ast_sockaddr_isnull(&new)) { ast_log(LOG_WARNING, "Invalid transfer request\n"); return -1; } pvt->transfercallno = newcall; ast_sockaddr_copy(&pvt->transfer, &new); pvt->transferid = ies->transferid; /* only store by transfercallno if this is a new transfer, * just in case we get a duplicate TXREQ */ if (pvt->transferring == TRANSFER_NONE) { store_by_transfercallno(pvt); } pvt->transferring = TRANSFER_BEGIN; if (ies->transferid) { iax_ie_append_int(&ied, IAX_IE_TRANSFERID, ies->transferid); } send_command_transfer(pvt, AST_FRAME_IAX, IAX_COMMAND_TXCNT, 0, ied.buf, ied.pos); return 0; } static int complete_dpreply(struct chan_iax2_pvt *pvt, struct iax_ies *ies) { char exten[256] = ""; int status = CACHE_FLAG_UNKNOWN, expiry = iaxdefaultdpcache, x, matchmore = 0; struct iax2_dpcache *dp = NULL; if (ies->called_number) ast_copy_string(exten, ies->called_number, sizeof(exten)); if (ies->dpstatus & IAX_DPSTATUS_EXISTS) status = CACHE_FLAG_EXISTS; else if (ies->dpstatus & IAX_DPSTATUS_CANEXIST) status = CACHE_FLAG_CANEXIST; else if (ies->dpstatus & IAX_DPSTATUS_NONEXISTENT) status = CACHE_FLAG_NONEXISTENT; if (ies->refresh) expiry = ies->refresh; if (ies->dpstatus & IAX_DPSTATUS_MATCHMORE) matchmore = CACHE_FLAG_MATCHMORE; AST_LIST_LOCK(&dpcache); AST_LIST_TRAVERSE_SAFE_BEGIN(&dpcache, dp, peer_list) { if (strcmp(dp->exten, exten)) continue; AST_LIST_REMOVE_CURRENT(peer_list); dp->callno = 0; dp->expiry.tv_sec = dp->orig.tv_sec + expiry; if (dp->flags & CACHE_FLAG_PENDING) { dp->flags &= ~CACHE_FLAG_PENDING; dp->flags |= status; dp->flags |= matchmore; } /* Wake up waiters */ for (x = 0; x < ARRAY_LEN(dp->waiters); x++) { if (dp->waiters[x] > -1) { if (write(dp->waiters[x], "asdf", 4) < 0) { } } } } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&dpcache); return 0; } static int complete_transfer(int callno, struct iax_ies *ies) { int peercallno = 0; struct chan_iax2_pvt *pvt = iaxs[callno]; struct iax_frame *cur; jb_frame frame; if (ies->callno) peercallno = ies->callno; if (peercallno < 1) { ast_log(LOG_WARNING, "Invalid transfer request\n"); return -1; } remove_by_transfercallno(pvt); /* since a transfer has taken place, the address will change. * This must be accounted for in the peercnts table. Remove * the old address and add the new one */ peercnt_remove_by_addr(&pvt->addr); peercnt_add(&pvt->transfer); /* now copy over the new address */ memcpy(&pvt->addr, &pvt->transfer, sizeof(pvt->addr)); memset(&pvt->transfer, 0, sizeof(pvt->transfer)); /* Reset sequence numbers */ pvt->oseqno = 0; pvt->rseqno = 0; pvt->iseqno = 0; pvt->aseqno = 0; if (pvt->peercallno) { remove_by_peercallno(pvt); } pvt->peercallno = peercallno; /*this is where the transfering call swiches hash tables */ store_by_peercallno(pvt); pvt->transferring = TRANSFER_NONE; pvt->svoiceformat = -1; pvt->voiceformat = 0; pvt->svideoformat = -1; pvt->videoformat = 0; pvt->transfercallno = 0; memset(&pvt->rxcore, 0, sizeof(pvt->rxcore)); memset(&pvt->offset, 0, sizeof(pvt->offset)); /* reset jitterbuffer */ while(jb_getall(pvt->jb,&frame) == JB_OK) iax2_frame_free(frame.data); jb_reset(pvt->jb); pvt->lag = 0; pvt->last = 0; pvt->lastsent = 0; pvt->nextpred = 0; pvt->pingtime = DEFAULT_RETRY_TIME; AST_LIST_TRAVERSE(&frame_queue[callno], cur, list) { /* We must cancel any packets that would have been transmitted because now we're talking to someone new. It's okay, they were transmitted to someone that didn't care anyway. */ cur->retries = -1; } return 0; } static void iax2_publish_registry(const char *username, const char *domain, const char *status, const char *cause) { ast_system_publish_registry("IAX2", username, domain, status, cause); } /*! \brief Acknowledgment received for OUR registration */ static int iax2_ack_registry(struct iax_ies *ies, struct ast_sockaddr *addr, int callno) { struct iax2_registry *reg; /* Start pessimistic */ char peer[256] = ""; char msgstatus[60]; int refresh = 60; char ourip[256] = ""; struct ast_sockaddr oldus; struct ast_sockaddr us; int oldmsgs; if (!ast_sockaddr_isnull(&ies->apparent_addr)) { ast_sockaddr_copy(&us, &ies->apparent_addr); } if (ies->username) { ast_copy_string(peer, ies->username, sizeof(peer)); } if (ies->refresh) { refresh = ies->refresh; } if (ies->calling_number) { /* We don't do anything with it really, but maybe we should */ } reg = iaxs[callno]->reg; if (!reg) { ast_log(LOG_WARNING, "Registry acknowledge on unknown registry '%s'\n", peer); return -1; } ast_sockaddr_copy(&oldus, ®->us); oldmsgs = reg->messages; if (ast_sockaddr_cmp(®->addr, addr)) { ast_log(LOG_WARNING, "Received unsolicited registry ack from '%s'\n", ast_sockaddr_stringify(addr)); return -1; } ast_sockaddr_copy(®->us, &us); if (ies->msgcount >= 0) { reg->messages = ies->msgcount & 0xffff; /* only low 16 bits are used in the transmission of the IE */ } /* always refresh the registration at the interval requested by the server we are registering to */ reg->refresh = refresh; reg->expire = iax2_sched_replace(reg->expire, sched, (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg); if (ast_sockaddr_cmp(&oldus, ®->us) || (reg->messages != oldmsgs)) { if (reg->messages > 255) { snprintf(msgstatus, sizeof(msgstatus), " with %d new and %d old messages waiting", reg->messages & 0xff, reg->messages >> 8); } else if (reg->messages > 1) { snprintf(msgstatus, sizeof(msgstatus), " with %d new messages waiting", reg->messages); } else if (reg->messages > 0) { ast_copy_string(msgstatus, " with 1 new message waiting", sizeof(msgstatus)); } else { ast_copy_string(msgstatus, " with no messages waiting", sizeof(msgstatus)); } snprintf(ourip, sizeof(ourip), "%s", ast_sockaddr_stringify(®->us)); ast_verb(3, "Registered IAX2 to '%s', who sees us as %s%s\n", ast_sockaddr_stringify(addr), ourip, msgstatus); iax2_publish_registry(reg->username, ast_sockaddr_stringify(addr), "Registered", NULL); } reg->regstate = REG_STATE_REGISTERED; return 0; } static int iax2_append_register(const char *hostname, const char *username, const char *secret, const char *porta) { struct iax2_registry *reg; if (!(reg = ast_calloc(1, sizeof(*reg) + strlen(hostname) + 1))) { return -1; } reg->addr.ss.ss_family = AST_AF_UNSPEC; if (ast_dnsmgr_lookup(hostname, ®->addr, ®->dnsmgr, srvlookup ? "_iax._udp" : NULL) < 0) { ast_free(reg); return -1; } ast_copy_string(reg->username, username, sizeof(reg->username)); strcpy(reg->hostname, hostname); /* Note: This is safe */ if (secret) { ast_copy_string(reg->secret, secret, sizeof(reg->secret)); } reg->expire = -1; reg->refresh = IAX_DEFAULT_REG_EXPIRE; reg->port = ast_sockaddr_port(®->addr); if (!porta && !reg->port) { reg->port = IAX_DEFAULT_PORTNO; } else if (porta) { sscanf(porta, "%5d", ®->port); } ast_sockaddr_set_port(®->addr, reg->port); AST_LIST_LOCK(®istrations); AST_LIST_INSERT_HEAD(®istrations, reg, entry); AST_LIST_UNLOCK(®istrations); return 0; } static int iax2_register(const char *value, int lineno) { char copy[256]; char *username, *hostname, *secret; char *porta; char *stringp=NULL; if (!value) return -1; ast_copy_string(copy, value, sizeof(copy)); stringp = copy; username = strsep(&stringp, "@"); hostname = strsep(&stringp, "@"); if (!hostname) { ast_log(LOG_WARNING, "Format for registration is user[:secret]@host[:port] at line %d\n", lineno); return -1; } stringp = username; username = strsep(&stringp, ":"); secret = strsep(&stringp, ":"); stringp = hostname; hostname = strsep(&stringp, ":"); porta = strsep(&stringp, ":"); if (porta && !atoi(porta)) { ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno); return -1; } return iax2_append_register(hostname, username, secret, porta); } static void register_peer_exten(struct iax2_peer *peer, int onoff) { char multi[256]; char *stringp, *ext; if (!ast_strlen_zero(regcontext)) { ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi)); stringp = multi; while((ext = strsep(&stringp, "&"))) { if (onoff) { if (!ast_exists_extension(NULL, regcontext, ext, 1, NULL)) ast_add_extension(regcontext, 1, ext, 1, NULL, NULL, "Noop", ast_strdup(peer->name), ast_free_ptr, "IAX2"); } else ast_context_remove_extension(regcontext, ext, 1, NULL); } } } static void prune_peers(void); static void unlink_peer(struct iax2_peer *peer) { if (peer->expire > -1) { if (!AST_SCHED_DEL(sched, peer->expire)) { peer->expire = -1; peer_unref(peer); } } if (peer->pokeexpire > -1) { if (!AST_SCHED_DEL(sched, peer->pokeexpire)) { peer->pokeexpire = -1; peer_unref(peer); } } ao2_unlink(peers, peer); } static void __expire_registry(const void *data) { struct iax2_peer *peer = (struct iax2_peer *) data; RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); if (!peer) return; if (peer->expire == -1) { /* Removed already (possibly through CLI), ignore */ return; } peer->expire = -1; ast_debug(1, "Expiring registration for peer '%s'\n", peer->name); if (ast_test_flag64((&globalflags), IAX_RTUPDATE) && (ast_test_flag64(peer, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) realtime_update_peer(peer->name, &peer->addr, 0); 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); /* modify entry in peercnts table as _not_ registered */ peercnt_modify((unsigned char) 0, 0, &peer->addr); /* Reset the address */ ast_sockaddr_setnull(&peer->addr); /* Reset expiry value */ peer->expiry = min_reg_expire; if (!ast_test_flag64(peer, IAX_TEMPONLY)) ast_db_del("IAX/Registry", peer->name); register_peer_exten(peer, 0); ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */ if (iax2_regfunk) iax2_regfunk(peer->name, 0); if (ast_test_flag64(peer, IAX_RTAUTOCLEAR)) unlink_peer(peer); peer_unref(peer); } static int expire_registry(const void *data) { #ifdef SCHED_MULTITHREADED if (schedule_action(__expire_registry, data)) #endif __expire_registry(data); return 0; } static void reg_source_db(struct iax2_peer *p) { char data[80]; char *expiry; if (ast_test_flag64(p, IAX_TEMPONLY) || ast_db_get("IAX/Registry", p->name, data, sizeof(data))) { return; } expiry = strrchr(data, ':'); if (!expiry) { ast_log(LOG_NOTICE, "IAX/Registry astdb entry missing expiry: '%s'\n", data); return; } *expiry++ = '\0'; if (!ast_sockaddr_parse(&p->addr, data, PARSE_PORT_REQUIRE)) { ast_log(LOG_NOTICE, "IAX/Registry astdb host:port invalid - '%s'\n", data); return; } p->expiry = atoi(expiry); ast_verb(3, "Seeding '%s' at %s for %d\n", p->name, ast_sockaddr_stringify(&p->addr), p->expiry); iax2_poke_peer(p, 0); if (p->expire > -1) { if (!AST_SCHED_DEL(sched, p->expire)) { p->expire = -1; peer_unref(p); } } ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */ p->expire = iax2_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, peer_ref(p)); if (p->expire == -1) { peer_unref(p); } if (iax2_regfunk) { iax2_regfunk(p->name, 1); } register_peer_exten(p, 1); } /*! * \pre iaxsl[callno] is locked * * \note Since this function calls send_command_final(), the pvt struct for * the given call number may disappear while executing this function. */ static int update_registry(struct ast_sockaddr *addr, int callno, char *devtype, int fd, unsigned short refresh) { /* Called from IAX thread only, with proper iaxsl lock */ struct iax_ie_data ied = { .pos = 0, }; struct iax2_peer *p; int msgcount; char data[80]; uint16_t version; const char *peer_name; int res = -1; char *str_addr; peer_name = ast_strdupa(iaxs[callno]->peer); /* SLD: Another find_peer call during registration - this time when we are really updating our registration */ ast_mutex_unlock(&iaxsl[callno]); if (!(p = find_peer(peer_name, 1))) { ast_mutex_lock(&iaxsl[callno]); ast_log(LOG_WARNING, "No such peer '%s'\n", peer_name); return -1; } ast_mutex_lock(&iaxsl[callno]); if (!iaxs[callno]) goto return_unref; if (ast_test_flag64((&globalflags), IAX_RTUPDATE) && (ast_test_flag64(p, IAX_TEMPONLY|IAX_RTCACHEFRIENDS))) { if (!ast_sockaddr_isnull(addr)) { time_t nowtime; time(&nowtime); realtime_update_peer(peer_name, addr, nowtime); } else { realtime_update_peer(peer_name, addr, 0); } } /* treat an unspecified refresh interval as the minimum */ if (!refresh) { refresh = min_reg_expire; } if (refresh > max_reg_expire) { ast_log(LOG_NOTICE, "Restricting registration for peer '%s' to %d seconds (requested %d)\n", p->name, max_reg_expire, refresh); p->expiry = max_reg_expire; } else if (refresh < min_reg_expire) { ast_log(LOG_NOTICE, "Restricting registration for peer '%s' to %d seconds (requested %d)\n", p->name, min_reg_expire, refresh); p->expiry = min_reg_expire; } else { p->expiry = refresh; } if (ast_sockaddr_cmp(&p->addr, addr)) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); if (iax2_regfunk) { iax2_regfunk(p->name, 1); } /* modify entry in peercnts table as _not_ registered */ peercnt_modify((unsigned char) 0, 0, &p->addr); /* Stash the IP address from which they registered */ ast_sockaddr_copy(&p->addr, addr); str_addr = ast_strdupa(ast_sockaddr_stringify_addr(addr)); snprintf(data, sizeof(data), "%s:%d", ast_sockaddr_stringify(addr), p->expiry); if (!ast_test_flag64(p, IAX_TEMPONLY) && !ast_sockaddr_isnull(addr)) { ast_db_put("IAX/Registry", p->name, data); ast_verb(3, "Registered IAX2 '%s' (%s) at %s\n", p->name, ast_test_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED) ? "AUTHENTICATED" : "UNAUTHENTICATED", ast_sockaddr_stringify(addr)); ast_endpoint_set_state(p->endpoint, AST_ENDPOINT_ONLINE); blob = ast_json_pack("{s: s, s: s, s: i}", "peer_status", "Registered", "address", str_addr, "port", ast_sockaddr_port(addr)); register_peer_exten(p, 1); ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */ } else if (!ast_test_flag64(p, IAX_TEMPONLY)) { ast_verb(3, "Unregistered IAX2 '%s' (%s)\n", p->name, ast_test_flag(&iaxs[callno]->state, IAX_STATE_AUTHENTICATED) ? "AUTHENTICATED" : "UNAUTHENTICATED"); ast_endpoint_set_state(p->endpoint, AST_ENDPOINT_OFFLINE); blob = ast_json_pack("{s: s}", "peer_status", "Unregistered"); register_peer_exten(p, 0); ast_db_del("IAX/Registry", p->name); ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", p->name); /* Activate notification */ } ast_endpoint_blob_publish(p->endpoint, ast_endpoint_state_type(), blob); /* Update the host */ /* Verify that the host is really there */ iax2_poke_peer(p, callno); } /* modify entry in peercnts table as registered */ if (p->maxcallno) { peercnt_modify((unsigned char) 1, p->maxcallno, &p->addr); } /* Make sure our call still exists, an INVAL at the right point may make it go away */ if (!iaxs[callno]) { res = -1; goto return_unref; } /* Store socket fd */ p->sockfd = fd; /* Setup the expiry */ if (p->expire > -1) { if (!AST_SCHED_DEL(sched, p->expire)) { p->expire = -1; peer_unref(p); } } if (p->expiry && !ast_sockaddr_isnull(addr)) { p->expire = iax2_sched_add(sched, (p->expiry + 10) * 1000, expire_registry, peer_ref(p)); if (p->expire == -1) peer_unref(p); } iax_ie_append_str(&ied, IAX_IE_USERNAME, p->name); iax_ie_append_int(&ied, IAX_IE_DATETIME, iax2_datetime(p->zonetag)); if (!ast_sockaddr_isnull(addr)) { struct ast_sockaddr peer_addr; ast_sockaddr_copy(&peer_addr, &p->addr); iax_ie_append_short(&ied, IAX_IE_REFRESH, p->expiry); iax_ie_append_addr(&ied, IAX_IE_APPARENT_ADDR, &peer_addr); if (!ast_strlen_zero(p->mailbox)) { int new, old; RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), p->mailbox); if (msg) { struct ast_mwi_state *mwi_state = stasis_message_data(msg); new = mwi_state->new_msgs; old = mwi_state->old_msgs; } else { /* Fall back on checking the mailbox directly */ ast_app_inboxcount(p->mailbox, &new, &old); } if (new > 255) { new = 255; } if (old > 255) { old = 255; } msgcount = (old << 8) | new; iax_ie_append_short(&ied, IAX_IE_MSGCOUNT, msgcount); } if (ast_test_flag64(p, IAX_HASCALLERID)) { iax_ie_append_str(&ied, IAX_IE_CALLING_NUMBER, p->cid_num); iax_ie_append_str(&ied, IAX_IE_CALLING_NAME, p->cid_name); } } if (iax_firmware_get_version(devtype, &version)) { iax_ie_append_short(&ied, IAX_IE_FIRMWAREVER, version); } res = 0; return_unref: peer_unref(p); return res ? res : send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1); } static int registry_authrequest(int callno) { struct iax_ie_data ied; struct iax2_peer *p; char challenge[10]; const char *peer_name; int sentauthmethod; peer_name = ast_strdupa(iaxs[callno]->peer); /* SLD: third call to find_peer in registration */ ast_mutex_unlock(&iaxsl[callno]); if ((p = find_peer(peer_name, 1))) { last_authmethod = p->authmethods; } ast_mutex_lock(&iaxsl[callno]); if (!iaxs[callno]) goto return_unref; memset(&ied, 0, sizeof(ied)); /* The selection of which delayed reject is sent may leak information, * if it sets a static response. For example, if a host is known to only * use MD5 authentication, then an RSA response would indicate that the * peer does not exist, and vice-versa. * Therefore, we use whatever the last peer used (which may vary over the * course of a server, which should leak minimal information). */ sentauthmethod = p ? p->authmethods : last_authmethod ? last_authmethod : (IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT); if (!p) { iaxs[callno]->authmethods = sentauthmethod; } iax_ie_append_short(&ied, IAX_IE_AUTHMETHODS, sentauthmethod); if (sentauthmethod & (IAX_AUTH_RSA | IAX_AUTH_MD5)) { /* Build the challenge */ snprintf(challenge, sizeof(challenge), "%d", (int)ast_random()); ast_string_field_set(iaxs[callno], challenge, challenge); iax_ie_append_str(&ied, IAX_IE_CHALLENGE, iaxs[callno]->challenge); } iax_ie_append_str(&ied, IAX_IE_USERNAME, peer_name); return_unref: if (p) { peer_unref(p); } return iaxs[callno] ? send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGAUTH, 0, ied.buf, ied.pos, -1) : -1; } static int registry_rerequest(struct iax_ies *ies, int callno, struct ast_sockaddr *addr) { struct iax2_registry *reg; /* Start pessimistic */ struct iax_ie_data ied; char peer[256] = ""; char challenge[256] = ""; int res; int authmethods = 0; if (ies->authmethods) authmethods = ies->authmethods; if (ies->username) ast_copy_string(peer, ies->username, sizeof(peer)); if (ies->challenge) ast_copy_string(challenge, ies->challenge, sizeof(challenge)); memset(&ied, 0, sizeof(ied)); reg = iaxs[callno]->reg; if (reg) { if (ast_sockaddr_cmp(®->addr, addr)) { ast_log(LOG_WARNING, "Received unsolicited registry authenticate request from '%s'\n", ast_sockaddr_stringify(addr)); return -1; } if (ast_strlen_zero(reg->secret)) { ast_log(LOG_NOTICE, "No secret associated with peer '%s'\n", reg->username); reg->regstate = REG_STATE_NOAUTH; return -1; } iax_ie_append_str(&ied, IAX_IE_USERNAME, reg->username); iax_ie_append_short(&ied, IAX_IE_REFRESH, reg->refresh); if (reg->secret[0] == '[') { char tmpkey[256]; ast_copy_string(tmpkey, reg->secret + 1, sizeof(tmpkey)); tmpkey[strlen(tmpkey) - 1] = '\0'; res = authenticate(challenge, NULL, tmpkey, authmethods, &ied, addr, NULL); } else res = authenticate(challenge, reg->secret, NULL, authmethods, &ied, addr, NULL); if (!res) { reg->regstate = REG_STATE_AUTHSENT; add_empty_calltoken_ie(iaxs[callno], &ied); /* this _MUST_ be the last ie added */ return send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGREQ, 0, ied.buf, ied.pos, -1); } else return -1; ast_log(LOG_WARNING, "Registry acknowledge on unknown registery '%s'\n", peer); } else ast_log(LOG_NOTICE, "Can't reregister without a reg\n"); return -1; } static void stop_stuff(int callno) { iax2_destroy_helper(iaxs[callno]); } static void __auth_reject(const void *nothing) { /* Called from IAX thread only, without iaxs lock */ int callno = (int)(long)(nothing); struct iax_ie_data ied; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { memset(&ied, 0, sizeof(ied)); if (iaxs[callno]->authfail == IAX_COMMAND_REGREJ) { iax_ie_append_str(&ied, IAX_IE_CAUSE, "Registration Refused"); iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_FACILITY_REJECTED); } else if (iaxs[callno]->authfail == IAX_COMMAND_REJECT) { iax_ie_append_str(&ied, IAX_IE_CAUSE, "No authority found"); iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_FACILITY_NOT_SUBSCRIBED); } send_command_final(iaxs[callno], AST_FRAME_IAX, iaxs[callno]->authfail, 0, ied.buf, ied.pos, -1); } ast_mutex_unlock(&iaxsl[callno]); } static int auth_reject(const void *data) { int callno = (int)(long)(data); ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) iaxs[callno]->authid = -1; ast_mutex_unlock(&iaxsl[callno]); #ifdef SCHED_MULTITHREADED if (schedule_action(__auth_reject, data)) #endif __auth_reject(data); return 0; } static int auth_fail(int callno, int failcode) { /* Schedule sending the authentication failure in one second, to prevent guessing */ if (iaxs[callno]) { iaxs[callno]->authfail = failcode; if (delayreject) { iaxs[callno]->authid = iax2_sched_replace(iaxs[callno]->authid, sched, 1000, auth_reject, (void *)(long)callno); } else auth_reject((void *)(long)callno); } return 0; } static void __auto_hangup(const void *nothing) { /* Called from IAX thread only, without iaxs lock */ int callno = (int)(long)(nothing); struct iax_ie_data ied; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { memset(&ied, 0, sizeof(ied)); iax_ie_append_str(&ied, IAX_IE_CAUSE, "Timeout"); iax_ie_append_byte(&ied, IAX_IE_CAUSECODE, AST_CAUSE_NO_USER_RESPONSE); send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_HANGUP, 0, ied.buf, ied.pos, -1); } ast_mutex_unlock(&iaxsl[callno]); } static int auto_hangup(const void *data) { int callno = (int)(long)(data); ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { iaxs[callno]->autoid = -1; } ast_mutex_unlock(&iaxsl[callno]); #ifdef SCHED_MULTITHREADED if (schedule_action(__auto_hangup, data)) #endif __auto_hangup(data); return 0; } static void iax2_dprequest(struct iax2_dpcache *dp, int callno) { struct iax_ie_data ied; /* Auto-hangup with 30 seconds of inactivity */ iaxs[callno]->autoid = iax2_sched_replace(iaxs[callno]->autoid, sched, 30000, auto_hangup, (void *)(long)callno); memset(&ied, 0, sizeof(ied)); iax_ie_append_str(&ied, IAX_IE_CALLED_NUMBER, dp->exten); send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_DPREQ, 0, ied.buf, ied.pos, -1); dp->flags |= CACHE_FLAG_TRANSMITTED; } static int iax2_vnak(int callno) { return send_command_immediate(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_VNAK, 0, NULL, 0, iaxs[callno]->iseqno); } static void vnak_retransmit(int callno, int last) { struct iax_frame *f; AST_LIST_TRAVERSE(&frame_queue[callno], f, list) { /* Send a copy immediately */ if (((unsigned char) (f->oseqno - last) < 128) && (f->retries >= 0)) { send_packet(f); } } } static void __iax2_poke_peer_s(const void *data) { struct iax2_peer *peer = (struct iax2_peer *)data; iax2_poke_peer(peer, 0); peer_unref(peer); } static int iax2_poke_peer_s(const void *data) { struct iax2_peer *peer = (struct iax2_peer *)data; peer->pokeexpire = -1; #ifdef SCHED_MULTITHREADED if (schedule_action(__iax2_poke_peer_s, data)) #endif __iax2_poke_peer_s(data); return 0; } static int send_trunk(struct iax2_trunk_peer *tpeer, struct timeval *now) { int res = 0; struct iax_frame *fr; struct ast_iax2_meta_hdr *meta; struct ast_iax2_meta_trunk_hdr *mth; int calls = 0; /* Point to frame */ fr = (struct iax_frame *)tpeer->trunkdata; /* Point to meta data */ meta = (struct ast_iax2_meta_hdr *)fr->afdata; mth = (struct ast_iax2_meta_trunk_hdr *)meta->data; if (tpeer->trunkdatalen) { /* We're actually sending a frame, so fill the meta trunk header and meta header */ meta->zeros = 0; meta->metacmd = IAX_META_TRUNK; if (ast_test_flag64(&globalflags, IAX_TRUNKTIMESTAMPS)) meta->cmddata = IAX_META_TRUNK_MINI; else meta->cmddata = IAX_META_TRUNK_SUPERMINI; mth->ts = htonl(calc_txpeerstamp(tpeer, trunkfreq, now)); /* And the rest of the ast_iax2 header */ fr->direction = DIRECTION_OUTGRESS; fr->retrans = -1; fr->transfer = 0; /* Any appropriate call will do */ fr->data = fr->afdata; fr->datalen = tpeer->trunkdatalen + sizeof(struct ast_iax2_meta_hdr) + sizeof(struct ast_iax2_meta_trunk_hdr); res = transmit_trunk(fr, &tpeer->addr, tpeer->sockfd); calls = tpeer->calls; #if 0 ast_debug(1, "Trunking %d call chunks in %d bytes to %s:%d, ts=%d\n", calls, fr->datalen, ast_inet_ntoa(tpeer->addr.sin_addr), ntohs(tpeer->addr.sin_port), ntohl(mth->ts)); #endif /* Reset transmit trunk side data */ tpeer->trunkdatalen = 0; tpeer->calls = 0; } if (res < 0) return res; return calls; } static inline int iax2_trunk_expired(struct iax2_trunk_peer *tpeer, struct timeval *now) { /* Drop when trunk is about 5 seconds idle */ if (now->tv_sec > tpeer->trunkact.tv_sec + 5) return 1; return 0; } static int timing_read(int *id, int fd, short events, void *cbdata) { int res, processed = 0, totalcalls = 0; struct iax2_trunk_peer *tpeer = NULL, *drop = NULL; struct timeval now = ast_tvnow(); if (iaxtrunkdebug) { ast_verbose("Beginning trunk processing. Trunk queue ceiling is %d bytes per host\n", trunkmaxsize); } if (timer) { if (ast_timer_ack(timer, 1) < 0) { ast_log(LOG_ERROR, "Timer failed acknowledge\n"); return 0; } } /* For each peer that supports trunking... */ AST_LIST_LOCK(&tpeers); AST_LIST_TRAVERSE_SAFE_BEGIN(&tpeers, tpeer, list) { processed++; res = 0; ast_mutex_lock(&tpeer->lock); /* We can drop a single tpeer per pass. That makes all this logic substantially easier */ if (!drop && iax2_trunk_expired(tpeer, &now)) { /* Take it out of the list, but don't free it yet, because it could be in use */ AST_LIST_REMOVE_CURRENT(list); drop = tpeer; } else { res = send_trunk(tpeer, &now); trunk_timed++; if (iaxtrunkdebug) { ast_verbose(" - Trunk peer (%s) has %d call chunk%s in transit, %u bytes backloged and has hit a high water mark of %u bytes\n", ast_sockaddr_stringify(&tpeer->addr), res, (res != 1) ? "s" : "", tpeer->trunkdatalen, tpeer->trunkdataalloc); } } totalcalls += res; res = 0; ast_mutex_unlock(&tpeer->lock); } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&tpeers); if (drop) { ast_mutex_lock(&drop->lock); /* Once we have this lock, we're sure nobody else is using it or could use it once we release it, because by the time they could get tpeerlock, we've already grabbed it */ ast_debug(1, "Dropping unused iax2 trunk peer '%s'\n", ast_sockaddr_stringify(&drop->addr)); if (drop->trunkdata) { ast_free(drop->trunkdata); drop->trunkdata = NULL; } ast_mutex_unlock(&drop->lock); ast_mutex_destroy(&drop->lock); ast_free(drop); } if (iaxtrunkdebug) { ast_verbose("Ending trunk processing with %d peers and %d call chunks processed\n", processed, totalcalls); } iaxtrunkdebug = 0; return 1; } struct dpreq_data { int callno; char context[AST_MAX_EXTENSION]; char callednum[AST_MAX_EXTENSION]; char *callerid; }; static void dp_lookup(int callno, const char *context, const char *callednum, const char *callerid, int skiplock) { unsigned short dpstatus = 0; struct iax_ie_data ied1; int mm; memset(&ied1, 0, sizeof(ied1)); mm = ast_matchmore_extension(NULL, context, callednum, 1, callerid); /* Must be started */ if (ast_exists_extension(NULL, context, callednum, 1, callerid)) { dpstatus = IAX_DPSTATUS_EXISTS; } else if (ast_canmatch_extension(NULL, context, callednum, 1, callerid)) { dpstatus = IAX_DPSTATUS_CANEXIST; } else { dpstatus = IAX_DPSTATUS_NONEXISTENT; } if (ast_ignore_pattern(context, callednum)) dpstatus |= IAX_DPSTATUS_IGNOREPAT; if (mm) dpstatus |= IAX_DPSTATUS_MATCHMORE; if (!skiplock) ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno]) { iax_ie_append_str(&ied1, IAX_IE_CALLED_NUMBER, callednum); iax_ie_append_short(&ied1, IAX_IE_DPSTATUS, dpstatus); iax_ie_append_short(&ied1, IAX_IE_REFRESH, iaxdefaultdpcache); send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_DPREP, 0, ied1.buf, ied1.pos, -1); } if (!skiplock) ast_mutex_unlock(&iaxsl[callno]); } static void *dp_lookup_thread(void *data) { /* Look up for dpreq */ struct dpreq_data *dpr = data; dp_lookup(dpr->callno, dpr->context, dpr->callednum, dpr->callerid, 0); if (dpr->callerid) ast_free(dpr->callerid); ast_free(dpr); return NULL; } static void spawn_dp_lookup(int callno, const char *context, const char *callednum, const char *callerid) { pthread_t newthread; struct dpreq_data *dpr; if (!(dpr = ast_calloc(1, sizeof(*dpr)))) return; dpr->callno = callno; ast_copy_string(dpr->context, context, sizeof(dpr->context)); ast_copy_string(dpr->callednum, callednum, sizeof(dpr->callednum)); if (callerid) dpr->callerid = ast_strdup(callerid); if (ast_pthread_create_detached(&newthread, NULL, dp_lookup_thread, dpr)) { ast_log(LOG_WARNING, "Unable to start lookup thread!\n"); } } static int check_provisioning(struct ast_sockaddr *addr, int sockfd, char *si, unsigned int ver) { unsigned int ourver; char rsi[80]; snprintf(rsi, sizeof(rsi), "si-%s", si); if (iax_provision_version(&ourver, rsi, 1)) return 0; ast_debug(1, "Service identifier '%s', we think '%08x', they think '%08x'\n", si, ourver, ver); if (ourver != ver) iax2_provision(addr, sockfd, NULL, rsi, 1); return 0; } static void construct_rr(struct chan_iax2_pvt *pvt, struct iax_ie_data *iep) { jb_info stats; jb_getinfo(pvt->jb, &stats); memset(iep, 0, sizeof(*iep)); iax_ie_append_int(iep,IAX_IE_RR_JITTER, stats.jitter); if(stats.frames_in == 0) stats.frames_in = 1; iax_ie_append_int(iep,IAX_IE_RR_LOSS, ((0xff & (stats.losspct/1000)) << 24 | (stats.frames_lost & 0x00ffffff))); iax_ie_append_int(iep,IAX_IE_RR_PKTS, stats.frames_in); iax_ie_append_short(iep,IAX_IE_RR_DELAY, stats.current - stats.min); iax_ie_append_int(iep,IAX_IE_RR_DROPPED, stats.frames_dropped); iax_ie_append_int(iep,IAX_IE_RR_OOO, stats.frames_ooo); } static void save_rr(struct iax_frame *fr, struct iax_ies *ies) { iaxs[fr->callno]->remote_rr.jitter = ies->rr_jitter; iaxs[fr->callno]->remote_rr.losspct = ies->rr_loss >> 24; iaxs[fr->callno]->remote_rr.losscnt = ies->rr_loss & 0xffffff; iaxs[fr->callno]->remote_rr.packets = ies->rr_pkts; iaxs[fr->callno]->remote_rr.delay = ies->rr_delay; iaxs[fr->callno]->remote_rr.dropped = ies->rr_dropped; iaxs[fr->callno]->remote_rr.ooo = ies->rr_ooo; } static void save_osptoken(struct iax_frame *fr, struct iax_ies *ies) { int i; unsigned int length, offset = 0; char full_osptoken[IAX_MAX_OSPBUFF_SIZE]; for (i = 0; i < IAX_MAX_OSPBLOCK_NUM; i++) { length = ies->ospblocklength[i]; if (length != 0) { if (length > IAX_MAX_OSPBLOCK_SIZE) { /* OSP token block length wrong, clear buffer */ offset = 0; break; } else { memcpy(full_osptoken + offset, ies->osptokenblock[i], length); offset += length; } } else { break; } } *(full_osptoken + offset) = '\0'; if (strlen(full_osptoken) != offset) { /* OSP token length wrong, clear buffer */ *full_osptoken = '\0'; } ast_string_field_set(iaxs[fr->callno], osptoken, full_osptoken); } static void log_jitterstats(unsigned short callno) { int localjitter = -1, localdelay = 0, locallost = -1, locallosspct = -1, localdropped = 0, localooo = -1, localpackets = -1; jb_info jbinfo; ast_mutex_lock(&iaxsl[callno]); if (iaxs[callno] && iaxs[callno]->owner && ast_channel_name(iaxs[callno]->owner)) { if(ast_test_flag64(iaxs[callno], IAX_USEJITTERBUF)) { jb_getinfo(iaxs[callno]->jb, &jbinfo); localjitter = jbinfo.jitter; localdelay = jbinfo.current - jbinfo.min; locallost = jbinfo.frames_lost; locallosspct = jbinfo.losspct/1000; localdropped = jbinfo.frames_dropped; localooo = jbinfo.frames_ooo; localpackets = jbinfo.frames_in; } ast_debug(3, "JB STATS:%s ping=%u ljitterms=%d ljbdelayms=%d ltotlost=%d lrecentlosspct=%d ldropped=%d looo=%d lrecvd=%d rjitterms=%d rjbdelayms=%d rtotlost=%d rrecentlosspct=%d rdropped=%d rooo=%d rrecvd=%d\n", ast_channel_name(iaxs[callno]->owner), iaxs[callno]->pingtime, localjitter, localdelay, locallost, locallosspct, localdropped, localooo, localpackets, iaxs[callno]->remote_rr.jitter, iaxs[callno]->remote_rr.delay, iaxs[callno]->remote_rr.losscnt, iaxs[callno]->remote_rr.losspct/1000, iaxs[callno]->remote_rr.dropped, iaxs[callno]->remote_rr.ooo, iaxs[callno]->remote_rr.packets); } ast_mutex_unlock(&iaxsl[callno]); } static int socket_process(struct iax2_thread *thread); /*! * \brief Handle any deferred full frames for this thread */ static void handle_deferred_full_frames(struct iax2_thread *thread) { struct iax2_pkt_buf *pkt_buf; ast_mutex_lock(&thread->lock); while ((pkt_buf = AST_LIST_REMOVE_HEAD(&thread->full_frames, entry))) { ast_mutex_unlock(&thread->lock); thread->buf = pkt_buf->buf; thread->buf_len = pkt_buf->len; thread->buf_size = pkt_buf->len + 1; socket_process(thread); thread->buf = NULL; ast_free(pkt_buf); ast_mutex_lock(&thread->lock); } ast_mutex_unlock(&thread->lock); } /*! * \brief Queue the last read full frame for processing by a certain thread * * If there are already any full frames queued, they are sorted * by sequence number. */ static void defer_full_frame(struct iax2_thread *from_here, struct iax2_thread *to_here) { struct iax2_pkt_buf *pkt_buf, *cur_pkt_buf; struct ast_iax2_full_hdr *fh, *cur_fh; if (!(pkt_buf = ast_calloc(1, sizeof(*pkt_buf) + from_here->buf_len))) return; pkt_buf->len = from_here->buf_len; memcpy(pkt_buf->buf, from_here->buf, pkt_buf->len); fh = (struct ast_iax2_full_hdr *) pkt_buf->buf; ast_mutex_lock(&to_here->lock); AST_LIST_TRAVERSE_SAFE_BEGIN(&to_here->full_frames, cur_pkt_buf, entry) { cur_fh = (struct ast_iax2_full_hdr *) cur_pkt_buf->buf; if (fh->oseqno < cur_fh->oseqno) { AST_LIST_INSERT_BEFORE_CURRENT(pkt_buf, entry); break; } } AST_LIST_TRAVERSE_SAFE_END if (!cur_pkt_buf) AST_LIST_INSERT_TAIL(&to_here->full_frames, pkt_buf, entry); to_here->iostate = IAX_IOSTATE_READY; ast_cond_signal(&to_here->cond); ast_mutex_unlock(&to_here->lock); } static int socket_read(int *id, int fd, short events, void *cbdata) { struct iax2_thread *thread; time_t t; static time_t last_errtime = 0; struct ast_iax2_full_hdr *fh; if (!(thread = find_idle_thread())) { time(&t); if (t != last_errtime) { last_errtime = t; ast_debug(1, "Out of idle IAX2 threads for I/O, pausing!\n"); } usleep(1); return 1; } thread->iofd = fd; thread->buf_len = ast_recvfrom(fd, thread->readbuf, sizeof(thread->readbuf), 0, &thread->ioaddr); thread->buf_size = sizeof(thread->readbuf); thread->buf = thread->readbuf; if (thread->buf_len < 0) { if (errno != ECONNREFUSED && errno != EAGAIN) ast_log(LOG_WARNING, "Error: %s\n", strerror(errno)); handle_error(); thread->iostate = IAX_IOSTATE_IDLE; signal_condition(&thread->lock, &thread->cond); return 1; } if (test_losspct && ((100.0 * ast_random() / (RAND_MAX + 1.0)) < test_losspct)) { /* simulate random loss condition */ thread->iostate = IAX_IOSTATE_IDLE; signal_condition(&thread->lock, &thread->cond); return 1; } /* Determine if this frame is a full frame; if so, and any thread is currently processing a full frame for the same callno from this peer, then drop this frame (and the peer will retransmit it) */ fh = (struct ast_iax2_full_hdr *) thread->buf; if (ntohs(fh->scallno) & IAX_FLAG_FULL) { struct iax2_thread *cur = NULL; uint16_t callno = ntohs(fh->scallno) & ~IAX_FLAG_FULL; AST_LIST_LOCK(&active_list); AST_LIST_TRAVERSE(&active_list, cur, list) { if ((cur->ffinfo.callno == callno) && !ast_sockaddr_cmp_addr(&cur->ffinfo.addr, &thread->ioaddr)) break; } if (cur) { /* we found another thread processing a full frame for this call, so queue it up for processing later. */ defer_full_frame(thread, cur); AST_LIST_UNLOCK(&active_list); thread->iostate = IAX_IOSTATE_IDLE; signal_condition(&thread->lock, &thread->cond); return 1; } else { /* this thread is going to process this frame, so mark it */ thread->ffinfo.callno = callno; ast_sockaddr_copy(&thread->ffinfo.addr, &thread->ioaddr); thread->ffinfo.type = fh->type; thread->ffinfo.csub = fh->csub; AST_LIST_INSERT_HEAD(&active_list, thread, list); } AST_LIST_UNLOCK(&active_list); } /* Mark as ready and send on its way */ thread->iostate = IAX_IOSTATE_READY; #ifdef DEBUG_SCHED_MULTITHREAD ast_copy_string(thread->curfunc, "socket_process", sizeof(thread->curfunc)); #endif signal_condition(&thread->lock, &thread->cond); return 1; } static int socket_process_meta(int packet_len, struct ast_iax2_meta_hdr *meta, struct ast_sockaddr *addr, int sockfd, struct iax_frame *fr) { unsigned char metatype; struct ast_iax2_meta_trunk_mini *mtm; struct ast_iax2_meta_trunk_hdr *mth; struct ast_iax2_meta_trunk_entry *mte; struct iax2_trunk_peer *tpeer; unsigned int ts; void *ptr; struct timeval rxtrunktime; struct ast_frame f = { 0, }; if (packet_len < sizeof(*meta)) { ast_log(LOG_WARNING, "Rejecting packet from '%s' that is flagged as a meta frame but is too short\n", ast_sockaddr_stringify(addr)); return 1; } if (meta->metacmd != IAX_META_TRUNK) return 1; if (packet_len < (sizeof(*meta) + sizeof(*mth))) { ast_log(LOG_WARNING, "midget meta trunk packet received (%d of %d min)\n", packet_len, (int) (sizeof(*meta) + sizeof(*mth))); return 1; } mth = (struct ast_iax2_meta_trunk_hdr *)(meta->data); ts = ntohl(mth->ts); metatype = meta->cmddata; packet_len -= (sizeof(*meta) + sizeof(*mth)); ptr = mth->data; tpeer = find_tpeer(addr, sockfd); if (!tpeer) { ast_log(LOG_WARNING, "Unable to accept trunked packet from '%s': No matching peer\n", ast_sockaddr_stringify(addr)); return 1; } tpeer->trunkact = ast_tvnow(); if (!ts || ast_tvzero(tpeer->rxtrunktime)) tpeer->rxtrunktime = tpeer->trunkact; rxtrunktime = tpeer->rxtrunktime; ast_mutex_unlock(&tpeer->lock); while (packet_len >= sizeof(*mte)) { /* Process channels */ unsigned short callno, trunked_ts, len; if (metatype == IAX_META_TRUNK_MINI) { mtm = (struct ast_iax2_meta_trunk_mini *) ptr; ptr += sizeof(*mtm); packet_len -= sizeof(*mtm); len = ntohs(mtm->len); callno = ntohs(mtm->mini.callno); trunked_ts = ntohs(mtm->mini.ts); } else if (metatype == IAX_META_TRUNK_SUPERMINI) { mte = (struct ast_iax2_meta_trunk_entry *)ptr; ptr += sizeof(*mte); packet_len -= sizeof(*mte); len = ntohs(mte->len); callno = ntohs(mte->callno); trunked_ts = 0; } else { ast_log(LOG_WARNING, "Unknown meta trunk cmd from '%s': dropping\n", ast_sockaddr_stringify(addr)); break; } /* Stop if we don't have enough data */ if (len > packet_len) break; fr->callno = find_callno_locked(callno & ~IAX_FLAG_FULL, 0, addr, NEW_PREVENT, sockfd, 0); if (!fr->callno) continue; /* If it's a valid call, deliver the contents. If not, we drop it, since we don't have a scallno to use for an INVAL */ /* Process as a mini frame */ memset(&f, 0, sizeof(f)); f.frametype = AST_FRAME_VOICE; if (!iaxs[fr->callno]) { /* drop it */ } else if (iaxs[fr->callno]->voiceformat == 0) { ast_log(LOG_WARNING, "Received trunked frame before first full voice frame\n"); iax2_vnak(fr->callno); } else { f.subclass.format = ast_format_compatibility_bitfield2format(iaxs[fr->callno]->voiceformat); f.datalen = len; if (f.datalen >= 0) { if (f.datalen) f.data.ptr = ptr; else f.data.ptr = NULL; if (trunked_ts) fr->ts = (iaxs[fr->callno]->last & 0xFFFF0000L) | (trunked_ts & 0xffff); else fr->ts = fix_peerts(&rxtrunktime, fr->callno, ts); /* Don't pass any packets until we're started */ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) { struct iax_frame *duped_fr; /* Common things */ f.src = "IAX2"; f.mallocd = 0; f.offset = 0; if (f.datalen && (f.frametype == AST_FRAME_VOICE)) f.samples = ast_codec_samples_count(&f); else f.samples = 0; fr->outoforder = 0; iax_frame_wrap(fr, &f); duped_fr = iaxfrdup2(fr); if (duped_fr) schedule_delivery(duped_fr, 1, 1, &fr->ts); if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts) iaxs[fr->callno]->last = fr->ts; } } else { ast_log(LOG_WARNING, "Datalen < 0?\n"); } } ast_mutex_unlock(&iaxsl[fr->callno]); ptr += len; packet_len -= len; } return 1; } static int acf_iaxvar_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { struct ast_datastore *variablestore; AST_LIST_HEAD(, ast_var_t) *varlist; struct ast_var_t *var; if (!chan) { ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); return -1; } variablestore = ast_channel_datastore_find(chan, &iax2_variable_datastore_info, NULL); if (!variablestore) { *buf = '\0'; return 0; } varlist = variablestore->data; AST_LIST_LOCK(varlist); AST_LIST_TRAVERSE(varlist, var, entries) { if (strcmp(var->name, data) == 0) { ast_copy_string(buf, var->value, len); break; } } AST_LIST_UNLOCK(varlist); return 0; } static int acf_iaxvar_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) { struct ast_datastore *variablestore; AST_LIST_HEAD(, ast_var_t) *varlist; struct ast_var_t *var; if (!chan) { ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd); return -1; } variablestore = ast_channel_datastore_find(chan, &iax2_variable_datastore_info, NULL); if (!variablestore) { variablestore = ast_datastore_alloc(&iax2_variable_datastore_info, NULL); if (!variablestore) { ast_log(LOG_ERROR, "Memory allocation error\n"); return -1; } varlist = ast_calloc(1, sizeof(*varlist)); if (!varlist) { ast_datastore_free(variablestore); ast_log(LOG_ERROR, "Unable to assign new variable '%s'\n", data); return -1; } AST_LIST_HEAD_INIT(varlist); variablestore->data = varlist; variablestore->inheritance = DATASTORE_INHERIT_FOREVER; ast_channel_datastore_add(chan, variablestore); } else varlist = variablestore->data; AST_LIST_LOCK(varlist); AST_LIST_TRAVERSE_SAFE_BEGIN(varlist, var, entries) { if (strcmp(var->name, data) == 0) { AST_LIST_REMOVE_CURRENT(entries); ast_var_delete(var); break; } } AST_LIST_TRAVERSE_SAFE_END; var = ast_var_assign(data, value); if (var) AST_LIST_INSERT_TAIL(varlist, var, entries); else ast_log(LOG_ERROR, "Unable to assign new variable '%s'\n", data); AST_LIST_UNLOCK(varlist); return 0; } static struct ast_custom_function iaxvar_function = { .name = "IAXVAR", .read = acf_iaxvar_read, .write = acf_iaxvar_write, }; static void set_hangup_source_and_cause(int callno, unsigned char causecode) { iax2_lock_owner(callno); if (iaxs[callno] && iaxs[callno]->owner) { struct ast_channel *owner; const char *name; owner = iaxs[callno]->owner; if (causecode) { ast_channel_hangupcause_set(owner, causecode); } name = ast_strdupa(ast_channel_name(owner)); ast_channel_ref(owner); ast_channel_unlock(owner); ast_mutex_unlock(&iaxsl[callno]); ast_set_hangupsource(owner, name, 0); ast_channel_unref(owner); ast_mutex_lock(&iaxsl[callno]); } } static int socket_process_helper(struct iax2_thread *thread) { struct ast_sockaddr addr; int res; int updatehistory=1; int new = NEW_PREVENT; int dcallno = 0; char decrypted = 0; struct ast_iax2_full_hdr *fh = (struct ast_iax2_full_hdr *)thread->buf; struct ast_iax2_mini_hdr *mh = (struct ast_iax2_mini_hdr *)thread->buf; struct ast_iax2_meta_hdr *meta = (struct ast_iax2_meta_hdr *)thread->buf; struct ast_iax2_video_hdr *vh = (struct ast_iax2_video_hdr *)thread->buf; struct iax_frame *fr; struct iax_frame *cur; struct ast_frame f = { 0, }; struct ast_channel *c = NULL; struct iax2_dpcache *dp; struct iax2_peer *peer; struct iax_ies ies; struct iax_ie_data ied0, ied1; iax2_format format; int fd; int exists; int minivid = 0; char empty[32]=""; /* Safety measure */ struct iax_frame *duped_fr; char host_pref_buf[128]; char caller_pref_buf[128]; struct iax2_codec_pref pref; char *using_prefs = "mine"; /* allocate an iax_frame with 4096 bytes of data buffer */ fr = ast_alloca(sizeof(*fr) + 4096); memset(fr, 0, sizeof(*fr)); fr->afdatalen = 4096; /* From ast_alloca() above */ /* Copy frequently used parameters to the stack */ res = thread->buf_len; fd = thread->iofd; ast_sockaddr_copy(&addr, &thread->ioaddr); if (res < sizeof(*mh)) { ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int) sizeof(*mh)); return 1; } if ((vh->zeros == 0) && (ntohs(vh->callno) & 0x8000)) { if (res < sizeof(*vh)) { ast_log(LOG_WARNING, "Rejecting packet from '%s' that is flagged as a video frame but is too short\n", ast_sockaddr_stringify(&addr)); return 1; } /* This is a video frame, get call number */ fr->callno = find_callno(ntohs(vh->callno) & ~0x8000, dcallno, &addr, new, fd, 0); minivid = 1; } else if ((meta->zeros == 0) && !(ntohs(meta->metacmd) & 0x8000)) return socket_process_meta(res, meta, &addr, fd, fr); #ifdef DEBUG_SUPPORT if (res >= sizeof(*fh)) iax_outputframe(NULL, fh, 1, &addr, res - sizeof(*fh)); #endif if (ntohs(mh->callno) & IAX_FLAG_FULL) { if (res < sizeof(*fh)) { ast_log(LOG_WARNING, "Rejecting packet from '%s' that is flagged as a full frame but is too short\n", ast_sockaddr_stringify(&addr)); return 1; } /* Get the destination call number */ dcallno = ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS; /* check to make sure this full frame isn't encrypted before we attempt * to look inside of it. If it is encrypted, decrypt it first. Its ok if the * callno is not found here, that just means one hasn't been allocated for * this connection yet. */ if ((dcallno != 1) && (fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &addr, NEW_PREVENT, fd, 1))) { ast_mutex_lock(&iaxsl[fr->callno]); if (iaxs[fr->callno] && ast_test_flag64(iaxs[fr->callno], IAX_ENCRYPTED)) { if (decrypt_frame(fr->callno, fh, &f, &res)) { ast_log(LOG_NOTICE, "Packet Decrypt Failed!\n"); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } decrypted = 1; } ast_mutex_unlock(&iaxsl[fr->callno]); } /* Retrieve the type and subclass */ f.frametype = fh->type; if (f.frametype == AST_FRAME_VIDEO) { f.subclass.format = ast_format_compatibility_bitfield2format(uncompress_subclass(fh->csub & ~0x40)); if ((fh->csub >> 6) & 0x1) { f.subclass.frame_ending = 1; } } else if (f.frametype == AST_FRAME_VOICE) { f.subclass.format = ast_format_compatibility_bitfield2format(uncompress_subclass(fh->csub)); } else { f.subclass.integer = uncompress_subclass(fh->csub); } /* Deal with POKE/PONG without allocating a callno */ if (f.frametype == AST_FRAME_IAX && f.subclass.integer == IAX_COMMAND_POKE) { /* Reply back with a PONG, but don't care about the result. */ send_apathetic_reply(1, ntohs(fh->scallno), &addr, IAX_COMMAND_PONG, ntohl(fh->ts), fh->iseqno + 1, fd, NULL); return 1; } else if (f.frametype == AST_FRAME_IAX && f.subclass.integer == IAX_COMMAND_ACK && dcallno == 1) { /* Ignore */ return 1; } f.datalen = res - sizeof(*fh); if (f.datalen) { if (f.frametype == AST_FRAME_IAX) { if (iax_parse_ies(&ies, thread->buf + sizeof(struct ast_iax2_full_hdr), f.datalen)) { ast_log(LOG_WARNING, "Undecodable frame received from '%s'\n", ast_sockaddr_stringify(&addr)); ast_variables_destroy(ies.vars); return 1; } f.data.ptr = NULL; f.datalen = 0; } else { f.data.ptr = thread->buf + sizeof(struct ast_iax2_full_hdr); memset(&ies, 0, sizeof(ies)); } } else { if (f.frametype == AST_FRAME_IAX) f.data.ptr = NULL; else f.data.ptr = empty; memset(&ies, 0, sizeof(ies)); } if (!dcallno && iax2_allow_new(f.frametype, f.subclass.integer, 1)) { /* only set NEW_ALLOW if calltoken checks out */ if (handle_call_token(fh, &ies, &addr, fd)) { ast_variables_destroy(ies.vars); return 1; } if (ies.calltoken && ies.calltokendata) { /* if we've gotten this far, and the calltoken ie data exists, * then calltoken validation _MUST_ have taken place. If calltoken * data is provided, it is always validated reguardless of any * calltokenoptional or requirecalltoken options */ new = NEW_ALLOW_CALLTOKEN_VALIDATED; } else { new = NEW_ALLOW; } } } else { /* Don't know anything about it yet */ f.frametype = AST_FRAME_NULL; f.subclass.integer = 0; memset(&ies, 0, sizeof(ies)); } if (!fr->callno) { int check_dcallno = 0; /* * We enforce accurate destination call numbers for ACKs. This forces the other * end to know the destination call number before call setup can complete. * * Discussed in the following thread: * http://lists.digium.com/pipermail/asterisk-dev/2008-May/033217.html */ if ((ntohs(mh->callno) & IAX_FLAG_FULL) && ((f.frametype == AST_FRAME_IAX) && (f.subclass.integer == IAX_COMMAND_ACK))) { check_dcallno = 1; } if (!(fr->callno = find_callno(ntohs(mh->callno) & ~IAX_FLAG_FULL, dcallno, &addr, new, fd, check_dcallno))) { if (f.frametype == AST_FRAME_IAX && f.subclass.integer == IAX_COMMAND_NEW) { send_apathetic_reply(1, ntohs(fh->scallno), &addr, IAX_COMMAND_REJECT, ntohl(fh->ts), fh->iseqno + 1, fd, NULL); } else if (f.frametype == AST_FRAME_IAX && (f.subclass.integer == IAX_COMMAND_REGREQ || f.subclass.integer == IAX_COMMAND_REGREL)) { send_apathetic_reply(1, ntohs(fh->scallno), &addr, IAX_COMMAND_REGREJ, ntohl(fh->ts), fh->iseqno + 1, fd, NULL); } ast_variables_destroy(ies.vars); return 1; } } if (fr->callno > 0) { struct ast_callid *mount_callid; ast_mutex_lock(&iaxsl[fr->callno]); if (iaxs[fr->callno] && ((mount_callid = iax_pvt_callid_get(fr->callno)))) { /* Bind to thread */ ast_callid_threadassoc_add(mount_callid); ast_callid_unref(mount_callid); } } if (!fr->callno || !iaxs[fr->callno]) { /* A call arrived for a nonexistent destination. Unless it's an "inval" frame, reply with an inval */ if (ntohs(mh->callno) & IAX_FLAG_FULL) { /* We can only raw hangup control frames */ if (((f.subclass.integer != IAX_COMMAND_INVAL) && (f.subclass.integer != IAX_COMMAND_TXCNT) && (f.subclass.integer != IAX_COMMAND_TXACC) && (f.subclass.integer != IAX_COMMAND_FWDOWNL))|| (f.frametype != AST_FRAME_IAX)) raw_hangup(&addr, ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS, ntohs(mh->callno) & ~IAX_FLAG_FULL, fd); } if (fr->callno > 0){ ast_mutex_unlock(&iaxsl[fr->callno]); } ast_variables_destroy(ies.vars); return 1; } if (ast_test_flag64(iaxs[fr->callno], IAX_ENCRYPTED) && !decrypted) { if (decrypt_frame(fr->callno, fh, &f, &res)) { ast_log(LOG_NOTICE, "Packet Decrypt Failed!\n"); ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } decrypted = 1; } #ifdef DEBUG_SUPPORT if (decrypted) { iax_outputframe(NULL, fh, 3, &addr, res - sizeof(*fh)); } #endif if (iaxs[fr->callno]->owner && fh->type == AST_FRAME_IAX && (fh->csub == IAX_COMMAND_HANGUP || fh->csub == IAX_COMMAND_REJECT || fh->csub == IAX_COMMAND_REGREJ || fh->csub == IAX_COMMAND_TXREJ)) { struct ast_control_pvt_cause_code *cause_code; int data_size = sizeof(*cause_code); char subclass[40] = ""; /* get subclass text */ iax_frame_subclass2str(fh->csub, subclass, sizeof(subclass)); /* add length of "IAX2 " */ data_size += 5; /* for IAX hangup frames, add length of () and number */ data_size += 3; if (ies.causecode > 9) { data_size++; } if (ies.causecode > 99) { data_size++; } /* add length of subclass */ data_size += strlen(subclass); cause_code = ast_alloca(data_size); memset(cause_code, 0, data_size); ast_copy_string(cause_code->chan_name, ast_channel_name(iaxs[fr->callno]->owner), AST_CHANNEL_NAME); cause_code->ast_cause = ies.causecode; snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "IAX2 %s(%d)", subclass, ies.causecode); iax2_lock_owner(fr->callno); if (iaxs[fr->callno] && iaxs[fr->callno]->owner) { ast_queue_control_data(iaxs[fr->callno]->owner, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size); ast_channel_hangupcause_hash_set(iaxs[fr->callno]->owner, cause_code, data_size); ast_channel_unlock(iaxs[fr->callno]->owner); } if (!iaxs[fr->callno]) { ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } } /* count this frame */ iaxs[fr->callno]->frames_received++; if (!ast_sockaddr_cmp(&addr, &iaxs[fr->callno]->addr) && !minivid && f.subclass.integer != IAX_COMMAND_TXCNT && /* for attended transfer */ f.subclass.integer != IAX_COMMAND_TXACC) { /* for attended transfer */ unsigned short new_peercallno; new_peercallno = (unsigned short) (ntohs(mh->callno) & ~IAX_FLAG_FULL); if (new_peercallno && new_peercallno != iaxs[fr->callno]->peercallno) { if (iaxs[fr->callno]->peercallno) { remove_by_peercallno(iaxs[fr->callno]); } iaxs[fr->callno]->peercallno = new_peercallno; store_by_peercallno(iaxs[fr->callno]); } } if (ntohs(mh->callno) & IAX_FLAG_FULL) { if (iaxdebug) ast_debug(1, "Received packet %d, (%u, %d)\n", fh->oseqno, f.frametype, f.subclass.integer); /* Check if it's out of order (and not an ACK or INVAL) */ fr->oseqno = fh->oseqno; fr->iseqno = fh->iseqno; fr->ts = ntohl(fh->ts); #ifdef IAXTESTS if (test_resync) { ast_debug(1, "Simulating frame ts resync, was %u now %u\n", fr->ts, fr->ts + test_resync); fr->ts += test_resync; } #endif /* IAXTESTS */ #if 0 if ( (ntohs(fh->dcallno) & IAX_FLAG_RETRANS) || ( (f.frametype != AST_FRAME_VOICE) && ! (f.frametype == AST_FRAME_IAX && (f.subclass == IAX_COMMAND_NEW || f.subclass == IAX_COMMAND_AUTHREQ || f.subclass == IAX_COMMAND_ACCEPT || f.subclass == IAX_COMMAND_REJECT)) ) ) #endif if ((ntohs(fh->dcallno) & IAX_FLAG_RETRANS) || (f.frametype != AST_FRAME_VOICE)) updatehistory = 0; if ((iaxs[fr->callno]->iseqno != fr->oseqno) && (iaxs[fr->callno]->iseqno || ((f.subclass.integer != IAX_COMMAND_TXCNT) && (f.subclass.integer != IAX_COMMAND_TXREADY) && /* for attended transfer */ (f.subclass.integer != IAX_COMMAND_TXREL) && /* for attended transfer */ (f.subclass.integer != IAX_COMMAND_UNQUELCH ) && /* for attended transfer */ (f.subclass.integer != IAX_COMMAND_TXACC)) || (f.frametype != AST_FRAME_IAX))) { if ( ((f.subclass.integer != IAX_COMMAND_ACK) && (f.subclass.integer != IAX_COMMAND_INVAL) && (f.subclass.integer != IAX_COMMAND_TXCNT) && (f.subclass.integer != IAX_COMMAND_TXREADY) && /* for attended transfer */ (f.subclass.integer != IAX_COMMAND_TXREL) && /* for attended transfer */ (f.subclass.integer != IAX_COMMAND_UNQUELCH ) && /* for attended transfer */ (f.subclass.integer != IAX_COMMAND_TXACC) && (f.subclass.integer != IAX_COMMAND_VNAK)) || (f.frametype != AST_FRAME_IAX)) { /* If it's not an ACK packet, it's out of order. */ ast_debug(1, "Packet arrived out of order (expecting %d, got %d) (frametype = %u, subclass = %d)\n", iaxs[fr->callno]->iseqno, fr->oseqno, f.frametype, f.subclass.integer); /* Check to see if we need to request retransmission, * and take sequence number wraparound into account */ if ((unsigned char) (iaxs[fr->callno]->iseqno - fr->oseqno) < 128) { /* If we've already seen it, ack it XXX There's a border condition here XXX */ if ((f.frametype != AST_FRAME_IAX) || ((f.subclass.integer != IAX_COMMAND_ACK) && (f.subclass.integer != IAX_COMMAND_INVAL))) { ast_debug(1, "Acking anyway\n"); /* XXX Maybe we should handle its ack to us, but then again, it's probably outdated anyway, and if we have anything to send, we'll retransmit and get an ACK back anyway XXX */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); } } else { /* Send a VNAK requesting retransmission */ iax2_vnak(fr->callno); } ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } } else { /* Increment unless it's an ACK or VNAK */ if (((f.subclass.integer != IAX_COMMAND_ACK) && (f.subclass.integer != IAX_COMMAND_INVAL) && (f.subclass.integer != IAX_COMMAND_TXCNT) && (f.subclass.integer != IAX_COMMAND_TXACC) && (f.subclass.integer != IAX_COMMAND_VNAK)) || (f.frametype != AST_FRAME_IAX)) iaxs[fr->callno]->iseqno++; } /* Ensure text frames are NULL-terminated */ if (f.frametype == AST_FRAME_TEXT && thread->buf[res - 1] != '\0') { if (res < thread->buf_size) thread->buf[res++] = '\0'; else /* Trims one character from the text message, but that's better than overwriting the end of the buffer. */ thread->buf[res - 1] = '\0'; } /* Handle implicit ACKing unless this is an INVAL, and only if this is from the real peer, not the transfer peer */ if (!ast_sockaddr_cmp(&addr, &iaxs[fr->callno]->addr) && ((f.subclass.integer != IAX_COMMAND_INVAL) || (f.frametype != AST_FRAME_IAX))) { unsigned char x; int call_to_destroy; /* First we have to qualify that the ACKed value is within our window */ if (iaxs[fr->callno]->rseqno >= iaxs[fr->callno]->oseqno || (fr->iseqno >= iaxs[fr->callno]->rseqno && fr->iseqno < iaxs[fr->callno]->oseqno)) x = fr->iseqno; else x = iaxs[fr->callno]->oseqno; if ((x != iaxs[fr->callno]->oseqno) || (iaxs[fr->callno]->oseqno == fr->iseqno)) { /* The acknowledgement is within our window. Time to acknowledge everything that it says to */ for (x=iaxs[fr->callno]->rseqno; x != fr->iseqno; x++) { /* Ack the packet with the given timestamp */ if (iaxdebug) ast_debug(1, "Cancelling transmission of packet %d\n", x); call_to_destroy = 0; AST_LIST_TRAVERSE(&frame_queue[fr->callno], cur, list) { /* If it's our call, and our timestamp, mark -1 retries */ if (x == cur->oseqno) { cur->retries = -1; /* Destroy call if this is the end */ if (cur->final) call_to_destroy = fr->callno; } } if (call_to_destroy) { if (iaxdebug) ast_debug(1, "Really destroying %d, having been acked on final message\n", call_to_destroy); ast_mutex_lock(&iaxsl[call_to_destroy]); iax2_destroy(call_to_destroy); ast_mutex_unlock(&iaxsl[call_to_destroy]); } } /* Note how much we've received acknowledgement for */ if (iaxs[fr->callno]) iaxs[fr->callno]->rseqno = fr->iseqno; else { /* Stop processing now */ ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } } else { ast_debug(1, "Received iseqno %d not within window %d->%d\n", fr->iseqno, iaxs[fr->callno]->rseqno, iaxs[fr->callno]->oseqno); } } if (ast_sockaddr_cmp(&addr, &iaxs[fr->callno]->addr) && ((f.frametype != AST_FRAME_IAX) || ((f.subclass.integer != IAX_COMMAND_TXACC) && (f.subclass.integer != IAX_COMMAND_TXCNT)))) { /* Only messages we accept from a transfer host are TXACC and TXCNT */ ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } /* when we receive the first full frame for a new incoming channel, it is safe to start the PBX on the channel because we have now completed a 3-way handshake with the peer */ if ((f.frametype == AST_FRAME_VOICE) || (f.frametype == AST_FRAME_VIDEO) || (f.frametype == AST_FRAME_IAX)) { if (ast_test_flag64(iaxs[fr->callno], IAX_DELAYPBXSTART)) { ast_clear_flag64(iaxs[fr->callno], IAX_DELAYPBXSTART); if (!ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->chosenformat, &iaxs[fr->callno]->rprefs, NULL, NULL, ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_AUTHENTICATED))) { ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } } if (ies.vars) { struct ast_datastore *variablestore = NULL; struct ast_variable *var, *prev = NULL; AST_LIST_HEAD(, ast_var_t) *varlist; iax2_lock_owner(fr->callno); if (!iaxs[fr->callno]) { ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } if ((c = iaxs[fr->callno]->owner)) { varlist = ast_calloc(1, sizeof(*varlist)); variablestore = ast_datastore_alloc(&iax2_variable_datastore_info, NULL); if (variablestore && varlist) { variablestore->data = varlist; variablestore->inheritance = DATASTORE_INHERIT_FOREVER; AST_LIST_HEAD_INIT(varlist); ast_debug(1, "I can haz IAX vars?\n"); for (var = ies.vars; var; var = var->next) { struct ast_var_t *newvar = ast_var_assign(var->name, var->value); if (prev) { ast_free(prev); } prev = var; if (!newvar) { /* Don't abort list traversal, as this would leave ies.vars in an inconsistent state. */ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); } else { AST_LIST_INSERT_TAIL(varlist, newvar, entries); } } if (prev) { ast_free(prev); } ies.vars = NULL; ast_channel_datastore_add(c, variablestore); } else { ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); if (variablestore) { ast_datastore_free(variablestore); } if (varlist) { ast_free(varlist); } } ast_channel_unlock(c); } else { /* No channel yet, so transfer the variables directly over to the pvt, * for later inheritance. */ ast_debug(1, "No channel, so populating IAXVARs to the pvt, as an intermediate step.\n"); for (var = ies.vars; var && var->next; var = var->next); if (var) { var->next = iaxs[fr->callno]->iaxvars; iaxs[fr->callno]->iaxvars = ies.vars; ies.vars = NULL; } } } if (ies.vars) { ast_debug(1, "I have IAX variables, but they were not processed\n"); } } /* once we receive our first IAX Full Frame that is not CallToken related, send all * queued signaling frames that were being held. */ if ((f.frametype == AST_FRAME_IAX) && (f.subclass.integer != IAX_COMMAND_CALLTOKEN) && iaxs[fr->callno]->hold_signaling) { send_signaling(iaxs[fr->callno]); } if (f.frametype == AST_FRAME_VOICE) { if (ast_format_compatibility_format2bitfield(f.subclass.format) != iaxs[fr->callno]->voiceformat) { iaxs[fr->callno]->voiceformat = ast_format_compatibility_format2bitfield(f.subclass.format); ast_debug(1, "Ooh, voice format changed to '%s'\n", ast_format_get_name(f.subclass.format)); if (iaxs[fr->callno]->owner) { iax2_lock_owner(fr->callno); if (iaxs[fr->callno]) { if (iaxs[fr->callno]->owner) { struct ast_format_cap *native = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (native) { ast_format_cap_append(native, f.subclass.format, 0); ast_channel_nativeformats_set(iaxs[fr->callno]->owner, native); if (ast_channel_readformat(iaxs[fr->callno]->owner)) { ast_set_read_format(iaxs[fr->callno]->owner, ast_channel_readformat(iaxs[fr->callno]->owner)); } ao2_ref(native, -1); } ast_channel_unlock(iaxs[fr->callno]->owner); } } else { ast_debug(1, "Neat, somebody took away the channel at a magical time but i found it!\n"); /* Free remote variables (if any) */ if (ies.vars) { ast_variables_destroy(ies.vars); ast_debug(1, "I can haz iaxvars, but they is no good. :-(\n"); ies.vars = NULL; } ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } } } } if (f.frametype == AST_FRAME_VIDEO) { if (ast_format_compatibility_format2bitfield(f.subclass.format) != iaxs[fr->callno]->videoformat) { ast_debug(1, "Ooh, video format changed to %s\n", ast_format_get_name(f.subclass.format)); iaxs[fr->callno]->videoformat = ast_format_compatibility_format2bitfield(f.subclass.format); } } if (f.frametype == AST_FRAME_IAX) { AST_SCHED_DEL(sched, iaxs[fr->callno]->initid); /* Handle the IAX pseudo frame itself */ if (iaxdebug) ast_debug(1, "IAX subclass %d received\n", f.subclass.integer); /* Update last ts unless the frame's timestamp originated with us. */ if (iaxs[fr->callno]->last < fr->ts && f.subclass.integer != IAX_COMMAND_ACK && f.subclass.integer != IAX_COMMAND_PONG && f.subclass.integer != IAX_COMMAND_LAGRP) { iaxs[fr->callno]->last = fr->ts; if (iaxdebug) ast_debug(1, "For call=%d, set last=%u\n", fr->callno, fr->ts); } iaxs[fr->callno]->last_iax_message = f.subclass.integer; if (!iaxs[fr->callno]->first_iax_message) { iaxs[fr->callno]->first_iax_message = f.subclass.integer; } switch(f.subclass.integer) { case IAX_COMMAND_ACK: /* Do nothing */ break; case IAX_COMMAND_QUELCH: if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) { ast_set_flag64(iaxs[fr->callno], IAX_QUELCH); if (ies.musiconhold) { const char *moh_suggest; iax2_lock_owner(fr->callno); if (!iaxs[fr->callno] || !iaxs[fr->callno]->owner) { break; } /* * We already hold the owner lock so we do not * need to check iaxs[fr->callno] after it returns. */ moh_suggest = iaxs[fr->callno]->mohsuggest; iax2_queue_hold(fr->callno, moh_suggest); ast_channel_unlock(iaxs[fr->callno]->owner); } } break; case IAX_COMMAND_UNQUELCH: if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) { iax2_lock_owner(fr->callno); if (!iaxs[fr->callno]) { break; } ast_clear_flag64(iaxs[fr->callno], IAX_QUELCH); if (!iaxs[fr->callno]->owner) { break; } /* * We already hold the owner lock so we do not * need to check iaxs[fr->callno] after it returns. */ iax2_queue_unhold(fr->callno); ast_channel_unlock(iaxs[fr->callno]->owner); } break; case IAX_COMMAND_TXACC: if (iaxs[fr->callno]->transferring == TRANSFER_BEGIN) { /* Ack the packet with the given timestamp */ AST_LIST_TRAVERSE(&frame_queue[fr->callno], cur, list) { /* Cancel any outstanding txcnt's */ if (cur->transfer) { cur->retries = -1; } } memset(&ied1, 0, sizeof(ied1)); iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[fr->callno]->callno); send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXREADY, 0, ied1.buf, ied1.pos, -1); iaxs[fr->callno]->transferring = TRANSFER_READY; } break; case IAX_COMMAND_NEW: /* Ignore if it's already up */ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD)) break; if (ies.provverpres && ies.serviceident && !(ast_sockaddr_isnull(&addr))) { ast_mutex_unlock(&iaxsl[fr->callno]); check_provisioning(&addr, fd, ies.serviceident, ies.provver); ast_mutex_lock(&iaxsl[fr->callno]); if (!iaxs[fr->callno]) { break; } } /* If we're in trunk mode, do it now, and update the trunk number in our frame before continuing */ if (ast_test_flag64(iaxs[fr->callno], IAX_TRUNK)) { int new_callno; if ((new_callno = make_trunk(fr->callno, 1)) != -1) fr->callno = new_callno; } /* For security, always ack immediately */ if (delayreject) send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); if (check_access(fr->callno, &addr, &ies)) { /* They're not allowed on */ auth_fail(fr->callno, IAX_COMMAND_REJECT); if (authdebug) { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, who was trying to reach '%s@%s'\n", ast_sockaddr_stringify(&addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context); } break; } if (ast_strlen_zero(iaxs[fr->callno]->secret) && ast_test_flag64(iaxs[fr->callno], IAX_FORCE_ENCRYPT)) { auth_fail(fr->callno, IAX_COMMAND_REJECT); ast_log(LOG_WARNING, "Rejected connect attempt. No secret present while force encrypt enabled.\n"); break; } if (strcasecmp(iaxs[fr->callno]->exten, "TBD")) { const char *context, *exten, *cid_num; context = ast_strdupa(iaxs[fr->callno]->context); exten = ast_strdupa(iaxs[fr->callno]->exten); cid_num = ast_strdupa(iaxs[fr->callno]->cid_num); /* This might re-enter the IAX code and need the lock */ ast_mutex_unlock(&iaxsl[fr->callno]); exists = ast_exists_extension(NULL, context, exten, 1, cid_num); ast_mutex_lock(&iaxsl[fr->callno]); if (!iaxs[fr->callno]) { break; } } else exists = 0; /* Get OSP token if it does exist */ save_osptoken(fr, &ies); if (ast_strlen_zero(iaxs[fr->callno]->secret) && ast_strlen_zero(iaxs[fr->callno]->inkeys)) { if (strcmp(iaxs[fr->callno]->exten, "TBD") && !exists) { memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No such context/extension"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_NO_ROUTE_DESTINATION); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } if (authdebug) { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s@%s' does not exist\n", ast_sockaddr_stringify(&addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context); } } else { /* Select an appropriate format */ if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOPREFS)) { if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { using_prefs = "reqonly"; } else { using_prefs = "disabled"; } format = iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability; memset(&pref, 0, sizeof(pref)); strcpy(caller_pref_buf, "disabled"); strcpy(host_pref_buf, "disabled"); } else { struct ast_format *tmpfmt; using_prefs = "mine"; /* If the information elements are in here... use them */ if (ies.codec_prefs) iax2_codec_pref_convert(&iaxs[fr->callno]->rprefs, ies.codec_prefs, 32, 0); if (iax2_codec_pref_index(&iaxs[fr->callno]->rprefs, 0, &tmpfmt)) { /* If we are codec_first_choice we let the caller have the 1st shot at picking the codec.*/ if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) { pref = iaxs[fr->callno]->rprefs; using_prefs = "caller"; } else { pref = iaxs[fr->callno]->prefs; } } else pref = iaxs[fr->callno]->prefs; format = iax2_codec_choose(&pref, iaxs[fr->callno]->capability & iaxs[fr->callno]->peercapability); iax2_codec_pref_string(&iaxs[fr->callno]->rprefs, caller_pref_buf, sizeof(caller_pref_buf) - 1); iax2_codec_pref_string(&iaxs[fr->callno]->prefs, host_pref_buf, sizeof(host_pref_buf) - 1); } if (!format) { if(!ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) format = iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability; if (!format) { memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } if (authdebug) { struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *peer_form_buf = ast_str_alloca(64); if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested '%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } else { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability '%s'/'%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->peercapability, &peer_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } } } else { /* Pick one... */ if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { if(!(iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability)) format = 0; } else { if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOPREFS)) { using_prefs = ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP) ? "reqonly" : "disabled"; memset(&pref, 0, sizeof(pref)); format = iax2_format_compatibility_best(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability); strcpy(caller_pref_buf,"disabled"); strcpy(host_pref_buf,"disabled"); } else { struct ast_format *tmpfmt; using_prefs = "mine"; if (iax2_codec_pref_index(&iaxs[fr->callno]->rprefs, 0, &tmpfmt)) { /* Do the opposite of what we tried above. */ if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) { pref = iaxs[fr->callno]->prefs; } else { pref = iaxs[fr->callno]->rprefs; using_prefs = "caller"; } format = iax2_codec_choose(&pref, iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability); } else /* if no codec_prefs IE do it the old way */ format = iax2_format_compatibility_best(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability); } } if (!format) { struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *peer_form_buf = ast_str_alloca(64); memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); ast_log(LOG_ERROR, "No best format in '%s'???\n", iax2_getformatname_multiple(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability, &cap_buf)); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } if (authdebug) { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability '%s'/'%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->peercapability, &peer_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } ast_set_flag64(iaxs[fr->callno], IAX_ALREADYGONE); break; } } } if (format) { /* No authentication required, let them in */ memset(&ied1, 0, sizeof(ied1)); iax_ie_append_int(&ied1, IAX_IE_FORMAT, format); iax_ie_append_versioned_uint64(&ied1, IAX_IE_FORMAT2, 0, format); send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACCEPT, 0, ied1.buf, ied1.pos, -1); if (strcmp(iaxs[fr->callno]->exten, "TBD")) { ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED); ast_verb(3, "Accepting UNAUTHENTICATED call from %s:\n" "%srequested format = %s,\n" "%srequested prefs = %s,\n" "%sactual format = %s,\n" "%shost prefs = %s,\n" "%spriority = %s\n", ast_sockaddr_stringify(&addr), VERBOSE_PREFIX_4, iax2_getformatname(iaxs[fr->callno]->peerformat), VERBOSE_PREFIX_4, caller_pref_buf, VERBOSE_PREFIX_4, iax2_getformatname(format), VERBOSE_PREFIX_4, host_pref_buf, VERBOSE_PREFIX_4, using_prefs); iaxs[fr->callno]->chosenformat = format; /* Since this is a new call, we should go ahead and set the callid for it. */ iax_pvt_callid_new(fr->callno); ast_set_flag64(iaxs[fr->callno], IAX_DELAYPBXSTART); } else { ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD); /* If this is a TBD call, we're ready but now what... */ ast_verb(3, "Accepted unauthenticated TBD call from %s\n", ast_sockaddr_stringify(&addr)); } } } break; } if (iaxs[fr->callno]->authmethods & IAX_AUTH_MD5) merge_encryption(iaxs[fr->callno],ies.encmethods); else iaxs[fr->callno]->encmethods = 0; if (!authenticate_request(fr->callno) && iaxs[fr->callno]) ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_AUTHENTICATED); break; case IAX_COMMAND_DPREQ: /* Request status in the dialplan */ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD) && !ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED) && ies.called_number) { if (iaxcompat) { /* Spawn a thread for the lookup */ spawn_dp_lookup(fr->callno, iaxs[fr->callno]->context, ies.called_number, iaxs[fr->callno]->cid_num); } else { /* Just look it up */ dp_lookup(fr->callno, iaxs[fr->callno]->context, ies.called_number, iaxs[fr->callno]->cid_num, 1); } } break; case IAX_COMMAND_HANGUP: ast_set_flag64(iaxs[fr->callno], IAX_ALREADYGONE); ast_debug(1, "Immediately destroying %d, having received hangup\n", fr->callno); /* Set hangup cause according to remote and hangupsource */ if (iaxs[fr->callno]->owner) { set_hangup_source_and_cause(fr->callno, ies.causecode); if (!iaxs[fr->callno]) { break; } } /* Send ack immediately, before we destroy */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); iax2_destroy(fr->callno); break; case IAX_COMMAND_REJECT: /* Set hangup cause according to remote and hangup source */ if (iaxs[fr->callno]->owner) { set_hangup_source_and_cause(fr->callno, ies.causecode); if (!iaxs[fr->callno]) { break; } } if (!ast_test_flag64(iaxs[fr->callno], IAX_PROVISION)) { if (iaxs[fr->callno]->owner && authdebug) ast_log(LOG_WARNING, "Call rejected by %s: %s\n", ast_sockaddr_stringify(&addr), ies.cause ? ies.cause : ""); ast_debug(1, "Immediately destroying %d, having received reject\n", fr->callno); } /* Send ack immediately, before we destroy */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0, fr->iseqno); if (!ast_test_flag64(iaxs[fr->callno], IAX_PROVISION)) iaxs[fr->callno]->error = EPERM; iax2_destroy(fr->callno); break; case IAX_COMMAND_TRANSFER: { iax2_lock_owner(fr->callno); if (!iaxs[fr->callno]) { /* Initiating call went away before we could transfer. */ break; } if (iaxs[fr->callno]->owner) { struct ast_channel *owner = iaxs[fr->callno]->owner; char *context = ast_strdupa(iaxs[fr->callno]->context); ast_channel_ref(owner); ast_channel_unlock(owner); ast_mutex_unlock(&iaxsl[fr->callno]); if (ast_bridge_transfer_blind(1, owner, ies.called_number, context, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) { ast_log(LOG_WARNING, "Blind transfer of '%s' to '%s@%s' failed\n", ast_channel_name(owner), ies.called_number, context); } ast_channel_unref(owner); ast_mutex_lock(&iaxsl[fr->callno]); } break; } case IAX_COMMAND_ACCEPT: /* Ignore if call is already up or needs authentication or is a TBD */ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD | IAX_STATE_AUTHENTICATED)) break; if (ast_test_flag64(iaxs[fr->callno], IAX_PROVISION)) { /* Send ack immediately, before we destroy */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); iax2_destroy(fr->callno); break; } if (ies.format) { iaxs[fr->callno]->peerformat = ies.format; } else { if (iaxs[fr->callno]->owner) iaxs[fr->callno]->peerformat = iax2_format_compatibility_cap2bitfield(ast_channel_nativeformats(iaxs[fr->callno]->owner)); else iaxs[fr->callno]->peerformat = iaxs[fr->callno]->capability; } ast_verb(3, "Call accepted by %s (format %s)\n", ast_sockaddr_stringify(&addr), iax2_getformatname(iaxs[fr->callno]->peerformat)); if (!(iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability)) { memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } if (authdebug) { struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *cap_buf = ast_str_alloca(64); ast_log(LOG_NOTICE, "Rejected call to %s, format %s incompatible with our capability %s.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } } else { struct ast_format_cap *native = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED); iax2_lock_owner(fr->callno); if (iaxs[fr->callno] && iaxs[fr->callno]->owner && native) { struct ast_str *cap_buf = ast_str_alloca(64); /* Switch us to use a compatible format */ iax2_codec_pref_best_bitfield2cap( iaxs[fr->callno]->peerformat, &iaxs[fr->callno]->rprefs, native); ast_channel_nativeformats_set(iaxs[fr->callno]->owner, native); ast_verb(3, "Format for call is %s\n", ast_format_cap_get_names(ast_channel_nativeformats(iaxs[fr->callno]->owner), &cap_buf)); /* Setup read/write formats properly. */ if (ast_channel_writeformat(iaxs[fr->callno]->owner)) ast_set_write_format(iaxs[fr->callno]->owner, ast_channel_writeformat(iaxs[fr->callno]->owner)); if (ast_channel_readformat(iaxs[fr->callno]->owner)) ast_set_read_format(iaxs[fr->callno]->owner, ast_channel_readformat(iaxs[fr->callno]->owner)); ast_channel_unlock(iaxs[fr->callno]->owner); } ao2_cleanup(native); } if (iaxs[fr->callno]) { AST_LIST_LOCK(&dpcache); AST_LIST_TRAVERSE(&iaxs[fr->callno]->dpentries, dp, peer_list) if (!(dp->flags & CACHE_FLAG_TRANSMITTED)) iax2_dprequest(dp, fr->callno); AST_LIST_UNLOCK(&dpcache); } break; case IAX_COMMAND_POKE: /* Send back a pong packet with the original timestamp */ send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_PONG, fr->ts, NULL, 0, -1); break; case IAX_COMMAND_PING: { struct iax_ie_data pingied; construct_rr(iaxs[fr->callno], &pingied); /* Send back a pong packet with the original timestamp */ send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_PONG, fr->ts, pingied.buf, pingied.pos, -1); } break; case IAX_COMMAND_PONG: /* Calculate ping time */ iaxs[fr->callno]->pingtime = calc_timestamp(iaxs[fr->callno], 0, &f) - fr->ts; /* save RR info */ save_rr(fr, &ies); /* Good time to write jb stats for this call */ log_jitterstats(fr->callno); if (iaxs[fr->callno]->peerpoke) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); peer = iaxs[fr->callno]->peerpoke; if ((peer->lastms < 0) || (peer->historicms > peer->maxms)) { if (iaxs[fr->callno]->pingtime <= peer->maxms) { ast_log(LOG_NOTICE, "Peer '%s' is now REACHABLE! Time: %u\n", peer->name, iaxs[fr->callno]->pingtime); ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); blob = ast_json_pack("{s: s, s: i}", "peer_status", "Reachable", "time", iaxs[fr->callno]->pingtime); ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */ } } else if ((peer->historicms > 0) && (peer->historicms <= peer->maxms)) { if (iaxs[fr->callno]->pingtime > peer->maxms) { ast_log(LOG_NOTICE, "Peer '%s' is now TOO LAGGED (%u ms)!\n", peer->name, iaxs[fr->callno]->pingtime); ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); blob = ast_json_pack("{s: s, s: i}", "peer_status", "Lagged", "time", iaxs[fr->callno]->pingtime); ast_devstate_changed(AST_DEVICE_UNAVAILABLE, AST_DEVSTATE_CACHABLE, "IAX2/%s", peer->name); /* Activate notification */ } } ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); peer->lastms = iaxs[fr->callno]->pingtime; if (peer->smoothing && (peer->lastms > -1)) peer->historicms = (iaxs[fr->callno]->pingtime + peer->historicms) / 2; else if (peer->smoothing && peer->lastms < 0) peer->historicms = (0 + peer->historicms) / 2; else peer->historicms = iaxs[fr->callno]->pingtime; /* Remove scheduled iax2_poke_noanswer */ if (peer->pokeexpire > -1) { if (!AST_SCHED_DEL(sched, peer->pokeexpire)) { peer_unref(peer); peer->pokeexpire = -1; } } /* Schedule the next cycle */ if ((peer->lastms < 0) || (peer->historicms > peer->maxms)) peer->pokeexpire = iax2_sched_add(sched, peer->pokefreqnotok, iax2_poke_peer_s, peer_ref(peer)); else peer->pokeexpire = iax2_sched_add(sched, peer->pokefreqok, iax2_poke_peer_s, peer_ref(peer)); if (peer->pokeexpire == -1) peer_unref(peer); /* and finally send the ack */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); /* And wrap up the qualify call */ iax2_destroy(fr->callno); peer->callno = 0; ast_debug(1, "Peer %s: got pong, lastms %d, historicms %d, maxms %d\n", peer->name, peer->lastms, peer->historicms, peer->maxms); } break; case IAX_COMMAND_LAGRQ: case IAX_COMMAND_LAGRP: f.src = "LAGRQ"; f.mallocd = 0; f.offset = 0; f.samples = 0; iax_frame_wrap(fr, &f); if (f.subclass.integer == IAX_COMMAND_LAGRQ) { /* Received a LAGRQ - echo back a LAGRP */ fr->af.subclass.integer = IAX_COMMAND_LAGRP; iax2_send(iaxs[fr->callno], &fr->af, fr->ts, -1, 0, 0, 0); } else { /* Received LAGRP in response to our LAGRQ */ unsigned int ts; /* This is a reply we've been given, actually measure the difference */ ts = calc_timestamp(iaxs[fr->callno], 0, &fr->af); iaxs[fr->callno]->lag = ts - fr->ts; if (iaxdebug) ast_debug(1, "Peer %s lag measured as %dms\n", ast_sockaddr_stringify(&addr), iaxs[fr->callno]->lag); } break; case IAX_COMMAND_AUTHREQ: if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD)) { ast_log(LOG_WARNING, "Call on %s is already up, can't start on it\n", iaxs[fr->callno]->owner ? ast_channel_name(iaxs[fr->callno]->owner) : ""); break; } if (authenticate_reply(iaxs[fr->callno], &iaxs[fr->callno]->addr, &ies, iaxs[fr->callno]->secret, iaxs[fr->callno]->outkey)) { struct ast_frame hangup_fr = { .frametype = AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_HANGUP, }; ast_log(LOG_WARNING, "I don't know how to authenticate %s to %s\n", ies.username ? ies.username : "", ast_sockaddr_stringify(&addr)); iax2_queue_frame(fr->callno, &hangup_fr); } break; case IAX_COMMAND_AUTHREP: /* For security, always ack immediately */ if (delayreject) send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); /* Ignore once we've started */ if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED | IAX_STATE_TBD)) { ast_log(LOG_WARNING, "Call on %s is already up, can't start on it\n", iaxs[fr->callno]->owner ? ast_channel_name(iaxs[fr->callno]->owner) : ""); break; } if (authenticate_verify(iaxs[fr->callno], &ies)) { if (authdebug) ast_log(LOG_NOTICE, "Host %s failed to authenticate as %s\n", ast_sockaddr_stringify(&addr), iaxs[fr->callno]->username); memset(&ied0, 0, sizeof(ied0)); auth_fail(fr->callno, IAX_COMMAND_REJECT); break; } if (strcasecmp(iaxs[fr->callno]->exten, "TBD")) { /* This might re-enter the IAX code and need the lock */ exists = ast_exists_extension(NULL, iaxs[fr->callno]->context, iaxs[fr->callno]->exten, 1, iaxs[fr->callno]->cid_num); } else exists = 0; if (strcmp(iaxs[fr->callno]->exten, "TBD") && !exists) { if (authdebug) ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s@%s' does not exist\n", ast_sockaddr_stringify(&addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context); memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No such context/extension"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_NO_ROUTE_DESTINATION); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } } else { /* Select an appropriate format */ if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOPREFS)) { if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { using_prefs = "reqonly"; } else { using_prefs = "disabled"; } format = iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability; memset(&pref, 0, sizeof(pref)); strcpy(caller_pref_buf, "disabled"); strcpy(host_pref_buf, "disabled"); } else { struct ast_format *tmpfmt; using_prefs = "mine"; if (ies.codec_prefs) iax2_codec_pref_convert(&iaxs[fr->callno]->rprefs, ies.codec_prefs, 32, 0); if (iax2_codec_pref_index(&iaxs[fr->callno]->rprefs, 0, &tmpfmt)) { if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) { pref = iaxs[fr->callno]->rprefs; using_prefs = "caller"; } else { pref = iaxs[fr->callno]->prefs; } } else /* if no codec_prefs IE do it the old way */ pref = iaxs[fr->callno]->prefs; format = iax2_codec_choose(&pref, iaxs[fr->callno]->capability & iaxs[fr->callno]->peercapability); iax2_codec_pref_string(&iaxs[fr->callno]->rprefs, caller_pref_buf, sizeof(caller_pref_buf) - 1); iax2_codec_pref_string(&iaxs[fr->callno]->prefs, host_pref_buf, sizeof(host_pref_buf) - 1); } if (!format) { struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *peer_form_buf = ast_str_alloca(64); if(!ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { ast_debug(1, "We don't do requested format %s, falling back to peer capability '%s'\n", iax2_getformatname(iaxs[fr->callno]->peerformat), iax2_getformatname_multiple(iaxs[fr->callno]->peercapability, &peer_buf)); format = iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability; } if (!format) { if (authdebug) { if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested '%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } else { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability '%s'/'%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->peercapability, &peer_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } } memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } } else { /* Pick one... */ if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { if(!(iaxs[fr->callno]->peerformat & iaxs[fr->callno]->capability)) format = 0; } else { if(ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOPREFS)) { using_prefs = ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP) ? "reqonly" : "disabled"; memset(&pref, 0, sizeof(pref)); format = ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP) ? iaxs[fr->callno]->peerformat : iax2_format_compatibility_best(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability); strcpy(caller_pref_buf,"disabled"); strcpy(host_pref_buf,"disabled"); } else { struct ast_format *tmpfmt; using_prefs = "mine"; if (iax2_codec_pref_index(&iaxs[fr->callno]->rprefs, 0, &tmpfmt)) { /* Do the opposite of what we tried above. */ if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_USER_FIRST)) { pref = iaxs[fr->callno]->prefs; } else { pref = iaxs[fr->callno]->rprefs; using_prefs = "caller"; } format = iax2_codec_choose(&pref, iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability); } else /* if no codec_prefs IE do it the old way */ format = iax2_format_compatibility_best(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability); } } if (!format) { struct ast_str *cap_buf = ast_str_alloca(64); struct ast_str *peer_buf = ast_str_alloca(64); struct ast_str *peer_form_buf = ast_str_alloca(64); ast_log(LOG_ERROR, "No best format in %s???\n", iax2_getformatname_multiple(iaxs[fr->callno]->peercapability & iaxs[fr->callno]->capability, &cap_buf)); if (authdebug) { if (ast_test_flag64(iaxs[fr->callno], IAX_CODEC_NOCAP)) { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested '%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } else { ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability '%s'/'%s' incompatible with our capability '%s'.\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &peer_form_buf), iax2_getformatname_multiple(iaxs[fr->callno]->peercapability, &peer_buf), iax2_getformatname_multiple(iaxs[fr->callno]->capability, &cap_buf)); } } memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "Unable to negotiate codec"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } } } } if (format) { /* Authentication received */ memset(&ied1, 0, sizeof(ied1)); iax_ie_append_int(&ied1, IAX_IE_FORMAT, format); iax_ie_append_versioned_uint64(&ied1, IAX_IE_FORMAT2, 0, format); send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACCEPT, 0, ied1.buf, ied1.pos, -1); if (strcmp(iaxs[fr->callno]->exten, "TBD")) { ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED); ast_verb(3, "Accepting AUTHENTICATED call from %s:\n" "%srequested format = %s,\n" "%srequested prefs = %s,\n" "%sactual format = %s,\n" "%shost prefs = %s,\n" "%spriority = %s\n", ast_sockaddr_stringify(&addr), VERBOSE_PREFIX_4, iax2_getformatname(iaxs[fr->callno]->peerformat), VERBOSE_PREFIX_4, caller_pref_buf, VERBOSE_PREFIX_4, iax2_getformatname(format), VERBOSE_PREFIX_4, host_pref_buf, VERBOSE_PREFIX_4, using_prefs); ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED); c = ast_iax2_new(fr->callno, AST_STATE_RING, format, &iaxs[fr->callno]->rprefs, NULL, NULL, 1); if (!c) { iax2_destroy(fr->callno); } else if (ies.vars) { struct ast_datastore *variablestore; struct ast_variable *var, *prev = NULL; AST_LIST_HEAD(, ast_var_t) *varlist; varlist = ast_calloc(1, sizeof(*varlist)); variablestore = ast_datastore_alloc(&iax2_variable_datastore_info, NULL); if (variablestore && varlist) { variablestore->data = varlist; variablestore->inheritance = DATASTORE_INHERIT_FOREVER; AST_LIST_HEAD_INIT(varlist); ast_debug(1, "I can haz IAX vars? w00t\n"); for (var = ies.vars; var; var = var->next) { struct ast_var_t *newvar = ast_var_assign(var->name, var->value); if (prev) ast_free(prev); prev = var; if (!newvar) { /* Don't abort list traversal, as this would leave ies.vars in an inconsistent state. */ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); } else { AST_LIST_INSERT_TAIL(varlist, newvar, entries); } } if (prev) ast_free(prev); ies.vars = NULL; ast_channel_datastore_add(c, variablestore); } else { ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); if (variablestore) ast_datastore_free(variablestore); if (varlist) ast_free(varlist); } } } else { ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD); /* If this is a TBD call, we're ready but now what... */ ast_verb(3, "Accepted AUTHENTICATED TBD call from %s\n", ast_sockaddr_stringify(&addr)); if (ast_test_flag64(iaxs[fr->callno], IAX_IMMEDIATE)) { goto immediatedial; } } } } break; case IAX_COMMAND_DIAL: immediatedial: if (ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD)) { ast_clear_flag(&iaxs[fr->callno]->state, IAX_STATE_TBD); ast_string_field_set(iaxs[fr->callno], exten, ies.called_number ? ies.called_number : "s"); if (!ast_exists_extension(NULL, iaxs[fr->callno]->context, iaxs[fr->callno]->exten, 1, iaxs[fr->callno]->cid_num)) { if (authdebug) ast_log(LOG_NOTICE, "Rejected dial attempt from %s, request '%s@%s' does not exist\n", ast_sockaddr_stringify(&addr), iaxs[fr->callno]->exten, iaxs[fr->callno]->context); memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No such context/extension"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_NO_ROUTE_DESTINATION); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); if (!iaxs[fr->callno]) { break; } } else { struct ast_str *cap_buf = ast_str_alloca(64); ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED); ast_verb(3, "Accepting DIAL from %s, formats = %s\n", ast_sockaddr_stringify(&addr), iax2_getformatname_multiple(iaxs[fr->callno]->peerformat, &cap_buf)); ast_set_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED); send_command(iaxs[fr->callno], AST_FRAME_CONTROL, AST_CONTROL_PROGRESS, 0, NULL, 0, -1); c = ast_iax2_new(fr->callno, AST_STATE_RING, iaxs[fr->callno]->peerformat, &iaxs[fr->callno]->rprefs, NULL, NULL, 1); if (!c) { iax2_destroy(fr->callno); } else if (ies.vars) { struct ast_datastore *variablestore; struct ast_variable *var, *prev = NULL; AST_LIST_HEAD(, ast_var_t) *varlist; varlist = ast_calloc(1, sizeof(*varlist)); variablestore = ast_datastore_alloc(&iax2_variable_datastore_info, NULL); ast_debug(1, "I can haz IAX vars? w00t\n"); if (variablestore && varlist) { variablestore->data = varlist; variablestore->inheritance = DATASTORE_INHERIT_FOREVER; AST_LIST_HEAD_INIT(varlist); for (var = ies.vars; var; var = var->next) { struct ast_var_t *newvar = ast_var_assign(var->name, var->value); if (prev) ast_free(prev); prev = var; if (!newvar) { /* Don't abort list traversal, as this would leave ies.vars in an inconsistent state. */ ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); } else { AST_LIST_INSERT_TAIL(varlist, newvar, entries); } } if (prev) ast_free(prev); ies.vars = NULL; ast_channel_datastore_add(c, variablestore); } else { ast_log(LOG_ERROR, "Memory allocation error while processing IAX2 variables\n"); if (variablestore) ast_datastore_free(variablestore); if (varlist) ast_free(varlist); } } } } break; case IAX_COMMAND_INVAL: iaxs[fr->callno]->error = ENOTCONN; ast_debug(1, "Immediately destroying %d, having received INVAL\n", fr->callno); iax2_destroy(fr->callno); ast_debug(1, "Destroying call %d\n", fr->callno); break; case IAX_COMMAND_VNAK: ast_debug(1, "Received VNAK: resending outstanding frames\n"); /* Force retransmission */ vnak_retransmit(fr->callno, fr->iseqno); break; case IAX_COMMAND_REGREQ: case IAX_COMMAND_REGREL: /* For security, always ack immediately */ if (delayreject) send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); if (register_verify(fr->callno, &addr, &ies)) { if (!iaxs[fr->callno]) { break; } /* Send delayed failure */ auth_fail(fr->callno, IAX_COMMAND_REGREJ); break; } if (!iaxs[fr->callno]) { break; } if ((ast_strlen_zero(iaxs[fr->callno]->secret) && ast_strlen_zero(iaxs[fr->callno]->inkeys)) || ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_AUTHENTICATED)) { if (f.subclass.integer == IAX_COMMAND_REGREL) { ast_sockaddr_setnull(&addr); } if (update_registry(&addr, fr->callno, ies.devicetype, fd, ies.refresh)) { ast_log(LOG_WARNING, "Registry error\n"); } if (!iaxs[fr->callno]) { break; } if (ies.provverpres && ies.serviceident && !(ast_sockaddr_isnull(&addr))) { ast_mutex_unlock(&iaxsl[fr->callno]); check_provisioning(&addr, fd, ies.serviceident, ies.provver); ast_mutex_lock(&iaxsl[fr->callno]); } break; } registry_authrequest(fr->callno); break; case IAX_COMMAND_REGACK: if (iax2_ack_registry(&ies, &addr, fr->callno)) { ast_log(LOG_WARNING, "Registration failure\n"); } /* Send ack immediately, before we destroy */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); iax2_destroy(fr->callno); break; case IAX_COMMAND_REGREJ: if (iaxs[fr->callno]->reg) { if (authdebug) { ast_log(LOG_NOTICE, "Registration of '%s' rejected: '%s' from: '%s'\n", iaxs[fr->callno]->reg->username, ies.cause ? ies.cause : "", ast_sockaddr_stringify(&addr)); } iax2_publish_registry(iaxs[fr->callno]->reg->username, ast_sockaddr_stringify(&addr), "Rejected", S_OR(ies.cause, "")); iaxs[fr->callno]->reg->regstate = REG_STATE_REJECTED; } /* Send ack immediately, before we destroy */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); iax2_destroy(fr->callno); break; case IAX_COMMAND_REGAUTH: /* Authentication request */ if (registry_rerequest(&ies, fr->callno, &addr)) { memset(&ied0, 0, sizeof(ied0)); iax_ie_append_str(&ied0, IAX_IE_CAUSE, "No authority found"); iax_ie_append_byte(&ied0, IAX_IE_CAUSECODE, AST_CAUSE_FACILITY_NOT_SUBSCRIBED); send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); } break; case IAX_COMMAND_TXREJ: while (iaxs[fr->callno] && iaxs[fr->callno]->bridgecallno && ast_mutex_trylock(&iaxsl[iaxs[fr->callno]->bridgecallno])) { DEADLOCK_AVOIDANCE(&iaxsl[fr->callno]); } if (!iaxs[fr->callno]) { break; } iaxs[fr->callno]->transferring = TRANSFER_NONE; ast_verb(3, "Channel '%s' unable to transfer\n", iaxs[fr->callno]->owner ? ast_channel_name(iaxs[fr->callno]->owner) : ""); memset(&iaxs[fr->callno]->transfer, 0, sizeof(iaxs[fr->callno]->transfer)); if (!iaxs[fr->callno]->bridgecallno) { break; } if (iaxs[iaxs[fr->callno]->bridgecallno] && iaxs[iaxs[fr->callno]->bridgecallno]->transferring) { iaxs[iaxs[fr->callno]->bridgecallno]->transferring = TRANSFER_NONE; send_command(iaxs[iaxs[fr->callno]->bridgecallno], AST_FRAME_IAX, IAX_COMMAND_TXREJ, 0, NULL, 0, -1); } ast_mutex_unlock(&iaxsl[iaxs[fr->callno]->bridgecallno]); break; case IAX_COMMAND_TXREADY: while (iaxs[fr->callno] && iaxs[fr->callno]->bridgecallno && ast_mutex_trylock(&iaxsl[iaxs[fr->callno]->bridgecallno])) { DEADLOCK_AVOIDANCE(&iaxsl[fr->callno]); } if (!iaxs[fr->callno]) { break; } if (iaxs[fr->callno]->transferring == TRANSFER_BEGIN) { iaxs[fr->callno]->transferring = TRANSFER_READY; } else if (iaxs[fr->callno]->transferring == TRANSFER_MBEGIN) { iaxs[fr->callno]->transferring = TRANSFER_MREADY; } else { if (iaxs[fr->callno]->bridgecallno) { ast_mutex_unlock(&iaxsl[iaxs[fr->callno]->bridgecallno]); } break; } ast_verb(3, "Channel '%s' ready to transfer\n", iaxs[fr->callno]->owner ? ast_channel_name(iaxs[fr->callno]->owner) : ""); if (!iaxs[fr->callno]->bridgecallno) { break; } if (!iaxs[iaxs[fr->callno]->bridgecallno] || (iaxs[iaxs[fr->callno]->bridgecallno]->transferring != TRANSFER_READY && iaxs[iaxs[fr->callno]->bridgecallno]->transferring != TRANSFER_MREADY)) { ast_mutex_unlock(&iaxsl[iaxs[fr->callno]->bridgecallno]); break; } /* Both sides are ready */ /* XXX what isn't checked here is that both sides match transfer types. */ if (iaxs[fr->callno]->transferring == TRANSFER_MREADY) { ast_verb(3, "Attempting media bridge of %s and %s\n", iaxs[fr->callno]->owner ? ast_channel_name(iaxs[fr->callno]->owner) : "", iaxs[iaxs[fr->callno]->bridgecallno]->owner ? ast_channel_name(iaxs[iaxs[fr->callno]->bridgecallno]->owner) : ""); iaxs[iaxs[fr->callno]->bridgecallno]->transferring = TRANSFER_MEDIA; iaxs[fr->callno]->transferring = TRANSFER_MEDIA; memset(&ied0, 0, sizeof(ied0)); memset(&ied1, 0, sizeof(ied1)); iax_ie_append_short(&ied0, IAX_IE_CALLNO, iaxs[iaxs[fr->callno]->bridgecallno]->peercallno); iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[fr->callno]->peercallno); send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXMEDIA, 0, ied0.buf, ied0.pos, -1); send_command(iaxs[iaxs[fr->callno]->bridgecallno], AST_FRAME_IAX, IAX_COMMAND_TXMEDIA, 0, ied1.buf, ied1.pos, -1); } else { ast_verb(3, "Releasing %s and %s\n", iaxs[fr->callno]->owner ? ast_channel_name(iaxs[fr->callno]->owner) : "", iaxs[iaxs[fr->callno]->bridgecallno]->owner ? ast_channel_name(iaxs[iaxs[fr->callno]->bridgecallno]->owner) : ""); iaxs[iaxs[fr->callno]->bridgecallno]->transferring = TRANSFER_RELEASED; iaxs[fr->callno]->transferring = TRANSFER_RELEASED; ast_set_flag64(iaxs[iaxs[fr->callno]->bridgecallno], IAX_ALREADYGONE); ast_set_flag64(iaxs[fr->callno], IAX_ALREADYGONE); /* Stop doing lag & ping requests */ stop_stuff(fr->callno); stop_stuff(iaxs[fr->callno]->bridgecallno); memset(&ied0, 0, sizeof(ied0)); memset(&ied1, 0, sizeof(ied1)); iax_ie_append_short(&ied0, IAX_IE_CALLNO, iaxs[iaxs[fr->callno]->bridgecallno]->peercallno); iax_ie_append_short(&ied1, IAX_IE_CALLNO, iaxs[fr->callno]->peercallno); send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXREL, 0, ied0.buf, ied0.pos, -1); send_command(iaxs[iaxs[fr->callno]->bridgecallno], AST_FRAME_IAX, IAX_COMMAND_TXREL, 0, ied1.buf, ied1.pos, -1); } ast_mutex_unlock(&iaxsl[iaxs[fr->callno]->bridgecallno]); break; case IAX_COMMAND_TXREQ: try_transfer(iaxs[fr->callno], &ies); break; case IAX_COMMAND_TXCNT: if (iaxs[fr->callno]->transferring) send_command_transfer(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_TXACC, 0, NULL, 0); break; case IAX_COMMAND_TXREL: /* Send ack immediately, rather than waiting until we've changed addresses */ send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); complete_transfer(fr->callno, &ies); stop_stuff(fr->callno); /* for attended transfer to work with libiax */ break; case IAX_COMMAND_TXMEDIA: if (iaxs[fr->callno]->transferring == TRANSFER_READY) { AST_LIST_TRAVERSE(&frame_queue[fr->callno], cur, list) { /* Cancel any outstanding frames and start anew */ if (cur->transfer) { cur->retries = -1; } } /* Start sending our media to the transfer address, but otherwise leave the call as-is */ iaxs[fr->callno]->transferring = TRANSFER_MEDIAPASS; } break; case IAX_COMMAND_RTKEY: if (!IAX_CALLENCRYPTED(iaxs[fr->callno])) { ast_log(LOG_WARNING, "we've been told to rotate our encryption key, " "but this isn't an encrypted call. bad things will happen.\n" ); break; } IAX_DEBUGDIGEST("Receiving", ies.challenge); ast_aes_set_decrypt_key((unsigned char *) ies.challenge, &iaxs[fr->callno]->dcx); break; case IAX_COMMAND_DPREP: complete_dpreply(iaxs[fr->callno], &ies); break; case IAX_COMMAND_UNSUPPORT: ast_log(LOG_NOTICE, "Peer did not understand our iax command '%d'\n", ies.iax_unknown); break; case IAX_COMMAND_FWDOWNL: /* Firmware download */ if (!ast_test_flag64(&globalflags, IAX_ALLOWFWDOWNLOAD)) { send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_UNSUPPORT, 0, NULL, 0, -1); break; } memset(&ied0, 0, sizeof(ied0)); res = iax_firmware_append(&ied0, ies.devicetype, ies.fwdesc); if (res < 0) send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1); else if (res > 0) send_command_final(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_FWDATA, 0, ied0.buf, ied0.pos, -1); else send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_FWDATA, 0, ied0.buf, ied0.pos, -1); break; case IAX_COMMAND_CALLTOKEN: { struct iax_frame *cur; /* find last sent frame */ if ((cur = AST_LIST_LAST(&frame_queue[fr->callno])) && ies.calltoken && ies.calltokendata) { resend_with_token(fr->callno, cur, (char *) ies.calltokendata); } break; } default: ast_debug(1, "Unknown IAX command %d on %d/%d\n", f.subclass.integer, fr->callno, iaxs[fr->callno]->peercallno); memset(&ied0, 0, sizeof(ied0)); iax_ie_append_byte(&ied0, IAX_IE_IAX_UNKNOWN, f.subclass.integer); send_command(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_UNSUPPORT, 0, ied0.buf, ied0.pos, -1); } /* Free remote variables (if any) */ if (ies.vars) { ast_variables_destroy(ies.vars); ast_debug(1, "I can haz IAX vars, but they is no good :-(\n"); ies.vars = NULL; } /* Don't actually pass these frames along */ if ((f.subclass.integer != IAX_COMMAND_ACK) && (f.subclass.integer != IAX_COMMAND_TXCNT) && (f.subclass.integer != IAX_COMMAND_TXACC) && (f.subclass.integer != IAX_COMMAND_INVAL) && (f.subclass.integer != IAX_COMMAND_VNAK)) { if (iaxs[fr->callno] && iaxs[fr->callno]->aseqno != iaxs[fr->callno]->iseqno) { send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); } } ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } /* Unless this is an ACK or INVAL frame, ack it */ if (iaxs[fr->callno] && iaxs[fr->callno]->aseqno != iaxs[fr->callno]->iseqno) send_command_immediate(iaxs[fr->callno], AST_FRAME_IAX, IAX_COMMAND_ACK, fr->ts, NULL, 0,fr->iseqno); } else if (minivid) { f.frametype = AST_FRAME_VIDEO; if (iaxs[fr->callno]->videoformat > 0) { if (ntohs(vh->ts) & 0x8000LL) { f.subclass.frame_ending = 1; } f.subclass.format = ast_format_compatibility_bitfield2format(iaxs[fr->callno]->videoformat); } else { ast_log(LOG_WARNING, "Received mini frame before first full video frame\n"); iax2_vnak(fr->callno); ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } f.datalen = res - sizeof(*vh); if (f.datalen) f.data.ptr = thread->buf + sizeof(*vh); else f.data.ptr = NULL; #ifdef IAXTESTS if (test_resync) { fr->ts = (iaxs[fr->callno]->last & 0xFFFF8000L) | ((ntohs(vh->ts) + test_resync) & 0x7fff); } else #endif /* IAXTESTS */ fr->ts = (iaxs[fr->callno]->last & 0xFFFF8000L) | (ntohs(vh->ts) & 0x7fff); } else { /* A mini frame */ f.frametype = AST_FRAME_VOICE; if (iaxs[fr->callno]->voiceformat > 0) f.subclass.format = ast_format_compatibility_bitfield2format(iaxs[fr->callno]->voiceformat); else { ast_debug(1, "Received mini frame before first full voice frame\n"); iax2_vnak(fr->callno); ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } f.datalen = res - sizeof(struct ast_iax2_mini_hdr); if (f.datalen < 0) { ast_log(LOG_WARNING, "Datalen < 0?\n"); ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } if (f.datalen) f.data.ptr = thread->buf + sizeof(*mh); else f.data.ptr = NULL; #ifdef IAXTESTS if (test_resync) { fr->ts = (iaxs[fr->callno]->last & 0xFFFF0000L) | ((ntohs(mh->ts) + test_resync) & 0xffff); } else #endif /* IAXTESTS */ fr->ts = (iaxs[fr->callno]->last & 0xFFFF0000L) | ntohs(mh->ts); /* FIXME? Surely right here would be the right place to undo timestamp wraparound? */ } /* Don't pass any packets until we're started */ if (!iaxs[fr->callno] || !ast_test_flag(&iaxs[fr->callno]->state, IAX_STATE_STARTED)) { ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } if (f.frametype == AST_FRAME_CONTROL) { if (!iax2_is_control_frame_allowed(f.subclass.integer)) { /* Control frame not allowed to come from the wire. */ ast_debug(2, "Callno %d: Blocked receiving control frame %d.\n", fr->callno, f.subclass.integer); ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } if (f.subclass.integer == AST_CONTROL_CONNECTED_LINE || f.subclass.integer == AST_CONTROL_REDIRECTING) { if (iaxs[fr->callno] && !ast_test_flag64(iaxs[fr->callno], IAX_RECVCONNECTEDLINE)) { /* We are not configured to allow receiving these updates. */ ast_debug(2, "Callno %d: Config blocked receiving control frame %d.\n", fr->callno, f.subclass.integer); ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } } iax2_lock_owner(fr->callno); if (iaxs[fr->callno] && iaxs[fr->callno]->owner) { if (f.subclass.integer == AST_CONTROL_BUSY) { ast_channel_hangupcause_set(iaxs[fr->callno]->owner, AST_CAUSE_BUSY); } else if (f.subclass.integer == AST_CONTROL_CONGESTION) { ast_channel_hangupcause_set(iaxs[fr->callno]->owner, AST_CAUSE_CONGESTION); } ast_channel_unlock(iaxs[fr->callno]->owner); } } if (f.frametype == AST_FRAME_CONTROL && f.subclass.integer == AST_CONTROL_CONNECTED_LINE && iaxs[fr->callno]) { struct ast_party_connected_line connected; /* * Process a received connected line update. * * Initialize defaults. */ ast_party_connected_line_init(&connected); connected.id.number.presentation = iaxs[fr->callno]->calling_pres; connected.id.name.presentation = iaxs[fr->callno]->calling_pres; if (!ast_connected_line_parse_data(f.data.ptr, f.datalen, &connected)) { ast_string_field_set(iaxs[fr->callno], cid_num, connected.id.number.str); ast_string_field_set(iaxs[fr->callno], cid_name, connected.id.name.str); iaxs[fr->callno]->calling_pres = ast_party_id_presentation(&connected.id); iax2_lock_owner(fr->callno); if (iaxs[fr->callno] && iaxs[fr->callno]->owner) { ast_set_callerid(iaxs[fr->callno]->owner, S_COR(connected.id.number.valid, connected.id.number.str, ""), S_COR(connected.id.name.valid, connected.id.name.str, ""), NULL); ast_channel_caller(iaxs[fr->callno]->owner)->id.number.presentation = connected.id.number.presentation; ast_channel_caller(iaxs[fr->callno]->owner)->id.name.presentation = connected.id.name.presentation; ast_channel_unlock(iaxs[fr->callno]->owner); } } ast_party_connected_line_free(&connected); } /* Common things */ f.src = "IAX2"; f.mallocd = 0; f.offset = 0; f.len = 0; if (f.datalen && (f.frametype == AST_FRAME_VOICE)) { f.samples = ast_codec_samples_count(&f); /* We need to byteswap incoming slinear samples from network byte order */ if (ast_format_cmp(f.subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) ast_frame_byteswap_be(&f); } else f.samples = 0; iax_frame_wrap(fr, &f); /* If this is our most recent packet, use it as our basis for timestamping */ if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts) { /*iaxs[fr->callno]->last = fr->ts; (do it afterwards cos schedule/forward_delivery needs the last ts too)*/ fr->outoforder = 0; } else { if (iaxdebug && iaxs[fr->callno]) { ast_debug(1, "Received out of order packet... (type=%u, subclass %d, ts = %u, last = %u)\n", f.frametype, f.subclass.integer, fr->ts, iaxs[fr->callno]->last); } fr->outoforder = -1; } fr->cacheable = ((f.frametype == AST_FRAME_VOICE) || (f.frametype == AST_FRAME_VIDEO)); if (iaxs[fr->callno]) { duped_fr = iaxfrdup2(fr); if (duped_fr) { schedule_delivery(duped_fr, updatehistory, 0, &fr->ts); } } if (iaxs[fr->callno] && iaxs[fr->callno]->last < fr->ts) { iaxs[fr->callno]->last = fr->ts; #if 1 if (iaxdebug) ast_debug(1, "For call=%d, set last=%u\n", fr->callno, fr->ts); #endif } /* Always run again */ ast_variables_destroy(ies.vars); ast_mutex_unlock(&iaxsl[fr->callno]); return 1; } static int socket_process(struct iax2_thread *thread) { struct ast_callid *callid; int res = socket_process_helper(thread); if ((callid = ast_read_threadstorage_callid())) { ast_callid_threadassoc_remove(); callid = ast_callid_unref(callid); } return res; } /* Function to clean up process thread if it is cancelled */ static void iax2_process_thread_cleanup(void *data) { struct iax2_thread *thread = data; ast_mutex_destroy(&thread->lock); ast_cond_destroy(&thread->cond); ast_mutex_destroy(&thread->init_lock); ast_cond_destroy(&thread->init_cond); ast_free(thread); /* Ignore check_return warning from Coverity for ast_atomic_dec_and_test below */ ast_atomic_dec_and_test(&iaxactivethreadcount); } static void *iax2_process_thread(void *data) { struct iax2_thread *thread = data; struct timeval wait; struct timespec ts; int put_into_idle = 0; int first_time = 1; int old_state; ast_atomic_fetchadd_int(&iaxactivethreadcount, 1); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); pthread_cleanup_push(iax2_process_thread_cleanup, data); for (;;) { /* Wait for something to signal us to be awake */ ast_mutex_lock(&thread->lock); if (thread->stop) { ast_mutex_unlock(&thread->lock); break; } /* Flag that we're ready to accept signals */ if (first_time) { signal_condition(&thread->init_lock, &thread->init_cond); first_time = 0; } /* Put into idle list if applicable */ if (put_into_idle) { insert_idle_thread(thread); } if (thread->type == IAX_THREAD_TYPE_DYNAMIC) { struct iax2_thread *t = NULL; /* Wait to be signalled or time out */ wait = ast_tvadd(ast_tvnow(), ast_samp2tv(30000, 1000)); ts.tv_sec = wait.tv_sec; ts.tv_nsec = wait.tv_usec * 1000; if (ast_cond_timedwait(&thread->cond, &thread->lock, &ts) == ETIMEDOUT) { /* This thread was never put back into the available dynamic * thread list, so just go away. */ if (!put_into_idle || thread->stop) { ast_mutex_unlock(&thread->lock); break; } AST_LIST_LOCK(&dynamic_list); /* Account for the case where this thread is acquired *right* after a timeout */ if ((t = AST_LIST_REMOVE(&dynamic_list, thread, list))) ast_atomic_fetchadd_int(&iaxdynamicthreadcount, -1); AST_LIST_UNLOCK(&dynamic_list); if (t) { /* This dynamic thread timed out waiting for a task and was * not acquired immediately after the timeout, * so it's time to go away. */ ast_mutex_unlock(&thread->lock); break; } /* Someone grabbed our thread *right* after we timed out. * Wait for them to set us up with something to do and signal * us to continue. */ wait = ast_tvadd(ast_tvnow(), ast_samp2tv(30000, 1000)); ts.tv_sec = wait.tv_sec; ts.tv_nsec = wait.tv_usec * 1000; if (ast_cond_timedwait(&thread->cond, &thread->lock, &ts) == ETIMEDOUT) { ast_mutex_unlock(&thread->lock); break; } } } else { ast_cond_wait(&thread->cond, &thread->lock); } /* Go back into our respective list */ put_into_idle = 1; ast_mutex_unlock(&thread->lock); if (thread->stop) { break; } /* See what we need to do */ switch (thread->iostate) { case IAX_IOSTATE_IDLE: continue; case IAX_IOSTATE_READY: thread->actions++; thread->iostate = IAX_IOSTATE_PROCESSING; socket_process(thread); handle_deferred_full_frames(thread); break; case IAX_IOSTATE_SCHEDREADY: thread->actions++; thread->iostate = IAX_IOSTATE_PROCESSING; #ifdef SCHED_MULTITHREADED thread->schedfunc(thread->scheddata); #endif break; default: break; } /* The network thread added us to the active_thread list when we were given * frames to process, Now that we are done, we must remove ourselves from * the active list, and return to the idle list */ AST_LIST_LOCK(&active_list); AST_LIST_REMOVE(&active_list, thread, list); AST_LIST_UNLOCK(&active_list); /* Make sure another frame didn't sneak in there after we thought we were done. */ handle_deferred_full_frames(thread); time(&thread->checktime); thread->iostate = IAX_IOSTATE_IDLE; #ifdef DEBUG_SCHED_MULTITHREAD thread->curfunc[0]='\0'; #endif } /*! * \note For some reason, idle threads are exiting without being * removed from an idle list, which is causing memory * corruption. Forcibly remove it from the list, if it's there. */ AST_LIST_LOCK(&idle_list); AST_LIST_REMOVE(&idle_list, thread, list); AST_LIST_UNLOCK(&idle_list); AST_LIST_LOCK(&dynamic_list); AST_LIST_REMOVE(&dynamic_list, thread, list); AST_LIST_UNLOCK(&dynamic_list); if (!thread->stop) { /* Nobody asked me to stop so nobody is waiting to join me. */ pthread_detach(pthread_self()); } /* I am exiting here on my own volition, I need to clean up my own data structures * Assume that I am no longer in any of the lists (idle, active, or dynamic) */ pthread_cleanup_pop(1); return NULL; } static int iax2_do_register(struct iax2_registry *reg) { struct iax_ie_data ied; if (iaxdebug) ast_debug(1, "Sending registration request for '%s'\n", reg->username); if (reg->dnsmgr && ((reg->regstate == REG_STATE_TIMEOUT) || ast_sockaddr_isnull(®->addr))) { /* Maybe the IP has changed, force DNS refresh */ ast_dnsmgr_refresh(reg->dnsmgr); } /* * if IP has Changed, free allocated call to create a new one with new IP * call has the pointer to IP and must be updated to the new one */ if (reg->dnsmgr && ast_dnsmgr_changed(reg->dnsmgr) && (reg->callno > 0)) { int callno = reg->callno; ast_mutex_lock(&iaxsl[callno]); iax2_destroy(callno); ast_mutex_unlock(&iaxsl[callno]); reg->callno = 0; } if (ast_sockaddr_isnull(®->addr)) { if (iaxdebug) ast_debug(1, "Unable to send registration request for '%s' without IP address\n", reg->username); /* Setup the next registration attempt */ reg->expire = iax2_sched_replace(reg->expire, sched, (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg); return -1; } if (!ast_sockaddr_port(®->addr) && reg->port) { ast_sockaddr_set_port(®->addr, reg->port); } if (!reg->callno) { ast_debug(3, "Allocate call number\n"); reg->callno = find_callno_locked(0, 0, ®->addr, NEW_FORCE, defaultsockfd, 0); if (reg->callno < 1) { ast_log(LOG_WARNING, "Unable to create call for registration\n"); return -1; } else ast_debug(3, "Registration created on call %d\n", reg->callno); iaxs[reg->callno]->reg = reg; ast_mutex_unlock(&iaxsl[reg->callno]); } /* Setup the next registration a little early */ reg->expire = iax2_sched_replace(reg->expire, sched, (5 * reg->refresh / 6) * 1000, iax2_do_register_s, reg); /* Send the request */ memset(&ied, 0, sizeof(ied)); iax_ie_append_str(&ied, IAX_IE_USERNAME, reg->username); iax_ie_append_short(&ied, IAX_IE_REFRESH, reg->refresh); add_empty_calltoken_ie(iaxs[reg->callno], &ied); /* this _MUST_ be the last ie added */ send_command(iaxs[reg->callno],AST_FRAME_IAX, IAX_COMMAND_REGREQ, 0, ied.buf, ied.pos, -1); reg->regstate = REG_STATE_REGSENT; return 0; } static int iax2_provision(struct ast_sockaddr *end, int sockfd, const char *dest, const char *template, int force) { /* Returns 1 if provisioned, -1 if not able to find destination, or 0 if no provisioning is found for template */ struct iax_ie_data provdata; struct iax_ie_data ied; unsigned int sig; struct ast_sockaddr addr; int callno; struct create_addr_info cai; memset(&cai, 0, sizeof(cai)); ast_debug(1, "Provisioning '%s' from template '%s'\n", dest, template); if (iax_provision_build(&provdata, &sig, template, force)) { ast_debug(1, "No provisioning found for template '%s'\n", template); return 0; } if (end) { ast_sockaddr_copy(&addr, end); cai.sockfd = sockfd; } else if (create_addr(dest, NULL, &addr, &cai)) return -1; /* Build the rest of the message */ memset(&ied, 0, sizeof(ied)); iax_ie_append_raw(&ied, IAX_IE_PROVISIONING, provdata.buf, provdata.pos); callno = find_callno_locked(0, 0, &addr, NEW_FORCE, cai.sockfd, 0); if (!callno) return -1; if (iaxs[callno]) { /* Schedule autodestruct in case they don't ever give us anything back */ iaxs[callno]->autoid = iax2_sched_replace(iaxs[callno]->autoid, sched, 15000, auto_hangup, (void *)(long)callno); ast_set_flag64(iaxs[callno], IAX_PROVISION); /* Got a call number now, so go ahead and send the provisioning information */ send_command(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_PROVISION, 0, ied.buf, ied.pos, -1); } ast_mutex_unlock(&iaxsl[callno]); return 1; } static char *papp = "IAX2Provision"; /*! iax2provision \ingroup applications */ static int iax2_prov_app(struct ast_channel *chan, const char *data) { int res; char *sdata; char *opts; int force =0; unsigned short callno = PTR_TO_CALLNO(ast_channel_tech_pvt(chan)); if (ast_strlen_zero(data)) data = "default"; sdata = ast_strdupa(data); opts = strchr(sdata, '|'); if (opts) *opts='\0'; if (ast_channel_tech(chan) != &iax2_tech) { ast_log(LOG_NOTICE, "Can't provision a non-IAX device!\n"); return -1; } if (!callno || !iaxs[callno] || ast_sockaddr_isnull(&iaxs[callno]->addr)) { ast_log(LOG_NOTICE, "Can't provision something with no IP?\n"); return -1; } res = iax2_provision(&iaxs[callno]->addr, iaxs[callno]->sockfd, NULL, sdata, force); ast_verb(3, "Provisioned IAXY at '%s' with '%s'= %d\n", ast_sockaddr_stringify(&iaxs[callno]->addr), sdata, res); return res; } static char *handle_cli_iax2_provision(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { int force = 0; int res; switch (cmd) { case CLI_INIT: e->command = "iax2 provision"; e->usage = "Usage: iax2 provision