From 7e6c48db4dfc19ef7870029cfeaab28b82a67616 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 8 Oct 2008 01:10:11 +0000 Subject: [PATCH] --- LICENSE | 22 + Makefile | 138 + README | 55 + allstar/rc.updatenodelist | 31 + asterisk/BUGS | 22 + asterisk/CHANGES | 356 + asterisk/COPYING | 341 + asterisk/CREDITS | 215 + asterisk/LICENSE | 68 + asterisk/Makefile | 763 + asterisk/Makefile.moddir_rules | 112 + asterisk/Makefile.rules | 81 + asterisk/README | 262 + asterisk/UPGRADE-1.2.txt | 210 + asterisk/UPGRADE.txt | 500 + asterisk/Zaptel-to-DAHDI.txt | 105 + asterisk/acinclude.m4 | 1035 + asterisk/agi/DialAnMp3.agi | 82 + asterisk/agi/Makefile | 47 + asterisk/agi/agi-test.agi | 79 + asterisk/agi/eagi-sphinx-test.c | 222 + asterisk/agi/eagi-test.c | 165 + asterisk/agi/fastagi-test | 94 + asterisk/agi/jukebox.agi | 488 + asterisk/agi/numeralize | 44 + asterisk/apps/Makefile | 42 + asterisk/apps/app_adsiprog.c | 1590 + asterisk/apps/app_alarmreceiver.c | 841 + asterisk/apps/app_amd.c | 430 + asterisk/apps/app_authenticate.c | 252 + asterisk/apps/app_cdr.c | 78 + asterisk/apps/app_chanisavail.c | 173 + asterisk/apps/app_channelredirect.c | 140 + asterisk/apps/app_chanspy.c | 874 + asterisk/apps/app_controlplayback.c | 168 + asterisk/apps/app_dahdibarge.c | 362 + asterisk/apps/app_dahdiras.c | 288 + asterisk/apps/app_dahdiscan.c | 389 + asterisk/apps/app_db.c | 167 + asterisk/apps/app_dial.c | 1977 + asterisk/apps/app_dictate.c | 349 + asterisk/apps/app_directed_pickup.c | 181 + asterisk/apps/app_directory.c | 704 + asterisk/apps/app_disa.c | 393 + asterisk/apps/app_dumpchan.c | 176 + asterisk/apps/app_echo.c | 104 + asterisk/apps/app_exec.c | 221 + asterisk/apps/app_externalivr.c | 585 + asterisk/apps/app_festival.c | 553 + asterisk/apps/app_flash.c | 136 + asterisk/apps/app_followme.c | 1113 + asterisk/apps/app_forkcdr.c | 267 + asterisk/apps/app_getcpeid.c | 148 + asterisk/apps/app_hasnewvoicemail.c | 225 + asterisk/apps/app_ices.c | 233 + asterisk/apps/app_image.c | 125 + asterisk/apps/app_ivrdemo.c | 132 + asterisk/apps/app_lookupblacklist.c | 160 + asterisk/apps/app_lookupcidname.c | 103 + asterisk/apps/app_macro.c | 557 + asterisk/apps/app_meetme.c | 4894 +++ asterisk/apps/app_milliwatt.c | 194 + asterisk/apps/app_mixmonitor.c | 446 + asterisk/apps/app_morsecode.c | 179 + asterisk/apps/app_mp3.c | 255 + asterisk/apps/app_nbscat.c | 237 + asterisk/apps/app_osplookup.c | 1677 + asterisk/apps/app_page.c | 203 + asterisk/apps/app_parkandannounce.c | 260 + asterisk/apps/app_playback.c | 494 + asterisk/apps/app_privacy.c | 232 + asterisk/apps/app_queue.c | 5118 +++ asterisk/apps/app_random.c | 108 + asterisk/apps/app_read.c | 234 + asterisk/apps/app_readfile.c | 120 + asterisk/apps/app_realtime.c | 261 + asterisk/apps/app_record.c | 386 + asterisk/apps/app_rpt.c | 15664 ++++++++ asterisk/apps/app_sayunixtime.c | 126 + asterisk/apps/app_senddtmf.c | 143 + asterisk/apps/app_sendtext.c | 131 + asterisk/apps/app_setcallerid.c | 162 + asterisk/apps/app_setcdruserfield.c | 175 + asterisk/apps/app_settransfercapability.c | 136 + asterisk/apps/app_skel.c | 133 + asterisk/apps/app_sms.c | 1536 + asterisk/apps/app_softhangup.c | 121 + asterisk/apps/app_speech_utils.c | 874 + asterisk/apps/app_stack.c | 174 + asterisk/apps/app_system.c | 162 + asterisk/apps/app_talkdetect.c | 227 + asterisk/apps/app_test.c | 512 + asterisk/apps/app_transfer.c | 156 + asterisk/apps/app_url.c | 173 + asterisk/apps/app_userevent.c | 109 + asterisk/apps/app_verbose.c | 168 + asterisk/apps/app_voicemail.c | 9108 +++++ asterisk/apps/app_waitforring.c | 136 + asterisk/apps/app_waitforsilence.c | 205 + asterisk/apps/app_while.c | 335 + asterisk/apps/app_zapateller.c | 120 + asterisk/apps/enter.h | 287 + asterisk/apps/leave.h | 207 + asterisk/apps/rpt_flow.pdf | Bin 0 -> 51935 bytes asterisk/bootstrap.sh | 40 + asterisk/build_tools/cflags-devmode.xml | 17 + asterisk/build_tools/cflags.xml | 21 + asterisk/build_tools/embed_modules.xml | 26 + asterisk/build_tools/get_makeopts | 3 + asterisk/build_tools/get_moduleinfo | 3 + asterisk/build_tools/make_build_h | 20 + asterisk/build_tools/make_buildopts_h | 29 + asterisk/build_tools/make_defaults_h | 26 + asterisk/build_tools/make_sample_voicemail | 25 + asterisk/build_tools/make_version | 83 + asterisk/build_tools/make_version_h | 25 + asterisk/build_tools/menuselect-deps.in | 41 + asterisk/build_tools/mkpkgconfig | 50 + asterisk/build_tools/prep_tarball | 9 + asterisk/build_tools/strip_nonapi | 37 + asterisk/cdr/Makefile | 32 + asterisk/cdr/cdr_csv.c | 365 + asterisk/cdr/cdr_custom.c | 174 + asterisk/cdr/cdr_manager.c | 170 + asterisk/cdr/cdr_odbc.c | 472 + asterisk/cdr/cdr_pgsql.c | 336 + asterisk/cdr/cdr_radius.c | 273 + asterisk/cdr/cdr_sqlite.c | 217 + asterisk/cdr/cdr_tds.c | 630 + asterisk/channels/DialTone.h | 252 + asterisk/channels/Makefile | 132 + asterisk/channels/answer.h | 237 + asterisk/channels/chan_agent.c | 2852 ++ asterisk/channels/chan_alsa.c | 1374 + asterisk/channels/chan_dahdi.c | 12006 ++++++ asterisk/channels/chan_echolink.c | 2754 ++ asterisk/channels/chan_features.c | 580 + asterisk/channels/chan_gtalk.c | 2067 + asterisk/channels/chan_h323.c | 3279 ++ asterisk/channels/chan_iax2.c | 11264 ++++++ asterisk/channels/chan_irlp.c | 1613 + asterisk/channels/chan_local.c | 792 + asterisk/channels/chan_mgcp.c | 4430 +++ asterisk/channels/chan_misdn.c | 5559 +++ asterisk/channels/chan_nbs.c | 302 + asterisk/channels/chan_oss.c | 1887 + asterisk/channels/chan_phone.c | 1433 + asterisk/channels/chan_rtpdir.c | 705 + asterisk/channels/chan_sip.c | 18725 +++++++++ asterisk/channels/chan_skinny.c | 5016 +++ asterisk/channels/chan_usbradio.c | 4057 ++ asterisk/channels/chan_vpb.cc | 2905 ++ asterisk/channels/gentone-ulaw.c | 157 + asterisk/channels/gentone.c | 95 + asterisk/channels/h323/ChangeLog | 43 + asterisk/channels/h323/INSTALL.openh323 | 18 + asterisk/channels/h323/Makefile.in | 48 + asterisk/channels/h323/README | 144 + asterisk/channels/h323/TODO | 9 + asterisk/channels/h323/ast_h323.cxx | 2487 ++ asterisk/channels/h323/ast_h323.h | 166 + asterisk/channels/h323/caps_h323.cxx | 239 + asterisk/channels/h323/caps_h323.h | 124 + asterisk/channels/h323/chan_h323.h | 254 + asterisk/channels/h323/cisco-h225.asn | 74 + asterisk/channels/h323/cisco-h225.cxx | 853 + asterisk/channels/h323/cisco-h225.h | 299 + asterisk/channels/h323/compat_h323.cxx | 138 + asterisk/channels/h323/compat_h323.h | 80 + asterisk/channels/h323/noexport.map | 5 + asterisk/channels/iax2-parser.c | 1053 + asterisk/channels/iax2-parser.h | 160 + asterisk/channels/iax2-provision.c | 540 + asterisk/channels/iax2-provision.h | 53 + asterisk/channels/iax2.h | 237 + asterisk/channels/misdn/Makefile | 17 + asterisk/channels/misdn/chan_misdn_config.h | 152 + asterisk/channels/misdn/ie.c | 1422 + asterisk/channels/misdn/isdn_lib.c | 4665 +++ asterisk/channels/misdn/isdn_lib.h | 674 + asterisk/channels/misdn/isdn_lib_intern.h | 142 + asterisk/channels/misdn/isdn_msg_parser.c | 1348 + asterisk/channels/misdn/portinfo.c | 198 + asterisk/channels/misdn_config.c | 1159 + asterisk/channels/ring10.h | 1752 + asterisk/channels/xpmr/sinetabx.h | 300 + asterisk/channels/xpmr/xpmr.c | 3332 ++ asterisk/channels/xpmr/xpmr.h | 951 + asterisk/channels/xpmr/xpmr_coef.h | 667 + asterisk/codecs/Makefile | 61 + asterisk/codecs/adpcm_slin_ex.h | 25 + asterisk/codecs/codec_a_mu.c | 168 + asterisk/codecs/codec_adpcm.c | 748 + asterisk/codecs/codec_alaw.c | 189 + asterisk/codecs/codec_dahdi.c | 494 + asterisk/codecs/codec_g726.c | 964 + asterisk/codecs/codec_gsm.c | 290 + asterisk/codecs/codec_ilbc.c | 248 + asterisk/codecs/codec_lpc10.c | 317 + asterisk/codecs/codec_speex.c | 522 + asterisk/codecs/codec_ulaw.c | 201 + asterisk/codecs/g726_slin_ex.h | 25 + asterisk/codecs/gsm/COPYRIGHT | 16 + asterisk/codecs/gsm/Makefile | 543 + asterisk/codecs/gsm/README | 37 + asterisk/codecs/gsm/inc/config.h | 51 + asterisk/codecs/gsm/inc/gsm.h | 71 + asterisk/codecs/gsm/inc/private.h | 312 + asterisk/codecs/gsm/inc/proto.h | 65 + asterisk/codecs/gsm/inc/unproto.h | 23 + asterisk/codecs/gsm/libgsm.vcproj | 253 + asterisk/codecs/gsm/src/add.c | 235 + asterisk/codecs/gsm/src/code.c | 97 + asterisk/codecs/gsm/src/debug.c | 76 + asterisk/codecs/gsm/src/decode.c | 62 + asterisk/codecs/gsm/src/gsm_create.c | 45 + asterisk/codecs/gsm/src/gsm_decode.c | 361 + asterisk/codecs/gsm/src/gsm_destroy.c | 26 + asterisk/codecs/gsm/src/gsm_encode.c | 451 + asterisk/codecs/gsm/src/gsm_explode.c | 417 + asterisk/codecs/gsm/src/gsm_implode.c | 515 + asterisk/codecs/gsm/src/gsm_option.c | 69 + asterisk/codecs/gsm/src/gsm_print.c | 167 + asterisk/codecs/gsm/src/k6opt.h | 84 + asterisk/codecs/gsm/src/k6opt.s | 739 + asterisk/codecs/gsm/src/long_term.c | 955 + asterisk/codecs/gsm/src/lpc.c | 372 + asterisk/codecs/gsm/src/preprocess.c | 127 + asterisk/codecs/gsm/src/rpe.c | 490 + asterisk/codecs/gsm/src/short_term.c | 448 + asterisk/codecs/gsm/src/table.c | 63 + asterisk/codecs/gsm_slin_ex.h | 16 + asterisk/codecs/ilbc/Makefile | 20 + asterisk/codecs/ilbc_slin_ex.h | 17 + asterisk/codecs/log2comp.h | 74 + asterisk/codecs/lpc10/Makefile | 77 + asterisk/codecs/lpc10/README | 89 + asterisk/codecs/lpc10/analys.c | 649 + asterisk/codecs/lpc10/bsynz.c | 447 + asterisk/codecs/lpc10/chanwr.c | 232 + asterisk/codecs/lpc10/dcbias.c | 107 + asterisk/codecs/lpc10/decode.c | 625 + asterisk/codecs/lpc10/deemp.c | 154 + asterisk/codecs/lpc10/difmag.c | 133 + asterisk/codecs/lpc10/dyptrk.c | 405 + asterisk/codecs/lpc10/encode.c | 373 + asterisk/codecs/lpc10/energy.c | 103 + asterisk/codecs/lpc10/f2c.h | 325 + asterisk/codecs/lpc10/f2clib.c | 85 + asterisk/codecs/lpc10/ham84.c | 126 + asterisk/codecs/lpc10/hp100.c | 169 + asterisk/codecs/lpc10/invert.c | 193 + asterisk/codecs/lpc10/irc2pc.c | 151 + asterisk/codecs/lpc10/ivfilt.c | 136 + asterisk/codecs/lpc10/liblpc10.vcproj | 305 + asterisk/codecs/lpc10/lpc10.h | 256 + asterisk/codecs/lpc10/lpcdec.c | 297 + asterisk/codecs/lpc10/lpcenc.c | 181 + asterisk/codecs/lpc10/lpcini.c | 446 + asterisk/codecs/lpc10/lpfilt.c | 125 + asterisk/codecs/lpc10/median.c | 89 + asterisk/codecs/lpc10/mload.c | 163 + asterisk/codecs/lpc10/onset.c | 324 + asterisk/codecs/lpc10/pitsyn.c | 583 + asterisk/codecs/lpc10/placea.c | 242 + asterisk/codecs/lpc10/placev.c | 275 + asterisk/codecs/lpc10/preemp.c | 144 + asterisk/codecs/lpc10/prepro.c | 116 + asterisk/codecs/lpc10/random.c | 125 + asterisk/codecs/lpc10/rcchk.c | 119 + asterisk/codecs/lpc10/synths.c | 425 + asterisk/codecs/lpc10/tbdm.c | 188 + asterisk/codecs/lpc10/voicin.c | 786 + asterisk/codecs/lpc10/vparms.c | 255 + asterisk/codecs/lpc10_slin_ex.h | 13 + asterisk/codecs/slin_adpcm_ex.h | 25 + asterisk/codecs/slin_g726_ex.h | 25 + asterisk/codecs/slin_gsm_ex.h | 28 + asterisk/codecs/slin_ilbc_ex.h | 28 + asterisk/codecs/slin_lpc10_ex.h | 21 + asterisk/codecs/slin_speex_ex.h | 262 + asterisk/codecs/slin_ulaw_ex.h | 25 + asterisk/codecs/speex_slin_ex.h | 16 + asterisk/codecs/ulaw_slin_ex.h | 25 + asterisk/config.guess | 1495 + asterisk/config.sub | 1609 + asterisk/configs/adsi.conf.sample | 8 + asterisk/configs/adtranvofr.conf.sample | 39 + asterisk/configs/agents.conf.sample | 107 + asterisk/configs/alarmreceiver.conf.sample | 80 + asterisk/configs/alsa.conf.sample | 62 + asterisk/configs/amd.conf.sample | 18 + asterisk/configs/asterisk.adsi | 159 + asterisk/configs/cdr.conf.sample | 148 + asterisk/configs/cdr_custom.conf.sample | 10 + asterisk/configs/cdr_manager.conf.sample | 6 + asterisk/configs/cdr_odbc.conf.sample | 12 + asterisk/configs/cdr_pgsql.conf.sample | 9 + asterisk/configs/cdr_tds.conf.sample | 11 + asterisk/configs/chan_dahdi.conf.sample | 675 + asterisk/configs/codecs.conf.sample | 65 + asterisk/configs/dnsmgr.conf.sample | 5 + asterisk/configs/dundi.conf.sample | 239 + asterisk/configs/enum.conf.sample | 22 + asterisk/configs/extconfig.conf.sample | 58 + asterisk/configs/extensions.ael.sample | 448 + asterisk/configs/extensions.conf.sample | 614 + asterisk/configs/features.conf.sample | 98 + asterisk/configs/festival.conf.sample | 35 + asterisk/configs/followme.conf.sample | 86 + asterisk/configs/func_odbc.conf.sample | 41 + asterisk/configs/gtalk.conf.sample | 19 + asterisk/configs/h323.conf.sample | 193 + asterisk/configs/http.conf.sample | 40 + asterisk/configs/iax.conf.sample | 416 + asterisk/configs/iaxprov.conf.sample | 81 + asterisk/configs/indications.conf.sample | 733 + asterisk/configs/jabber.conf.sample | 18 + asterisk/configs/logger.conf.sample | 69 + asterisk/configs/manager.conf.sample | 56 + asterisk/configs/meetme.conf.sample | 26 + asterisk/configs/mgcp.conf.sample | 104 + asterisk/configs/misdn.conf.sample | 438 + asterisk/configs/modules.conf.sample | 35 + asterisk/configs/musiconhold.conf.sample | 66 + asterisk/configs/muted.conf.sample | 39 + asterisk/configs/osp.conf.sample | 72 + asterisk/configs/oss.conf.sample | 75 + asterisk/configs/phone.conf.sample | 49 + asterisk/configs/privacy.conf.sample | 3 + asterisk/configs/queues.conf.sample | 311 + asterisk/configs/res_odbc.conf.sample | 49 + asterisk/configs/res_pgsql.conf.sample | 14 + asterisk/configs/res_snmp.conf.sample | 10 + asterisk/configs/rpt.conf.sample | 537 + asterisk/configs/rtp.conf.sample | 22 + asterisk/configs/say.conf.sample | 171 + asterisk/configs/sip.conf.sample | 685 + asterisk/configs/sip_notify.conf.sample | 22 + asterisk/configs/skinny.conf.sample | 96 + asterisk/configs/sla.conf.sample | 140 + asterisk/configs/smdi.conf.sample | 75 + asterisk/configs/telcordia-1.adsi | 83 + asterisk/configs/udptl.conf.sample | 30 + asterisk/configs/usbradio.conf.sample | 102 + asterisk/configs/users.conf.sample | 79 + asterisk/configs/voicemail.conf.sample | 250 + asterisk/configs/vpb.conf.sample | 108 + asterisk/configure | 33025 ++++++++++++++++ asterisk/configure.ac | 1661 + asterisk/contrib/README.festival | 47 + asterisk/contrib/asterisk-doxygen-header | 10 + asterisk/contrib/asterisk-ices.xml | 93 + asterisk/contrib/asterisk-ng-doxygen | 1230 + asterisk/contrib/dictionary.digium | 31 + asterisk/contrib/festival-1.4.1-diff | 76 + asterisk/contrib/festival-1.4.2.diff | 75 + asterisk/contrib/festival-1.4.3.diff | 93 + asterisk/contrib/festival-1.95.diff | 107 + asterisk/contrib/firmware/iax/iaxy.bin | Bin 0 -> 39402 bytes asterisk/contrib/i18n.testsuite.conf | 136 + asterisk/contrib/init.d/rc.debian.asterisk | 95 + asterisk/contrib/init.d/rc.gentoo.asterisk | 26 + asterisk/contrib/init.d/rc.mandrake.asterisk | 195 + asterisk/contrib/init.d/rc.mandrake.zaptel | 108 + asterisk/contrib/init.d/rc.redhat.asterisk | 144 + asterisk/contrib/init.d/rc.slackware.asterisk | 51 + asterisk/contrib/init.d/rc.suse.asterisk | 136 + .../contrib/scripts/README.messages-expire | 20 + asterisk/contrib/scripts/agents.php | 73 + asterisk/contrib/scripts/ast_grab_core | 70 + asterisk/contrib/scripts/astgenkey | 61 + asterisk/contrib/scripts/astgenkey.8 | 144 + asterisk/contrib/scripts/autosupport | 237 + asterisk/contrib/scripts/autosupport.8 | 41 + asterisk/contrib/scripts/get_ilbc_source.sh | 33 + asterisk/contrib/scripts/iax-friends.sql | 54 + asterisk/contrib/scripts/loadtest.tcl | 148 + asterisk/contrib/scripts/lookup.agi | 90 + asterisk/contrib/scripts/managerproxy.pl | 242 + asterisk/contrib/scripts/meetme.sql | 12 + asterisk/contrib/scripts/messages-expire.pl | 96 + asterisk/contrib/scripts/qview.pl | 100 + asterisk/contrib/scripts/realtime_pgsql.sql | 142 + .../scripts/retrieve_extensions_from_mysql.pl | 113 + .../scripts/retrieve_extensions_from_sql.pl | 158 + .../scripts/retrieve_sip_conf_from_mysql.pl | 93 + asterisk/contrib/scripts/safe_asterisk | 178 + asterisk/contrib/scripts/safe_asterisk.8 | 69 + .../contrib/scripts/safe_asterisk_restart | 110 + asterisk/contrib/scripts/sip-friends.sql | 54 + asterisk/contrib/scripts/vmail.cgi | 1140 + asterisk/contrib/scripts/vmdb.sql | 64 + .../contrib/thirdparty/spexxilbcfix_xlite.reg | Bin 0 -> 452 bytes .../contrib/thirdparty/spexxilbcfix_xpro.reg | Bin 0 -> 450 bytes asterisk/contrib/utils/README.rawplayer | 37 + asterisk/contrib/utils/rawplayer.c | 46 + asterisk/contrib/utils/zones2indications.c | 153 + asterisk/contrib/valgrind-RedHat-8.0.supp | 41 + asterisk/dev-1.0/apps/app_rpt.c | 15525 ++++++++ asterisk/dev-1.0/channels/chan_irlp.c | 1383 + asterisk/dev-1.0/channels/chan_locloop.c | 421 + .../channels/irlp-scripts/noupdate.tar.gz | Bin 0 -> 5854 bytes asterisk/doc/00README.1st | 75 + asterisk/doc/CODING-GUIDELINES | 543 + asterisk/doc/PEERING | 499 + asterisk/doc/ael.txt | 1260 + asterisk/doc/ajam.txt | 91 + asterisk/doc/app-sms.txt | 470 + asterisk/doc/apps.txt | 10 + asterisk/doc/asterisk-conf.txt | 89 + asterisk/doc/asterisk-mib.txt | 748 + asterisk/doc/asterisk.8 | 195 + asterisk/doc/asterisk.sgml | 364 + asterisk/doc/backtrace.txt | 197 + asterisk/doc/billing.txt | 105 + asterisk/doc/callfiles.txt | 139 + asterisk/doc/callingpres.txt | 18 + asterisk/doc/cdrdriver.txt | 216 + asterisk/doc/chaniax.txt | 369 + asterisk/doc/channels.txt | 44 + asterisk/doc/channelvariables.txt | 815 + asterisk/doc/cli.txt | 33 + asterisk/doc/cliprompt.txt | 29 + asterisk/doc/configuration.txt | 180 + asterisk/doc/cygwin.txt | 9 + asterisk/doc/datastores.txt | 63 + asterisk/doc/digium-mib.txt | 24 + asterisk/doc/dundi.txt | 26 + asterisk/doc/enum.txt | 308 + asterisk/doc/extconfig.txt | 91 + asterisk/doc/extensions.txt | 58 + asterisk/doc/externalivr.txt | 109 + asterisk/doc/freetds.txt | 18 + asterisk/doc/h323.txt | 22 + asterisk/doc/hardware.txt | 74 + asterisk/doc/iax.txt | 67 + asterisk/doc/ices.txt | 12 + asterisk/doc/imapstorage.txt | 217 + asterisk/doc/ip-tos.txt | 81 + asterisk/doc/jabber.txt | 15 + asterisk/doc/jingle.txt | 11 + asterisk/doc/jitterbuffer.txt | 137 + asterisk/doc/lang/hebrew.ods | Bin 0 -> 23290 bytes asterisk/doc/linkedlists.txt | 98 + asterisk/doc/localchannel.txt | 49 + asterisk/doc/macroexclusive.txt | 78 + asterisk/doc/manager.txt | 311 + asterisk/doc/math.txt | 69 + asterisk/doc/misdn.txt | 301 + asterisk/doc/model.txt | 15 + asterisk/doc/modules.txt | 26 + asterisk/doc/mp3.txt | 13 + asterisk/doc/musiconhold-fpm.txt | 8 + asterisk/doc/mysql.txt | 15 + asterisk/doc/odbcstorage.txt | 30 + asterisk/doc/osp.txt | 421 + asterisk/doc/privacy.txt | 361 + asterisk/doc/queuelog.txt | 99 + asterisk/doc/queues-with-callback-members.txt | 521 + asterisk/doc/radius.txt | 203 + asterisk/doc/realtime.txt | 138 + asterisk/doc/rtp-packetization.txt | 73 + asterisk/doc/security.txt | 80 + asterisk/doc/sip-retransmit.txt | 126 + asterisk/doc/sla.pdf | Bin 0 -> 68499 bytes asterisk/doc/sla.tex | 378 + asterisk/doc/smdi.txt | 137 + asterisk/doc/sms.txt | 147 + asterisk/doc/snmp.txt | 39 + asterisk/doc/speechrec.txt | 295 + asterisk/doc/valgrind.txt | 26 + asterisk/doc/video.txt | 46 + asterisk/doc/voicemail_odbc_postgresql.txt | 453 + asterisk/formats/Makefile | 32 + asterisk/formats/format_g723.c | 163 + asterisk/formats/format_g726.c | 277 + asterisk/formats/format_g729.c | 159 + asterisk/formats/format_gsm.c | 181 + asterisk/formats/format_h263.c | 197 + asterisk/formats/format_h264.c | 186 + asterisk/formats/format_ilbc.c | 157 + asterisk/formats/format_jpeg.c | 127 + asterisk/formats/format_ogg_vorbis.c | 564 + asterisk/formats/format_pcm.c | 507 + asterisk/formats/format_sln.c | 141 + asterisk/formats/format_vox.c | 146 + asterisk/formats/format_wav.c | 528 + asterisk/formats/format_wav_gsm.c | 560 + asterisk/formats/msgsm.h | 689 + asterisk/funcs/Makefile | 32 + asterisk/funcs/func_base64.c | 94 + asterisk/funcs/func_callerid.c | 174 + asterisk/funcs/func_cdr.c | 185 + asterisk/funcs/func_channel.c | 202 + asterisk/funcs/func_curl.c | 215 + asterisk/funcs/func_cut.c | 332 + asterisk/funcs/func_db.c | 231 + asterisk/funcs/func_enum.c | 198 + asterisk/funcs/func_env.c | 158 + asterisk/funcs/func_global.c | 86 + asterisk/funcs/func_groupcount.c | 243 + asterisk/funcs/func_language.c | 90 + asterisk/funcs/func_logic.c | 214 + asterisk/funcs/func_math.c | 268 + asterisk/funcs/func_md5.c | 120 + asterisk/funcs/func_moh.c | 87 + asterisk/funcs/func_odbc.c | 657 + asterisk/funcs/func_rand.c | 104 + asterisk/funcs/func_realtime.c | 175 + asterisk/funcs/func_sha1.c | 83 + asterisk/funcs/func_strings.c | 652 + asterisk/funcs/func_timeout.c | 194 + asterisk/funcs/func_uri.c | 101 + asterisk/images/animlogo.gif | Bin 0 -> 63968 bytes asterisk/images/asterisk-intro.jpg | Bin 0 -> 6143 bytes asterisk/images/play.gif | Bin 0 -> 341 bytes asterisk/include/asterisk.h | 210 + asterisk/include/asterisk/abstract_jb.h | 226 + asterisk/include/asterisk/acl.h | 57 + asterisk/include/asterisk/adsi.h | 353 + asterisk/include/asterisk/ael_structs.h | 196 + asterisk/include/asterisk/aes.h | 170 + asterisk/include/asterisk/agi.h | 57 + asterisk/include/asterisk/alaw.h | 43 + asterisk/include/asterisk/app.h | 434 + asterisk/include/asterisk/ast_expr.h | 32 + asterisk/include/asterisk/astdb.h | 52 + asterisk/include/asterisk/astmm.h | 89 + asterisk/include/asterisk/astobj.h | 824 + asterisk/include/asterisk/astobj2.h | 543 + asterisk/include/asterisk/astosp.h | 31 + asterisk/include/asterisk/audiohook.h | 182 + asterisk/include/asterisk/autoconfig.h.in | 704 + asterisk/include/asterisk/callerid.h | 339 + asterisk/include/asterisk/causes.h | 83 + asterisk/include/asterisk/cdr.h | 342 + asterisk/include/asterisk/channel.h | 1418 + asterisk/include/asterisk/chanvars.h | 42 + asterisk/include/asterisk/cli.h | 183 + asterisk/include/asterisk/compat.h | 131 + asterisk/include/asterisk/compiler.h | 62 + asterisk/include/asterisk/config.h | 206 + asterisk/include/asterisk/crypto.h | 112 + asterisk/include/asterisk/dahdi_compat.h | 184 + asterisk/include/asterisk/devicestate.h | 133 + asterisk/include/asterisk/dial.h | 151 + asterisk/include/asterisk/dns.h | 39 + asterisk/include/asterisk/dnsmgr.h | 62 + asterisk/include/asterisk/doxyref.h | 580 + asterisk/include/asterisk/dsp.h | 124 + asterisk/include/asterisk/dundi.h | 226 + asterisk/include/asterisk/endian.h | 62 + asterisk/include/asterisk/enum.h | 59 + asterisk/include/asterisk/features.h | 99 + asterisk/include/asterisk/file.h | 419 + asterisk/include/asterisk/frame.h | 593 + asterisk/include/asterisk/fskmodem.h | 72 + asterisk/include/asterisk/global_datastores.h | 47 + asterisk/include/asterisk/http.h | 65 + asterisk/include/asterisk/image.h | 96 + asterisk/include/asterisk/indications.h | 89 + asterisk/include/asterisk/inline_api.h | 66 + asterisk/include/asterisk/io.h | 145 + asterisk/include/asterisk/jabber.h | 146 + asterisk/include/asterisk/jingle.h | 45 + asterisk/include/asterisk/linkedlists.h | 761 + asterisk/include/asterisk/localtime.h | 30 + asterisk/include/asterisk/lock.h | 1253 + asterisk/include/asterisk/logger.h | 139 + asterisk/include/asterisk/manager.h | 148 + asterisk/include/asterisk/md5.h | 40 + asterisk/include/asterisk/module.h | 292 + asterisk/include/asterisk/monitor.h | 66 + asterisk/include/asterisk/musiconhold.h | 59 + asterisk/include/asterisk/netsock.h | 68 + asterisk/include/asterisk/options.h | 138 + asterisk/include/asterisk/paths.h | 43 + asterisk/include/asterisk/pbx.h | 892 + asterisk/include/asterisk/plc.h | 161 + asterisk/include/asterisk/poll-compat.h | 111 + asterisk/include/asterisk/privacy.h | 46 + asterisk/include/asterisk/res_odbc.h | 103 + asterisk/include/asterisk/rtp.h | 265 + asterisk/include/asterisk/say.h | 158 + asterisk/include/asterisk/sched.h | 198 + asterisk/include/asterisk/sha1.h | 81 + asterisk/include/asterisk/slinfactory.h | 57 + asterisk/include/asterisk/smdi.h | 195 + asterisk/include/asterisk/speech.h | 152 + asterisk/include/asterisk/srv.h | 45 + asterisk/include/asterisk/stringfields.h | 387 + asterisk/include/asterisk/strings.h | 285 + asterisk/include/asterisk/tdd.h | 82 + asterisk/include/asterisk/term.h | 76 + asterisk/include/asterisk/threadstorage.h | 498 + asterisk/include/asterisk/time.h | 144 + asterisk/include/asterisk/tonezone_compat.h | 35 + asterisk/include/asterisk/transcap.h | 42 + asterisk/include/asterisk/translate.h | 273 + asterisk/include/asterisk/udptl.h | 120 + asterisk/include/asterisk/ulaw.h | 43 + asterisk/include/asterisk/unaligned.h | 102 + asterisk/include/asterisk/utils.h | 580 + asterisk/include/jitterbuf.h | 162 + asterisk/include/solaris-compat/compat.h | 46 + asterisk/include/solaris-compat/sys/cdefs.h | 10 + asterisk/include/solaris-compat/sys/queue.h | 540 + asterisk/install-sh | 269 + asterisk/irlp-scripts/noupdate.tar.gz | Bin 0 -> 18783 bytes asterisk/keys/freeworlddialup.pub | 6 + asterisk/keys/iaxtel.pub | 6 + asterisk/main/Makefile | 156 + asterisk/main/abstract_jb.c | 822 + asterisk/main/acl.c | 590 + asterisk/main/aescrypt.c | 317 + asterisk/main/aeskey.c | 469 + asterisk/main/aesopt.h | 1029 + asterisk/main/aestab.c | 232 + asterisk/main/alaw.c | 101 + asterisk/main/app.c | 1447 + asterisk/main/ast_expr2.c | 2859 ++ asterisk/main/ast_expr2.fl | 414 + asterisk/main/ast_expr2.h | 112 + asterisk/main/ast_expr2.y | 1045 + asterisk/main/ast_expr2f.c | 2478 ++ asterisk/main/asterisk.c | 3188 ++ asterisk/main/astmm.c | 498 + asterisk/main/astobj2.c | 728 + asterisk/main/audiohook.c | 659 + asterisk/main/autoservice.c | 314 + asterisk/main/buildinfo.c | 33 + asterisk/main/callerid.c | 1109 + asterisk/main/cdr.c | 1496 + asterisk/main/channel.c | 4748 +++ asterisk/main/chanvars.c | 87 + asterisk/main/cli.c | 2037 + asterisk/main/coef_in.h | 13 + asterisk/main/coef_out.h | 4 + asterisk/main/config.c | 1520 + asterisk/main/cryptostub.c | 95 + asterisk/main/db.c | 592 + asterisk/main/db1-ast/Makefile | 71 + asterisk/main/db1-ast/btree/bt_close.c | 182 + asterisk/main/db1-ast/btree/bt_conv.c | 221 + asterisk/main/db1-ast/btree/bt_debug.c | 329 + asterisk/main/db1-ast/btree/bt_delete.c | 657 + asterisk/main/db1-ast/btree/bt_get.c | 105 + asterisk/main/db1-ast/btree/bt_open.c | 458 + asterisk/main/db1-ast/btree/bt_overflow.c | 228 + asterisk/main/db1-ast/btree/bt_page.c | 100 + asterisk/main/db1-ast/btree/bt_put.c | 321 + asterisk/main/db1-ast/btree/bt_search.c | 213 + asterisk/main/db1-ast/btree/bt_seq.c | 460 + asterisk/main/db1-ast/btree/bt_split.c | 829 + asterisk/main/db1-ast/btree/bt_utils.c | 260 + asterisk/main/db1-ast/btree/btree.h | 391 + asterisk/main/db1-ast/btree/extern.h | 70 + asterisk/main/db1-ast/db/db.c | 103 + asterisk/main/db1-ast/hash/README | 72 + asterisk/main/db1-ast/hash/extern.h | 65 + asterisk/main/db1-ast/hash/hash.c | 999 + asterisk/main/db1-ast/hash/hash.h | 293 + asterisk/main/db1-ast/hash/hash_bigkey.c | 668 + asterisk/main/db1-ast/hash/hash_buf.c | 355 + asterisk/main/db1-ast/hash/hash_func.c | 225 + asterisk/main/db1-ast/hash/hash_log2.c | 56 + asterisk/main/db1-ast/hash/hash_page.c | 944 + asterisk/main/db1-ast/hash/hsearch.c | 107 + asterisk/main/db1-ast/hash/ndbm.c | 235 + asterisk/main/db1-ast/hash/page.h | 92 + asterisk/main/db1-ast/hash/search.h | 51 + asterisk/main/db1-ast/include/circ-queue.h | 131 + asterisk/main/db1-ast/include/compat.h | 49 + asterisk/main/db1-ast/include/db.h | 250 + asterisk/main/db1-ast/include/mpool.h | 115 + asterisk/main/db1-ast/include/ndbm.h | 79 + asterisk/main/db1-ast/libdb.map | 11 + asterisk/main/db1-ast/mpool/README | 7 + asterisk/main/db1-ast/mpool/mpool.c | 498 + asterisk/main/db1-ast/recno/extern.h | 54 + asterisk/main/db1-ast/recno/rec_close.c | 183 + asterisk/main/db1-ast/recno/rec_delete.c | 197 + asterisk/main/db1-ast/recno/rec_get.c | 311 + asterisk/main/db1-ast/recno/rec_open.c | 241 + asterisk/main/db1-ast/recno/rec_put.c | 280 + asterisk/main/db1-ast/recno/rec_search.c | 126 + asterisk/main/db1-ast/recno/rec_seq.c | 131 + asterisk/main/db1-ast/recno/rec_utils.c | 122 + asterisk/main/db1-ast/recno/recno.h | 39 + asterisk/main/devicestate.c | 369 + asterisk/main/dial.c | 894 + asterisk/main/dns.c | 292 + asterisk/main/dnsmgr.c | 424 + asterisk/main/dsp.c | 1819 + asterisk/main/ecdisa.h | 15 + asterisk/main/editline/CHANGES | 42 + asterisk/main/editline/INSTALL | 64 + asterisk/main/editline/Makefile.in | 234 + asterisk/main/editline/PLATFORMS | 13 + asterisk/main/editline/README | 11 + asterisk/main/editline/TEST/test.c | 268 + asterisk/main/editline/chared.c | 695 + asterisk/main/editline/chared.h | 159 + asterisk/main/editline/common.c | 951 + asterisk/main/editline/config.guess | 1449 + asterisk/main/editline/config.h.in | 21 + asterisk/main/editline/config.sub | 1412 + asterisk/main/editline/configure | 2459 ++ asterisk/main/editline/configure.in | 278 + asterisk/main/editline/editline.3 | 646 + asterisk/main/editline/editrc.5 | 491 + asterisk/main/editline/el.c | 509 + asterisk/main/editline/el.h | 145 + asterisk/main/editline/emacs.c | 488 + asterisk/main/editline/hist.c | 197 + asterisk/main/editline/hist.h | 80 + asterisk/main/editline/histedit.h | 197 + asterisk/main/editline/history.c | 875 + asterisk/main/editline/install-sh | 250 + asterisk/main/editline/key.c | 687 + asterisk/main/editline/key.h | 79 + asterisk/main/editline/makelist.in | 254 + asterisk/main/editline/map.c | 1418 + asterisk/main/editline/map.h | 79 + asterisk/main/editline/np/fgetln.c | 88 + asterisk/main/editline/np/strlcat.c | 75 + asterisk/main/editline/np/strlcpy.c | 75 + asterisk/main/editline/np/unvis.c | 322 + asterisk/main/editline/np/vis.c | 348 + asterisk/main/editline/np/vis.h | 96 + asterisk/main/editline/parse.c | 259 + asterisk/main/editline/parse.h | 52 + asterisk/main/editline/prompt.c | 174 + asterisk/main/editline/prompt.h | 62 + asterisk/main/editline/read.c | 555 + asterisk/main/editline/read.h | 55 + asterisk/main/editline/readline.c | 1664 + asterisk/main/editline/readline/readline.h | 118 + asterisk/main/editline/refresh.c | 1104 + asterisk/main/editline/refresh.h | 63 + asterisk/main/editline/search.c | 649 + asterisk/main/editline/search.h | 70 + asterisk/main/editline/sig.c | 198 + asterisk/main/editline/sig.h | 72 + asterisk/main/editline/sys.h | 133 + asterisk/main/editline/term.c | 1587 + asterisk/main/editline/term.h | 124 + asterisk/main/editline/tokenizer.c | 397 + asterisk/main/editline/tokenizer.h | 54 + asterisk/main/editline/tty.c | 1182 + asterisk/main/editline/tty.h | 484 + asterisk/main/editline/vi.c | 941 + asterisk/main/enum.c | 671 + asterisk/main/file.c | 1344 + asterisk/main/fixedjitterbuf.c | 351 + asterisk/main/fixedjitterbuf.h | 92 + asterisk/main/frame.c | 1600 + asterisk/main/fskmodem.c | 309 + asterisk/main/global_datastores.c | 113 + asterisk/main/http.c | 745 + asterisk/main/image.c | 225 + asterisk/main/indications.c | 600 + asterisk/main/io.c | 371 + asterisk/main/jitterbuf.c | 839 + asterisk/main/loader.c | 1001 + asterisk/main/logger.c | 939 + asterisk/main/manager.c | 3183 ++ asterisk/main/md5.c | 267 + asterisk/main/netsock.c | 217 + asterisk/main/pbx.c | 6394 +++ asterisk/main/plc.c | 251 + asterisk/main/poll.c | 306 + asterisk/main/privacy.c | 119 + asterisk/main/rtp.c | 3828 ++ asterisk/main/say.c | 7285 ++++ asterisk/main/sched.c | 404 + asterisk/main/sha1.c | 385 + asterisk/main/slinfactory.c | 177 + asterisk/main/srv.c | 246 + asterisk/main/stdtime/Makefile | 29 + asterisk/main/stdtime/localtime.c | 1651 + asterisk/main/stdtime/private.h | 358 + asterisk/main/stdtime/test.c | 21 + asterisk/main/stdtime/tzfile.h | 184 + asterisk/main/strcompat.c | 472 + asterisk/main/tdd.c | 328 + asterisk/main/term.c | 303 + asterisk/main/threadstorage.c | 229 + asterisk/main/translate.c | 985 + asterisk/main/udptl.c | 1249 + asterisk/main/ulaw.c | 106 + asterisk/main/utils.c | 1425 + asterisk/makeopts.in | 193 + asterisk/menuselect/Makefile | 78 + asterisk/menuselect/README | 159 + asterisk/menuselect/acinclude.m4 | 177 + asterisk/menuselect/aclocal.m4 | 14 + asterisk/menuselect/autoconfig.h.in | 96 + asterisk/menuselect/bootstrap.sh | 41 + asterisk/menuselect/config.guess | 1495 + asterisk/menuselect/config.sub | 1609 + asterisk/menuselect/configure | 5283 +++ asterisk/menuselect/configure.ac | 74 + asterisk/menuselect/example_menuselect-tree | 500 + asterisk/menuselect/install-sh | 323 + asterisk/menuselect/linkedlists.h | 370 + asterisk/menuselect/make_version | 56 + asterisk/menuselect/makeopts.in | 18 + asterisk/menuselect/menuselect.c | 1282 + asterisk/menuselect/menuselect.h | 150 + asterisk/menuselect/menuselect_curses.c | 867 + asterisk/menuselect/menuselect_gtk.c | 354 + asterisk/menuselect/menuselect_stub.c | 39 + asterisk/menuselect/missing | 360 + asterisk/menuselect/mxml/ANNOUNCEMENT | 5 + asterisk/menuselect/mxml/CHANGES | 213 + asterisk/menuselect/mxml/COPYING | 482 + asterisk/menuselect/mxml/Makefile.in | 353 + asterisk/menuselect/mxml/README | 204 + asterisk/menuselect/mxml/config.h.in | 69 + asterisk/menuselect/mxml/configure | 4620 +++ asterisk/menuselect/mxml/install-sh | 251 + asterisk/menuselect/mxml/mxml-attr.c | 181 + asterisk/menuselect/mxml/mxml-entity.c | 472 + asterisk/menuselect/mxml/mxml-file.c | 2843 ++ asterisk/menuselect/mxml/mxml-index.c | 649 + asterisk/menuselect/mxml/mxml-node.c | 664 + asterisk/menuselect/mxml/mxml-private.c | 128 + asterisk/menuselect/mxml/mxml-search.c | 199 + asterisk/menuselect/mxml/mxml-set.c | 257 + asterisk/menuselect/mxml/mxml-string.c | 377 + asterisk/menuselect/mxml/mxml.h | 254 + asterisk/menuselect/mxml/mxml.list.in | 114 + asterisk/menuselect/mxml/mxml.pc | 10 + asterisk/menuselect/mxml/mxml.pc.in | 10 + asterisk/menuselect/strcompat.c | 344 + asterisk/missing | 198 + asterisk/mkinstalldirs | 40 + asterisk/pbx/Makefile | 61 + .../ael/ael-test/ael-ntest10/extensions.ael | 131 + .../ael/ael-test/ael-ntest12/extensions.ael | 13 + .../ael/ael-test/ael-ntest22/extensions.ael | 7 + asterisk/pbx/ael/ael-test/ael-ntest22/qq.ael | 6 + .../pbx/ael/ael-test/ael-ntest22/t1/a.ael | 4 + .../pbx/ael/ael-test/ael-ntest22/t1/b.ael | 6 + .../pbx/ael/ael-test/ael-ntest22/t1/c.ael | 13 + .../pbx/ael/ael-test/ael-ntest22/t2/d.ael | 4 + .../pbx/ael/ael-test/ael-ntest22/t2/e.ael | 6 + .../pbx/ael/ael-test/ael-ntest22/t2/f.ael | 9 + .../pbx/ael/ael-test/ael-ntest22/t3/g.ael | 4 + .../pbx/ael/ael-test/ael-ntest22/t3/h.ael | 6 + .../pbx/ael/ael-test/ael-ntest22/t3/i.ael | 4 + .../pbx/ael/ael-test/ael-ntest22/t3/j.ael | 6 + .../ael/ael-test/ael-ntest23/extensions.ael | 7 + asterisk/pbx/ael/ael-test/ael-ntest23/qq.ael | 6 + .../pbx/ael/ael-test/ael-ntest23/t1/a.ael | 4 + .../pbx/ael/ael-test/ael-ntest23/t1/b.ael | 6 + .../pbx/ael/ael-test/ael-ntest23/t1/c.ael | 13 + .../pbx/ael/ael-test/ael-ntest23/t2/d.ael | 4 + .../pbx/ael/ael-test/ael-ntest23/t2/e.ael | 6 + .../pbx/ael/ael-test/ael-ntest23/t2/f.ael | 9 + .../pbx/ael/ael-test/ael-ntest23/t3/g.ael | 4 + .../pbx/ael/ael-test/ael-ntest23/t3/h.ael | 6 + .../pbx/ael/ael-test/ael-ntest23/t3/i.ael | 4 + .../pbx/ael/ael-test/ael-ntest23/t3/j.ael | 6 + .../ael/ael-test/ael-ntest9/extensions.ael | 12 + .../pbx/ael/ael-test/ael-test1/extensions.ael | 163 + .../ael/ael-test/ael-test11/extensions.ael | 56 + .../ael/ael-test/ael-test14/extensions.ael | 20 + .../ael/ael-test/ael-test15/extensions.ael | 17 + .../ael/ael-test/ael-test16/extensions.ael | 4 + .../ael/ael-test/ael-test18/extensions.ael | 40 + .../ael/ael-test/ael-test19/extensions.ael | 377 + .../pbx/ael/ael-test/ael-test2/apptest.ael2 | 146 + .../pbx/ael/ael-test/ael-test2/extensions.ael | 8 + .../ael/ael-test/ael-test20/extensions.ael | 8 + .../pbx/ael/ael-test/ael-test3/extensions.ael | 3183 ++ .../pbx/ael/ael-test/ael-test3/include1.ael2 | 3 + .../pbx/ael/ael-test/ael-test3/include2.ael2 | 4 + .../pbx/ael/ael-test/ael-test3/include3.ael2 | 2 + .../pbx/ael/ael-test/ael-test3/include4.ael2 | 2 + .../pbx/ael/ael-test/ael-test3/include5.ael2 | 1 + .../ael-test3/telemarket_torture.ael2 | 812 + .../pbx/ael/ael-test/ael-test4/apptest.ael2 | 146 + .../pbx/ael/ael-test/ael-test4/extensions.ael | 8 + .../pbx/ael/ael-test/ael-test5/extensions.ael | 833 + .../pbx/ael/ael-test/ael-test6/extensions.ael | 833 + .../pbx/ael/ael-test/ael-test7/extensions.ael | 460 + .../pbx/ael/ael-test/ael-test8/extensions.ael | 27 + .../ael/ael-test/ael-vtest13/extensions.ael | 3183 ++ .../ael/ael-test/ael-vtest13/include1.ael2 | 3 + .../ael/ael-test/ael-vtest13/include2.ael2 | 4 + .../ael/ael-test/ael-vtest13/include3.ael2 | 2 + .../ael/ael-test/ael-vtest13/include4.ael2 | 2 + .../ael/ael-test/ael-vtest13/include5.ael2 | 1 + .../ael-vtest13/telemarket_torture.ael2 | 812 + .../ael/ael-test/ael-vtest17/extensions.ael | 116 + .../ael/ael-test/ael-vtest21/extensions.ael | 14 + .../ael/ael-test/ael-vtest25/extensions.ael | 8 + asterisk/pbx/ael/ael-test/ref.ael-ntest10 | 163 + asterisk/pbx/ael/ael-test/ref.ael-ntest12 | 31 + asterisk/pbx/ael/ael-test/ref.ael-ntest22 | 55 + asterisk/pbx/ael/ael-test/ref.ael-ntest23 | 25 + asterisk/pbx/ael/ael-test/ref.ael-ntest9 | 24 + asterisk/pbx/ael/ael-test/ref.ael-test1 | 15 + asterisk/pbx/ael/ael-test/ref.ael-test11 | 14 + asterisk/pbx/ael/ael-test/ref.ael-test14 | 12 + asterisk/pbx/ael/ael-test/ref.ael-test15 | 12 + asterisk/pbx/ael/ael-test/ref.ael-test16 | 13 + asterisk/pbx/ael/ael-test/ref.ael-test18 | 12 + asterisk/pbx/ael/ael-test/ref.ael-test19 | 14 + asterisk/pbx/ael/ael-test/ref.ael-test2 | 21 + asterisk/pbx/ael/ael-test/ref.ael-test20 | 12 + asterisk/pbx/ael/ael-test/ref.ael-test3 | 18 + asterisk/pbx/ael/ael-test/ref.ael-test4 | 21 + asterisk/pbx/ael/ael-test/ref.ael-test5 | 12 + asterisk/pbx/ael/ael-test/ref.ael-test6 | 16 + asterisk/pbx/ael/ael-test/ref.ael-test7 | 14 + asterisk/pbx/ael/ael-test/ref.ael-test8 | 12 + asterisk/pbx/ael/ael-test/ref.ael-vtest13 | 3027 ++ asterisk/pbx/ael/ael-test/ref.ael-vtest17 | 71 + asterisk/pbx/ael/ael-test/ref.ael-vtest21 | 9 + asterisk/pbx/ael/ael-test/ref.ael-vtest25 | 7 + asterisk/pbx/ael/ael-test/runtests | 56 + asterisk/pbx/ael/ael-test/setref | 7 + asterisk/pbx/ael/ael.flex | 863 + asterisk/pbx/ael/ael.tab.c | 3376 ++ asterisk/pbx/ael/ael.tab.h | 152 + asterisk/pbx/ael/ael.y | 826 + asterisk/pbx/ael/ael_lex.c | 3369 ++ asterisk/pbx/dundi-parser.c | 831 + asterisk/pbx/dundi-parser.h | 88 + asterisk/pbx/pbx_ael.c | 4958 +++ asterisk/pbx/pbx_config.c | 2555 ++ asterisk/pbx/pbx_dundi.c | 4636 +++ asterisk/pbx/pbx_gtkconsole.c | 515 + asterisk/pbx/pbx_loopback.c | 191 + asterisk/pbx/pbx_realtime.c | 258 + asterisk/pbx/pbx_spool.c | 521 + asterisk/res/Makefile | 39 + asterisk/res/res_adsi.c | 1132 + asterisk/res/res_agi.c | 2201 + asterisk/res/res_clioriginate.c | 175 + asterisk/res/res_config_odbc.c | 564 + asterisk/res/res_config_pgsql.c | 842 + asterisk/res/res_convert.c | 224 + asterisk/res/res_crypto.c | 628 + asterisk/res/res_features.c | 2710 ++ asterisk/res/res_indications.c | 406 + asterisk/res/res_jabber.c | 2521 ++ asterisk/res/res_monitor.c | 714 + asterisk/res/res_musiconhold.c | 1355 + asterisk/res/res_odbc.c | 741 + asterisk/res/res_smdi.c | 1384 + asterisk/res/res_snmp.c | 115 + asterisk/res/res_speech.c | 404 + asterisk/res/snmp/agent.c | 823 + asterisk/res/snmp/agent.h | 40 + asterisk/sample.call | 84 + asterisk/sounds/Makefile | 158 + asterisk/sounds/sounds.xml | 68 + asterisk/static-http/ajamdemo.html | 219 + asterisk/static-http/astman.css | 34 + asterisk/static-http/astman.js | 262 + asterisk/static-http/prototype.js | 1781 + asterisk/utils/Makefile | 132 + asterisk/utils/ael_main.c | 564 + asterisk/utils/astman.1 | 102 + asterisk/utils/astman.c | 748 + asterisk/utils/check_expr.c | 355 + asterisk/utils/expr2.testinput | 92 + asterisk/utils/frame.c | 1034 + asterisk/utils/frame.h | 300 + asterisk/utils/muted.c | 704 + asterisk/utils/smsq.c | 765 + asterisk/utils/stereorize.c | 159 + asterisk/utils/streamplayer.c | 121 + configs/amd.conf | 18 + configs/asterisk.conf | 2 + configs/dundi.conf | 7 + configs/examples/echolink/echolink.conf | 16 + configs/examples/irlp/irlp.conf | 9 + configs/examples/twousbnodes/extensions.conf | 13 + configs/examples/twousbnodes/iax.conf | 51 + configs/examples/twousbnodes/readme | 6 + configs/examples/twousbnodes/rpt.conf | 190 + configs/examples/twousbnodes/usbradio.conf | 134 + configs/extensions.conf | 58 + configs/features.conf | 11 + configs/iax.conf | 62 + configs/indications.conf | 146 + configs/logger.conf | 17 + configs/manager.conf | 49 + configs/pciradio/modules.conf | 14 + configs/pciradio/rpt.conf | 229 + configs/pciradio/zapata.conf | 19 + configs/pciradio/zaptel.conf | 81 + configs/sip.conf | 15 + configs/tdm400p/modules.conf | 14 + configs/tdm400p/rpt.conf | 243 + configs/tdm400p/zapata.conf | 44 + configs/tdm400p/zaptel.conf | 13 + configs/usbradio/modules.conf | 13 + configs/usbradio/rpt.conf | 153 + configs/usbradio/usbradio.conf | 70 + configs/usbradio/zapata.conf | 13 + configs/usbradio/zaptel.conf | 8 + configs/wct1xxp/modules.conf | 14 + configs/wct1xxp/rpt.conf | 244 + configs/wct1xxp/zapata.conf | 55 + configs/wct1xxp/zaptel.conf | 14 + getsrc | 19 + id.gsm | Bin 0 -> 6204 bytes irlp-overlay/noupdate.tar.gz | Bin 0 -> 18783 bytes libpri/.version | 1 + libpri/ChangeLog | 347 + libpri/LICENSE | 341 + libpri/Makefile | 168 + libpri/README | 45 + libpri/TODO | 8 + libpri/build_tools/make_version | 72 + libpri/build_tools/make_version_c | 24 + libpri/compat.h | 10 + libpri/compiler.h | 36 + libpri/copy_string.c | 47 + libpri/libpri.h | 695 + libpri/pri.c | 913 + libpri/pri_facility.c | 2438 ++ libpri/pri_facility.h | 325 + libpri/pri_internal.h | 284 + libpri/pri_q921.h | 195 + libpri/pri_q931.h | 296 + libpri/pri_timers.h | 97 + libpri/pridump.c | 149 + libpri/prisched.c | 129 + libpri/pritest.c | 410 + libpri/q921.c | 1170 + libpri/q931.c | 4035 ++ libpri/testprilib.c | 288 + sounds/rpt/act-timeout-warning.pcm | 1 + sounds/rpt/alllinksdisconnected.ulaw | 1 + sounds/rpt/alllinksrestored.ulaw | 1 + sounds/rpt/autopatch_on.pcm | 1 + sounds/rpt/callproceeding.gsm | Bin 0 -> 1386 bytes sounds/rpt/callterminated.gsm | Bin 0 -> 1914 bytes sounds/rpt/connected.gsm | Bin 0 -> 1320 bytes sounds/rpt/connecting.ulaw | 1 + sounds/rpt/connection_failed.gsm | Bin 0 -> 1980 bytes sounds/rpt/down.gsm | Bin 0 -> 1155 bytes sounds/rpt/fast.gsm | Bin 0 -> 957 bytes sounds/rpt/frequency.gsm | Bin 0 -> 1683 bytes sounds/rpt/functioncomplete.pcm | 1 + sounds/rpt/goodafternoon.gsm | Bin 0 -> 1617 bytes sounds/rpt/goodevening.gsm | Bin 0 -> 1452 bytes sounds/rpt/goodmorning.gsm | Bin 0 -> 1683 bytes sounds/rpt/hipwr.gsm | Bin 0 -> 1881 bytes sounds/rpt/in-call.pcm | 1 + sounds/rpt/invalid-freq.gsm | Bin 0 -> 3267 bytes sounds/rpt/keyedfor.ulaw | 1 + sounds/rpt/login.pcm | 1 + sounds/rpt/lopwr.gsm | Bin 0 -> 2013 bytes sounds/rpt/medpwr.gsm | Bin 0 -> 2310 bytes sounds/rpt/memory_notfound.gsm | Bin 0 -> 2343 bytes sounds/rpt/minus.gsm | Bin 0 -> 1551 bytes sounds/rpt/monitor.pcm | 1 + sounds/rpt/node.gsm | Bin 0 -> 1056 bytes sounds/rpt/nodenames/2000.ulaw | 1 + sounds/rpt/nodenames/2001.ulaw | 2 + sounds/rpt/nodenames/2003.ulaw | 4 + sounds/rpt/nodenames/2009.ulaw | 1 + sounds/rpt/nodenames/2010.ulaw | 1 + sounds/rpt/nodenames/2011.ulaw | 1 + sounds/rpt/nodenames/2012.ulaw | 1 + sounds/rpt/nodenames/2013.ulaw | 1 + sounds/rpt/nodenames/2014.ulaw | 1 + sounds/rpt/nodenames/2016.ulaw | 1 + sounds/rpt/nodenames/2022.ulaw | 1 + sounds/rpt/nodenames/2025.ulaw | 1 + sounds/rpt/nodenames/2030.ulaw | 1 + sounds/rpt/nodenames/2043.ulaw | 1 + sounds/rpt/nodenames/2059.ulaw | 1 + sounds/rpt/nodenames/2060.ulaw | 1 + sounds/rpt/nodenames/2061.ulaw | 2 + sounds/rpt/nodenames/2063.ulaw | 1 + sounds/rpt/nodenames/2065.ulaw | 1 + sounds/rpt/nodenames/2070.ulaw | 1 + sounds/rpt/off.gsm | Bin 0 -> 1650 bytes sounds/rpt/on.gsm | Bin 0 -> 1485 bytes sounds/rpt/plus.gsm | Bin 0 -> 1386 bytes sounds/rpt/quick.gsm | Bin 0 -> 1518 bytes sounds/rpt/remote_already.gsm | Bin 0 -> 3267 bytes sounds/rpt/remote_busy.pcm | 1 + sounds/rpt/remote_cmd.pcm | 1 + sounds/rpt/remote_disc.gsm | Bin 0 -> 1782 bytes sounds/rpt/remote_go.gsm | Bin 0 -> 2376 bytes sounds/rpt/remote_monitor.pcm | 1 + sounds/rpt/remote_notfound.pcm | 1 + sounds/rpt/remote_tx.pcm | 1 + sounds/rpt/repeat_only.gsm | Bin 0 -> 1782 bytes sounds/rpt/rxpl.gsm | Bin 0 -> 2310 bytes sounds/rpt/seconds.ulaw | 3 + sounds/rpt/simplex.gsm | Bin 0 -> 1749 bytes sounds/rpt/sitenorm.ulaw | 1 + sounds/rpt/slow.gsm | Bin 0 -> 1122 bytes sounds/rpt/stop.gsm | Bin 0 -> 1353 bytes sounds/rpt/thetimeis.gsm | Bin 0 -> 1848 bytes sounds/rpt/timeout-warning.pcm | 1 + sounds/rpt/timeout.pcm | 1 + sounds/rpt/tranceive.pcm | 1 + sounds/rpt/txpl.gsm | Bin 0 -> 3036 bytes sounds/rpt/unauthtx.ulaw | 1 + sounds/rpt/unkeyedfor.ulaw | 1 + sounds/rpt/up.gsm | Bin 0 -> 660 bytes sounds/rpt/version.gsm | Bin 0 -> 2310 bytes zaptel/.version | 1 + zaptel/ChangeLog | 4249 ++ zaptel/LICENSE | 341 + zaptel/LICENSE.LGPL | 504 + zaptel/Makefile | 689 + zaptel/README | 761 + zaptel/README.Astribank | 3 + zaptel/README.b410p | 29 + zaptel/README.fxotune | 23 + zaptel/README.fxsusb | 15 + zaptel/README.hpec | 21 + zaptel/acinclude.m4 | 89 + zaptel/aclocal.m4 | 14 + zaptel/bittest.h | 17 + zaptel/bootstrap.sh | 25 + zaptel/build_tools/builder | 168 + zaptel/build_tools/genmodconf | 107 + zaptel/build_tools/genudevrules | 35 + zaptel/build_tools/make_firmware_object.in | 11 + zaptel/build_tools/make_svn_branch_name | 61 + zaptel/build_tools/make_tree | 8 + zaptel/build_tools/make_version | 56 + zaptel/build_tools/make_version_h | 9 + zaptel/build_tools/menuselect-deps.in | 1 + zaptel/build_tools/test_kernel_git | 101 + zaptel/build_tools/uninstall-modules | 64 + zaptel/build_tools/zaptel_svn_tarball | 90 + zaptel/checkstack | 46 + zaptel/complex.cc | 61 + zaptel/complex.h | 76 + zaptel/config.guess | 1495 + zaptel/config.sub | 1609 + zaptel/configure | 6488 +++ zaptel/configure.ac | 132 + zaptel/doc/fxotune.8 | 206 + zaptel/doc/fxstest.8 | 60 + zaptel/doc/module-parameters.txt | 57 + zaptel/doc/patgen.8 | 42 + zaptel/doc/pattest.8 | 48 + zaptel/doc/torisatool.8 | 29 + zaptel/doc/ztcfg.8 | 61 + zaptel/doc/ztdiag.8 | 52 + zaptel/doc/ztmonitor.8 | 43 + zaptel/doc/ztscan.8 | 94 + zaptel/doc/ztspeed.8 | 26 + zaptel/doc/zttest.8 | 53 + zaptel/doc/zttool.8 | 34 + zaptel/firmware/Makefile | 172 + zaptel/firmware/firmware.xml | 18 + zaptel/fxotune.c | 1119 + zaptel/fxotune.h | 119 + zaptel/fxstest.c | 157 + zaptel/hdlcgen.c | 118 + zaptel/hdlcstress.c | 199 + zaptel/hdlctest.c | 274 + zaptel/hdlcverify.c | 119 + zaptel/ifcfg-hdlc0 | 6 + zaptel/ifup-hdlc | 39 + zaptel/install-sh | 323 + zaptel/install_prereq | 129 + zaptel/kernel/GNUmakefile | 144 + zaptel/kernel/Kbuild | 71 + zaptel/kernel/Makefile | 6 + zaptel/kernel/adt_lec.c | 68 + zaptel/kernel/adt_lec.h | 43 + zaptel/kernel/arith.h | 377 + zaptel/kernel/biquad.h | 73 + zaptel/kernel/datamods/Makefile | 32 + zaptel/kernel/datamods/hdlc_cisco.c | 335 + zaptel/kernel/datamods/hdlc_fr.c | 1273 + zaptel/kernel/datamods/hdlc_generic.c | 355 + zaptel/kernel/datamods/hdlc_ppp.c | 114 + zaptel/kernel/datamods/hdlc_raw.c | 89 + zaptel/kernel/datamods/hdlc_raw_eth.c | 107 + zaptel/kernel/datamods/syncppp.c | 1485 + zaptel/kernel/digits.h | 47 + zaptel/kernel/ecdis.h | 118 + zaptel/kernel/fasthdlc.h | 472 + zaptel/kernel/fir.h | 130 + zaptel/kernel/fxo_modes.h | 588 + zaptel/kernel/hpec/hpec.h | 47 + zaptel/kernel/hpec/hpec_user.h | 40 + zaptel/kernel/hpec/hpec_zaptel.h | 146 + zaptel/kernel/jpah.h | 112 + zaptel/kernel/kb1ec.h | 605 + zaptel/kernel/kb1ec_const.h | 85 + zaptel/kernel/makefw.c | 84 + zaptel/kernel/mg2ec.h | 733 + zaptel/kernel/mg2ec_const.h | 101 + zaptel/kernel/oct612x/Makefile | 38 + zaptel/kernel/oct612x/apilib/bt/octapi_bt0.c | 1217 + .../oct612x/apilib/bt/octapi_bt0_private.h | 93 + .../oct612x/apilib/largmath/octapi_largmath.c | 628 + .../oct612x/apilib/llman/octapi_llman.c | 2787 ++ .../apilib/llman/octapi_llman_private.h | 206 + zaptel/kernel/oct612x/get_discards | 51 + .../oct612x/include/apilib/octapi_bt0.h | 75 + .../oct612x/include/apilib/octapi_largmath.h | 69 + .../oct612x/include/apilib/octapi_llman.h | 142 + zaptel/kernel/oct612x/include/digium_unused.h | 297 + .../oct6100api/oct6100_adpcm_chan_inst.h | 74 + .../oct6100api/oct6100_adpcm_chan_pub.h | 90 + .../oct612x/include/oct6100api/oct6100_api.h | 84 + .../include/oct6100api/oct6100_api_inst.h | 138 + .../include/oct6100api/oct6100_apimi.h | 69 + .../include/oct6100api/oct6100_apiud.h | 312 + .../include/oct6100api/oct6100_channel_inst.h | 389 + .../include/oct6100api/oct6100_channel_pub.h | 547 + .../oct6100api/oct6100_chip_open_inst.h | 517 + .../oct6100api/oct6100_chip_open_pub.h | 241 + .../oct6100api/oct6100_chip_stats_inst.h | 84 + .../oct6100api/oct6100_chip_stats_pub.h | 150 + .../oct6100api/oct6100_conf_bridge_inst.h | 104 + .../oct6100api/oct6100_conf_bridge_pub.h | 169 + .../include/oct6100api/oct6100_debug_inst.h | 124 + .../include/oct6100api/oct6100_debug_pub.h | 76 + .../include/oct6100api/oct6100_defines.h | 670 + .../include/oct6100api/oct6100_errors.h | 838 + .../include/oct6100api/oct6100_events_inst.h | 69 + .../include/oct6100api/oct6100_events_pub.h | 111 + .../oct6100api/oct6100_interrupts_inst.h | 134 + .../oct6100api/oct6100_interrupts_pub.h | 102 + .../include/oct6100api/oct6100_mixer_inst.h | 86 + .../include/oct6100api/oct6100_mixer_pub.h | 77 + .../oct6100api/oct6100_phasing_tsst_inst.h | 68 + .../oct6100api/oct6100_phasing_tsst_pub.h | 78 + .../oct6100api/oct6100_playout_buf_inst.h | 88 + .../oct6100api/oct6100_playout_buf_pub.h | 183 + .../oct6100api/oct6100_remote_debug_inst.h | 73 + .../oct6100api/oct6100_remote_debug_pub.h | 64 + .../include/oct6100api/oct6100_tlv_inst.h | 72 + .../oct6100api/oct6100_tone_detection_inst.h | 46 + .../oct6100api/oct6100_tone_detection_pub.h | 74 + .../oct6100api/oct6100_tsi_cnct_inst.h | 70 + .../include/oct6100api/oct6100_tsi_cnct_pub.h | 76 + .../include/oct6100api/oct6100_tsst_inst.h | 55 + zaptel/kernel/oct612x/include/octdef.h | 116 + zaptel/kernel/oct612x/include/octmac.h | 98 + .../kernel/oct612x/include/octosdependant.h | 167 + .../include/octrpc/oct6100_rpc_protocol.h | 348 + .../oct612x/include/octrpc/rpc_protocol.h | 115 + zaptel/kernel/oct612x/include/octtype.h | 153 + zaptel/kernel/oct612x/include/octtypevx.h | 132 + zaptel/kernel/oct612x/include/octtypewin.h | 100 + zaptel/kernel/oct612x/octasic-helper | 39 + .../oct6100api/oct6100_adpcm_chan_priv.h | 131 + .../oct6100_api/oct6100_adpcm_chan.c | 1237 + .../oct6100api/oct6100_api/oct6100_channel.c | 13931 +++++++ .../oct6100_api/oct6100_chip_open.c | 6905 ++++ .../oct6100_api/oct6100_chip_stats.c | 440 + .../oct6100_api/oct6100_conf_bridge.c | 7687 ++++ .../oct6100api/oct6100_api/oct6100_debug.c | 1278 + .../oct6100api/oct6100_api/oct6100_events.c | 1380 + .../oct6100_api/oct6100_interrupts.c | 2011 + .../oct6100api/oct6100_api/oct6100_memory.c | 831 + .../oct6100_api/oct6100_miscellaneous.c | 640 + .../oct6100api/oct6100_api/oct6100_mixer.c | 1586 + .../oct6100_api/oct6100_phasing_tsst.c | 921 + .../oct6100_api/oct6100_playout_buf.c | 3350 ++ .../oct6100_api/oct6100_remote_debug.c | 1598 + .../oct6100api/oct6100_api/oct6100_tlv.c | 2055 + .../oct6100_api/oct6100_tone_detection.c | 1088 + .../oct6100api/oct6100_api/oct6100_tsi_cnct.c | 1023 + .../oct6100api/oct6100_api/oct6100_tsst.c | 575 + .../oct6100api/oct6100_api/oct6100_user.c | 508 + .../oct6100_apimi/oct6100_mask_interrupts.c | 116 + .../oct6100api/oct6100_channel_priv.h | 529 + .../oct6100api/oct6100_chip_open_priv.h | 264 + .../oct6100api/oct6100_chip_stats_priv.h | 55 + .../oct6100api/oct6100_conf_bridge_priv.h | 318 + .../oct6100api/oct6100_debug_priv.h | 58 + .../oct6100api/oct6100_events_priv.h | 82 + .../oct6100api/oct6100_interrupts_priv.h | 158 + .../oct6100api/oct6100_memory_priv.h | 97 + .../oct6100api/oct6100_miscellaneous_priv.h | 431 + .../oct6100api/oct6100_mixer_priv.h | 150 + .../oct6100api/oct6100_phasing_tsst_priv.h | 114 + .../oct6100api/oct6100_playout_buf_priv.h | 201 + .../oct6100api/oct6100_remote_debug_priv.h | 144 + .../oct6100api/oct6100_tlv_priv.h | 515 + .../oct6100api/oct6100_tone_detection_priv.h | 111 + .../oct6100api/oct6100_tsi_cnct_priv.h | 126 + .../oct6100api/oct6100_tsst_priv.h | 89 + .../octdeviceapi/oct6100api/oct6100_version.h | 39 + zaptel/kernel/oct612x/test.c | 46 + zaptel/kernel/pciradio.c | 1934 + zaptel/kernel/pciradio.rbt | 10531 +++++ zaptel/kernel/proslic.h | 203 + zaptel/kernel/sec-2.h | 451 + zaptel/kernel/sec.h | 310 + zaptel/kernel/tor2-hw.h | 186 + zaptel/kernel/tor2.c | 1521 + zaptel/kernel/torisa.c | 1171 + zaptel/kernel/tormenta2.rbt | 17482 ++++++++ zaptel/kernel/voicebus.c | 1491 + zaptel/kernel/voicebus.h | 53 + zaptel/kernel/wcfxo.c | 1102 + zaptel/kernel/wct1xxp.c | 1438 + zaptel/kernel/wct4xxp/Kbuild | 27 + zaptel/kernel/wct4xxp/Makefile | 36 + zaptel/kernel/wct4xxp/base.c | 3878 ++ zaptel/kernel/wct4xxp/vpm450m.c | 590 + zaptel/kernel/wct4xxp/vpm450m.h | 43 + zaptel/kernel/wct4xxp/wct4xxp-diag.c | 427 + zaptel/kernel/wct4xxp/wct4xxp.h | 113 + zaptel/kernel/wctc4xxp/Kbuild | 20 + zaptel/kernel/wctc4xxp/Makefile | 6 + zaptel/kernel/wctc4xxp/base.c | 3279 ++ zaptel/kernel/wctdm.c | 2558 ++ zaptel/kernel/wctdm.h | 68 + zaptel/kernel/wctdm24xxp/GpakApi.c | 1630 + zaptel/kernel/wctdm24xxp/GpakApi.h | 636 + zaptel/kernel/wctdm24xxp/GpakCust.c | 478 + zaptel/kernel/wctdm24xxp/GpakCust.h | 180 + zaptel/kernel/wctdm24xxp/GpakHpi.h | 78 + zaptel/kernel/wctdm24xxp/Kbuild | 25 + zaptel/kernel/wctdm24xxp/Makefile | 27 + zaptel/kernel/wctdm24xxp/base.c | 4165 ++ zaptel/kernel/wctdm24xxp/gpakErrs.h | 155 + zaptel/kernel/wctdm24xxp/gpakenum.h | 191 + zaptel/kernel/wctdm24xxp/voicebus.c | 1 + zaptel/kernel/wctdm24xxp/wctdm24xxp.h | 286 + zaptel/kernel/wcte11xp.c | 1644 + zaptel/kernel/wcte12xp/GpakApi.c | 1616 + zaptel/kernel/wcte12xp/GpakApi.h | 636 + zaptel/kernel/wcte12xp/GpakErrs.h | 155 + zaptel/kernel/wcte12xp/GpakHpi.h | 79 + zaptel/kernel/wcte12xp/Kbuild | 25 + zaptel/kernel/wcte12xp/Makefile | 25 + zaptel/kernel/wcte12xp/base.c | 1762 + zaptel/kernel/wcte12xp/gpakenum.h | 190 + zaptel/kernel/wcte12xp/voicebus.c | 1 + zaptel/kernel/wcte12xp/vpmadt032.c | 1311 + zaptel/kernel/wcte12xp/vpmadt032.h | 145 + zaptel/kernel/wcte12xp/wcte12xp.h | 163 + zaptel/kernel/wcusb.c | 1489 + zaptel/kernel/wcusb.h | 154 + zaptel/kernel/xpp/.version | 1 + zaptel/kernel/xpp/Changelog_xpp | 340 + zaptel/kernel/xpp/Kbuild | 58 + zaptel/kernel/xpp/Makefile | 7 + zaptel/kernel/xpp/README.Astribank | 1374 + zaptel/kernel/xpp/card_bri.c | 1506 + zaptel/kernel/xpp/card_bri.h | 31 + zaptel/kernel/xpp/card_fxo.c | 1354 + zaptel/kernel/xpp/card_fxo.h | 42 + zaptel/kernel/xpp/card_fxs.c | 1506 + zaptel/kernel/xpp/card_fxs.h | 42 + zaptel/kernel/xpp/card_global.c | 851 + zaptel/kernel/xpp/card_global.h | 113 + zaptel/kernel/xpp/card_pri.c | 1845 + zaptel/kernel/xpp/card_pri.h | 32 + zaptel/kernel/xpp/firmwares/FPGA_1141.hex | 650 + zaptel/kernel/xpp/firmwares/FPGA_1151.hex | 697 + zaptel/kernel/xpp/firmwares/FPGA_FXS.hex | 644 + zaptel/kernel/xpp/firmwares/LICENSE.firmware | 37 + zaptel/kernel/xpp/firmwares/README | 19 + zaptel/kernel/xpp/firmwares/USB_FW.hex | 223 + zaptel/kernel/xpp/init_card_1_30 | 535 + zaptel/kernel/xpp/init_card_2_30 | 426 + zaptel/kernel/xpp/init_card_3_30 | 432 + zaptel/kernel/xpp/init_card_4_30 | 387 + zaptel/kernel/xpp/param_doc | 40 + zaptel/kernel/xpp/parport_debug.c | 113 + zaptel/kernel/xpp/parport_debug.h | 31 + zaptel/kernel/xpp/utils/Makefile | 144 + zaptel/kernel/xpp/utils/astribank_hook | 57 + .../kernel/xpp/utils/example_default_zaptel | 31 + zaptel/kernel/xpp/utils/fpga_load.8 | 86 + zaptel/kernel/xpp/utils/fpga_load.c | 1011 + zaptel/kernel/xpp/utils/genzaptelconf | 1198 + zaptel/kernel/xpp/utils/genzaptelconf.8 | 326 + zaptel/kernel/xpp/utils/hexfile.c | 567 + zaptel/kernel/xpp/utils/hexfile.h | 123 + zaptel/kernel/xpp/utils/lszaptel | 110 + zaptel/kernel/xpp/utils/print_modes.c | 33 + zaptel/kernel/xpp/utils/test_parse.c | 35 + zaptel/kernel/xpp/utils/xpp.rules | 14 + zaptel/kernel/xpp/utils/xpp_blink | 168 + zaptel/kernel/xpp/utils/xpp_fxloader | 292 + zaptel/kernel/xpp/utils/xpp_fxloader.usermap | 10 + zaptel/kernel/xpp/utils/xpp_modprobe | 10 + zaptel/kernel/xpp/utils/xpp_sync | 226 + zaptel/kernel/xpp/utils/xpp_timing | 6 + zaptel/kernel/xpp/utils/zapconf | 603 + zaptel/kernel/xpp/utils/zaptel-helper | 401 + zaptel/kernel/xpp/utils/zaptel_drivers | 9 + zaptel/kernel/xpp/utils/zaptel_hardware | 164 + zaptel/kernel/xpp/utils/zconf/Zaptel.pm | 68 + zaptel/kernel/xpp/utils/zconf/Zaptel/Chans.pm | 255 + .../xpp/utils/zconf/Zaptel/Config/Defaults.pm | 56 + .../kernel/xpp/utils/zconf/Zaptel/Hardware.pm | 168 + .../xpp/utils/zconf/Zaptel/Hardware/PCI.pm | 211 + .../xpp/utils/zconf/Zaptel/Hardware/USB.pm | 116 + zaptel/kernel/xpp/utils/zconf/Zaptel/Span.pm | 300 + zaptel/kernel/xpp/utils/zconf/Zaptel/Utils.pm | 52 + zaptel/kernel/xpp/utils/zconf/Zaptel/Xpp.pm | 199 + .../kernel/xpp/utils/zconf/Zaptel/Xpp/Line.pm | 95 + .../kernel/xpp/utils/zconf/Zaptel/Xpp/Xbus.pm | 118 + .../kernel/xpp/utils/zconf/Zaptel/Xpp/Xpd.pm | 123 + zaptel/kernel/xpp/utils/zt_registration | 125 + zaptel/kernel/xpp/xbus-core.c | 1708 + zaptel/kernel/xpp/xbus-core.h | 305 + zaptel/kernel/xpp/xbus-pcm.c | 1312 + zaptel/kernel/xpp/xbus-pcm.h | 134 + zaptel/kernel/xpp/xbus-sysfs.c | 427 + zaptel/kernel/xpp/xdefs.h | 138 + zaptel/kernel/xpp/xframe_queue.c | 278 + zaptel/kernel/xpp/xframe_queue.h | 34 + zaptel/kernel/xpp/xpd.h | 231 + zaptel/kernel/xpp/xpp_log.h | 52 + zaptel/kernel/xpp/xpp_usb.c | 1107 + zaptel/kernel/xpp/xpp_zap.c | 1091 + zaptel/kernel/xpp/xpp_zap.h | 53 + zaptel/kernel/xpp/xproto.c | 478 + zaptel/kernel/xpp/xproto.h | 292 + zaptel/kernel/xpp/zap_debug.c | 91 + zaptel/kernel/xpp/zap_debug.h | 201 + zaptel/kernel/zaptel-base.c | 7821 ++++ zaptel/kernel/zaptel.h | 2133 + zaptel/kernel/zconfig.h | 210 + zaptel/kernel/ztd-eth.c | 450 + zaptel/kernel/ztd-loc.c | 280 + zaptel/kernel/ztdummy.c | 428 + zaptel/kernel/ztdummy.h | 150 + zaptel/kernel/ztdynamic.c | 875 + zaptel/kernel/zttranscode.c | 464 + zaptel/live_zap | 200 + zaptel/makeopts.in | 47 + zaptel/menuselect/Makefile | 78 + zaptel/menuselect/README | 159 + zaptel/menuselect/acinclude.m4 | 177 + zaptel/menuselect/aclocal.m4 | 14 + zaptel/menuselect/autoconfig.h.in | 96 + zaptel/menuselect/bootstrap.sh | 41 + zaptel/menuselect/config.guess | 1495 + zaptel/menuselect/config.sub | 1609 + zaptel/menuselect/configure | 5283 +++ zaptel/menuselect/configure.ac | 74 + zaptel/menuselect/example_menuselect-tree | 500 + zaptel/menuselect/install-sh | 323 + zaptel/menuselect/linkedlists.h | 370 + zaptel/menuselect/make_version | 56 + zaptel/menuselect/makeopts.in | 18 + zaptel/menuselect/menuselect.c | 1282 + zaptel/menuselect/menuselect.h | 150 + zaptel/menuselect/menuselect_curses.c | 867 + zaptel/menuselect/menuselect_gtk.c | 351 + zaptel/menuselect/menuselect_stub.c | 39 + zaptel/menuselect/missing | 360 + zaptel/menuselect/mxml/ANNOUNCEMENT | 5 + zaptel/menuselect/mxml/CHANGES | 213 + zaptel/menuselect/mxml/COPYING | 482 + zaptel/menuselect/mxml/Makefile.in | 353 + zaptel/menuselect/mxml/README | 204 + zaptel/menuselect/mxml/config.h.in | 69 + zaptel/menuselect/mxml/configure | 4620 +++ zaptel/menuselect/mxml/install-sh | 251 + zaptel/menuselect/mxml/mxml-attr.c | 181 + zaptel/menuselect/mxml/mxml-entity.c | 472 + zaptel/menuselect/mxml/mxml-file.c | 2843 ++ zaptel/menuselect/mxml/mxml-index.c | 649 + zaptel/menuselect/mxml/mxml-node.c | 664 + zaptel/menuselect/mxml/mxml-private.c | 128 + zaptel/menuselect/mxml/mxml-search.c | 199 + zaptel/menuselect/mxml/mxml-set.c | 257 + zaptel/menuselect/mxml/mxml-string.c | 377 + zaptel/menuselect/mxml/mxml.h | 254 + zaptel/menuselect/mxml/mxml.list.in | 114 + zaptel/menuselect/mxml/mxml.pc | 10 + zaptel/menuselect/mxml/mxml.pc.in | 10 + zaptel/menuselect/strcompat.c | 344 + zaptel/mkfilter.h | 62 + zaptel/mknotch.cc | 145 + zaptel/orig.ee | Bin 0 -> 172 bytes zaptel/patgen.c | 111 + zaptel/patlooptest.c | 137 + zaptel/pattest.c | 117 + zaptel/ppp/Makefile | 29 + zaptel/ppp/zaptel.c | 293 + zaptel/sethdlc-new.c | 702 + zaptel/sethdlc.c | 400 + zaptel/timertest.c | 67 + zaptel/tonezone.c | 518 + zaptel/tonezone.h | 92 + zaptel/tor2.ee | Bin 0 -> 172 bytes zaptel/torisatool.c | 65 + zaptel/tormenta2.ucf | 194 + zaptel/tormenta2.vhd | 657 + zaptel/usbfxstest.c | 99 + zaptel/zaptel.conf.sample | 288 + zaptel/zaptel.init | 260 + zaptel/zaptel.sysconfig | 69 + zaptel/zaptel.xml | 68 + zaptel/zonedata.c | 938 + zaptel/ztcfg-dude.c | 863 + zaptel/ztcfg.c | 1487 + zaptel/ztcfg.h | 27 + zaptel/ztdiag.c | 44 + zaptel/ztmonitor.c | 650 + zaptel/ztscan.c | 171 + zaptel/ztspeed.c | 49 + zaptel/zttest.c | 148 + zaptel/zttool.c | 586 + 1519 files changed, 763975 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100755 allstar/rc.updatenodelist create mode 100644 asterisk/BUGS create mode 100644 asterisk/CHANGES create mode 100644 asterisk/COPYING create mode 100644 asterisk/CREDITS create mode 100644 asterisk/LICENSE create mode 100644 asterisk/Makefile create mode 100644 asterisk/Makefile.moddir_rules create mode 100644 asterisk/Makefile.rules create mode 100644 asterisk/README create mode 100644 asterisk/UPGRADE-1.2.txt create mode 100644 asterisk/UPGRADE.txt create mode 100644 asterisk/Zaptel-to-DAHDI.txt create mode 100644 asterisk/acinclude.m4 create mode 100644 asterisk/agi/DialAnMp3.agi create mode 100644 asterisk/agi/Makefile create mode 100644 asterisk/agi/agi-test.agi create mode 100644 asterisk/agi/eagi-sphinx-test.c create mode 100644 asterisk/agi/eagi-test.c create mode 100644 asterisk/agi/fastagi-test create mode 100755 asterisk/agi/jukebox.agi create mode 100644 asterisk/agi/numeralize create mode 100644 asterisk/apps/Makefile create mode 100644 asterisk/apps/app_adsiprog.c create mode 100644 asterisk/apps/app_alarmreceiver.c create mode 100644 asterisk/apps/app_amd.c create mode 100644 asterisk/apps/app_authenticate.c create mode 100644 asterisk/apps/app_cdr.c create mode 100644 asterisk/apps/app_chanisavail.c create mode 100644 asterisk/apps/app_channelredirect.c create mode 100644 asterisk/apps/app_chanspy.c create mode 100644 asterisk/apps/app_controlplayback.c create mode 100644 asterisk/apps/app_dahdibarge.c create mode 100644 asterisk/apps/app_dahdiras.c create mode 100644 asterisk/apps/app_dahdiscan.c create mode 100644 asterisk/apps/app_db.c create mode 100644 asterisk/apps/app_dial.c create mode 100644 asterisk/apps/app_dictate.c create mode 100644 asterisk/apps/app_directed_pickup.c create mode 100644 asterisk/apps/app_directory.c create mode 100644 asterisk/apps/app_disa.c create mode 100644 asterisk/apps/app_dumpchan.c create mode 100644 asterisk/apps/app_echo.c create mode 100644 asterisk/apps/app_exec.c create mode 100644 asterisk/apps/app_externalivr.c create mode 100644 asterisk/apps/app_festival.c create mode 100644 asterisk/apps/app_flash.c create mode 100644 asterisk/apps/app_followme.c create mode 100644 asterisk/apps/app_forkcdr.c create mode 100644 asterisk/apps/app_getcpeid.c create mode 100644 asterisk/apps/app_hasnewvoicemail.c create mode 100644 asterisk/apps/app_ices.c create mode 100644 asterisk/apps/app_image.c create mode 100644 asterisk/apps/app_ivrdemo.c create mode 100644 asterisk/apps/app_lookupblacklist.c create mode 100644 asterisk/apps/app_lookupcidname.c create mode 100644 asterisk/apps/app_macro.c create mode 100644 asterisk/apps/app_meetme.c create mode 100644 asterisk/apps/app_milliwatt.c create mode 100644 asterisk/apps/app_mixmonitor.c create mode 100644 asterisk/apps/app_morsecode.c create mode 100644 asterisk/apps/app_mp3.c create mode 100644 asterisk/apps/app_nbscat.c create mode 100644 asterisk/apps/app_osplookup.c create mode 100644 asterisk/apps/app_page.c create mode 100644 asterisk/apps/app_parkandannounce.c create mode 100644 asterisk/apps/app_playback.c create mode 100644 asterisk/apps/app_privacy.c create mode 100644 asterisk/apps/app_queue.c create mode 100644 asterisk/apps/app_random.c create mode 100644 asterisk/apps/app_read.c create mode 100644 asterisk/apps/app_readfile.c create mode 100644 asterisk/apps/app_realtime.c create mode 100644 asterisk/apps/app_record.c create mode 100644 asterisk/apps/app_rpt.c create mode 100644 asterisk/apps/app_sayunixtime.c create mode 100644 asterisk/apps/app_senddtmf.c create mode 100644 asterisk/apps/app_sendtext.c create mode 100644 asterisk/apps/app_setcallerid.c create mode 100644 asterisk/apps/app_setcdruserfield.c create mode 100644 asterisk/apps/app_settransfercapability.c create mode 100644 asterisk/apps/app_skel.c create mode 100644 asterisk/apps/app_sms.c create mode 100644 asterisk/apps/app_softhangup.c create mode 100644 asterisk/apps/app_speech_utils.c create mode 100644 asterisk/apps/app_stack.c create mode 100644 asterisk/apps/app_system.c create mode 100644 asterisk/apps/app_talkdetect.c create mode 100644 asterisk/apps/app_test.c create mode 100644 asterisk/apps/app_transfer.c create mode 100644 asterisk/apps/app_url.c create mode 100644 asterisk/apps/app_userevent.c create mode 100644 asterisk/apps/app_verbose.c create mode 100644 asterisk/apps/app_voicemail.c create mode 100644 asterisk/apps/app_waitforring.c create mode 100644 asterisk/apps/app_waitforsilence.c create mode 100644 asterisk/apps/app_while.c create mode 100644 asterisk/apps/app_zapateller.c create mode 100644 asterisk/apps/enter.h create mode 100644 asterisk/apps/leave.h create mode 100644 asterisk/apps/rpt_flow.pdf create mode 100755 asterisk/bootstrap.sh create mode 100644 asterisk/build_tools/cflags-devmode.xml create mode 100644 asterisk/build_tools/cflags.xml create mode 100644 asterisk/build_tools/embed_modules.xml create mode 100644 asterisk/build_tools/get_makeopts create mode 100644 asterisk/build_tools/get_moduleinfo create mode 100755 asterisk/build_tools/make_build_h create mode 100755 asterisk/build_tools/make_buildopts_h create mode 100755 asterisk/build_tools/make_defaults_h create mode 100755 asterisk/build_tools/make_sample_voicemail create mode 100755 asterisk/build_tools/make_version create mode 100755 asterisk/build_tools/make_version_h create mode 100644 asterisk/build_tools/menuselect-deps.in create mode 100755 asterisk/build_tools/mkpkgconfig create mode 100755 asterisk/build_tools/prep_tarball create mode 100755 asterisk/build_tools/strip_nonapi create mode 100644 asterisk/cdr/Makefile create mode 100644 asterisk/cdr/cdr_csv.c create mode 100644 asterisk/cdr/cdr_custom.c create mode 100644 asterisk/cdr/cdr_manager.c create mode 100644 asterisk/cdr/cdr_odbc.c create mode 100644 asterisk/cdr/cdr_pgsql.c create mode 100644 asterisk/cdr/cdr_radius.c create mode 100644 asterisk/cdr/cdr_sqlite.c create mode 100644 asterisk/cdr/cdr_tds.c create mode 100644 asterisk/channels/DialTone.h create mode 100644 asterisk/channels/Makefile create mode 100644 asterisk/channels/answer.h create mode 100644 asterisk/channels/chan_agent.c create mode 100644 asterisk/channels/chan_alsa.c create mode 100644 asterisk/channels/chan_dahdi.c create mode 100644 asterisk/channels/chan_echolink.c create mode 100644 asterisk/channels/chan_features.c create mode 100644 asterisk/channels/chan_gtalk.c create mode 100644 asterisk/channels/chan_h323.c create mode 100644 asterisk/channels/chan_iax2.c create mode 100644 asterisk/channels/chan_irlp.c create mode 100644 asterisk/channels/chan_local.c create mode 100644 asterisk/channels/chan_mgcp.c create mode 100644 asterisk/channels/chan_misdn.c create mode 100644 asterisk/channels/chan_nbs.c create mode 100644 asterisk/channels/chan_oss.c create mode 100644 asterisk/channels/chan_phone.c create mode 100644 asterisk/channels/chan_rtpdir.c create mode 100644 asterisk/channels/chan_sip.c create mode 100644 asterisk/channels/chan_skinny.c create mode 100644 asterisk/channels/chan_usbradio.c create mode 100644 asterisk/channels/chan_vpb.cc create mode 100644 asterisk/channels/gentone-ulaw.c create mode 100644 asterisk/channels/gentone.c create mode 100644 asterisk/channels/h323/ChangeLog create mode 100644 asterisk/channels/h323/INSTALL.openh323 create mode 100644 asterisk/channels/h323/Makefile.in create mode 100644 asterisk/channels/h323/README create mode 100644 asterisk/channels/h323/TODO create mode 100644 asterisk/channels/h323/ast_h323.cxx create mode 100644 asterisk/channels/h323/ast_h323.h create mode 100644 asterisk/channels/h323/caps_h323.cxx create mode 100644 asterisk/channels/h323/caps_h323.h create mode 100644 asterisk/channels/h323/chan_h323.h create mode 100644 asterisk/channels/h323/cisco-h225.asn create mode 100644 asterisk/channels/h323/cisco-h225.cxx create mode 100644 asterisk/channels/h323/cisco-h225.h create mode 100644 asterisk/channels/h323/compat_h323.cxx create mode 100644 asterisk/channels/h323/compat_h323.h create mode 100644 asterisk/channels/h323/noexport.map create mode 100644 asterisk/channels/iax2-parser.c create mode 100644 asterisk/channels/iax2-parser.h create mode 100644 asterisk/channels/iax2-provision.c create mode 100644 asterisk/channels/iax2-provision.h create mode 100644 asterisk/channels/iax2.h create mode 100644 asterisk/channels/misdn/Makefile create mode 100644 asterisk/channels/misdn/chan_misdn_config.h create mode 100644 asterisk/channels/misdn/ie.c create mode 100644 asterisk/channels/misdn/isdn_lib.c create mode 100644 asterisk/channels/misdn/isdn_lib.h create mode 100644 asterisk/channels/misdn/isdn_lib_intern.h create mode 100644 asterisk/channels/misdn/isdn_msg_parser.c create mode 100644 asterisk/channels/misdn/portinfo.c create mode 100644 asterisk/channels/misdn_config.c create mode 100644 asterisk/channels/ring10.h create mode 100755 asterisk/channels/xpmr/sinetabx.h create mode 100755 asterisk/channels/xpmr/xpmr.c create mode 100755 asterisk/channels/xpmr/xpmr.h create mode 100755 asterisk/channels/xpmr/xpmr_coef.h create mode 100644 asterisk/codecs/Makefile create mode 100644 asterisk/codecs/adpcm_slin_ex.h create mode 100644 asterisk/codecs/codec_a_mu.c create mode 100644 asterisk/codecs/codec_adpcm.c create mode 100644 asterisk/codecs/codec_alaw.c create mode 100644 asterisk/codecs/codec_dahdi.c create mode 100644 asterisk/codecs/codec_g726.c create mode 100644 asterisk/codecs/codec_gsm.c create mode 100644 asterisk/codecs/codec_ilbc.c create mode 100644 asterisk/codecs/codec_lpc10.c create mode 100644 asterisk/codecs/codec_speex.c create mode 100644 asterisk/codecs/codec_ulaw.c create mode 100644 asterisk/codecs/g726_slin_ex.h create mode 100644 asterisk/codecs/gsm/COPYRIGHT create mode 100644 asterisk/codecs/gsm/Makefile create mode 100644 asterisk/codecs/gsm/README create mode 100644 asterisk/codecs/gsm/inc/config.h create mode 100644 asterisk/codecs/gsm/inc/gsm.h create mode 100644 asterisk/codecs/gsm/inc/private.h create mode 100644 asterisk/codecs/gsm/inc/proto.h create mode 100644 asterisk/codecs/gsm/inc/unproto.h create mode 100644 asterisk/codecs/gsm/libgsm.vcproj create mode 100644 asterisk/codecs/gsm/src/add.c create mode 100644 asterisk/codecs/gsm/src/code.c create mode 100644 asterisk/codecs/gsm/src/debug.c create mode 100644 asterisk/codecs/gsm/src/decode.c create mode 100644 asterisk/codecs/gsm/src/gsm_create.c create mode 100644 asterisk/codecs/gsm/src/gsm_decode.c create mode 100644 asterisk/codecs/gsm/src/gsm_destroy.c create mode 100644 asterisk/codecs/gsm/src/gsm_encode.c create mode 100644 asterisk/codecs/gsm/src/gsm_explode.c create mode 100644 asterisk/codecs/gsm/src/gsm_implode.c create mode 100644 asterisk/codecs/gsm/src/gsm_option.c create mode 100644 asterisk/codecs/gsm/src/gsm_print.c create mode 100644 asterisk/codecs/gsm/src/k6opt.h create mode 100644 asterisk/codecs/gsm/src/k6opt.s create mode 100644 asterisk/codecs/gsm/src/long_term.c create mode 100644 asterisk/codecs/gsm/src/lpc.c create mode 100644 asterisk/codecs/gsm/src/preprocess.c create mode 100644 asterisk/codecs/gsm/src/rpe.c create mode 100644 asterisk/codecs/gsm/src/short_term.c create mode 100644 asterisk/codecs/gsm/src/table.c create mode 100644 asterisk/codecs/gsm_slin_ex.h create mode 100644 asterisk/codecs/ilbc/Makefile create mode 100644 asterisk/codecs/ilbc_slin_ex.h create mode 100644 asterisk/codecs/log2comp.h create mode 100644 asterisk/codecs/lpc10/Makefile create mode 100644 asterisk/codecs/lpc10/README create mode 100644 asterisk/codecs/lpc10/analys.c create mode 100644 asterisk/codecs/lpc10/bsynz.c create mode 100644 asterisk/codecs/lpc10/chanwr.c create mode 100644 asterisk/codecs/lpc10/dcbias.c create mode 100644 asterisk/codecs/lpc10/decode.c create mode 100644 asterisk/codecs/lpc10/deemp.c create mode 100644 asterisk/codecs/lpc10/difmag.c create mode 100644 asterisk/codecs/lpc10/dyptrk.c create mode 100644 asterisk/codecs/lpc10/encode.c create mode 100644 asterisk/codecs/lpc10/energy.c create mode 100644 asterisk/codecs/lpc10/f2c.h create mode 100644 asterisk/codecs/lpc10/f2clib.c create mode 100644 asterisk/codecs/lpc10/ham84.c create mode 100644 asterisk/codecs/lpc10/hp100.c create mode 100644 asterisk/codecs/lpc10/invert.c create mode 100644 asterisk/codecs/lpc10/irc2pc.c create mode 100644 asterisk/codecs/lpc10/ivfilt.c create mode 100644 asterisk/codecs/lpc10/liblpc10.vcproj create mode 100644 asterisk/codecs/lpc10/lpc10.h create mode 100644 asterisk/codecs/lpc10/lpcdec.c create mode 100644 asterisk/codecs/lpc10/lpcenc.c create mode 100644 asterisk/codecs/lpc10/lpcini.c create mode 100644 asterisk/codecs/lpc10/lpfilt.c create mode 100644 asterisk/codecs/lpc10/median.c create mode 100644 asterisk/codecs/lpc10/mload.c create mode 100644 asterisk/codecs/lpc10/onset.c create mode 100644 asterisk/codecs/lpc10/pitsyn.c create mode 100644 asterisk/codecs/lpc10/placea.c create mode 100644 asterisk/codecs/lpc10/placev.c create mode 100644 asterisk/codecs/lpc10/preemp.c create mode 100644 asterisk/codecs/lpc10/prepro.c create mode 100644 asterisk/codecs/lpc10/random.c create mode 100644 asterisk/codecs/lpc10/rcchk.c create mode 100644 asterisk/codecs/lpc10/synths.c create mode 100644 asterisk/codecs/lpc10/tbdm.c create mode 100644 asterisk/codecs/lpc10/voicin.c create mode 100644 asterisk/codecs/lpc10/vparms.c create mode 100644 asterisk/codecs/lpc10_slin_ex.h create mode 100644 asterisk/codecs/slin_adpcm_ex.h create mode 100644 asterisk/codecs/slin_g726_ex.h create mode 100644 asterisk/codecs/slin_gsm_ex.h create mode 100644 asterisk/codecs/slin_ilbc_ex.h create mode 100644 asterisk/codecs/slin_lpc10_ex.h create mode 100644 asterisk/codecs/slin_speex_ex.h create mode 100644 asterisk/codecs/slin_ulaw_ex.h create mode 100644 asterisk/codecs/speex_slin_ex.h create mode 100644 asterisk/codecs/ulaw_slin_ex.h create mode 100755 asterisk/config.guess create mode 100755 asterisk/config.sub create mode 100644 asterisk/configs/adsi.conf.sample create mode 100644 asterisk/configs/adtranvofr.conf.sample create mode 100644 asterisk/configs/agents.conf.sample create mode 100644 asterisk/configs/alarmreceiver.conf.sample create mode 100644 asterisk/configs/alsa.conf.sample create mode 100644 asterisk/configs/amd.conf.sample create mode 100644 asterisk/configs/asterisk.adsi create mode 100644 asterisk/configs/cdr.conf.sample create mode 100644 asterisk/configs/cdr_custom.conf.sample create mode 100644 asterisk/configs/cdr_manager.conf.sample create mode 100644 asterisk/configs/cdr_odbc.conf.sample create mode 100644 asterisk/configs/cdr_pgsql.conf.sample create mode 100644 asterisk/configs/cdr_tds.conf.sample create mode 100644 asterisk/configs/chan_dahdi.conf.sample create mode 100644 asterisk/configs/codecs.conf.sample create mode 100644 asterisk/configs/dnsmgr.conf.sample create mode 100644 asterisk/configs/dundi.conf.sample create mode 100644 asterisk/configs/enum.conf.sample create mode 100644 asterisk/configs/extconfig.conf.sample create mode 100644 asterisk/configs/extensions.ael.sample create mode 100644 asterisk/configs/extensions.conf.sample create mode 100644 asterisk/configs/features.conf.sample create mode 100644 asterisk/configs/festival.conf.sample create mode 100644 asterisk/configs/followme.conf.sample create mode 100644 asterisk/configs/func_odbc.conf.sample create mode 100644 asterisk/configs/gtalk.conf.sample create mode 100644 asterisk/configs/h323.conf.sample create mode 100644 asterisk/configs/http.conf.sample create mode 100644 asterisk/configs/iax.conf.sample create mode 100644 asterisk/configs/iaxprov.conf.sample create mode 100644 asterisk/configs/indications.conf.sample create mode 100644 asterisk/configs/jabber.conf.sample create mode 100644 asterisk/configs/logger.conf.sample create mode 100644 asterisk/configs/manager.conf.sample create mode 100644 asterisk/configs/meetme.conf.sample create mode 100644 asterisk/configs/mgcp.conf.sample create mode 100644 asterisk/configs/misdn.conf.sample create mode 100644 asterisk/configs/modules.conf.sample create mode 100644 asterisk/configs/musiconhold.conf.sample create mode 100644 asterisk/configs/muted.conf.sample create mode 100644 asterisk/configs/osp.conf.sample create mode 100644 asterisk/configs/oss.conf.sample create mode 100644 asterisk/configs/phone.conf.sample create mode 100644 asterisk/configs/privacy.conf.sample create mode 100644 asterisk/configs/queues.conf.sample create mode 100644 asterisk/configs/res_odbc.conf.sample create mode 100644 asterisk/configs/res_pgsql.conf.sample create mode 100644 asterisk/configs/res_snmp.conf.sample create mode 100644 asterisk/configs/rpt.conf.sample create mode 100644 asterisk/configs/rtp.conf.sample create mode 100644 asterisk/configs/say.conf.sample create mode 100644 asterisk/configs/sip.conf.sample create mode 100644 asterisk/configs/sip_notify.conf.sample create mode 100644 asterisk/configs/skinny.conf.sample create mode 100644 asterisk/configs/sla.conf.sample create mode 100644 asterisk/configs/smdi.conf.sample create mode 100644 asterisk/configs/telcordia-1.adsi create mode 100644 asterisk/configs/udptl.conf.sample create mode 100644 asterisk/configs/usbradio.conf.sample create mode 100644 asterisk/configs/users.conf.sample create mode 100644 asterisk/configs/voicemail.conf.sample create mode 100644 asterisk/configs/vpb.conf.sample create mode 100755 asterisk/configure create mode 100644 asterisk/configure.ac create mode 100644 asterisk/contrib/README.festival create mode 100644 asterisk/contrib/asterisk-doxygen-header create mode 100644 asterisk/contrib/asterisk-ices.xml create mode 100644 asterisk/contrib/asterisk-ng-doxygen create mode 100644 asterisk/contrib/dictionary.digium create mode 100644 asterisk/contrib/festival-1.4.1-diff create mode 100644 asterisk/contrib/festival-1.4.2.diff create mode 100644 asterisk/contrib/festival-1.4.3.diff create mode 100644 asterisk/contrib/festival-1.95.diff create mode 100644 asterisk/contrib/firmware/iax/iaxy.bin create mode 100644 asterisk/contrib/i18n.testsuite.conf create mode 100755 asterisk/contrib/init.d/rc.debian.asterisk create mode 100755 asterisk/contrib/init.d/rc.gentoo.asterisk create mode 100755 asterisk/contrib/init.d/rc.mandrake.asterisk create mode 100755 asterisk/contrib/init.d/rc.mandrake.zaptel create mode 100755 asterisk/contrib/init.d/rc.redhat.asterisk create mode 100755 asterisk/contrib/init.d/rc.slackware.asterisk create mode 100755 asterisk/contrib/init.d/rc.suse.asterisk create mode 100644 asterisk/contrib/scripts/README.messages-expire create mode 100644 asterisk/contrib/scripts/agents.php create mode 100644 asterisk/contrib/scripts/ast_grab_core create mode 100644 asterisk/contrib/scripts/astgenkey create mode 100644 asterisk/contrib/scripts/astgenkey.8 create mode 100644 asterisk/contrib/scripts/autosupport create mode 100644 asterisk/contrib/scripts/autosupport.8 create mode 100755 asterisk/contrib/scripts/get_ilbc_source.sh create mode 100644 asterisk/contrib/scripts/iax-friends.sql create mode 100644 asterisk/contrib/scripts/loadtest.tcl create mode 100644 asterisk/contrib/scripts/lookup.agi create mode 100644 asterisk/contrib/scripts/managerproxy.pl create mode 100644 asterisk/contrib/scripts/meetme.sql create mode 100644 asterisk/contrib/scripts/messages-expire.pl create mode 100644 asterisk/contrib/scripts/qview.pl create mode 100644 asterisk/contrib/scripts/realtime_pgsql.sql create mode 100644 asterisk/contrib/scripts/retrieve_extensions_from_mysql.pl create mode 100644 asterisk/contrib/scripts/retrieve_extensions_from_sql.pl create mode 100644 asterisk/contrib/scripts/retrieve_sip_conf_from_mysql.pl create mode 100644 asterisk/contrib/scripts/safe_asterisk create mode 100644 asterisk/contrib/scripts/safe_asterisk.8 create mode 100644 asterisk/contrib/scripts/safe_asterisk_restart create mode 100644 asterisk/contrib/scripts/sip-friends.sql create mode 100644 asterisk/contrib/scripts/vmail.cgi create mode 100644 asterisk/contrib/scripts/vmdb.sql create mode 100644 asterisk/contrib/thirdparty/spexxilbcfix_xlite.reg create mode 100644 asterisk/contrib/thirdparty/spexxilbcfix_xpro.reg create mode 100644 asterisk/contrib/utils/README.rawplayer create mode 100644 asterisk/contrib/utils/rawplayer.c create mode 100644 asterisk/contrib/utils/zones2indications.c create mode 100644 asterisk/contrib/valgrind-RedHat-8.0.supp create mode 100644 asterisk/dev-1.0/apps/app_rpt.c create mode 100644 asterisk/dev-1.0/channels/chan_irlp.c create mode 100644 asterisk/dev-1.0/channels/chan_locloop.c create mode 100644 asterisk/dev-1.0/channels/irlp-scripts/noupdate.tar.gz create mode 100644 asterisk/doc/00README.1st create mode 100644 asterisk/doc/CODING-GUIDELINES create mode 100644 asterisk/doc/PEERING create mode 100644 asterisk/doc/ael.txt create mode 100644 asterisk/doc/ajam.txt create mode 100644 asterisk/doc/app-sms.txt create mode 100644 asterisk/doc/apps.txt create mode 100644 asterisk/doc/asterisk-conf.txt create mode 100644 asterisk/doc/asterisk-mib.txt create mode 100644 asterisk/doc/asterisk.8 create mode 100644 asterisk/doc/asterisk.sgml create mode 100644 asterisk/doc/backtrace.txt create mode 100644 asterisk/doc/billing.txt create mode 100644 asterisk/doc/callfiles.txt create mode 100644 asterisk/doc/callingpres.txt create mode 100644 asterisk/doc/cdrdriver.txt create mode 100644 asterisk/doc/chaniax.txt create mode 100644 asterisk/doc/channels.txt create mode 100644 asterisk/doc/channelvariables.txt create mode 100644 asterisk/doc/cli.txt create mode 100644 asterisk/doc/cliprompt.txt create mode 100644 asterisk/doc/configuration.txt create mode 100644 asterisk/doc/cygwin.txt create mode 100644 asterisk/doc/datastores.txt create mode 100644 asterisk/doc/digium-mib.txt create mode 100644 asterisk/doc/dundi.txt create mode 100644 asterisk/doc/enum.txt create mode 100644 asterisk/doc/extconfig.txt create mode 100644 asterisk/doc/extensions.txt create mode 100644 asterisk/doc/externalivr.txt create mode 100644 asterisk/doc/freetds.txt create mode 100644 asterisk/doc/h323.txt create mode 100644 asterisk/doc/hardware.txt create mode 100644 asterisk/doc/iax.txt create mode 100644 asterisk/doc/ices.txt create mode 100644 asterisk/doc/imapstorage.txt create mode 100644 asterisk/doc/ip-tos.txt create mode 100644 asterisk/doc/jabber.txt create mode 100644 asterisk/doc/jingle.txt create mode 100644 asterisk/doc/jitterbuffer.txt create mode 100644 asterisk/doc/lang/hebrew.ods create mode 100644 asterisk/doc/linkedlists.txt create mode 100644 asterisk/doc/localchannel.txt create mode 100644 asterisk/doc/macroexclusive.txt create mode 100644 asterisk/doc/manager.txt create mode 100644 asterisk/doc/math.txt create mode 100644 asterisk/doc/misdn.txt create mode 100644 asterisk/doc/model.txt create mode 100644 asterisk/doc/modules.txt create mode 100644 asterisk/doc/mp3.txt create mode 100644 asterisk/doc/musiconhold-fpm.txt create mode 100644 asterisk/doc/mysql.txt create mode 100644 asterisk/doc/odbcstorage.txt create mode 100644 asterisk/doc/osp.txt create mode 100644 asterisk/doc/privacy.txt create mode 100644 asterisk/doc/queuelog.txt create mode 100644 asterisk/doc/queues-with-callback-members.txt create mode 100644 asterisk/doc/radius.txt create mode 100644 asterisk/doc/realtime.txt create mode 100644 asterisk/doc/rtp-packetization.txt create mode 100644 asterisk/doc/security.txt create mode 100644 asterisk/doc/sip-retransmit.txt create mode 100644 asterisk/doc/sla.pdf create mode 100644 asterisk/doc/sla.tex create mode 100644 asterisk/doc/smdi.txt create mode 100644 asterisk/doc/sms.txt create mode 100644 asterisk/doc/snmp.txt create mode 100644 asterisk/doc/speechrec.txt create mode 100644 asterisk/doc/valgrind.txt create mode 100644 asterisk/doc/video.txt create mode 100644 asterisk/doc/voicemail_odbc_postgresql.txt create mode 100644 asterisk/formats/Makefile create mode 100644 asterisk/formats/format_g723.c create mode 100644 asterisk/formats/format_g726.c create mode 100644 asterisk/formats/format_g729.c create mode 100644 asterisk/formats/format_gsm.c create mode 100644 asterisk/formats/format_h263.c create mode 100644 asterisk/formats/format_h264.c create mode 100644 asterisk/formats/format_ilbc.c create mode 100644 asterisk/formats/format_jpeg.c create mode 100644 asterisk/formats/format_ogg_vorbis.c create mode 100644 asterisk/formats/format_pcm.c create mode 100644 asterisk/formats/format_sln.c create mode 100644 asterisk/formats/format_vox.c create mode 100644 asterisk/formats/format_wav.c create mode 100644 asterisk/formats/format_wav_gsm.c create mode 100644 asterisk/formats/msgsm.h create mode 100644 asterisk/funcs/Makefile create mode 100644 asterisk/funcs/func_base64.c create mode 100644 asterisk/funcs/func_callerid.c create mode 100644 asterisk/funcs/func_cdr.c create mode 100644 asterisk/funcs/func_channel.c create mode 100644 asterisk/funcs/func_curl.c create mode 100644 asterisk/funcs/func_cut.c create mode 100644 asterisk/funcs/func_db.c create mode 100644 asterisk/funcs/func_enum.c create mode 100644 asterisk/funcs/func_env.c create mode 100644 asterisk/funcs/func_global.c create mode 100644 asterisk/funcs/func_groupcount.c create mode 100644 asterisk/funcs/func_language.c create mode 100644 asterisk/funcs/func_logic.c create mode 100644 asterisk/funcs/func_math.c create mode 100644 asterisk/funcs/func_md5.c create mode 100644 asterisk/funcs/func_moh.c create mode 100644 asterisk/funcs/func_odbc.c create mode 100644 asterisk/funcs/func_rand.c create mode 100644 asterisk/funcs/func_realtime.c create mode 100644 asterisk/funcs/func_sha1.c create mode 100644 asterisk/funcs/func_strings.c create mode 100644 asterisk/funcs/func_timeout.c create mode 100644 asterisk/funcs/func_uri.c create mode 100644 asterisk/images/animlogo.gif create mode 100644 asterisk/images/asterisk-intro.jpg create mode 100644 asterisk/images/play.gif create mode 100644 asterisk/include/asterisk.h create mode 100644 asterisk/include/asterisk/abstract_jb.h create mode 100644 asterisk/include/asterisk/acl.h create mode 100644 asterisk/include/asterisk/adsi.h create mode 100644 asterisk/include/asterisk/ael_structs.h create mode 100644 asterisk/include/asterisk/aes.h create mode 100644 asterisk/include/asterisk/agi.h create mode 100644 asterisk/include/asterisk/alaw.h create mode 100644 asterisk/include/asterisk/app.h create mode 100644 asterisk/include/asterisk/ast_expr.h create mode 100644 asterisk/include/asterisk/astdb.h create mode 100644 asterisk/include/asterisk/astmm.h create mode 100644 asterisk/include/asterisk/astobj.h create mode 100644 asterisk/include/asterisk/astobj2.h create mode 100644 asterisk/include/asterisk/astosp.h create mode 100644 asterisk/include/asterisk/audiohook.h create mode 100644 asterisk/include/asterisk/autoconfig.h.in create mode 100644 asterisk/include/asterisk/callerid.h create mode 100644 asterisk/include/asterisk/causes.h create mode 100644 asterisk/include/asterisk/cdr.h create mode 100644 asterisk/include/asterisk/channel.h create mode 100644 asterisk/include/asterisk/chanvars.h create mode 100644 asterisk/include/asterisk/cli.h create mode 100644 asterisk/include/asterisk/compat.h create mode 100644 asterisk/include/asterisk/compiler.h create mode 100644 asterisk/include/asterisk/config.h create mode 100644 asterisk/include/asterisk/crypto.h create mode 100644 asterisk/include/asterisk/dahdi_compat.h create mode 100644 asterisk/include/asterisk/devicestate.h create mode 100644 asterisk/include/asterisk/dial.h create mode 100644 asterisk/include/asterisk/dns.h create mode 100644 asterisk/include/asterisk/dnsmgr.h create mode 100644 asterisk/include/asterisk/doxyref.h create mode 100644 asterisk/include/asterisk/dsp.h create mode 100644 asterisk/include/asterisk/dundi.h create mode 100644 asterisk/include/asterisk/endian.h create mode 100644 asterisk/include/asterisk/enum.h create mode 100644 asterisk/include/asterisk/features.h create mode 100644 asterisk/include/asterisk/file.h create mode 100644 asterisk/include/asterisk/frame.h create mode 100644 asterisk/include/asterisk/fskmodem.h create mode 100644 asterisk/include/asterisk/global_datastores.h create mode 100644 asterisk/include/asterisk/http.h create mode 100644 asterisk/include/asterisk/image.h create mode 100644 asterisk/include/asterisk/indications.h create mode 100644 asterisk/include/asterisk/inline_api.h create mode 100644 asterisk/include/asterisk/io.h create mode 100644 asterisk/include/asterisk/jabber.h create mode 100644 asterisk/include/asterisk/jingle.h create mode 100644 asterisk/include/asterisk/linkedlists.h create mode 100644 asterisk/include/asterisk/localtime.h create mode 100644 asterisk/include/asterisk/lock.h create mode 100644 asterisk/include/asterisk/logger.h create mode 100644 asterisk/include/asterisk/manager.h create mode 100644 asterisk/include/asterisk/md5.h create mode 100644 asterisk/include/asterisk/module.h create mode 100644 asterisk/include/asterisk/monitor.h create mode 100644 asterisk/include/asterisk/musiconhold.h create mode 100644 asterisk/include/asterisk/netsock.h create mode 100644 asterisk/include/asterisk/options.h create mode 100644 asterisk/include/asterisk/paths.h create mode 100644 asterisk/include/asterisk/pbx.h create mode 100644 asterisk/include/asterisk/plc.h create mode 100644 asterisk/include/asterisk/poll-compat.h create mode 100644 asterisk/include/asterisk/privacy.h create mode 100644 asterisk/include/asterisk/res_odbc.h create mode 100644 asterisk/include/asterisk/rtp.h create mode 100644 asterisk/include/asterisk/say.h create mode 100644 asterisk/include/asterisk/sched.h create mode 100644 asterisk/include/asterisk/sha1.h create mode 100644 asterisk/include/asterisk/slinfactory.h create mode 100644 asterisk/include/asterisk/smdi.h create mode 100644 asterisk/include/asterisk/speech.h create mode 100644 asterisk/include/asterisk/srv.h create mode 100644 asterisk/include/asterisk/stringfields.h create mode 100644 asterisk/include/asterisk/strings.h create mode 100644 asterisk/include/asterisk/tdd.h create mode 100644 asterisk/include/asterisk/term.h create mode 100644 asterisk/include/asterisk/threadstorage.h create mode 100644 asterisk/include/asterisk/time.h create mode 100644 asterisk/include/asterisk/tonezone_compat.h create mode 100644 asterisk/include/asterisk/transcap.h create mode 100644 asterisk/include/asterisk/translate.h create mode 100644 asterisk/include/asterisk/udptl.h create mode 100644 asterisk/include/asterisk/ulaw.h create mode 100644 asterisk/include/asterisk/unaligned.h create mode 100644 asterisk/include/asterisk/utils.h create mode 100644 asterisk/include/jitterbuf.h create mode 100644 asterisk/include/solaris-compat/compat.h create mode 100644 asterisk/include/solaris-compat/sys/cdefs.h create mode 100644 asterisk/include/solaris-compat/sys/queue.h create mode 100755 asterisk/install-sh create mode 100644 asterisk/irlp-scripts/noupdate.tar.gz create mode 100644 asterisk/keys/freeworlddialup.pub create mode 100644 asterisk/keys/iaxtel.pub create mode 100644 asterisk/main/Makefile create mode 100644 asterisk/main/abstract_jb.c create mode 100644 asterisk/main/acl.c create mode 100644 asterisk/main/aescrypt.c create mode 100644 asterisk/main/aeskey.c create mode 100644 asterisk/main/aesopt.h create mode 100644 asterisk/main/aestab.c create mode 100644 asterisk/main/alaw.c create mode 100644 asterisk/main/app.c create mode 100644 asterisk/main/ast_expr2.c create mode 100644 asterisk/main/ast_expr2.fl create mode 100644 asterisk/main/ast_expr2.h create mode 100644 asterisk/main/ast_expr2.y create mode 100644 asterisk/main/ast_expr2f.c create mode 100644 asterisk/main/asterisk.c create mode 100644 asterisk/main/astmm.c create mode 100644 asterisk/main/astobj2.c create mode 100644 asterisk/main/audiohook.c create mode 100644 asterisk/main/autoservice.c create mode 100644 asterisk/main/buildinfo.c create mode 100644 asterisk/main/callerid.c create mode 100644 asterisk/main/cdr.c create mode 100644 asterisk/main/channel.c create mode 100644 asterisk/main/chanvars.c create mode 100644 asterisk/main/cli.c create mode 100644 asterisk/main/coef_in.h create mode 100644 asterisk/main/coef_out.h create mode 100644 asterisk/main/config.c create mode 100644 asterisk/main/cryptostub.c create mode 100644 asterisk/main/db.c create mode 100644 asterisk/main/db1-ast/Makefile create mode 100644 asterisk/main/db1-ast/btree/bt_close.c create mode 100644 asterisk/main/db1-ast/btree/bt_conv.c create mode 100644 asterisk/main/db1-ast/btree/bt_debug.c create mode 100644 asterisk/main/db1-ast/btree/bt_delete.c create mode 100644 asterisk/main/db1-ast/btree/bt_get.c create mode 100644 asterisk/main/db1-ast/btree/bt_open.c create mode 100644 asterisk/main/db1-ast/btree/bt_overflow.c create mode 100644 asterisk/main/db1-ast/btree/bt_page.c create mode 100644 asterisk/main/db1-ast/btree/bt_put.c create mode 100644 asterisk/main/db1-ast/btree/bt_search.c create mode 100644 asterisk/main/db1-ast/btree/bt_seq.c create mode 100644 asterisk/main/db1-ast/btree/bt_split.c create mode 100644 asterisk/main/db1-ast/btree/bt_utils.c create mode 100644 asterisk/main/db1-ast/btree/btree.h create mode 100644 asterisk/main/db1-ast/btree/extern.h create mode 100644 asterisk/main/db1-ast/db/db.c create mode 100644 asterisk/main/db1-ast/hash/README create mode 100644 asterisk/main/db1-ast/hash/extern.h create mode 100644 asterisk/main/db1-ast/hash/hash.c create mode 100644 asterisk/main/db1-ast/hash/hash.h create mode 100644 asterisk/main/db1-ast/hash/hash_bigkey.c create mode 100644 asterisk/main/db1-ast/hash/hash_buf.c create mode 100644 asterisk/main/db1-ast/hash/hash_func.c create mode 100644 asterisk/main/db1-ast/hash/hash_log2.c create mode 100644 asterisk/main/db1-ast/hash/hash_page.c create mode 100644 asterisk/main/db1-ast/hash/hsearch.c create mode 100644 asterisk/main/db1-ast/hash/ndbm.c create mode 100644 asterisk/main/db1-ast/hash/page.h create mode 100644 asterisk/main/db1-ast/hash/search.h create mode 100644 asterisk/main/db1-ast/include/circ-queue.h create mode 100644 asterisk/main/db1-ast/include/compat.h create mode 100644 asterisk/main/db1-ast/include/db.h create mode 100644 asterisk/main/db1-ast/include/mpool.h create mode 100644 asterisk/main/db1-ast/include/ndbm.h create mode 100644 asterisk/main/db1-ast/libdb.map create mode 100644 asterisk/main/db1-ast/mpool/README create mode 100644 asterisk/main/db1-ast/mpool/mpool.c create mode 100644 asterisk/main/db1-ast/recno/extern.h create mode 100644 asterisk/main/db1-ast/recno/rec_close.c create mode 100644 asterisk/main/db1-ast/recno/rec_delete.c create mode 100644 asterisk/main/db1-ast/recno/rec_get.c create mode 100644 asterisk/main/db1-ast/recno/rec_open.c create mode 100644 asterisk/main/db1-ast/recno/rec_put.c create mode 100644 asterisk/main/db1-ast/recno/rec_search.c create mode 100644 asterisk/main/db1-ast/recno/rec_seq.c create mode 100644 asterisk/main/db1-ast/recno/rec_utils.c create mode 100644 asterisk/main/db1-ast/recno/recno.h create mode 100644 asterisk/main/devicestate.c create mode 100644 asterisk/main/dial.c create mode 100644 asterisk/main/dns.c create mode 100644 asterisk/main/dnsmgr.c create mode 100644 asterisk/main/dsp.c create mode 100644 asterisk/main/ecdisa.h create mode 100644 asterisk/main/editline/CHANGES create mode 100644 asterisk/main/editline/INSTALL create mode 100644 asterisk/main/editline/Makefile.in create mode 100644 asterisk/main/editline/PLATFORMS create mode 100644 asterisk/main/editline/README create mode 100644 asterisk/main/editline/TEST/test.c create mode 100644 asterisk/main/editline/chared.c create mode 100644 asterisk/main/editline/chared.h create mode 100644 asterisk/main/editline/common.c create mode 100755 asterisk/main/editline/config.guess create mode 100644 asterisk/main/editline/config.h.in create mode 100755 asterisk/main/editline/config.sub create mode 100755 asterisk/main/editline/configure create mode 100644 asterisk/main/editline/configure.in create mode 100644 asterisk/main/editline/editline.3 create mode 100644 asterisk/main/editline/editrc.5 create mode 100644 asterisk/main/editline/el.c create mode 100644 asterisk/main/editline/el.h create mode 100644 asterisk/main/editline/emacs.c create mode 100644 asterisk/main/editline/hist.c create mode 100644 asterisk/main/editline/hist.h create mode 100644 asterisk/main/editline/histedit.h create mode 100644 asterisk/main/editline/history.c create mode 100755 asterisk/main/editline/install-sh create mode 100644 asterisk/main/editline/key.c create mode 100644 asterisk/main/editline/key.h create mode 100644 asterisk/main/editline/makelist.in create mode 100644 asterisk/main/editline/map.c create mode 100644 asterisk/main/editline/map.h create mode 100644 asterisk/main/editline/np/fgetln.c create mode 100644 asterisk/main/editline/np/strlcat.c create mode 100644 asterisk/main/editline/np/strlcpy.c create mode 100644 asterisk/main/editline/np/unvis.c create mode 100644 asterisk/main/editline/np/vis.c create mode 100644 asterisk/main/editline/np/vis.h create mode 100644 asterisk/main/editline/parse.c create mode 100644 asterisk/main/editline/parse.h create mode 100644 asterisk/main/editline/prompt.c create mode 100644 asterisk/main/editline/prompt.h create mode 100644 asterisk/main/editline/read.c create mode 100644 asterisk/main/editline/read.h create mode 100644 asterisk/main/editline/readline.c create mode 100644 asterisk/main/editline/readline/readline.h create mode 100644 asterisk/main/editline/refresh.c create mode 100644 asterisk/main/editline/refresh.h create mode 100644 asterisk/main/editline/search.c create mode 100644 asterisk/main/editline/search.h create mode 100644 asterisk/main/editline/sig.c create mode 100644 asterisk/main/editline/sig.h create mode 100644 asterisk/main/editline/sys.h create mode 100644 asterisk/main/editline/term.c create mode 100644 asterisk/main/editline/term.h create mode 100644 asterisk/main/editline/tokenizer.c create mode 100644 asterisk/main/editline/tokenizer.h create mode 100644 asterisk/main/editline/tty.c create mode 100644 asterisk/main/editline/tty.h create mode 100644 asterisk/main/editline/vi.c create mode 100644 asterisk/main/enum.c create mode 100644 asterisk/main/file.c create mode 100644 asterisk/main/fixedjitterbuf.c create mode 100644 asterisk/main/fixedjitterbuf.h create mode 100644 asterisk/main/frame.c create mode 100644 asterisk/main/fskmodem.c create mode 100644 asterisk/main/global_datastores.c create mode 100644 asterisk/main/http.c create mode 100644 asterisk/main/image.c create mode 100644 asterisk/main/indications.c create mode 100644 asterisk/main/io.c create mode 100644 asterisk/main/jitterbuf.c create mode 100644 asterisk/main/loader.c create mode 100644 asterisk/main/logger.c create mode 100644 asterisk/main/manager.c create mode 100644 asterisk/main/md5.c create mode 100644 asterisk/main/netsock.c create mode 100644 asterisk/main/pbx.c create mode 100644 asterisk/main/plc.c create mode 100644 asterisk/main/poll.c create mode 100644 asterisk/main/privacy.c create mode 100644 asterisk/main/rtp.c create mode 100644 asterisk/main/say.c create mode 100644 asterisk/main/sched.c create mode 100644 asterisk/main/sha1.c create mode 100644 asterisk/main/slinfactory.c create mode 100644 asterisk/main/srv.c create mode 100644 asterisk/main/stdtime/Makefile create mode 100644 asterisk/main/stdtime/localtime.c create mode 100644 asterisk/main/stdtime/private.h create mode 100644 asterisk/main/stdtime/test.c create mode 100644 asterisk/main/stdtime/tzfile.h create mode 100644 asterisk/main/strcompat.c create mode 100644 asterisk/main/tdd.c create mode 100644 asterisk/main/term.c create mode 100644 asterisk/main/threadstorage.c create mode 100644 asterisk/main/translate.c create mode 100644 asterisk/main/udptl.c create mode 100644 asterisk/main/ulaw.c create mode 100644 asterisk/main/utils.c create mode 100644 asterisk/makeopts.in create mode 100644 asterisk/menuselect/Makefile create mode 100644 asterisk/menuselect/README create mode 100644 asterisk/menuselect/acinclude.m4 create mode 100644 asterisk/menuselect/aclocal.m4 create mode 100644 asterisk/menuselect/autoconfig.h.in create mode 100755 asterisk/menuselect/bootstrap.sh create mode 100755 asterisk/menuselect/config.guess create mode 100755 asterisk/menuselect/config.sub create mode 100755 asterisk/menuselect/configure create mode 100644 asterisk/menuselect/configure.ac create mode 100644 asterisk/menuselect/example_menuselect-tree create mode 100755 asterisk/menuselect/install-sh create mode 100644 asterisk/menuselect/linkedlists.h create mode 100755 asterisk/menuselect/make_version create mode 100644 asterisk/menuselect/makeopts.in create mode 100644 asterisk/menuselect/menuselect.c create mode 100644 asterisk/menuselect/menuselect.h create mode 100644 asterisk/menuselect/menuselect_curses.c create mode 100644 asterisk/menuselect/menuselect_gtk.c create mode 100644 asterisk/menuselect/menuselect_stub.c create mode 100755 asterisk/menuselect/missing create mode 100644 asterisk/menuselect/mxml/ANNOUNCEMENT create mode 100644 asterisk/menuselect/mxml/CHANGES create mode 100644 asterisk/menuselect/mxml/COPYING create mode 100644 asterisk/menuselect/mxml/Makefile.in create mode 100644 asterisk/menuselect/mxml/README create mode 100644 asterisk/menuselect/mxml/config.h.in create mode 100755 asterisk/menuselect/mxml/configure create mode 100755 asterisk/menuselect/mxml/install-sh create mode 100644 asterisk/menuselect/mxml/mxml-attr.c create mode 100644 asterisk/menuselect/mxml/mxml-entity.c create mode 100644 asterisk/menuselect/mxml/mxml-file.c create mode 100644 asterisk/menuselect/mxml/mxml-index.c create mode 100644 asterisk/menuselect/mxml/mxml-node.c create mode 100644 asterisk/menuselect/mxml/mxml-private.c create mode 100644 asterisk/menuselect/mxml/mxml-search.c create mode 100644 asterisk/menuselect/mxml/mxml-set.c create mode 100644 asterisk/menuselect/mxml/mxml-string.c create mode 100644 asterisk/menuselect/mxml/mxml.h create mode 100644 asterisk/menuselect/mxml/mxml.list.in create mode 100644 asterisk/menuselect/mxml/mxml.pc create mode 100644 asterisk/menuselect/mxml/mxml.pc.in create mode 100644 asterisk/menuselect/strcompat.c create mode 100755 asterisk/missing create mode 100755 asterisk/mkinstalldirs create mode 100644 asterisk/pbx/Makefile create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest10/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest12/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/qq.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t1/a.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t1/b.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t1/c.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t2/d.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t2/e.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t2/f.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t3/g.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t3/h.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t3/i.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest22/t3/j.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/qq.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t1/a.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t1/b.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t1/c.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t2/d.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t2/e.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t2/f.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t3/g.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t3/h.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t3/i.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-ntest23/t3/j.ael create mode 100755 asterisk/pbx/ael/ael-test/ael-ntest9/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test1/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test11/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test14/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test15/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test16/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test18/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test19/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test2/apptest.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test2/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test20/extensions.ael create mode 100755 asterisk/pbx/ael/ael-test/ael-test3/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test3/include1.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test3/include2.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test3/include3.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test3/include4.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test3/include5.ael2 create mode 100755 asterisk/pbx/ael/ael-test/ael-test3/telemarket_torture.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test4/apptest.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-test4/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test5/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test6/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test7/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-test8/extensions.ael create mode 100755 asterisk/pbx/ael/ael-test/ael-vtest13/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest13/include1.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest13/include2.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest13/include3.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest13/include4.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest13/include5.ael2 create mode 100755 asterisk/pbx/ael/ael-test/ael-vtest13/telemarket_torture.ael2 create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest17/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest21/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ael-vtest25/extensions.ael create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-ntest10 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-ntest12 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-ntest22 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-ntest23 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-ntest9 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test1 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test11 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test14 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test15 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test16 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test18 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test19 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test2 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test20 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test3 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test4 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test5 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test6 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test7 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-test8 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-vtest13 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-vtest17 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-vtest21 create mode 100644 asterisk/pbx/ael/ael-test/ref.ael-vtest25 create mode 100755 asterisk/pbx/ael/ael-test/runtests create mode 100755 asterisk/pbx/ael/ael-test/setref create mode 100644 asterisk/pbx/ael/ael.flex create mode 100644 asterisk/pbx/ael/ael.tab.c create mode 100644 asterisk/pbx/ael/ael.tab.h create mode 100644 asterisk/pbx/ael/ael.y create mode 100644 asterisk/pbx/ael/ael_lex.c create mode 100644 asterisk/pbx/dundi-parser.c create mode 100644 asterisk/pbx/dundi-parser.h create mode 100644 asterisk/pbx/pbx_ael.c create mode 100644 asterisk/pbx/pbx_config.c create mode 100644 asterisk/pbx/pbx_dundi.c create mode 100644 asterisk/pbx/pbx_gtkconsole.c create mode 100644 asterisk/pbx/pbx_loopback.c create mode 100644 asterisk/pbx/pbx_realtime.c create mode 100644 asterisk/pbx/pbx_spool.c create mode 100644 asterisk/res/Makefile create mode 100644 asterisk/res/res_adsi.c create mode 100644 asterisk/res/res_agi.c create mode 100644 asterisk/res/res_clioriginate.c create mode 100644 asterisk/res/res_config_odbc.c create mode 100644 asterisk/res/res_config_pgsql.c create mode 100644 asterisk/res/res_convert.c create mode 100644 asterisk/res/res_crypto.c create mode 100644 asterisk/res/res_features.c create mode 100644 asterisk/res/res_indications.c create mode 100644 asterisk/res/res_jabber.c create mode 100644 asterisk/res/res_monitor.c create mode 100644 asterisk/res/res_musiconhold.c create mode 100644 asterisk/res/res_odbc.c create mode 100644 asterisk/res/res_smdi.c create mode 100644 asterisk/res/res_snmp.c create mode 100644 asterisk/res/res_speech.c create mode 100644 asterisk/res/snmp/agent.c create mode 100644 asterisk/res/snmp/agent.h create mode 100644 asterisk/sample.call create mode 100644 asterisk/sounds/Makefile create mode 100644 asterisk/sounds/sounds.xml create mode 100644 asterisk/static-http/ajamdemo.html create mode 100644 asterisk/static-http/astman.css create mode 100644 asterisk/static-http/astman.js create mode 100644 asterisk/static-http/prototype.js create mode 100644 asterisk/utils/Makefile create mode 100644 asterisk/utils/ael_main.c create mode 100644 asterisk/utils/astman.1 create mode 100644 asterisk/utils/astman.c create mode 100644 asterisk/utils/check_expr.c create mode 100644 asterisk/utils/expr2.testinput create mode 100644 asterisk/utils/frame.c create mode 100644 asterisk/utils/frame.h create mode 100644 asterisk/utils/muted.c create mode 100644 asterisk/utils/smsq.c create mode 100644 asterisk/utils/stereorize.c create mode 100644 asterisk/utils/streamplayer.c create mode 100644 configs/amd.conf create mode 100644 configs/asterisk.conf create mode 100644 configs/dundi.conf create mode 100644 configs/examples/echolink/echolink.conf create mode 100644 configs/examples/irlp/irlp.conf create mode 100644 configs/examples/twousbnodes/extensions.conf create mode 100644 configs/examples/twousbnodes/iax.conf create mode 100644 configs/examples/twousbnodes/readme create mode 100644 configs/examples/twousbnodes/rpt.conf create mode 100644 configs/examples/twousbnodes/usbradio.conf create mode 100644 configs/extensions.conf create mode 100644 configs/features.conf create mode 100644 configs/iax.conf create mode 100644 configs/indications.conf create mode 100644 configs/logger.conf create mode 100644 configs/manager.conf create mode 100644 configs/pciradio/modules.conf create mode 100644 configs/pciradio/rpt.conf create mode 100644 configs/pciradio/zapata.conf create mode 100644 configs/pciradio/zaptel.conf create mode 100644 configs/sip.conf create mode 100644 configs/tdm400p/modules.conf create mode 100644 configs/tdm400p/rpt.conf create mode 100644 configs/tdm400p/zapata.conf create mode 100644 configs/tdm400p/zaptel.conf create mode 100644 configs/usbradio/modules.conf create mode 100644 configs/usbradio/rpt.conf create mode 100644 configs/usbradio/usbradio.conf create mode 100644 configs/usbradio/zapata.conf create mode 100644 configs/usbradio/zaptel.conf create mode 100644 configs/wct1xxp/modules.conf create mode 100644 configs/wct1xxp/rpt.conf create mode 100644 configs/wct1xxp/zapata.conf create mode 100644 configs/wct1xxp/zaptel.conf create mode 100755 getsrc create mode 100644 id.gsm create mode 100644 irlp-overlay/noupdate.tar.gz create mode 100644 libpri/.version create mode 100644 libpri/ChangeLog create mode 100644 libpri/LICENSE create mode 100644 libpri/Makefile create mode 100644 libpri/README create mode 100644 libpri/TODO create mode 100755 libpri/build_tools/make_version create mode 100755 libpri/build_tools/make_version_c create mode 100644 libpri/compat.h create mode 100644 libpri/compiler.h create mode 100644 libpri/copy_string.c create mode 100644 libpri/libpri.h create mode 100644 libpri/pri.c create mode 100644 libpri/pri_facility.c create mode 100644 libpri/pri_facility.h create mode 100644 libpri/pri_internal.h create mode 100644 libpri/pri_q921.h create mode 100644 libpri/pri_q931.h create mode 100644 libpri/pri_timers.h create mode 100644 libpri/pridump.c create mode 100644 libpri/prisched.c create mode 100644 libpri/pritest.c create mode 100644 libpri/q921.c create mode 100644 libpri/q931.c create mode 100644 libpri/testprilib.c create mode 100644 sounds/rpt/act-timeout-warning.pcm create mode 100644 sounds/rpt/alllinksdisconnected.ulaw create mode 100644 sounds/rpt/alllinksrestored.ulaw create mode 100644 sounds/rpt/autopatch_on.pcm create mode 100644 sounds/rpt/callproceeding.gsm create mode 100644 sounds/rpt/callterminated.gsm create mode 100644 sounds/rpt/connected.gsm create mode 100644 sounds/rpt/connecting.ulaw create mode 100644 sounds/rpt/connection_failed.gsm create mode 100644 sounds/rpt/down.gsm create mode 100644 sounds/rpt/fast.gsm create mode 100644 sounds/rpt/frequency.gsm create mode 100644 sounds/rpt/functioncomplete.pcm create mode 100644 sounds/rpt/goodafternoon.gsm create mode 100644 sounds/rpt/goodevening.gsm create mode 100644 sounds/rpt/goodmorning.gsm create mode 100644 sounds/rpt/hipwr.gsm create mode 100644 sounds/rpt/in-call.pcm create mode 100644 sounds/rpt/invalid-freq.gsm create mode 100644 sounds/rpt/keyedfor.ulaw create mode 100644 sounds/rpt/login.pcm create mode 100644 sounds/rpt/lopwr.gsm create mode 100644 sounds/rpt/medpwr.gsm create mode 100644 sounds/rpt/memory_notfound.gsm create mode 100644 sounds/rpt/minus.gsm create mode 100644 sounds/rpt/monitor.pcm create mode 100644 sounds/rpt/node.gsm create mode 100644 sounds/rpt/nodenames/2000.ulaw create mode 100644 sounds/rpt/nodenames/2001.ulaw create mode 100644 sounds/rpt/nodenames/2003.ulaw create mode 100644 sounds/rpt/nodenames/2009.ulaw create mode 100644 sounds/rpt/nodenames/2010.ulaw create mode 100644 sounds/rpt/nodenames/2011.ulaw create mode 100644 sounds/rpt/nodenames/2012.ulaw create mode 100644 sounds/rpt/nodenames/2013.ulaw create mode 100644 sounds/rpt/nodenames/2014.ulaw create mode 100644 sounds/rpt/nodenames/2016.ulaw create mode 100644 sounds/rpt/nodenames/2022.ulaw create mode 100644 sounds/rpt/nodenames/2025.ulaw create mode 100644 sounds/rpt/nodenames/2030.ulaw create mode 100644 sounds/rpt/nodenames/2043.ulaw create mode 100644 sounds/rpt/nodenames/2059.ulaw create mode 100644 sounds/rpt/nodenames/2060.ulaw create mode 100644 sounds/rpt/nodenames/2061.ulaw create mode 100644 sounds/rpt/nodenames/2063.ulaw create mode 100644 sounds/rpt/nodenames/2065.ulaw create mode 100644 sounds/rpt/nodenames/2070.ulaw create mode 100644 sounds/rpt/off.gsm create mode 100644 sounds/rpt/on.gsm create mode 100644 sounds/rpt/plus.gsm create mode 100644 sounds/rpt/quick.gsm create mode 100644 sounds/rpt/remote_already.gsm create mode 100644 sounds/rpt/remote_busy.pcm create mode 100644 sounds/rpt/remote_cmd.pcm create mode 100644 sounds/rpt/remote_disc.gsm create mode 100644 sounds/rpt/remote_go.gsm create mode 100644 sounds/rpt/remote_monitor.pcm create mode 100644 sounds/rpt/remote_notfound.pcm create mode 100644 sounds/rpt/remote_tx.pcm create mode 100644 sounds/rpt/repeat_only.gsm create mode 100644 sounds/rpt/rxpl.gsm create mode 100644 sounds/rpt/seconds.ulaw create mode 100644 sounds/rpt/simplex.gsm create mode 100644 sounds/rpt/sitenorm.ulaw create mode 100644 sounds/rpt/slow.gsm create mode 100644 sounds/rpt/stop.gsm create mode 100644 sounds/rpt/thetimeis.gsm create mode 100644 sounds/rpt/timeout-warning.pcm create mode 100644 sounds/rpt/timeout.pcm create mode 100644 sounds/rpt/tranceive.pcm create mode 100644 sounds/rpt/txpl.gsm create mode 100644 sounds/rpt/unauthtx.ulaw create mode 100644 sounds/rpt/unkeyedfor.ulaw create mode 100644 sounds/rpt/up.gsm create mode 100644 sounds/rpt/version.gsm create mode 100644 zaptel/.version create mode 100644 zaptel/ChangeLog create mode 100644 zaptel/LICENSE create mode 100644 zaptel/LICENSE.LGPL create mode 100644 zaptel/Makefile create mode 100644 zaptel/README create mode 100644 zaptel/README.Astribank create mode 100644 zaptel/README.b410p create mode 100644 zaptel/README.fxotune create mode 100644 zaptel/README.fxsusb create mode 100644 zaptel/README.hpec create mode 100644 zaptel/acinclude.m4 create mode 100644 zaptel/aclocal.m4 create mode 100644 zaptel/bittest.h create mode 100755 zaptel/bootstrap.sh create mode 100755 zaptel/build_tools/builder create mode 100755 zaptel/build_tools/genmodconf create mode 100755 zaptel/build_tools/genudevrules create mode 100755 zaptel/build_tools/make_firmware_object.in create mode 100755 zaptel/build_tools/make_svn_branch_name create mode 100755 zaptel/build_tools/make_tree create mode 100755 zaptel/build_tools/make_version create mode 100755 zaptel/build_tools/make_version_h create mode 100644 zaptel/build_tools/menuselect-deps.in create mode 100755 zaptel/build_tools/test_kernel_git create mode 100755 zaptel/build_tools/uninstall-modules create mode 100755 zaptel/build_tools/zaptel_svn_tarball create mode 100755 zaptel/checkstack create mode 100644 zaptel/complex.cc create mode 100644 zaptel/complex.h create mode 100755 zaptel/config.guess create mode 100755 zaptel/config.sub create mode 100755 zaptel/configure create mode 100644 zaptel/configure.ac create mode 100644 zaptel/doc/fxotune.8 create mode 100644 zaptel/doc/fxstest.8 create mode 100644 zaptel/doc/module-parameters.txt create mode 100644 zaptel/doc/patgen.8 create mode 100644 zaptel/doc/pattest.8 create mode 100644 zaptel/doc/torisatool.8 create mode 100644 zaptel/doc/ztcfg.8 create mode 100644 zaptel/doc/ztdiag.8 create mode 100644 zaptel/doc/ztmonitor.8 create mode 100644 zaptel/doc/ztscan.8 create mode 100644 zaptel/doc/ztspeed.8 create mode 100644 zaptel/doc/zttest.8 create mode 100644 zaptel/doc/zttool.8 create mode 100644 zaptel/firmware/Makefile create mode 100644 zaptel/firmware/firmware.xml create mode 100644 zaptel/fxotune.c create mode 100644 zaptel/fxotune.h create mode 100644 zaptel/fxstest.c create mode 100644 zaptel/hdlcgen.c create mode 100644 zaptel/hdlcstress.c create mode 100644 zaptel/hdlctest.c create mode 100644 zaptel/hdlcverify.c create mode 100644 zaptel/ifcfg-hdlc0 create mode 100644 zaptel/ifup-hdlc create mode 100755 zaptel/install-sh create mode 100755 zaptel/install_prereq create mode 100644 zaptel/kernel/GNUmakefile create mode 100644 zaptel/kernel/Kbuild create mode 100644 zaptel/kernel/Makefile create mode 100644 zaptel/kernel/adt_lec.c create mode 100644 zaptel/kernel/adt_lec.h create mode 100644 zaptel/kernel/arith.h create mode 100644 zaptel/kernel/biquad.h create mode 100644 zaptel/kernel/datamods/Makefile create mode 100644 zaptel/kernel/datamods/hdlc_cisco.c create mode 100644 zaptel/kernel/datamods/hdlc_fr.c create mode 100644 zaptel/kernel/datamods/hdlc_generic.c create mode 100644 zaptel/kernel/datamods/hdlc_ppp.c create mode 100644 zaptel/kernel/datamods/hdlc_raw.c create mode 100644 zaptel/kernel/datamods/hdlc_raw_eth.c create mode 100644 zaptel/kernel/datamods/syncppp.c create mode 100644 zaptel/kernel/digits.h create mode 100644 zaptel/kernel/ecdis.h create mode 100644 zaptel/kernel/fasthdlc.h create mode 100644 zaptel/kernel/fir.h create mode 100644 zaptel/kernel/fxo_modes.h create mode 100644 zaptel/kernel/hpec/hpec.h create mode 100644 zaptel/kernel/hpec/hpec_user.h create mode 100644 zaptel/kernel/hpec/hpec_zaptel.h create mode 100644 zaptel/kernel/jpah.h create mode 100644 zaptel/kernel/kb1ec.h create mode 100644 zaptel/kernel/kb1ec_const.h create mode 100644 zaptel/kernel/makefw.c create mode 100644 zaptel/kernel/mg2ec.h create mode 100644 zaptel/kernel/mg2ec_const.h create mode 100644 zaptel/kernel/oct612x/Makefile create mode 100644 zaptel/kernel/oct612x/apilib/bt/octapi_bt0.c create mode 100644 zaptel/kernel/oct612x/apilib/bt/octapi_bt0_private.h create mode 100644 zaptel/kernel/oct612x/apilib/largmath/octapi_largmath.c create mode 100644 zaptel/kernel/oct612x/apilib/llman/octapi_llman.c create mode 100644 zaptel/kernel/oct612x/apilib/llman/octapi_llman_private.h create mode 100755 zaptel/kernel/oct612x/get_discards create mode 100644 zaptel/kernel/oct612x/include/apilib/octapi_bt0.h create mode 100644 zaptel/kernel/oct612x/include/apilib/octapi_largmath.h create mode 100644 zaptel/kernel/oct612x/include/apilib/octapi_llman.h create mode 100644 zaptel/kernel/oct612x/include/digium_unused.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_adpcm_chan_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_adpcm_chan_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_api.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_api_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_apimi.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_apiud.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_channel_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_channel_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_chip_open_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_chip_open_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_chip_stats_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_chip_stats_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_conf_bridge_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_conf_bridge_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_debug_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_debug_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_defines.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_errors.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_events_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_events_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_interrupts_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_interrupts_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_mixer_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_mixer_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_phasing_tsst_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_phasing_tsst_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_playout_buf_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_playout_buf_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_remote_debug_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_remote_debug_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_tlv_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_tone_detection_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_tone_detection_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_tsi_cnct_inst.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_tsi_cnct_pub.h create mode 100644 zaptel/kernel/oct612x/include/oct6100api/oct6100_tsst_inst.h create mode 100644 zaptel/kernel/oct612x/include/octdef.h create mode 100644 zaptel/kernel/oct612x/include/octmac.h create mode 100644 zaptel/kernel/oct612x/include/octosdependant.h create mode 100644 zaptel/kernel/oct612x/include/octrpc/oct6100_rpc_protocol.h create mode 100644 zaptel/kernel/oct612x/include/octrpc/rpc_protocol.h create mode 100644 zaptel/kernel/oct612x/include/octtype.h create mode 100644 zaptel/kernel/oct612x/include/octtypevx.h create mode 100644 zaptel/kernel/oct612x/include/octtypewin.h create mode 100755 zaptel/kernel/oct612x/octasic-helper create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_adpcm_chan_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_adpcm_chan.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_channel.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_chip_open.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_chip_stats.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_conf_bridge.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_debug.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_events.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_interrupts.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_memory.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_miscellaneous.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_mixer.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_phasing_tsst.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_playout_buf.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_remote_debug.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_tlv.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_tone_detection.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_tsi_cnct.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_tsst.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_api/oct6100_user.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_apimi/oct6100_mask_interrupts.c create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_channel_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_chip_open_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_chip_stats_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_conf_bridge_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_debug_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_events_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_interrupts_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_memory_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_miscellaneous_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_mixer_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_phasing_tsst_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_playout_buf_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_remote_debug_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_tlv_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_tone_detection_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_tsi_cnct_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_tsst_priv.h create mode 100644 zaptel/kernel/oct612x/octdeviceapi/oct6100api/oct6100_version.h create mode 100644 zaptel/kernel/oct612x/test.c create mode 100644 zaptel/kernel/pciradio.c create mode 100644 zaptel/kernel/pciradio.rbt create mode 100644 zaptel/kernel/proslic.h create mode 100644 zaptel/kernel/sec-2.h create mode 100644 zaptel/kernel/sec.h create mode 100644 zaptel/kernel/tor2-hw.h create mode 100644 zaptel/kernel/tor2.c create mode 100644 zaptel/kernel/torisa.c create mode 100644 zaptel/kernel/tormenta2.rbt create mode 100644 zaptel/kernel/voicebus.c create mode 100644 zaptel/kernel/voicebus.h create mode 100644 zaptel/kernel/wcfxo.c create mode 100644 zaptel/kernel/wct1xxp.c create mode 100644 zaptel/kernel/wct4xxp/Kbuild create mode 100644 zaptel/kernel/wct4xxp/Makefile create mode 100644 zaptel/kernel/wct4xxp/base.c create mode 100644 zaptel/kernel/wct4xxp/vpm450m.c create mode 100644 zaptel/kernel/wct4xxp/vpm450m.h create mode 100644 zaptel/kernel/wct4xxp/wct4xxp-diag.c create mode 100644 zaptel/kernel/wct4xxp/wct4xxp.h create mode 100644 zaptel/kernel/wctc4xxp/Kbuild create mode 100644 zaptel/kernel/wctc4xxp/Makefile create mode 100644 zaptel/kernel/wctc4xxp/base.c create mode 100644 zaptel/kernel/wctdm.c create mode 100644 zaptel/kernel/wctdm.h create mode 100644 zaptel/kernel/wctdm24xxp/GpakApi.c create mode 100644 zaptel/kernel/wctdm24xxp/GpakApi.h create mode 100644 zaptel/kernel/wctdm24xxp/GpakCust.c create mode 100644 zaptel/kernel/wctdm24xxp/GpakCust.h create mode 100644 zaptel/kernel/wctdm24xxp/GpakHpi.h create mode 100644 zaptel/kernel/wctdm24xxp/Kbuild create mode 100644 zaptel/kernel/wctdm24xxp/Makefile create mode 100644 zaptel/kernel/wctdm24xxp/base.c create mode 100644 zaptel/kernel/wctdm24xxp/gpakErrs.h create mode 100644 zaptel/kernel/wctdm24xxp/gpakenum.h create mode 120000 zaptel/kernel/wctdm24xxp/voicebus.c create mode 100644 zaptel/kernel/wctdm24xxp/wctdm24xxp.h create mode 100644 zaptel/kernel/wcte11xp.c create mode 100644 zaptel/kernel/wcte12xp/GpakApi.c create mode 100644 zaptel/kernel/wcte12xp/GpakApi.h create mode 100644 zaptel/kernel/wcte12xp/GpakErrs.h create mode 100644 zaptel/kernel/wcte12xp/GpakHpi.h create mode 100644 zaptel/kernel/wcte12xp/Kbuild create mode 100644 zaptel/kernel/wcte12xp/Makefile create mode 100644 zaptel/kernel/wcte12xp/base.c create mode 100644 zaptel/kernel/wcte12xp/gpakenum.h create mode 120000 zaptel/kernel/wcte12xp/voicebus.c create mode 100644 zaptel/kernel/wcte12xp/vpmadt032.c create mode 100644 zaptel/kernel/wcte12xp/vpmadt032.h create mode 100644 zaptel/kernel/wcte12xp/wcte12xp.h create mode 100644 zaptel/kernel/wcusb.c create mode 100644 zaptel/kernel/wcusb.h create mode 100644 zaptel/kernel/xpp/.version create mode 100644 zaptel/kernel/xpp/Changelog_xpp create mode 100644 zaptel/kernel/xpp/Kbuild create mode 100644 zaptel/kernel/xpp/Makefile create mode 100644 zaptel/kernel/xpp/README.Astribank create mode 100644 zaptel/kernel/xpp/card_bri.c create mode 100644 zaptel/kernel/xpp/card_bri.h create mode 100644 zaptel/kernel/xpp/card_fxo.c create mode 100644 zaptel/kernel/xpp/card_fxo.h create mode 100644 zaptel/kernel/xpp/card_fxs.c create mode 100644 zaptel/kernel/xpp/card_fxs.h create mode 100644 zaptel/kernel/xpp/card_global.c create mode 100644 zaptel/kernel/xpp/card_global.h create mode 100644 zaptel/kernel/xpp/card_pri.c create mode 100644 zaptel/kernel/xpp/card_pri.h create mode 100644 zaptel/kernel/xpp/firmwares/FPGA_1141.hex create mode 100644 zaptel/kernel/xpp/firmwares/FPGA_1151.hex create mode 100644 zaptel/kernel/xpp/firmwares/FPGA_FXS.hex create mode 100644 zaptel/kernel/xpp/firmwares/LICENSE.firmware create mode 100644 zaptel/kernel/xpp/firmwares/README create mode 100644 zaptel/kernel/xpp/firmwares/USB_FW.hex create mode 100755 zaptel/kernel/xpp/init_card_1_30 create mode 100755 zaptel/kernel/xpp/init_card_2_30 create mode 100755 zaptel/kernel/xpp/init_card_3_30 create mode 100755 zaptel/kernel/xpp/init_card_4_30 create mode 100755 zaptel/kernel/xpp/param_doc create mode 100644 zaptel/kernel/xpp/parport_debug.c create mode 100644 zaptel/kernel/xpp/parport_debug.h create mode 100644 zaptel/kernel/xpp/utils/Makefile create mode 100755 zaptel/kernel/xpp/utils/astribank_hook create mode 100644 zaptel/kernel/xpp/utils/example_default_zaptel create mode 100644 zaptel/kernel/xpp/utils/fpga_load.8 create mode 100644 zaptel/kernel/xpp/utils/fpga_load.c create mode 100755 zaptel/kernel/xpp/utils/genzaptelconf create mode 100644 zaptel/kernel/xpp/utils/genzaptelconf.8 create mode 100644 zaptel/kernel/xpp/utils/hexfile.c create mode 100644 zaptel/kernel/xpp/utils/hexfile.h create mode 100755 zaptel/kernel/xpp/utils/lszaptel create mode 100644 zaptel/kernel/xpp/utils/print_modes.c create mode 100644 zaptel/kernel/xpp/utils/test_parse.c create mode 100644 zaptel/kernel/xpp/utils/xpp.rules create mode 100755 zaptel/kernel/xpp/utils/xpp_blink create mode 100755 zaptel/kernel/xpp/utils/xpp_fxloader create mode 100644 zaptel/kernel/xpp/utils/xpp_fxloader.usermap create mode 100644 zaptel/kernel/xpp/utils/xpp_modprobe create mode 100755 zaptel/kernel/xpp/utils/xpp_sync create mode 100755 zaptel/kernel/xpp/utils/xpp_timing create mode 100755 zaptel/kernel/xpp/utils/zapconf create mode 100644 zaptel/kernel/xpp/utils/zaptel-helper create mode 100755 zaptel/kernel/xpp/utils/zaptel_drivers create mode 100755 zaptel/kernel/xpp/utils/zaptel_hardware create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Chans.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Config/Defaults.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Hardware.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Hardware/PCI.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Hardware/USB.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Span.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Utils.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Xpp.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Xpp/Line.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Xpp/Xbus.pm create mode 100644 zaptel/kernel/xpp/utils/zconf/Zaptel/Xpp/Xpd.pm create mode 100755 zaptel/kernel/xpp/utils/zt_registration create mode 100644 zaptel/kernel/xpp/xbus-core.c create mode 100644 zaptel/kernel/xpp/xbus-core.h create mode 100644 zaptel/kernel/xpp/xbus-pcm.c create mode 100644 zaptel/kernel/xpp/xbus-pcm.h create mode 100644 zaptel/kernel/xpp/xbus-sysfs.c create mode 100644 zaptel/kernel/xpp/xdefs.h create mode 100644 zaptel/kernel/xpp/xframe_queue.c create mode 100644 zaptel/kernel/xpp/xframe_queue.h create mode 100644 zaptel/kernel/xpp/xpd.h create mode 100644 zaptel/kernel/xpp/xpp_log.h create mode 100644 zaptel/kernel/xpp/xpp_usb.c create mode 100644 zaptel/kernel/xpp/xpp_zap.c create mode 100644 zaptel/kernel/xpp/xpp_zap.h create mode 100644 zaptel/kernel/xpp/xproto.c create mode 100644 zaptel/kernel/xpp/xproto.h create mode 100644 zaptel/kernel/xpp/zap_debug.c create mode 100644 zaptel/kernel/xpp/zap_debug.h create mode 100644 zaptel/kernel/zaptel-base.c create mode 100644 zaptel/kernel/zaptel.h create mode 100644 zaptel/kernel/zconfig.h create mode 100644 zaptel/kernel/ztd-eth.c create mode 100644 zaptel/kernel/ztd-loc.c create mode 100644 zaptel/kernel/ztdummy.c create mode 100644 zaptel/kernel/ztdummy.h create mode 100644 zaptel/kernel/ztdynamic.c create mode 100644 zaptel/kernel/zttranscode.c create mode 100755 zaptel/live_zap create mode 100644 zaptel/makeopts.in create mode 100644 zaptel/menuselect/Makefile create mode 100644 zaptel/menuselect/README create mode 100644 zaptel/menuselect/acinclude.m4 create mode 100644 zaptel/menuselect/aclocal.m4 create mode 100644 zaptel/menuselect/autoconfig.h.in create mode 100755 zaptel/menuselect/bootstrap.sh create mode 100755 zaptel/menuselect/config.guess create mode 100755 zaptel/menuselect/config.sub create mode 100755 zaptel/menuselect/configure create mode 100644 zaptel/menuselect/configure.ac create mode 100644 zaptel/menuselect/example_menuselect-tree create mode 100755 zaptel/menuselect/install-sh create mode 100644 zaptel/menuselect/linkedlists.h create mode 100755 zaptel/menuselect/make_version create mode 100644 zaptel/menuselect/makeopts.in create mode 100644 zaptel/menuselect/menuselect.c create mode 100644 zaptel/menuselect/menuselect.h create mode 100644 zaptel/menuselect/menuselect_curses.c create mode 100644 zaptel/menuselect/menuselect_gtk.c create mode 100644 zaptel/menuselect/menuselect_stub.c create mode 100755 zaptel/menuselect/missing create mode 100644 zaptel/menuselect/mxml/ANNOUNCEMENT create mode 100644 zaptel/menuselect/mxml/CHANGES create mode 100644 zaptel/menuselect/mxml/COPYING create mode 100644 zaptel/menuselect/mxml/Makefile.in create mode 100644 zaptel/menuselect/mxml/README create mode 100644 zaptel/menuselect/mxml/config.h.in create mode 100755 zaptel/menuselect/mxml/configure create mode 100755 zaptel/menuselect/mxml/install-sh create mode 100644 zaptel/menuselect/mxml/mxml-attr.c create mode 100644 zaptel/menuselect/mxml/mxml-entity.c create mode 100644 zaptel/menuselect/mxml/mxml-file.c create mode 100644 zaptel/menuselect/mxml/mxml-index.c create mode 100644 zaptel/menuselect/mxml/mxml-node.c create mode 100644 zaptel/menuselect/mxml/mxml-private.c create mode 100644 zaptel/menuselect/mxml/mxml-search.c create mode 100644 zaptel/menuselect/mxml/mxml-set.c create mode 100644 zaptel/menuselect/mxml/mxml-string.c create mode 100644 zaptel/menuselect/mxml/mxml.h create mode 100644 zaptel/menuselect/mxml/mxml.list.in create mode 100644 zaptel/menuselect/mxml/mxml.pc create mode 100644 zaptel/menuselect/mxml/mxml.pc.in create mode 100644 zaptel/menuselect/strcompat.c create mode 100644 zaptel/mkfilter.h create mode 100644 zaptel/mknotch.cc create mode 100644 zaptel/orig.ee create mode 100644 zaptel/patgen.c create mode 100644 zaptel/patlooptest.c create mode 100644 zaptel/pattest.c create mode 100644 zaptel/ppp/Makefile create mode 100644 zaptel/ppp/zaptel.c create mode 100644 zaptel/sethdlc-new.c create mode 100644 zaptel/sethdlc.c create mode 100644 zaptel/timertest.c create mode 100644 zaptel/tonezone.c create mode 100644 zaptel/tonezone.h create mode 100644 zaptel/tor2.ee create mode 100644 zaptel/torisatool.c create mode 100644 zaptel/tormenta2.ucf create mode 100644 zaptel/tormenta2.vhd create mode 100644 zaptel/usbfxstest.c create mode 100644 zaptel/zaptel.conf.sample create mode 100644 zaptel/zaptel.init create mode 100644 zaptel/zaptel.sysconfig create mode 100644 zaptel/zaptel.xml create mode 100644 zaptel/zonedata.c create mode 100644 zaptel/ztcfg-dude.c create mode 100644 zaptel/ztcfg.c create mode 100644 zaptel/ztcfg.h create mode 100644 zaptel/ztdiag.c create mode 100644 zaptel/ztmonitor.c create mode 100644 zaptel/ztscan.c create mode 100644 zaptel/ztspeed.c create mode 100644 zaptel/zttest.c create mode 100644 zaptel/zttool.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..996bdef6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (C) 2005 Stephen A. Rodgers + +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 +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, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + + +Stephen A. Rodgers +4341 Miriam Pl. +La Mesa CA 91941 USA + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..dbb506cb --- /dev/null +++ b/Makefile @@ -0,0 +1,138 @@ +# Makefile for Asterisk, Zaptel and Libpri + +-include /etc/sysinfo #include if it exists, else use defaults + +ASTSRC_VERS:=1.0.6 +KVERS?=$(shell uname -r) +PROCESSOR?=i586 + +.kernel-dev-installed: + -umount /mnt/cf + mount /mnt/cf + (cd /lib/modules/$(KVERS)/build; tar xvzf /mnt/cf/kdev.tgz) + umount /mnt/cf + rm -f /usr/include/linux + ln -s /lib/modules/$(KVERS)/build/include/linux /usr/include/linux + touch .kernel-dev-installed + +.zaptel-built: .kernel-dev-installed + (cd zaptel; ./configure --build=$(PROCESSOR)-pc-linux) + (MAKELEVEL=0; make -C zaptel); # Why does MAKELEVEL need to be 0 for zaptel to build correctly? + touch .zaptel-built + +.zaptel-installed: .zaptel-built + (MAKELEVEL=0; make -C zaptel install) + #make -C zaptel clean + touch .zaptel-installed + +.libpri-built: + make -C libpri + touch .libpri-built + +.libpri-installed: .libpri-built + make -C libpri install + #make -C libpri clean + touch .libpri-installed + +build-only: .zaptel-installed .libpri-installed + (cd asterisk; ./configure CXX=gcc --build=$(PROCESSOR)-pc-linux; make menuconfig) + make -C asterisk + +.asterisk-installed: + -rm -rf /usr/lib/asterisk/modules/* + (cd asterisk; ./configure CXX=gcc --build=$(PROCESSOR)-pc-linux) + make -C asterisk install + #make -C asterisk clean + touch .asterisk-installed + +.rpt-sounds-installed: + mkdir -p /var/lib/asterisk/sounds/rpt + cp -a sounds/* /var/lib/asterisk/sounds + +.id_file: + -mkdir -p /etc/asterisk + -cp id.gsm /etc/asterisk + touch .id_file + +.PHONY: help archive svsrc + +help: + @echo "make upgrade - build and install sources only" + @echo "make install_pciradio - build and install sources plus pciradio config files" + @echo "make install_usbradio - build and install sources plus usbradio config files" + @echo "make install_wcte11xp - build and install sources plus single span wcte11xp config files" + @echo "make install_wct1xxp - build and install sources plus single span t100p config files" + @echo "make install_wctdm - build and install sources plus tdm400 config files" + @echo "make svsrc - save the sources on the CF as astsrc.tgz" + @echo "make build_only - build only, do not install" + @echo "make clean - delete all object files, intermediate files, and executables" + +upgrade: .kernel-dev-installed .zaptel-installed .libpri-installed .asterisk-installed .rpt-sounds-installed + -umount /mnt/cf + svastbin + mount /mnt/cf + mv -f /root/astbin.tgz /mnt/cf + umount /mnt/cf + +install_pciradio: .id_file upgrade + -@mkdir -p /etc/asterisk + -@cp configs/* /etc/asterisk + -@cp configs/pciradio/* /etc/asterisk + -@mv /etc/asterisk/zaptel.conf /etc + -@touch /etc/init.d/pciradio + -@svcfg + +install_usbradio: .id_file upgrade + -@mkdir -p /etc/asterisk + -@cp configs/* /etc/asterisk + -@cp configs/usbradio/* /etc/asterisk + -@mv /etc/asterisk/zaptel.conf /etc + -@touch /etc/init.d/usbradio + -@svcfg + +install_wcte11xp: .id_file upgrade + -@mkdir -p /etc/asterisk + -@cp configs/* /etc/asterisk + -@cp configs/wct1xxp/* /etc/asterisk + -@mv /etc/asterisk/zaptel.conf /etc + -@touch /etc/init.d/wcte11xp + -@svcfg + +install_wct1xxp: .id_file upgrade + -@mkdir -p /etc/asterisk + -@cp configs/* /etc/asterisk + -@cp configs/wct1xxp/* /etc/asterisk + -@mv /etc/asterisk/zaptel.conf /etc + -@touch /etc/init.d/wct1xxp + -@svcfg + +install_wctdm: .id_file upgrade + -@mkdir -p /etc/asterisk + -@cp configs/* /etc/asterisk + -@cp configs/tdm400p/* /etc/asterisk + -@mv /etc/asterisk/zaptel.conf /etc + -@touch /etc/init.d/wctdm + +clean: .kernel-dev-installed + -rm .zaptel-installed \ + .libpri-installed .asterisk-installed \ + .rpt-sounds-installed .installed .zaptel-built \ + .libpri-built .asterisk-built .id_file + make -C libpri clean + make -C asterisk clean + (MAKELEVEL=0; make -C zaptel clean) + +release: + tar cvzf ../astsrc-vers-$(ASTSRC_VERS).tar.gz --exclude='.svn' allstar asterisk libpri zaptel sounds configs Makefile id.gsm README LICENSE + -@rm ../files.tar.gz + (cd ..; ln -s astsrc-vers-$(ASTSRC_VERS).tar.gz files.tar.gz) + (cd ..; sha256sum files.tar.gz | cut -d ' ' -f 1 >files.tar.gz.sha256sum) + + + +svsrc: + -umount /mnt/cf + mount /mnt/cf + tar cvfz /mnt/cf/astsrc.tgz * + sync + umount /mnt/cf diff --git a/README b/README new file mode 100644 index 00000000..45ebfe66 --- /dev/null +++ b/README @@ -0,0 +1,55 @@ +This is the Asterisk source package for ACID and LIMEY LINUX + +asterisk-1.4.23pre +libpri-1.4.7 +zaptel 1.4,12,1 + +This a patched version of Asterisk to run with uClibc on an embedded mini-itx system and on an ACID automatic install disk. + +ACID BUILD INSTRUCTIONS +---------------------- + +The build is automatically handled by the astupd.sh script. + + +LIMEY LINUX BUILD INSTRUCTIONS +----------------------------- + +First, unpack this archive in /usr/src on the target system. + +If you are doing a new install, then use one of the following targets: + +make install_pciradio + +or + +make install_usbradio + + +Choose the correct target for your hardware configuration. install_pciradio +is for users with quad radio PCI cards, and install_usbradio is for users with +a modified USB sound FOB, or commercially available URI. + +When the build completes, run the savecfg script on the target to save the +Asterisk config files in /mnt/cf/syscfg.tgz so that they are loaded every +time the system boots up. + +If you are doing an upgrade on a target already configured, then use this +target: + +make upgrade + +DETAILS +------- + +The supplied Makefile will build zaptel, libpri and asterisk, then install them +in the correct directories. Once they are installed, then the Makefile runs the +svastbin script to generate the astbin.tgz file, and copies it to /mnt/cf. + +With a copy astbin.tgz the system will automatically unpack the asterisk +binaries each time it is booted, load the correct drivers, and then start +Asterisk. + + + + diff --git a/allstar/rc.updatenodelist b/allstar/rc.updatenodelist new file mode 100755 index 00000000..c3e4a9c2 --- /dev/null +++ b/allstar/rc.updatenodelist @@ -0,0 +1,31 @@ +#! /bin/sh + +TOPDOMAIN=allstarlink.org +SUBDOMAINS="nodes1" +FILEPATH=/var/lib/asterisk +WGET=`which wget` + +while [ 1 ] +do + for i in $SUBDOMAINS + do + res=0 + while [ $res -eq 0 ] + do + $WGET -q -O $FILEPATH/rpt_extnodes-temp http://$i.$TOPDOMAIN/cgi-bin/nodes.pl + res=$? + if [ $res -eq 0 ] + then + /bin/chmod 700 $FILEPATH/rpt_extnodes-temp + /bin/mv -f $FILEPATH/rpt_extnodes-temp $FILEPATH/rpt_extnodes + #echo "Retrieved node list from $i.$TOPDOMAIN" + #date + sleep 600 + else + #echo "Problem retrieving node list from $i.$TOPDOMAIN, trying another server"; + sleep 30 + fi + done + done +done + diff --git a/asterisk/BUGS b/asterisk/BUGS new file mode 100644 index 00000000..96b55e65 --- /dev/null +++ b/asterisk/BUGS @@ -0,0 +1,22 @@ +Asterisk Bug Tracking Information +================================= + +To learn about and report Asterisk bugs, please visit +the official Asterisk Bug Tracker at: + + http://bugs.digium.com + +For more information on using the bug tracker, or to +learn how you can contribute by acting as a bug marshal +please see: + + http://www.asterisk.org/developers/bug-guidelines + +If you would like to submit a feature request, please +resist the temptation to post it to the bug tracker. +Feature requests should be posted to the asterisk-dev +mailing list, located at: + + http://lists.digium.com + +Thank you! diff --git a/asterisk/CHANGES b/asterisk/CHANGES new file mode 100644 index 00000000..02919617 --- /dev/null +++ b/asterisk/CHANGES @@ -0,0 +1,356 @@ +Changes since Asterisk 1.2: + + * over 4,000 commits since 1.2 + * queue member naming + * CLI commands rework + o Change the way CLI commands are structured. + o Most commands are now + * chan_h323 update + * RTP packetization + * SLA (Shared Line Appearance) support + * T.38 Passthrough Support for faxing in SIP + * Generic channel jitterbuffer (spawned from RTP) + * Variable Length DTMF for better DTMF compatibility + * Improved chan_iax2 scalability by using multithreading + * AEL2 has replaced the original implementation of AEL. The "2" is removed. For more details, + read: http://www.voip-info.org/wiki/view/Asterisk+AEL2 + AEL is no longer considered experimental. + * New sounds; English, Spanish, and French prompts, as well as music on hold files, in + multiple Asterisk native formats. + * IMAP storage of voicemail + * Jabber/GoogleTalk integration + * New speech recognition API for interfacing to different Voice Recognition software packages + * much more customizable and portable build system + o also for asterisk-addons + * Radius CDR logging + * SNMP support + * SMDI (Simplified Message Desk Interface) support + * Redesign of MusicOnHold configuration settings + * Manager over HTTP + * Significant chan_skinny updates + * Significant chan_misdn updates + * Improved SIP transfers + * SIP MWI subscription support + * Much improved support for SIP video + * Control over SIP transfers and subscriptions (enable/disable per device) + * ChanSpy whisper mode (Whisper Paging) + * Configurable language support for saying dates and times + * Significant architecture improvements for memory usage and performance + * Media-only IAX2 transfers + * Updates to the Radio Repeater app code + * Deprecation of AgentCallbackLogin in favor of a dialplan-based solution + * uClibc builds supported + * Work done for freeBSD portability + * Work done for Solaris portability + * FreeTDS-based database can be used with Realtime + * New internal data structure, stringfields, is implemented in IAX and SIP, reducing memory consumption by about 50%. + * Use of thread local storage for reduced memory allocation/freeing and lower stack consumption + * Reorganized files into docs/ main/ configs/, including name changes in some cases + * Much effort was expended in arranging documentation in source files in doxygen format + * Improved IP TOS support for IAX and SIP + * Builtin mini HTTP server + * Added support for Sigma Designs cards. + * Frame header caching to reduce memory allocation/freeing + * Passthrough and record/playback support for G.722 wideband audio + * using mpg123 to play MP3 files for music-on-hold will be deprecated in 1.4 (start using the "native support") + * New Apps: + 1. AMD() ;; Answering Machine Detection + 2. ChannelRedirect() ;; asynch goto, redirect chan to context/exten/priority + 3. ContinueWhile() ;; Addition to the While() suite. Acts like "continue". + 4. ExitWhile() ;; Addition to the While() suite. Acts like "break". + 5. ExtenSpy() ;; A close cousin to ChanSpy(). + 6. FollowMe() ;; findme/followme call redirect app + 7. Log() ;; Send a message to the log, based on severity level. + 8. MacroExclusive() ;; No more than one invocation of this macro allowed at any one time. + 9. MorseCode() ;; turns strings into dits and dahs. A playground for ham radio licensees! + 10. OSPAuth() ;; OSP authentication + 11. QueueLog() ;; allows you to write your own events into the queue log + 12. SLAStation() ;; Shared Line Appearance + 13. SLATrunk() ;; Shared Line Appearance + 14. SpeechCreate() ;; Voice Recognition Engine interface... + 15. SpeechActivateGrammar() + 16. SpeechStart() + 17. SpeechBackground + 18. SpeechDeactivateGrammar() + 19. SpeechProcessingSound() + 20. SpeechDestroy() + 21. SpeechLoadGrammar() + 22. SpeechUnloadGrammar() + 23. StopMixMonitor() ;; to stop the MixMonitor App. + 24. TryExec() ;; execute dialplan app without fatal consequences + * Apps removed: + 1. CheckGroup -- do a comparison to ${GROUP()} + 2. Curl -- use the function CURL() instead + 3. Cut -- use the function CUT() instead + 4. DateTime -- use sayunixtime() app instead. + 5. DBget -- deprecated in 1.2, now removed. + 6. DBput -- deprecated in 1.2, now removed. + 7. Enumlookup -- use the function ENUMLOOKUP() instead + 8. Eval -- use the function EVAL() instead + 9. GetGroupCount -- use the function GROUP_COUNT() instead + 10. GetGroupMatchCount -- use the function GROUP_MATCH_COUNT() instead + 11. Intercom -- use the chan_oss module instead + 12. Math -- use the function MATH() instead + 13. MD5 -- use the function MD5() instead + 14. SetCIDname -- use the function CALLERID(name) instead + 15. SetCIDnum -- use the function CALLERID(number) instead + 16. SetGroup -- use Set(GROUP=group) instead + 17. SetRDNIS -- use the function CALLERID(rdnis) instead + 18. Sql_postgres -- was deprecated in 1.2, now removed + 19. Txtcidname -- use the function TXTCIDNAME instead + * New Dialplan Functions: + 1. ARRAY() + 2. BASE_64_DECODE() + 3. BASE_64_ENCODE() + 4. CHANNEL() + 5. CURL() + 6. CUT() + 7. DB_DELETE() + 8. FILTER() + 9. GLOBAL() + 10. IFTIME() + 11. KEYPADHASH() + 12. ODBC() + 13. QUOTE() + 14. RAND() + 15. REALTIME() + 16. SHA1() + 17. SORT() + 18. SPRINTF() + 19. SQL_ESC() + 20. STAT() + 21. STRPTIME() + * Apps that have changes to their interface: + 1. Authenticate() -- optional maxdigits argument added. + 2. ChanSpy() -- new options: + o w -- Enable 'whisper' mode, so the spying channel can talk to... + o W -- Enable 'private whisper' mode, so the spying channel can... + 3. DBdel() -- now marked as DEPRECATED in favor of the DB_DELETE func + 4. Dial() + o New Option: O([x]) for Zaptel operator mode + o New Option: K/k parking via dtmf tones + 5. Dictate() -- optional filename argument added. + 6. Directory() -- new option: e - In addition to the name, also read the extension number... + 7. ForkCDR() -- new options: + o 'a' -- update answer time on new cdr + o 'A' -- Lock the orig CDR answer time against changes. + o 'D' -- Copy the disposition from the orig to the new CDR. + o 'd' -- clear the dstcannel field in the new CDR. + o 'e' -- set the end time of the original CDR. + o 'R' -- do NOT reset the new CDR. + o 's' -- Add/change var in orig CDR. + o 'T' -- Force ast_cdr_end, answer to obey LOCKED flag for the orig. CDR. + -- ast_cdr_setvar will be forced also (used by the CDR() func in write mode) + 8. Meetme() -- new options: + o 'I' -- announce user join/leave without review + o 'l' -- set listen only mode (Listen only, no talking) + o 'o' -- set talker optimization - treats talkers who aren't speaking as... + o '1' -- do not play message when first person enters + 9. MeetmeAdmin() -- new options: + o 'r' -- Reset one user's volume settings + o 'R' -- Reset all users volume settings + o 's' -- Lower entire conference speaking volume + o 'S' -- Raise entire conference speaking volume + o 't' -- Lower one user's talk volume + o 'T' -- Lower all users talk volume + o 'u' -- Lower one user's listen volume + o 'U' -- Lower all users listen volume + o 'v' -- Lower entire conference listening volume + o 'V' -- Raise entire conference listening volume + 10. OSPFinish() : now also can return ERROR result. + 11. OSPLookup() : Sets more variables, also now returns ERROR result. + 12. Page() -- New option: r - record the page into a file (see 'r' for app_meetme) + 13. Pickup() -- multiple extensions, PICKUPMARK; read the description! + 14. Queue() + o New Argument: AGI + o New option: i + 15. Random() -- is now deprecated in 1.4 + 16. Read() -- replace 'skip' and 'noanswer' options with 's', 'n', add 'i' option. + 17. Record() -- New option: 'x' : ignore all terminator keys (DTMF) and keep recording until hangup + 18. UserEvent() -- slight change in behavior. Read the description. + 19. VoiceMailMain() -- new a(#) option, goes to folder # directly. + 20. WaitForSilence() -- new optional 3rd arg, time delay before returning. + * Functions that have changes to their interfaces: + 1. CDR -- new options: u and s + 2. LANGUAGE -- Deprecated. Use CHANNEL(language) instead. + 3. MUSICCLASS -- Deprecated. Use CHANNEL(musicclass) instead. + * Configuration File Changes: + 1. NEW config files: + 1. amd.conf -- Answering Machine Detection parameters + 2. followme.conf -- parameters for the findme/followme call forwarding + 3. func_odbc.conf -- define sql access functions here + 4. gtalk.conf -- how to handle gtalk protocol calls + 5. h323.conf -- h323 configuration + 6. http.conf -- config for the builtin mini-http server in asterisk + 7. jabber.conf -- jabber interface + 8. jingle.conf -- jingle protocol interface config + 10. res_snmp.conf -- to enable snmp in asterisk, and define full/sub agent status + 11. say.conf -- define per-language rules for numbers, dates, etc. + 12. skinny.conf -- for those special skinny phones you want to use... + 13. sla.conf -- Shared Line Appearance config + 14. smdi.conf -- SMDI messaging config + 15. udptl.conf -- T38's udptl transport config + 16. users.conf -- user config + 2. Changes to Existing Config files: + 1. In General: + o Jitterbuffer support added to several channels. Usually adds these variables to a config file: + 1. jbenable + 2. jbmaxsize + 3. jbresyncthreshold + 4. jbimpl + 5. jblog + o MusicOnHold upgrade introduces two new variables: + 1. mohinterpret + 2. mohsuggest + 2. agents.conf + o maxlogintries variable added + o autologoffunavail variable added + o endcall variable added + o agentgoodbye variable added + o createlink variable REMOVED + 3. alsa.conf + o mohinterpret variable added + o Jitterbuffer variables added + 4. cdr.conf + o endbeforehexten variable added + o sections for csv and radius added, with variables usegmtime, loguniqueid, + loguserfield, and radiuscfg variables. + 5. cdr_tds.conf + o table variable added + 6. extensions.ael + o Many upgrades. See the info at http://www.voip-info.org/wiki/view/Asterisk+AEL2 + 7. extensions.conf + o autofallthru now set to "yes" by default + o userscontext variable added + o added info/examples on paging and hints. + 8. features.conf + o parkedplay variable added (who to beep at) + o parkedmusicclass + o atxfernoanswertimeout variable added + o parkcall variable added (one step parking) + o improved documentation for dynamic feature declarations! + 9. iax.conf + o adsi variable added + o mohinterpret variable added + o mohsuggest variable added + o jitterbuffer updates + o iaxthreadcount variable added + o iaxmaxthreadcount variable added + o the way to specify TOS has changed. + o mailboxdetail variable has been REMOVED. + 10. indications.conf + o [bg] entry added (Bulgaria). + o [il] entry added (Israel) + o [in] entry added (India) + o [jp] entry added (Japan) + o [my] entry added (Malaysia) + o [th] entry added (Thailand) + 11. manager.conf + o webenabled variable added + o httptimeout variable added + o timestampevents variable added + 12. mgcp.conf + o Jitterbuffer support added + 13. misdn.conf + o l1watcher_timeout variable added + o pp_l2_check variable added + o echocancelwhenbridged variable added + o echotraining variable added + o max_incoming variable added + o max_outgoing variable added + 14. modules.conf + o a comment for preloading res_speech.so is added + o mention of global symbols is removed + o obsolesced entries for chan_modem_* and app_intercom have been removed + 15. musiconhold.conf + o the default is now to do native moh from /var/lib/asterisk/moh + 16. osp.conf + o authpolicy variable added + 17. oss.conf + o debug variable added + o device variable added + o mixer variable added + o boost variable added + o callerid variable added + o autohangup variable added + o queuesize variable added + o frags variable added + o JitterBuffer support + o sections to define alternate sound cards + 18. queues.conf + o autofill variable added + o monitor-type variable added + o musiconhold is now musicclass, with a difference in interpretation + o autofill variable added + o autopause variable added + o setinterfacevar variable added + o ringinuse variable added + 19. res_odbc.conf + o pooling variable added + 20. rpt.conf + o duplex variable added + o tailmessagetime variable added + o tailsquashedtime variable added + o tailmessages variable added + 21. rtp.conf + o rtcpinterval varaible added + 22. sip.conf + o allowoverlap variable added + o allowtransfer variable added + o tos variable REMOVED + o tos_sip variable added + o tos_audio variable added + o tos_video variable added + o minexpiry variable added + o t1min variable added + o musicclass variable REMOVED + o mohinterpret variable added + o maxcallbitratesuggest variable added + o allowsubscribe variable added + o videosupport variable added + o maxcallbitrate variable added + o g726nonstandard variable added + o dumphistory variable added + o allowsubscribe variable added + o t38pt_udptl variable added + o canreinvite variable can also now be set to 'nonat' + o rtsavesysname variable added + o JitterBuffer support added + o t38pt_usertpsource variable added + 23. skinny.conf + o port variable renamed to bindport + o JitterBuffer support added + o model variable REMOVED + o mohinterpret variable added + o mohsuggest variable added + o speeddial variable added + o addon variable added + 24. voicemail.conf + o userscontext variable added + o smdiport variable added + o attachfmt variable added + o volgain variable added + o tempgreetwarn variable added + 25. zapata.conf + o pritimer variable has improved documentation + o New signalling method: fgccama + o New signalling method: fgccamamf + o outsignalling variable added + o distinctiveringaftercid variable added + o cidsignalling now also accepts v23_jp, and smdi + o usesmdi variable added + o smdiport variable added + o mohinterpret variable added + o mohsuggest variable added + o JitterBuffer support added + * Removed Codecs/Channels: + 1. codec_g723 was removed because the actual codec implementation it was designed to use is not distributable + 2. chan_modem_* and related modules are gone because the kernel support for those interfaces is old, buggy and unsupported + * New Utils: + 1. aelparse -- compile .ael files outside of asterisk + * New manager events: + 1. OriginateResponse event comes to replace OriginateSuccess and OriginateFailure + * iLBC source code no longer included (see UPGRADE.txt for details) + * New CLI command "pri show version" that shows the current version of libpri + that the library was built against (requires a version of libpri since this API + feature was added). diff --git a/asterisk/COPYING b/asterisk/COPYING new file mode 100644 index 00000000..aa2ebac6 --- /dev/null +++ b/asterisk/COPYING @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/asterisk/CREDITS b/asterisk/CREDITS new file mode 100644 index 00000000..a5163821 --- /dev/null +++ b/asterisk/CREDITS @@ -0,0 +1,215 @@ + +=== 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 + +=== WISHLIST CONTRIBUTERS === +Jeremy McNamara - SpeeX support +Nick Seraphin - RDNIS support +Gary - Phonejack ADSI (in progress) +Wasim - Hangup detect + +=== HARDWARE DONORS === +* 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 === +Jim Dixon - Zapata Telephony and app_rpt + http://www.zapatatelephony.org/app_rpt.html + +Russell Bryant - Asterisk 1.0 maintainer and misc. enhancements + russelb@clemson.edu + +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@yahoo.com http://www.asterlink.com + +James Golovich - Innumerable contributions + You can find him and asterisk-perl at http://asterisk.gnuinter.net + +Andre Bierwirth - Extension hints and status + +Oliver Daudey - ISDN4Linux fixes + +Pauline Middelink - ISDN4Linux patches and some general patches. + She can be found at http://www.polyware.nl/~middelink/En/ + +Jean-Denis Girard - Various contributions from the South Pacific Islands + jd-girard@esoft.pf http://www.esoft.pf + +William Jordan / Vonage - MySQL enhancements to Voicemail + wjordan@vonage.com + +Jac Kersing - Various fixes + +Steven Critchfield - Seek and Trunc functions for playback and recording + critch@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@oa.com.au + +James Dennis - Cisco SIP compatibility patches to work with SIP service + providers. Can be contacted at asterisk@jdennis.net + +Tilghman Lesher - ast_localtime(); ast_say_date_with_format(); + GotoIfTime, Random, SayUnixTime, HasNewVoicemail applications; + CUT, SORT, EVAL, CURL, FIELDQTY, STRFTIME, QUEUEAGENT* functions; + and other innumerable bug fixes. http://asterisk.drunkcoder.com/ + +Jayson Vantuyl - Manager protocol changes, various other bugs. + jvantuyl@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@sigmasoft.com + +Brian West - ODBC support and Bug Marshaling + +Josh Roberson - chan_zap reload support, Advanced Voicemail Features, other misc. patches, + and Bug Marshalling. - josh@asteriasgi.com, http://www.asteriasgi.com + +William Waites - syslog support, SIP NAT traversal for SIP-UA. ww@styx.org + +Rich Murphey - Porting to FreeBSD, NetBSD, OpenBSD, and Darwin. + rich@whiteoaklabs.com http://whiteoaklabs.com + +Simon Lockhart - Porting to Solaris (based on work of Logan ???) + simon@slimey.org + +Olle E. Johansson - SIP RFC compliance, documentation and testing, testing, testing + oej@edvina.net, http://edvina.net + +Steve Kann - new jitter buffer for IAX2 + stevek@stevek.com + +Constantine Filin - major contributions to the Asterisk Realtime Architecture + +Steve Murphy - privacy support, $[ ] parser upgrade, AEL2 parser upgrade + +Claude Patry - bug fixes, feature enhancements, and bug marshalling + cpatry@gmail.com + +Miroslav Nachev, miro@space-comm.com COSMOS Software Enterprises, Ltd. + - for Variable for No Answer Timeout for Attended Transfer + +Slav Klenov & Vanheuverzwijn Joachim - development of the generic jitterbuffer + Securax Ltd. info@securax.be + +Roy Sigurd Karlsbakk - providing funding for generic jitterbuffer development + roy@karlsbakk.net, Briiz Telecom AS + +Voop A/S, Nuvio Inc, Inotel S.A and Foniris Telecom A/S - funding for rewrite of SIP transfers + +Philippe Sultan - RADIUS CDR module + INRIA, http://www.inria.fr/ + +John Martin, Aupix - Improved video support in the SIP channel + +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@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@gdspartners.com + +Bartosz Supczinski - Support for Polish added by DIR (www.dir.pl) Bartosz.Supczinski@dir.pl + +James Rothenberger - Support for IMAP storage integration added by OneBizTone LLC Work funded by University of Pennsylvania jar@onebiztone.com + +Paul Cadach - Bringing chan_h323 up to date, bug fixes, and more! + +=== OTHER 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 === +Music provided by www.freeplaymusic.com + +=== OTHER SOURCE CODE IN ASTERISK === +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. diff --git a/asterisk/LICENSE b/asterisk/LICENSE new file mode 100644 index 00000000..77ff5bb1 --- /dev/null +++ b/asterisk/LICENSE @@ -0,0 +1,68 @@ +Asterisk is distributed under the GNU General Public License version 2 +and is also available under alternative licenses negotiated directly +with Digium, Inc. If you obtained Asterisk under the GPL, then the GPL +applies to all loadable Asterisk modules used on your system as well, +except as defined below. The GPL (version 2) is included in this +source tree in the file COPYING. + +This package also includes various components that are not part of +Asterisk itself; these components are in the 'contrib' directory +and its subdirectories. Most of these components are also +distributed under the GPL version 2 as well, except for the following: + +contrib/firmware/iax/iaxy.bin: + This file is Copyright (C) Digium, Inc. and is licensed for + use with Digium IAXy hardware devices only. It can be + distributed freely as long as the distribution is in the + original form present in this package (not reformatted or + modified). + +Digium, Inc. (formerly Linux Support Services) holds copyright +and/or sufficient licenses to all components of the Asterisk +package, and therefore can grant, at its sole discretion, the ability +for companies, individuals, or organizations to create proprietary or +Open Source (even if not GPL) modules which may be dynamically linked at +runtime with the portions of Asterisk which fall under our +copyright/license umbrella, or are distributed under more flexible +licenses than GPL. + +If you wish to use our code in other GPL programs, don't worry -- +there is no requirement that you provide the same exception in your +GPL'd products (although if you've written a module for Asterisk we +would strongly encourage you to make the same exception that we do). + +Specific permission is also granted to link Asterisk with OpenSSL, OpenH323 +and/or the UW IMAP Toolkit and distribute the resulting binary files. + +In addition, Asterisk implements two management/control protocols: the +Asterisk Manager Interface (AMI) and the Asterisk Gateway Interface +(AGI). It is our belief that applications using these protocols to +manage or control an Asterisk instance do not have to be licensed +under the GPL or a compatible license, as we believe these protocols +do not create a 'derivative work' as referred to in the GPL. However, +should any court or other judiciary body find that these protocols do +fall under the terms of the GPL, then we hereby grant you a license to +use these protocols in combination with Asterisk in external +applications licensed under any license you wish. + +The 'Asterisk' name and logos are trademarks owned by Digium, Inc., +and use of them is subject to our trademark licensing policies. If you +wish to use these trademarks for purposes other than simple +redistribution of Asterisk source code obtained from Digium, you +should contact our licensing department to determine the necessary +steps you must take. For more information on this policy, please read +http://www.digium.com/en/company/profile/trademarkpolicy.php + +If you have any questions regarding our licensing policy, please +contact us: + ++1.877.344.4861 (via telephone in the USA) ++1.256.428.6000 (via telephone outside the USA) ++1.256.864.0464 (via FAX inside or outside the USA) +IAX2/pbx.digium.com (via IAX2) +licensing@digium.com (via email) + +Digium, Inc. +445 Jan Davis Drive +Huntsville, AL 35806 +USA diff --git a/asterisk/Makefile b/asterisk/Makefile new file mode 100644 index 00000000..bb4adcb6 --- /dev/null +++ b/asterisk/Makefile @@ -0,0 +1,763 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Top level Makefile +# +# Copyright (C) 1999-2006, Digium, Inc. +# +# Mark Spencer +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +# All Makefiles use the following variables: +# +# ASTCFLAGS - compiler options +# ASTLDFLAGS - linker flags (not libraries) +# AST_LIBS - libraries to build binaries XXX +# LIBS - additional libraries, at top-level for all links, +# on a single object just for that object +# SOLINK - linker flags used only for creating shared objects (.so files), +# used for all .so links +# +# Default values fo ASTCFLAGS and ASTLDFLAGS can be specified in the +# environment when running make, as follows: +# +# $ ASTCFLAGS="-Werror" make + +export ASTTOPDIR +export ASTERISKVERSION +export ASTERISKVERSIONNUM +export INSTALL_PATH +export ASTETCDIR +export ASTVARRUNDIR +export MODULES_DIR +export ASTSPOOLDIR +export ASTVARLIBDIR +export ASTDATADIR +export ASTLOGDIR +export ASTLIBDIR +export ASTMANDIR +export ASTHEADERDIR +export ASTBINDIR +export ASTSBINDIR +export AGI_DIR +export ASTCONFPATH +export NOISY_BUILD +export MENUSELECT_CFLAGS +export CC +export CXX +export AR +export RANLIB +export HOST_CC +export STATIC_BUILD +export INSTALL +export DESTDIR +export PROC +export SOLINK +export STRIP +export DOWNLOAD +export AWK +export GREP +export ID +export OSARCH +export CURSES_DIR +export NCURSES_DIR +export TERMCAP_DIR +export TINFO_DIR +export GTK2_LIB +export GTK2_INCLUDE + +# even though we could use '-include makeopts' here, use a wildcard +# lookup anyway, so that make won't try to build makeopts if it doesn't +# exist (other rules will force it to be built if needed) +ifneq ($(wildcard makeopts),) + include makeopts +endif + +# Some build systems, such as the one in openwrt, like to pass custom target +# CFLAGS and LDFLAGS in the COPTS and LDOPTS variables. +ASTCFLAGS+=$(COPTS) +ASTLDFLAGS+=$(LDOPTS) + +#Uncomment this to see all build commands instead of 'quiet' output +#NOISY_BUILD=yes + +# Create OPTIONS variable +OPTIONS= + +empty:= +space:=$(empty) $(empty) +ASTTOPDIR:=$(subst $(space),\$(space),$(CURDIR)) + +# Overwite config files on "make samples" +OVERWRITE=y + +# Include debug and macro symbols in the executables (-g) and profiling info (-pg) +DEBUG=-g3 + +# Staging directory +# Files are copied here temporarily during the install process +# For example, make DESTDIR=/tmp/asterisk woud put things in +# /tmp/asterisk/etc/asterisk +# !!! Watch out, put no spaces or comments after the value !!! +#DESTDIR?=/tmp/asterisk + +# Define standard directories for various platforms +# These apply if they are not redefined in asterisk.conf +ifeq ($(OSARCH),SunOS) + ASTETCDIR=/var/etc/asterisk + ASTLIBDIR=/opt/asterisk/lib + ASTVARLIBDIR=/var/opt/asterisk + ASTSPOOLDIR=/var/spool/asterisk + ASTLOGDIR=/var/log/asterisk + ASTHEADERDIR=/opt/asterisk/include + ASTBINDIR=/opt/asterisk/bin + ASTSBINDIR=/opt/asterisk/sbin + ASTVARRUNDIR=/var/run/asterisk + ASTMANDIR=/opt/asterisk/man +else + ASTETCDIR=$(sysconfdir)/asterisk + ASTLIBDIR=$(libdir)/asterisk + ASTHEADERDIR=$(includedir)/asterisk + ASTBINDIR=$(bindir) + ASTSBINDIR=$(sbindir) + ASTSPOOLDIR=$(localstatedir)/spool/asterisk + ASTLOGDIR=$(localstatedir)/log/asterisk + ASTVARRUNDIR=$(localstatedir)/run + ASTMANDIR=$(mandir) +ifneq ($(findstring BSD,$(OSARCH)),) + ASTVARLIBDIR=$(prefix)/share/asterisk + ASTVARRUNDIR=$(localstatedir)/run/asterisk +else + ASTVARLIBDIR=$(localstatedir)/lib/asterisk +endif +endif +ifeq ($(ASTDATADIR),) + ASTDATADIR:=$(ASTVARLIBDIR) +endif + +# Asterisk.conf is located in ASTETCDIR or by using the -C flag +# when starting Asterisk +ASTCONFPATH=$(ASTETCDIR)/asterisk.conf +MODULES_DIR=$(ASTLIBDIR)/modules +AGI_DIR=$(ASTDATADIR)/agi-bin + +# If you use Apache, you may determine by a grep 'DocumentRoot' of your httpd.conf file +HTTP_DOCSDIR=/var/www/html +# Determine by a grep 'ScriptAlias' of your Apache httpd.conf file +HTTP_CGIDIR=/var/www/cgi-bin + +# Uncomment this to use the older DSP routines +#ASTCFLAGS+=-DOLD_DSP_ROUTINES + +# If the file .asterisk.makeopts is present in your home directory, you can +# include all of your favorite menuselect options so that every time you download +# a new version of Asterisk, you don't have to run menuselect to set them. +# The file /etc/asterisk.makeopts will also be included but can be overridden +# by the file in your home directory. + +GLOBAL_MAKEOPTS=$(wildcard /etc/asterisk.makeopts) +USER_MAKEOPTS=$(wildcard ~/.asterisk.makeopts) + +MOD_SUBDIR_CFLAGS=-I$(ASTTOPDIR)/include +OTHER_SUBDIR_CFLAGS=-I$(ASTTOPDIR)/include + +ifeq ($(OSARCH),linux-gnu) + ifeq ($(PROC),x86_64) + # You must have GCC 3.4 to use k8, otherwise use athlon + PROC=k8 + #PROC=athlon + endif + + ifeq ($(PROC),sparc64) + #The problem with sparc is the best stuff is in newer versions of gcc (post 3.0) only. + #This works for even old (2.96) versions of gcc and provides a small boost either way. + #A ultrasparc cpu is really v9 but the stock debian stable 3.0 gcc doesn't support it. + #So we go lowest common available by gcc and go a step down, still a step up from + #the default as we now have a better instruction set to work with. - Belgarath + PROC=ultrasparc + OPTIONS+=$(shell if $(CC) -mtune=$(PROC) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-mtune=$(PROC)"; fi) + OPTIONS+=$(shell if $(CC) -mcpu=v8 -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-mcpu=v8"; fi) + OPTIONS+=-fomit-frame-pointer + endif + + ifeq ($(PROC),arm) + # The Cirrus logic is the only heavily shipping arm processor with a real floating point unit + ifeq ($(SUB_PROC),maverick) + OPTIONS+=-fsigned-char -mcpu=ep9312 + else + ifeq ($(SUB_PROC),xscale) + OPTIONS+=-fsigned-char -mcpu=xscale + else + OPTIONS+=-fsigned-char + endif + endif + endif +endif + +ifeq ($(findstring -save-temps,$(ASTCFLAGS)),) +ASTCFLAGS+=-pipe +endif + +ASTCFLAGS+=-Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations $(DEBUG) + +ifeq ($(AST_DEVMODE),yes) + ASTCFLAGS+=-Werror -Wunused $(AST_DECLARATION_AFTER_STATEMENT) +endif + +ifneq ($(findstring BSD,$(OSARCH)),) + ASTCFLAGS+=-I/usr/local/include + ASTLDFLAGS+=-L/usr/local/lib +endif + +ifneq ($(PROC),ultrasparc) + ASTCFLAGS+=$(shell if $(CC) -march=$(PROC) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-march=$(PROC)"; fi) +endif + +ifeq ($(PROC),ppc) + ASTCFLAGS+=-fsigned-char +endif + +ifeq ($(OSARCH),FreeBSD) + # -V is understood by BSD Make, not by GNU make. + BSDVERSION=$(shell make -V OSVERSION -f /usr/share/mk/bsd.port.subdir.mk) + ASTCFLAGS+=$(shell if test $(BSDVERSION) -lt 500016 ; then echo "-D_THREAD_SAFE"; fi) + AST_LIBS+=$(shell if test $(BSDVERSION) -lt 502102 ; then echo "-lc_r"; else echo "-pthread"; fi) +endif + +ifeq ($(OSARCH),NetBSD) + ASTCFLAGS+=-pthread -I/usr/pkg/include +endif + +ifeq ($(OSARCH),OpenBSD) + ASTCFLAGS+=-pthread +endif + +ifeq ($(OSARCH),SunOS) + ASTCFLAGS+=-Wcast-align -DSOLARIS -I../include/solaris-compat -I/opt/ssl/include -I/usr/local/ssl/include +endif + +ASTERISKVERSION:=$(shell GREP=$(GREP) AWK=$(AWK) build_tools/make_version .) + +ifneq ($(wildcard .version),) + ASTERISKVERSIONNUM:=$(shell $(AWK) -F. '{printf "%01d%02d%02d", $$1, $$2, $$3}' .version) +endif + +ifneq ($(wildcard .svn),) + ASTERISKVERSIONNUM=999999 +endif + +ASTCFLAGS+=$(MALLOC_DEBUG)$(BUSYDETECT)$(OPTIONS) + +MOD_SUBDIRS:=res channels pbx apps codecs formats cdr funcs main +OTHER_SUBDIRS:=utils agi +SUBDIRS:=$(OTHER_SUBDIRS) $(MOD_SUBDIRS) +SUBDIRS_INSTALL:=$(SUBDIRS:%=%-install) +SUBDIRS_CLEAN:=$(SUBDIRS:%=%-clean) +SUBDIRS_DIST_CLEAN:=$(SUBDIRS:%=%-dist-clean) +SUBDIRS_UNINSTALL:=$(SUBDIRS:%=%-uninstall) +MOD_SUBDIRS_EMBED_LDSCRIPT:=$(MOD_SUBDIRS:%=%-embed-ldscript) +MOD_SUBDIRS_EMBED_LDFLAGS:=$(MOD_SUBDIRS:%=%-embed-ldflags) +MOD_SUBDIRS_EMBED_LIBS:=$(MOD_SUBDIRS:%=%-embed-libs) +MOD_SUBDIRS_MENUSELECT_TREE:=$(MOD_SUBDIRS:%=%-menuselect-tree) + +ifneq ($(findstring darwin,$(OSARCH)),) + ASTCFLAGS+=-D__Darwin__ + AUDIO_LIBS=-framework CoreAudio + SOLINK=-dynamic -bundle -undefined suppress -force_flat_namespace +else +# These are used for all but Darwin + SOLINK=-shared -Xlinker -x + ifneq ($(findstring BSD,$(OSARCH)),) + LDFLAGS+=-L/usr/local/lib + endif +endif + +ifeq ($(OSARCH),SunOS) + SOLINK=-shared -fpic -L/usr/local/ssl/lib -lrt +endif + +SUBMAKE=$(MAKE) --quiet --no-print-directory + +# This is used when generating the doxygen documentation +ifneq ($(DOT),:) + HAVEDOT=yes +else + HAVEDOT=no +endif + +all: _all + @echo " +--------- Asterisk Build Complete ---------+" + @echo " + Asterisk has successfully been built, and +" + @echo " + can be installed by running: +" + @echo " + +" +ifeq ($(MAKE), gmake) + @echo " + $(MAKE) install +" +else + @echo " + $(MAKE) install +" +endif + @echo " +-------------------------------------------+" + +_all: cleantest $(SUBDIRS) + +makeopts: configure + @echo "****" + @echo "**** The configure script must be executed before running '$(MAKE)'." + @echo "**** Please run \"./configure\"." + @echo "****" + @exit 1 + +menuselect.makeopts: menuselect/menuselect menuselect-tree + menuselect/menuselect --check-deps menuselect.makeopts $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) + +$(MOD_SUBDIRS_EMBED_LDSCRIPT): + @echo "EMBED_LDSCRIPTS+="`$(SUBMAKE) -C $(@:-embed-ldscript=) SUBDIR=$(@:-embed-ldscript=) __embed_ldscript` >> makeopts.embed_rules + +$(MOD_SUBDIRS_EMBED_LDFLAGS): + @echo "EMBED_LDFLAGS+="`$(SUBMAKE) -C $(@:-embed-ldflags=) SUBDIR=$(@:-embed-ldflags=) __embed_ldflags` >> makeopts.embed_rules + +$(MOD_SUBDIRS_EMBED_LIBS): + @echo "EMBED_LIBS+="`$(SUBMAKE) -C $(@:-embed-libs=) SUBDIR=$(@:-embed-libs=) __embed_libs` >> makeopts.embed_rules + +$(MOD_SUBDIRS_MENUSELECT_TREE): + @$(SUBMAKE) -C $(@:-menuselect-tree=) SUBDIR=$(@:-menuselect-tree=) moduleinfo + @$(SUBMAKE) -C $(@:-menuselect-tree=) SUBDIR=$(@:-menuselect-tree=) makeopts + +makeopts.embed_rules: menuselect.makeopts + @echo "Generating embedded module rules ..." + @rm -f $@ + @$(MAKE) --no-print-directory $(MOD_SUBDIRS_EMBED_LDSCRIPT) + @$(MAKE) --no-print-directory $(MOD_SUBDIRS_EMBED_LDFLAGS) + @$(MAKE) --no-print-directory $(MOD_SUBDIRS_EMBED_LIBS) + +$(SUBDIRS): include/asterisk/version.h include/asterisk/buildopts.h defaults.h makeopts.embed_rules + +# ensure that all module subdirectories are processed before 'main' during +# a parallel build, since if there are modules selected to be embedded the +# directories containing them must be completed before the main Asterisk +# binary can be built +main: $(filter-out main,$(MOD_SUBDIRS)) + +$(MOD_SUBDIRS): + @ASTCFLAGS="$(MOD_SUBDIR_CFLAGS) $(ASTCFLAGS)" ASTLDFLAGS="$(ASTLDFLAGS)" AST_LIBS="$(AST_LIBS)" $(MAKE) --no-builtin-rules -C $@ SUBDIR=$@ all + +$(OTHER_SUBDIRS): + @ASTCFLAGS="$(OTHER_SUBDIR_CFLAGS) $(ASTCFLAGS)" ASTLDFLAGS="$(ASTLDFLAGS)" AUDIO_LIBS="$(AUDIO_LIBS)" $(MAKE) --no-builtin-rules -C $@ SUBDIR=$@ all + +defaults.h: makeopts + @build_tools/make_defaults_h > $@.tmp + @if cmp -s $@.tmp $@ ; then : ; else \ + mv $@.tmp $@ ; \ + fi + @rm -f $@.tmp + +include/asterisk/version.h: FORCE + @build_tools/make_version_h > $@.tmp + @if cmp -s $@.tmp $@ ; then : ; else \ + mv $@.tmp $@ ; \ + fi + @rm -f $@.tmp + +include/asterisk/buildopts.h: menuselect.makeopts + @build_tools/make_buildopts_h > $@.tmp + @if cmp -s $@.tmp $@ ; then : ; else \ + mv $@.tmp $@ ; \ + fi + @rm -f $@.tmp + +$(SUBDIRS_CLEAN): + @$(MAKE) --no-print-directory -C $(@:-clean=) clean + +$(SUBDIRS_DIST_CLEAN): + @$(MAKE) --no-print-directory -C $(@:-dist-clean=) dist-clean + +clean: $(SUBDIRS_CLEAN) + rm -f defaults.h + rm -f include/asterisk/build.h + rm -f include/asterisk/version.h + @$(MAKE) -C menuselect clean + cp -f .cleancount .lastclean + +dist-clean: distclean + +distclean: $(SUBDIRS_DIST_CLEAN) clean + @$(MAKE) -C menuselect dist-clean + @$(MAKE) -C sounds dist-clean + rm -f menuselect.makeopts makeopts menuselect-tree menuselect.makedeps + rm -f makeopts.embed_rules + rm -f config.log config.status config.cache + rm -rf autom4te.cache + rm -f include/asterisk/autoconfig.h + rm -f include/asterisk/buildopts.h + rm -rf doc/api + rm -f build_tools/menuselect-deps + +datafiles: _all + if [ x`$(ID) -un` = xroot ]; then CFLAGS="$(ASTCFLAGS)" sh build_tools/mkpkgconfig $(DESTDIR)/usr/lib/pkgconfig; fi +# Should static HTTP be installed during make samples or even with its own target ala +# webvoicemail? There are portions here that *could* be customized but might also be +# improved a lot. I'll put it here for now. + mkdir -p $(DESTDIR)$(ASTDATADIR)/static-http + for x in static-http/*; do \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/static-http ; \ + done + mkdir -p $(DESTDIR)$(ASTDATADIR)/images + for x in images/*.jpg; do \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/images ; \ + done + mkdir -p $(DESTDIR)$(AGI_DIR) + $(MAKE) -C sounds install + +update: + @if [ -d .svn ]; then \ + echo "Updating from Subversion..." ; \ + svn update | tee update.out; \ + rm -f .version; \ + if [ `grep -c ^C update.out` -gt 0 ]; then \ + echo ; echo "The following files have conflicts:" ; \ + grep ^C update.out | cut -b4- ; \ + fi ; \ + rm -f update.out; \ + else \ + echo "Not under version control"; \ + fi + +NEWHEADERS=$(notdir $(wildcard include/asterisk/*.h)) +OLDHEADERS=$(filter-out $(NEWHEADERS),$(notdir $(wildcard $(DESTDIR)$(ASTHEADERDIR)/*.h))) + +installdirs: + mkdir -p $(DESTDIR)$(MODULES_DIR) + mkdir -p $(DESTDIR)$(ASTSBINDIR) + mkdir -p $(DESTDIR)$(ASTETCDIR) + mkdir -p $(DESTDIR)$(ASTBINDIR) + mkdir -p $(DESTDIR)$(ASTVARRUNDIR) + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/voicemail + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/dictate + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/system + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/tmp + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/meetme + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/monitor + +bininstall: _all installdirs $(SUBDIRS_INSTALL) + $(INSTALL) -m 755 main/asterisk $(DESTDIR)$(ASTSBINDIR)/ + $(LN) -sf asterisk $(DESTDIR)$(ASTSBINDIR)/rasterisk + $(INSTALL) -m 755 contrib/scripts/astgenkey $(DESTDIR)$(ASTSBINDIR)/ + $(INSTALL) -m 755 contrib/scripts/autosupport $(DESTDIR)$(ASTSBINDIR)/ + if [ ! -f $(DESTDIR)$(ASTSBINDIR)/safe_asterisk ]; then \ + cat contrib/scripts/safe_asterisk | sed 's|__ASTERISK_SBIN_DIR__|$(ASTSBINDIR)|;s|__ASTERISK_VARRUN_DIR__|$(ASTVARRUNDIR)|;' > $(DESTDIR)$(ASTSBINDIR)/safe_asterisk ;\ + chmod 755 $(DESTDIR)$(ASTSBINDIR)/safe_asterisk;\ + fi + $(INSTALL) -d $(DESTDIR)$(ASTHEADERDIR) + $(INSTALL) -m 644 include/asterisk.h $(DESTDIR)$(includedir) + $(INSTALL) -m 644 include/asterisk/*.h $(DESTDIR)$(ASTHEADERDIR) + if [ -n "$(OLDHEADERS)" ]; then \ + rm -f $(addprefix $(DESTDIR)$(ASTHEADERDIR)/,$(OLDHEADERS)) ;\ + fi + mkdir -p $(DESTDIR)$(ASTLOGDIR)/cdr-csv + mkdir -p $(DESTDIR)$(ASTLOGDIR)/cdr-custom + mkdir -p $(DESTDIR)$(ASTDATADIR)/keys + mkdir -p $(DESTDIR)$(ASTDATADIR)/firmware + mkdir -p $(DESTDIR)$(ASTDATADIR)/firmware/iax + mkdir -p $(DESTDIR)$(ASTMANDIR)/man8 + $(INSTALL) -m 644 keys/iaxtel.pub $(DESTDIR)$(ASTDATADIR)/keys + $(INSTALL) -m 644 keys/freeworlddialup.pub $(DESTDIR)$(ASTDATADIR)/keys + $(INSTALL) -m 644 doc/asterisk.8 $(DESTDIR)$(ASTMANDIR)/man8 + $(INSTALL) -m 644 contrib/scripts/astgenkey.8 $(DESTDIR)$(ASTMANDIR)/man8 + $(INSTALL) -m 644 contrib/scripts/autosupport.8 $(DESTDIR)$(ASTMANDIR)/man8 + $(INSTALL) -m 644 contrib/scripts/safe_asterisk.8 $(DESTDIR)$(ASTMANDIR)/man8 + if [ -f contrib/firmware/iax/iaxy.bin ] ; then \ + $(INSTALL) -m 644 contrib/firmware/iax/iaxy.bin $(DESTDIR)$(ASTDATADIR)/firmware/iax/iaxy.bin; \ + fi + +$(SUBDIRS_INSTALL): + @DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" $(MAKE) -C $(@:-install=) install + +NEWMODS=$(notdir $(wildcard */*.so)) +DEPMODS=chan_zap.so app_zapras.so app_zapscan.so app_zapbarge.so codec_zap.so +OLDMODS=$(filter-out $(NEWMODS) $(DEPMODS),$(notdir $(wildcard $(DESTDIR)$(MODULES_DIR)/*.so))) + +oldmodcheck: + @for f in $(DEPMODS); do rm -f $(DESTDIR)$(MODULES_DIR)/$$f; done + @if [ -n "$(OLDMODS)" ]; then \ + echo " WARNING WARNING WARNING" ;\ + echo "" ;\ + echo " Your Asterisk modules directory, located at" ;\ + echo " $(DESTDIR)$(MODULES_DIR)" ;\ + echo " contains modules that were not installed by this " ;\ + echo " version of Asterisk. Please ensure that these" ;\ + echo " modules are compatible with this version before" ;\ + echo " attempting to run Asterisk." ;\ + echo "" ;\ + for f in $(OLDMODS); do \ + echo " $$f" ;\ + done ;\ + echo "" ;\ + echo " WARNING WARNING WARNING" ;\ + fi + +badshell: +ifneq ($(findstring ~,$(DESTDIR)),) + @echo "Your shell doesn't do ~ expansion when expected (specifically, when doing \"make install DESTDIR=~/path\")." + @echo "Try replacing ~ with \$$HOME, as in \"make install DESTDIR=\$$HOME/path\"." + @exit 1 +endif + +install: badshell datafiles bininstall + @if [ -x /usr/sbin/asterisk-post-install ]; then \ + /usr/sbin/asterisk-post-install $(DESTDIR) . ; \ + fi + @echo " +---- Asterisk Installation Complete -------+" + @echo " + +" + @echo " + YOU MUST READ THE SECURITY DOCUMENT +" + @echo " + +" + @echo " + Asterisk has successfully been installed. +" + @echo " + If you would like to install the sample +" + @echo " + configuration files (overwriting any +" + @echo " + existing config files), run: +" + @echo " + +" +ifeq ($(MAKE), gmake) + @echo " + $(MAKE) samples +" +else + @echo " + $(MAKE) samples +" +endif + @echo " + +" + @echo " +----------------- or ---------------------+" + @echo " + +" + @echo " + You can go ahead and install the asterisk +" + @echo " + program documentation now or later run: +" + @echo " + +" +ifeq ($(MAKE), gmake) + @echo " + $(MAKE) progdocs +" +else + @echo " + $(MAKE) progdocs +" +endif + @echo " + +" + @echo " + **Note** This requires that you have +" + @echo " + doxygen installed on your local system +" + @echo " +-------------------------------------------+" + @$(MAKE) -s oldmodcheck + +upgrade: bininstall + +adsi: + mkdir -p $(DESTDIR)$(ASTETCDIR) + for x in configs/*.adsi; do \ + if [ ! -f $(DESTDIR)$(ASTETCDIR)/$$x ]; then \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x` ; \ + fi ; \ + done + +samples: adsi + mkdir -p $(DESTDIR)$(ASTETCDIR) + for x in configs/*.sample; do \ + if [ -f $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample` ]; then \ + if [ "$(OVERWRITE)" = "y" ]; then \ + if cmp -s $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample` $$x ; then \ + echo "Config file $$x is unchanged"; \ + continue; \ + fi ; \ + mv -f $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample` $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample`.old ; \ + else \ + echo "Skipping config file $$x"; \ + continue; \ + fi ;\ + fi ; \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample` ;\ + done + if [ "$(OVERWRITE)" = "y" ] || [ ! -f $(DESTDIR)$(ASTCONFPATH) ]; then \ + ( \ + echo "[directories]" ; \ + echo "astetcdir => $(ASTETCDIR)" ; \ + echo "astmoddir => $(MODULES_DIR)" ; \ + echo "astvarlibdir => $(ASTVARLIBDIR)" ; \ + echo "astdatadir => $(ASTDATADIR)" ; \ + echo "astagidir => $(AGI_DIR)" ; \ + echo "astspooldir => $(ASTSPOOLDIR)" ; \ + echo "astrundir => $(ASTVARRUNDIR)" ; \ + echo "astlogdir => $(ASTLOGDIR)" ; \ + echo "" ; \ + echo "[options]" ; \ + echo "languageprefix = yes ; Use the new sound prefix path syntax" ; \ + echo ";verbose = 3" ; \ + echo ";debug = 3" ; \ + echo ";alwaysfork = yes ; same as -F at startup" ; \ + echo ";nofork = yes ; same as -f at startup" ; \ + echo ";quiet = yes ; same as -q at startup" ; \ + echo ";timestamp = yes ; same as -T at startup" ; \ + echo ";execincludes = yes ; support #exec in config files" ; \ + echo ";console = yes ; Run as console (same as -c at startup)" ; \ + echo ";highpriority = yes ; Run realtime priority (same as -p at startup)" ; \ + echo ";initcrypto = yes ; Initialize crypto keys (same as -i at startup)" ; \ + echo ";nocolor = yes ; Disable console colors" ; \ + echo ";dontwarn = yes ; Disable some warnings" ; \ + echo ";dumpcore = yes ; Dump core on crash (same as -g at startup)" ; \ + echo ";internal_timing = yes" ; \ + echo ";systemname = my_system_name ; prefix uniqueid with a system name for global uniqueness issues" ; \ + echo ";maxcalls = 10 ; Maximum amount of calls allowed" ; \ + echo ";maxload = 0.9 ; Asterisk stops accepting new calls if the load average exceed this limit" ; \ + echo ";cache_record_files = yes ; Cache recorded sound files to another directory during recording" ; \ + echo ";record_cache_dir = /tmp ; Specify cache directory (used in cnjunction with cache_record_files)" ; \ + echo ";transmit_silence_during_record = yes ; Transmit SLINEAR silence while a channel is being recorded" ; \ + echo ";transmit_silence = yes ; Transmit SLINEAR silence while a channel is being recorded or DTMF is being generated" ; \ + echo ";transcode_via_sln = yes ; Build transcode paths via SLINEAR, instead of directly" ; \ + echo ";runuser = asterisk ; The user to run as" ; \ + echo ";rungroup = asterisk ; The group to run as" ; \ + echo ";dahdichanname = yes ; Channels created by chan_dahdi will be called 'DAHDI', otherwise 'Zap'" ; \ + echo "" ; \ + echo "; Changing the following lines may compromise your security." ; \ + echo ";[files]" ; \ + echo ";astctlpermissions = 0660" ; \ + echo ";astctlowner = root" ; \ + echo ";astctlgroup = apache" ; \ + echo ";astctl = asterisk.ctl" ; \ + ) > $(DESTDIR)$(ASTCONFPATH) ; \ + else \ + echo "Skipping asterisk.conf creation"; \ + fi + mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/voicemail/default/1234/INBOX + build_tools/make_sample_voicemail $(DESTDIR)/$(ASTDATADIR) $(DESTDIR)/$(ASTSPOOLDIR) + +webvmail: + @[ -d $(DESTDIR)$(HTTP_DOCSDIR)/ ] || ( printf "http docs directory not found.\nUpdate assignment of variable HTTP_DOCSDIR in Makefile!\n" && exit 1 ) + @[ -d $(DESTDIR)$(HTTP_CGIDIR) ] || ( printf "cgi-bin directory not found.\nUpdate assignment of variable HTTP_CGIDIR in Makefile!\n" && exit 1 ) + $(INSTALL) -m 4755 -o root -g root contrib/scripts/vmail.cgi $(DESTDIR)$(HTTP_CGIDIR)/vmail.cgi + mkdir -p $(DESTDIR)$(HTTP_DOCSDIR)/_asterisk + for x in images/*.gif; do \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(HTTP_DOCSDIR)/_asterisk/; \ + done + @echo " +--------- Asterisk Web Voicemail ----------+" + @echo " + +" + @echo " + Asterisk Web Voicemail is installed in +" + @echo " + your cgi-bin directory: +" + @echo " + $(DESTDIR)$(HTTP_CGIDIR)" + @echo " + IT USES A SETUID ROOT PERL SCRIPT, SO +" + @echo " + IF YOU DON'T LIKE THAT, UNINSTALL IT! +" + @echo " + +" + @echo " + Other static items have been stored in: +" + @echo " + $(DESTDIR)$(HTTP_DOCSDIR)" + @echo " + +" + @echo " + If these paths do not match your httpd +" + @echo " + installation, correct the definitions +" + @echo " + in your Makefile of HTTP_CGIDIR and +" + @echo " + HTTP_DOCSDIR +" + @echo " + +" + @echo " +-------------------------------------------+" + +progdocs: + (cat contrib/asterisk-ng-doxygen; echo "HAVE_DOT=$(HAVEDOT)"; \ + echo "PROJECT_NUMBER=$(ASTERISKVERSION)") | doxygen - + +config: + @if [ "${OSARCH}" = "linux-gnu" ]; then \ + if [ -f /etc/redhat-release -o -f /etc/fedora-release ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.redhat.asterisk $(DESTDIR)/etc/rc.d/init.d/asterisk; \ + if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \ + elif [ -f /etc/debian_version ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.debian.asterisk $(DESTDIR)/etc/init.d/asterisk; \ + if [ -z "$(DESTDIR)" ]; then /usr/sbin/update-rc.d asterisk start 50 2 3 4 5 . stop 91 2 3 4 5 .; fi; \ + elif [ -f /etc/gentoo-release ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.gentoo.asterisk $(DESTDIR)/etc/init.d/asterisk; \ + if [ -z "$(DESTDIR)" ]; then /sbin/rc-update add asterisk default; fi; \ + elif [ -f /etc/mandrake-release -o -f /etc/mandriva-release ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.mandrake.asterisk $(DESTDIR)/etc/rc.d/init.d/asterisk; \ + if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \ + elif [ -f /etc/SuSE-release -o -f /etc/novell-release ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.suse.asterisk $(DESTDIR)/etc/init.d/asterisk; \ + if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \ + elif [ -f /etc/slackware-version ]; then \ + echo "Slackware is not currently supported, although an init script does exist for it." \ + else \ + echo "We could not install init scripts for your distribution."; \ + fi \ + else \ + echo "We could not install init scripts for your operating system."; \ + fi + +sounds: + $(MAKE) -C sounds all + +# If the cleancount has been changed, force a make clean. +# .cleancount is the global clean count, and .lastclean is the +# last clean count we had + +cleantest: + @cmp -s .cleancount .lastclean || $(MAKE) clean + +$(SUBDIRS_UNINSTALL): + @$(MAKE) --no-print-directory -C $(@:-uninstall=) uninstall + +_uninstall: $(SUBDIRS_UNINSTALL) + rm -f $(DESTDIR)$(MODULES_DIR)/* + rm -f $(DESTDIR)$(ASTSBINDIR)/*asterisk* + rm -f $(DESTDIR)$(ASTSBINDIR)/astgenkey + rm -f $(DESTDIR)$(ASTSBINDIR)/autosupport + rm -rf $(DESTDIR)$(ASTHEADERDIR) + rm -rf $(DESTDIR)$(ASTDATADIR)/firmware + rm -f $(DESTDIR)$(ASTMANDIR)/man8/asterisk.8 + rm -f $(DESTDIR)$(ASTMANDIR)/man8/astgenkey.8 + rm -f $(DESTDIR)$(ASTMANDIR)/man8/autosupport.8 + rm -f $(DESTDIR)$(ASTMANDIR)/man8/safe_asterisk.8 + $(MAKE) -C sounds uninstall + +uninstall: _uninstall + @echo " +--------- Asterisk Uninstall Complete -----+" + @echo " + Asterisk binaries, sounds, man pages, +" + @echo " + headers, modules, and firmware builds, +" + @echo " + have all been uninstalled. +" + @echo " + +" + @echo " + To remove ALL traces of Asterisk, +" + @echo " + including configuration, spool +" + @echo " + directories, and logs, run the following +" + @echo " + command: +" + @echo " + +" +ifeq ($(MAKE), gmake) + @echo " + $(MAKE) uninstall-all +" +else + @echo " + $(MAKE) uninstall-all +" +endif + @echo " +-------------------------------------------+" + +uninstall-all: _uninstall + rm -rf $(DESTDIR)$(ASTLIBDIR) + rm -rf $(DESTDIR)$(ASTVARLIBDIR) + rm -rf $(DESTDIR)$(ASTDATADIR) + rm -rf $(DESTDIR)$(ASTSPOOLDIR) + rm -rf $(DESTDIR)$(ASTETCDIR) + rm -rf $(DESTDIR)$(ASTLOGDIR) + +menuconfig: menuselect + +gmenuconfig: gmenuselect + +menuselect: menuselect/menuselect menuselect-tree + -@menuselect/menuselect menuselect.makeopts $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) && (echo "menuselect changes saved!"; rm -f channels/h323/Makefile.ast main/asterisk) || echo "menuselect changes NOT saved!" + +gmenuselect: menuselect/gmenuselect menuselect-tree + -@menuselect/gmenuselect menuselect.makeopts $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) && (echo "menuselect changes saved!"; rm -f channels/h323/Makefile.ast main/asterisk) || echo "menuselect changes NOT saved!" + +menuselect/menuselect: makeopts menuselect/menuselect.c menuselect/menuselect_curses.c menuselect/menuselect_stub.c menuselect/menuselect.h menuselect/linkedlists.h makeopts + @CC="$(HOST_CC)" LD="" AR="" RANLIB="" CFLAGS="" $(MAKE) -C menuselect CONFIGURE_SILENT="--silent" + +menuselect/gmenuselect: makeopts menuselect/menuselect.c menuselect/menuselect_gtk.c menuselect/menuselect_stub.c menuselect/menuselect.h menuselect/linkedlists.h makeopts + @CC="$(HOST_CC)" CXX="$(CXX)" LD="" AR="" RANLIB="" CFLAGS="" $(MAKE) -C menuselect _gmenuselect CONFIGURE_SILENT="--silent" + +menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(dir)/*.c) $(wildcard $(dir)/*.cc)) build_tools/cflags.xml build_tools/cflags-devmode.xml sounds/sounds.xml build_tools/embed_modules.xml configure + @echo "Generating input for menuselect ..." + @echo "" > $@ + @echo >> $@ + @echo "" >> $@ + @for dir in $(sort $(filter-out main,$(MOD_SUBDIRS))); do $(SUBMAKE) -C $${dir} SUBDIR=$${dir} moduleinfo >> $@; done + @for dir in $(sort $(filter-out main,$(MOD_SUBDIRS))); do $(SUBMAKE) -C $${dir} SUBDIR=$${dir} makeopts >> $@; done + @cat build_tools/cflags.xml >> $@ + @if [ "${AST_DEVMODE}" = "yes" ]; then \ + cat build_tools/cflags-devmode.xml >> $@; \ + fi + @cat build_tools/embed_modules.xml >> $@ + @cat sounds/sounds.xml >> $@ + @echo "" >> $@ + +.PHONY: menuselect main sounds clean dist-clean distclean all prereqs cleantest uninstall _uninstall uninstall-all dont-optimize $(SUBDIRS_INSTALL) $(SUBDIRS_DIST_CLEAN) $(SUBDIRS_CLEAN) $(SUBDIRS_UNINSTALL) $(SUBDIRS) $(MOD_SUBDIRS_EMBED_LDSCRIPT) $(MOD_SUBDIRS_EMBED_LDFLAGS) $(MOD_SUBDIRS_EMBED_LIBS) badshell menuselect.makeopts installdirs + +FORCE: diff --git a/asterisk/Makefile.moddir_rules b/asterisk/Makefile.moddir_rules new file mode 100644 index 00000000..eebad80e --- /dev/null +++ b/asterisk/Makefile.moddir_rules @@ -0,0 +1,112 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# 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 +# + +ifeq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),) + ASTCFLAGS+=${GC_CFLAGS} +endif + +ifneq ($(findstring STATIC_BUILD,$(MENUSELECT_CFLAGS)),) + STATIC_BUILD=-static +endif + +include $(ASTTOPDIR)/Makefile.rules + +comma:=, + +$(addsuffix .o,$(C_MODS)): ASTCFLAGS+=-DAST_MODULE=\"$*\" $(MENUSELECT_OPTS_$*:%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_INCLUDE)) +$(addsuffix .oo,$(CC_MODS)): ASTCFLAGS+=-DAST_MODULE=\"$*\" $(MENUSELECT_OPTS_$*:%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_INCLUDE)) + +$(LOADABLE_MODS:%=%.so): ASTCFLAGS+=-fPIC +$(LOADABLE_MODS:%=%.so): LIBS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LIB)) +$(LOADABLE_MODS:%=%.so): ASTLDFLAGS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LDFLAGS)) + +$(addsuffix .so,$(filter $(LOADABLE_MODS),$(C_MODS))): %.so: %.o +$(addsuffix .so,$(filter $(LOADABLE_MODS),$(CC_MODS))): %.so: %.oo + +modules.link: $(addsuffix .o,$(filter $(EMBEDDED_MODS),$(C_MODS))) +modules.link: $(addsuffix .oo,$(filter $(EMBEDDED_MODS),$(CC_MODS))) + +.PHONY: clean uninstall _all moduleinfo makeopts + +ifneq ($(LOADABLE_MODS),) +_all: $(LOADABLE_MODS:%=%.so) +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 %.o,$^)); do echo "INPUT (../$${file})" >> $@; done + @for file in $(patsubst %,$(SUBDIR)/%,$(filter-out %.o,$^)); do echo "INPUT (../$${file})" >> $@; done + +clean:: + rm -f *.so *.o *.oo *.s *.i + rm -f .*.o.d .*.oo.d + rm -f modules.link + +install:: all + for x in $(LOADABLE_MODS:%=%.so); do $(INSTALL) -m 755 $$x $(DESTDIR)$(MODULES_DIR) ; done + +uninstall:: + +dist-clean:: + rm -f .*.moduleinfo .moduleinfo + rm -f .*.makeopts .makeopts + +.%.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 .,$(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 diff --git a/asterisk/Makefile.rules b/asterisk/Makefile.rules new file mode 100644 index 00000000..5bad2a1c --- /dev/null +++ b/asterisk/Makefile.rules @@ -0,0 +1,81 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Makefile rules +# +# Copyright (C) 2006, Digium, Inc. +# +# Kevin P. Fleming +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +# Each command is preceded by a short comment on what to do. +# Prefixing one or the other with @\# or @ or nothing makes the desired +# behaviour. ECHO_PREFIX prefixes the comment, CMD_PREFIX prefixes the command. + +-include $(ASTTOPDIR)/makeopts + +.PHONY: dist-clean + +ifeq ($(NOISY_BUILD),) + ECHO_PREFIX=@ + CMD_PREFIX=@ +else + ECHO_PREFIX=@\# + CMD_PREFIX= +endif + +ifeq ($(findstring DONT_OPTIMIZE,$(MENUSELECT_CFLAGS)),) +# More GSM codec optimization +# Uncomment to enable MMXTM optimizations for x86 architecture CPU's +# which support MMX instructions. This should be newer pentiums, +# ppro's, etc, as well as the AMD K6 and K7. +#K6OPT=-DK6OPT + +OPTIMIZE?=-O6 +ASTCFLAGS+=$(OPTIMIZE) +endif + +%.o: %.c + $(ECHO_PREFIX) echo " [CC] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) -MD -MT $@ -MF .$(subst /,_,$@).d -MP + +%.o: %.i + $(ECHO_PREFIX) echo " [CCi] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) -MD -MT $@ -MF .$(subst /,_,$@).d -MP + +%.i: %.c + $(ECHO_PREFIX) echo " [CPP] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -E $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) -MD -MT $@ -MF .$(subst /,_,$@).d -MP + +%.o: %.s + $(ECHO_PREFIX) echo " [AS] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) -MD -MT $@ -MF .$(subst /,_,$@).d -MP + +%.oo: %.cc + $(ECHO_PREFIX) echo " [CXX] $< -> $@" + $(CMD_PREFIX) $(CXX) -o $@ -c $< $(PTHREAD_CFLAGS) $(filter-out -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations $(AST_DECLARATION_AFTER_STATEMENT),$(ASTCFLAGS)) -MD -MT $@ -MF .$(subst /,_,$@).d -MP + +%.c: %.y + $(ECHO_PREFIX) echo " [BISON] $< -> $@" + $(CMD_PREFIX) bison -o $@ -d --name-prefix=ast_yy $< + +%.c: %.fl + $(ECHO_PREFIX) echo " [FLEX] $< -> $@" + $(CMD_PREFIX) flex -o $@ --full $< + +%.so: %.o + $(ECHO_PREFIX) echo " [LD] $^ -> $@" + $(CMD_PREFIX) $(CC) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) $^ $(PTHREAD_LIBS) $(LIBS) + +%.so: %.oo + $(ECHO_PREFIX) echo " [LDXX] $^ -> $@" + $(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) $^ $(PTHREAD_LIBS) $(LIBS) + +%: %.o + $(ECHO_PREFIX) echo " [LD] $^ -> $@" + $(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $^ $(PTHREAD_LIBS) $(LIBS) + +dist-clean:: diff --git a/asterisk/README b/asterisk/README new file mode 100644 index 00000000..87ea8c38 --- /dev/null +++ b/asterisk/README @@ -0,0 +1,262 @@ +The Asterisk(R) Open Source PBX +by Mark Spencer +and the Asterisk.org developer community + +Copyright (C) 2001-2008 Digium, Inc. +and other copyright holders. +================================================================ + +* SECURITY + It is imperative that you read and fully understand the contents of +the security information file (doc/security.txt) before you attempt +to configure and run an Asterisk server. + +* WHAT IS ASTERISK ? + Asterisk is an Open Source PBX and telephony toolkit. It is, in a +sense, middleware between Internet and telephony channels on the bottom, +and Internet and telephony applications at the top. For more information +on the project itself, please visit the Asterisk home page at: + + http://www.asterisk.org + +In addition you'll find lots of information compiled by the Asterisk +community on this Wiki: + + http://www.voip-info.org/wiki-Asterisk + +There is a book on Asterisk published by O'Reilly under the +Creative Commons License. It is available in book stores as well +as in a downloadable version on the http://www.asteriskdocs.org +web site. + +* SUPPORTED OPERATING SYSTEMS + +== Linux == + The Asterisk Open Source PBX is developed and tested primarily on the +GNU/Linux operating system, and is supported on every major GNU/Linux +distribution. + +== Others == + Asterisk has also been 'ported' and reportedly runs properly on other +operating systems as well, including Sun Solaris, Apple's Mac OS X, and +the BSD variants. + +* GETTING STARTED + + First, be sure you've got supported hardware (but note that you don't need +ANY special hardware, not even a soundcard) to install and run Asterisk. + + Supported telephony hardware includes: + + * All Wildcard (tm) products from Digium (www.digium.com) + * QuickNet Internet PhoneJack and LineJack (http://www.quicknet.net) + * any full duplex sound card supported by ALSA or OSS + * any ISDN card supported by mISDN on Linux (BRI) + * The Xorcom AstriBank channel bank + * VoiceTronix OpenLine products + +The are several drivers for ISDN BRI cards available from third party sources. +Check the voip-info.org wiki for more information on chan_capi and +zaphfc. + +* UPGRADING FROM AN EARLIER VERSION + + If you are updating from a previous version of Asterisk, make sure you +read the UPGRADE.txt file in the source directory. There are some files +and configuration options that you will have to change, even though we +made every effort possible to maintain backwards compatibility. + + In order to discover new features to use, please check the configuration +examples in the /configs directory of the source code distribution. +To discover the major new features of Asterisk 1.2, please visit +http://edvina.net/asterisk1-2/ + +* NEW INSTALLATIONS + + Ensure that your system contains a compatible compiler and development +libraries. Asterisk requires either the GNU Compiler Collection (GCC) version +3.0 or higher, or a compiler that supports the C99 specification and some of +the gcc language extensions. In addition, your system needs to have the C +library headers available, and the headers and libraries for OpenSSL, +ncurses and zlib. +On many distributions, these files are installed by packages with names like +'glibc-devel', 'ncurses-devel', 'openssl-devel' and 'zlib-devel' or similar. + + So let's proceed: + +1) Read this README file. + + There are more documents than this one in the doc/ directory. +You may also want to check the configuration files that contain +examples and reference guides. They are all in the configs/ +directory. + +2) Run "./configure" + + Execute the configure script to guess values for system-dependent +variables used during compilation. + +3) Run "make menuselect" [optional] + + This is needed if you want to select the modules that will be +compiled and to check modules dependencies. + +4) Run "make" + + Assuming the build completes successfully: + +5) Run "make install" + + Each time you update or checkout from the repository, you are strongly +encouraged to ensure all previous object files are removed to avoid internal +inconsistency in Asterisk. Normally, this is automatically done with +the presence of the file .cleancount, which increments each time a 'make clean' +is required, and the file .lastclean, which contains the last .cleancount used. + + If this is your first time working with Asterisk, you may wish to install +the sample PBX, with demonstration extensions, etc. If so, run: + +6) "make samples" + + Doing so will overwrite any existing config files you have. + + Finally, you can launch Asterisk in the foreground mode (not a daemon) +with: + +# asterisk -vvvc + + You'll see a bunch of verbose messages fly by your screen as Asterisk +initializes (that's the "very very verbose" mode). When it's ready, if +you specified the "c" then you'll get a command line console, that looks +like this: + +*CLI> + + You can type "help" at any time to get help with the system. For help +with a specific command, type "help ". To start the PBX using +your sound card, you can type "dial" to dial the PBX. Then you can use +"answer", "hangup", and "dial" to simulate the actions of a telephone. +Remember that if you don't have a full duplex sound card (and Asterisk +will tell you somewhere in its verbose messages if you do/don't) then it +won't work right (not yet). + + "man asterisk" at the Unix/Linux command prompt will give you detailed +information on how to start and stop Asterisk, as well as all the command +line options for starting Asterisk. + + Feel free to look over the configuration files in /etc/asterisk, where +you'll find a lot of information about what you can do with Asterisk. + +* ABOUT CONFIGURATION FILES + + All Asterisk configuration files share a common format. Comments are +delimited by ';' (since '#' of course, being a DTMF digit, may occur in +many places). A configuration file is divided into sections whose names +appear in []'s. Each section typically contains two types of statements, +those of the form 'variable = value', and those of the form 'object => +parameters'. Internally the use of '=' and '=>' is exactly the same, so +they're used only to help make the configuration file easier to +understand, and do not affect how it is actually parsed. + + Entries of the form 'variable=value' set the value of some parameter in +asterisk. For example, in chan_dahdi.conf, one might specify: + + switchtype=national + +in order to indicate to Asterisk that the switch they are connecting to is +of the type "national". In general, the parameter will apply to +instantiations which occur below its specification. For example, if the +configuration file read: + + switchtype = national + channel => 1-4 + channel => 10-12 + switchtype = dms100 + channel => 25-47 + +the "national" switchtype would be applied to channels one through +four and channels 10 through 12, whereas the "dms100" switchtype would +apply to channels 25 through 47. + + The "object => parameters" instantiates an object with the given +parameters. For example, the line "channel => 25-47" creates objects for +the channels 25 through 47 of the card, obtaining the settings +from the variables specified above. + +* SPECIAL NOTE ON TIME + + Those using SIP phones should be aware that Asterisk is sensitive to +large jumps in time. Manually changing the system time using date(1) +(or other similar commands) may cause SIP registrations and other +internal processes to fail. If your system cannot keep accurate time +by itself use NTP (http://www.ntp.org/) to keep the system clock +synchronized to "real time". NTP is designed to keep the system clock +synchronized by speeding up or slowing down the system clock until it +is synchronized to "real time" rather than by jumping the time and +causing discontinuities. Most Linux distributions include precompiled +versions of NTP. Beware of some time synchronization methods that get +the correct real time periodically and then manually set the system +clock. + + Apparent time changes due to daylight savings time are just that, +apparent. The use of daylight savings time in a Linux system is +purely a user interface issue and does not affect the operation of the +Linux kernel or Asterisk. The system clock on Linux kernels operates +on UTC. UTC does not use daylight savings time. + + Also note that this issue is separate from the clocking of TDM +channels, and is known to at least affect SIP registrations. + +* FILE DESCRIPTORS + + Depending on the size of your system and your configuration, +Asterisk can consume a large number of file descriptors. In UNIX, +file descriptors are used for more than just files on disk. File +descriptors are also used for handling network communication +(e.g. SIP, IAX2, or H.323 calls) and hardware access (e.g. analog and +digital trunk hardware). Asterisk accesses many on-disk files for +everything from configuration information to voicemail storage. + + Most systems limit the number of file descriptors that Asterisk can +have open at one time. This can limit the number of simultaneous +calls that your system can handle. For example, if the limit is set +at 1024 (a common default value) Asterisk can handle approxiately 150 +SIP calls simultaneously. To change the number of file descriptors +follow the instructions for your system below: + +== PAM-based Linux System == + + If your system uses PAM (Pluggable Authentication Modules) edit +/etc/security/limits.conf. Add these lines to the bottom of the file: + +root soft nofile 4096 +root hard nofile 8196 +asterisk soft nofile 4096 +asterisk hard nofile 8196 + +(adjust the numbers to taste). You may need to reboot the system for +these changes to take effect. + +== Generic UNIX System == + + If there are no instructions specifically adapted to your system +above you can try adding the command "ulimit -n 8192" to the script +that starts Asterisk. + +* MORE INFORMATION + + See the doc directory for more documentation on various features. Again, +please read all the configuration samples that include documentation on +the configuration options. + + Finally, you may wish to visit the web site and join the mailing list if +you're interested in getting more information. + + http://www.asterisk.org/support + + Welcome to the growing worldwide community of Asterisk users! + +Mark Spencer + +---- +Asterisk is a trademark belonging to Digium, inc diff --git a/asterisk/UPGRADE-1.2.txt b/asterisk/UPGRADE-1.2.txt new file mode 100644 index 00000000..7070bec1 --- /dev/null +++ b/asterisk/UPGRADE-1.2.txt @@ -0,0 +1,210 @@ +========================================================= +=== Information for upgrading from Asterisk 1.0 to 1.2 +=== +=== +=== UPGRADE-1.2.txt -- Upgrade info for 1.0 to 1.2 +=== UPGRADE.txt -- Upgrade info for 1.2 to 1.4 +========================================================= + +Compiling: + +* The Asterisk 1.2 source code now uses C language features + supported only by 'modern' C compilers. Generally, this means GCC + version 3.0 or higher, although some GCC 2.96 releases will also + work. Some non-GCC compilers that support C99 and the common GCC + extensions (including anonymous structures and unions) will also + work. All releases of GCC 2.95 do _not_ have the requisite feature + support; systems using that compiler will need to be upgraded to + a more recent compiler release. + +Dialplan Expressions: + +* The dialplan expression parser (which handles $[ ... ] constructs) + has gone through a major upgrade, but has one incompatible change: + spaces are no longer required around expression operators, including + string comparisons. However, you can now use quoting to keep strings + together for comparison. For more details, please read the + doc/README.variables file, and check over your dialplan for possible + problems. + +Agents: + +* The default for ackcall has been changed to "no" instead of "yes" + because of a bug which caused the "yes" behavior to generally act like + "no". You may need to adjust the value if your agents behave + differently than you expect with respect to acknowledgement. + +* The AgentCallBackLogin application now requires a second '|' before + specifying an extension@context. This is to distinguish the options + string from the extension, so that they do not conflict. See + 'show application AgentCallbackLogin' for more details. + +Parking: + +* Parking behavior has changed slightly; when a parked call times out, + Asterisk will attempt to deliver the call back to the extension that + parked it, rather than the 's' extension. If that extension is busy + or unavailable, the parked call will be lost. + +Dialing: + +* The Caller*ID of the outbound leg is now the extension that was + called, rather than the Caller*ID of the inbound leg of the call. The + "o" flag for Dial can be used to restore the original behavior if + desired. Note that if you are looking for the originating callerid + from the manager event, there is a new manager event "Dial" which + provides the source and destination channels and callerid. + +IAX: + +* The naming convention for IAX channels has changed in two ways: + 1. The call number follows a "-" rather than a "/" character. + 2. The name of the channel has been simplified to IAX2/peer-callno, + rather than IAX2/peer@peer-callno or even IAX2/peer@peer/callno. + +SIP: + +* The global option "port" in 1.0.X that is used to set which port to + bind to has been changed to "bindport" to be more consistent with + the other channel drivers and to avoid confusion with the "port" + option for users/peers. + +* The "Registry" event now uses "Username" rather than "User" for + consistency with IAX. + +Applications: + +* With the addition of dialplan functions (which operate similarly + to variables), the SetVar application has been renamed to Set. + +* The CallerPres application has been removed. Use SetCallerPres + instead. It accepts both numeric and symbolic names. + +* The applications GetGroupCount, GetGroupMatchCount, SetGroup, and + CheckGroup have been deprecated in favor of functions. Here is a + table of their replacements: + + GetGroupCount([groupname][@category] GROUP_COUNT([groupname][@category]) Set(GROUPCOUNT=${GROUP_COUNT()}) + GroupMatchCount(groupmatch[@category]) GROUP_MATCH_COUNT(groupmatch[@category]) Set(GROUPCOUNT=${GROUP_MATCH_COUNT(SIP/.*)}) + SetGroup(groupname[@category]) GROUP([category])=groupname Set(GROUP()=test) + CheckGroup(max[@category]) N/A GotoIf($[ ${GROUP_COUNT()} > 5 ]?103) + + Note that CheckGroup does not have a direct replacement. There is + also a new function called GROUP_LIST() which will return a space + separated list of all of the groups set on a channel. The GROUP() + function can also return the name of the group set on a channel when + used in a read environment. + +* The applications DBGet and DBPut have been deprecated in favor of + functions. Here is a table of their replacements: + + DBGet(foo=family/key) Set(foo=${DB(family/key)}) + DBPut(family/key=${foo}) Set(DB(family/key)=${foo}) + +* The application SetLanguage has been deprecated in favor of the + function LANGUAGE(). + + SetLanguage(fr) Set(LANGUAGE()=fr) + + The LANGUAGE function can also return the currently set language: + + Set(MYLANG=${LANGUAGE()}) + +* The applications AbsoluteTimeout, DigitTimeout, and ResponseTimeout + have been deprecated in favor of the function TIMEOUT(timeouttype): + + AbsoluteTimeout(300) Set(TIMEOUT(absolute)=300) + DigitTimeout(15) Set(TIMEOUT(digit)=15) + ResponseTimeout(15) Set(TIMEOUT(response)=15) + + The TIMEOUT() function can also return the currently set timeouts: + + Set(DTIMEOUT=${TIMEOUT(digit)}) + +* The applications SetCIDName, SetCIDNum, and SetRDNIS have been + deprecated in favor of the CALLERID(datatype) function: + + SetCIDName(Joe Cool) Set(CALLERID(name)=Joe Cool) + SetCIDNum(2025551212) Set(CALLERID(number)=2025551212) + SetRDNIS(2024561414) Set(CALLERID(RDNIS)=2024561414) + +* The application Record now uses the period to separate the filename + from the format, rather than the colon. + +* The application VoiceMail now supports a 'temporary' greeting for each + mailbox. This greeting can be recorded by using option 4 in the + 'mailbox options' menu, and 'change your password' option has been + moved to option 5. + +* The application VoiceMailMain now only matches the 'default' context if + none is specified in the arguments. (This was the previously + documented behavior, however, we didn't follow that behavior.) The old + behavior can be restored by setting searchcontexts=yes in voicemail.conf. + +Queues: + +* A queue is now considered empty not only if there are no members but if + none of the members are available (e.g. agents not logged on). To + restore the original behavior, use "leavewhenempty=strict" or + "joinwhenempty=strict" instead of "=yes" for those options. + +* It is now possible to use multi-digit extensions in the exit context + for a queue (although you should not have overlapping extensions, + as there is no digit timeout). This means that the EXITWITHKEY event + in queue_log can now contain a key field with more than a single + character in it. + +Extensions: + +* By default, there is a new option called "autofallthrough" in + extensions.conf that is set to yes. Asterisk 1.0 (and earlier) + behavior was to wait for an extension to be dialed after there were no + more extensions to execute. "autofallthrough" changes this behavior + so that the call will immediately be terminated with BUSY, + CONGESTION, or HANGUP based on Asterisk's best guess. If you are + writing an extension for IVR, you must use the WaitExten application + if "autofallthrough" is set to yes. + +AGI: + +* AGI scripts did not always get SIGHUP at the end, previously. That + behavior has been fixed. If you do not want your script to terminate + at the end of AGI being called (e.g. on a hangup) then set SIGHUP to + be ignored within your application. + +* CallerID is reported with agi_callerid and agi_calleridname instead + of a single parameter holding both. + +Music On Hold: + +* The preferred format for musiconhold.conf has changed; please see the + sample configuration file for the new format. The existing format + is still supported but will generate warnings when the module is loaded. + +chan_modem: + +* All the chan_modem channel drivers (aopen, bestdata and i4l) are deprecated + in this release, and will be removed in the next major Asterisk release. + Please migrate to chan_misdn for ISDN interfaces; there is no upgrade + path for aopen and bestdata modem users. + +MeetMe: + +* The conference application now allows users to increase/decrease their + speaking volume and listening volume (independently of each other and + other users); the 'admin' and 'user' menus have changed, and new sound + files are included with this release. However, if a user calling in + over a Zaptel channel that does NOT have hardware DTMF detection + increases their speaking volume, it is likely they will no longer be + able to enter/exit the menu or make any further adjustments, as the + software DTMF detector will not be able to recognize the DTMF coming + from their device. + +GetVar Manager Action: + +* Previously, the behavior of the GetVar manager action reported the value + of a variable in the following manner: + > name: value + This has been changed to a manner similar to the SetVar action and is now + > Variable: name + > Value: value diff --git a/asterisk/UPGRADE.txt b/asterisk/UPGRADE.txt new file mode 100644 index 00000000..6dad890e --- /dev/null +++ b/asterisk/UPGRADE.txt @@ -0,0 +1,500 @@ +========================================================= +=== Information for upgrading from Asterisk 1.2 to 1.4 +=== +=== +=== UPGRADE-1.2.txt -- Upgrade info for 1.0 to 1.2 +=== UPGRADE.txt -- Upgrade info for 1.2 to 1.4 +========================================================= + +Build Process (configure script): + +Asterisk now uses an autoconf-generated configuration script to learn how it +should build itself for your system. As it is a standard script, running: + +$ ./configure --help + +will show you all the options available. This script can be used to tell the +build process what libraries you have on your system (if it cannot find them +automatically), which libraries you wish to have ignored even though they may +be present, etc. + +You must run the configure script before Asterisk will build, although it will +attempt to automatically run it for you with no options specified; for most +users, that will result in a similar build to what they would have had before +the configure script was added to the build process (except for having to run +'make' again after the configure script is run). Note that the configure script +does NOT need to be re-run just to rebuild Asterisk; you only need to re-run it +when your system configuration changes or you wish to build Asterisk with +different options. + +Build Process (module selection): + +The Asterisk source tree now includes a basic module selection and build option +selection tool called 'menuselect'. Run 'make menuselect' to make your choices. +In this tool, you can disable building of modules that you don't care about, +turn on/off global options for the build and see which modules will not +(and cannot) be built because your system does not have the required external +dependencies installed. + +The resulting file from menuselect is called 'menuselect.makeopts'. Note that +the resulting menuselect.makeopts file generally contains which modules *not* +to build. The modules listed in this file indicate which modules have unmet +dependencies, a present conflict, or have been disabled by the user in the +menuselect interface. Compiler Flags can also be set in the menuselect +interface. In this case, the resulting file contains which CFLAGS are in use, +not which ones are not in use. + +If you would like to save your choices and have them applied against all +builds, the file can be copied to '~/.asterisk.makeopts' or +'/etc/asterisk.makeopts'. + +Build Process (Makefile targets): + +The 'valgrind' and 'dont-optimize' targets have been removed; their functionality +is available by enabling the DONT_OPTIMIZE setting in the 'Compiler Flags' menu +in the menuselect tool. + +It is now possible to run most make targets against a single subdirectory; from +the top level directory, for example, 'make channels' will run 'make all' in the +'channels' subdirectory. This also is true for 'clean', 'distclean' and 'depend'. + +Sound (prompt) and Music On Hold files: + +Beginning with Asterisk 1.4, the sound files and music on hold files supplied for +use with Asterisk have been replaced with new versions produced from high quality +master recordings, and are available in three languages (English, French and +Spanish) and in five formats (WAV (uncompressed), mu-Law, a-Law, GSM and G.729). +In addition, the music on hold files provided by FreePlay Music are now available +in the same five formats, but no longer available in MP3 format. + +The Asterisk 1.4 tarball packages will only include English prompts in GSM format, +(as were supplied with previous releases) and the FreePlay MOH files in WAV format. +All of the other variations can be installed by running 'make menuselect' and +selecting the packages you wish to install; when you run 'make install', those +packages will be downloaded and installed along with the standard files included +in the tarball. + +If for some reason you expect to not have Internet access at the time you will be +running 'make install', you can make your package selections using menuselect and +then run 'make sounds' to download (only) the sound packages; this will leave the +sound packages in the 'sounds' subdirectory to be used later during installation. + +WARNING: Asterisk 1.4 supports a new layout for sound files in multiple languages; +instead of the alternate-language files being stored in subdirectories underneath +the existing files (for French, that would be digits/fr, letters/fr, phonetic/fr, +etc.) the new layout creates one directory under /var/lib/asterisk/sounds for the +language itself, then places all the sound files for that language under that +directory and its subdirectories. This is the layout that will be created if you +select non-English languages to be installed via menuselect, HOWEVER Asterisk does +not default to this layout and will not find the files in the places it expects them +to be. If you wish to use this layout, make sure you put 'languageprefix=yes' in your +/etc/asterisk/asterisk.conf file, so that Asterisk will know how the files were +installed. + +PBX Core: + +* The (very old and undocumented) ability to use BYEXTENSION for dialing + instead of ${EXTEN} has been removed. + +* Builtin (res_features) transfer functionality attempts to use the context + defined in TRANSFER_CONTEXT variable of the transferer channel first. If + not set, it uses the transferee variable. If not set in any channel, it will + attempt to use the last non macro context. If not possible, it will default + to the current context. + +* The autofallthrough setting introduced in Asterisk 1.2 now defaults to 'yes'; + if your dialplan relies on the ability to 'run off the end' of an extension + and wait for a new extension without using WaitExten() to accomplish that, + you will need set autofallthrough to 'no' in your extensions.conf file. + +Command Line Interface: + +* 'show channels concise', designed to be used by applications that will parse + its output, previously used ':' characters to separate fields. However, some + of those fields can easily contain that character, making the output not + parseable. The delimiter has been changed to '!'. + +Applications: + +* In previous Asterisk releases, many applications would jump to priority n+101 + to indicate some kind of status or error condition. This functionality was + marked deprecated in Asterisk 1.2. An option to disable it was provided with + the default value set to 'on'. The default value for the global priority + jumping option is now 'off'. + +* The applications Cut, Sort, DBGet, DBPut, SetCIDNum, SetCIDName, SetRDNIS, + AbsoluteTimeout, DigitTimeout, ResponseTimeout, SetLanguage, GetGroupCount, + and GetGroupMatchCount were all deprecated in version 1.2, and therefore have + been removed in this version. You should use the equivalent dialplan + function in places where you have previously used one of these applications. + +* The application SetGlobalVar has been deprecated. You should replace uses + of this application with the following combination of Set and GLOBAL(): + Set(GLOBAL(name)=value). You may also access global variables exclusively by + using the GLOBAL() dialplan function, instead of relying on variable + interpolation falling back to globals when no channel variable is set. + +* The application SetVar has been renamed to Set. The syntax SetVar was marked + deprecated in version 1.2 and is no longer recognized in this version. The + use of Set with multiple argument pairs has also been deprecated. Please + separate each name/value pair into its own dialplan line. + +* app_read has been updated to use the newer options codes, using "skip" or + "noanswer" will not work. Use s or n. Also there is a new feature i, for + using indication tones, so typing in skip would give you unexpected results. + +* OSPAuth is added to authenticate OSP tokens in in_bound call setup messages. + +* The CONNECT event in the queue_log from app_queue now has a second field + in addition to the holdtime field. It contains the unique ID of the + queue member channel that is taking the call. This is useful when trying + to link recording filenames back to a particular call from the queue. + +* The old/current behavior of app_queue has a serial type behavior + in that the queue will make all waiting callers wait in the queue + even if there is more than one available member ready to take + calls until the head caller is connected with the member they + were trying to get to. The next waiting caller in line then + becomes the head caller, and they are then connected with the + next available member and all available members and waiting callers + waits while this happens. This cycle continues until there are + no more available members or waiting callers, whichever comes first. + The new behavior, enabled by setting autofill=yes in queues.conf + either at the [general] level to default for all queues or + to set on a per-queue level, makes sure that when the waiting + callers are connecting with available members in a parallel fashion + until there are no more available members or no more waiting callers, + whichever comes first. This is probably more along the lines of how + one would expect a queue should work and in most cases, you will want + to enable this new behavior. If you do not specify or comment out this + option, it will default to "no" to keep backward compatability with the old + behavior. + +* Queues depend on the channel driver reporting the proper state + for each member of the queue. To get proper signalling on + queue members that use the SIP channel driver, you need to + enable a call limit (could be set to a high value so it + is not put into action) and also make sure that both inbound + and outbound calls are accounted for. + + Example: + + [general] + limitonpeer = yes + + [peername] + type=friend + call-limit=10 + + +* The app_queue application now has the ability to use MixMonitor to + record conversations queue members are having with queue callers. Please + see configs/queues.conf.sample for more information on this option. + +* The app_queue application strategy called 'roundrobin' has been deprecated + for this release. Users are encouraged to use 'rrmemory' instead, since it + provides more 'true' round-robin call delivery. For the Asterisk 1.6 release, + 'rrmemory' will be renamed 'roundrobin'. + +* The app_queue application option called 'monitor-join' has been deprecated + for this release. Users are encouraged to use 'monitor-type=mixmonitor' instead, + since it provides the same functionality but is not dependent on soxmix or some + other external program in order to mix the audio. + +* app_meetme: The 'm' option (monitor) is renamed to 'l' (listen only), and + the 'm' option now provides the functionality of "initially muted". + In practice, most existing dialplans using the 'm' flag should not notice + any difference, unless the keypad menu is enabled, allowing the user + to unmute themsleves. + +* ast_play_and_record would attempt to cancel the recording if a DTMF + '0' was received. This behavior was not documented in most of the + applications that used ast_play_and_record and the return codes from + ast_play_and_record weren't checked for properly. + ast_play_and_record has been changed so that '0' no longer cancels a + recording. If you want to allow DTMF digits to cancel an + in-progress recording use ast_play_and_record_full which allows you + to specify which DTMF digits can be used to accept a recording and + which digits can be used to cancel a recording. + +* ast_app_messagecount has been renamed to ast_app_inboxcount. There is now a + new ast_app_messagecount function which takes a single context/mailbox/folder + mailbox specification and returns the message count for that folder only. + This addresses the deficiency of not being able to count the number of + messages in folders other than INBOX and Old. + +* The exit behavior of the AGI applications has changed. Previously, when + a connection to an AGI server failed, the application would cause the channel + to immediately stop dialplan execution and hangup. Now, the only time that + the AGI applications will cause the channel to stop dialplan execution is + when the channel itself requests hangup. The AGI applications now set an + AGISTATUS variable which will allow you to find out whether running the AGI + was successful or not. + + Previously, there was no way to handle the case where Asterisk was unable to + locally execute an AGI script for some reason. In this case, dialplan + execution will continue as it did before, but the AGISTATUS variable will be + set to "FAILURE". + + A locally executed AGI script can now exit with a non-zero exit code and this + failure will be detected by Asterisk. If an AGI script exits with a non-zero + exit code, the AGISTATUS variable will be set to "FAILURE" as opposed to + "SUCCESS". + +* app_voicemail: The ODBC_STORAGE capability now requires the extended table format + previously used only by EXTENDED_ODBC_STORAGE. This means that you will need to update + your table format using the schema provided in doc/odbcstorage.txt + +* app_waitforsilence: Fixes have been made to this application which changes the + default behavior with how quickly it returns. You can maintain "old-style" behavior + with the addition/use of a third "timeout" parameter. + Please consult the application documentation and make changes to your dialplan + if appropriate. + +Manager: + +* After executing the 'status' manager action, the "Status" manager events + included the header "CallerID:" which was actually only the CallerID number, + and not the full CallerID string. This header has been renamed to + "CallerIDNum". For compatibility purposes, the CallerID parameter will remain + until after the release of 1.4, when it will be removed. Please use the time + during the 1.4 release to make this transition. + +* The AgentConnect event now has an additional field called "BridgedChannel" + which contains the unique ID of the queue member channel that is taking the + call. This is useful when trying to link recording filenames back to + a particular call from the queue. + +* app_userevent has been modified to always send Event: UserEvent with the + additional header UserEvent: . Also, the Channel and UniqueID + headers are not automatically sent, unless you specify them as separate + arguments. Please see the application help for the new syntax. + +* app_meetme: Mute and Unmute events are now reported via the Manager API. + Native Manager API commands MeetMeMute and MeetMeUnmute are provided, which + are easier to use than "Action Command:". The MeetMeStopTalking event has + also been deprecated in favor of the already existing MeetmeTalking event + with a "Status" of "on" or "off" added. + +* OriginateFailure and OriginateSuccess events were replaced by event + OriginateResponse with a header named "Response" to indicate success or + failure + +Variables: + +* The builtin variables ${CALLERID}, ${CALLERIDNAME}, ${CALLERIDNUM}, + ${CALLERANI}, ${DNID}, ${RDNIS}, ${DATETIME}, ${TIMESTAMP}, ${ACCOUNTCODE}, + and ${LANGUAGE} have all been deprecated in favor of their related dialplan + functions. You are encouraged to move towards the associated dialplan + function, as these variables will be removed in a future release. + +* The CDR-CSV variables uniqueid, userfield, and basing time on GMT are now + adjustable from cdr.conf, instead of recompiling. + +* OSP applications exports several new variables, ${OSPINHANDLE}, + ${OSPOUTHANDLE}, ${OSPINTOKEN}, ${OSPOUTTOKEN}, ${OSPCALLING}, + ${OSPINTIMELIMIT}, and ${OSPOUTTIMELIMIT} + +* Builtin transfer functionality sets the variable ${TRANSFERERNAME} in the new + created channel. This variables holds the channel name of the transferer. + +* The dial plan variable PRI_CAUSE will be removed from future versions + of Asterisk. + It is replaced by adding a cause value to the hangup() application. + +Functions: + +* The function ${CHECK_MD5()} has been deprecated in favor of using an + expression: $[${MD5()} = ${saved_md5}]. + +* The 'builtin' functions that used to be combined in pbx_functions.so are + now built as separate modules. If you are not using 'autoload=yes' in your + modules.conf file then you will need to explicitly load the modules that + contain the functions you want to use. + +* The ENUMLOOKUP() function with the 'c' option (for counting the number of + records), but the lookup fails to match any records, the returned value will + now be "0" instead of blank. + +* The REALTIME() function is now available in version 1.4 and app_realtime has + been deprecated in favor of the new function. app_realtime will be removed + completely with the version 1.6 release so please take the time between + releases to make any necessary changes + +* The QUEUEAGENTCOUNT() function has been deprecated in favor of + QUEUE_MEMBER_COUNT(). + +The IAX2 channel: + +* It is possible that previous configurations depended on the order in which + peers and users were specified in iax.conf for forcing the order in which + chan_iax2 matched against them. This behavior is going away and is considered + deprecated in this version. Avoid having ambiguous peer and user entries and + to make things easy on yourself, always set the "username" option for users + so that the remote end can match on that exactly instead of trying to infer + which user you want based on host. + + If you would like to go ahead and use the new behavior which doesn't use the + order in the config file to influence matching order, then change the + MAX_PEER_BUCKETS define in chan_iax2.c to a value greater than one. An + example is provided there. By changing this, you will get *much* better + performance on systems that do a lot of peer and user lookups as they will be + stored in memory in a much more efficient manner. + +* The "mailboxdetail" option has been deprecated. Previously, if this option + was not enabled, the 2 byte MSGCOUNT information element would be set to all + 1's to indicate there there is some number of messages waiting. With this + option enabled, the number of new messages were placed in one byte and the + number of old messages are placed in the other. This is now the default + (and the only) behavior. + +The SIP channel: + +* The "incominglimit" setting is replaced by the "call-limit" setting in + sip.conf. + +* OSP support code is removed from SIP channel to OSP applications. ospauth + option in sip.conf is removed to osp.conf as authpolicy. allowguest option + in sip.conf cannot be set as osp anymore. + +* The Asterisk RTP stack has been changed in regards to RFC2833 reception + and transmission. Packets will now be sent with proper duration instead of all + at once. If you are receiving calls from a pre-1.4 Asterisk installation you + will want to turn on the rfc2833compensate option. Without this option your + DTMF reception may act poorly. + +* The $SIPUSERAGENT dialplan variable is deprecated and will be removed + in coming versions of Asterisk. Please use the dialplan function + SIPCHANINFO(useragent) instead. + +* The ALERT_INFO dialplan variable is deprecated and will be removed + in coming versions of Asterisk. Please use the dialplan application + sipaddheader() to add the "Alert-Info" header to the outbound invite. + +* The "canreinvite" option has changed. canreinvite=yes used to disable + re-invites if you had NAT=yes. In 1.4, you need to set canreinvite=nonat + to disable re-invites when NAT=yes. This is propably what you want. + The settings are now: "yes", "no", "nonat", "update". Please consult + sip.conf.sample for detailed information. + +The Zap channel: + +* Support for MFC/R2 has been removed, as it has not been functional for some + time and it has no maintainer. + +The Agent channel: + +* Callback mode (AgentCallbackLogin) is now deprecated, since the entire function + it provided can be done using dialplan logic, without requiring additional + channel and module locks (which frequently caused deadlocks). An example of + how to do this using AEL dialplan is in doc/queues-with-callback-members.txt. + +The G726-32 codec: + +* It has been determined that previous versions of Asterisk used the wrong codeword + packing order for G726-32 data. This version supports both available packing orders, + and can transcode between them. It also now selects the proper order when + negotiating with a SIP peer based on the codec name supplied in the SDP. However, + there are existing devices that improperly request one order and then use another; + Sipura and Grandstream ATAs are known to do this, and there may be others. To + be able to continue to use these devices with this version of Asterisk and the + G726-32 codec, a configuration parameter called 'g726nonstandard' has been added + to sip.conf, so that Asterisk can use the packing order expected by the device (even + though it requested a different order). In addition, the internal format number for + G726-32 has been changed, and the old number is now assigned to AAL2-G726-32. The + result of this is that this version of Asterisk will be able to interoperate over + IAX2 with older versions of Asterisk, as long as this version is told to allow + 'g726aal2' instead of 'g726' as the codec for the call. + +Installation: + +* On BSD systems, the installation directories have changed to more "FreeBSDish" + directories. On startup, Asterisk will look for the main configuration in + /usr/local/etc/asterisk/asterisk.conf + If you have an old installation, you might want to remove the binaries and + move the configuration files to the new locations. The following directories + are now default: + ASTLIBDIR /usr/local/lib/asterisk + ASTVARLIBDIR /usr/local/share/asterisk + ASTETCDIR /usr/local/etc/asterisk + ASTBINDIR /usr/local/bin/asterisk + ASTSBINDIR /usr/local/sbin/asterisk + +Music on Hold: + +* The music on hold handling has been changed in some significant ways in hopes + to make it work in a way that is much less confusing to users. Behavior will + not change if the same configuration is used from older versions of Asterisk. + However, there are some new configuration options that will make things work + in a way that makes more sense. + + Previously, many of the channel drivers had an option called "musicclass" or + something similar. This option set what music on hold class this channel + would *hear* when put on hold. Some people expected (with good reason) that + this option was to configure what music on hold class to play when putting + the bridged channel on hold. This option has now been deprecated. + + Two new music on hold related configuration options for channel drivers have + been introduced. Some channel drivers support both options, some just one, + and some support neither of them. Check the sample configuration files to see + which options apply to which channel driver. + + The "mohsuggest" option specifies which music on hold class to suggest to the + bridged channel when putting them on hold. The only way that this class can + be overridden is if the bridged channel has a specific music class set that + was done in the dialplan using Set(CHANNEL(musicclass)=something). + + The "mohinterpret" option is similar to the old "musicclass" option. It + specifies which music on hold class this channel would like to listen to when + put on hold. This music class is only effective if this channel has no music + class set on it from the dialplan and the bridged channel putting this one on + hold had no "mohsuggest" setting. + + The IAX2 and Zap channel drivers have an additional feature for the + "mohinterpret" option. If this option is set to "passthrough", then these + channel drivers will pass through the HOLD message in signalling instead of + starting music on hold on the channel. An example for how this would be + useful is in an enterprise network of Asterisk servers. When one phone on one + server puts a phone on a different server on hold, the remote server will be + responsible for playing the hold music to its local phone that was put on + hold instead of the far end server across the network playing the music. + +CDR Records: + +* The behavior of the "clid" field of the CDR has always been that it will + contain the callerid ANI if it is set, or the callerid number if ANI was not + set. When using the "callerid" option for various channel drivers, some + would set ANI and some would not. This has been cleared up so that all + channel drivers set ANI. If you would like to change the callerid number + on the channel from the dialplan and have that change also show up in the + CDR, then you *must* set CALLERID(ANI) as well as CALLERID(num). + +API: + +* There are some API functions that were not previously prefixed with the 'ast_' + prefix but now are; these include the ADSI, ODBC and AGI interfaces. If you + have a module that uses the services provided by res_adsi, res_odbc, or + res_agi, you will need to add ast_ prefixes to the functions that you call + from those modules. + +Formats: + +* format_wav: The GAIN preprocessor definition has been changed from 2 to 0 + in Asterisk 1.4. This change was made in response to user complaints of + choppiness or the clipping of loud signal peaks. The GAIN preprocessor + definition will be retained in Asterisk 1.4, but will be removed in a + future release. The use of GAIN for the increasing of voicemail message + volume should use the 'volgain' option in voicemail.conf + +iLBC Codec: + +* Previously, the Asterisk source code distribution included the iLBC + encoder/decoder source code, from Global IP Solutions + (http://www.gipscorp.com). This code is not licensed for + distribution, and thus has been removed from the Asterisk source + code distribution. If you wish to use codec_ilbc to support iLBC + channels in Asterisk, you can run the contrib/scripts/get_ilbc_source.sh + script to download the source and put it in the proper place in + the Asterisk build tree. Once that is done you can follow your normal + steps of building Asterisk. You will need to run 'menuselect' and enable + the iLBC codec in the 'Codec Translators' category. diff --git a/asterisk/Zaptel-to-DAHDI.txt b/asterisk/Zaptel-to-DAHDI.txt new file mode 100644 index 00000000..a08fdea6 --- /dev/null +++ b/asterisk/Zaptel-to-DAHDI.txt @@ -0,0 +1,105 @@ +========================================================= +=== Information for upgrading from Zaptel to DAHDI === +========================================================= + +As announced in early 2008, Digium is renaming the Zaptel telephony +interface project to DAHDI (Digium Asterisk Hardware Device Interface) +to accommodate the desires of the owner of the Zaptel trademark for +telephony purposes. + +This version of Asterisk can be built using either Zaptel or DAHDI, +and has many changes to make the use of DAHDI as easy as possible for +existing users with dialplans, CDR parsers, AMI applications, and +others that expect Zaptel to be in use. + +First, the modules that directly use services from Zaptel/DAHDI have been +renamed; the new names are: + + chan_zap.so -> chan_dahdi.so + app_zapbarge.so -> app_dahdibarge.so + app_zapras.so -> app_dahdiras.so + app_zapscan.so -> app_dahdiscan.so + codec_zap.so -> codec_dahdi.so + +However, in spite of the file name changes, the channels and +applications provided by these modules can still be used with 'Zap' +style names; see below for more information. + +Second, there are have been a number of efforts made to ensure that +existing systems will not have to have any major configuration changes +made solely because Asterisk was built against DAHDI instead of +Zaptel. This includes: + +chan_dahdi.so: + + This module will determine which channel name ('Zap' or 'DAHDI') + should be used for incoming and outgoing channels based on the + build-time choice of telephony drivers. However, if you wish to + continue using the 'Zap' channel name even though you built Asterisk + against the DAHDI drivers, you can add the following line to the + [options] section of your /etc/asterisk/asterisk.conf file: + + dahdichanname = no + + All CLI commands that begin with 'zap' are now available as 'dahdi' + commands as well; the 'zap' variants will report that they are + deprecated the first time you use each one in an Asterisk instance, + but they will otherwise operate just as they did in previous versions. + + All Asterisk Manager Interface (AMI) actions that begin with 'Zap' + are also available with 'DAHDI' prefixes. + + The ZapSendKeypadFacility dialplan application is now available as + DAHDISendKeypadFacility as well; the Zap variant will report a deprecation + warning but will otherwise operate as it did it in previous + versions. + + The configuration for the channel driver will be read from + /etc/asterisk/chan_dahdi.conf unless 'dahdichanname' has been set to + 'no' in asterisk.conf; if that is done, then the configuration will + be read from /etc/asterisk/zapata.conf, just as it was in previous + versions. + +app_dahdibarge.so: + + The ZapBarge application is now available as DAHDIBarge as well; the + ZapBarge variant will report a deprecation warning when used, but + will otherwise operate as it did in previous versions. Regardless of + which application name is used, the application will restrict itself + to channels of the proper type, based on the 'dahdichanname' setting + in asterisk.conf. + +app_dahdiras.so: + + The ZapRAS application is now available as DAHDIRAS as well; the + ZapRAS variant will report a deprecation warning when used, but will + otherwise operate as it did in previous versions. Regardless of + which application name is used, the application will restrict itself + to channels of the proper type, based on the 'dahdichanname' setting + in asterisk.conf. + +app_dahdiscan.so: + + The ZapScan application is now available as DAHDIScan as well; the + ZapScan variant will report a deprecation warning when used, but will + otherwise operate as it did in previous versions. Regardless of + which application name is used, the application will restrict itself + to channels of the proper type, based on the 'dahdichanname' setting + in asterisk.conf. + +app_flash.so: + + This application has not had any name changes, but will report its + usage (via 'show application flash') as being for either DAHDI or + Zaptel channels based on the 'dahdichanname' setting in + asterisk.conf. + +app_chanspy.so: + + This application will transparently create 'DAHDI' or 'Zap' channels + as needed, based on the 'dahdichanname' setting in asterisk.conf. + +app_meetme.so: + + This application will transparently create 'DAHDI' or 'Zap' channels + as needed, based on the 'dahdichanname' setting in asterisk.conf. diff --git a/asterisk/acinclude.m4 b/asterisk/acinclude.m4 new file mode 100644 index 00000000..555be179 --- /dev/null +++ b/asterisk/acinclude.m4 @@ -0,0 +1,1035 @@ +# AST_GCC_ATTRIBUTE([attribute name]) + +AC_DEFUN([AST_GCC_ATTRIBUTE], +[ +AC_MSG_CHECKING(for compiler 'attribute $1' support) +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -Werror" +AC_COMPILE_IFELSE( + AC_LANG_PROGRAM([static void __attribute__(($1)) *test(void *muffin, ...) {}], + []), + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED([HAVE_ATTRIBUTE_$1], 1, [Define to 1 if your GCC C compiler supports the '$1' attribute.]), + AC_MSG_RESULT(no)) +] +CFLAGS="$saved_CFLAGS" +) + +# AST_EXT_LIB_SETUP([package symbol name], [package friendly name], [package option name], [additional help text]) + +AC_DEFUN([AST_EXT_LIB_SETUP], +[ +$1_DESCRIP="$2" +$1_OPTION="$3" +AC_ARG_WITH([$3], AC_HELP_STRING([--with-$3=PATH],[use $2 files in PATH $4]),[ +case ${withval} in + n|no) + USE_$1=no + ;; + y|ye|yes) + $1_MANDATORY="yes" + ;; + *) + $1_DIR="${withval}" + $1_MANDATORY="yes" + ;; +esac +]) +PBX_$1=0 +AC_SUBST([$1_LIB]) +AC_SUBST([$1_INCLUDE]) +AC_SUBST([$1_DIR]) +AC_SUBST([PBX_$1]) +]) + +# AST_EXT_LIB_CHECK([package symbol name], [package library name], [function to check], [package header], [additional LIB data], [additional INCLUDE data]) + +AC_DEFUN([AST_EXT_LIB_CHECK], +[ +if test "${USE_$1}" != "no"; then + pbxlibdir="" + if test "x${$1_DIR}" != "x"; then + if test -d ${$1_DIR}/lib; then + pbxlibdir="-L${$1_DIR}/lib" + else + pbxlibdir="-L${$1_DIR}" + fi + fi + AC_CHECK_LIB([$2], [$3], [AST_$1_FOUND=yes], [AST_$1_FOUND=no], ${pbxlibdir} $5) + + if test "${AST_$1_FOUND}" = "yes"; then + $1_LIB="-l$2 $5" + $1_HEADER_FOUND="1" + if test "x${$1_DIR}" != "x"; then + $1_LIB="${pbxlibdir} ${$1_LIB}" + $1_INCLUDE="-I${$1_DIR}/include" + fi + $1_INCLUDE="${$1_INCLUDE} $6" + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${$1_INCLUDE}" + if test "x$4" != "x" ; then + AC_CHECK_HEADER([$4], [$1_HEADER_FOUND=1], [$1_HEADER_FOUND=0]) + fi + CPPFLAGS="${saved_cppflags}" + if test "x${$1_HEADER_FOUND}" = "x0" ; then + if test -n "${$1_MANDATORY}" ; + then + AC_MSG_NOTICE([***]) + AC_MSG_NOTICE([*** It appears that you do not have the $2 development package installed.]) + AC_MSG_NOTICE([*** Please install it to include ${$1_DESCRIP} support, or re-run configure]) + AC_MSG_NOTICE([*** without explicitly specifying --with-${$1_OPTION}]) + exit 1 + fi + $1_LIB="" + $1_INCLUDE="" + PBX_$1=0 + else + PBX_$1=1 + AC_DEFINE_UNQUOTED([HAVE_$1], 1, [Define to indicate the ${$1_DESCRIP} library]) + fi + elif test -n "${$1_MANDATORY}"; + then + AC_MSG_NOTICE([***]) + AC_MSG_NOTICE([*** The ${$1_DESCRIP} installation on this system appears to be broken.]) + AC_MSG_NOTICE([*** Either correct the installation, or run configure]) + AC_MSG_NOTICE([*** without explicitly specifying --with-${$1_OPTION}]) + exit 1 + fi +fi +]) + +# The next three functions check for the availability of a given package. +# AST_C_DEFINE_CHECK looks for the presence of a #define in a header file, +# AST_C_COMPILE_CHECK can be used for testing for various items in header files, +# AST_EXT_LIB_CHECK looks for a symbol in a given library, or at least +# for the presence of a header file. +# AST_EXT_TOOL_CHECK looks for a symbol in using $1-config to determine CFLAGS and LIBS +# +# They are only run if PBX_$1 != 1 (where $1 is the package), +# so you can call them multiple times and stop at the first matching one. +# On success, they both set PBX_$1 = 1, set $1_INCLUDE and $1_LIB as applicable, +# and also #define HAVE_$1 1 and #define HAVE_$1_VERSION ${last_argument} +# in autoconfig.h so you can tell which test succeeded. +# They should be called after AST_EXT_LIB_SETUP($1, ...) + +# Check if a given macro is defined in a certain header. + +# AST_C_DEFINE_CHECK([package], [macro name], [header file], [version]) +AC_DEFUN([AST_C_DEFINE_CHECK], +[ + if test "x${PBX_$1}" != "x1"; then + AC_MSG_CHECKING([for $2 in $3]) + saved_cppflags="${CPPFLAGS}" + if test "x${$1_DIR}" != "x"; then + $1_INCLUDE="-I${$1_DIR}/include" + fi + CPPFLAGS="${CPPFLAGS} ${$1_INCLUDE}" + + AC_COMPILE_IFELSE( + [ AC_LANG_PROGRAM( [#include <$3>], + [#if defined($2) + int foo = 0; + #else + int foo = bar; + #endif + 0 + ])], + [ AC_MSG_RESULT(yes) + PBX_$1=1 + AC_DEFINE([HAVE_$1], 1, [Define if your system has the $1 headers.]) + AC_DEFINE([HAVE_$1_VERSION], $4, [Define $1 headers version]) + ], + [ AC_MSG_RESULT(no) ] + ) + CPPFLAGS="${saved_cppflags}" + fi + AC_SUBST(PBX_$1) +]) + +# AST_C_COMPILE_CHECK can be used for testing for various items in header files + +# AST_C_COMPILE_CHECK([package], [expression], [header file], [version], [description]) +AC_DEFUN([AST_C_COMPILE_CHECK], +[ + if test "x${PBX_$1}" != "x1" -a "${USE_$1}" != "no"; then + if test "x$5" != "x"; then + AC_MSG_CHECKING([for $5]) + else + AC_MSG_CHECKING([if "$2" compiles using $3]) + fi + saved_cppflags="${CPPFLAGS}" + if test "x${$1_DIR}" != "x"; then + $1_INCLUDE="-I${$1_DIR}/include" + fi + CPPFLAGS="${CPPFLAGS} ${$1_INCLUDE}" + + AC_COMPILE_IFELSE( + [ AC_LANG_PROGRAM( [#include <$3>], + [ $2; ] + )], + [ AC_MSG_RESULT(yes) + PBX_$1=1 + AC_DEFINE([HAVE_$1], 1, [Define if your system has the $1 headers.]) + AC_DEFINE([HAVE_$1_VERSION], $4, [Define $1 headers version]) + ], + [ AC_MSG_RESULT(no) ] + ) + CPPFLAGS="${saved_cppflags}" + fi +]) + +AC_DEFUN( +[AST_CHECK_GNU_MAKE], [AC_CACHE_CHECK(for GNU make, GNU_MAKE, + GNU_MAKE='Not Found' ; + GNU_MAKE_VERSION_MAJOR=0 ; + GNU_MAKE_VERSION_MINOR=0 ; + for a in make gmake gnumake ; do + if test -z "$a" ; then continue ; fi ; + if ( sh -c "$a --version" 2> /dev/null | grep GNU 2>&1 > /dev/null ) ; then + GNU_MAKE=$a ; + GNU_MAKE_VERSION_MAJOR=`$GNU_MAKE --version | grep "GNU Make" | cut -f3 -d' ' | cut -f1 -d'.'` + GNU_MAKE_VERSION_MINOR=`$GNU_MAKE --version | grep "GNU Make" | cut -f2 -d'.' | cut -c1-2` + break; + fi + done ; +) ; +if test "x$GNU_MAKE" = "xNot Found" ; then + AC_MSG_ERROR( *** Please install GNU make. It is required to build Asterisk!) + exit 1 +fi +AC_SUBST([GNU_MAKE]) +]) + + +AC_DEFUN( +[AST_CHECK_PWLIB], [ +PWLIB_INCDIR= +PWLIB_LIBDIR= +AC_LANG_PUSH([C++]) +if test "${PWLIBDIR:-unset}" != "unset" ; then + AC_CHECK_HEADER(${PWLIBDIR}/version.h, HAS_PWLIB=1, ) +fi +if test "${HAS_PWLIB:-unset}" = "unset" ; then + if test "${OPENH323DIR:-unset}" != "unset"; then + AC_CHECK_HEADER(${OPENH323DIR}/../pwlib/version.h, HAS_PWLIB=1, ) + fi + if test "${HAS_PWLIB:-unset}" != "unset" ; then + PWLIBDIR="${OPENH323DIR}/../pwlib" + else + AC_CHECK_HEADER(${HOME}/pwlib/include/ptlib.h, HAS_PWLIB=1, ) + if test "${HAS_PWLIB:-unset}" != "unset" ; then + PWLIBDIR="${HOME}/pwlib" + else + AC_CHECK_HEADER(/usr/local/include/ptlib.h, HAS_PWLIB=1, ) + if test "${HAS_PWLIB:-unset}" != "unset" ; then + AC_PATH_PROG(PTLIB_CONFIG, ptlib-config, , /usr/local/bin) + if test "${PTLIB_CONFIG:-unset}" = "unset" ; then + AC_PATH_PROG(PTLIB_CONFIG, ptlib-config, , /usr/local/share/pwlib/make) + fi + PWLIB_INCDIR="/usr/local/include" + PWLIB_LIBDIR=`${PTLIB_CONFIG} --pwlibdir` + if test "${PWLIB_LIBDIR:-unset}" = "unset"; then + if test "x$LIB64" != "x"; then + PWLIB_LIBDIR="/usr/local/lib64" + else + PWLIB_LIBDIR="/usr/local/lib" + fi + fi + PWLIB_LIB=`${PTLIB_CONFIG} --ldflags --libs` + PWLIB_LIB="-L${PWLIB_LIBDIR} `echo ${PWLIB_LIB}`" + else + AC_CHECK_HEADER(/usr/include/ptlib.h, HAS_PWLIB=1, ) + if test "${HAS_PWLIB:-unset}" != "unset" ; then + AC_PATH_PROG(PTLIB_CONFIG, ptlib-config, , /usr/share/pwlib/make) + PWLIB_INCDIR="/usr/include" + PWLIB_LIBDIR=`${PTLIB_CONFIG} --pwlibdir` + if test "${PWLIB_LIBDIR:-unset}" = "unset"; then + if test "x$LIB64" != "x"; then + PWLIB_LIBDIR="/usr/lib64" + else + PWLIB_LIBDIR="/usr/lib" + fi + fi + PWLIB_LIB=`${PTLIB_CONFIG} --ldflags --libs` + PWLIB_LIB="-L${PWLIB_LIBDIR} `echo ${PWLIB_LIB}`" + fi + fi + fi + fi +fi + +#if test "${HAS_PWLIB:-unset}" = "unset" ; then +# echo "Cannot find pwlib - please install or set PWLIBDIR and try again" +# exit +#fi + +if test "${HAS_PWLIB:-unset}" != "unset" ; then + if test "${PWLIBDIR:-unset}" = "unset" ; then + if test "${PTLIB_CONFIG:-unset}" != "unset" ; then + PWLIBDIR=`$PTLIB_CONFIG --prefix` + else + echo "Cannot find ptlib-config - please install and try again" + exit + fi + fi + + if test "x$PWLIBDIR" = "x/usr" -o "x$PWLIBDIR" = "x/usr/"; then + PWLIBDIR="/usr/share/pwlib" + PWLIB_INCDIR="/usr/include" + if test "x$LIB64" != "x"; then + PWLIB_LIBDIR="/usr/lib64" + else + PWLIB_LIBDIR="/usr/lib" + fi + fi + if test "x$PWLIBDIR" = "x/usr/local" -o "x$PWLIBDIR" = "x/usr/"; then + PWLIBDIR="/usr/local/share/pwlib" + PWLIB_INCDIR="/usr/local/include" + if test "x$LIB64" != "x"; then + PWLIB_LIBDIR="/usr/local/lib64" + else + PWLIB_LIBDIR="/usr/local/lib" + fi + fi + + if test "${PWLIB_INCDIR:-unset}" = "unset"; then + PWLIB_INCDIR="${PWLIBDIR}/include" + fi + if test "${PWLIB_LIBDIR:-unset}" = "unset"; then + PWLIB_LIBDIR="${PWLIBDIR}/lib" + fi + + AC_SUBST([PWLIBDIR]) + AC_SUBST([PWLIB_INCDIR]) + AC_SUBST([PWLIB_LIBDIR]) +fi + AC_LANG_POP([C++]) +]) + + +AC_DEFUN( +[AST_CHECK_OPENH323_PLATFORM], [ +PWLIB_OSTYPE= +case "$host_os" in + linux*) PWLIB_OSTYPE=linux ; + ;; + freebsd* ) PWLIB_OSTYPE=FreeBSD ; + ;; + openbsd* ) PWLIB_OSTYPE=OpenBSD ; + ENDLDLIBS="-lossaudio" ; + ;; + netbsd* ) PWLIB_OSTYPE=NetBSD ; + ENDLDLIBS="-lossaudio" ; + ;; + solaris* | sunos* ) PWLIB_OSTYPE=solaris ; + ;; + darwin* ) PWLIB_OSTYPE=Darwin ; + ;; + beos*) PWLIB_OSTYPE=beos ; + STDCCFLAGS="$STDCCFLAGS -D__BEOS__" + ;; + cygwin*) PWLIB_OSTYPE=cygwin ; + ;; + mingw*) PWLIB_OSTYPE=mingw ; + STDCCFLAGS="$STDCCFLAGS -mms-bitfields" ; + ENDLDLIBS="-lwinmm -lwsock32 -lsnmpapi -lmpr -lcomdlg32 -lgdi32 -lavicap32" ; + ;; + * ) PWLIB_OSTYPE="$host_os" ; + AC_MSG_WARN("OS $PWLIB_OSTYPE not recognized - proceed with caution!") ; + ;; +esac + +PWLIB_MACHTYPE= +case "$host_cpu" in + x86 | i686 | i586 | i486 | i386 ) PWLIB_MACHTYPE=x86 + ;; + + x86_64) PWLIB_MACHTYPE=x86_64 ; + P_64BIT=1 ; + LIB64=1 ; + ;; + + alpha | alphaev56 | alphaev6 | alphaev67 | alphaev7) PWLIB_MACHTYPE=alpha ; + P_64BIT=1 ; + ;; + + sparc ) PWLIB_MACHTYPE=sparc ; + ;; + + powerpc ) PWLIB_MACHTYPE=ppc ; + ;; + + ppc ) PWLIB_MACHTYPE=ppc ; + ;; + + powerpc64 ) PWLIB_MACHTYPE=ppc64 ; + P_64BIT=1 ; + LIB64=1 ; + ;; + + ppc64 ) PWLIB_MACHTYPE=ppc64 ; + P_64BIT=1 ; + LIB64=1 ; + ;; + + ia64) PWLIB_MACHTYPE=ia64 ; + P_64BIT=1 ; + ;; + + s390x) PWLIB_MACHTYPE=s390x ; + P_64BIT=1 ; + LIB64=1 ; + ;; + + s390) PWLIB_MACHTYPE=s390 ; + ;; + + * ) PWLIB_MACHTYPE="$host_cpu"; + AC_MSG_WARN("CPU $PWLIB_MACHTYPE not recognized - proceed with caution!") ;; +esac + +PWLIB_PLATFORM="${PWLIB_OSTYPE}_${PWLIB_MACHTYPE}" + +AC_SUBST([PWLIB_PLATFORM]) +]) + + +AC_DEFUN( +[AST_CHECK_OPENH323], [ +OPENH323_INCDIR= +OPENH323_LIBDIR= +AC_LANG_PUSH([C++]) +if test "${OPENH323DIR:-unset}" != "unset" ; then + AC_CHECK_HEADER(${OPENH323DIR}/version.h, HAS_OPENH323=1, ) +fi +if test "${HAS_OPENH323:-unset}" = "unset" ; then + AC_CHECK_HEADER(${PWLIBDIR}/../openh323/version.h, OPENH323DIR="${PWLIBDIR}/../openh323"; HAS_OPENH323=1, ) + if test "${HAS_OPENH323:-unset}" != "unset" ; then + OPENH323DIR="${PWLIBDIR}/../openh323" + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} -I${PWLIB_INCDIR}/openh323 -I${PWLIB_INCDIR}" + AC_CHECK_HEADER(${OPENH323DIR}/include/h323.h, , OPENH323_INCDIR="${PWLIB_INCDIR}/openh323"; OPENH323_LIBDIR="${PWLIB_LIBDIR}", [#include ]) + CPPFLAGS="${saved_cppflags}" + else + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} -I${HOME}/openh323/include -I${PWLIB_INCDIR}" + AC_CHECK_HEADER(${HOME}/openh323/include/h323.h, HAS_OPENH323=1, ) + CPPFLAGS="${saved_cppflags}" + if test "${HAS_OPENH323:-unset}" != "unset" ; then + OPENH323DIR="${HOME}/openh323" + else + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} -I/usr/local/include/openh323 -I${PWLIB_INCDIR}" + AC_CHECK_HEADER(/usr/local/include/openh323/h323.h, HAS_OPENH323=1, ) + CPPFLAGS="${saved_cppflags}" + if test "${HAS_OPENH323:-unset}" != "unset" ; then + OPENH323DIR="/usr/local/share/openh323" + OPENH323_INCDIR="/usr/local/include/openh323" + if test "x$LIB64" != "x"; then + OPENH323_LIBDIR="/usr/local/lib64" + else + OPENH323_LIBDIR="/usr/local/lib" + fi + else + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} -I/usr/include/openh323 -I${PWLIB_INCDIR}" + AC_CHECK_HEADER(/usr/include/openh323/h323.h, HAS_OPENH323=1, , [#include ]) + CPPFLAGS="${saved_cppflags}" + if test "${HAS_OPENH323:-unset}" != "unset" ; then + OPENH323DIR="/usr/share/openh323" + OPENH323_INCDIR="/usr/include/openh323" + if test "x$LIB64" != "x"; then + OPENH323_LIBDIR="/usr/lib64" + else + OPENH323_LIBDIR="/usr/lib" + fi + fi + fi + fi + fi +fi + +if test "${HAS_OPENH323:-unset}" != "unset" ; then + if test "${OPENH323_INCDIR:-unset}" = "unset"; then + OPENH323_INCDIR="${OPENH323DIR}/include" + fi + if test "${OPENH323_LIBDIR:-unset}" = "unset"; then + OPENH323_LIBDIR="${OPENH323DIR}/lib" + fi + + OPENH323_LIBDIR="`cd ${OPENH323_LIBDIR}; pwd`" + OPENH323_INCDIR="`cd ${OPENH323_INCDIR}; pwd`" + OPENH323DIR="`cd ${OPENH323DIR}; pwd`" + + AC_SUBST([OPENH323DIR]) + AC_SUBST([OPENH323_INCDIR]) + AC_SUBST([OPENH323_LIBDIR]) +fi + AC_LANG_POP([C++]) +]) + + +AC_DEFUN( +[AST_CHECK_PWLIB_VERSION], [ + if test "${HAS_$2:-unset}" != "unset"; then + $2_VERSION=`grep "$2_VERSION" ${$2_INCDIR}/$3 | sed -e 's/[[[:space:]]]\{1,\}/ /g' | cut -f3 -d ' ' | sed -e 's/"//g'` + $2_MAJOR_VERSION=`echo ${$2_VERSION} | cut -f1 -d.` + $2_MINOR_VERSION=`echo ${$2_VERSION} | cut -f2 -d.` + $2_BUILD_NUMBER=`echo ${$2_VERSION} | cut -f3 -d.` + let $2_VER=${$2_MAJOR_VERSION}*10000+${$2_MINOR_VERSION}*100+${$2_BUILD_NUMBER} + let $2_REQ=$4*10000+$5*100+$6 + + AC_MSG_CHECKING(if $1 version ${$2_VERSION} is compatible with chan_h323) + if test ${$2_VER} -lt ${$2_REQ}; then + AC_MSG_RESULT(no) + unset HAS_$2 + else + AC_MSG_RESULT(yes) + fi + fi +]) + + +AC_DEFUN( +[AST_CHECK_PWLIB_BUILD], [ + if test "${HAS_$2:-unset}" != "unset"; then + AC_MSG_CHECKING($1 installation validity) + + saved_cppflags="${CPPFLAGS}" + saved_libs="${LIBS}" + if test "${$2_LIB:-unset}" != "unset"; then + LIBS="${LIBS} ${$2_LIB} $7" + else + LIBS="${LIBS} -L${$2_LIBDIR} -l${PLATFORM_$2} $7" + fi + CPPFLAGS="${CPPFLAGS} -I${$2_INCDIR} $6" + + AC_LANG_PUSH([C++]) + + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([$4],[$5])], + [ AC_MSG_RESULT(yes) + ac_cv_lib_$2="yes" + ], + [ AC_MSG_RESULT(no) + ac_cv_lib_$2="no" + ] + ) + + AC_LANG_POP([C++]) + + LIBS="${saved_libs}" + CPPFLAGS="${saved_cppflags}" + + if test "${ac_cv_lib_$2}" = "yes"; then + if test "${$2_LIB:-undef}" = "undef"; then + if test "${$2_LIBDIR}" != "" -a "${$2_LIBDIR}" != "/usr/lib"; then + $2_LIB="-L${$2_LIBDIR} -l${PLATFORM_$2}" + else + $2_LIB="-l${PLATFORM_$2}" + fi + fi + if test "${$2_INCDIR}" != "" -a "${$2_INCDIR}" != "/usr/include"; then + $2_INCLUDE="-I${$2_INCDIR}" + fi + PBX_$2=1 + AC_DEFINE([HAVE_$2], 1, [$3]) + fi + fi +]) + +AC_DEFUN( +[AST_CHECK_OPENH323_BUILD], [ + if test "${HAS_OPENH323:-unset}" != "unset"; then + AC_MSG_CHECKING(OpenH323 build option) + OPENH323_SUFFIX= + prefixes="h323_${PWLIB_PLATFORM}_ h323_ openh323" + for pfx in $prefixes; do + files=`ls -l ${OPENH323_LIBDIR}/lib${pfx}*.so* 2>/dev/null` + libfile= + if test -n "$files"; then + for f in $files; do + if test -f $f -a ! -L $f; then + libfile=`basename $f` + break; + fi + done + fi + if test -n "$libfile"; then + OPENH323_PREFIX=$pfx + break; + fi + done + if test "${libfile:-unset}" != "unset"; then + OPENH323_SUFFIX=`eval "echo ${libfile} | sed -e 's/lib${OPENH323_PREFIX}\(@<:@^.@:>@*\)\..*/\1/'"` + fi + case "${OPENH323_SUFFIX}" in + n) + OPENH323_BUILD="notrace";; + r) + OPENH323_BUILD="opt";; + d) + OPENH323_BUILD="debug";; + *) + if test "${OPENH323_PREFIX:-undef}" = "openh323"; then + notrace=`eval "grep NOTRACE ${OPENH323DIR}/openh323u.mak | grep = | sed -e 's/@<:@A-Z0-9_@:>@*@<:@ @:>@*=@<:@ @:>@*//'"` + if test "x$notrace" = "x"; then + notrace="0" + fi + if test "$notrace" -ne 0; then + OPENH323_BUILD="notrace" + else + OPENH323_BUILD="opt" + fi + OPENH323_LIB="-l${OPENH323_PREFIX}" + else + OPENH323_BUILD="notrace" + fi + ;; + esac + AC_MSG_RESULT(${OPENH323_BUILD}) + + AC_SUBST([OPENH323_SUFFIX]) + AC_SUBST([OPENH323_BUILD]) + fi +]) + + +# AST_FUNC_FORK +# ------------- +AN_FUNCTION([fork], [AST_FUNC_FORK]) +AN_FUNCTION([vfork], [AST_FUNC_FORK]) +AC_DEFUN([AST_FUNC_FORK], +[AC_REQUIRE([AC_TYPE_PID_T])dnl +AC_CHECK_HEADERS(vfork.h) +AC_CHECK_FUNCS(fork vfork) +if test "x$ac_cv_func_fork" = xyes; then + _AST_FUNC_FORK +else + ac_cv_func_fork_works=$ac_cv_func_fork +fi +if test "x$ac_cv_func_fork_works" = xcross; then + case $host in + *-*-amigaos* | *-*-msdosdjgpp* | *-*-uclinux* | *-*-linux-uclibc* ) + # Override, as these systems have only a dummy fork() stub + ac_cv_func_fork_works=no + ;; + *) + ac_cv_func_fork_works=yes + ;; + esac + AC_MSG_WARN([result $ac_cv_func_fork_works guessed because of cross compilation]) +fi +ac_cv_func_vfork_works=$ac_cv_func_vfork +if test "x$ac_cv_func_vfork" = xyes; then + _AC_FUNC_VFORK +fi; +if test "x$ac_cv_func_fork_works" = xcross; then + ac_cv_func_vfork_works=$ac_cv_func_vfork + AC_MSG_WARN([result $ac_cv_func_vfork_works guessed because of cross compilation]) +fi + +if test "x$ac_cv_func_vfork_works" = xyes; then + AC_DEFINE(HAVE_WORKING_VFORK, 1, [Define to 1 if `vfork' works.]) +else + AC_DEFINE(vfork, fork, [Define as `fork' if `vfork' does not work.]) +fi +if test "x$ac_cv_func_fork_works" = xyes; then + AC_DEFINE(HAVE_WORKING_FORK, 1, [Define to 1 if `fork' works.]) +fi +])# AST_FUNC_FORK + + +# _AST_FUNC_FORK +# ------------- +AC_DEFUN([_AST_FUNC_FORK], + [AC_CACHE_CHECK(for working fork, ac_cv_func_fork_works, + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [ + /* By Ruediger Kuhlmann. */ + return fork () < 0; + ])], + [ac_cv_func_fork_works=yes], + [ac_cv_func_fork_works=no], + [ac_cv_func_fork_works=cross])])] +)# _AST_FUNC_FORK + +# AST_PROG_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([AST_PROG_LD], +[AC_ARG_WITH([gnu-ld], + [AC_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test "$withval" = no || with_gnu_ld=yes], + [with_gnu_ld=no]) +AC_REQUIRE([AST_PROG_SED])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo $ac_prog| $SED 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$lt_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 /dev/null 2>&1 + then ac_cv_prog_egrep='grep -E' + else ac_cv_prog_egrep='egrep' + fi]) + EGREP=$ac_cv_prog_egrep + AC_SUBST([EGREP]) +])]) # AST_PROG_EGREP + +# AST_PROG_SED +# ----------- +# Check for a fully functional sed program that truncates +# as few characters as possible. Prefer GNU sed if found. +AC_DEFUN([AST_PROG_SED], +[AC_CACHE_CHECK([for a sed that does not truncate output], ac_cv_path_SED, + [dnl ac_script should not contain more than 99 commands (for HP-UX sed), + dnl but more than about 7000 bytes, to catch a limit in Solaris 8 /usr/ucb/sed. + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" | sed 99q >conftest.sed + $as_unset ac_script || ac_script= + _AC_PATH_PROG_FEATURE_CHECK(SED, [sed gsed], + [_AC_FEATURE_CHECK_LENGTH([ac_path_SED], [ac_cv_path_SED], + ["$ac_path_SED" -f conftest.sed])])]) + SED="$ac_cv_path_SED" + AC_SUBST([SED])dnl + rm -f conftest.sed +])# AST_PROG_SED + +dnl @synopsis ACX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +dnl +dnl @summary figure out how to build C programs using POSIX threads +dnl +dnl This macro figures out how to build C programs using POSIX threads. +dnl It sets the PTHREAD_LIBS output variable to the threads library and +dnl linker flags, and the PTHREAD_CFLAGS output variable to any special +dnl C compiler flags that are needed. (The user can also force certain +dnl compiler flags/libs to be tested by setting these environment +dnl variables.) +dnl +dnl Also sets PTHREAD_CC to any special C compiler that is needed for +dnl multi-threaded programs (defaults to the value of CC otherwise). +dnl (This is necessary on AIX to use the special cc_r compiler alias.) +dnl +dnl NOTE: You are assumed to not only compile your program with these +dnl flags, but also link it with them as well. e.g. you should link +dnl with $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS +dnl $LIBS +dnl +dnl If you are only building threads programs, you may wish to use +dnl these variables in your default LIBS, CFLAGS, and CC: +dnl +dnl LIBS="$PTHREAD_LIBS $LIBS" +dnl CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +dnl CC="$PTHREAD_CC" +dnl +dnl In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute +dnl constant has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to +dnl that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +dnl +dnl ACTION-IF-FOUND is a list of shell commands to run if a threads +dnl library is found, and ACTION-IF-NOT-FOUND is a list of commands to +dnl run it if it is not found. If ACTION-IF-FOUND is not specified, the +dnl default action will define HAVE_PTHREAD. +dnl +dnl Please let the authors know if this macro fails on any platform, or +dnl if you have any other suggestions or comments. This macro was based +dnl on work by SGJ on autoconf scripts for FFTW (www.fftw.org) (with +dnl help from M. Frigo), as well as ac_pthread and hb_pthread macros +dnl posted by Alejandro Forero Cuervo to the autoconf macro repository. +dnl We are also grateful for the helpful feedback of numerous users. +dnl +dnl @category InstalledPackages +dnl @author Steven G. Johnson +dnl @version 2006-05-29 +dnl @license GPLWithACException + +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthreads pthread -mt -pthread $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_TRY_LINK([#include ], [int attr=$attr; return attr;], + [attr_name=$attr; break]) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with xlc_r or cc_r + if test x"$GCC" != xyes; then + AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) + else + PTHREAD_CC=$CC + fi +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD diff --git a/asterisk/agi/DialAnMp3.agi b/asterisk/agi/DialAnMp3.agi new file mode 100644 index 00000000..59a54265 --- /dev/null +++ b/asterisk/agi/DialAnMp3.agi @@ -0,0 +1,82 @@ +#!/usr/bin/perl +# +# Simple AGI application to play mp3's selected by a user both using +# xmms and over the phone itself. +# +$|=1; +while() { + chomp; + last unless length($_); + if (/^agi_(\w+)\:\s+(.*)$/) { + $AGI{$1} = $2; + } +} + +print STDERR "AGI Environment Dump:\n"; +foreach $i (sort keys %AGI) { + print STDERR " -- $i = $AGI{$i}\n"; +} + +dbmopen(%DIGITS, "/var/lib/asterisk/mp3list", 0644) || die("Unable to open mp3list");; + +sub checkresult { + my ($res) = @_; + my $retval; + $tests++; + chomp $res; + if ($res =~ /^200/) { + $res =~ /result=(-?[\w\*\#]+)/; + return $1; + } else { + return -1; + } +} + +#print STDERR "1. Playing beep...\n"; +#print "STREAM FILE beep \"\"\n"; +#$result = ; +#checkresult($result); + +print STDERR "2. Getting song name...\n"; +print "GET DATA demo-enterkeywords\n"; +$result = ; +$digitstr = checkresult($result); +if ($digitstr < 0) { + exit(1); +} +$digitstr =~ s/\*/ /g; + +print STDERR "Resulting songname is $digitstr\n"; +@searchwords = split (/\s+/, $digitstr); +print STDERR "Searchwords: " . join(':', @searchwords) . "\n"; + +foreach $key (sort keys %DIGITS) { + @words = split(/\s+/, $DIGITS{$key}); + $match = 1; + foreach $search (@searchwords) { + $match = 0 unless grep(/$search/, @words); + } + if ($match > 0) { + print STDERR "File $key matches\n"; + # Play a beep + print "STREAM FILE beep \"\"\n"; + system("xmms", $key); + $result = ; + if (&checkresult($result) < 0) { + exit 0; + } + print "EXEC MP3Player \"$key\"\n"; +# print "WAIT FOR DIGIT 60000\n"; + $result = ; + if (&checkresult($result) < 0) { + exit 0; + } + print STDERR "Got here...\n"; + } +} + +print STDERR "4. Testing 'saynumber' of $digitstr...\n"; +print "STREAM FILE demo-nomatch\"\"\n"; +$result = ; +checkresult($result); + diff --git a/asterisk/agi/Makefile b/asterisk/agi/Makefile new file mode 100644 index 00000000..c24c7f10 --- /dev/null +++ b/asterisk/agi/Makefile @@ -0,0 +1,47 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Makefile for AGI-related stuff +# +# Copyright (C) 1999-2006, Digium +# +# Mark Spencer +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +.PHONY: clean all uninstall + +AGIS=agi-test.agi eagi-test eagi-sphinx-test jukebox.agi + +ifeq ($(OSARCH),SunOS) + LIBS+=-lsocket -lnsl +endif + +include $(ASTTOPDIR)/Makefile.rules + +all: $(AGIS) + +strcompat.c: ../main/strcompat.c + @cp $< $@ + +eagi-test: eagi-test.o strcompat.o + +eagi-sphinx-test: eagi-sphinx-test.o + +install: all + mkdir -p $(DESTDIR)$(AGI_DIR) + for x in $(AGIS); do $(INSTALL) -m 755 $$x $(DESTDIR)$(AGI_DIR) ; done + +uninstall: + for x in $(AGIS); do rm -f $(DESTDIR)$(AGI_DIR)/$$x ; done + +clean: + rm -f *.so *.o look eagi-test eagi-sphinx-test + rm -f .*.o.d .*.oo.d *.s *.i + rm -f strcompat.c + +ifneq ($(wildcard .*.d),) + include .*.d +endif diff --git a/asterisk/agi/agi-test.agi b/asterisk/agi/agi-test.agi new file mode 100644 index 00000000..4fc36eda --- /dev/null +++ b/asterisk/agi/agi-test.agi @@ -0,0 +1,79 @@ +#!/usr/bin/perl +use strict; + +$|=1; + +# Setup some variables +my %AGI; my $tests = 0; my $fail = 0; my $pass = 0; + +while() { + chomp; + last unless length($_); + if (/^agi_(\w+)\:\s+(.*)$/) { + $AGI{$1} = $2; + } +} + +print STDERR "AGI Environment Dump:\n"; +foreach my $i (sort keys %AGI) { + print STDERR " -- $i = $AGI{$i}\n"; +} + +sub checkresult { + my ($res) = @_; + my $retval; + $tests++; + chomp $res; + if ($res =~ /^200/) { + $res =~ /result=(-?\d+)/; + if (!length($1)) { + print STDERR "FAIL ($res)\n"; + $fail++; + } else { + print STDERR "PASS ($1)\n"; + $pass++; + } + } else { + print STDERR "FAIL (unexpected result '$res')\n"; + $fail++; + } +} + +print STDERR "1. Testing 'sendfile'..."; +print "STREAM FILE beep \"\"\n"; +my $result = ; +&checkresult($result); + +print STDERR "2. Testing 'sendtext'..."; +print "SEND TEXT \"hello world\"\n"; +my $result = ; +&checkresult($result); + +print STDERR "3. Testing 'sendimage'..."; +print "SEND IMAGE asterisk-image\n"; +my $result = ; +&checkresult($result); + +print STDERR "4. Testing 'saynumber'..."; +print "SAY NUMBER 192837465 \"\"\n"; +my $result = ; +&checkresult($result); + +print STDERR "5. Testing 'waitdtmf'..."; +print "WAIT FOR DIGIT 1000\n"; +my $result = ; +&checkresult($result); + +print STDERR "6. Testing 'record'..."; +print "RECORD FILE testagi gsm 1234 3000\n"; +my $result = ; +&checkresult($result); + +print STDERR "6a. Testing 'record' playback..."; +print "STREAM FILE testagi \"\"\n"; +my $result = ; +&checkresult($result); + +print STDERR "================== Complete ======================\n"; +print STDERR "$tests tests completed, $pass passed, $fail failed\n"; +print STDERR "==================================================\n"; diff --git a/asterisk/agi/eagi-sphinx-test.c b/asterisk/agi/eagi-sphinx-test.c new file mode 100644 index 00000000..0ad12c78 --- /dev/null +++ b/asterisk/agi/eagi-sphinx-test.c @@ -0,0 +1,222 @@ +/* + * Extended AGI test application + * + * This code is released into public domain + * without any warranty of any kind. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk.h" + +#include "asterisk/compat.h" + +#define AUDIO_FILENO (STDERR_FILENO + 1) + +#define SPHINX_HOST "192.168.1.108" +#define SPHINX_PORT 3460 + +static int sphinx_sock = -1; + +static int connect_sphinx(void) +{ + struct hostent *hp; + struct sockaddr_in sin; + int res; + hp = gethostbyname(SPHINX_HOST); + if (!hp) { + fprintf(stderr, "Unable to resolve '%s'\n", SPHINX_HOST); + return -1; + } + sphinx_sock = socket(PF_INET, SOCK_STREAM, 0); + if (sphinx_sock < 0) { + fprintf(stderr, "Unable to allocate socket: %s\n", strerror(errno)); + return -1; + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(SPHINX_PORT); + memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr)); + if (connect(sphinx_sock, (struct sockaddr *)&sin, sizeof(sin))) { + fprintf(stderr, "Unable to connect on socket: %s\n", strerror(errno)); + close(sphinx_sock); + sphinx_sock = -1; + return -1; + } + res = fcntl(sphinx_sock, F_GETFL); + if ((res < 0) || (fcntl(sphinx_sock, F_SETFL, res | O_NONBLOCK) < 0)) { + fprintf(stderr, "Unable to set flags on socket: %s\n", strerror(errno)); + close(sphinx_sock); + sphinx_sock = -1; + return -1; + } + return 0; +} + +static int read_environment(void) +{ + char buf[256]; + char *val; + /* Read environment */ + for(;;) { + fgets(buf, sizeof(buf), stdin); + if (feof(stdin)) + return -1; + buf[strlen(buf) - 1] = '\0'; + /* Check for end of environment */ + if (!strlen(buf)) + return 0; + val = strchr(buf, ':'); + if (!val) { + fprintf(stderr, "Invalid environment: '%s'\n", buf); + return -1; + } + *val = '\0'; + val++; + val++; + /* Skip space */ + fprintf(stderr, "Environment: '%s' is '%s'\n", buf, val); + + /* Load into normal environment */ + setenv(buf, val, 1); + + } + /* Never reached */ + return 0; +} + +static char *wait_result(void) +{ + fd_set fds; + int res; + int max; + static char astresp[256]; + static char sphinxresp[256]; + char audiobuf[4096]; + for (;;) { + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + FD_SET(AUDIO_FILENO, &fds); + max = AUDIO_FILENO; + if (sphinx_sock > -1) { + FD_SET(sphinx_sock, &fds); + if (sphinx_sock > max) + max = sphinx_sock; + } + /* Wait for *some* sort of I/O */ + res = select(max + 1, &fds, NULL, NULL, NULL); + if (res < 0) { + fprintf(stderr, "Error in select: %s\n", strerror(errno)); + return NULL; + } + if (FD_ISSET(STDIN_FILENO, &fds)) { + fgets(astresp, sizeof(astresp), stdin); + if (feof(stdin)) { + fprintf(stderr, "Got hungup on apparently\n"); + return NULL; + } + astresp[strlen(astresp) - 1] = '\0'; + fprintf(stderr, "Ooh, got a response from Asterisk: '%s'\n", astresp); + return astresp; + } + if (FD_ISSET(AUDIO_FILENO, &fds)) { + res = read(AUDIO_FILENO, audiobuf, sizeof(audiobuf)); + if (res > 0) { + if (sphinx_sock > -1) + write(sphinx_sock, audiobuf, res); + } + } + if ((sphinx_sock > -1) && FD_ISSET(sphinx_sock, &fds)) { + res = read(sphinx_sock, sphinxresp, sizeof(sphinxresp)); + if (res > 0) { + fprintf(stderr, "Oooh, Sphinx found a token: '%s'\n", sphinxresp); + return sphinxresp; + } else if (res == 0) { + fprintf(stderr, "Hrm, lost sphinx, guess we're on our own\n"); + close(sphinx_sock); + sphinx_sock = -1; + } + } + } + +} + +static char *run_command(char *command) +{ + fprintf(stdout, "%s\n", command); + return wait_result(); +} + +static int run_script(void) +{ + char *res; + res = run_command("STREAM FILE demo-enterkeywords 0123456789*#"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "1. Result is '%s'\n", res); + res = run_command("STREAM FILE demo-nomatch 0123456789*#"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "2. Result is '%s'\n", res); + res = run_command("SAY NUMBER 23452345 0123456789*#"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "3. Result is '%s'\n", res); + res = run_command("GET DATA demo-enterkeywords"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "4. Result is '%s'\n", res); + res = run_command("STREAM FILE auth-thankyou \"\""); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "5. Result is '%s'\n", res); + return 0; +} + +int main(int argc, char *argv[]) +{ + char *tmp; + int ver = 0; + int subver = 0; + /* Setup stdin/stdout for line buffering */ + setlinebuf(stdin); + setlinebuf(stdout); + if (read_environment()) { + fprintf(stderr, "Failed to read environment: %s\n", strerror(errno)); + exit(1); + } + connect_sphinx(); + tmp = getenv("agi_enhanced"); + if (tmp) { + if (sscanf(tmp, "%d.%d", &ver, &subver) != 2) + ver = 0; + } + if (ver < 1) { + fprintf(stderr, "No enhanced AGI services available. Use EAGI, not AGI\n"); + exit(1); + } + if (run_script()) + return -1; + exit(0); +} diff --git a/asterisk/agi/eagi-test.c b/asterisk/agi/eagi-test.c new file mode 100644 index 00000000..7745d18a --- /dev/null +++ b/asterisk/agi/eagi-test.c @@ -0,0 +1,165 @@ +/* + * Extended AGI test application + * + * This code is released into the public domain + * with no warranty of any kind + */ + +#include +#include +#include +#include +#include +#include + +#include "asterisk.h" + +#include "asterisk/compat.h" + +#define AUDIO_FILENO (STDERR_FILENO + 1) + +static int read_environment(void) +{ + char buf[256]; + char *val; + /* Read environment */ + for(;;) { + fgets(buf, sizeof(buf), stdin); + if (feof(stdin)) + return -1; + buf[strlen(buf) - 1] = '\0'; + /* Check for end of environment */ + if (!strlen(buf)) + return 0; + val = strchr(buf, ':'); + if (!val) { + fprintf(stderr, "Invalid environment: '%s'\n", buf); + return -1; + } + *val = '\0'; + val++; + val++; + /* Skip space */ + fprintf(stderr, "Environment: '%s' is '%s'\n", buf, val); + + /* Load into normal environment */ + setenv(buf, val, 1); + + } + /* Never reached */ + return 0; +} + +static char *wait_result(void) +{ + fd_set fds; + int res; + int bytes = 0; + static char astresp[256]; + char audiobuf[4096]; + for (;;) { + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + FD_SET(AUDIO_FILENO, &fds); + /* Wait for *some* sort of I/O */ + res = select(AUDIO_FILENO + 1, &fds, NULL, NULL, NULL); + if (res < 0) { + fprintf(stderr, "Error in select: %s\n", strerror(errno)); + return NULL; + } + if (FD_ISSET(STDIN_FILENO, &fds)) { + fgets(astresp, sizeof(astresp), stdin); + if (feof(stdin)) { + fprintf(stderr, "Got hungup on apparently\n"); + return NULL; + } + astresp[strlen(astresp) - 1] = '\0'; + fprintf(stderr, "Ooh, got a response from Asterisk: '%s'\n", astresp); + return astresp; + } + if (FD_ISSET(AUDIO_FILENO, &fds)) { + res = read(AUDIO_FILENO, audiobuf, sizeof(audiobuf)); + if (res > 0) { + /* XXX Process the audio with sphinx here XXX */ +#if 0 + fprintf(stderr, "Got %d/%d bytes of audio\n", res, bytes); +#endif + bytes += res; + /* Prentend we detected some audio after 3 seconds */ + if (bytes > 16000 * 3) { + return "Sample Message"; + bytes = 0; + } + } + } + } + +} + +static char *run_command(char *command) +{ + fprintf(stdout, "%s\n", command); + return wait_result(); +} + +static int run_script(void) +{ + char *res; + res = run_command("STREAM FILE demo-enterkeywords 0123456789*#"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "1. Result is '%s'\n", res); + res = run_command("STREAM FILE demo-nomatch 0123456789*#"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "2. Result is '%s'\n", res); + res = run_command("SAY NUMBER 23452345 0123456789*#"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "3. Result is '%s'\n", res); + res = run_command("GET DATA demo-enterkeywords"); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "4. Result is '%s'\n", res); + res = run_command("STREAM FILE auth-thankyou \"\""); + if (!res) { + fprintf(stderr, "Failed to execute command\n"); + return -1; + } + fprintf(stderr, "5. Result is '%s'\n", res); + return 0; +} + +int main(int argc, char *argv[]) +{ + char *tmp; + int ver = 0; + int subver = 0; + /* Setup stdin/stdout for line buffering */ + setlinebuf(stdin); + setlinebuf(stdout); + if (read_environment()) { + fprintf(stderr, "Failed to read environment: %s\n", strerror(errno)); + exit(1); + } + tmp = getenv("agi_enhanced"); + if (tmp) { + if (sscanf(tmp, "%d.%d", &ver, &subver) != 2) + ver = 0; + } + if (ver < 1) { + fprintf(stderr, "No enhanced AGI services available. Use EAGI, not AGI\n"); + exit(1); + } + if (run_script()) + return -1; + exit(0); +} diff --git a/asterisk/agi/fastagi-test b/asterisk/agi/fastagi-test new file mode 100644 index 00000000..d3f13cf6 --- /dev/null +++ b/asterisk/agi/fastagi-test @@ -0,0 +1,94 @@ +#!/usr/bin/perl +use strict; +use Socket; +use Carp; +use IO::Handle; + +my $port = 4573; + +$|=1; + +# Setup some variables +my %AGI; my $tests = 0; my $fail = 0; my $pass = 0; + +sub checkresult { + my ($res) = @_; + my $retval; + $tests++; + chomp $res; + if ($res =~ /^200/) { + $res =~ /result=(-?\d+)/; + if (!length($1)) { + print STDERR "FAIL ($res)\n"; + $fail++; + } else { + print STDERR "PASS ($1)\n"; + $pass++; + } + } else { + print STDERR "FAIL (unexpected result '$res')\n"; + $fail++; + } +} + +socket(SERVER, PF_INET, SOCK_STREAM, 0); +setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)); +bind(SERVER, sockaddr_in($port, INADDR_ANY)) || die("can't bind\n"); +listen(SERVER, SOMAXCONN); + +for(;;) { + my $raddr = accept(CLIENT, SERVER); + my ($s, $p) = sockaddr_in($raddr); + CLIENT->autoflush(1); + while() { + chomp; + last unless length($_); + if (/^agi_(\w+)\:\s+(.*)$/) { + $AGI{$1} = $2; + } + } + print STDERR "AGI Environment Dump from $s:$p --\n"; + foreach my $i (sort keys %AGI) { + print STDERR " -- $i = $AGI{$i}\n"; + } + + print STDERR "1. Testing 'sendfile'..."; + print CLIENT "STREAM FILE beep \"\"\n"; + my $result = ; + &checkresult($result); + + print STDERR "2. Testing 'sendtext'..."; + print CLIENT "SEND TEXT \"hello world\"\n"; + my $result = ; + &checkresult($result); + + print STDERR "3. Testing 'sendimage'..."; + print CLIENT "SEND IMAGE asterisk-image\n"; + my $result = ; + &checkresult($result); + + print STDERR "4. Testing 'saynumber'..."; + print CLIENT "SAY NUMBER 192837465 \"\"\n"; + my $result = ; + &checkresult($result); + + print STDERR "5. Testing 'waitdtmf'..."; + print CLIENT "WAIT FOR DIGIT 1000\n"; + my $result = ; + &checkresult($result); + + print STDERR "6. Testing 'record'..."; + print CLIENT "RECORD FILE testagi gsm 1234 3000\n"; + my $result = ; + &checkresult($result); + + print STDERR "6a. Testing 'record' playback..."; + print CLIENT "STREAM FILE testagi \"\"\n"; + my $result = ; + &checkresult($result); + close(CLIENT); + print STDERR "================== Complete ======================\n"; + print STDERR "$tests tests completed, $pass passed, $fail failed\n"; + print STDERR "==================================================\n"; +} + diff --git a/asterisk/agi/jukebox.agi b/asterisk/agi/jukebox.agi new file mode 100755 index 00000000..7bd9c10f --- /dev/null +++ b/asterisk/agi/jukebox.agi @@ -0,0 +1,488 @@ +#!/usr/bin/perl +# +# Jukebox 0.2 +# +# A music manager for Asterisk. +# +# Copyright (C) 2005-2006, Justin Tunney +# +# Justin Tunney +# +# This program is free software, distributed under the terms of the +# GNU General Public License v2. +# +# Keep it open source pigs +# +# -------------------------------------------------------------------- +# +# Uses festival to list off all your MP3 music files over a channel in +# a hierarchical fashion. Put this file in your agi-bin folder which +# is located at: /var/lib/asterisk/agi-bin Be sure to chmod +x it! +# +# Invocation Example: +# exten => 68742,1,Answer() +# exten => 68742,2,agi,jukebox.agi|/home/justin/Music +# exten => 68742,3,Hangup() +# +# exten => 68742,1,Answer() +# exten => 68742,2,agi,jukebox.agi|/home/justin/Music|pm +# exten => 68742,3,Hangup() +# +# Options: +# p - Precache text2wave outputs for every possible filename. +# It is much better to set this option because if a caller +# presses a key during a cache operation, it will be ignored. +# m - Go back to menu after playing song +# g - Do not play the greeting message +# +# Usage Instructions: +# - Press '*' to go up a directory. If you are in the root music +# folder you will be exitted from the script. +# - If you have a really long list of files, you can filter the list +# at any time by pressing '#' and spelling out a few letters you +# expect the files to start with. For example, if you wanted to +# know what extension 'Requiem For A Dream' was, you'd type: +# '#737'. Note, phone keypads don't include Q and Z. Q is 7 and +# Z is 9. +# +# Notes: +# - This AGI script uses the MP3Player command which uses the +# mpg123 Program. Grab yourself a copy of this program by +# going to http://www.mpg123.de/cgi-bin/sitexplorer.cgi?/mpg123/ +# Be sure to download mpg123-0.59r.tar.gz because it is known to +# work with Asterisk and hopefully isn't the release with that +# awful security problem. If you're using Fedora Core 3 with +# Alsa like me, make linux-alsa isn't going to work. Do make +# linux-devel and you're peachy keen. +# +# - You won't get nifty STDERR debug messages if you're using a +# remote asterisk shell. +# +# - For some reason, caching certain files will generate the +# error: 'using default diphone ax-ax for y-pau'. Example: +# # echo "Depeche Mode - CUW - 05 - The Meaning of Love" | text2wave -o /var/jukeboxcache/jukeboxcache/Depeche_Mode/Depeche_Mode_-_CUW_-_05_-_The_Meaning_of_Love.mp3.ul -otype ulaw - +# The temporary work around is to just touch these files. +# +# - The background app doesn't like to get more than 2031 chars +# of input. +# + +use strict; + +$|=1; + +# Setup some variables +my %AGI; my $tests = 0; my $fail = 0; my $pass = 0; +my @masterCacheList = (); +my $maxNumber = 10; + +while () { + chomp; + last unless length($_); + if (/^agi_(\w+)\:\s+(.*)$/) { + $AGI{$1} = $2; + } +} + +# setup options +my $SHOWGREET = 1; +my $PRECACHE = 0; +my $MENUAFTERSONG = 0; + +$PRECACHE = 1 if $ARGV[1] =~ /p/; +$MENUAFTERSONG = 1 if $ARGV[1] =~ /m/; +$SHOWGREET = 0 if $ARGV[1] =~ /g/; + +# setup folders +my $MUSIC = $ARGV[0]; +$MUSIC = &rmts($MUSIC); +my $FESTIVALCACHE = "/var/jukeboxcache"; +if (! -e $FESTIVALCACHE) { + `mkdir -p -m0776 $FESTIVALCACHE`; +} + +# make sure we have some essential files +if (! -e "$FESTIVALCACHE/jukebox_greet.ul") { + `echo "Welcome to the Asterisk Jukebox" | text2wave -o $FESTIVALCACHE/jukebox_greet.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_press.ul") { + `echo "Press" | text2wave -o $FESTIVALCACHE/jukebox_press.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_for.ul") { + `echo "For" | text2wave -o $FESTIVALCACHE/jukebox_for.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_toplay.ul") { + `echo "To play" | text2wave -o $FESTIVALCACHE/jukebox_toplay.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_nonefound.ul") { + `echo "There were no music files found in this folder" | text2wave -o $FESTIVALCACHE/jukebox_nonefound.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_percent.ul") { + `echo "Percent" | text2wave -o $FESTIVALCACHE/jukebox_percent.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_generate.ul") { + `echo "Please wait while Astrisk Jukebox cashes the files of your music collection" | text2wave -o $FESTIVALCACHE/jukebox_generate.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_invalid.ul") { + `echo "You have entered an invalid selection" | text2wave -o $FESTIVALCACHE/jukebox_invalid.ul -otype ulaw -`; +} +if (! -e "$FESTIVALCACHE/jukebox_thankyou.ul") { + `echo "Thank you for using Astrisk Jukebox, Goodbye" | text2wave -o $FESTIVALCACHE/jukebox_thankyou.ul -otype ulaw -`; +} + +# greet the user +if ($SHOWGREET) { + print "EXEC Playback \"$FESTIVALCACHE/jukebox_greet\"\n"; + my $result = ; &check_result($result); +} + +# go through the directories +music_dir_cache() if $PRECACHE; +music_dir_menu('/'); + +exit 0; + +########################################################################## + +sub music_dir_menu { + my $dir = shift; + +# generate a list of mp3's and directories and assign each one it's +# own selection number. Then make sure that we've got a sound clip +# for the file name + if (!opendir(THEDIR, rmts($MUSIC.$dir))) { + print STDERR "Failed to open music directory: $dir\n"; + exit 1; + } + my @files = sort readdir THEDIR; + my $cnt = 1; + my @masterBgList = (); + + foreach my $file (@files) { + chomp($file); + if ($file ne '.' && $file ne '..' && $file ne 'festivalcache') { # ignore special files + my $real_version = &rmts($MUSIC.$dir).'/'.$file; + my $cache_version = &rmts($FESTIVALCACHE.$dir).'/'.$file.'.ul'; + my $cache_version2 = &rmts($FESTIVALCACHE.$dir).'/'.$file; + my $cache_version_esc = &clean_file($cache_version); + my $cache_version2_esc = &clean_file($cache_version2); + + if (-d $real_version) { +# 0:id 1:type 2:text2wav-file 3:for-filtering 4:the-directory 5:text2wav echo + push(@masterBgList, [$cnt++, 1, $cache_version2_esc, &remove_special_chars($file), $file, "for the $file folder"]); + } elsif ($real_version =~ /\.mp3$/) { +# 0:id 1:type 2:text2wav-file 3:for-filtering 4:the-mp3 + push(@masterBgList, [$cnt++, 2, $cache_version2_esc, &remove_special_chars($file), $real_version, "to play $file"]); + } + } + } + close(THEDIR); + + my @filterList = @masterBgList; + + if (@filterList == 0) { + print "EXEC Playback \"$FESTIVALCACHE/jukebox_nonefound\"\n"; + my $result = ; &check_result($result); + return 0; + } + + for (;;) { +MYCONTINUE: + +# play bg selections and figure out their selection + my $digit = ''; + my $digitstr = ''; + for (my $n=0; $n<@filterList; $n++) { + &cache_speech(&remove_file_extension($filterList[$n][5]), "$filterList[$n][2].ul") if ! -e "$filterList[$n][2].ul"; + &cache_speech("Press $filterList[$n][0]", "$FESTIVALCACHE/jukebox_$filterList[$n][0].ul") if ! -e "$FESTIVALCACHE/jukebox_$filterList[$n][0].ul"; + print "EXEC Background \"$filterList[$n][2]&$FESTIVALCACHE/jukebox_$filterList[$n][0]\"\n"; + my $result = ; + $digit = &check_result($result); + if ($digit > 0) { + $digitstr .= chr($digit); + last; + } + } + for (;;) { + print "WAIT FOR DIGIT 3000\n"; + my $result = ; + $digit = &check_result($result); + last if $digit <= 0; + $digitstr .= chr($digit); + } + +# see if it's a valid selection + print STDERR "Digits Entered: '$digitstr'\n"; + exit 0 if $digitstr eq ''; + my $found = 0; + goto EXITSUB if $digitstr =~ /\*/; + +# filter the list + if ($digitstr =~ /^\#\d+/) { + my $regexp = ''; + for (my $n=1; $n; &check_result($result); + + `rm $link`; + + if (!$MENUAFTERSONG) { + print "EXEC Playback \"$FESTIVALCACHE/jukebox_thankyou\"\n"; + my $result = ; &check_result($result); + exit 0; + } else { + goto MYCONTINUE; + } + } + } + } + print "EXEC Playback \"$FESTIVALCACHE/jukebox_invalid\"\n"; + my $result = ; &check_result($result); + } + EXITSUB: +} + +sub cache_speech { + my $speech = shift; + my $file = shift; + + my $theDir = extract_file_dir($file); + `mkdir -p -m0776 $theDir`; + + print STDERR "echo \"$speech\" | text2wave -o $file -otype ulaw -\n"; + my $cmdr = `echo "$speech" | text2wave -o $file -otype ulaw -`; + chomp($cmdr); + if ($cmdr =~ /using default diphone/) { +# temporary bug work around.... + `touch $file`; + } elsif ($cmdr ne '') { + print STDERR "Command Failed\n"; + exit 1; + } +} + +sub music_dir_cache { +# generate list of text2speech files to generate + if (!music_dir_cache_genlist('/')) { + print STDERR "Horrible Dreadful Error: No Music Found in $MUSIC!"; + exit 1; + } + +# add to list how many 'number' files we have to generate. We can't +# use the SayNumber app in Asterisk because we want to chain all +# talking in one Background command. We also want a consistent +# voice... + for (my $n=1; $n<=$maxNumber; $n++) { + push(@masterCacheList, [3, "Press $n", "$FESTIVALCACHE/jukebox_$n.ul"]) if ! -e "$FESTIVALCACHE/jukebox_$n.ul"; + } + +# now generate all these darn text2speech files + if (@masterCacheList > 5) { + print "EXEC Playback \"$FESTIVALCACHE/jukebox_generate\"\n"; + my $result = ; &check_result($result); + } + my $theTime = time(); + for (my $n=0; $n < @masterCacheList; $n++) { + my $cmdr = ''; + if ($masterCacheList[$n][0] == 1) { # directory + &cache_speech("for folder $masterCacheList[$n][1]", $masterCacheList[$n][2]); + } elsif ($masterCacheList[$n][0] == 2) { # file + &cache_speech("to play $masterCacheList[$n][1]", $masterCacheList[$n][2]); + } elsif ($masterCacheList[$n][0] == 3) { # number + &cache_speech($masterCacheList[$n][1], $masterCacheList[$n][2]); + } + if (time() >= $theTime + 30) { + my $percent = int($n / @masterCacheList * 100); + print "SAY NUMBER $percent \"\"\n"; + my $result = ; &check_result($result); + print "EXEC Playback \"$FESTIVALCACHE/jukebox_percent\"\n"; + my $result = ; &check_result($result); + $theTime = time(); + } + } +} + +# this function will fill the @masterCacheList of all the files that +# need to have text2speeced ulaw files of their names generated +sub music_dir_cache_genlist { + my $dir = shift; + if (!opendir(THEDIR, rmts($MUSIC.$dir))) { + print STDERR "Failed to open music directory: $dir\n"; + exit 1; + } + my @files = sort readdir THEDIR; + my $foundFiles = 0; + my $tmpMaxNum = 0; + foreach my $file (@files) { + chomp; + if ($file ne '.' && $file ne '..' && $file ne 'festivalcache') { # ignore special files + my $real_version = &rmts($MUSIC.$dir).'/'.$file; + my $cache_version = &rmts($FESTIVALCACHE.$dir).'/'.$file.'.ul'; + my $cache_version2 = &rmts($FESTIVALCACHE.$dir).'/'.$file; + my $cache_version_esc = &clean_file($cache_version); + my $cache_version2_esc = &clean_file($cache_version2); + + if (-d $real_version) { + if (music_dir_cache_genlist(rmts($dir).'/'.$file)) { + $tmpMaxNum++; + $maxNumber = $tmpMaxNum if $tmpMaxNum > $maxNumber; + push(@masterCacheList, [1, $file, $cache_version_esc]) if ! -e $cache_version_esc; + $foundFiles = 1; + } + } elsif ($real_version =~ /\.mp3$/) { + $tmpMaxNum++; + $maxNumber = $tmpMaxNum if $tmpMaxNum > $maxNumber; + push(@masterCacheList, [2, &remove_file_extension($file), $cache_version_esc]) if ! -e $cache_version_esc; + $foundFiles = 1; + } + } + } + close(THEDIR); + return $foundFiles; +} + +sub rmts { # remove trailing slash + my $hog = shift; + $hog =~ s/\/$//; + return $hog; +} + +sub extract_file_name { + my $hog = shift; + $hog =~ /\/?([^\/]+)$/; + return $1; +} + +sub extract_file_dir { + my $hog = shift; + return $hog if ! ($hog =~ /\//); + $hog =~ /(.*)\/[^\/]*$/; + return $1; +} + +sub remove_file_extension { + my $hog = shift; + return $hog if ! ($hog =~ /\./); + $hog =~ /(.*)\.[^.]*$/; + return $1; +} + +sub clean_file { + my $hog = shift; + $hog =~ s/\\/_/g; + $hog =~ s/ /_/g; + $hog =~ s/\t/_/g; + $hog =~ s/\'/_/g; + $hog =~ s/\"/_/g; + $hog =~ s/\(/_/g; + $hog =~ s/\)/_/g; + $hog =~ s/&/_/g; + $hog =~ s/\[/_/g; + $hog =~ s/\]/_/g; + $hog =~ s/\$/_/g; + $hog =~ s/\|/_/g; + $hog =~ s/\^/_/g; + return $hog; +} + +sub remove_special_chars { + my $hog = shift; + $hog =~ s/\\//g; + $hog =~ s/ //g; + $hog =~ s/\t//g; + $hog =~ s/\'//g; + $hog =~ s/\"//g; + $hog =~ s/\(//g; + $hog =~ s/\)//g; + $hog =~ s/&//g; + $hog =~ s/\[//g; + $hog =~ s/\]//g; + $hog =~ s/\$//g; + $hog =~ s/\|//g; + $hog =~ s/\^//g; + return $hog; +} + +sub escape_file { + my $hog = shift; + $hog =~ s/\\/\\\\/g; + $hog =~ s/ /\\ /g; + $hog =~ s/\t/\\\t/g; + $hog =~ s/\'/\\\'/g; + $hog =~ s/\"/\\\"/g; + $hog =~ s/\(/\\\(/g; + $hog =~ s/\)/\\\)/g; + $hog =~ s/&/\\&/g; + $hog =~ s/\[/\\\[/g; + $hog =~ s/\]/\\\]/g; + $hog =~ s/\$/\\\$/g; + $hog =~ s/\|/\\\|/g; + $hog =~ s/\^/\\\^/g; + return $hog; +} + +sub check_result { + my ($res) = @_; + my $retval; + $tests++; + chomp $res; + if ($res =~ /^200/) { + $res =~ /result=(-?\d+)/; + if (!length($1)) { + print STDERR "FAIL ($res)\n"; + $fail++; + exit 1; + } else { + print STDERR "PASS ($1)\n"; + return $1; + } + } else { + print STDERR "FAIL (unexpected result '$res')\n"; + exit 1; + } +} diff --git a/asterisk/agi/numeralize b/asterisk/agi/numeralize new file mode 100644 index 00000000..5ca51913 --- /dev/null +++ b/asterisk/agi/numeralize @@ -0,0 +1,44 @@ +#!/usr/bin/perl +# +# Build a database linking filenames to their numerical representations +# using a keypad for the DialAnMp3 application +# + +$mp3dir="/usr/media/mpeg3"; + +dbmopen(%DIGITS, "/var/lib/asterisk/mp3list", 0644) || die("Unable to open mp3list");; +sub process_dir { + my ($dir) = @_; + my $file; + my $digits; + my @entries; + opendir(DIR, $dir); + @entries = readdir(DIR); + closedir(DIR); + foreach $_ (@entries) { + if (!/^\./) { + $file = "$dir/$_"; + if (-d "$file") { + process_dir("$file"); + } else { + $digits = $_; + $digits =~ s/[^ \w]+//g; + $digits =~ s/\_/ /g; + $digits =~ tr/[a-z]/[A-Z]/; + $digits =~ tr/[A-C]/2/; + $digits =~ tr/[D-F]/3/; + $digits =~ tr/[G-I]/4/; + $digits =~ tr/[J-L]/5/; + $digits =~ tr/[M-O]/6/; + $digits =~ tr/[P-S]/7/; + $digits =~ tr/[T-V]/8/; + $digits =~ tr/[W-Z]/9/; + $digits =~ s/\s+/ /; + print "File: $file, digits: $digits\n"; + $DIGITS{$file} = $digits; + } + } + } +} + +process_dir($mp3dir); diff --git a/asterisk/apps/Makefile b/asterisk/apps/Makefile new file mode 100644 index 00000000..c57926a6 --- /dev/null +++ b/asterisk/apps/Makefile @@ -0,0 +1,42 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Makefile for PBX applications +# +# Copyright (C) 1999-2006, Digium, Inc. +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +-include ../menuselect.makeopts ../menuselect.makedeps + +MENUSELECT_CATEGORY=APPS +MENUSELECT_DESCRIPTION=Applications + +ALL_C_MODS:=$(patsubst %.c,%,$(wildcard app_*.c)) +ALL_CC_MODS:=$(patsubst %.cc,%,$(wildcard app_*.cc)) + +C_MODS:=$(filter-out $(MENUSELECT_APPS),$(ALL_C_MODS)) +CC_MODS:=$(filter-out $(MENUSELECT_APPS),$(ALL_CC_MODS)) + +LOADABLE_MODS:=$(C_MODS) $(CC_MODS) + +ifneq ($(findstring apps,$(MENUSELECT_EMBED)),) + EMBEDDED_MODS:=$(LOADABLE_MODS) + LOADABLE_MODS:= +endif + +MENUSELECT_OPTS_app_directory:=$(MENUSELECT_OPTS_app_voicemail) +ifneq ($(findstring ODBC_STORAGE,$(MENUSELECT_OPTS_app_voicemail)),) +MENUSELECT_DEPENDS_app_voicemail+=$(MENUSELECT_DEPENDS_ODBC_STORAGE) +MENUSELECT_DEPENDS_app_directory+=$(MENUSELECT_DEPENDS_ODBC_STORAGE) +endif +ifneq ($(findstring IMAP_STORAGE,$(MENUSELECT_OPTS_app_voicemail)),) +MENUSELECT_DEPENDS_app_voicemail+=$(MENUSELECT_DEPENDS_IMAP_STORAGE) +MENUSELECT_DEPENDS_app_directory+=$(MENUSELECT_DEPENDS_IMAP_STORAGE) +endif + +all: _all + +include $(ASTTOPDIR)/Makefile.moddir_rules diff --git a/asterisk/apps/app_adsiprog.c b/asterisk/apps/app_adsiprog.c new file mode 100644 index 00000000..cbad3fb7 --- /dev/null +++ b/asterisk/apps/app_adsiprog.c @@ -0,0 +1,1590 @@ +/* + * 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 Program Asterisk ADSI Scripts into phone + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + res_adsi + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 53780 $") + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/adsi.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" + +static char *app = "ADSIProg"; + +static char *synopsis = "Load Asterisk ADSI Scripts into phone"; + +/* #define DUMP_MESSAGES */ + +static char *descrip = +" ADSIProg(script): This application programs an ADSI Phone with the given\n" +"script. If nothing is specified, the default script (asterisk.adsi) is used.\n"; + +struct adsi_event { + int id; + char *name; +}; + +static struct adsi_event events[] = { + { 1, "CALLERID" }, + { 2, "VMWI" }, + { 3, "NEARANSWER" }, + { 4, "FARANSWER" }, + { 5, "ENDOFRING" }, + { 6, "IDLE" }, + { 7, "OFFHOOK" }, + { 8, "CIDCW" }, + { 9, "BUSY" }, + { 10, "FARRING" }, + { 11, "DIALTONE" }, + { 12, "RECALL" }, + { 13, "MESSAGE" }, + { 14, "REORDER" }, + { 15, "DISTINCTIVERING" }, + { 16, "RING" }, + { 17, "REMINDERRING" }, + { 18, "SPECIALRING" }, + { 19, "CODEDRING" }, + { 20, "TIMER" }, + { 21, "INUSE" }, + { 22, "EVENT22" }, + { 23, "EVENT23" }, + { 24, "CPEID" }, +}; + +static struct adsi_event justify[] = { + { 0, "CENTER" }, + { 1, "RIGHT" }, + { 2, "LEFT" }, + { 3, "INDENT" }, +}; + +#define STATE_NORMAL 0 +#define STATE_INKEY 1 +#define STATE_INSUB 2 +#define STATE_INIF 3 + +#define MAX_RET_CODE 20 +#define MAX_SUB_LEN 255 +#define MAX_MAIN_LEN 1600 + +#define ARG_STRING (1 << 0) +#define ARG_NUMBER (1 << 1) + +struct adsi_soft_key { + char vname[40]; /* Which "variable" is associated with it */ + int retstrlen; /* Length of return string */ + int initlen; /* initial length */ + int id; + int defined; + char retstr[80]; /* Return string data */ +}; + +struct adsi_subscript { + char vname[40]; + int id; + int defined; + int datalen; + int inscount; + int ifinscount; + char *ifdata; + char data[2048]; +}; + +struct adsi_state { + char vname[40]; + int id; +}; + +struct adsi_flag { + char vname[40]; + int id; +}; + +struct adsi_display { + char vname[40]; + int id; + char data[70]; + int datalen; +}; + +struct adsi_script { + int state; + int numkeys; + int numsubs; + int numstates; + int numdisplays; + int numflags; + struct adsi_soft_key *key; + struct adsi_subscript *sub; + /* Pre-defined displays */ + struct adsi_display displays[63]; + /* ADSI States 1 (initial) - 254 */ + struct adsi_state states[256]; + /* Keys 2-63 */ + struct adsi_soft_key keys[62]; + /* Subscripts 0 (main) to 127 */ + struct adsi_subscript subs[128]; + /* Flags 1-7 */ + struct adsi_flag flags[7]; + + /* Stuff from adsi script */ + unsigned char sec[5]; + char desc[19]; + unsigned char fdn[5]; + int ver; +}; + + +static int process_token(void *out, char *src, int maxlen, int argtype) +{ + if ((strlen(src) > 1) && src[0] == '\"') { + /* This is a quoted string */ + if (!(argtype & ARG_STRING)) + return -1; + src++; + /* Don't take more than what's there */ + if (maxlen > strlen(src) - 1) + maxlen = strlen(src) - 1; + memcpy(out, src, maxlen); + ((char *)out)[maxlen] = '\0'; + } else if (!ast_strlen_zero(src) && (src[0] == '\\')) { + if (!(argtype & ARG_NUMBER)) + return -1; + /* Octal value */ + if (sscanf(src, "%o", (int *)out) != 1) + return -1; + if (argtype & ARG_STRING) { + /* Convert */ + *((unsigned int *)out) = htonl(*((unsigned int *)out)); + } + } else if ((strlen(src) > 2) && (src[0] == '0') && (tolower(src[1]) == 'x')) { + if (!(argtype & ARG_NUMBER)) + return -1; + /* Hex value */ + if (sscanf(src + 2, "%x", (unsigned int *)out) != 1) + return -1; + if (argtype & ARG_STRING) { + /* Convert */ + *((unsigned int *)out) = htonl(*((unsigned int *)out)); + } + } else if ((!ast_strlen_zero(src) && isdigit(src[0]))) { + if (!(argtype & ARG_NUMBER)) + return -1; + /* Hex value */ + if (sscanf(src, "%d", (int *)out) != 1) + return -1; + if (argtype & ARG_STRING) { + /* Convert */ + *((unsigned int *)out) = htonl(*((unsigned int *)out)); + } + } else + return -1; + return 0; +} + +static char *get_token(char **buf, char *script, int lineno) +{ + char *tmp = *buf; + char *keyword; + int quoted = 0; + /* Advance past any white space */ + while(*tmp && (*tmp < 33)) + tmp++; + if (!*tmp) + return NULL; + keyword = tmp; + while(*tmp && ((*tmp > 32) || quoted)) { + if (*tmp == '\"') { + quoted = !quoted; + } + tmp++; + } + if (quoted) { + ast_log(LOG_WARNING, "Mismatched quotes at line %d of %s\n", lineno, script); + return NULL; + } + *tmp = '\0'; + tmp++; + while(*tmp && (*tmp < 33)) + tmp++; + /* Note where we left off */ + *buf = tmp; + return keyword; +} + +static char *validdtmf = "123456789*0#ABCD"; + +static int send_dtmf(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char dtmfstr[80]; + char *a; + int bytes=0; + a = get_token(&args, script, lineno); + if (!a) { + ast_log(LOG_WARNING, "Expecting something to send for SENDDTMF at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(dtmfstr, a, sizeof(dtmfstr) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid token for SENDDTMF at line %d of %s\n", lineno, script); + return 0; + } + a = dtmfstr; + while(*a) { + if (strchr(validdtmf, *a)) { + *buf = *a; + buf++; + bytes++; + } else + ast_log(LOG_WARNING, "'%c' is not a valid DTMF tone at line %d of %s\n", *a, lineno, script); + a++; + } + return bytes; +} + +static int goto_line(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *page; + char *gline; + int line; + unsigned char cmd; + page = get_token(&args, script, lineno); + gline = get_token(&args, script, lineno); + if (!page || !gline) { + ast_log(LOG_WARNING, "Expecting page and line number for GOTOLINE at line %d of %s\n", lineno, script); + return 0; + } + if (!strcasecmp(page, "INFO")) { + cmd = 0; + } else if (!strcasecmp(page, "COMM")) { + cmd = 0x80; + } else { + ast_log(LOG_WARNING, "Expecting either 'INFO' or 'COMM' page, got got '%s' at line %d of %s\n", page, lineno, script); + return 0; + } + if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script); + return 0; + } + cmd |= line; + buf[0] = 0x8b; + buf[1] = cmd; + return 2; +} + +static int goto_line_rel(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *dir; + char *gline; + int line; + unsigned char cmd; + dir = get_token(&args, script, lineno); + gline = get_token(&args, script, lineno); + if (!dir || !gline) { + ast_log(LOG_WARNING, "Expecting direction and number of lines for GOTOLINEREL at line %d of %s\n", lineno, script); + return 0; + } + if (!strcasecmp(dir, "UP")) { + cmd = 0; + } else if (!strcasecmp(dir, "DOWN")) { + cmd = 0x20; + } else { + ast_log(LOG_WARNING, "Expecting either 'UP' or 'DOWN' direction, got '%s' at line %d of %s\n", dir, lineno, script); + return 0; + } + if (process_token(&line, gline, sizeof(line), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid line number '%s' at line %d of %s\n", gline, lineno, script); + return 0; + } + cmd |= line; + buf[0] = 0x8c; + buf[1] = cmd; + return 2; +} + +static int send_delay(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *gtime; + int ms; + gtime = get_token(&args, script, lineno); + if (!gtime) { + ast_log(LOG_WARNING, "Expecting number of milliseconds to wait at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(&ms, gtime, sizeof(ms), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid delay milliseconds '%s' at line %d of %s\n", gtime, lineno, script); + return 0; + } + buf[0] = 0x90; + if (id == 11) + buf[1] = ms / 100; + else + buf[1] = ms / 10; + return 2; +} + +static int set_state(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *gstate; + int state; + gstate = get_token(&args, script, lineno); + if (!gstate) { + ast_log(LOG_WARNING, "Expecting state number at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(&state, gstate, sizeof(state), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid state number '%s' at line %d of %s\n", gstate, lineno, script); + return 0; + } + buf[0] = id; + buf[1] = state; + return 2; +} + +static int cleartimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *tok; + tok = get_token(&args, script, lineno); + if (tok) + ast_log(LOG_WARNING, "Clearing timer requires no arguments ('%s') at line %d of %s\n", tok, lineno, script); + + buf[0] = id; + /* For some reason the clear code is different slightly */ + if (id == 7) + buf[1] = 0x10; + else + buf[1] = 0x00; + return 2; +} + +static struct adsi_flag *getflagbyname(struct adsi_script *state, char *name, char *script, int lineno, int create) +{ + int x; + for (x=0;xnumflags;x++) + if (!strcasecmp(state->flags[x].vname, name)) + return &state->flags[x]; + /* Return now if we're not allowed to create */ + if (!create) + return NULL; + if (state->numflags > 6) { + ast_log(LOG_WARNING, "No more flag space at line %d of %s\n", lineno, script); + return NULL; + } + ast_copy_string(state->flags[state->numflags].vname, name, sizeof(state->flags[state->numflags].vname)); + state->flags[state->numflags].id = state->numflags + 1; + state->numflags++; + return &state->flags[state->numflags-1]; +} + +static int setflag(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *tok; + char sname[80]; + struct adsi_flag *flag; + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Setting flag requires a flag number at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script); + return 0; + } + flag = getflagbyname(state, sname, script, lineno, 0); + if (!flag) { + ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script); + return 0; + } + buf[0] = id; + buf[1] = ((flag->id & 0x7) << 4) | 1; + return 2; +} + +static int clearflag(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *tok; + struct adsi_flag *flag; + char sname[80]; + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Clearing flag requires a flag number at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(sname, tok, sizeof(sname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid flag '%s' at line %d of %s\n", tok, lineno, script); + return 0; + } + flag = getflagbyname(state, sname, script, lineno, 0); + if (!flag) { + ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", sname, lineno, script); + return 0; + } + buf[0] = id; + buf[1] = ((flag->id & 0x7) << 4); + return 2; +} + +static int starttimer(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *tok; + int secs; + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Missing number of seconds at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(&secs, tok, sizeof(secs), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script); + return 0; + } + buf[0] = id; + buf[1] = 0x1; + buf[2] = secs; + return 3; +} + +static int geteventbyname(char *name) +{ + int x; + for (x=0;xnumkeys;x++) + if (!strcasecmp(state->keys[x].vname, name)) + return &state->keys[x]; + if (state->numkeys > 61) { + ast_log(LOG_WARNING, "No more key space at line %d of %s\n", lineno, script); + return NULL; + } + ast_copy_string(state->keys[state->numkeys].vname, name, sizeof(state->keys[state->numkeys].vname)); + state->keys[state->numkeys].id = state->numkeys + 2; + state->numkeys++; + return &state->keys[state->numkeys-1]; +} + +static struct adsi_subscript *getsubbyname(struct adsi_script *state, char *name, char *script, int lineno) +{ + int x; + for (x=0;xnumsubs;x++) + if (!strcasecmp(state->subs[x].vname, name)) + return &state->subs[x]; + if (state->numsubs > 127) { + ast_log(LOG_WARNING, "No more subscript space at line %d of %s\n", lineno, script); + return NULL; + } + ast_copy_string(state->subs[state->numsubs].vname, name, sizeof(state->subs[state->numsubs].vname)); + state->subs[state->numsubs].id = state->numsubs; + state->numsubs++; + return &state->subs[state->numsubs-1]; +} + +static struct adsi_state *getstatebyname(struct adsi_script *state, char *name, char *script, int lineno, int create) +{ + int x; + for (x=0;xnumstates;x++) + if (!strcasecmp(state->states[x].vname, name)) + return &state->states[x]; + /* Return now if we're not allowed to create */ + if (!create) + return NULL; + if (state->numstates > 253) { + ast_log(LOG_WARNING, "No more state space at line %d of %s\n", lineno, script); + return NULL; + } + ast_copy_string(state->states[state->numstates].vname, name, sizeof(state->states[state->numstates].vname)); + state->states[state->numstates].id = state->numstates + 1; + state->numstates++; + return &state->states[state->numstates-1]; +} + +static struct adsi_display *getdisplaybyname(struct adsi_script *state, char *name, char *script, int lineno, int create) +{ + int x; + for (x=0;xnumdisplays;x++) + if (!strcasecmp(state->displays[x].vname, name)) + return &state->displays[x]; + /* Return now if we're not allowed to create */ + if (!create) + return NULL; + if (state->numdisplays > 61) { + ast_log(LOG_WARNING, "No more display space at line %d of %s\n", lineno, script); + return NULL; + } + ast_copy_string(state->displays[state->numdisplays].vname, name, sizeof(state->displays[state->numdisplays].vname)); + state->displays[state->numdisplays].id = state->numdisplays + 1; + state->numdisplays++; + return &state->displays[state->numdisplays-1]; +} + +static int showkeys(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *tok; + char newkey[80]; + int bytes; + unsigned char keyid[6]; + int x; + int flagid=0; + struct adsi_soft_key *key; + struct adsi_flag *flag; + + for (x=0;x<7;x++) { + /* Up to 6 key arguments */ + tok = get_token(&args, script, lineno); + if (!tok) + break; + if (!strcasecmp(tok, "UNLESS")) { + /* Check for trailing UNLESS flag */ + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script); + } else if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid flag name '%s' at line %d of %s\n", tok, lineno, script); + } else if (!(flag = getflagbyname(state, newkey, script, lineno, 0))) { + ast_log(LOG_WARNING, "Flag '%s' is undeclared at line %d of %s\n", newkey, lineno, script); + } else + flagid = flag->id; + if ((tok = get_token(&args, script, lineno))) + ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script); + break; + } + if (x > 5) { + ast_log(LOG_WARNING, "Only 6 keys can be defined, ignoring '%s' at line %d of %s\n", tok, lineno, script); + break; + } + if (process_token(newkey, tok, sizeof(newkey) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid token for key name: %s\n", tok); + continue; + } + + key = getkeybyname(state, newkey, script, lineno); + if (!key) + break; + keyid[x] = key->id; + } + buf[0] = id; + buf[1] = (flagid & 0x7) << 3 | (x & 0x7); + for (bytes=0;bytes", lineno, script); + return 0; + } + disp = getdisplaybyname(state, dispname, script, lineno, 0); + if (!disp) { + ast_log(LOG_WARNING, "Display '%s' is undefined at line %d of %s\n", dispname, lineno, script); + return 0; + } + + tok = get_token(&args, script, lineno); + if (!tok || strcasecmp(tok, "AT")) { + ast_log(LOG_WARNING, "Missing token 'AT' at line %d of %s\n", lineno, script); + return 0; + } + /* Get line number */ + tok = get_token(&args, script, lineno); + if (!tok || process_token(&line, tok, sizeof(line), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid line: '%s' at line %d of %s\n", tok ? tok : "", lineno, script); + return 0; + } + tok = get_token(&args, script, lineno); + if (tok && !strcasecmp(tok, "NOUPDATE")) { + cmd = 1; + tok = get_token(&args, script, lineno); + } + if (tok && !strcasecmp(tok, "UNLESS")) { + /* Check for trailing UNLESS flag */ + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Missing argument for UNLESS clause at line %d of %s\n", lineno, script); + } else if (process_token(&flag, tok, sizeof(flag), ARG_NUMBER)) { + ast_log(LOG_WARNING, "Invalid flag number '%s' at line %d of %s\n", tok, lineno, script); + } + if ((tok = get_token(&args, script, lineno))) + ast_log(LOG_WARNING, "Extra arguments after UNLESS clause: '%s' at line %d of %s\n", tok, lineno, script); + } + + buf[0] = id; + buf[1] = (cmd << 6) | (disp->id & 0x3f); + buf[2] = ((line & 0x1f) << 3) | (flag & 0x7); + return 3; +} + +static int cleardisplay(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *tok; + tok = get_token(&args, script, lineno); + if (tok) + ast_log(LOG_WARNING, "Clearing display requires no arguments ('%s') at line %d of %s\n", tok, lineno, script); + + buf[0] = id; + buf[1] = 0x00; + return 2; +} + +static int digitdirect(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *tok; + tok = get_token(&args, script, lineno); + if (tok) + ast_log(LOG_WARNING, "Digitdirect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script); + + buf[0] = id; + buf[1] = 0x7; + return 2; +} + +static int clearcbone(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *tok; + tok = get_token(&args, script, lineno); + if (tok) + ast_log(LOG_WARNING, "CLEARCB1 requires no arguments ('%s') at line %d of %s\n", tok, lineno, script); + + buf[0] = id; + buf[1] = 0; + return 2; +} + +static int digitcollect(char *buf, char *name, int id, char *args, struct adsi_script *istate, char *script, int lineno) +{ + char *tok; + tok = get_token(&args, script, lineno); + if (tok) + ast_log(LOG_WARNING, "Digitcollect requires no arguments ('%s') at line %d of %s\n", tok, lineno, script); + + buf[0] = id; + buf[1] = 0xf; + return 2; +} + +static int subscript(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *tok; + char subscript[80]; + struct adsi_subscript *sub; + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(subscript, tok, sizeof(subscript) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid number of seconds '%s' at line %d of %s\n", tok, lineno, script); + return 0; + } + sub = getsubbyname(state, subscript, script, lineno); + if (!sub) + return 0; + buf[0] = 0x9d; + buf[1] = sub->id; + return 2; +} + +static int onevent(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *tok; + char subscript[80]; + char sname[80]; + int sawin=0; + int event; + int snums[8]; + int scnt = 0; + int x; + struct adsi_subscript *sub; + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Missing event for 'ONEVENT' at line %d of %s\n", lineno, script); + return 0; + } + event = geteventbyname(tok); + if (event < 1) { + ast_log(LOG_WARNING, "'%s' is not a valid event name, at line %d of %s\n", args, lineno, script); + return 0; + } + tok = get_token(&args, script, lineno); + while ((!sawin && !strcasecmp(tok, "IN")) || + (sawin && !strcasecmp(tok, "OR"))) { + sawin = 1; + if (scnt > 7) { + ast_log(LOG_WARNING, "No more than 8 states may be specified for inclusion at line %d of %s\n", lineno, script); + return 0; + } + /* Process 'in' things */ + tok = get_token(&args, script, lineno); + if (process_token(sname, tok, sizeof(sname), ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid state name at line %d of %s\n", tok, lineno, script); + return 0; + } + if ((snums[scnt] = getstatebyname(state, sname, script, lineno, 0) < 0)) { + ast_log(LOG_WARNING, "State '%s' not declared at line %d of %s\n", sname, lineno, script); + return 0; + } + scnt++; + tok = get_token(&args, script, lineno); + if (!tok) + break; + } + if (!tok || strcasecmp(tok, "GOTO")) { + if (!tok) + tok = ""; + if (sawin) + ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'OR' at line %d of %s\n", tok, lineno, script); + else + ast_log(LOG_WARNING, "Got '%s' while looking for 'GOTO' or 'IN' at line %d of %s\n", tok, lineno, script); + } + tok = get_token(&args, script, lineno); + if (!tok) { + ast_log(LOG_WARNING, "Missing subscript to call at line %d of %s\n", lineno, script); + return 0; + } + if (process_token(subscript, tok, sizeof(subscript) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid subscript '%s' at line %d of %s\n", tok, lineno, script); + return 0; + } + sub = getsubbyname(state, subscript, script, lineno); + if (!sub) + return 0; + buf[0] = 8; + buf[1] = event; + buf[2] = sub->id | 0x80; + for (x=0;x -1) && !strcasecmp(kcmds[x].name, code)) { + if (kcmds[x].add_args) { + res = kcmds[x].add_args(key->retstr + key->retstrlen, + code, kcmds[x].id, args, state, script, lineno); + if ((key->retstrlen + res - key->initlen) <= MAX_RET_CODE) + key->retstrlen += res; + else + ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script); + } else { + if ((unused = get_token(&args, script, lineno))) + ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", kcmds[x].name, lineno, script, unused); + if ((key->retstrlen + 1 - key->initlen) <= MAX_RET_CODE) { + key->retstr[key->retstrlen] = kcmds[x].id; + key->retstrlen++; + } else + ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", kcmds[x].name, key->vname, lineno, script); + } + return 0; + } + } + return -1; +} + +static int process_opcode(struct adsi_subscript *sub, char *code, char *args, struct adsi_script *state, char *script, int lineno) +{ + int x; + char *unused; + int res; + int max = sub->id ? MAX_SUB_LEN : MAX_MAIN_LEN; + for (x=0;x -1) && !strcasecmp(opcmds[x].name, code)) { + if (opcmds[x].add_args) { + res = opcmds[x].add_args(sub->data + sub->datalen, + code, opcmds[x].id, args, state, script, lineno); + if ((sub->datalen + res + 1) <= max) + sub->datalen += res; + else { + ast_log(LOG_WARNING, "No space for '%s' code in subscript '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script); + return -1; + } + } else { + if ((unused = get_token(&args, script, lineno))) + ast_log(LOG_WARNING, "'%s' takes no arguments at line %d of %s (token is '%s')\n", opcmds[x].name, lineno, script, unused); + if ((sub->datalen + 2) <= max) { + sub->data[sub->datalen] = opcmds[x].id; + sub->datalen++; + } else { + ast_log(LOG_WARNING, "No space for '%s' code in key '%s' at line %d of %s\n", opcmds[x].name, sub->vname, lineno, script); + return -1; + } + } + /* Separate commands with 0xff */ + sub->data[sub->datalen] = 0xff; + sub->datalen++; + sub->inscount++; + return 0; + } + } + return -1; +} + +static int adsi_process(struct adsi_script *state, char *buf, char *script, int lineno) +{ + char *keyword; + char *args; + char vname[256]; + char tmp[80]; + char tmp2[80]; + int lrci; + int wi; + int event; + struct adsi_display *disp; + struct adsi_subscript *newsub; + /* Find the first keyword */ + keyword = get_token(&buf, script, lineno); + if (!keyword) + return 0; + switch(state->state) { + case STATE_NORMAL: + if (!strcasecmp(keyword, "DESCRIPTION")) { + args = get_token(&buf, script, lineno); + if (args) { + if (process_token(state->desc, args, sizeof(state->desc) - 1, ARG_STRING)) + ast_log(LOG_WARNING, "'%s' is not a valid token for DESCRIPTION at line %d of %s\n", args, lineno, script); + } else + ast_log(LOG_WARNING, "Missing argument for DESCRIPTION at line %d of %s\n", lineno, script); + } else if (!strcasecmp(keyword, "VERSION")) { + args = get_token(&buf, script, lineno); + if (args) { + if (process_token(&state->ver, args, sizeof(state->ver) - 1, ARG_NUMBER)) + ast_log(LOG_WARNING, "'%s' is not a valid token for VERSION at line %d of %s\n", args, lineno, script); + } else + ast_log(LOG_WARNING, "Missing argument for VERSION at line %d of %s\n", lineno, script); + } else if (!strcasecmp(keyword, "SECURITY")) { + args = get_token(&buf, script, lineno); + if (args) { + if (process_token(state->sec, args, sizeof(state->sec) - 1, ARG_STRING | ARG_NUMBER)) + ast_log(LOG_WARNING, "'%s' is not a valid token for SECURITY at line %d of %s\n", args, lineno, script); + } else + ast_log(LOG_WARNING, "Missing argument for SECURITY at line %d of %s\n", lineno, script); + } else if (!strcasecmp(keyword, "FDN")) { + args = get_token(&buf, script, lineno); + if (args) { + if (process_token(state->fdn, args, sizeof(state->fdn) - 1, ARG_STRING | ARG_NUMBER)) + ast_log(LOG_WARNING, "'%s' is not a valid token for FDN at line %d of %s\n", args, lineno, script); + } else + ast_log(LOG_WARNING, "Missing argument for FDN at line %d of %s\n", lineno, script); + } else if (!strcasecmp(keyword, "KEY")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "KEY definition missing name at line %d of %s\n", lineno, script); + break; + } + if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script); + break; + } + state->key = getkeybyname(state, vname, script, lineno); + if (!state->key) { + ast_log(LOG_WARNING, "Out of key space at line %d of %s\n", lineno, script); + break; + } + if (state->key->defined) { + ast_log(LOG_WARNING, "Cannot redefine key '%s' at line %d of %s\n", vname, lineno, script); + break; + } + args = get_token(&buf, script, lineno); + if (!args || strcasecmp(args, "IS")) { + ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "", lineno, script); + break; + } + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "KEY definition missing short name at line %d of %s\n", lineno, script); + break; + } + if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY short name at line %d of %s\n", args, lineno, script); + break; + } + args = get_token(&buf, script, lineno); + if (args) { + if (strcasecmp(args, "OR")) { + ast_log(LOG_WARNING, "Expecting 'OR' but got '%s' instead at line %d of %s\n", args, lineno, script); + break; + } + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "KEY definition missing optional long name at line %d of %s\n", lineno, script); + break; + } + if (process_token(tmp2, args, sizeof(tmp2) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY long name at line %d of %s\n", args, lineno, script); + break; + } + } else { + ast_copy_string(tmp2, tmp, sizeof(tmp2)); + } + if (strlen(tmp2) > 18) { + ast_log(LOG_WARNING, "Truncating full name to 18 characters at line %d of %s\n", lineno, script); + tmp2[18] = '\0'; + } + if (strlen(tmp) > 7) { + ast_log(LOG_WARNING, "Truncating short name to 7 bytes at line %d of %s\n", lineno, script); + tmp[7] = '\0'; + } + /* Setup initial stuff */ + state->key->retstr[0] = 128; + /* 1 has the length */ + state->key->retstr[2] = state->key->id; + /* Put the Full name in */ + memcpy(state->key->retstr + 3, tmp2, strlen(tmp2)); + /* Update length */ + state->key->retstrlen = strlen(tmp2) + 3; + /* Put trailing 0xff */ + state->key->retstr[state->key->retstrlen++] = 0xff; + /* Put the short name */ + memcpy(state->key->retstr + state->key->retstrlen, tmp, strlen(tmp)); + /* Update length */ + state->key->retstrlen += strlen(tmp); + /* Put trailing 0xff */ + state->key->retstr[state->key->retstrlen++] = 0xff; + /* Record initial length */ + state->key->initlen = state->key->retstrlen; + state->state = STATE_INKEY; + } else if (!strcasecmp(keyword, "SUB")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script); + break; + } + if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script); + break; + } + state->sub = getsubbyname(state, vname, script, lineno); + if (!state->sub) { + ast_log(LOG_WARNING, "Out of subroutine space at line %d of %s\n", lineno, script); + break; + } + if (state->sub->defined) { + ast_log(LOG_WARNING, "Cannot redefine subroutine '%s' at line %d of %s\n", vname, lineno, script); + break; + } + /* Setup sub */ + state->sub->data[0] = 130; + /* 1 is the length */ + state->sub->data[2] = 0x0; /* Clear extensibility bit */ + state->sub->datalen = 3; + if (state->sub->id) { + /* If this isn't the main subroutine, make a subroutine label for it */ + state->sub->data[3] = 9; + state->sub->data[4] = state->sub->id; + /* 5 is length */ + state->sub->data[6] = 0xff; + state->sub->datalen = 7; + } + args = get_token(&buf, script, lineno); + if (!args || strcasecmp(args, "IS")) { + ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "", lineno, script); + break; + } + state->state = STATE_INSUB; + } else if (!strcasecmp(keyword, "STATE")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "STATE definition missing name at line %d of %s\n", lineno, script); + break; + } + if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a STATE name at line %d of %s\n", args, lineno, script); + break; + } + if (getstatebyname(state, vname, script, lineno, 0)) { + ast_log(LOG_WARNING, "State '%s' is already defined at line %d of %s\n", vname, lineno, script); + break; + } + getstatebyname(state, vname, script, lineno, 1); + } else if (!strcasecmp(keyword, "FLAG")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "FLAG definition missing name at line %d of %s\n", lineno, script); + break; + } + if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a FLAG name at line %d of %s\n", args, lineno, script); + break; + } + if (getflagbyname(state, vname, script, lineno, 0)) { + ast_log(LOG_WARNING, "Flag '%s' is already defined\n", vname); + break; + } + getflagbyname(state, vname, script, lineno, 1); + } else if (!strcasecmp(keyword, "DISPLAY")) { + lrci = 0; + wi = 0; + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "SUB definition missing name at line %d of %s\n", lineno, script); + break; + } + if (process_token(vname, args, sizeof(vname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid token for a KEY name at line %d of %s\n", args, lineno, script); + break; + } + if (getdisplaybyname(state, vname, script, lineno, 0)) { + ast_log(LOG_WARNING, "State '%s' is already defined\n", vname); + break; + } + disp = getdisplaybyname(state, vname, script, lineno, 1); + if (!disp) + break; + args = get_token(&buf, script, lineno); + if (!args || strcasecmp(args, "IS")) { + ast_log(LOG_WARNING, "Missing 'IS' at line %d of %s\n", lineno, script); + break; + } + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "Missing Column 1 text at line %d of %s\n", lineno, script); + break; + } + if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Token '%s' is not valid column 1 text at line %d of %s\n", args, lineno, script); + break; + } + if (strlen(tmp) > 20) { + ast_log(LOG_WARNING, "Truncating column one to 20 characters at line %d of %s\n", lineno, script); + tmp[20] = '\0'; + } + memcpy(disp->data + 5, tmp, strlen(tmp)); + disp->datalen = strlen(tmp) + 5; + disp->data[disp->datalen++] = 0xff; + + args = get_token(&buf, script, lineno); + if (args && !process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) { + /* Got a column two */ + if (strlen(tmp) > 20) { + ast_log(LOG_WARNING, "Truncating column two to 20 characters at line %d of %s\n", lineno, script); + tmp[20] = '\0'; + } + memcpy(disp->data + disp->datalen, tmp, strlen(tmp)); + disp->datalen += strlen(tmp); + args = get_token(&buf, script, lineno); + } + while(args) { + if (!strcasecmp(args, "JUSTIFY")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "Qualifier 'JUSTIFY' requires an argument at line %d of %s\n", lineno, script); + break; + } + lrci = getjustifybyname(args); + if (lrci < 0) { + ast_log(LOG_WARNING, "'%s' is not a valid justification at line %d of %s\n", args, lineno, script); + break; + } + } else if (!strcasecmp(args, "WRAP")) { + wi = 0x80; + } else { + ast_log(LOG_WARNING, "'%s' is not a known qualifier at line %d of %s\n", args, lineno, script); + break; + } + args = get_token(&buf, script, lineno); + } + if (args) { + /* Something bad happened */ + break; + } + disp->data[0] = 129; + disp->data[1] = disp->datalen - 2; + disp->data[2] = ((lrci & 0x3) << 6) | disp->id; + disp->data[3] = wi; + disp->data[4] = 0xff; + } else { + ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in PROGRAM\n", keyword); + } + break; + case STATE_INKEY: + if (process_returncode(state->key, keyword, buf, state, script, lineno)) { + if (!strcasecmp(keyword, "ENDKEY")) { + /* Return to normal operation and increment current key */ + state->state = STATE_NORMAL; + state->key->defined = 1; + state->key->retstr[1] = state->key->retstrlen - 2; + state->key = NULL; + } else { + ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SOFTKEY definition at line %d of %s\n", keyword, lineno, script); + } + } + break; + case STATE_INIF: + if (process_opcode(state->sub, keyword, buf, state, script, lineno)) { + if (!strcasecmp(keyword, "ENDIF")) { + /* Return to normal SUB operation and increment current key */ + state->state = STATE_INSUB; + state->sub->defined = 1; + /* Store the proper number of instructions */ + state->sub->ifdata[2] = state->sub->ifinscount; + } else if (!strcasecmp(keyword, "GOTO")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "GOTO clause missing Subscript name at line %d of %s\n", lineno, script); + break; + } + if (process_token(tmp, args, sizeof(tmp) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "'%s' is not a valid subscript name token at line %d of %s\n", args, lineno, script); + break; + } + newsub = getsubbyname(state, tmp, script, lineno); + if (!newsub) + break; + /* Somehow you use GOTO to go to another place */ + state->sub->data[state->sub->datalen++] = 0x8; + state->sub->data[state->sub->datalen++] = state->sub->ifdata[1]; + state->sub->data[state->sub->datalen++] = newsub->id; + /* Terminate */ + state->sub->data[state->sub->datalen++] = 0xff; + /* Increment counters */ + state->sub->inscount++; + state->sub->ifinscount++; + } else { + ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in IF clause at line %d of %s\n", keyword, lineno, script); + } + } else + state->sub->ifinscount++; + break; + case STATE_INSUB: + if (process_opcode(state->sub, keyword, buf, state, script, lineno)) { + if (!strcasecmp(keyword, "ENDSUB")) { + /* Return to normal operation and increment current key */ + state->state = STATE_NORMAL; + state->sub->defined = 1; + /* Store the proper length */ + state->sub->data[1] = state->sub->datalen - 2; + if (state->sub->id) { + /* if this isn't main, store number of instructions, too */ + state->sub->data[5] = state->sub->inscount; + } + state->sub = NULL; + } else if (!strcasecmp(keyword, "IFEVENT")) { + args = get_token(&buf, script, lineno); + if (!args) { + ast_log(LOG_WARNING, "IFEVENT clause missing Event name at line %d of %s\n", lineno, script); + break; + } + event = geteventbyname(args); + if (event < 1) { + ast_log(LOG_WARNING, "'%s' is not a valid event\n", args); + break; + } + args = get_token(&buf, script, lineno); + if (!args || strcasecmp(args, "THEN")) { + ast_log(LOG_WARNING, "IFEVENT clause missing 'THEN' at line %d of %s\n", lineno, script); + break; + } + state->sub->ifinscount = 0; + state->sub->ifdata = state->sub->data + + state->sub->datalen; + /* Reserve header and insert op codes */ + state->sub->ifdata[0] = 0x1; + state->sub->ifdata[1] = event; + /* 2 is for the number of instructions */ + state->sub->ifdata[3] = 0xff; + state->sub->datalen += 4; + /* Update Subscript instruction count */ + state->sub->inscount++; + state->state = STATE_INIF; + } else { + ast_log(LOG_WARNING, "Invalid or Unknown keyword '%s' in SUB definition at line %d of %s\n", keyword, lineno, script); + } + } + break; + default: + ast_log(LOG_WARNING, "Can't process keyword '%s' in weird state %d\n", keyword, state->state); + } + return 0; +} + +static struct adsi_script *compile_script(char *script) +{ + FILE *f; + char fn[256]; + char buf[256]; + char *c; + int lineno=0; + int x, err; + struct adsi_script *scr; + if (script[0] == '/') + ast_copy_string(fn, script, sizeof(fn)); + else + snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, script); + f = fopen(fn, "r"); + if (!f) { + ast_log(LOG_WARNING, "Can't open file '%s'\n", fn); + return NULL; + } + if (!(scr = ast_calloc(1, sizeof(*scr)))) { + fclose(f); + return NULL; + } + /* Create "main" as first subroutine */ + getsubbyname(scr, "main", NULL, 0); + while(!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!feof(f)) { + lineno++; + /* Trim off trailing return */ + buf[strlen(buf) - 1] = '\0'; + c = strchr(buf, ';'); + /* Strip comments */ + if (c) + *c = '\0'; + if (!ast_strlen_zero(buf)) + adsi_process(scr, buf, script, lineno); + } + } + fclose(f); + /* Make sure we're in the main routine again */ + switch(scr->state) { + case STATE_NORMAL: + break; + case STATE_INSUB: + ast_log(LOG_WARNING, "Missing ENDSUB at end of file %s\n", script); + free(scr); + return NULL; + case STATE_INKEY: + ast_log(LOG_WARNING, "Missing ENDKEY at end of file %s\n", script); + free(scr); + return NULL; + } + err = 0; + + /* Resolve all keys and record their lengths */ + for (x=0;xnumkeys;x++) { + if (!scr->keys[x].defined) { + ast_log(LOG_WARNING, "Key '%s' referenced but never defined in file %s\n", scr->keys[x].vname, fn); + err++; + } + } + + /* Resolve all subs */ + for (x=0;xnumsubs;x++) { + if (!scr->subs[x].defined) { + ast_log(LOG_WARNING, "Subscript '%s' referenced but never defined in file %s\n", scr->subs[x].vname, fn); + err++; + } + if (x == (scr->numsubs - 1)) { + /* Clear out extension bit on last message */ + scr->subs[x].data[2] = 0x80; + } + } + + if (err) { + free(scr); + return NULL; + } + return scr; +} + +#ifdef DUMP_MESSAGES +static void dump_message(char *type, char *vname, unsigned char *buf, int buflen) +{ + int x; + printf("%s %s: [ ", type, vname); + for (x=0;xdesc, scr->fdn, scr->sec, scr->ver)) { + /* User rejected us for some reason */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User rejected download attempt\n"); + ast_log(LOG_NOTICE, "User rejected download on channel %s\n", chan->name); + free(scr); + return -1; + } + + bytes = 0; + /* Start with key definitions */ + for (x=0;xnumkeys;x++) { + if (bytes + scr->keys[x].retstrlen > 253) { + /* Send what we've collected so far */ + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) { + ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x); + return -1; + } + bytes =0; + } + memcpy(buf + bytes, scr->keys[x].retstr, scr->keys[x].retstrlen); + bytes += scr->keys[x].retstrlen; +#ifdef DUMP_MESSAGES + dump_message("Key", scr->keys[x].vname, scr->keys[x].retstr, scr->keys[x].retstrlen); +#endif + } + if (bytes) { + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) { + ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x); + return -1; + } + } + + bytes = 0; + /* Continue with the display messages */ + for (x=0;xnumdisplays;x++) { + if (bytes + scr->displays[x].datalen > 253) { + /* Send what we've collected so far */ + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) { + ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x); + return -1; + } + bytes =0; + } + memcpy(buf + bytes, scr->displays[x].data, scr->displays[x].datalen); + bytes += scr->displays[x].datalen; +#ifdef DUMP_MESSAGES + dump_message("Display", scr->displays[x].vname, scr->displays[x].data, scr->displays[x].datalen); +#endif + } + if (bytes) { + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) { + ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x); + return -1; + } + } + + bytes = 0; + /* Send subroutines */ + for (x=0;xnumsubs;x++) { + if (bytes + scr->subs[x].datalen > 253) { + /* Send what we've collected so far */ + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) { + ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x); + return -1; + } + bytes =0; + } + memcpy(buf + bytes, scr->subs[x].data, scr->subs[x].datalen); + bytes += scr->subs[x].datalen; +#ifdef DUMP_MESSAGES + dump_message("Sub", scr->subs[x].vname, scr->subs[x].data, scr->subs[x].datalen); +#endif + } + if (bytes) { + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DOWNLOAD)) { + ast_log(LOG_WARNING, "Unable to send chunk ending at %d\n", x); + return -1; + } + } + + + bytes = 0; + bytes += ast_adsi_display(buf, ADSI_INFO_PAGE, 1, ADSI_JUST_LEFT, 0, "Download complete.", ""); + bytes += ast_adsi_set_line(buf, ADSI_INFO_PAGE, 1); + if (ast_adsi_transmit_message(chan, buf, bytes, ADSI_MSG_DISPLAY) < 0) + return -1; + if (ast_adsi_end_download(chan)) { + /* Download failed for some reason */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Download attempt failed\n"); + ast_log(LOG_NOTICE, "Download failed on %s\n", chan->name); + free(scr); + return -1; + } + free(scr); + ast_adsi_unload_session(chan); + return 0; +} + +static int adsi_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + if (ast_strlen_zero(data)) + data = "asterisk.adsi"; + + if (!ast_adsi_available(chan)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "ADSI Unavailable on CPE. Not bothering to try.\n"); + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "ADSI Available on CPE. Attempting Upload.\n"); + res = adsi_prog(chan, data); + } + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + ast_module_user_hangup_all(); + + res = ast_unregister_application(app); + + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, adsi_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Asterisk ADSI Programming Application"); diff --git a/asterisk/apps/app_alarmreceiver.c b/asterisk/apps/app_alarmreceiver.c new file mode 100644 index 00000000..c1842dc4 --- /dev/null +++ b/asterisk/apps/app_alarmreceiver.c @@ -0,0 +1,841 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2004 - 2005 Steve Rodgers + * + * Steve Rodgers + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Central Station Alarm receiver for Ademco Contact ID + * \author Steve Rodgers + * + * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** + * + * Use at your own risk. Please consult the GNU GPL license document included with Asterisk. * + * + * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 43933 $") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/ulaw.h" +#include "asterisk/options.h" +#include "asterisk/app.h" +#include "asterisk/dsp.h" +#include "asterisk/config.h" +#include "asterisk/localtime.h" +#include "asterisk/callerid.h" +#include "asterisk/astdb.h" +#include "asterisk/utils.h" + +#define ALMRCV_CONFIG "alarmreceiver.conf" +#define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID" + +struct event_node{ + char data[17]; + struct event_node *next; +}; + +typedef struct event_node event_node_t; + +static char *app = "AlarmReceiver"; + +static char *synopsis = "Provide support for receiving alarm reports from a burglar or fire alarm panel"; +static char *descrip = +" AlarmReceiver(): Only 1 signalling format is supported at this time: Ademco\n" +"Contact ID. This application should be called whenever there is an alarm\n" +"panel calling in to dump its events. The application will handshake with the\n" +"alarm panel, and receive events, validate them, handshake them, and store them\n" +"until the panel hangs up. Once the panel hangs up, the application will run the\n" +"system command specified by the eventcmd setting in alarmreceiver.conf and pipe\n" +"the events to the standard input of the application. The configuration file also\n" +"contains settings for DTMF timing, and for the loudness of the acknowledgement\n" +"tones.\n"; + +/* Config Variables */ + +static int fdtimeout = 2000; +static int sdtimeout = 200; +static int toneloudness = 4096; +static int log_individual_events = 0; +static char event_spool_dir[128] = {'\0'}; +static char event_app[128] = {'\0'}; +static char db_family[128] = {'\0'}; +static char time_stamp_format[128] = {"%a %b %d, %Y @ %H:%M:%S %Z"}; + +/* Misc variables */ + +static char event_file[14] = "/event-XXXXXX"; + +/* +* Attempt to access a database variable and increment it, +* provided that the user defined db-family in alarmreceiver.conf +* The alarmreceiver app will write statistics to a few variables +* in this family if it is defined. If the new key doesn't exist in the +* family, then create it and set its value to 1. +*/ + +static void database_increment( char *key ) +{ + int res = 0; + unsigned v; + char value[16]; + + + if (ast_strlen_zero(db_family)) + return; /* If not defined, don't do anything */ + + res = ast_db_get(db_family, key, value, sizeof(value) - 1); + + if(res){ + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Creating database entry %s and setting to 1\n", key); + /* Guess we have to create it */ + res = ast_db_put(db_family, key, "1"); + return; + } + + sscanf(value, "%u", &v); + v++; + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: New value for %s: %u\n", key, v); + + snprintf(value, sizeof(value), "%u", v); + + res = ast_db_put(db_family, key, value); + + if((res)&&(option_verbose >= 4)) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: database_increment write error\n"); + + return; +} + + +/* +* Build a MuLaw data block for a single frequency tone +*/ + +static void make_tone_burst(unsigned char *data, float freq, float loudness, int len, int *x) +{ + int i; + float val; + + for(i = 0; i < len; i++){ + val = loudness * sin((freq * 2.0 * M_PI * (*x)++)/8000.0); + data[i] = AST_LIN2MU((int)val); + } + + /* wrap back around from 8000 */ + + if (*x >= 8000) *x = 0; + return; +} + +/* +* Send a single tone burst for a specifed duration and frequency. +* Returns 0 if successful +*/ + +static int send_tone_burst(struct ast_channel *chan, float freq, int duration, int tldn) +{ + int res = 0; + int i = 0; + int x = 0; + struct ast_frame *f, wf; + + struct { + unsigned char offset[AST_FRIENDLY_OFFSET]; + unsigned char buf[640]; + } tone_block; + + for(;;) + { + + if (ast_waitfor(chan, -1) < 0){ + res = -1; + break; + } + + f = ast_read(chan); + if (!f){ + res = -1; + break; + } + + if (f->frametype == AST_FRAME_VOICE) { + wf.frametype = AST_FRAME_VOICE; + wf.subclass = AST_FORMAT_ULAW; + wf.offset = AST_FRIENDLY_OFFSET; + wf.mallocd = 0; + wf.data = tone_block.buf; + wf.datalen = f->datalen; + wf.samples = wf.datalen; + + make_tone_burst(tone_block.buf, freq, (float) tldn, wf.datalen, &x); + + i += wf.datalen / 8; + if (i > duration) { + ast_frfree(f); + break; + } + if (ast_write(chan, &wf)){ + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Failed to write frame on %s\n", chan->name); + ast_log(LOG_WARNING, "AlarmReceiver Failed to write frame on %s\n",chan->name); + res = -1; + ast_frfree(f); + break; + } + } + + ast_frfree(f); + } + return res; +} + +/* +* Receive a string of DTMF digits where the length of the digit string is known in advance. Do not give preferential +* treatment to any digit value, and allow separate time out values to be specified for the first digit and all subsequent +* digits. +* +* Returns 0 if all digits successfully received. +* Returns 1 if a digit time out occurred +* Returns -1 if the caller hung up or there was a channel error. +* +*/ + +static int receive_dtmf_digits(struct ast_channel *chan, char *digit_string, int length, int fdto, int sdto) +{ + int res = 0; + int i = 0; + int r; + struct ast_frame *f; + struct timeval lastdigittime; + + lastdigittime = ast_tvnow(); + for(;;){ + /* if outa time, leave */ + if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > + ((i > 0) ? sdto : fdto)){ + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: DTMF Digit Timeout on %s\n", chan->name); + + ast_log(LOG_DEBUG,"AlarmReceiver: DTMF timeout on chan %s\n",chan->name); + + res = 1; + break; + } + + if ((r = ast_waitfor(chan, -1) < 0)) { + ast_log(LOG_DEBUG, "Waitfor returned %d\n", r); + continue; + } + + f = ast_read(chan); + + if (f == NULL){ + res = -1; + break; + } + + /* If they hung up, leave */ + if ((f->frametype == AST_FRAME_CONTROL) && + (f->subclass == AST_CONTROL_HANGUP)){ + ast_frfree(f); + res = -1; + break; + } + + /* if not DTMF, just do it again */ + if (f->frametype != AST_FRAME_DTMF){ + ast_frfree(f); + continue; + } + + digit_string[i++] = f->subclass; /* save digit */ + + ast_frfree(f); + + /* If we have all the digits we expect, leave */ + if(i >= length) + break; + + lastdigittime = ast_tvnow(); + } + + digit_string[i] = '\0'; /* Nul terminate the end of the digit string */ + return res; + +} + +/* +* Write the metadata to the log file +*/ + +static int write_metadata( FILE *logfile, char *signalling_type, struct ast_channel *chan) +{ + int res = 0; + time_t t; + struct tm now; + char *cl,*cn; + char workstring[80]; + char timestamp[80]; + + /* Extract the caller ID location */ + if (chan->cid.cid_num) + ast_copy_string(workstring, chan->cid.cid_num, sizeof(workstring)); + workstring[sizeof(workstring) - 1] = '\0'; + + ast_callerid_parse(workstring, &cn, &cl); + if (cl) + ast_shrink_phone_number(cl); + + + /* Get the current time */ + + time(&t); + ast_localtime(&t, &now, NULL); + + /* Format the time */ + + strftime(timestamp, sizeof(timestamp), time_stamp_format, &now); + + + res = fprintf(logfile, "\n\n[metadata]\n\n"); + + if(res >= 0) + res = fprintf(logfile, "PROTOCOL=%s\n", signalling_type); + + if(res >= 0) + res = fprintf(logfile, "CALLINGFROM=%s\n", (!cl) ? "" : cl); + + if(res >- 0) + res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "" : cn); + + if(res >= 0) + res = fprintf(logfile, "TIMESTAMP=%s\n\n", timestamp); + + if(res >= 0) + res = fprintf(logfile, "[events]\n\n"); + + if(res < 0){ + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't write metadata\n"); + + ast_log(LOG_DEBUG,"AlarmReceiver: can't write metadata\n"); + } + else + res = 0; + + return res; +} + +/* +* Write a single event to the log file +*/ + +static int write_event( FILE *logfile, event_node_t *event) +{ + int res = 0; + + if( fprintf(logfile, "%s\n", event->data) < 0) + res = -1; + + return res; +} + + +/* +* If we are configured to log events, do so here. +* +*/ + +static int log_events(struct ast_channel *chan, char *signalling_type, event_node_t *event) +{ + + int res = 0; + char workstring[sizeof(event_spool_dir)+sizeof(event_file)] = ""; + int fd; + FILE *logfile; + event_node_t *elp = event; + + if (!ast_strlen_zero(event_spool_dir)) { + + /* Make a template */ + + ast_copy_string(workstring, event_spool_dir, sizeof(workstring)); + strncat(workstring, event_file, sizeof(workstring) - strlen(workstring) - 1); + + /* Make the temporary file */ + + fd = mkstemp(workstring); + + if(fd == -1){ + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't make temporary file\n"); + ast_log(LOG_DEBUG,"AlarmReceiver: can't make temporary file\n"); + res = -1; + } + + if(!res){ + logfile = fdopen(fd, "w"); + if(logfile){ + /* Write the file */ + res = write_metadata(logfile, signalling_type, chan); + if(!res) + while((!res) && (elp != NULL)){ + res = write_event(logfile, elp); + elp = elp->next; + } + if(!res){ + if(fflush(logfile) == EOF) + res = -1; + if(!res){ + if(fclose(logfile) == EOF) + res = -1; + } + } + } + else + res = -1; + } + } + + return res; +} + +/* +* This function implements the logic to receive the Ademco contact ID format. +* +* The function will return 0 when the caller hangs up, else a -1 if there was a problem. +*/ + +static int receive_ademco_contact_id( struct ast_channel *chan, void *data, int fdto, int sdto, int tldn, event_node_t **ehead) +{ + int i,j; + int res = 0; + int checksum; + char event[17]; + event_node_t *enew, *elp; + int got_some_digits = 0; + int events_received = 0; + int ack_retries = 0; + + static char digit_map[15] = "0123456789*#ABC"; + static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15}; + + database_increment("calls-received"); + + /* Wait for first event */ + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for first event from panel\n"); + + while(res >= 0){ + + if(got_some_digits == 0){ + + /* Send ACK tone sequence */ + + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 1400Hz 100ms burst (ACK)\n"); + + + res = send_tone_burst(chan, 1400.0, 100, tldn); + + if(!res) + res = ast_safe_sleep(chan, 100); + + if(!res){ + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n"); + + res = send_tone_burst(chan, 2300.0, 100, tldn); + } + + } + + if( res >= 0) + res = receive_dtmf_digits(chan, event, sizeof(event) - 1, fdto, sdto); + + if (res < 0){ + + if(events_received == 0) + /* Hangup with no events received should be logged in the DB */ + database_increment("no-events-received"); + else{ + if(ack_retries){ + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: ACK retries during this call: %d\n", ack_retries); + + database_increment("ack-retries"); + } + } + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: App exiting...\n"); + res = -1; + break; + } + + if(res != 0){ + /* Didn't get all of the digits */ + if(option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Incomplete string: %s, trying again...\n", event); + + if(!got_some_digits){ + got_some_digits = (!ast_strlen_zero(event)) ? 1 : 0; + ack_retries++; + } + continue; + } + + got_some_digits = 1; + + if(option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Received Event %s\n", event); + ast_log(LOG_DEBUG, "AlarmReceiver: Received event: %s\n", event); + + /* Calculate checksum */ + + for(j = 0, checksum = 0; j < 16; j++){ + for(i = 0 ; i < sizeof(digit_map) ; i++){ + if(digit_map[i] == event[j]) + break; + } + + if(i == 16) + break; + + checksum += digit_weights[i]; + } + + if(i == 16){ + if(option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Bad DTMF character %c, trying again\n", event[j]); + continue; /* Bad character */ + } + + /* Checksum is mod(15) of the total */ + + checksum = checksum % 15; + + if (checksum) { + database_increment("checksum-errors"); + if (option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Nonzero checksum\n"); + ast_log(LOG_DEBUG, "AlarmReceiver: Nonzero checksum\n"); + continue; + } + + /* Check the message type for correctness */ + + if(strncmp(event + 4, "18", 2)){ + if(strncmp(event + 4, "98", 2)){ + database_increment("format-errors"); + if(option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Wrong message type\n"); + ast_log(LOG_DEBUG, "AlarmReceiver: Wrong message type\n"); + continue; + } + } + + events_received++; + + /* Queue the Event */ + if (!(enew = ast_calloc(1, sizeof(*enew)))) { + res = -1; + break; + } + + enew->next = NULL; + ast_copy_string(enew->data, event, sizeof(enew->data)); + + /* + * Insert event onto end of list + */ + + if(*ehead == NULL){ + *ehead = enew; + } + else{ + for(elp = *ehead; elp->next != NULL; elp = elp->next) + ; + + elp->next = enew; + } + + if(res > 0) + res = 0; + + /* Let the user have the option of logging the single event before sending the kissoff tone */ + + if((res == 0) && (log_individual_events)) + res = log_events(chan, ADEMCO_CONTACT_ID, enew); + + /* Wait 200 msec before sending kissoff */ + + if(res == 0) + res = ast_safe_sleep(chan, 200); + + /* Send the kissoff tone */ + + if(res == 0) + res = send_tone_burst(chan, 1400.0, 900, tldn); + } + + + return res; +} + + +/* +* This is the main function called by Asterisk Core whenever the App is invoked in the extension logic. +* This function will always return 0. +*/ + +static int alarmreceiver_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u; + event_node_t *elp, *efree; + char signalling_type[64] = ""; + + event_node_t *event_head = NULL; + + u = ast_module_user_add(chan); + + /* Set write and read formats to ULAW */ + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Setting read and write formats to ULAW\n"); + + if (ast_set_write_format(chan,AST_FORMAT_ULAW)){ + ast_log(LOG_WARNING, "AlarmReceiver: Unable to set write format to Mu-law on %s\n",chan->name); + ast_module_user_remove(u); + return -1; + } + + if (ast_set_read_format(chan,AST_FORMAT_ULAW)){ + ast_log(LOG_WARNING, "AlarmReceiver: Unable to set read format to Mu-law on %s\n",chan->name); + ast_module_user_remove(u); + return -1; + } + + /* Set default values for this invokation of the application */ + + ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type)); + + + /* Answer the channel if it is not already */ + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Answering channel\n"); + + if (chan->_state != AST_STATE_UP) { + + res = ast_answer(chan); + + if (res) { + ast_module_user_remove(u); + return -1; + } + } + + /* Wait for the connection to settle post-answer */ + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for connection to stabilize\n"); + + res = ast_safe_sleep(chan, 1250); + + /* Attempt to receive the events */ + + if(!res){ + + /* Determine the protocol to receive in advance */ + /* Note: Ademco contact is the only one supported at this time */ + /* Others may be added later */ + + if(!strcmp(signalling_type, ADEMCO_CONTACT_ID)) + receive_ademco_contact_id(chan, data, fdtimeout, sdtimeout, toneloudness, &event_head); + else + res = -1; + } + + + + /* Events queued by receiver, write them all out here if so configured */ + + if((!res) && (log_individual_events == 0)){ + res = log_events(chan, signalling_type, event_head); + + } + + /* + * Do we exec a command line at the end? + */ + + if((!res) && (!ast_strlen_zero(event_app)) && (event_head)){ + ast_log(LOG_DEBUG,"Alarmreceiver: executing: %s\n", event_app); + ast_safe_system(event_app); + } + + /* + * Free up the data allocated in our linked list + */ + + for(elp = event_head; (elp != NULL);){ + efree = elp; + elp = elp->next; + free(efree); + } + + + ast_module_user_remove(u); + + return 0; +} + +/* +* Load the configuration from the configuration file +*/ + +static int load_config(void) +{ + struct ast_config *cfg; + const char *p; + + /* Read in the config file */ + + cfg = ast_config_load(ALMRCV_CONFIG); + + if(!cfg){ + + if(option_verbose >= 4) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: No config file\n"); + return 0; + } + else{ + + + p = ast_variable_retrieve(cfg, "general", "eventcmd"); + + if(p){ + ast_copy_string(event_app, p, sizeof(event_app)); + event_app[sizeof(event_app) - 1] = '\0'; + } + + p = ast_variable_retrieve(cfg, "general", "loudness"); + if(p){ + toneloudness = atoi(p); + if(toneloudness < 100) + toneloudness = 100; + if(toneloudness > 8192) + toneloudness = 8192; + } + p = ast_variable_retrieve(cfg, "general", "fdtimeout"); + if(p){ + fdtimeout = atoi(p); + if(fdtimeout < 1000) + fdtimeout = 1000; + if(fdtimeout > 10000) + fdtimeout = 10000; + } + + p = ast_variable_retrieve(cfg, "general", "sdtimeout"); + if(p){ + sdtimeout = atoi(p); + if(sdtimeout < 110) + sdtimeout = 110; + if(sdtimeout > 4000) + sdtimeout = 4000; + + } + + p = ast_variable_retrieve(cfg, "general", "logindividualevents"); + if(p){ + log_individual_events = ast_true(p); + + } + + p = ast_variable_retrieve(cfg, "general", "eventspooldir"); + + if(p){ + ast_copy_string(event_spool_dir, p, sizeof(event_spool_dir)); + event_spool_dir[sizeof(event_spool_dir) - 1] = '\0'; + } + + p = ast_variable_retrieve(cfg, "general", "timestampformat"); + + if(p){ + ast_copy_string(time_stamp_format, p, sizeof(time_stamp_format)); + time_stamp_format[sizeof(time_stamp_format) - 1] = '\0'; + } + + p = ast_variable_retrieve(cfg, "general", "db-family"); + + if(p){ + ast_copy_string(db_family, p, sizeof(db_family)); + db_family[sizeof(db_family) - 1] = '\0'; + } + ast_config_destroy(cfg); + } + return 1; + +} + +/* +* These functions are required to implement an Asterisk App. +*/ + + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + if(load_config()) + return ast_register_application(app, alarmreceiver_exec, synopsis, descrip); + else + return AST_MODULE_LOAD_DECLINE; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk"); diff --git a/asterisk/apps/app_amd.c b/asterisk/apps/app_amd.c new file mode 100644 index 00000000..6d66f55b --- /dev/null +++ b/asterisk/apps/app_amd.c @@ -0,0 +1,430 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2003 - 2006, Aheeva Technology. + * + * Claude Klimos (claude.klimos@aheeva.com) + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 101661 $") + +#include +#include + +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/dsp.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/app.h" + + +static char *app = "AMD"; +static char *synopsis = "Attempts to detect answering machines"; +static char *descrip = +" AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n" +" [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n" +" [|silenceThreshold])\n" +" This application attempts to detect answering machines at the beginning\n" +" of outbound calls. Simply call this application after the call\n" +" has been answered (outbound only, of course).\n" +" When loaded, AMD reads amd.conf and uses the parameters specified as\n" +" default values. Those default values get overwritten when calling AMD\n" +" with parameters.\n" +"- 'initialSilence' is the maximum silence duration before the greeting. If\n" +" exceeded then MACHINE.\n" +"- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n" +"- 'afterGreetingSilence' is the silence after detecting a greeting.\n" +" If exceeded then HUMAN.\n" +"- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n" +" on a HUMAN or MACHINE.\n" +"- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n" +"- 'betweenWordsSilence' is the minimum duration of silence after a word to \n" +" consider the audio that follows as a new word.\n" +"- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n" +" If exceeded then MACHINE.\n" +"- 'silenceThreshold' is the silence threshold.\n" +"This application sets the following channel variable upon completion:\n" +" AMDSTATUS - This is the status of the answering machine detection.\n" +" Possible values are:\n" +" MACHINE | HUMAN | NOTSURE | HANGUP\n" +" AMDCAUSE - Indicates the cause that led to the conclusion.\n" +" Possible values are:\n" +" TOOLONG-<%d total_time>\n" +" INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n" +" HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n" +" MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n" +" LONGGREETING-<%d voiceDuration>-<%d greeting>\n"; + +#define STATE_IN_WORD 1 +#define STATE_IN_SILENCE 2 + +/* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */ +static int dfltInitialSilence = 2500; +static int dfltGreeting = 1500; +static int dfltAfterGreetingSilence = 800; +static int dfltTotalAnalysisTime = 5000; +static int dfltMinimumWordLength = 100; +static int dfltBetweenWordsSilence = 50; +static int dfltMaximumNumberOfWords = 3; +static int dfltSilenceThreshold = 256; + +/* Set to the lowest ms value provided in amd.conf or application parameters */ +static int dfltMaxWaitTimeForFrame = 50; + +static void isAnsweringMachine(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_frame *f = NULL; + struct ast_dsp *silenceDetector = NULL; + int dspsilence = 0, readFormat, framelength = 0; + int inInitialSilence = 1; + int inGreeting = 0; + int voiceDuration = 0; + int silenceDuration = 0; + int iTotalTime = 0; + int iWordsCount = 0; + int currentState = STATE_IN_WORD; + int previousState = STATE_IN_SILENCE; + int consecutiveVoiceDuration = 0; + char amdCause[256] = "", amdStatus[256] = ""; + char *parse = ast_strdupa(data); + + /* Lets set the initial values of the variables that will control the algorithm. + The initial values are the default ones. If they are passed as arguments + when invoking the application, then the default values will be overwritten + by the ones passed as parameters. */ + int initialSilence = dfltInitialSilence; + int greeting = dfltGreeting; + int afterGreetingSilence = dfltAfterGreetingSilence; + int totalAnalysisTime = dfltTotalAnalysisTime; + int minimumWordLength = dfltMinimumWordLength; + int betweenWordsSilence = dfltBetweenWordsSilence; + int maximumNumberOfWords = dfltMaximumNumberOfWords; + int silenceThreshold = dfltSilenceThreshold; + int maxWaitTimeForFrame = dfltMaxWaitTimeForFrame; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(argInitialSilence); + AST_APP_ARG(argGreeting); + AST_APP_ARG(argAfterGreetingSilence); + AST_APP_ARG(argTotalAnalysisTime); + AST_APP_ARG(argMinimumWordLength); + AST_APP_ARG(argBetweenWordsSilence); + AST_APP_ARG(argMaximumNumberOfWords); + AST_APP_ARG(argSilenceThreshold); + ); + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat); + + /* Lets parse the arguments. */ + if (!ast_strlen_zero(parse)) { + /* Some arguments have been passed. Lets parse them and overwrite the defaults. */ + AST_STANDARD_APP_ARGS(args, parse); + if (!ast_strlen_zero(args.argInitialSilence)) + initialSilence = atoi(args.argInitialSilence); + if (!ast_strlen_zero(args.argGreeting)) + greeting = atoi(args.argGreeting); + if (!ast_strlen_zero(args.argAfterGreetingSilence)) + afterGreetingSilence = atoi(args.argAfterGreetingSilence); + if (!ast_strlen_zero(args.argTotalAnalysisTime)) + totalAnalysisTime = atoi(args.argTotalAnalysisTime); + if (!ast_strlen_zero(args.argMinimumWordLength)) + minimumWordLength = atoi(args.argMinimumWordLength); + if (!ast_strlen_zero(args.argBetweenWordsSilence)) + betweenWordsSilence = atoi(args.argBetweenWordsSilence); + if (!ast_strlen_zero(args.argMaximumNumberOfWords)) + maximumNumberOfWords = atoi(args.argMaximumNumberOfWords); + if (!ast_strlen_zero(args.argSilenceThreshold)) + silenceThreshold = atoi(args.argSilenceThreshold); + } else if (option_debug) + ast_log(LOG_DEBUG, "AMD using the default parameters.\n"); + + /* Find lowest ms value, that will be max wait time for a frame */ + if (maxWaitTimeForFrame > initialSilence) + maxWaitTimeForFrame = initialSilence; + if (maxWaitTimeForFrame > greeting) + maxWaitTimeForFrame = greeting; + if (maxWaitTimeForFrame > afterGreetingSilence) + maxWaitTimeForFrame = afterGreetingSilence; + if (maxWaitTimeForFrame > totalAnalysisTime) + maxWaitTimeForFrame = totalAnalysisTime; + if (maxWaitTimeForFrame > minimumWordLength) + maxWaitTimeForFrame = minimumWordLength; + if (maxWaitTimeForFrame > betweenWordsSilence) + maxWaitTimeForFrame = betweenWordsSilence; + + /* Now we're ready to roll! */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " + "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n", + initialSilence, greeting, afterGreetingSilence, totalAnalysisTime, + minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold ); + + /* Set read format to signed linear so we get signed linear frames in */ + readFormat = chan->readformat; + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) { + ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name ); + pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); + pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); + return; + } + + /* Create a new DSP that will detect the silence */ + if (!(silenceDetector = ast_dsp_new())) { + ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name ); + pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); + pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); + return; + } + + /* Set silence threshold to specified value */ + ast_dsp_set_threshold(silenceDetector, silenceThreshold); + + /* Now we go into a loop waiting for frames from the channel */ + while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) { + + /* If we fail to read in a frame, that means they hung up */ + if (!(f = ast_read(chan))) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n"); + if (option_debug) + ast_log(LOG_DEBUG, "Got hangup\n"); + strcpy(amdStatus, "HANGUP"); + break; + } + + if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) { + /* If the total time exceeds the analysis time then give up as we are not too sure */ + if (f->frametype == AST_FRAME_VOICE) + framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS); + else + framelength += 2 * maxWaitTimeForFrame; + + iTotalTime += framelength; + if (iTotalTime >= totalAnalysisTime) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name ); + ast_frfree(f); + strcpy(amdStatus , "NOTSURE"); + sprintf(amdCause , "TOOLONG-%d", iTotalTime); + break; + } + + /* Feed the frame of audio into the silence detector and see if we get a result */ + if (f->frametype != AST_FRAME_VOICE) + dspsilence += 2 * maxWaitTimeForFrame; + else { + dspsilence = 0; + ast_dsp_silence(silenceDetector, f, &dspsilence); + } + + if (dspsilence > 0) { + silenceDuration = dspsilence; + + if (silenceDuration >= betweenWordsSilence) { + if (currentState != STATE_IN_SILENCE ) { + previousState = currentState; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n"); + } + currentState = STATE_IN_SILENCE; + consecutiveVoiceDuration = 0; + } + + if (inInitialSilence == 1 && silenceDuration >= initialSilence) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", + silenceDuration, initialSilence); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); + res = 1; + break; + } + + if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", + silenceDuration, afterGreetingSilence); + ast_frfree(f); + strcpy(amdStatus , "HUMAN"); + sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); + res = 1; + break; + } + + } else { + consecutiveVoiceDuration += framelength; + voiceDuration += framelength; + + /* If I have enough consecutive voice to say that I am in a Word, I can only increment the + number of words if my previous state was Silence, which means that I moved into a word. */ + if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) { + iWordsCount++; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount); + previousState = currentState; + currentState = STATE_IN_WORD; + } + + if (iWordsCount >= maximumNumberOfWords) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords); + res = 1; + break; + } + + if (inGreeting == 1 && voiceDuration >= greeting) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting); + res = 1; + break; + } + + if (voiceDuration >= minimumWordLength ) { + silenceDuration = 0; + inInitialSilence = 0; + inGreeting = 1; + } + + } + } + ast_frfree(f); + } + + if (!res) { + /* It took too long to get a frame back. Giving up. */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name); + strcpy(amdStatus , "NOTSURE"); + sprintf(amdCause , "TOOLONG-%d", iTotalTime); + } + + /* Set the status and cause on the channel */ + pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus); + pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause); + + /* Restore channel read format */ + if (readFormat && ast_set_read_format(chan, readFormat)) + ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name); + + /* Free the DSP used to detect silence */ + ast_dsp_free(silenceDetector); + + return; +} + + +static int amd_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u = NULL; + + u = ast_module_user_add(chan); + isAnsweringMachine(chan, data); + ast_module_user_remove(u); + + return 0; +} + +static void load_config(void) +{ + struct ast_config *cfg = NULL; + char *cat = NULL; + struct ast_variable *var = NULL; + + if (!(cfg = ast_config_load("amd.conf"))) { + ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n"); + return; + } + + cat = ast_category_browse(cfg, NULL); + + while (cat) { + if (!strcasecmp(cat, "general") ) { + var = ast_variable_browse(cfg, cat); + while (var) { + if (!strcasecmp(var->name, "initial_silence")) { + dfltInitialSilence = atoi(var->value); + } else if (!strcasecmp(var->name, "greeting")) { + dfltGreeting = atoi(var->value); + } else if (!strcasecmp(var->name, "after_greeting_silence")) { + dfltAfterGreetingSilence = atoi(var->value); + } else if (!strcasecmp(var->name, "silence_threshold")) { + dfltSilenceThreshold = atoi(var->value); + } else if (!strcasecmp(var->name, "total_analysis_time")) { + dfltTotalAnalysisTime = atoi(var->value); + } else if (!strcasecmp(var->name, "min_word_length")) { + dfltMinimumWordLength = atoi(var->value); + } else if (!strcasecmp(var->name, "between_words_silence")) { + dfltBetweenWordsSilence = atoi(var->value); + } else if (!strcasecmp(var->name, "maximum_number_of_words")) { + dfltMaximumNumberOfWords = atoi(var->value); + } else { + ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n", + app, cat, var->name, var->lineno); + } + var = var->next; + } + } + cat = ast_category_browse(cfg, cat); + } + + ast_config_destroy(cfg); + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " + "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n", + dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime, + dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold ); + + return; +} + +static int unload_module(void) +{ + ast_module_user_hangup_all(); + return ast_unregister_application(app); +} + +static int load_module(void) +{ + load_config(); + return ast_register_application(app, amd_exec, synopsis, descrip); +} + +static int reload(void) +{ + load_config(); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/asterisk/apps/app_authenticate.c b/asterisk/apps/app_authenticate.c new file mode 100644 index 00000000..a41b933d --- /dev/null +++ b/asterisk/apps/app_authenticate.c @@ -0,0 +1,252 @@ +/* + * 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 Execute arbitrary authenticate commands + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 101835 $") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/app.h" +#include "asterisk/astdb.h" +#include "asterisk/utils.h" +#include "asterisk/options.h" + +enum { + OPT_ACCOUNT = (1 << 0), + OPT_DATABASE = (1 << 1), + OPT_JUMP = (1 << 2), + OPT_MULTIPLE = (1 << 3), + OPT_REMOVE = (1 << 4), +} auth_option_flags; + +AST_APP_OPTIONS(auth_app_options, { + AST_APP_OPTION('a', OPT_ACCOUNT), + AST_APP_OPTION('d', OPT_DATABASE), + AST_APP_OPTION('j', OPT_JUMP), + AST_APP_OPTION('m', OPT_MULTIPLE), + AST_APP_OPTION('r', OPT_REMOVE), +}); + + +static char *app = "Authenticate"; + +static char *synopsis = "Authenticate a user"; + +static char *descrip = +" Authenticate(password[|options[|maxdigits]]): This application asks the caller\n" +"to enter a given password in order to continue dialplan execution. If the password\n" +"begins with the '/' character, it is interpreted as a file which contains a list of\n" +"valid passwords, listed 1 password per line in the file.\n" +" When using a database key, the value associated with the key can be anything.\n" +"Users have three attempts to authenticate before the channel is hung up. If the\n" +"passsword is invalid, the 'j' option is specified, and priority n+101 exists,\n" +"dialplan execution will continnue at this location.\n" +" Options:\n" +" a - Set the channels' account code to the password that is entered\n" +" d - Interpret the given path as database key, not a literal file\n" +" j - Support jumping to n+101 if authentication fails\n" +" m - Interpret the given path as a file which contains a list of account\n" +" codes and password hashes delimited with ':', listed one per line in\n" +" the file. When one of the passwords is matched, the channel will have\n" +" its account code set to the corresponding account code in the file.\n" +" r - Remove the database key upon successful entry (valid with 'd' only)\n" +" maxdigits - maximum acceptable number of digits. Stops reading after\n" +" maxdigits have been entered (without requiring the user to\n" +" press the '#' key).\n" +" Defaults to 0 - no limit - wait for the user press the '#' key.\n" +; + +static int auth_exec(struct ast_channel *chan, void *data) +{ + int res=0; + int retries; + struct ast_module_user *u; + char passwd[256]; + char *prompt; + int maxdigits; + char *argcopy =NULL; + struct ast_flags flags = {0}; + + AST_DECLARE_APP_ARGS(arglist, + AST_APP_ARG(password); + AST_APP_ARG(options); + AST_APP_ARG(maxdigits); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (chan->_state != AST_STATE_UP) { + res = ast_answer(chan); + if (res) { + ast_module_user_remove(u); + return -1; + } + } + + argcopy = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(arglist,argcopy); + + if (!ast_strlen_zero(arglist.options)) { + ast_app_parse_options(auth_app_options, &flags, NULL, arglist.options); + } + + if (!ast_strlen_zero(arglist.maxdigits)) { + maxdigits = atoi(arglist.maxdigits); + if ((maxdigits<1) || (maxdigits>sizeof(passwd)-2)) + maxdigits = sizeof(passwd) - 2; + } else { + maxdigits = sizeof(passwd) - 2; + } + + /* Start asking for password */ + prompt = "agent-pass"; + for (retries = 0; retries < 3; retries++) { + res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0); + if (res < 0) + break; + res = 0; + if (arglist.password[0] == '/') { + if (ast_test_flag(&flags,OPT_DATABASE)) { + char tmp[256]; + /* Compare against a database key */ + if (!ast_db_get(arglist.password + 1, passwd, tmp, sizeof(tmp))) { + /* It's a good password */ + if (ast_test_flag(&flags,OPT_REMOVE)) { + ast_db_del(arglist.password + 1, passwd); + } + break; + } + } else { + /* Compare against a file */ + FILE *f; + f = fopen(arglist.password, "r"); + if (f) { + char buf[256] = ""; + char md5passwd[33] = ""; + char *md5secret = NULL; + + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!ast_strlen_zero(buf)) { + size_t len = strlen(buf); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + if (ast_test_flag(&flags,OPT_MULTIPLE)) { + md5secret = strchr(buf, ':'); + if (md5secret == NULL) + continue; + *md5secret = '\0'; + md5secret++; + ast_md5_hash(md5passwd, passwd); + if (!strcmp(md5passwd, md5secret)) { + if (ast_test_flag(&flags,OPT_ACCOUNT)) + ast_cdr_setaccount(chan, buf); + break; + } + } else { + if (!strcmp(passwd, buf)) { + if (ast_test_flag(&flags,OPT_ACCOUNT)) + ast_cdr_setaccount(chan, buf); + break; + } + } + } + } + fclose(f); + if (!ast_strlen_zero(buf)) { + if (ast_test_flag(&flags,OPT_MULTIPLE)) { + if (md5secret && !strcmp(md5passwd, md5secret)) + break; + } else { + if (!strcmp(passwd, buf)) + break; + } + } + } else + ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno)); + } + } else { + /* Compare against a fixed password */ + if (!strcmp(passwd, arglist.password)) + break; + } + prompt="auth-incorrect"; + } + if ((retries < 3) && !res) { + if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE)) + ast_cdr_setaccount(chan, passwd); + res = ast_streamfile(chan, "auth-thankyou", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + } else { + if (ast_test_flag(&flags,OPT_JUMP) && ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101) == 0) { + res = 0; + } else { + if (!ast_streamfile(chan, "vm-goodbye", chan->language)) + res = ast_waitstream(chan, ""); + res = -1; + } + } + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + ast_module_user_hangup_all(); + + res = ast_unregister_application(app); + + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, auth_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Authentication Application"); diff --git a/asterisk/apps/app_cdr.c b/asterisk/apps/app_cdr.c new file mode 100644 index 00000000..629ec48c --- /dev/null +++ b/asterisk/apps/app_cdr.c @@ -0,0 +1,78 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Martin Pycko + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Applications connected with CDR engine + * + * Martin Pycko + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 61136 $") + +#include +#include + +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" + +static char *nocdr_descrip = +" NoCDR(): This application will tell Asterisk not to maintain a CDR for the\n" +"current call.\n"; + +static char *nocdr_app = "NoCDR"; +static char *nocdr_synopsis = "Tell Asterisk to not maintain a CDR for the current call"; + + +static int nocdr_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + if (chan->cdr) { + ast_set_flag(chan->cdr, AST_CDR_FLAG_POST_DISABLED); + } + + ast_module_user_remove(u); + + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(nocdr_app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(nocdr_app, nocdr_exec, nocdr_synopsis, nocdr_descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Tell Asterisk to not maintain a CDR for the current call"); diff --git a/asterisk/apps/app_chanisavail.c b/asterisk/apps/app_chanisavail.c new file mode 100644 index 00000000..3d148eba --- /dev/null +++ b/asterisk/apps/app_chanisavail.c @@ -0,0 +1,173 @@ +/* +* Asterisk -- An open source telephony toolkit. +* +* Copyright (C) 1999 - 2005, Digium, Inc. +* +* Mark Spencer +* James Golovich +* +* See http://www.asterisk.org for more information about +* the Asterisk project. Please do not directly contact +* any of the maintainers of this project for assistance; +* the project provides a web site, mailing lists and IRC +* channels for your use. +* +* This program is free 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 Check if Channel is Available + * + * \author Mark Spencer + * \author James Golovich + + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40722 $") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/app.h" +#include "asterisk/devicestate.h" +#include "asterisk/options.h" + +static char *app = "ChanIsAvail"; + +static char *synopsis = "Check channel availability"; + +static char *descrip = +" ChanIsAvail(Technology/resource[&Technology2/resource2...][|options]): \n" +"This application will check to see if any of the specified channels are\n" +"available. The following variables will be set by this application:\n" +" ${AVAILCHAN} - the name of the available channel, if one exists\n" +" ${AVAILORIGCHAN} - the canonical channel name that was used to create the channel\n" +" ${AVAILSTATUS} - the status code for the available channel\n" +" Options:\n" +" s - Consider the channel unavailable if the channel is in use at all\n" +" j - Support jumping to priority n+101 if no channel is available\n"; + + +static int chanavail_exec(struct ast_channel *chan, void *data) +{ + int res=-1, inuse=-1, option_state=0, priority_jump=0; + int status; + struct ast_module_user *u; + char *info, tmp[512], trychan[512], *peers, *tech, *number, *rest, *cur; + struct ast_channel *tempchan; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(reqchans); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ChanIsAvail requires an argument (Zap/1&Zap/2)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + info = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, info); + + if (args.options) { + if (strchr(args.options, 's')) + option_state = 1; + if (strchr(args.options, 'j')) + priority_jump = 1; + } + peers = args.reqchans; + if (peers) { + cur = peers; + do { + /* remember where to start next time */ + rest = strchr(cur, '&'); + if (rest) { + *rest = 0; + rest++; + } + tech = cur; + number = strchr(tech, '/'); + if (!number) { + ast_log(LOG_WARNING, "ChanIsAvail argument takes format ([technology]/[device])\n"); + ast_module_user_remove(u); + return -1; + } + *number = '\0'; + number++; + + if (option_state) { + /* If the pbx says in use then don't bother trying further. + This is to permit testing if someone's on a call, even if the + channel can permit more calls (ie callwaiting, sip calls, etc). */ + + snprintf(trychan, sizeof(trychan), "%s/%s",cur,number); + status = inuse = ast_device_state(trychan); + } + if ((inuse <= 1) && (tempchan = ast_request(tech, chan->nativeformats, number, &status))) { + pbx_builtin_setvar_helper(chan, "AVAILCHAN", tempchan->name); + /* Store the originally used channel too */ + snprintf(tmp, sizeof(tmp), "%s/%s", tech, number); + pbx_builtin_setvar_helper(chan, "AVAILORIGCHAN", tmp); + snprintf(tmp, sizeof(tmp), "%d", status); + pbx_builtin_setvar_helper(chan, "AVAILSTATUS", tmp); + ast_hangup(tempchan); + tempchan = NULL; + res = 1; + break; + } else { + snprintf(tmp, sizeof(tmp), "%d", status); + pbx_builtin_setvar_helper(chan, "AVAILSTATUS", tmp); + } + cur = rest; + } while (cur); + } + if (res < 1) { + pbx_builtin_setvar_helper(chan, "AVAILCHAN", ""); + pbx_builtin_setvar_helper(chan, "AVAILORIGCHAN", ""); + if (priority_jump || ast_opt_priority_jumping) { + if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) { + ast_module_user_remove(u); + return -1; + } + } + } + + ast_module_user_remove(u); + return 0; +} + +static int unload_module(void) +{ + int res = 0; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, chanavail_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Check channel availability"); diff --git a/asterisk/apps/app_channelredirect.c b/asterisk/apps/app_channelredirect.c new file mode 100644 index 00000000..6abfb5ee --- /dev/null +++ b/asterisk/apps/app_channelredirect.c @@ -0,0 +1,140 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Sergey Basmanov + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 ChannelRedirect application + * + * \author Sergey Basmanov + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40722 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" +#include "asterisk/features.h" +#include "asterisk/options.h" + +static char *app = "ChannelRedirect"; +static char *synopsis = "Redirects given channel to a dialplan target."; +static char *descrip = +"ChannelRedirect(channel|[[context|]extension|]priority):\n" +" Sends the specified channel to the specified extension priority\n"; + + +static int asyncgoto_exec(struct ast_channel *chan, void *data) +{ + int res = -1; + struct ast_module_user *u; + char *info, *context, *exten, *priority; + int prio = 1; + struct ast_channel *chan2 = NULL; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(channel); + AST_APP_ARG(label); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s requires an argument (channel|[[context|]exten|]priority)\n", app); + return -1; + } + + u = ast_module_user_add(chan); + + info = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, info); + + if (ast_strlen_zero(args.channel) || ast_strlen_zero(args.label)) { + ast_log(LOG_WARNING, "%s requires an argument (channel|[[context|]exten|]priority)\n", app); + goto quit; + } + + chan2 = ast_get_channel_by_name_locked(args.channel); + if (!chan2) { + ast_log(LOG_WARNING, "No such channel: %s\n", args.channel); + goto quit; + } + + /* Parsed right to left, so standard parsing won't work */ + context = strsep(&args.label, "|"); + exten = strsep(&args.label, "|"); + if (exten) { + priority = strsep(&args.label, "|"); + if (!priority) { + priority = exten; + exten = context; + context = NULL; + } + } else { + priority = context; + context = NULL; + } + + /* ast_findlabel_extension does not convert numeric priorities; it only does a lookup */ + if (!(prio = atoi(priority)) && !(prio = ast_findlabel_extension(chan2, S_OR(context, chan2->context), + S_OR(exten, chan2->exten), priority, chan2->cid.cid_num))) { + ast_log(LOG_WARNING, "'%s' is not a known priority or label\n", priority); + goto chanquit; + } + + if (option_debug > 1) + ast_log(LOG_DEBUG, "Attempting async goto (%s) to %s|%s|%d\n", args.channel, S_OR(context, chan2->context), S_OR(exten, chan2->exten), prio); + + if (ast_async_goto_if_exists(chan2, S_OR(context, chan2->context), S_OR(exten, chan2->exten), prio)) + ast_log(LOG_WARNING, "%s failed for %s\n", app, args.channel); + else + res = 0; + + chanquit: + ast_mutex_unlock(&chan2->lock); + quit: + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, asyncgoto_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Channel Redirect"); diff --git a/asterisk/apps/app_chanspy.c b/asterisk/apps/app_chanspy.c new file mode 100644 index 00000000..1b8994c5 --- /dev/null +++ b/asterisk/apps/app_chanspy.c @@ -0,0 +1,874 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com) + * Copyright (C) 2005 - 2008, Digium, Inc. + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 ChanSpy: Listen in on any channel. + * + * \author Anthony Minessale II + * \author Joshua Colp + * \author Russell Bryant + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/audiohook.h" +#include "asterisk/features.h" +#include "asterisk/options.h" +#include "asterisk/app.h" +#include "asterisk/utils.h" +#include "asterisk/say.h" +#include "asterisk/pbx.h" +#include "asterisk/translate.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" + +#define AST_NAME_STRLEN 256 + +/* "Zap/pseudo" is ten characters. + * "DAHDI/pseudo" is twelve characters. + */ + +static const char *tdesc = "Listen to a channel, and optionally whisper into it"; +static const char *app_chan = "ChanSpy"; +static const char *desc_chan = +" ChanSpy([chanprefix][|options]): This application is used to listen to the\n" +"audio from an Asterisk channel. This includes the audio coming in and\n" +"out of the channel being spied on. If the 'chanprefix' parameter is specified,\n" +"only channels beginning with this string will be spied upon.\n" +" While spying, the following actions may be performed:\n" +" - Dialing # cycles the volume level.\n" +" - Dialing * will stop spying and look for another channel to spy on.\n" +" - Dialing a series of digits followed by # builds a channel name to append\n" +" to 'chanprefix'. For example, executing ChanSpy(Agent) and then dialing\n" +" the digits '1234#' while spying will begin spying on the channel\n" +" 'Agent/1234'.\n" +" Options:\n" +" b - Only spy on channels involved in a bridged call.\n" +" g(grp) - Match only channels where their ${SPYGROUP} variable is set to\n" +" contain 'grp' in an optional : delimited list.\n" +" q - Don't play a beep when beginning to spy on a channel, or speak the\n" +" selected channel name.\n" +" r[(basename)] - Record the session to the monitor spool directory. An\n" +" optional base for the filename may be specified. The\n" +" default is 'chanspy'.\n" +" v([value]) - Adjust the initial volume in the range from -4 to 4. A\n" +" negative value refers to a quieter setting.\n" +" w - Enable 'whisper' mode, so the spying channel can talk to\n" +" the spied-on channel.\n" +" W - Enable 'private whisper' mode, so the spying channel can\n" +" talk to the spied-on channel but cannot listen to that\n" +" channel.\n" +; + +static const char *app_ext = "ExtenSpy"; +static const char *desc_ext = +" ExtenSpy(exten[@context][|options]): This application is used to listen to the\n" +"audio from an Asterisk channel. This includes the audio coming in and\n" +"out of the channel being spied on. Only channels created by outgoing calls for the\n" +"specified extension will be selected for spying. If the optional context is not\n" +"supplied, the current channel's context will be used.\n" +" While spying, the following actions may be performed:\n" +" - Dialing # cycles the volume level.\n" +" - Dialing * will stop spying and look for another channel to spy on.\n" +" Options:\n" +" b - Only spy on channels involved in a bridged call.\n" +" g(grp) - Match only channels where their ${SPYGROUP} variable is set to\n" +" contain 'grp' in an optional : delimited list.\n" +" q - Don't play a beep when beginning to spy on a channel, or speak the\n" +" selected channel name.\n" +" r[(basename)] - Record the session to the monitor spool directory. An\n" +" optional base for the filename may be specified. The\n" +" default is 'chanspy'.\n" +" v([value]) - Adjust the initial volume in the range from -4 to 4. A\n" +" negative value refers to a quieter setting.\n" +" w - Enable 'whisper' mode, so the spying channel can talk to\n" +" the spied-on channel.\n" +" W - Enable 'private whisper' mode, so the spying channel can\n" +" talk to the spied-on channel but cannot listen to that\n" +" channel.\n" +; + +enum { + OPTION_QUIET = (1 << 0), /* Quiet, no announcement */ + OPTION_BRIDGED = (1 << 1), /* Only look at bridged calls */ + OPTION_VOLUME = (1 << 2), /* Specify initial volume */ + OPTION_GROUP = (1 << 3), /* Only look at channels in group */ + OPTION_RECORD = (1 << 4), + OPTION_WHISPER = (1 << 5), + OPTION_PRIVATE = (1 << 6), /* Private Whisper mode */ +} chanspy_opt_flags; + +enum { + OPT_ARG_VOLUME = 0, + OPT_ARG_GROUP, + OPT_ARG_RECORD, + OPT_ARG_ARRAY_SIZE, +} chanspy_opt_args; + +AST_APP_OPTIONS(spy_opts, { + AST_APP_OPTION('q', OPTION_QUIET), + AST_APP_OPTION('b', OPTION_BRIDGED), + AST_APP_OPTION('w', OPTION_WHISPER), + AST_APP_OPTION('W', OPTION_PRIVATE), + AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME), + AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP), + AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD), +}); + +static int next_unique_id_to_use = 0; + +struct chanspy_translation_helper { + /* spy data */ + struct ast_audiohook spy_audiohook; + struct ast_audiohook whisper_audiohook; + int fd; + int volfactor; +}; + +static void *spy_alloc(struct ast_channel *chan, void *data) +{ + /* just store the data pointer in the channel structure */ + return data; +} + +static void spy_release(struct ast_channel *chan, void *data) +{ + /* nothing to do */ +} + +static int spy_generate(struct ast_channel *chan, void *data, int len, int samples) +{ + struct chanspy_translation_helper *csth = data; + struct ast_frame *f; + + ast_audiohook_lock(&csth->spy_audiohook); + if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) { + ast_audiohook_unlock(&csth->spy_audiohook); + return -1; + } + + f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR); + + ast_audiohook_unlock(&csth->spy_audiohook); + + if (!f) + return 0; + + if (ast_write(chan, f)) { + ast_frfree(f); + return -1; + } + + if (csth->fd) + write(csth->fd, f->data, f->datalen); + + ast_frfree(f); + + return 0; +} + +static struct ast_generator spygen = { + .alloc = spy_alloc, + .release = spy_release, + .generate = spy_generate, +}; + +static int start_spying(struct ast_channel *chan, const char *spychan_name, struct ast_audiohook *audiohook) +{ + int res; + struct ast_channel *peer; + + ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, chan->name); + + res = ast_audiohook_attach(chan, audiohook); + + if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) { + ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE); + } + return res; +} + +struct chanspy_ds { + struct ast_channel *chan; + char unique_id[20]; + ast_mutex_t lock; +}; + +static int channel_spy(struct ast_channel *chan, struct chanspy_ds *spyee_chanspy_ds, + int *volfactor, int fd, const struct ast_flags *flags) +{ + struct chanspy_translation_helper csth; + int running = 0, res, x = 0; + char inp[24] = {0}; + char *name; + struct ast_frame *f; + struct ast_silence_generator *silgen = NULL; + struct ast_channel *spyee = NULL; + const char *spyer_name; + + ast_channel_lock(chan); + spyer_name = ast_strdupa(chan->name); + ast_channel_unlock(chan); + + ast_mutex_lock(&spyee_chanspy_ds->lock); + if (spyee_chanspy_ds->chan) { + spyee = spyee_chanspy_ds->chan; + ast_channel_lock(spyee); + } + ast_mutex_unlock(&spyee_chanspy_ds->lock); + + if (!spyee) + return 0; + + /* We now hold the channel lock on spyee */ + + if (ast_check_hangup(chan) || ast_check_hangup(spyee)) { + ast_channel_unlock(spyee); + return 0; + } + + name = ast_strdupa(spyee->name); + if (option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "Spying on channel %s\n", name); + + memset(&csth, 0, sizeof(csth)); + + ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy"); + + if (start_spying(spyee, spyer_name, &csth.spy_audiohook)) { + ast_audiohook_destroy(&csth.spy_audiohook); + ast_channel_unlock(spyee); + return 0; + } + + if (ast_test_flag(flags, OPTION_WHISPER)) { + ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy"); + start_spying(spyee, spyer_name, &csth.whisper_audiohook); + } + + ast_channel_unlock(spyee); + spyee = NULL; + + csth.volfactor = *volfactor; + + if (csth.volfactor) { + csth.spy_audiohook.options.read_volume = csth.volfactor; + csth.spy_audiohook.options.write_volume = csth.volfactor; + } + + csth.fd = fd; + + if (ast_test_flag(flags, OPTION_PRIVATE)) + silgen = ast_channel_start_silence_generator(chan); + else + ast_activate_generator(chan, &spygen, &csth); + + /* We can no longer rely on 'spyee' being an actual channel; + it can be hung up and freed out from under us. However, the + channel destructor will put NULL into our csth.spy.chan + field when that happens, so that is our signal that the spyee + channel has gone away. + */ + + /* Note: it is very important that the ast_waitfor() be the first + condition in this expression, so that if we wait for some period + of time before receiving a frame from our spying channel, we check + for hangup on the spied-on channel _after_ knowing that a frame + has arrived, since the spied-on channel could have gone away while + we were waiting + */ + while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) { + if (!(f = ast_read(chan)) || ast_check_hangup(chan)) { + running = -1; + break; + } + + if (ast_test_flag(flags, OPTION_WHISPER) && (f->frametype == AST_FRAME_VOICE)) { + ast_audiohook_lock(&csth.whisper_audiohook); + ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f); + ast_audiohook_unlock(&csth.whisper_audiohook); + ast_frfree(f); + continue; + } + + res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0; + ast_frfree(f); + if (!res) + continue; + + if (x == sizeof(inp)) + x = 0; + + if (res < 0) { + running = -1; + break; + } + + if (res == '*') { + running = 0; + break; + } else if (res == '#') { + if (!ast_strlen_zero(inp)) { + running = atoi(inp); + break; + } + + (*volfactor)++; + if (*volfactor > 4) + *volfactor = -4; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Setting spy volume on %s to %d\n", chan->name, *volfactor); + csth.volfactor = *volfactor; + csth.spy_audiohook.options.read_volume = csth.volfactor; + csth.spy_audiohook.options.write_volume = csth.volfactor; + } else if (res >= '0' && res <= '9') { + inp[x++] = res; + } + } + + if (ast_test_flag(flags, OPTION_PRIVATE)) + ast_channel_stop_silence_generator(chan, silgen); + else + ast_deactivate_generator(chan); + + if (ast_test_flag(flags, OPTION_WHISPER)) { + ast_audiohook_lock(&csth.whisper_audiohook); + ast_audiohook_detach(&csth.whisper_audiohook); + ast_audiohook_unlock(&csth.whisper_audiohook); + ast_audiohook_destroy(&csth.whisper_audiohook); + } + + ast_audiohook_lock(&csth.spy_audiohook); + ast_audiohook_detach(&csth.spy_audiohook); + ast_audiohook_unlock(&csth.spy_audiohook); + ast_audiohook_destroy(&csth.spy_audiohook); + + if (option_verbose >= 2) + ast_verbose(VERBOSE_PREFIX_2 "Done Spying on channel %s\n", name); + + return running; +} + +/*! + * \note This relies on the embedded lock to be recursive, as it may be called + * due to a call to chanspy_ds_free with the lock held there. + */ +static void chanspy_ds_destroy(void *data) +{ + struct chanspy_ds *chanspy_ds = data; + + /* Setting chan to be NULL is an atomic operation, but we don't want this + * value to change while this lock is held. The lock is held elsewhere + * while it performs non-atomic operations with this channel pointer */ + + ast_mutex_lock(&chanspy_ds->lock); + chanspy_ds->chan = NULL; + ast_mutex_unlock(&chanspy_ds->lock); +} + +static void chanspy_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + struct chanspy_ds *chanspy_ds = data; + + ast_mutex_lock(&chanspy_ds->lock); + chanspy_ds->chan = new_chan; + ast_mutex_unlock(&chanspy_ds->lock); +} + +static const struct ast_datastore_info chanspy_ds_info = { + .type = "chanspy", + .destroy = chanspy_ds_destroy, + .chan_fixup = chanspy_ds_chan_fixup, +}; + +static struct chanspy_ds *chanspy_ds_free(struct chanspy_ds *chanspy_ds) +{ + if (!chanspy_ds) + return NULL; + + ast_mutex_lock(&chanspy_ds->lock); + if (chanspy_ds->chan) { + struct ast_datastore *datastore; + struct ast_channel *chan; + + chan = chanspy_ds->chan; + + ast_channel_lock(chan); + if ((datastore = ast_channel_datastore_find(chan, &chanspy_ds_info, chanspy_ds->unique_id))) { + ast_channel_datastore_remove(chan, datastore); + /* chanspy_ds->chan is NULL after this call */ + chanspy_ds_destroy(datastore->data); + datastore->data = NULL; + ast_channel_datastore_free(datastore); + } + ast_channel_unlock(chan); + } + ast_mutex_unlock(&chanspy_ds->lock); + + return NULL; +} + +/*! \note Returns the channel in the chanspy_ds locked as well as the chanspy_ds locked */ +static struct chanspy_ds *setup_chanspy_ds(struct ast_channel *chan, struct chanspy_ds *chanspy_ds) +{ + struct ast_datastore *datastore = NULL; + + ast_mutex_lock(&chanspy_ds->lock); + + if (!(datastore = ast_channel_datastore_alloc(&chanspy_ds_info, chanspy_ds->unique_id))) { + ast_mutex_unlock(&chanspy_ds->lock); + chanspy_ds = chanspy_ds_free(chanspy_ds); + ast_channel_unlock(chan); + return NULL; + } + + chanspy_ds->chan = chan; + datastore->data = chanspy_ds; + ast_channel_datastore_add(chan, datastore); + + return chanspy_ds; +} + +static struct chanspy_ds *next_channel(struct ast_channel *chan, + const struct ast_channel *last, const char *spec, + const char *exten, const char *context, struct chanspy_ds *chanspy_ds) +{ + struct ast_channel *this; + char channel_name[AST_CHANNEL_NAME]; + static size_t PSEUDO_CHAN_LEN = 0; + + if (!PSEUDO_CHAN_LEN) { + PSEUDO_CHAN_LEN = *dahdi_chan_name_len + strlen("/pseudo"); + } + +redo: + if (spec) + this = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec)); + else if (exten) + this = ast_walk_channel_by_exten_locked(last, exten, context); + else + this = ast_channel_walk_locked(last); + + if (!this) + return NULL; + + snprintf(channel_name, AST_CHANNEL_NAME, "%s/pseudo", dahdi_chan_name); + if (!strncmp(this->name, channel_name, PSEUDO_CHAN_LEN)) { + last = this; + ast_channel_unlock(this); + goto redo; + } else if (this == chan) { + last = this; + ast_channel_unlock(this); + goto redo; + } + + return setup_chanspy_ds(this, chanspy_ds); +} + +static int common_exec(struct ast_channel *chan, const struct ast_flags *flags, + int volfactor, const int fd, const char *mygroup, const char *spec, + const char *exten, const char *context) +{ + char nameprefix[AST_NAME_STRLEN]; + char peer_name[AST_NAME_STRLEN + 5]; + signed char zero_volume = 0; + int waitms; + int res; + char *ptr; + int num; + int num_spyed_upon = 1; + struct chanspy_ds chanspy_ds; + + ast_mutex_init(&chanspy_ds.lock); + + snprintf(chanspy_ds.unique_id, sizeof(chanspy_ds.unique_id), "%d", ast_atomic_fetchadd_int(&next_unique_id_to_use, +1)); + + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */ + + waitms = 100; + + for (;;) { + struct chanspy_ds *peer_chanspy_ds = NULL, *next_chanspy_ds = NULL; + struct ast_channel *prev = NULL, *peer = NULL; + + if (!ast_test_flag(flags, OPTION_QUIET) && num_spyed_upon) { + res = ast_streamfile(chan, "beep", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + else if (res < 0) { + ast_clear_flag(chan, AST_FLAG_SPYING); + break; + } + } + + res = ast_waitfordigit(chan, waitms); + if (res < 0) { + ast_clear_flag(chan, AST_FLAG_SPYING); + break; + } + + /* reset for the next loop around, unless overridden later */ + waitms = 100; + num_spyed_upon = 0; + + for (peer_chanspy_ds = next_channel(chan, prev, spec, exten, context, &chanspy_ds); + peer_chanspy_ds; + chanspy_ds_free(peer_chanspy_ds), prev = peer, + peer_chanspy_ds = next_chanspy_ds ? next_chanspy_ds : + next_channel(chan, prev, spec, exten, context, &chanspy_ds), next_chanspy_ds = NULL) { + const char *group; + int igrp = !mygroup; + char *groups[25]; + int num_groups = 0; + char dup_group[512]; + int x; + char *s; + + peer = peer_chanspy_ds->chan; + + ast_mutex_unlock(&peer_chanspy_ds->lock); + + if (peer == prev) { + ast_channel_unlock(peer); + chanspy_ds_free(peer_chanspy_ds); + break; + } + + if (ast_check_hangup(chan)) { + ast_channel_unlock(peer); + chanspy_ds_free(peer_chanspy_ds); + break; + } + + if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer)) { + ast_channel_unlock(peer); + continue; + } + + if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING)) { + ast_channel_unlock(peer); + continue; + } + + if (mygroup) { + if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) { + ast_copy_string(dup_group, group, sizeof(dup_group)); + num_groups = ast_app_separate_args(dup_group, ':', groups, + sizeof(groups) / sizeof(groups[0])); + } + + for (x = 0; x < num_groups; x++) { + if (!strcmp(mygroup, groups[x])) { + igrp = 1; + break; + } + } + } + + if (!igrp) { + ast_channel_unlock(peer); + continue; + } + + strcpy(peer_name, "spy-"); + strncat(peer_name, peer->name, AST_NAME_STRLEN - 4 - 1); + ptr = strchr(peer_name, '/'); + *ptr++ = '\0'; + + for (s = peer_name; s < ptr; s++) + *s = tolower(*s); + + /* We have to unlock the peer channel here to avoid a deadlock. + * So, when we need to dereference it again, we have to lock the + * datastore and get the pointer from there to see if the channel + * is still valid. */ + ast_channel_unlock(peer); + + if (!ast_test_flag(flags, OPTION_QUIET)) { + if (ast_fileexists(peer_name, NULL, NULL) != -1) { + res = ast_streamfile(chan, peer_name, chan->language); + if (!res) + res = ast_waitstream(chan, ""); + if (res) { + chanspy_ds_free(peer_chanspy_ds); + break; + } + } else + res = ast_say_character_str(chan, peer_name, "", chan->language); + if ((num = atoi(ptr))) + ast_say_digits(chan, atoi(ptr), "", chan->language); + } + + res = channel_spy(chan, peer_chanspy_ds, &volfactor, fd, flags); + num_spyed_upon++; + + if (res == -1) { + chanspy_ds_free(peer_chanspy_ds); + break; + } else if (res > 1 && spec) { + struct ast_channel *next; + + snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res); + + if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) { + peer_chanspy_ds = chanspy_ds_free(peer_chanspy_ds); + next_chanspy_ds = setup_chanspy_ds(next, &chanspy_ds); + } else { + /* stay on this channel, if it is still valid */ + + ast_mutex_lock(&peer_chanspy_ds->lock); + if (peer_chanspy_ds->chan) { + ast_channel_lock(peer_chanspy_ds->chan); + next_chanspy_ds = peer_chanspy_ds; + peer_chanspy_ds = NULL; + } else { + /* the channel is gone */ + ast_mutex_unlock(&peer_chanspy_ds->lock); + next_chanspy_ds = NULL; + } + } + + peer = NULL; + } + } + if (res == -1 || ast_check_hangup(chan)) + break; + } + + ast_clear_flag(chan, AST_FLAG_SPYING); + + ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0); + + ast_mutex_lock(&chanspy_ds.lock); + ast_mutex_unlock(&chanspy_ds.lock); + ast_mutex_destroy(&chanspy_ds.lock); + + return res; +} + +static int chanspy_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + char *options = NULL; + char *spec = NULL; + char *argv[2]; + char *mygroup = NULL; + char *recbase = NULL; + int fd = 0; + struct ast_flags flags; + int oldwf = 0; + int argc = 0; + int volfactor = 0; + int res; + + data = ast_strdupa(data); + + u = ast_module_user_add(chan); + + if ((argc = ast_app_separate_args(data, '|', argv, sizeof(argv) / sizeof(argv[0])))) { + spec = argv[0]; + if (argc > 1) + options = argv[1]; + + if (ast_strlen_zero(spec) || !strcmp(spec, "all")) + spec = NULL; + } + + if (options) { + char *opts[OPT_ARG_ARRAY_SIZE]; + + ast_app_parse_options(spy_opts, &flags, opts, options); + if (ast_test_flag(&flags, OPTION_GROUP)) + mygroup = opts[OPT_ARG_GROUP]; + + if (ast_test_flag(&flags, OPTION_RECORD) && + !(recbase = opts[OPT_ARG_RECORD])) + recbase = "chanspy"; + + if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) { + int vol; + + if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4)) + ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n"); + else + volfactor = vol; + } + + if (ast_test_flag(&flags, OPTION_PRIVATE)) + ast_set_flag(&flags, OPTION_WHISPER); + } else + ast_clear_flag(&flags, AST_FLAGS_ALL); + + oldwf = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) { + ast_log(LOG_ERROR, "Could Not Set Write Format.\n"); + ast_module_user_remove(u); + return -1; + } + + if (recbase) { + char filename[PATH_MAX]; + + snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL)); + if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) <= 0) { + ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename); + fd = 0; + } + } + + res = common_exec(chan, &flags, volfactor, fd, mygroup, spec, NULL, NULL); + + if (fd) + close(fd); + + if (oldwf && ast_set_write_format(chan, oldwf) < 0) + ast_log(LOG_ERROR, "Could Not Set Write Format.\n"); + + ast_module_user_remove(u); + + return res; +} + +static int extenspy_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + char *options = NULL; + char *exten = NULL; + char *context = NULL; + char *argv[2]; + char *mygroup = NULL; + char *recbase = NULL; + int fd = 0; + struct ast_flags flags; + int oldwf = 0; + int argc = 0; + int volfactor = 0; + int res; + + data = ast_strdupa(data); + + u = ast_module_user_add(chan); + + if ((argc = ast_app_separate_args(data, '|', argv, sizeof(argv) / sizeof(argv[0])))) { + context = argv[0]; + if (!ast_strlen_zero(argv[0])) + exten = strsep(&context, "@"); + if (ast_strlen_zero(context)) + context = ast_strdupa(chan->context); + if (argc > 1) + options = argv[1]; + } + + if (options) { + char *opts[OPT_ARG_ARRAY_SIZE]; + + ast_app_parse_options(spy_opts, &flags, opts, options); + if (ast_test_flag(&flags, OPTION_GROUP)) + mygroup = opts[OPT_ARG_GROUP]; + + if (ast_test_flag(&flags, OPTION_RECORD) && + !(recbase = opts[OPT_ARG_RECORD])) + recbase = "chanspy"; + + if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) { + int vol; + + if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4)) + ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n"); + else + volfactor = vol; + } + + if (ast_test_flag(&flags, OPTION_PRIVATE)) + ast_set_flag(&flags, OPTION_WHISPER); + } else + ast_clear_flag(&flags, AST_FLAGS_ALL); + + oldwf = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) { + ast_log(LOG_ERROR, "Could Not Set Write Format.\n"); + ast_module_user_remove(u); + return -1; + } + + if (recbase) { + char filename[PATH_MAX]; + + snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL)); + if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) <= 0) { + ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename); + fd = 0; + } + } + + res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, exten, context); + + if (fd) + close(fd); + + if (oldwf && ast_set_write_format(chan, oldwf) < 0) + ast_log(LOG_ERROR, "Could Not Set Write Format.\n"); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res = 0; + + res |= ast_unregister_application(app_chan); + res |= ast_unregister_application(app_ext); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res = 0; + + res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan); + res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel"); diff --git a/asterisk/apps/app_controlplayback.c b/asterisk/apps/app_controlplayback.c new file mode 100644 index 00000000..31611e7a --- /dev/null +++ b/asterisk/apps/app_controlplayback.c @@ -0,0 +1,168 @@ +/* + * 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 Trivial application to control playback of a sound file + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 86769 $") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/utils.h" +#include "asterisk/options.h" + +static const char *app = "ControlPlayback"; + +static const char *synopsis = "Play a file with fast forward and rewind"; + +static const char *descrip = +" ControlPlayback(file[|skipms[|ff[|rew[|stop[|pause[|restart|options]]]]]]]):\n" +"This application will play back the given filename. By default, the '*' key\n" +"can be used to rewind, and the '#' key can be used to fast-forward.\n" +"Parameters:\n" +" skipms - This is number of milliseconds to skip when rewinding or\n" +" fast-forwarding.\n" +" ff - Fast-forward when this DTMF digit is received.\n" +" rew - Rewind when this DTMF digit is received.\n" +" stop - Stop playback when this DTMF digit is received.\n" +" pause - Pause playback when this DTMF digit is received.\n" +" restart - Restart playback when this DTMF digit is received.\n" +"Options:\n" +" j - Jump to priority n+101 if the requested file is not found.\n" +"This application sets the following channel variable upon completion:\n" +" CPLAYBACKSTATUS - This variable contains the status of the attempt as a text\n" +" string, one of: SUCCESS | USERSTOPPED | ERROR\n"; + + +static int is_on_phonepad(char key) +{ + return key == 35 || key == 42 || (key >= 48 && key <= 57); +} + +static int controlplayback_exec(struct ast_channel *chan, void *data) +{ + int res = 0, priority_jump = 0; + int skipms = 0; + struct ast_module_user *u; + char *tmp; + int argc; + char *argv[8]; + enum arg_ids { + arg_file = 0, + arg_skip = 1, + arg_fwd = 2, + arg_rev = 3, + arg_stop = 4, + arg_pause = 5, + arg_restart = 6, + options = 7, + }; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + tmp = ast_strdupa(data); + memset(argv, 0, sizeof(argv)); + + argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0])); + + if (argc < 1) { + ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n"); + ast_module_user_remove(u); + return -1; + } + + skipms = argv[arg_skip] ? atoi(argv[arg_skip]) : 3000; + if (!skipms) + skipms = 3000; + + if (!argv[arg_fwd] || !is_on_phonepad(*argv[arg_fwd])) + argv[arg_fwd] = "#"; + if (!argv[arg_rev] || !is_on_phonepad(*argv[arg_rev])) + argv[arg_rev] = "*"; + if (argv[arg_stop] && !is_on_phonepad(*argv[arg_stop])) + argv[arg_stop] = NULL; + if (argv[arg_pause] && !is_on_phonepad(*argv[arg_pause])) + argv[arg_pause] = NULL; + if (argv[arg_restart] && !is_on_phonepad(*argv[arg_restart])) + argv[arg_restart] = NULL; + + if (argv[options]) { + if (strchr(argv[options], 'j')) + priority_jump = 1; + } + + res = ast_control_streamfile(chan, argv[arg_file], argv[arg_fwd], argv[arg_rev], argv[arg_stop], argv[arg_pause], argv[arg_restart], skipms); + + /* If we stopped on one of our stop keys, return 0 */ + if (res > 0 && argv[arg_stop] && strchr(argv[arg_stop], res)) { + res = 0; + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED"); + } else { + if (res < 0) { + if (priority_jump || ast_opt_priority_jumping) { + if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) { + ast_log(LOG_WARNING, "ControlPlayback tried to jump to priority n+101 as requested, but priority didn't exist\n"); + } + } + res = 0; + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR"); + } else + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS"); + } + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + res = ast_unregister_application(app); + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, controlplayback_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application"); diff --git a/asterisk/apps/app_dahdibarge.c b/asterisk/apps/app_dahdibarge.c new file mode 100644 index 00000000..6a25a822 --- /dev/null +++ b/asterisk/apps/app_dahdibarge.c @@ -0,0 +1,362 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * Special thanks to comphealth.com for sponsoring this + * GPL application. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Zap Barge support + * + * \author Mark Spencer + * + * \note Special thanks to comphealth.com for sponsoring this + * GPL application. + * + * \ingroup applications + */ + +/*** MODULEINFO + dahdi + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/app.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/say.h" +#include "asterisk/utils.h" + +#include "asterisk/dahdi_compat.h" + +static char *dahdi_app = "DAHDIBarge"; +static char *zap_app = "ZapBarge"; + +static char *dahdi_synopsis = "Barge in (monitor) DAHDI channel"; +static char *zap_synopsis = "Barge in (monitor) Zap channel"; + +static char *dahdi_descrip = +" DAHDIBarge([channel]): Barges in on a specified DAHDI\n" +"channel or prompts if one is not specified. Returns\n" +"-1 when caller user hangs up and is independent of the\n" +"state of the channel being monitored."; + +static char *zap_descrip = +" ZapBarge([channel]): Barges in on a specified Zaptel\n" +"channel or prompts if one is not specified. Returns\n" +"-1 when caller user hangs up and is independent of the\n" +"state of the channel being monitored."; + +#define CONF_SIZE 160 + +static int careful_write(int fd, unsigned char *data, int len) +{ + int res; + while(len) { + res = write(fd, data, len); + if (res < 1) { + if (errno != EAGAIN) { + ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno)); + return -1; + } else + return 0; + } + len -= res; + data += res; + } + return 0; +} + +static int conf_run(struct ast_channel *chan, int confno, int confflags) +{ + int fd; + struct dahdi_confinfo ztc; + struct ast_frame *f; + struct ast_channel *c; + struct ast_frame fr; + int outfd; + int ms; + int nfds; + int res; + int flags; + int retryzap; + int origfd; + int ret = -1; + struct dahdi_bufferinfo bi; + char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET]; + char *buf = __buf + AST_FRIENDLY_OFFSET; + + /* Set it into U-law mode (write) */ + if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) { + ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name); + goto outrun; + } + + /* Set it into U-law mode (read) */ + if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) { + ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name); + goto outrun; + } + ast_indicate(chan, -1); + retryzap = strcasecmp(chan->tech->type, dahdi_chan_name); +zapretry: + origfd = chan->fds[0]; + if (retryzap) { +#ifdef HAVE_ZAPTEL + fd = open("/dev/zap/pseudo", O_RDWR); +#else + fd = open("/dev/dahdi/pseudo", O_RDWR); +#endif + if (fd < 0) { + ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno)); + goto outrun; + } + /* Make non-blocking */ + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { + ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + /* Setup buffering information */ + memset(&bi, 0, sizeof(bi)); + bi.bufsize = CONF_SIZE; + bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE; + bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE; + bi.numbufs = 4; + if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) { + ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + nfds = 1; + } else { + /* XXX Make sure we're not running on a pseudo channel XXX */ + fd = chan->fds[0]; + nfds = 0; + } + memset(&ztc, 0, sizeof(ztc)); + /* Check to see if we're in a conference... */ + ztc.chan = 0; + if (ioctl(fd, DAHDI_GETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error getting conference\n"); + close(fd); + goto outrun; + } + if (ztc.confmode) { + /* Whoa, already in a conference... Retry... */ + if (!retryzap) { + ast_log(LOG_DEBUG, "Channel is in a conference already, retrying with pseudo\n"); + retryzap = 1; + goto zapretry; + } + } + memset(&ztc, 0, sizeof(ztc)); + /* Add us to the conference */ + ztc.chan = 0; + ztc.confno = confno; + ztc.confmode = DAHDI_CONF_MONITORBOTH; + + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + goto outrun; + } + ast_log(LOG_DEBUG, "Placed channel %s in channel %d monitor\n", chan->name, confno); + + for(;;) { + outfd = -1; + ms = -1; + c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms); + if (c) { + if (c->fds[0] != origfd) { + if (retryzap) { + /* Kill old pseudo */ + close(fd); + } + ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n"); + retryzap = 0; + goto zapretry; + } + f = ast_read(c); + if (!f) + break; + if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) { + ret = 0; + ast_frfree(f); + break; + } else if (fd != chan->fds[0]) { + if (f->frametype == AST_FRAME_VOICE) { + if (f->subclass == AST_FORMAT_ULAW) { + /* Carefully write */ + careful_write(fd, f->data, f->datalen); + } else + ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass); + } + } + ast_frfree(f); + } else if (outfd > -1) { + res = read(outfd, buf, CONF_SIZE); + if (res > 0) { + memset(&fr, 0, sizeof(fr)); + fr.frametype = AST_FRAME_VOICE; + fr.subclass = AST_FORMAT_ULAW; + fr.datalen = res; + fr.samples = res; + fr.data = buf; + fr.offset = AST_FRIENDLY_OFFSET; + if (ast_write(chan, &fr) < 0) { + ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno)); + /* break; */ + } + } else + ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno)); + } + } + if (fd != chan->fds[0]) + close(fd); + else { + /* Take out of conference */ + /* Add us to the conference */ + ztc.chan = 0; + ztc.confno = 0; + ztc.confmode = 0; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + } + } + +outrun: + + return ret; +} + +static int exec(struct ast_channel *chan, void *data, int dahdimode) +{ + int res=-1; + struct ast_module_user *u; + int retrycnt = 0; + int confflags = 0; + int confno = 0; + char confstr[80] = ""; + + u = ast_module_user_add(chan); + + if (!ast_strlen_zero(data)) { + if (dahdimode) { + if ((sscanf(data, "DAHDI/%d", &confno) != 1) && + (sscanf(data, "%d", &confno) != 1)) { + ast_log(LOG_WARNING, "Argument (if specified) must be a channel number, not '%s'\n", (char *) data); + ast_module_user_remove(u); + return 0; + } + } else { + if ((sscanf(data, "Zap/%d", &confno) != 1) && + (sscanf(data, "%d", &confno) != 1)) { + ast_log(LOG_WARNING, "Argument (if specified) must be a channel number, not '%s'\n", (char *) data); + ast_module_user_remove(u); + return 0; + } + } + } + + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + while(!confno && (++retrycnt < 4)) { + /* Prompt user for conference number */ + confstr[0] = '\0'; + res = ast_app_getdata(chan, "conf-getchannel",confstr, sizeof(confstr) - 1, 0); + if (res <0) goto out; + if (sscanf(confstr, "%d", &confno) != 1) + confno = 0; + } + if (confno) { + /* XXX Should prompt user for pin if pin is required XXX */ + /* Run the conference */ + res = conf_run(chan, confno, confflags); + } +out: + /* Do the conference */ + ast_module_user_remove(u); + return res; +} + +static int exec_zap(struct ast_channel *chan, void *data) +{ + ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", zap_app, dahdi_app); + + return exec(chan, data, 0); +} + +static int exec_dahdi(struct ast_channel *chan, void *data) +{ + return exec(chan, data, 1); +} + +static int unload_module(void) +{ + int res = 0; + + if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) { + res |= ast_unregister_application(dahdi_app); + } + + res |= ast_unregister_application(zap_app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res = 0; + + if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) { + res |= ast_register_application(dahdi_app, exec_dahdi, dahdi_synopsis, dahdi_descrip); + } + + res |= ast_register_application(zap_app, exec_zap, zap_synopsis, zap_descrip); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Barge in on channel application"); diff --git a/asterisk/apps/app_dahdiras.c b/asterisk/apps/app_dahdiras.c new file mode 100644 index 00000000..df91c76f --- /dev/null +++ b/asterisk/apps/app_dahdiras.c @@ -0,0 +1,288 @@ +/* + * 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 Execute an ISDN RAS + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + dahdi + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#ifdef __linux__ +#include +#else +#include +#endif /* __linux__ */ + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/options.h" + +#include "asterisk/dahdi_compat.h" + +static char *dahdi_app = "DAHDIRAS"; +static char *zap_app = "ZapRAS"; + +static char *dahdi_synopsis = "Executes DAHDI ISDN RAS application"; +static char *zap_synopsis = "Executes Zaptel ISDN RAS application"; + +static char *dahdi_descrip = +" DAHDIRAS(args): Executes a RAS server using pppd on the given channel.\n" +"The channel must be a clear channel (i.e. PRI source) and a DAHDI\n" +"channel to be able to use this function (no modem emulation is included).\n" +"Your pppd must have the DAHDI plugin available. Arguments should be\n" +"separated by | characters.\n"; + +static char *zap_descrip = +" ZapRAS(args): Executes a RAS server using pppd on the given channel.\n" +"The channel must be a clear channel (i.e. PRI source) and a Zaptel\n" +"channel to be able to use this function (no modem emulation is included).\n" +"Your pppd must have the Zaptel plugin available. Arguments should be\n" +"separated by | characters.\n"; + +#define PPP_MAX_ARGS 32 +#define PPP_EXEC "/usr/sbin/pppd" + +static pid_t spawn_ras(struct ast_channel *chan, char *args) +{ + pid_t pid; + int x; + char *c; + + char *argv[PPP_MAX_ARGS]; + int argc = 0; + char *stringp=NULL; + sigset_t fullset, oldset; + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + /* Start by forking */ + pid = fork(); + if (pid) { + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + return pid; + } + + /* Restore original signal handlers */ + for (x=0;xfds[0], STDIN_FILENO); + + /* Drop high priority */ + if (ast_opt_high_priority) + ast_set_priority(0); + + /* Close other file descriptors */ + for (x=STDERR_FILENO + 1;x<1024;x++) + close(x); + + /* Reset all arguments */ + memset(argv, 0, sizeof(argv)); + + /* First argument is executable, followed by standard + arguments for DAHDI PPP */ + argv[argc++] = PPP_EXEC; + argv[argc++] = "nodetach"; + + /* And all the other arguments */ + stringp=args; + c = strsep(&stringp, "|"); + while(c && strlen(c) && (argc < (PPP_MAX_ARGS - 4))) { + argv[argc++] = c; + c = strsep(&stringp, "|"); + } + + argv[argc++] = "plugin"; +#ifdef HAVE_ZAPTEL + argv[argc++] = "zaptel.so"; +#else + argv[argc++] = "dahdi.so"; +#endif + argv[argc++] = "stdin"; + + /* Finally launch PPP */ + execv(PPP_EXEC, argv); + fprintf(stderr, "Failed to exec PPPD!\n"); + exit(1); +} + +static void run_ras(struct ast_channel *chan, char *args) +{ + pid_t pid; + int status; + int res; + int signalled = 0; + struct dahdi_bufferinfo savebi; + int x; + + res = ioctl(chan->fds[0], DAHDI_GET_BUFINFO, &savebi); + if(res) { + ast_log(LOG_WARNING, "Unable to check buffer policy on channel %s\n", chan->name); + return; + } + + pid = spawn_ras(chan, args); + if (pid < 0) { + ast_log(LOG_WARNING, "Failed to spawn RAS\n"); + } else { + for (;;) { + res = wait4(pid, &status, WNOHANG, NULL); + if (!res) { + /* Check for hangup */ + if (chan->_softhangup && !signalled) { + ast_log(LOG_DEBUG, "Channel '%s' hungup. Signalling RAS at %d to die...\n", chan->name, pid); + kill(pid, SIGTERM); + signalled=1; + } + /* Try again */ + sleep(1); + continue; + } + if (res < 0) { + ast_log(LOG_WARNING, "wait4 returned %d: %s\n", res, strerror(errno)); + } + if (option_verbose > 2) { + if (WIFEXITED(status)) { + ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated with status %d\n", chan->name, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated with signal %d\n", + chan->name, WTERMSIG(status)); + } else { + ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated weirdly.\n", chan->name); + } + } + /* Throw back into audio mode */ + x = 1; + ioctl(chan->fds[0], DAHDI_AUDIOMODE, &x); + + /* Restore saved values */ + res = ioctl(chan->fds[0], DAHDI_SET_BUFINFO, &savebi); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set buffer policy on channel %s\n", chan->name); + } + break; + } + } +} + +static int exec(struct ast_channel *chan, void *data) +{ + int res=-1; + char *args; + struct ast_module_user *u; + struct dahdi_params ztp; + + if (!data) + data = ""; + + u = ast_module_user_add(chan); + + args = ast_strdupa(data); + + /* Answer the channel if it's not up */ + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + if (strcasecmp(chan->tech->type, dahdi_chan_name)) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Channel %s is not a %s channel\n", chan->name, dahdi_chan_name); + sleep(2); + } else { + memset(&ztp, 0, sizeof(ztp)); + if (ioctl(chan->fds[0], DAHDI_GET_PARAMS, &ztp)) { + ast_log(LOG_WARNING, "Unable to get parameters\n"); + } else if (ztp.sigtype != DAHDI_SIG_CLEAR) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Channel %s is not a clear channel\n", chan->name); + } else { + /* Everything should be okay. Run PPP. */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Starting RAS on %s\n", chan->name); + /* Execute RAS */ + run_ras(chan, args); + } + } + ast_module_user_remove(u); + return res; +} + +static int exec_warn(struct ast_channel *chan, void *data) +{ + ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", zap_app, dahdi_app); + + return exec(chan, data); +} + +static int unload_module(void) +{ + int res = 0; + + if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) { + res |= ast_unregister_application(dahdi_app); + } + + res |= ast_unregister_application(zap_app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res = 0; + + if (*dahdi_chan_mode == CHAN_DAHDI_PLUS_ZAP_MODE) { + res |= ast_register_application(dahdi_app, exec, dahdi_synopsis, dahdi_descrip); + } + + res |= ast_register_application(zap_app, exec_warn, zap_synopsis, zap_descrip); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DAHDI RAS Application"); + diff --git a/asterisk/apps/app_dahdiscan.c b/asterisk/apps/app_dahdiscan.c new file mode 100644 index 00000000..e358dc8d --- /dev/null +++ b/asterisk/apps/app_dahdiscan.c @@ -0,0 +1,389 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * Modified from app_zapbarge by David Troy + * + * Special thanks to comphealth.com for sponsoring this + * GPL application. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Zap Scanner + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + dahdi + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/app.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/say.h" + +#include "asterisk/dahdi_compat.h" + +static char *app = "DAHDIScan"; +static char *deprecated_app = "ZapScan"; + +static char *synopsis = "Scan Zap channels to monitor calls"; + +static char *descrip = +" ZapScan([group]) allows a call center manager to monitor Zap channels in\n" +"a convenient way. Use '#' to select the next channel and use '*' to exit\n" +"Limit scanning to a channel GROUP by setting the option group argument.\n"; + + +#define CONF_SIZE 160 + +static struct ast_channel *get_zap_channel_locked(int num) { + char name[80]; + + snprintf(name,sizeof(name),"%s/%d-1", dahdi_chan_name, num); + return ast_get_channel_by_name_locked(name); +} + +static int careful_write(int fd, unsigned char *data, int len) +{ + int res; + while(len) { + res = write(fd, data, len); + if (res < 1) { + if (errno != EAGAIN) { + ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno)); + return -1; + } else + return 0; + } + len -= res; + data += res; + } + return 0; +} + +static int conf_run(struct ast_channel *chan, int confno, int confflags) +{ + int fd; + struct dahdi_confinfo ztc; + struct ast_frame *f; + struct ast_channel *c; + struct ast_frame fr; + int outfd; + int ms; + int nfds; + int res; + int flags; + int retryzap; + int origfd; + int ret = -1; + char input[4]; + int ic=0; + struct dahdi_bufferinfo bi; + char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET]; + char *buf = __buf + AST_FRIENDLY_OFFSET; + + /* Set it into U-law mode (write) */ + if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) { + ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name); + goto outrun; + } + + /* Set it into U-law mode (read) */ + if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) { + ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name); + goto outrun; + } + ast_indicate(chan, -1); + retryzap = strcasecmp(chan->tech->type, "Zap"); + zapretry: + origfd = chan->fds[0]; + if (retryzap) { + fd = open("/dev/zap/pseudo", O_RDWR); + if (fd < 0) { + ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno)); + goto outrun; + } + /* Make non-blocking */ + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { + ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + /* Setup buffering information */ + memset(&bi, 0, sizeof(bi)); + bi.bufsize = CONF_SIZE; + bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE; + bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE; + bi.numbufs = 4; + if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) { + ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + nfds = 1; + } else { + /* XXX Make sure we're not running on a pseudo channel XXX */ + fd = chan->fds[0]; + nfds = 0; + } + memset(&ztc, 0, sizeof(ztc)); + /* Check to see if we're in a conference... */ + ztc.chan = 0; + if (ioctl(fd, DAHDI_GETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error getting conference\n"); + close(fd); + goto outrun; + } + if (ztc.confmode) { + /* Whoa, already in a conference... Retry... */ + if (!retryzap) { + ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n"); + retryzap = 1; + goto zapretry; + } + } + memset(&ztc, 0, sizeof(ztc)); + /* Add us to the conference */ + ztc.chan = 0; + ztc.confno = confno; + ztc.confmode = DAHDI_CONF_MONITORBOTH; + + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + goto outrun; + } + ast_log(LOG_DEBUG, "Placed channel %s in ZAP channel %d monitor\n", chan->name, confno); + + for(;;) { + outfd = -1; + ms = -1; + c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms); + if (c) { + if (c->fds[0] != origfd) { + if (retryzap) { + /* Kill old pseudo */ + close(fd); + } + ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n"); + retryzap = 0; + goto zapretry; + } + f = ast_read(c); + if (!f) + break; + if(f->frametype == AST_FRAME_DTMF) { + if(f->subclass == '#') { + ret = 0; + break; + } + else if (f->subclass == '*') { + ret = -1; + break; + + } + else { + input[ic++] = f->subclass; + } + if(ic == 3) { + input[ic++] = '\0'; + ic=0; + ret = atoi(input); + ast_verbose(VERBOSE_PREFIX_3 "Zapscan: change channel to %d\n",ret); + break; + } + } + + if (fd != chan->fds[0]) { + if (f->frametype == AST_FRAME_VOICE) { + if (f->subclass == AST_FORMAT_ULAW) { + /* Carefully write */ + careful_write(fd, f->data, f->datalen); + } else + ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass); + } + } + ast_frfree(f); + } else if (outfd > -1) { + res = read(outfd, buf, CONF_SIZE); + if (res > 0) { + memset(&fr, 0, sizeof(fr)); + fr.frametype = AST_FRAME_VOICE; + fr.subclass = AST_FORMAT_ULAW; + fr.datalen = res; + fr.samples = res; + fr.data = buf; + fr.offset = AST_FRIENDLY_OFFSET; + if (ast_write(chan, &fr) < 0) { + ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno)); + /* break; */ + } + } else + ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno)); + } + } + if (f) + ast_frfree(f); + if (fd != chan->fds[0]) + close(fd); + else { + /* Take out of conference */ + /* Add us to the conference */ + ztc.chan = 0; + ztc.confno = 0; + ztc.confmode = 0; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + } + } + + outrun: + + return ret; +} + +static int conf_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + struct ast_module_user *u; + int confflags = 0; + int confno = 0; + char confstr[80] = "", *tmp = NULL; + struct ast_channel *tempchan = NULL, *lastchan = NULL,*ichan = NULL; + struct ast_frame *f; + char *desired_group; + int input=0,search_group=0; + + u = ast_module_user_add(chan); + + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + desired_group = ast_strdupa(data); + if(!ast_strlen_zero(desired_group)) { + ast_verbose(VERBOSE_PREFIX_3 "Scanning for group %s\n", desired_group); + search_group = 1; + } + + for (;;) { + if (ast_waitfor(chan, 100) < 0) + break; + + f = ast_read(chan); + if (!f) + break; + if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*')) { + ast_frfree(f); + break; + } + ast_frfree(f); + ichan = NULL; + if(input) { + ichan = get_zap_channel_locked(input); + input = 0; + } + + tempchan = ichan ? ichan : ast_channel_walk_locked(tempchan); + + if ( !tempchan && !lastchan ) + break; + + if (tempchan && search_group) { + const char *mygroup; + if((mygroup = pbx_builtin_getvar_helper(tempchan, "GROUP")) && (!strcmp(mygroup, desired_group))) { + ast_verbose(VERBOSE_PREFIX_3 "Found Matching Channel %s in group %s\n", tempchan->name, desired_group); + } else { + ast_mutex_unlock(&tempchan->lock); + lastchan = tempchan; + continue; + } + } + if (tempchan && (!strcmp(tempchan->tech->type, "Zap")) && (tempchan != chan) ) { + ast_verbose(VERBOSE_PREFIX_3 "Zap channel %s is in-use, monitoring...\n", tempchan->name); + ast_copy_string(confstr, tempchan->name, sizeof(confstr)); + ast_mutex_unlock(&tempchan->lock); + if ((tmp = strchr(confstr,'-'))) { + *tmp = '\0'; + } + confno = atoi(strchr(confstr,'/') + 1); + ast_stopstream(chan); + ast_say_number(chan, confno, AST_DIGIT_ANY, chan->language, (char *) NULL); + res = conf_run(chan, confno, confflags); + if (res<0) break; + input = res; + } else if (tempchan) + ast_mutex_unlock(&tempchan->lock); + lastchan = tempchan; + } + ast_module_user_remove(u); + return res; +} + +static int conf_exec_warn(struct ast_channel *chan, void *data) +{ + ast_log(LOG_WARNING, "Use of the command %s is deprecated, please use %s instead.\n", deprecated_app, app); + return conf_exec(chan, data); +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + ast_register_application(deprecated_app, conf_exec_warn, synopsis, descrip); + return ast_register_application(app, conf_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Scan Zap channels application"); + diff --git a/asterisk/apps/app_db.c b/asterisk/apps/app_db.c new file mode 100644 index 00000000..f39a199a --- /dev/null +++ b/asterisk/apps/app_db.c @@ -0,0 +1,167 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * Copyright (C) 2003, Jefferson Noxon + * + * Mark Spencer + * Jefferson Noxon + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Database access functions + * + * \author Mark Spencer + * \author Jefferson Noxon + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 47782 $") + +#include +#include +#include +#include +#include + +#include "asterisk/options.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/astdb.h" +#include "asterisk/lock.h" +#include "asterisk/options.h" + +/*! \todo XXX Remove this application after 1.4 is relased */ +static char *d_descrip = +" DBdel(family/key): This application will delete a key from the Asterisk\n" +"database.\n" +" This application has been DEPRECATED in favor of the DB_DELETE function.\n"; + +static char *dt_descrip = +" DBdeltree(family[/keytree]): This application will delete a family or keytree\n" +"from the Asterisk database\n"; + +static char *d_app = "DBdel"; +static char *dt_app = "DBdeltree"; + +static char *d_synopsis = "Delete a key from the database"; +static char *dt_synopsis = "Delete a family or keytree from the database"; + + +static int deltree_exec(struct ast_channel *chan, void *data) +{ + char *argv, *family, *keytree; + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + argv = ast_strdupa(data); + + if (strchr(argv, '/')) { + family = strsep(&argv, "/"); + keytree = strsep(&argv, "\0"); + if (!family || !keytree) { + ast_log(LOG_DEBUG, "Ignoring; Syntax error in argument\n"); + ast_module_user_remove(u); + return 0; + } + if (ast_strlen_zero(keytree)) + keytree = 0; + } else { + family = argv; + keytree = 0; + } + + if (option_verbose > 2) { + if (keytree) + ast_verbose(VERBOSE_PREFIX_3 "DBdeltree: family=%s, keytree=%s\n", family, keytree); + else + ast_verbose(VERBOSE_PREFIX_3 "DBdeltree: family=%s\n", family); + } + + if (ast_db_deltree(family, keytree)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "DBdeltree: Error deleting key from database.\n"); + } + + ast_module_user_remove(u); + + return 0; +} + +static int del_exec(struct ast_channel *chan, void *data) +{ + char *argv, *family, *key; + struct ast_module_user *u; + static int deprecation_warning = 0; + + u = ast_module_user_add(chan); + + if (!deprecation_warning) { + deprecation_warning = 1; + ast_log(LOG_WARNING, "The DBdel application has been deprecated in favor of the DB_DELETE dialplan function!\n"); + } + + argv = ast_strdupa(data); + + if (strchr(argv, '/')) { + family = strsep(&argv, "/"); + key = strsep(&argv, "\0"); + if (!family || !key) { + ast_log(LOG_DEBUG, "Ignoring; Syntax error in argument\n"); + ast_module_user_remove(u); + return 0; + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "DBdel: family=%s, key=%s\n", family, key); + if (ast_db_del(family, key)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "DBdel: Error deleting key from database.\n"); + } + } else { + ast_log(LOG_DEBUG, "Ignoring, no parameters\n"); + } + + ast_module_user_remove(u); + + return 0; +} + +static int unload_module(void) +{ + int retval; + + retval = ast_unregister_application(dt_app); + retval |= ast_unregister_application(d_app); + + return retval; +} + +static int load_module(void) +{ + int retval; + + retval = ast_register_application(d_app, del_exec, d_synopsis, d_descrip); + retval |= ast_register_application(dt_app, deltree_exec, dt_synopsis, dt_descrip); + + return retval; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Database Access Functions"); diff --git a/asterisk/apps/app_dial.c b/asterisk/apps/app_dial.c new file mode 100644 index 00000000..1fdd94b5 --- /dev/null +++ b/asterisk/apps/app_dial.c @@ -0,0 +1,1977 @@ +/* + * 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 dial() & retrydial() - Trivial application to dial a channel and send an URL on answer + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + chan_local + ***/ + + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/options.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/say.h" +#include "asterisk/config.h" +#include "asterisk/features.h" +#include "asterisk/musiconhold.h" +#include "asterisk/callerid.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" +#include "asterisk/causes.h" +#include "asterisk/rtp.h" +#include "asterisk/cdr.h" +#include "asterisk/manager.h" +#include "asterisk/privacy.h" +#include "asterisk/stringfields.h" +#include "asterisk/global_datastores.h" + +static char *app = "Dial"; + +static char *synopsis = "Place a call and connect to the current channel"; + +static char *descrip = +" Dial(Technology/resource[&Tech2/resource2...][|timeout][|options][|URL]):\n" +"This application will place calls to one or more specified channels. As soon\n" +"as one of the requested channels answers, the originating channel will be\n" +"answered, if it has not already been answered. These two channels will then\n" +"be active in a bridged call. All other channels that were requested will then\n" +"be hung up.\n" +" Unless there is a timeout specified, the Dial application will wait\n" +"indefinitely until one of the called channels answers, the user hangs up, or\n" +"if all of the called channels are busy or unavailable. Dialplan executing will\n" +"continue if no requested channels can be called, or if the timeout expires.\n\n" +" This application sets the following channel variables upon completion:\n" +" DIALEDTIME - This is the time from dialing a channel until when it\n" +" is disconnected.\n" +" ANSWEREDTIME - This is the amount of time for actual call.\n" +" DIALSTATUS - This is the status of the call:\n" +" CHANUNAVAIL | CONGESTION | NOANSWER | BUSY | ANSWER | CANCEL\n" +" DONTCALL | TORTURE | INVALIDARGS\n" +" For the Privacy and Screening Modes, the DIALSTATUS variable will be set to\n" +"DONTCALL if the called party chooses to send the calling party to the 'Go Away'\n" +"script. The DIALSTATUS variable will be set to TORTURE if the called party\n" +"wants to send the caller to the 'torture' script.\n" +" This application will report normal termination if the originating channel\n" +"hangs up, or if the call is bridged and either of the parties in the bridge\n" +"ends the call.\n" +" The optional URL will be sent to the called party if the channel supports it.\n" +" If the OUTBOUND_GROUP variable is set, all peer channels created by this\n" +"application will be put into that group (as in Set(GROUP()=...).\n" +" If the OUTBOUND_GROUP_ONCE variable is set, all peer channels created by this\n" +"application will be put into that group (as in Set(GROUP()=...). Unlike OUTBOUND_GROUP,\n" +"however, the variable will be unset after use.\n\n" +" Options:\n" +" A(x) - Play an announcement to the called party, using 'x' as the file.\n" +" C - Reset the CDR for this call.\n" +" d - Allow the calling user to dial a 1 digit extension while waiting for\n" +" a call to be answered. Exit to that extension if it exists in the\n" +" current context, or the context defined in the EXITCONTEXT variable,\n" +" if it exists.\n" +" D([called][:calling]) - Send the specified DTMF strings *after* the called\n" +" party has answered, but before the call gets bridged. The 'called'\n" +" DTMF string is sent to the called party, and the 'calling' DTMF\n" +" string is sent to the calling party. Both parameters can be used\n" +" alone.\n" +" f - Force the callerid of the *calling* channel to be set as the\n" +" extension associated with the channel using a dialplan 'hint'.\n" +" For example, some PSTNs do not allow CallerID to be set to anything\n" +" other than the number assigned to the caller.\n" +" g - Proceed with dialplan execution at the current extension if the\n" +" destination channel hangs up.\n" +" G(context^exten^pri) - If the call is answered, transfer the calling party to\n" +" the specified priority and the called party to the specified priority+1.\n" +" Optionally, an extension, or extension and context may be specified. \n" +" Otherwise, the current extension is used. You cannot use any additional\n" +" action post answer options in conjunction with this option.\n" +" h - Allow the called party to hang up by sending the '*' DTMF digit.\n" +" H - Allow the calling party to hang up by hitting the '*' DTMF digit.\n" +" i - Asterisk will ignore any forwarding requests it may receive on this\n" +" dial attempt.\n" +" j - Jump to priority n+101 if all of the requested channels were busy.\n" +" k - Allow the called party to enable parking of the call by sending\n" +" the DTMF sequence defined for call parking in features.conf.\n" +" K - Allow the calling party to enable parking of the call by sending\n" +" the DTMF sequence defined for call parking in features.conf.\n" +" L(x[:y][:z]) - Limit the call to 'x' ms. Play a warning when 'y' ms are\n" +" left. Repeat the warning every 'z' ms. The following special\n" +" variables can be used with this option:\n" +" * LIMIT_PLAYAUDIO_CALLER yes|no (default yes)\n" +" Play sounds to the caller.\n" +" * LIMIT_PLAYAUDIO_CALLEE yes|no\n" +" Play sounds to the callee.\n" +" * LIMIT_TIMEOUT_FILE File to play when time is up.\n" +" * LIMIT_CONNECT_FILE File to play when call begins.\n" +" * LIMIT_WARNING_FILE File to play as warning if 'y' is defined.\n" +" The default is to say the time remaining.\n" +" m([class]) - Provide hold music to the calling party until a requested\n" +" channel answers. A specific MusicOnHold class can be\n" +" specified.\n" +" M(x[^arg]) - Execute the Macro for the *called* channel before connecting\n" +" to the calling channel. Arguments can be specified to the Macro\n" +" using '^' as a delimeter. The Macro can set the variable\n" +" MACRO_RESULT to specify the following actions after the Macro is\n" +" finished executing.\n" +" * ABORT Hangup both legs of the call.\n" +" * CONGESTION Behave as if line congestion was encountered.\n" +" * BUSY Behave as if a busy signal was encountered. This will also\n" +" have the application jump to priority n+101 if the\n" +" 'j' option is set.\n" +" * CONTINUE Hangup the called party and allow the calling party\n" +" to continue dialplan execution at the next priority.\n" +" * GOTO:^^ - Transfer the call to the\n" +" specified priority. Optionally, an extension, or\n" +" extension and priority can be specified.\n" +" You cannot use any additional action post answer options in conjunction\n" +" with this option. Also, pbx services are not run on the peer (called) channel,\n" +" so you will not be able to set timeouts via the TIMEOUT() function in this macro.\n" +" n - This option is a modifier for the screen/privacy mode. It specifies\n" +" that no introductions are to be saved in the priv-callerintros\n" +" directory.\n" +" N - This option is a modifier for the screen/privacy mode. It specifies\n" +" that if callerID is present, do not screen the call.\n" +" o - Specify that the CallerID that was present on the *calling* channel\n" +" be set as the CallerID on the *called* channel. This was the\n" +" behavior of Asterisk 1.0 and earlier.\n" +" O([x]) - \"Operator Services\" mode (Zaptel channel to Zaptel channel\n" +" only, if specified on non-Zaptel interface, it will be ignored).\n" +" When the destination answers (presumably an operator services\n" +" station), the originator no longer has control of their line.\n" +" They may hang up, but the switch will not release their line\n" +" until the destination party hangs up (the operator). Specified\n" +" without an arg, or with 1 as an arg, the originator hanging up\n" +" will cause the phone to ring back immediately. With a 2 specified,\n" +" when the \"operator\" flashes the trunk, it will ring their phone\n" +" back.\n" +" p - This option enables screening mode. This is basically Privacy mode\n" +" without memory.\n" +" P([x]) - Enable privacy mode. Use 'x' as the family/key in the database if\n" +" it is provided. The current extension is used if a database\n" +" family/key is not specified.\n" +" r - Indicate ringing to the calling party. Pass no audio to the calling\n" +" party until the called channel has answered.\n" +" S(x) - Hang up the call after 'x' seconds *after* the called party has\n" +" answered the call.\n" +" t - Allow the called party to transfer the calling party by sending the\n" +" DTMF sequence defined in features.conf.\n" +" T - Allow the calling party to transfer the called party by sending the\n" +" DTMF sequence defined in features.conf.\n" +" w - Allow the called party to enable recording of the call by sending\n" +" the DTMF sequence defined for one-touch recording in features.conf.\n" +" W - Allow the calling party to enable recording of the call by sending\n" +" the DTMF sequence defined for one-touch recording in features.conf.\n"; + +/* RetryDial App by Anthony Minessale II Jan/2005 */ +static char *rapp = "RetryDial"; +static char *rsynopsis = "Place a call, retrying on failure allowing optional exit extension."; +static char *rdescrip = +" RetryDial(announce|sleep|retries|dialargs): This application will attempt to\n" +"place a call using the normal Dial application. If no channel can be reached,\n" +"the 'announce' file will be played. Then, it will wait 'sleep' number of\n" +"seconds before retrying the call. After 'retries' number of attempts, the\n" +"calling channel will continue at the next priority in the dialplan. If the\n" +"'retries' setting is set to 0, this application will retry endlessly.\n" +" While waiting to retry a call, a 1 digit extension may be dialed. If that\n" +"extension exists in either the context defined in ${EXITCONTEXT} or the current\n" +"one, The call will jump to that extension immediately.\n" +" The 'dialargs' are specified in the same format that arguments are provided\n" +"to the Dial application.\n"; + +enum { + OPT_ANNOUNCE = (1 << 0), + OPT_RESETCDR = (1 << 1), + OPT_DTMF_EXIT = (1 << 2), + OPT_SENDDTMF = (1 << 3), + OPT_FORCECLID = (1 << 4), + OPT_GO_ON = (1 << 5), + OPT_CALLEE_HANGUP = (1 << 6), + OPT_CALLER_HANGUP = (1 << 7), + OPT_PRIORITY_JUMP = (1 << 8), + OPT_DURATION_LIMIT = (1 << 9), + OPT_MUSICBACK = (1 << 10), + OPT_CALLEE_MACRO = (1 << 11), + OPT_SCREEN_NOINTRO = (1 << 12), + OPT_SCREEN_NOCLID = (1 << 13), + OPT_ORIGINAL_CLID = (1 << 14), + OPT_SCREENING = (1 << 15), + OPT_PRIVACY = (1 << 16), + OPT_RINGBACK = (1 << 17), + OPT_DURATION_STOP = (1 << 18), + OPT_CALLEE_TRANSFER = (1 << 19), + OPT_CALLER_TRANSFER = (1 << 20), + OPT_CALLEE_MONITOR = (1 << 21), + OPT_CALLER_MONITOR = (1 << 22), + OPT_GOTO = (1 << 23), + OPT_OPERMODE = (1 << 24), + OPT_CALLEE_PARK = (1 << 25), + OPT_CALLER_PARK = (1 << 26), + OPT_IGNORE_FORWARDING = (1 << 27), +} dial_exec_option_flags; + +#define DIAL_STILLGOING (1 << 30) +#define DIAL_NOFORWARDHTML (1 << 31) + +enum { + OPT_ARG_ANNOUNCE = 0, + OPT_ARG_SENDDTMF, + OPT_ARG_GOTO, + OPT_ARG_DURATION_LIMIT, + OPT_ARG_MUSICBACK, + OPT_ARG_CALLEE_MACRO, + OPT_ARG_PRIVACY, + OPT_ARG_DURATION_STOP, + OPT_ARG_OPERMODE, + /* note: this entry _MUST_ be the last one in the enum */ + OPT_ARG_ARRAY_SIZE, +} dial_exec_option_args; + +AST_APP_OPTIONS(dial_exec_options, { + AST_APP_OPTION_ARG('A', OPT_ANNOUNCE, OPT_ARG_ANNOUNCE), + AST_APP_OPTION('C', OPT_RESETCDR), + AST_APP_OPTION('d', OPT_DTMF_EXIT), + AST_APP_OPTION_ARG('D', OPT_SENDDTMF, OPT_ARG_SENDDTMF), + AST_APP_OPTION('f', OPT_FORCECLID), + AST_APP_OPTION('g', OPT_GO_ON), + AST_APP_OPTION_ARG('G', OPT_GOTO, OPT_ARG_GOTO), + AST_APP_OPTION('h', OPT_CALLEE_HANGUP), + AST_APP_OPTION('H', OPT_CALLER_HANGUP), + AST_APP_OPTION('i', OPT_IGNORE_FORWARDING), + AST_APP_OPTION('j', OPT_PRIORITY_JUMP), + AST_APP_OPTION('k', OPT_CALLEE_PARK), + AST_APP_OPTION('K', OPT_CALLER_PARK), + AST_APP_OPTION_ARG('L', OPT_DURATION_LIMIT, OPT_ARG_DURATION_LIMIT), + AST_APP_OPTION_ARG('m', OPT_MUSICBACK, OPT_ARG_MUSICBACK), + AST_APP_OPTION_ARG('M', OPT_CALLEE_MACRO, OPT_ARG_CALLEE_MACRO), + AST_APP_OPTION('n', OPT_SCREEN_NOINTRO), + AST_APP_OPTION('N', OPT_SCREEN_NOCLID), + AST_APP_OPTION('o', OPT_ORIGINAL_CLID), + AST_APP_OPTION_ARG('O', OPT_OPERMODE,OPT_ARG_OPERMODE), + AST_APP_OPTION('p', OPT_SCREENING), + AST_APP_OPTION_ARG('P', OPT_PRIVACY, OPT_ARG_PRIVACY), + AST_APP_OPTION('r', OPT_RINGBACK), + AST_APP_OPTION_ARG('S', OPT_DURATION_STOP, OPT_ARG_DURATION_STOP), + AST_APP_OPTION('t', OPT_CALLEE_TRANSFER), + AST_APP_OPTION('T', OPT_CALLER_TRANSFER), + AST_APP_OPTION('w', OPT_CALLEE_MONITOR), + AST_APP_OPTION('W', OPT_CALLER_MONITOR), +}); + +#define CAN_EARLY_BRIDGE(flags,chan,peer) (!ast_test_flag(flags, OPT_CALLEE_HANGUP | \ + OPT_CALLER_HANGUP | OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | \ + OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR | OPT_CALLEE_PARK | OPT_CALLER_PARK) && \ + !chan->audiohooks && !peer->audiohooks) + +/* We define a custom "local user" structure because we + use it not only for keeping track of what is in use but + also for keeping track of who we're dialing. */ + +struct dial_localuser { + struct ast_channel *chan; + unsigned int flags; + struct dial_localuser *next; +}; + + +static void hanguptree(struct dial_localuser *outgoing, struct ast_channel *exception) +{ + /* Hang up a tree of stuff */ + struct dial_localuser *oo; + while (outgoing) { + /* Hangup any existing lines we have open */ + if (outgoing->chan && (outgoing->chan != exception)) + ast_hangup(outgoing->chan); + oo = outgoing; + outgoing=outgoing->next; + free(oo); + } +} + +#define AST_MAX_WATCHERS 256 + +#define HANDLE_CAUSE(cause, chan) do { \ + switch(cause) { \ + case AST_CAUSE_BUSY: \ + if (chan->cdr) \ + ast_cdr_busy(chan->cdr); \ + numbusy++; \ + break; \ + case AST_CAUSE_CONGESTION: \ + if (chan->cdr) \ + ast_cdr_failed(chan->cdr); \ + numcongestion++; \ + break; \ + case AST_CAUSE_NO_ROUTE_DESTINATION: \ + case AST_CAUSE_UNREGISTERED: \ + if (chan->cdr) \ + ast_cdr_failed(chan->cdr); \ + numnochan++; \ + break; \ + case AST_CAUSE_NORMAL_CLEARING: \ + break; \ + default: \ + numnochan++; \ + break; \ + } \ +} while (0) + + +static int onedigit_goto(struct ast_channel *chan, const char *context, char exten, int pri) +{ + char rexten[2] = { exten, '\0' }; + + if (context) { + if (!ast_goto_if_exists(chan, context, rexten, pri)) + return 1; + } else { + if (!ast_goto_if_exists(chan, chan->context, rexten, pri)) + return 1; + else if (!ast_strlen_zero(chan->macrocontext)) { + if (!ast_goto_if_exists(chan, chan->macrocontext, rexten, pri)) + return 1; + } + } + return 0; +} + + +static const char *get_cid_name(char *name, int namelen, struct ast_channel *chan) +{ + const char *context = S_OR(chan->macrocontext, chan->context); + const char *exten = S_OR(chan->macroexten, chan->exten); + + return ast_get_hint(NULL, 0, name, namelen, chan, context, exten) ? name : ""; +} + +static void senddialevent(struct ast_channel *src, struct ast_channel *dst) +{ + /* XXX do we need also CallerIDnum ? */ + manager_event(EVENT_FLAG_CALL, "Dial", + "Source: %s\r\n" + "Destination: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "SrcUniqueID: %s\r\n" + "DestUniqueID: %s\r\n", + src->name, dst->name, S_OR(src->cid.cid_num, ""), + S_OR(src->cid.cid_name, ""), src->uniqueid, + dst->uniqueid); +} + +static struct ast_channel *wait_for_answer(struct ast_channel *in, struct dial_localuser *outgoing, int *to, struct ast_flags *peerflags, int *sentringing, char *status, size_t statussize, int busystart, int nochanstart, int congestionstart, int priority_jump, int *result) +{ + int numbusy = busystart; + int numcongestion = congestionstart; + int numnochan = nochanstart; + int prestart = busystart + congestionstart + nochanstart; + int orig = *to; + struct ast_channel *peer = NULL; + /* single is set if only one destination is enabled */ + int single = outgoing && !outgoing->next && !ast_test_flag(outgoing, OPT_MUSICBACK | OPT_RINGBACK); + + if (single) { + /* Turn off hold music, etc */ + ast_deactivate_generator(in); + /* If we are calling a single channel, make them compatible for in-band tone purpose */ + ast_channel_make_compatible(outgoing->chan, in); + } + + + while (*to && !peer) { + struct dial_localuser *o; + int pos = 0; /* how many channels do we handle */ + int numlines = prestart; + struct ast_channel *winner; + struct ast_channel *watchers[AST_MAX_WATCHERS]; + + watchers[pos++] = in; + for (o = outgoing; o; o = o->next) { + /* Keep track of important channels */ + if (ast_test_flag(o, DIAL_STILLGOING) && o->chan) + watchers[pos++] = o->chan; + numlines++; + } + if (pos == 1) { /* only the input channel is available */ + if (numlines == (numbusy + numcongestion + numnochan)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_2 "Everyone is busy/congested at this time (%d:%d/%d/%d)\n", numlines, numbusy, numcongestion, numnochan); + if (numbusy) + strcpy(status, "BUSY"); + else if (numcongestion) + strcpy(status, "CONGESTION"); + else if (numnochan) + strcpy(status, "CHANUNAVAIL"); + if (ast_opt_priority_jumping || priority_jump) + ast_goto_if_exists(in, in->context, in->exten, in->priority + 101); + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, numbusy, numcongestion, numnochan); + } + *to = 0; + return NULL; + } + winner = ast_waitfor_n(watchers, pos, to); + for (o = outgoing; o; o = o->next) { + struct ast_frame *f; + struct ast_channel *c = o->chan; + + if (c == NULL) + continue; + if (ast_test_flag(o, DIAL_STILLGOING) && c->_state == AST_STATE_UP) { + if (!peer) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s answered %s\n", c->name, in->name); + peer = c; + ast_copy_flags(peerflags, o, + OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | + OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP | + OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR | + OPT_CALLEE_PARK | OPT_CALLER_PARK | + DIAL_NOFORWARDHTML); + ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext)); + ast_copy_string(c->exten, "", sizeof(c->exten)); + } + continue; + } + if (c != winner) + continue; + if (!ast_strlen_zero(c->call_forward)) { + char tmpchan[256]; + char *stuff; + char *tech; + int cause; + + ast_copy_string(tmpchan, c->call_forward, sizeof(tmpchan)); + if ((stuff = strchr(tmpchan, '/'))) { + *stuff++ = '\0'; + tech = tmpchan; + } else { + const char *forward_context = pbx_builtin_getvar_helper(c, "FORWARD_CONTEXT"); + snprintf(tmpchan, sizeof(tmpchan), "%s@%s", c->call_forward, forward_context ? forward_context : c->context); + stuff = tmpchan; + tech = "Local"; + } + /* Before processing channel, go ahead and check for forwarding */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, c->name); + /* If we have been told to ignore forwards, just set this channel to null and continue processing extensions normally */ + if (ast_test_flag(peerflags, OPT_IGNORE_FORWARDING)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Forwarding %s to '%s/%s' prevented.\n", in->name, tech, stuff); + c = o->chan = NULL; + cause = AST_CAUSE_BUSY; + } else { + /* Setup parameters */ + if ((c = o->chan = ast_request(tech, in->nativeformats, stuff, &cause))) { + if (single) + ast_channel_make_compatible(o->chan, in); + ast_channel_inherit_variables(in, o->chan); + ast_channel_datastore_inherit(in, o->chan); + } else + ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s' (cause = %d)\n", tech, stuff, cause); + } + if (!c) { + ast_clear_flag(o, DIAL_STILLGOING); + HANDLE_CAUSE(cause, in); + } else { + ast_rtp_make_compatible(c, in, single); + if (c->cid.cid_num) + free(c->cid.cid_num); + c->cid.cid_num = NULL; + if (c->cid.cid_name) + free(c->cid.cid_name); + c->cid.cid_name = NULL; + + if (ast_test_flag(o, OPT_FORCECLID)) { + c->cid.cid_num = ast_strdup(S_OR(in->macroexten, in->exten)); + ast_string_field_set(c, accountcode, winner->accountcode); + c->cdrflags = winner->cdrflags; + } else { + c->cid.cid_num = ast_strdup(in->cid.cid_num); + c->cid.cid_name = ast_strdup(in->cid.cid_name); + ast_string_field_set(c, accountcode, in->accountcode); + c->cdrflags = in->cdrflags; + } + + if (in->cid.cid_ani) { + if (c->cid.cid_ani) + free(c->cid.cid_ani); + c->cid.cid_ani = ast_strdup(in->cid.cid_ani); + } + if (c->cid.cid_rdnis) + free(c->cid.cid_rdnis); + c->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten)); + if (ast_call(c, tmpchan, 0)) { + ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan); + ast_clear_flag(o, DIAL_STILLGOING); + ast_hangup(c); + c = o->chan = NULL; + numnochan++; + } else { + senddialevent(in, c); + /* After calling, set callerid to extension */ + if (!ast_test_flag(peerflags, OPT_ORIGINAL_CLID)) { + char cidname[AST_MAX_EXTENSION] = ""; + ast_set_callerid(c, S_OR(in->macroexten, in->exten), get_cid_name(cidname, sizeof(cidname), in), NULL); + } + } + } + /* Hangup the original channel now, in case we needed it */ + ast_hangup(winner); + continue; + } + f = ast_read(winner); + if (!f) { + in->hangupcause = c->hangupcause; + ast_hangup(c); + c = o->chan = NULL; + ast_clear_flag(o, DIAL_STILLGOING); + HANDLE_CAUSE(in->hangupcause, in); + continue; + } + if (f->frametype == AST_FRAME_CONTROL) { + switch(f->subclass) { + case AST_CONTROL_ANSWER: + /* This is our guy if someone answered. */ + if (!peer) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", c->name, in->name); + peer = c; + if (peer->cdr) { + peer->cdr->answer = ast_tvnow(); + peer->cdr->disposition = AST_CDR_ANSWERED; + } + ast_copy_flags(peerflags, o, + OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | + OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP | + OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR | + OPT_CALLEE_PARK | OPT_CALLER_PARK | + DIAL_NOFORWARDHTML); + ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext)); + ast_copy_string(c->exten, "", sizeof(c->exten)); + /* Setup RTP early bridge if appropriate */ + if (CAN_EARLY_BRIDGE(peerflags, in, peer)) + ast_rtp_early_bridge(in, peer); + } + /* If call has been answered, then the eventual hangup is likely to be normal hangup */ + in->hangupcause = AST_CAUSE_NORMAL_CLEARING; + c->hangupcause = AST_CAUSE_NORMAL_CLEARING; + break; + case AST_CONTROL_BUSY: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s is busy\n", c->name); + in->hangupcause = c->hangupcause; + ast_hangup(c); + c = o->chan = NULL; + ast_clear_flag(o, DIAL_STILLGOING); + HANDLE_CAUSE(AST_CAUSE_BUSY, in); + break; + case AST_CONTROL_CONGESTION: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s is circuit-busy\n", c->name); + in->hangupcause = c->hangupcause; + ast_hangup(c); + c = o->chan = NULL; + ast_clear_flag(o, DIAL_STILLGOING); + HANDLE_CAUSE(AST_CAUSE_CONGESTION, in); + break; + case AST_CONTROL_RINGING: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s is ringing\n", c->name); + /* Setup early media if appropriate */ + if (single && CAN_EARLY_BRIDGE(peerflags, in, c)) + ast_rtp_early_bridge(in, c); + if (!(*sentringing) && !ast_test_flag(outgoing, OPT_MUSICBACK)) { + ast_indicate(in, AST_CONTROL_RINGING); + (*sentringing)++; + } + break; + case AST_CONTROL_PROGRESS: + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "%s is making progress passing it to %s\n", c->name, in->name); + /* Setup early media if appropriate */ + if (single && CAN_EARLY_BRIDGE(peerflags, in, c)) + ast_rtp_early_bridge(in, c); + if (!ast_test_flag(outgoing, OPT_RINGBACK)) + ast_indicate(in, AST_CONTROL_PROGRESS); + break; + case AST_CONTROL_VIDUPDATE: + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "%s requested a video update, passing it to %s\n", c->name, in->name); + ast_indicate(in, AST_CONTROL_VIDUPDATE); + break; + case AST_CONTROL_SRCUPDATE: + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "%s requested a source update, passing it to %s\n", c->name, in->name); + ast_indicate(in, AST_CONTROL_SRCUPDATE); + break; + case AST_CONTROL_PROCEEDING: + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "%s is proceeding passing it to %s\n", c->name, in->name); + if (single && CAN_EARLY_BRIDGE(peerflags, in, c)) + ast_rtp_early_bridge(in, c); + if (!ast_test_flag(outgoing, OPT_RINGBACK)) + ast_indicate(in, AST_CONTROL_PROCEEDING); + break; + case AST_CONTROL_HOLD: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Call on %s placed on hold\n", c->name); + ast_indicate(in, AST_CONTROL_HOLD); + break; + case AST_CONTROL_UNHOLD: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Call on %s left from hold\n", c->name); + ast_indicate(in, AST_CONTROL_UNHOLD); + break; + case AST_CONTROL_OFFHOOK: + case AST_CONTROL_FLASH: + /* Ignore going off hook and flash */ + break; + case -1: + if (!ast_test_flag(outgoing, OPT_RINGBACK | OPT_MUSICBACK)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s stopped sounds\n", c->name); + ast_indicate(in, -1); + (*sentringing) = 0; + } + break; + default: + if (option_debug) + ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass); + } + } else if (single) { + /* XXX are we sure the logic is correct ? or we should just switch on f->frametype ? */ + if (f->frametype == AST_FRAME_VOICE && !ast_test_flag(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) { + if (ast_write(in, f)) + ast_log(LOG_WARNING, "Unable to forward voice frame\n"); + } else if (f->frametype == AST_FRAME_IMAGE && !ast_test_flag(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) { + if (ast_write(in, f)) + ast_log(LOG_WARNING, "Unable to forward image\n"); + } else if (f->frametype == AST_FRAME_TEXT && !ast_test_flag(outgoing, OPT_RINGBACK|OPT_MUSICBACK)) { + if (ast_write(in, f)) + ast_log(LOG_WARNING, "Unable to send text\n"); + } else if (f->frametype == AST_FRAME_HTML && !ast_test_flag(outgoing, DIAL_NOFORWARDHTML)) { + if (ast_channel_sendhtml(in, f->subclass, f->data, f->datalen) == -1) + ast_log(LOG_WARNING, "Unable to send URL\n"); + } + } + ast_frfree(f); + } /* end for */ + if (winner == in) { + struct ast_frame *f = ast_read(in); +#if 0 + if (f && (f->frametype != AST_FRAME_VOICE)) + printf("Frame type: %d, %d\n", f->frametype, f->subclass); + else if (!f || (f->frametype != AST_FRAME_VOICE)) + printf("Hangup received on %s\n", in->name); +#endif + if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) { + /* Got hung up */ + *to = -1; + ast_cdr_noanswer(in->cdr); + strcpy(status, "CANCEL"); + if (f) + ast_frfree(f); + return NULL; + } + + if (f && (f->frametype == AST_FRAME_DTMF)) { + if (ast_test_flag(peerflags, OPT_DTMF_EXIT)) { + const char *context = pbx_builtin_getvar_helper(in, "EXITCONTEXT"); + if (onedigit_goto(in, context, (char) f->subclass, 1)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass); + *to=0; + ast_cdr_noanswer(in->cdr); + *result = f->subclass; + strcpy(status, "CANCEL"); + ast_frfree(f); + return NULL; + } + } + + if (ast_test_flag(peerflags, OPT_CALLER_HANGUP) && + (f->subclass == '*')) { /* hmm it it not guaranteed to be '*' anymore. */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass); + *to=0; + ast_cdr_noanswer(in->cdr); + strcpy(status, "CANCEL"); + ast_frfree(f); + return NULL; + } + } + + /* Forward HTML stuff */ + if (single && f && (f->frametype == AST_FRAME_HTML) && !ast_test_flag(outgoing, DIAL_NOFORWARDHTML)) + if(ast_channel_sendhtml(outgoing->chan, f->subclass, f->data, f->datalen) == -1) + ast_log(LOG_WARNING, "Unable to send URL\n"); + + + if (single && ((f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_DTMF_BEGIN) || (f->frametype == AST_FRAME_DTMF_END))) { + if (ast_write(outgoing->chan, f)) + ast_log(LOG_WARNING, "Unable to forward voice or dtmf\n"); + } + if (single && (f->frametype == AST_FRAME_CONTROL) && + ((f->subclass == AST_CONTROL_HOLD) || + (f->subclass == AST_CONTROL_UNHOLD) || + (f->subclass == AST_CONTROL_VIDUPDATE) || + (f->subclass == AST_CONTROL_SRCUPDATE))) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "%s requested special control %d, passing it to %s\n", in->name, f->subclass, outgoing->chan->name); + ast_indicate_data(outgoing->chan, f->subclass, f->data, f->datalen); + } + ast_frfree(f); + } + if (!*to && (option_verbose > 2)) + ast_verbose(VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig); + if (!*to || ast_check_hangup(in)) { + ast_cdr_noanswer(in->cdr); + } + + } + + return peer; +} + +static void replace_macro_delimiter(char *s) +{ + for (; *s; s++) + if (*s == '^') + *s = '|'; +} + + +/* returns true if there is a valid privacy reply */ +static int valid_priv_reply(struct ast_flags *opts, int res) +{ + if (res < '1') + return 0; + if (ast_test_flag(opts, OPT_PRIVACY) && res <= '5') + return 1; + if (ast_test_flag(opts, OPT_SCREENING) && res <= '4') + return 1; + return 0; +} + +static void set_dial_features(struct ast_flags *opts, struct ast_dial_features *features) +{ + struct ast_flags perm_opts = {.flags = 0}; + + ast_copy_flags(&perm_opts, opts, + OPT_CALLER_TRANSFER | OPT_CALLER_PARK | OPT_CALLER_MONITOR | OPT_CALLER_HANGUP | + OPT_CALLEE_TRANSFER | OPT_CALLEE_PARK | OPT_CALLEE_MONITOR | OPT_CALLEE_HANGUP); + + memset(features->options, 0, sizeof(features->options)); + + ast_app_options2str(dial_exec_options, &perm_opts, features->options, sizeof(features->options)); + if (ast_test_flag(&perm_opts, OPT_CALLEE_TRANSFER)) + ast_set_flag(&(features->features_callee), AST_FEATURE_REDIRECT); + if (ast_test_flag(&perm_opts, OPT_CALLER_TRANSFER)) + ast_set_flag(&(features->features_caller), AST_FEATURE_REDIRECT); + if (ast_test_flag(&perm_opts, OPT_CALLEE_HANGUP)) + ast_set_flag(&(features->features_callee), AST_FEATURE_DISCONNECT); + if (ast_test_flag(&perm_opts, OPT_CALLER_HANGUP)) + ast_set_flag(&(features->features_caller), AST_FEATURE_DISCONNECT); + if (ast_test_flag(&perm_opts, OPT_CALLEE_MONITOR)) + ast_set_flag(&(features->features_callee), AST_FEATURE_AUTOMON); + if (ast_test_flag(&perm_opts, OPT_CALLER_MONITOR)) + ast_set_flag(&(features->features_caller), AST_FEATURE_AUTOMON); + if (ast_test_flag(&perm_opts, OPT_CALLEE_PARK)) + ast_set_flag(&(features->features_callee), AST_FEATURE_PARKCALL); + if (ast_test_flag(&perm_opts, OPT_CALLER_PARK)) + ast_set_flag(&(features->features_caller), AST_FEATURE_PARKCALL); +} + +static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags *peerflags, int *continue_exec) +{ + int res = -1; + struct ast_module_user *u; + char *rest, *cur; + struct dial_localuser *outgoing = NULL; + struct ast_channel *peer; + int to; + int numbusy = 0; + int numcongestion = 0; + int numnochan = 0; + int cause; + char numsubst[256]; + char cidname[AST_MAX_EXTENSION] = ""; + int privdb_val = 0; + unsigned int calldurationlimit = 0; + long timelimit = 0; + long play_warning = 0; + long warning_freq = 0; + const char *warning_sound = NULL; + const char *end_sound = NULL; + const char *start_sound = NULL; + char *dtmfcalled = NULL, *dtmfcalling = NULL; + char status[256] = "INVALIDARGS"; + int play_to_caller = 0, play_to_callee = 0; + int sentringing = 0, moh = 0; + const char *outbound_group = NULL; + int result = 0; + time_t start_time; + char privintro[1024]; + char privcid[256]; + char *parse; + int opermode = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peers); + AST_APP_ARG(timeout); + AST_APP_ARG(options); + AST_APP_ARG(url); + ); + struct ast_flags opts = { 0, }; + char *opt_args[OPT_ARG_ARRAY_SIZE]; + struct ast_datastore *datastore = NULL; + struct ast_datastore *ds_caller_features = NULL; + struct ast_datastore *ds_callee_features = NULL; + struct ast_dial_features *caller_features; + int fulldial = 0, num_dialed = 0; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n"); + pbx_builtin_setvar_helper(chan, "DIALSTATUS", status); + return -1; + } + + u = ast_module_user_add(chan); + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (!ast_strlen_zero(args.options) && + ast_app_parse_options(dial_exec_options, &opts, opt_args, args.options)) { + pbx_builtin_setvar_helper(chan, "DIALSTATUS", status); + goto done; + } + + if (ast_strlen_zero(args.peers)) { + ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n"); + pbx_builtin_setvar_helper(chan, "DIALSTATUS", status); + goto done; + } + + if (ast_test_flag(&opts, OPT_OPERMODE)) { + if (ast_strlen_zero(opt_args[OPT_ARG_OPERMODE])) + opermode = 1; + else opermode = atoi(opt_args[OPT_ARG_OPERMODE]); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Setting operator services mode to %d.\n", opermode); + } + + if (ast_test_flag(&opts, OPT_DURATION_STOP) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_STOP])) { + calldurationlimit = atoi(opt_args[OPT_ARG_DURATION_STOP]); + if (!calldurationlimit) { + ast_log(LOG_WARNING, "Dial does not accept S(%s), hanging up.\n", opt_args[OPT_ARG_DURATION_STOP]); + pbx_builtin_setvar_helper(chan, "DIALSTATUS", status); + goto done; + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Setting call duration limit to %d seconds.\n", calldurationlimit); + } + + if (ast_test_flag(&opts, OPT_SENDDTMF) && !ast_strlen_zero(opt_args[OPT_ARG_SENDDTMF])) { + dtmfcalling = opt_args[OPT_ARG_SENDDTMF]; + dtmfcalled = strsep(&dtmfcalling, ":"); + } + + if (ast_test_flag(&opts, OPT_DURATION_LIMIT) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_LIMIT])) { + char *limit_str, *warning_str, *warnfreq_str; + const char *var; + + warnfreq_str = opt_args[OPT_ARG_DURATION_LIMIT]; + limit_str = strsep(&warnfreq_str, ":"); + warning_str = strsep(&warnfreq_str, ":"); + + timelimit = atol(limit_str); + if (warning_str) + play_warning = atol(warning_str); + if (warnfreq_str) + warning_freq = atol(warnfreq_str); + + if (!timelimit) { + ast_log(LOG_WARNING, "Dial does not accept L(%s), hanging up.\n", limit_str); + goto done; + } else if (play_warning > timelimit) { + /* If the first warning is requested _after_ the entire call would end, + and no warning frequency is requested, then turn off the warning. If + a warning frequency is requested, reduce the 'first warning' time by + that frequency until it falls within the call's total time limit. + */ + + if (!warning_freq) { + play_warning = 0; + } else { + /* XXX fix this!! */ + while (play_warning > timelimit) + play_warning -= warning_freq; + if (play_warning < 1) + play_warning = warning_freq = 0; + } + } + + var = pbx_builtin_getvar_helper(chan,"LIMIT_PLAYAUDIO_CALLER"); + play_to_caller = var ? ast_true(var) : 1; + + var = pbx_builtin_getvar_helper(chan,"LIMIT_PLAYAUDIO_CALLEE"); + play_to_callee = var ? ast_true(var) : 0; + + if (!play_to_caller && !play_to_callee) + play_to_caller = 1; + + var = pbx_builtin_getvar_helper(chan,"LIMIT_WARNING_FILE"); + warning_sound = S_OR(var, "timeleft"); + + var = pbx_builtin_getvar_helper(chan,"LIMIT_TIMEOUT_FILE"); + end_sound = S_OR(var, NULL); /* XXX not much of a point in doing this! */ + + var = pbx_builtin_getvar_helper(chan,"LIMIT_CONNECT_FILE"); + start_sound = S_OR(var, NULL); /* XXX not much of a point in doing this! */ + + /* undo effect of S(x) in case they are both used */ + calldurationlimit = 0; + /* more efficient to do it like S(x) does since no advanced opts */ + if (!play_warning && !start_sound && !end_sound && timelimit) { + calldurationlimit = timelimit / 1000; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Setting call duration limit to %d seconds.\n", calldurationlimit); + timelimit = play_to_caller = play_to_callee = play_warning = warning_freq = 0; + } else if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "Limit Data for this call:\n"); + ast_verbose(VERBOSE_PREFIX_4 "timelimit = %ld\n", timelimit); + ast_verbose(VERBOSE_PREFIX_4 "play_warning = %ld\n", play_warning); + ast_verbose(VERBOSE_PREFIX_4 "play_to_caller = %s\n", play_to_caller ? "yes" : "no"); + ast_verbose(VERBOSE_PREFIX_4 "play_to_callee = %s\n", play_to_callee ? "yes" : "no"); + ast_verbose(VERBOSE_PREFIX_4 "warning_freq = %ld\n", warning_freq); + ast_verbose(VERBOSE_PREFIX_4 "start_sound = %s\n", start_sound); + ast_verbose(VERBOSE_PREFIX_4 "warning_sound = %s\n", warning_sound); + ast_verbose(VERBOSE_PREFIX_4 "end_sound = %s\n", end_sound); + } + } + + if (ast_test_flag(&opts, OPT_RESETCDR) && chan->cdr) + ast_cdr_reset(chan->cdr, NULL); + if (ast_test_flag(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY])) + opt_args[OPT_ARG_PRIVACY] = ast_strdupa(chan->exten); + if (ast_test_flag(&opts, OPT_PRIVACY) || ast_test_flag(&opts, OPT_SCREENING)) { + char callerid[60]; + char *l = chan->cid.cid_num; /* XXX watch out, we are overwriting it */ + if (!ast_strlen_zero(l)) { + ast_shrink_phone_number(l); + if( ast_test_flag(&opts, OPT_PRIVACY) ) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Privacy DB is '%s', clid is '%s'\n", + opt_args[OPT_ARG_PRIVACY], l); + privdb_val = ast_privacy_check(opt_args[OPT_ARG_PRIVACY], l); + } + else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Privacy Screening, clid is '%s'\n", l); + privdb_val = AST_PRIVACY_UNKNOWN; + } + } else { + char *tnam, *tn2; + + tnam = ast_strdupa(chan->name); + /* clean the channel name so slashes don't try to end up in disk file name */ + for(tn2 = tnam; *tn2; tn2++) { + if( *tn2=='/') + *tn2 = '='; /* any other chars to be afraid of? */ + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Privacy-- callerid is empty\n"); + + snprintf(callerid, sizeof(callerid), "NOCALLERID_%s%s", chan->exten, tnam); + l = callerid; + privdb_val = AST_PRIVACY_UNKNOWN; + } + + ast_copy_string(privcid,l,sizeof(privcid)); + + if( strncmp(privcid,"NOCALLERID",10) != 0 && ast_test_flag(&opts, OPT_SCREEN_NOCLID) ) { /* if callerid is set, and ast_test_flag(&opts, OPT_SCREEN_NOCLID) is set also */ + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "CallerID set (%s); N option set; Screening should be off\n", privcid); + privdb_val = AST_PRIVACY_ALLOW; + } + else if(ast_test_flag(&opts, OPT_SCREEN_NOCLID) && strncmp(privcid,"NOCALLERID",10) == 0 ) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "CallerID blank; N option set; Screening should happen; dbval is %d\n", privdb_val); + } + + if(privdb_val == AST_PRIVACY_DENY ) { + ast_copy_string(status, "NOANSWER", sizeof(status)); + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Privacy DB reports PRIVACY_DENY for this callerid. Dial reports unavailable\n"); + res=0; + goto out; + } + else if(privdb_val == AST_PRIVACY_KILL ) { + ast_copy_string(status, "DONTCALL", sizeof(status)); + if (ast_opt_priority_jumping || ast_test_flag(&opts, OPT_PRIORITY_JUMP)) { + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 201); + } + res = 0; + goto out; /* Is this right? */ + } + else if(privdb_val == AST_PRIVACY_TORTURE ) { + ast_copy_string(status, "TORTURE", sizeof(status)); + if (ast_opt_priority_jumping || ast_test_flag(&opts, OPT_PRIORITY_JUMP)) { + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 301); + } + res = 0; + goto out; /* is this right??? */ + } + else if(privdb_val == AST_PRIVACY_UNKNOWN ) { + /* Get the user's intro, store it in priv-callerintros/$CID, + unless it is already there-- this should be done before the + call is actually dialed */ + + /* make sure the priv-callerintros dir actually exists */ + snprintf(privintro, sizeof(privintro), "%s/sounds/priv-callerintros", ast_config_AST_DATA_DIR); + if (mkdir(privintro, 0755) && errno != EEXIST) { + ast_log(LOG_WARNING, "privacy: can't create directory priv-callerintros: %s\n", strerror(errno)); + res = -1; + goto out; + } + + snprintf(privintro,sizeof(privintro), "priv-callerintros/%s", privcid); + if( ast_fileexists(privintro,NULL,NULL ) > 0 && strncmp(privcid,"NOCALLERID",10) != 0) { + /* the DELUX version of this code would allow this caller the + option to hear and retape their previously recorded intro. + */ + } + else { + int duration; /* for feedback from play_and_wait */ + /* the file doesn't exist yet. Let the caller submit his + vocal intro for posterity */ + /* priv-recordintro script: + + "At the tone, please say your name:" + + */ + ast_answer(chan); + res = ast_play_and_record(chan, "priv-recordintro", privintro, 4, "gsm", &duration, 128, 2000, 0); /* NOTE: I've reduced the total time to 4 sec */ + /* don't think we'll need a lock removed, we took care of + conflicts by naming the privintro file */ + if (res == -1) { + /* Delete the file regardless since they hung up during recording */ + ast_filedelete(privintro, NULL); + if( ast_fileexists(privintro,NULL,NULL ) > 0 ) + ast_log(LOG_NOTICE,"privacy: ast_filedelete didn't do its job on %s\n", privintro); + else if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Successfully deleted %s intro file\n", privintro); + goto out; + } + if( !ast_streamfile(chan, "vm-dialout", chan->language) ) + ast_waitstream(chan, ""); + } + } + } + + if (continue_exec) + *continue_exec = 0; + + /* If a channel group has been specified, get it for use when we create peer channels */ + if ((outbound_group = pbx_builtin_getvar_helper(chan, "OUTBOUND_GROUP_ONCE"))) { + outbound_group = ast_strdupa(outbound_group); + pbx_builtin_setvar_helper(chan, "OUTBOUND_GROUP_ONCE", NULL); + } else { + outbound_group = pbx_builtin_getvar_helper(chan, "OUTBOUND_GROUP"); + } + + ast_copy_flags(peerflags, &opts, OPT_DTMF_EXIT | OPT_GO_ON | OPT_ORIGINAL_CLID | OPT_CALLER_HANGUP | OPT_IGNORE_FORWARDING); + + /* Create datastore for channel dial features for caller */ + if (!(ds_caller_features = ast_channel_datastore_alloc(&dial_features_info, NULL))) { + ast_log(LOG_WARNING, "Unable to create channel datastore for dial features. Aborting!\n"); + goto out; + } + + if (!(caller_features = ast_calloc(1, sizeof(*caller_features)))) { + ast_log(LOG_WARNING, "Unable to allocate memory for feature flags. Aborting!\n"); + goto out; + } + + ast_channel_lock(chan); + caller_features->is_caller = 1; + set_dial_features(&opts, caller_features); + ds_caller_features->inheritance = -1; + ds_caller_features->data = caller_features; + ast_channel_datastore_add(chan, ds_caller_features); + ast_channel_unlock(chan); + + /* loop through the list of dial destinations */ + rest = args.peers; + while ((cur = strsep(&rest, "&")) ) { + struct dial_localuser *tmp; + /* Get a technology/[device:]number pair */ + char *number = cur; + char *interface = ast_strdupa(number); + char *tech = strsep(&number, "/"); + /* find if we already dialed this interface */ + struct ast_dialed_interface *di; + struct ast_dial_features *callee_features; + AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces; + num_dialed++; + if (!number) { + ast_log(LOG_WARNING, "Dial argument takes format (technology/[device:]number1)\n"); + goto out; + } + if (!(tmp = ast_calloc(1, sizeof(*tmp)))) + goto out; + if (opts.flags) { + ast_copy_flags(tmp, &opts, + OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | + OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP | + OPT_CALLEE_MONITOR | OPT_CALLER_MONITOR | + OPT_CALLEE_PARK | OPT_CALLER_PARK | + OPT_RINGBACK | OPT_MUSICBACK | OPT_FORCECLID); + ast_set2_flag(tmp, args.url, DIAL_NOFORWARDHTML); + } + ast_copy_string(numsubst, number, sizeof(numsubst)); + /* Request the peer */ + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL); + ast_channel_unlock(chan); + + if (datastore) + dialed_interfaces = datastore->data; + else { + if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) { + ast_log(LOG_WARNING, "Unable to create channel datastore for dialed interfaces. Aborting!\n"); + free(tmp); + goto out; + } + + datastore->inheritance = DATASTORE_INHERIT_FOREVER; + + if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) { + free(tmp); + goto out; + } + + datastore->data = dialed_interfaces; + AST_LIST_HEAD_INIT(dialed_interfaces); + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + } + + AST_LIST_LOCK(dialed_interfaces); + AST_LIST_TRAVERSE(dialed_interfaces, di, list) { + if (!strcasecmp(di->interface, interface)) { + ast_log(LOG_WARNING, "Skipping dialing interface '%s' again since it has already been dialed\n", + di->interface); + break; + } + } + AST_LIST_UNLOCK(dialed_interfaces); + + if (di) { + fulldial++; + free(tmp); + continue; + } + + /* It is always ok to dial a Local interface. We only keep track of + * which "real" interfaces have been dialed. The Local channel will + * inherit this list so that if it ends up dialing a real interface, + * it won't call one that has already been called. */ + if (strcasecmp(tech, "Local")) { + if (!(di = ast_calloc(1, sizeof(*di) + strlen(interface)))) { + AST_LIST_UNLOCK(dialed_interfaces); + free(tmp); + goto out; + } + strcpy(di->interface, interface); + + AST_LIST_LOCK(dialed_interfaces); + AST_LIST_INSERT_TAIL(dialed_interfaces, di, list); + AST_LIST_UNLOCK(dialed_interfaces); + } + + tmp->chan = ast_request(tech, chan->nativeformats, numsubst, &cause); + if (!tmp->chan) { + /* If we can't, just go on to the next call */ + ast_log(LOG_WARNING, "Unable to create channel of type '%s' (cause %d - %s)\n", tech, cause, ast_cause2str(cause)); + HANDLE_CAUSE(cause, chan); + if (!rest) /* we are on the last destination */ + chan->hangupcause = cause; + free(tmp); + continue; + } + + pbx_builtin_setvar_helper(tmp->chan, "DIALEDPEERNUMBER", numsubst); + + /* Setup outgoing SDP to match incoming one */ + ast_rtp_make_compatible(tmp->chan, chan, !outgoing && !rest); + + /* Inherit specially named variables from parent channel */ + ast_channel_inherit_variables(chan, tmp->chan); + + tmp->chan->appl = "AppDial"; + tmp->chan->data = "(Outgoing Line)"; + tmp->chan->whentohangup = 0; + + if (tmp->chan->cid.cid_num) + free(tmp->chan->cid.cid_num); + tmp->chan->cid.cid_num = ast_strdup(chan->cid.cid_num); + + if (tmp->chan->cid.cid_name) + free(tmp->chan->cid.cid_name); + tmp->chan->cid.cid_name = ast_strdup(chan->cid.cid_name); + + if (tmp->chan->cid.cid_ani) + free(tmp->chan->cid.cid_ani); + tmp->chan->cid.cid_ani = ast_strdup(chan->cid.cid_ani); + + /* Copy language from incoming to outgoing */ + ast_string_field_set(tmp->chan, language, chan->language); + ast_string_field_set(tmp->chan, accountcode, chan->accountcode); + tmp->chan->cdrflags = chan->cdrflags; + if (ast_strlen_zero(tmp->chan->musicclass)) + ast_string_field_set(tmp->chan, musicclass, chan->musicclass); + /* XXX don't we free previous values ? */ + tmp->chan->cid.cid_rdnis = ast_strdup(chan->cid.cid_rdnis); + /* Pass callingpres setting */ + tmp->chan->cid.cid_pres = chan->cid.cid_pres; + /* Pass type of number */ + tmp->chan->cid.cid_ton = chan->cid.cid_ton; + /* Pass type of tns */ + tmp->chan->cid.cid_tns = chan->cid.cid_tns; + /* Presense of ADSI CPE on outgoing channel follows ours */ + tmp->chan->adsicpe = chan->adsicpe; + /* Pass the transfer capability */ + tmp->chan->transfercapability = chan->transfercapability; + + /* If we have an outbound group, set this peer channel to it */ + if (outbound_group) + ast_app_group_set_channel(tmp->chan, outbound_group); + + /* Inherit context and extension */ + if (!ast_strlen_zero(chan->macrocontext)) + ast_copy_string(tmp->chan->dialcontext, chan->macrocontext, sizeof(tmp->chan->dialcontext)); + else + ast_copy_string(tmp->chan->dialcontext, chan->context, sizeof(tmp->chan->dialcontext)); + if (!ast_strlen_zero(chan->macroexten)) + ast_copy_string(tmp->chan->exten, chan->macroexten, sizeof(tmp->chan->exten)); + else + ast_copy_string(tmp->chan->exten, chan->exten, sizeof(tmp->chan->exten)); + + /* Save callee features */ + if (!(ds_callee_features = ast_channel_datastore_alloc(&dial_features_info, NULL))) { + ast_log(LOG_WARNING, "Unable to create channel datastore for dial features. Aborting!\n"); + ast_free(tmp); + goto out; + } + + if (!(callee_features = ast_calloc(1, sizeof(*callee_features)))) { + ast_log(LOG_WARNING, "Unable to allocate memory for feature flags. Aborting!\n"); + ast_free(tmp); + goto out; + } + + ast_channel_lock(tmp->chan); + callee_features->is_caller = 0; + set_dial_features(&opts, callee_features); + ds_callee_features->inheritance = -1; + ds_callee_features->data = callee_features; + ast_channel_datastore_add(tmp->chan, ds_callee_features); + ast_channel_unlock(tmp->chan); + + /* Place the call, but don't wait on the answer */ + res = ast_call(tmp->chan, numsubst, 0); + + /* Save the info in cdr's that we called them */ + if (chan->cdr) + ast_cdr_setdestchan(chan->cdr, tmp->chan->name); + + /* check the results of ast_call */ + if (res) { + /* Again, keep going even if there's an error */ + if (option_debug) + ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", numsubst); + ast_hangup(tmp->chan); + tmp->chan = NULL; + free(tmp); + continue; + } else { + senddialevent(chan, tmp->chan); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", numsubst); + if (!ast_test_flag(peerflags, OPT_ORIGINAL_CLID)) + ast_set_callerid(tmp->chan, S_OR(chan->macroexten, chan->exten), get_cid_name(cidname, sizeof(cidname), chan), NULL); + } + /* Put them in the list of outgoing thingies... We're ready now. + XXX If we're forcibly removed, these outgoing calls won't get + hung up XXX */ + ast_set_flag(tmp, DIAL_STILLGOING); + tmp->next = outgoing; + outgoing = tmp; + /* If this line is up, don't try anybody else */ + if (outgoing->chan->_state == AST_STATE_UP) + break; + } + + if (ast_strlen_zero(args.timeout)) { + to = -1; + } else { + to = atoi(args.timeout); + if (to > 0) + to *= 1000; + else + ast_log(LOG_WARNING, "Invalid timeout specified: '%s'\n", args.timeout); + } + + if (!outgoing) { + strcpy(status, "CHANUNAVAIL"); + if(fulldial == num_dialed) { + res = -1; + goto out; + } + } else { + /* Our status will at least be NOANSWER */ + strcpy(status, "NOANSWER"); + if (ast_test_flag(outgoing, OPT_MUSICBACK)) { + moh = 1; + if (!ast_strlen_zero(opt_args[OPT_ARG_MUSICBACK])) { + char *original_moh = ast_strdupa(chan->musicclass); + ast_string_field_set(chan, musicclass, opt_args[OPT_ARG_MUSICBACK]); + ast_moh_start(chan, opt_args[OPT_ARG_MUSICBACK], NULL); + ast_string_field_set(chan, musicclass, original_moh); + } else { + ast_moh_start(chan, NULL, NULL); + } + ast_indicate(chan, AST_CONTROL_PROGRESS); + } else if (ast_test_flag(outgoing, OPT_RINGBACK)) { + ast_indicate(chan, AST_CONTROL_RINGING); + sentringing++; + } + } + + time(&start_time); + peer = wait_for_answer(chan, outgoing, &to, peerflags, &sentringing, status, sizeof(status), numbusy, numnochan, numcongestion, ast_test_flag(&opts, OPT_PRIORITY_JUMP), &result); + + /* The ast_channel_datastore_remove() function could fail here if the + * datastore was moved to another channel during a masquerade. If this is + * the case, don't free the datastore here because later, when the channel + * to which the datastore was moved hangs up, it will attempt to free this + * datastore again, causing a crash + */ + if (!ast_channel_datastore_remove(chan, datastore)) + ast_channel_datastore_free(datastore); + if (!peer) { + if (result) { + res = result; + } else if (to) { /* Musta gotten hung up */ + res = -1; + } else { /* Nobody answered, next please? */ + res = 0; + } + /* almost done, although the 'else' block is 400 lines */ + } else { + const char *number; + time_t end_time, answer_time = time(NULL); + + strcpy(status, "ANSWER"); + /* Ah ha! Someone answered within the desired timeframe. Of course after this + we will always return with -1 so that it is hung up properly after the + conversation. */ + hanguptree(outgoing, peer); + outgoing = NULL; + /* If appropriate, log that we have a destination channel */ + if (chan->cdr) + ast_cdr_setdestchan(chan->cdr, peer->name); + if (peer->name) + pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", peer->name); + + number = pbx_builtin_getvar_helper(peer, "DIALEDPEERNUMBER"); + if (!number) + number = numsubst; + pbx_builtin_setvar_helper(chan, "DIALEDPEERNUMBER", number); + if (!ast_strlen_zero(args.url) && ast_channel_supports_html(peer) ) { + if (option_debug) + ast_log(LOG_DEBUG, "app_dial: sendurl=%s.\n", args.url); + ast_channel_sendurl( peer, args.url ); + } + if ( (ast_test_flag(&opts, OPT_PRIVACY) || ast_test_flag(&opts, OPT_SCREENING)) && privdb_val == AST_PRIVACY_UNKNOWN) { + int res2; + int loopcount = 0; + + /* Get the user's intro, store it in priv-callerintros/$CID, + unless it is already there-- this should be done before the + call is actually dialed */ + + /* all ring indications and moh for the caller has been halted as soon as the + target extension was picked up. We are going to have to kill some + time and make the caller believe the peer hasn't picked up yet */ + + if (ast_test_flag(&opts, OPT_MUSICBACK) && !ast_strlen_zero(opt_args[OPT_ARG_MUSICBACK])) { + char *original_moh = ast_strdupa(chan->musicclass); + ast_indicate(chan, -1); + ast_string_field_set(chan, musicclass, opt_args[OPT_ARG_MUSICBACK]); + ast_moh_start(chan, opt_args[OPT_ARG_MUSICBACK], NULL); + ast_string_field_set(chan, musicclass, original_moh); + } else if (ast_test_flag(&opts, OPT_RINGBACK)) { + ast_indicate(chan, AST_CONTROL_RINGING); + sentringing++; + } + + /* Start autoservice on the other chan ?? */ + res2 = ast_autoservice_start(chan); + /* Now Stream the File */ + for (loopcount = 0; loopcount < 3; loopcount++) { + if (res2 && loopcount == 0) /* error in ast_autoservice_start() */ + break; + if (!res2) /* on timeout, play the message again */ + res2 = ast_play_and_wait(peer,"priv-callpending"); + if (!valid_priv_reply(&opts, res2)) + res2 = 0; + /* priv-callpending script: + "I have a caller waiting, who introduces themselves as:" + */ + if (!res2) + res2 = ast_play_and_wait(peer,privintro); + if (!valid_priv_reply(&opts, res2)) + res2 = 0; + /* now get input from the called party, as to their choice */ + if( !res2 ) { + /* XXX can we have both, or they are mutually exclusive ? */ + if( ast_test_flag(&opts, OPT_PRIVACY) ) + res2 = ast_play_and_wait(peer,"priv-callee-options"); + if( ast_test_flag(&opts, OPT_SCREENING) ) + res2 = ast_play_and_wait(peer,"screen-callee-options"); + } + /*! \page DialPrivacy Dial Privacy scripts + \par priv-callee-options script: + "Dial 1 if you wish this caller to reach you directly in the future, + and immediately connect to their incoming call + Dial 2 if you wish to send this caller to voicemail now and + forevermore. + Dial 3 to send this caller to the torture menus, now and forevermore. + Dial 4 to send this caller to a simple "go away" menu, now and forevermore. + Dial 5 to allow this caller to come straight thru to you in the future, + but right now, just this once, send them to voicemail." + \par screen-callee-options script: + "Dial 1 if you wish to immediately connect to the incoming call + Dial 2 if you wish to send this caller to voicemail. + Dial 3 to send this caller to the torture menus. + Dial 4 to send this caller to a simple "go away" menu. + */ + if (valid_priv_reply(&opts, res2)) + break; + /* invalid option */ + res2 = ast_play_and_wait(peer, "vm-sorry"); + } + + if (ast_test_flag(&opts, OPT_MUSICBACK)) { + ast_moh_stop(chan); + } else if (ast_test_flag(&opts, OPT_RINGBACK)) { + ast_indicate(chan, -1); + sentringing=0; + } + ast_autoservice_stop(chan); + + switch (res2) { + case '1': + if( ast_test_flag(&opts, OPT_PRIVACY) ) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to ALLOW\n", + opt_args[OPT_ARG_PRIVACY], privcid); + ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_ALLOW); + } + break; + case '2': + if( ast_test_flag(&opts, OPT_PRIVACY) ) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to DENY\n", + opt_args[OPT_ARG_PRIVACY], privcid); + ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_DENY); + } + ast_copy_string(status, "NOANSWER", sizeof(status)); + ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */ + res=0; + goto out; + case '3': + if( ast_test_flag(&opts, OPT_PRIVACY) ) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to TORTURE\n", + opt_args[OPT_ARG_PRIVACY], privcid); + ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_TORTURE); + } + ast_copy_string(status, "TORTURE", sizeof(status)); + + res = 0; + ast_hangup(peer); /* hang up on the caller -- he didn't want to talk anyway! */ + goto out; /* Is this right? */ + case '4': + if( ast_test_flag(&opts, OPT_PRIVACY) ) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to KILL\n", + opt_args[OPT_ARG_PRIVACY], privcid); + ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_KILL); + } + + ast_copy_string(status, "DONTCALL", sizeof(status)); + res = 0; + ast_hangup(peer); /* hang up on the caller -- he didn't want to talk anyway! */ + goto out; /* Is this right? */ + case '5': + if( ast_test_flag(&opts, OPT_PRIVACY) ) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "--Set privacy database entry %s/%s to ALLOW\n", + opt_args[OPT_ARG_PRIVACY], privcid); + ast_privacy_set(opt_args[OPT_ARG_PRIVACY], privcid, AST_PRIVACY_ALLOW); + ast_hangup(peer); /* hang up on the caller -- he didn't want to talk anyway! */ + res=0; + goto out; + } /* if not privacy, then 5 is the same as "default" case */ + default: /* bad input or -1 if failure to start autoservice */ + /* well, if the user messes up, ... he had his chance... What Is The Best Thing To Do? */ + /* well, there seems basically two choices. Just patch the caller thru immediately, + or,... put 'em thru to voicemail. */ + /* since the callee may have hung up, let's do the voicemail thing, no database decision */ + ast_log(LOG_NOTICE, "privacy: no valid response from the callee. Sending the caller to voicemail, the callee isn't responding\n"); + ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */ + res=0; + goto out; + } + + /* XXX once again, this path is only taken in the case '1', so it could be + * moved there, although i am not really sure that this is correct - maybe + * the check applies to other cases as well. + */ + /* if the intro is NOCALLERID, then there's no reason to leave it on disk, it'll + just clog things up, and it's not useful information, not being tied to a CID */ + if( strncmp(privcid,"NOCALLERID",10) == 0 || ast_test_flag(&opts, OPT_SCREEN_NOINTRO) ) { + ast_filedelete(privintro, NULL); + if( ast_fileexists(privintro, NULL, NULL ) > 0 ) + ast_log(LOG_NOTICE, "privacy: ast_filedelete didn't do its job on %s\n", privintro); + else if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Successfully deleted %s intro file\n", privintro); + } + } + if (!ast_test_flag(&opts, OPT_ANNOUNCE) || ast_strlen_zero(opt_args[OPT_ARG_ANNOUNCE])) { + res = 0; + } else { + int digit = 0; + /* Start autoservice on the other chan */ + res = ast_autoservice_start(chan); + /* Now Stream the File */ + if (!res) + res = ast_streamfile(peer, opt_args[OPT_ARG_ANNOUNCE], peer->language); + if (!res) { + digit = ast_waitstream(peer, AST_DIGIT_ANY); + } + /* Ok, done. stop autoservice */ + res = ast_autoservice_stop(chan); + if (digit > 0 && !res) + res = ast_senddigit(chan, digit); + else + res = digit; + + } + + if (chan && peer && ast_test_flag(&opts, OPT_GOTO) && !ast_strlen_zero(opt_args[OPT_ARG_GOTO])) { + replace_macro_delimiter(opt_args[OPT_ARG_GOTO]); + ast_parseable_goto(chan, opt_args[OPT_ARG_GOTO]); + /* peer goes to the same context and extension as chan, so just copy info from chan*/ + ast_copy_string(peer->context, chan->context, sizeof(peer->context)); + ast_copy_string(peer->exten, chan->exten, sizeof(peer->exten)); + peer->priority = chan->priority + 2; + ast_pbx_start(peer); + hanguptree(outgoing, NULL); + if (continue_exec) + *continue_exec = 1; + res = 0; + goto done; + } + + if (ast_test_flag(&opts, OPT_CALLEE_MACRO) && !ast_strlen_zero(opt_args[OPT_ARG_CALLEE_MACRO])) { + struct ast_app *theapp; + const char *macro_result; + + res = ast_autoservice_start(chan); + if (res) { + ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n"); + res = -1; + } + + theapp = pbx_findapp("Macro"); + + if (theapp && !res) { /* XXX why check res here ? */ + replace_macro_delimiter(opt_args[OPT_ARG_CALLEE_MACRO]); + res = pbx_exec(peer, theapp, opt_args[OPT_ARG_CALLEE_MACRO]); + ast_log(LOG_DEBUG, "Macro exited with status %d\n", res); + res = 0; + } else { + ast_log(LOG_ERROR, "Could not find application Macro\n"); + res = -1; + } + + if (ast_autoservice_stop(chan) < 0) { + ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n"); + res = -1; + } + + if (!res && (macro_result = pbx_builtin_getvar_helper(peer, "MACRO_RESULT"))) { + char *macro_transfer_dest; + + if (!strcasecmp(macro_result, "BUSY")) { + ast_copy_string(status, macro_result, sizeof(status)); + if (ast_opt_priority_jumping || ast_test_flag(&opts, OPT_PRIORITY_JUMP)) { + if (!ast_goto_if_exists(chan, NULL, NULL, chan->priority + 101)) { + ast_set_flag(peerflags, OPT_GO_ON); + } + } else + ast_set_flag(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(macro_result, "CONGESTION") || !strcasecmp(macro_result, "CHANUNAVAIL")) { + ast_copy_string(status, macro_result, sizeof(status)); + ast_set_flag(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(macro_result, "CONTINUE")) { + /* hangup peer and keep chan alive assuming the macro has changed + the context / exten / priority or perhaps + the next priority in the current exten is desired. + */ + ast_set_flag(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(macro_result, "ABORT")) { + /* Hangup both ends unless the caller has the g flag */ + res = -1; + } else if (!strncasecmp(macro_result, "GOTO:", 5) && (macro_transfer_dest = ast_strdupa(macro_result + 5))) { + res = -1; + /* perform a transfer to a new extension */ + if (strchr(macro_transfer_dest, '^')) { /* context^exten^priority*/ + replace_macro_delimiter(macro_transfer_dest); + if (!ast_parseable_goto(chan, macro_transfer_dest)) + ast_set_flag(peerflags, OPT_GO_ON); + + } + } + } + } + + if (!res) { + if (calldurationlimit > 0) { + peer->whentohangup = time(NULL) + calldurationlimit; + } + if (!ast_strlen_zero(dtmfcalled)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Sending DTMF '%s' to the called party.\n", dtmfcalled); + res = ast_dtmf_stream(peer,chan,dtmfcalled,250); + } + if (!ast_strlen_zero(dtmfcalling)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Sending DTMF '%s' to the calling party.\n", dtmfcalling); + res = ast_dtmf_stream(chan,peer,dtmfcalling,250); + } + } + + if (!res) { + struct ast_bridge_config config; + + memset(&config,0,sizeof(struct ast_bridge_config)); + if (play_to_caller) + ast_set_flag(&(config.features_caller), AST_FEATURE_PLAY_WARNING); + if (play_to_callee) + ast_set_flag(&(config.features_callee), AST_FEATURE_PLAY_WARNING); + if (ast_test_flag(peerflags, OPT_CALLEE_TRANSFER)) + ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); + if (ast_test_flag(peerflags, OPT_CALLER_TRANSFER)) + ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); + if (ast_test_flag(peerflags, OPT_CALLEE_HANGUP)) + ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT); + if (ast_test_flag(peerflags, OPT_CALLER_HANGUP)) + ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT); + if (ast_test_flag(peerflags, OPT_CALLEE_MONITOR)) + ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON); + if (ast_test_flag(peerflags, OPT_CALLER_MONITOR)) + ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON); + if (ast_test_flag(peerflags, OPT_CALLEE_PARK)) + ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL); + if (ast_test_flag(peerflags, OPT_CALLER_PARK)) + ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL); + if (ast_test_flag(peerflags, OPT_GO_ON)) + ast_set_flag(&(config.features_caller), AST_FEATURE_NO_H_EXTEN); + + config.timelimit = timelimit; + config.play_warning = play_warning; + config.warning_freq = warning_freq; + config.warning_sound = warning_sound; + config.end_sound = end_sound; + config.start_sound = start_sound; + if (moh) { + moh = 0; + ast_moh_stop(chan); + } else if (sentringing) { + sentringing = 0; + ast_indicate(chan, -1); + } + /* Be sure no generators are left on it */ + ast_deactivate_generator(chan); + /* Make sure channels are compatible */ + res = ast_channel_make_compatible(chan, peer); + if (res < 0) { + ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, peer->name); + ast_hangup(peer); + res = -1; + goto done; + } + if (opermode && + (((!strncasecmp(chan->name,"Zap",3)) && (!strncasecmp(peer->name,"Zap",3))) || + ((!strncasecmp(chan->name,"Dahdi",5)) && (!strncasecmp(peer->name,"Dahdi",5))))) + { + struct oprmode oprmode; + + oprmode.peer = peer; + oprmode.mode = opermode; + + ast_channel_setoption(chan, + AST_OPTION_OPRMODE,&oprmode,sizeof(struct oprmode),0); + } + res = ast_bridge_call(chan,peer,&config); + time(&end_time); + { + char toast[80]; + snprintf(toast, sizeof(toast), "%ld", (long)(end_time - answer_time)); + pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", toast); + } + } else { + time(&end_time); + res = -1; + } + { + char toast[80]; + snprintf(toast, sizeof(toast), "%ld", (long)(end_time - start_time)); + pbx_builtin_setvar_helper(chan, "DIALEDTIME", toast); + } + + if (res != AST_PBX_NO_HANGUP_PEER) { + if (!chan->_softhangup) + chan->hangupcause = peer->hangupcause; + ast_hangup(peer); + } + } +out: + if (moh) { + moh = 0; + ast_moh_stop(chan); + } else if (sentringing) { + sentringing = 0; + ast_indicate(chan, -1); + } + ast_rtp_early_bridge(chan, NULL); + hanguptree(outgoing, NULL); + pbx_builtin_setvar_helper(chan, "DIALSTATUS", status); + if (option_debug) + ast_log(LOG_DEBUG, "Exiting with DIALSTATUS=%s.\n", status); + + if ((ast_test_flag(peerflags, OPT_GO_ON)) && (!chan->_softhangup) && (res != AST_PBX_KEEPALIVE)) { + if (calldurationlimit) + chan->whentohangup = 0; + res = 0; + } + +done: + ast_module_user_remove(u); + return res; +} + +static int dial_exec(struct ast_channel *chan, void *data) +{ + struct ast_flags peerflags; + + memset(&peerflags, 0, sizeof(peerflags)); + + return dial_exec_full(chan, data, &peerflags, NULL); +} + +static int retrydial_exec(struct ast_channel *chan, void *data) +{ + char *announce = NULL, *dialdata = NULL; + const char *context = NULL; + int sleep = 0, loops = 0, res = -1; + struct ast_module_user *u; + struct ast_flags peerflags; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "RetryDial requires an argument!\n"); + return -1; + } + + u = ast_module_user_add(chan); + + announce = ast_strdupa(data); + + memset(&peerflags, 0, sizeof(peerflags)); + + if ((dialdata = strchr(announce, '|'))) { + *dialdata++ = '\0'; + if (sscanf(dialdata, "%d", &sleep) == 1) { + sleep *= 1000; + } else { + ast_log(LOG_ERROR, "%s requires the numerical argument \n",rapp); + goto done; + } + if ((dialdata = strchr(dialdata, '|'))) { + *dialdata++ = '\0'; + if (sscanf(dialdata, "%d", &loops) != 1) { + ast_log(LOG_ERROR, "%s requires the numerical argument \n",rapp); + goto done; + } + } + } + + if ((dialdata = strchr(dialdata, '|'))) { + *dialdata++ = '\0'; + } else { + ast_log(LOG_ERROR, "%s requires more arguments\n",rapp); + goto done; + } + + if (sleep < 1000) + sleep = 10000; + + if (!loops) + loops = -1; /* run forever */ + + context = pbx_builtin_getvar_helper(chan, "EXITCONTEXT"); + + res = 0; + while (loops) { + int continue_exec; + + chan->data = "Retrying"; + if (ast_test_flag(chan, AST_FLAG_MOH)) + ast_moh_stop(chan); + + res = dial_exec_full(chan, dialdata, &peerflags, &continue_exec); + if (continue_exec) + break; + + if (res == 0) { + if (ast_test_flag(&peerflags, OPT_DTMF_EXIT)) { + if (!ast_strlen_zero(announce)) { + if (ast_fileexists(announce, NULL, chan->language) > 0) { + if(!(res = ast_streamfile(chan, announce, chan->language))) + ast_waitstream(chan, AST_DIGIT_ANY); + } else + ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", announce); + } + if (!res && sleep) { + if (!ast_test_flag(chan, AST_FLAG_MOH)) + ast_moh_start(chan, NULL, NULL); + res = ast_waitfordigit(chan, sleep); + } + } else { + if (!ast_strlen_zero(announce)) { + if (ast_fileexists(announce, NULL, chan->language) > 0) { + if (!(res = ast_streamfile(chan, announce, chan->language))) + res = ast_waitstream(chan, ""); + } else + ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", announce); + } + if (sleep) { + if (!ast_test_flag(chan, AST_FLAG_MOH)) + ast_moh_start(chan, NULL, NULL); + if (!res) + res = ast_waitfordigit(chan, sleep); + } + } + } + + if (res < 0) + break; + else if (res > 0) { /* Trying to send the call elsewhere (1 digit ext) */ + if (onedigit_goto(chan, context, (char) res, 1)) { + res = 0; + break; + } + } + loops--; + } + if (loops == 0) + res = 0; + else if (res == 1) + res = 0; + + if (ast_test_flag(chan, AST_FLAG_MOH)) + ast_moh_stop(chan); + done: + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + res |= ast_unregister_application(rapp); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res; + + res = ast_register_application(app, dial_exec, synopsis, descrip); + res |= ast_register_application(rapp, retrydial_exec, rsynopsis, rdescrip); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialing Application"); diff --git a/asterisk/apps/app_dictate.c b/asterisk/apps/app_dictate.c new file mode 100644 index 00000000..90a3f4ac --- /dev/null +++ b/asterisk/apps/app_dictate.c @@ -0,0 +1,349 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Anthony Minessale II + * + * Anthony Minessale II + * + * Donated by Sangoma Technologies + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Virtual Dictation Machine Application For Asterisk + * + * \author Anthony Minessale II + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 68527 $") + +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/say.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" + +static char *app = "Dictate"; +static char *synopsis = "Virtual Dictation Machine"; +static char *desc = " Dictate([[|]])\n" +"Start dictation machine using optional base dir for files.\n"; + + +typedef enum { + DFLAG_RECORD = (1 << 0), + DFLAG_PLAY = (1 << 1), + DFLAG_TRUNC = (1 << 2), + DFLAG_PAUSE = (1 << 3), +} dflags; + +typedef enum { + DMODE_INIT, + DMODE_RECORD, + DMODE_PLAY +} dmodes; + +#define ast_toggle_flag(it,flag) if(ast_test_flag(it, flag)) ast_clear_flag(it, flag); else ast_set_flag(it, flag) + +static int play_and_wait(struct ast_channel *chan, char *file, char *digits) +{ + int res = -1; + if (!ast_streamfile(chan, file, chan->language)) { + res = ast_waitstream(chan, digits); + } + return res; +} + +static int dictate_exec(struct ast_channel *chan, void *data) +{ + char *path = NULL, filein[256], *filename = ""; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(base); + AST_APP_ARG(filename); + ); + char dftbase[256]; + char *base; + struct ast_flags flags = {0}; + struct ast_filestream *fs; + struct ast_frame *f = NULL; + struct ast_module_user *u; + int ffactor = 320 * 80, + res = 0, + done = 0, + oldr = 0, + lastop = 0, + samples = 0, + speed = 1, + digit = 0, + len = 0, + maxlen = 0, + mode = 0; + + u = ast_module_user_add(chan); + + snprintf(dftbase, sizeof(dftbase), "%s/dictate", ast_config_AST_SPOOL_DIR); + if (!ast_strlen_zero(data)) { + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + } else + args.argc = 0; + + if (args.argc && !ast_strlen_zero(args.base)) { + base = args.base; + } else { + base = dftbase; + } + if (args.argc > 1 && args.filename) { + filename = args.filename; + } + oldr = chan->readformat; + if ((res = ast_set_read_format(chan, AST_FORMAT_SLINEAR)) < 0) { + ast_log(LOG_WARNING, "Unable to set to linear mode.\n"); + ast_module_user_remove(u); + return -1; + } + + ast_answer(chan); + ast_safe_sleep(chan, 200); + for (res = 0; !res;) { + if (ast_strlen_zero(filename)) { + if (ast_app_getdata(chan, "dictate/enter_filename", filein, sizeof(filein), 0) || + ast_strlen_zero(filein)) { + res = -1; + break; + } + } else { + ast_copy_string(filein, filename, sizeof(filein)); + filename = ""; + } + mkdir(base, 0755); + len = strlen(base) + strlen(filein) + 2; + if (!path || len > maxlen) { + path = alloca(len); + memset(path, 0, len); + maxlen = len; + } else { + memset(path, 0, maxlen); + } + + snprintf(path, len, "%s/%s", base, filein); + fs = ast_writefile(path, "raw", NULL, O_CREAT|O_APPEND, 0, 0700); + mode = DMODE_PLAY; + memset(&flags, 0, sizeof(flags)); + ast_set_flag(&flags, DFLAG_PAUSE); + digit = play_and_wait(chan, "dictate/forhelp", AST_DIGIT_ANY); + done = 0; + speed = 1; + res = 0; + lastop = 0; + samples = 0; + while (!done && ((res = ast_waitfor(chan, -1)) > -1) && fs && (f = ast_read(chan))) { + if (digit) { + struct ast_frame fr = {AST_FRAME_DTMF, digit}; + ast_queue_frame(chan, &fr); + digit = 0; + } + if ((f->frametype == AST_FRAME_DTMF)) { + int got = 1; + switch(mode) { + case DMODE_PLAY: + switch(f->subclass) { + case '1': + ast_set_flag(&flags, DFLAG_PAUSE); + mode = DMODE_RECORD; + break; + case '2': + speed++; + if (speed > 4) { + speed = 1; + } + res = ast_say_number(chan, speed, AST_DIGIT_ANY, chan->language, (char *) NULL); + break; + case '7': + samples -= ffactor; + if(samples < 0) { + samples = 0; + } + ast_seekstream(fs, samples, SEEK_SET); + break; + case '8': + samples += ffactor; + ast_seekstream(fs, samples, SEEK_SET); + break; + + default: + got = 0; + } + break; + case DMODE_RECORD: + switch(f->subclass) { + case '1': + ast_set_flag(&flags, DFLAG_PAUSE); + mode = DMODE_PLAY; + break; + case '8': + ast_toggle_flag(&flags, DFLAG_TRUNC); + lastop = 0; + break; + default: + got = 0; + } + break; + default: + got = 0; + } + if (!got) { + switch(f->subclass) { + case '#': + done = 1; + continue; + break; + case '*': + ast_toggle_flag(&flags, DFLAG_PAUSE); + if (ast_test_flag(&flags, DFLAG_PAUSE)) { + digit = play_and_wait(chan, "dictate/pause", AST_DIGIT_ANY); + } else { + digit = play_and_wait(chan, mode == DMODE_PLAY ? "dictate/playback" : "dictate/record", AST_DIGIT_ANY); + } + break; + case '0': + ast_set_flag(&flags, DFLAG_PAUSE); + digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY); + switch(mode) { + case DMODE_PLAY: + digit = play_and_wait(chan, "dictate/play_help", AST_DIGIT_ANY); + break; + case DMODE_RECORD: + digit = play_and_wait(chan, "dictate/record_help", AST_DIGIT_ANY); + break; + } + if (digit == 0) { + digit = play_and_wait(chan, "dictate/both_help", AST_DIGIT_ANY); + } else if (digit < 0) { + done = 1; + break; + } + break; + } + } + + } else if (f->frametype == AST_FRAME_VOICE) { + switch(mode) { + struct ast_frame *fr; + int x; + case DMODE_PLAY: + if (lastop != DMODE_PLAY) { + if (ast_test_flag(&flags, DFLAG_PAUSE)) { + digit = play_and_wait(chan, "dictate/playback_mode", AST_DIGIT_ANY); + if (digit == 0) { + digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY); + } else if (digit < 0) { + break; + } + } + if (lastop != DFLAG_PLAY) { + lastop = DFLAG_PLAY; + ast_closestream(fs); + if (!(fs = ast_openstream(chan, path, chan->language))) + break; + ast_seekstream(fs, samples, SEEK_SET); + chan->stream = NULL; + } + lastop = DMODE_PLAY; + } + + if (!ast_test_flag(&flags, DFLAG_PAUSE)) { + for (x = 0; x < speed; x++) { + if ((fr = ast_readframe(fs))) { + ast_write(chan, fr); + samples += fr->samples; + ast_frfree(fr); + fr = NULL; + } else { + samples = 0; + ast_seekstream(fs, 0, SEEK_SET); + } + } + } + break; + case DMODE_RECORD: + if (lastop != DMODE_RECORD) { + int oflags = O_CREAT | O_WRONLY; + if (ast_test_flag(&flags, DFLAG_PAUSE)) { + digit = play_and_wait(chan, "dictate/record_mode", AST_DIGIT_ANY); + if (digit == 0) { + digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY); + } else if (digit < 0) { + break; + } + } + lastop = DMODE_RECORD; + ast_closestream(fs); + if ( ast_test_flag(&flags, DFLAG_TRUNC)) { + oflags |= O_TRUNC; + digit = play_and_wait(chan, "dictate/truncating_audio", AST_DIGIT_ANY); + } else { + oflags |= O_APPEND; + } + fs = ast_writefile(path, "raw", NULL, oflags, 0, 0700); + if (ast_test_flag(&flags, DFLAG_TRUNC)) { + ast_seekstream(fs, 0, SEEK_SET); + ast_clear_flag(&flags, DFLAG_TRUNC); + } else { + ast_seekstream(fs, 0, SEEK_END); + } + } + if (!ast_test_flag(&flags, DFLAG_PAUSE)) { + res = ast_writestream(fs, f); + } + break; + } + + } + + ast_frfree(f); + } + } + if (oldr) { + ast_set_read_format(chan, oldr); + } + ast_module_user_remove(u); + return 0; +} + +static int unload_module(void) +{ + int res; + res = ast_unregister_application(app); + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, dictate_exec, synopsis, desc); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Virtual Dictation Machine"); diff --git a/asterisk/apps/app_directed_pickup.c b/asterisk/apps/app_directed_pickup.c new file mode 100644 index 00000000..77968a92 --- /dev/null +++ b/asterisk/apps/app_directed_pickup.c @@ -0,0 +1,181 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Joshua Colp + * + * 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 + * + * \brief Directed Call Pickup Support + * + * \author Joshua Colp + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 67626 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" +#include "asterisk/options.h" + +#define PICKUPMARK "PICKUPMARK" + +static const char *app = "Pickup"; +static const char *synopsis = "Directed Call Pickup"; +static const char *descrip = +" Pickup(extension[@context][&extension2@context...]): This application can pickup any ringing channel\n" +"that is calling the specified extension. If no context is specified, the current\n" +"context will be used. If you use the special string \"PICKUPMARK\" for the context parameter, for example\n" +"10@PICKUPMARK, this application tries to find a channel which has defined a channel variable with the same content\n" +"as \"extension\"."; + +/* Perform actual pickup between two channels */ +static int pickup_do(struct ast_channel *chan, struct ast_channel *target) +{ + int res = 0; + + if (option_debug) + ast_log(LOG_DEBUG, "Call pickup on '%s' by '%s'\n", target->name, chan->name); + + if ((res = ast_answer(chan))) { + ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name); + return -1; + } + + if ((res = ast_queue_control(chan, AST_CONTROL_ANSWER))) { + ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name); + return -1; + } + + if ((res = ast_channel_masquerade(target, chan))) { + ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, target->name); + return -1; + } + + return res; +} + +/* Helper function that determines whether a channel is capable of being picked up */ +static int can_pickup(struct ast_channel *chan) +{ + if (!chan->pbx && (chan->_state == AST_STATE_RINGING || chan->_state == AST_STATE_RING)) + return 1; + else + return 0; +} + +/* Attempt to pick up specified extension with context */ +static int pickup_by_exten(struct ast_channel *chan, char *exten, char *context) +{ + int res = -1; + struct ast_channel *target = NULL; + + while ((target = ast_channel_walk_locked(target))) { + if ((!strcasecmp(target->macroexten, exten) || !strcasecmp(target->exten, exten)) && + !strcasecmp(target->dialcontext, context) && + can_pickup(target)) { + res = pickup_do(chan, target); + ast_channel_unlock(target); + break; + } + ast_channel_unlock(target); + } + + return res; +} + +/* Attempt to pick up specified mark */ +static int pickup_by_mark(struct ast_channel *chan, char *mark) +{ + int res = -1; + const char *tmp = NULL; + struct ast_channel *target = NULL; + + while ((target = ast_channel_walk_locked(target))) { + if ((tmp = pbx_builtin_getvar_helper(target, PICKUPMARK)) && + !strcasecmp(tmp, mark) && + can_pickup(target)) { + res = pickup_do(chan, target); + ast_channel_unlock(target); + break; + } + ast_channel_unlock(target); + } + + return res; +} + +/* Main application entry point */ +static int pickup_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u = NULL; + char *tmp = ast_strdupa(data); + char *exten = NULL, *context = NULL; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Pickup requires an argument (extension)!\n"); + return -1; + } + + u = ast_module_user_add(chan); + + /* Parse extension (and context if there) */ + while (!ast_strlen_zero(tmp) && (exten = strsep(&tmp, "&"))) { + if ((context = strchr(exten, '@'))) + *context++ = '\0'; + if (context && !strcasecmp(context, PICKUPMARK)) { + if (!pickup_by_mark(chan, exten)) + break; + } else { + if (!pickup_by_exten(chan, exten, context ? context : chan->context)) + break; + } + ast_log(LOG_NOTICE, "No target channel found for %s.\n", exten); + } + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, pickup_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Directed Call Pickup Application"); diff --git a/asterisk/apps/app_directory.c b/asterisk/apps/app_directory.c new file mode 100644 index 00000000..b470f53d --- /dev/null +++ b/asterisk/apps/app_directory.c @@ -0,0 +1,704 @@ +/* + * 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 Provide a directory of extensions + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 78415 $") + +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/say.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" + +#ifdef ODBC_STORAGE +#include +#include +#include "asterisk/res_odbc.h" + +static char odbc_database[80] = "asterisk"; +static char odbc_table[80] = "voicemessages"; +static char vmfmts[80] = "wav"; +#endif + +static char *app = "Directory"; + +static char *synopsis = "Provide directory of voicemail extensions"; +static char *descrip = +" Directory(vm-context[|dial-context[|options]]): This application will present\n" +"the calling channel with a directory of extensions from which they can search\n" +"by name. The list of names and corresponding extensions is retrieved from the\n" +"voicemail configuration file, voicemail.conf.\n" +" This application will immediately exit if one of the following DTMF digits are\n" +"received and the extension to jump to exists:\n" +" 0 - Jump to the 'o' extension, if it exists.\n" +" * - Jump to the 'a' extension, if it exists.\n\n" +" Parameters:\n" +" vm-context - This is the context within voicemail.conf to use for the\n" +" Directory.\n" +" dial-context - This is the dialplan context to use when looking for an\n" +" extension that the user has selected, or when jumping to the\n" +" 'o' or 'a' extension.\n\n" +" Options:\n" +" e - In addition to the name, also read the extension number to the\n" +" caller before presenting dialing options.\n" +" f - Allow the caller to enter the first name of a user in the directory\n" +" instead of using the last name.\n"; + +/* For simplicity, I'm keeping the format compatible with the voicemail config, + but i'm open to suggestions for isolating it */ + +#define VOICEMAIL_CONFIG "voicemail.conf" + +/* How many digits to read in */ +#define NUMDIGITS 3 + + +#ifdef ODBC_STORAGE +struct generic_prepare_struct { + const char *sql; + const char *param; +}; + +static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data) +{ + struct generic_prepare_struct *gps = data; + SQLHSTMT stmt; + int res; + + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + return NULL; + } + + res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *)gps->sql); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return NULL; + } + + if (!ast_strlen_zero(gps->param)) + SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->param), 0, (void *)gps->param, 0, NULL); + + return stmt; +} + +static void retrieve_file(char *dir) +{ + int x = 0; + int res; + int fd=-1; + size_t fdlen = 0; + void *fdm = MAP_FAILED; + SQLHSTMT stmt; + char sql[256]; + char fmt[80]="", empty[10] = ""; + char *c; + SQLLEN colsize; + char full_fn[256]; + struct odbc_obj *obj; + struct generic_prepare_struct gps = { .sql = sql, .param = dir }; + + obj = ast_odbc_request_obj(odbc_database, 1); + if (obj) { + do { + ast_copy_string(fmt, vmfmts, sizeof(fmt)); + c = strchr(fmt, '|'); + if (c) + *c = '\0'; + if (!strcasecmp(fmt, "wav49")) + strcpy(fmt, "WAV"); + snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt); + snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table); + stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps); + + if (!stmt) { + ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); + break; + } + res = SQLFetch(stmt); + if (res == SQL_NO_DATA) { + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + break; + } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + break; + } + fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, 0770); + if (fd < 0) { + ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno)); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + break; + } + + res = SQLGetData(stmt, 1, SQL_BINARY, empty, 0, &colsize); + fdlen = colsize; + if (fd > -1) { + char tmp[1]=""; + lseek(fd, fdlen - 1, SEEK_SET); + if (write(fd, tmp, 1) != 1) { + close(fd); + fd = -1; + break; + } + if (fd > -1) + fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + } + if (fdm != MAP_FAILED) { + memset(fdm, 0, fdlen); + res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + break; + } + } + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + } while (0); + ast_odbc_release_obj(obj); + } else + ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database); + if (fdm != MAP_FAILED) + munmap(fdm, fdlen); + if (fd > -1) + close(fd); + return; +} +#endif + +static char *convert(const char *lastname) +{ + char *tmp; + int lcount = 0; + tmp = ast_malloc(NUMDIGITS + 1); + if (tmp) { + while((*lastname > 32) && lcount < NUMDIGITS) { + switch(toupper(*lastname)) { + case '1': + tmp[lcount++] = '1'; + break; + case '2': + case 'A': + case 'B': + case 'C': + tmp[lcount++] = '2'; + break; + case '3': + case 'D': + case 'E': + case 'F': + tmp[lcount++] = '3'; + break; + case '4': + case 'G': + case 'H': + case 'I': + tmp[lcount++] = '4'; + break; + case '5': + case 'J': + case 'K': + case 'L': + tmp[lcount++] = '5'; + break; + case '6': + case 'M': + case 'N': + case 'O': + tmp[lcount++] = '6'; + break; + case '7': + case 'P': + case 'Q': + case 'R': + case 'S': + tmp[lcount++] = '7'; + break; + case '8': + case 'T': + case 'U': + case 'V': + tmp[lcount++] = '8'; + break; + case '9': + case 'W': + case 'X': + case 'Y': + case 'Z': + tmp[lcount++] = '9'; + break; + } + lastname++; + } + tmp[lcount] = '\0'; + } + return tmp; +} + +/* play name of mailbox owner. + * returns: -1 for bad or missing extension + * '1' for selected entry from directory + * '*' for skipped entry from directory + */ +static int play_mailbox_owner(struct ast_channel *chan, char *context, + char *dialcontext, char *ext, char *name, int readext, + int fromappvm) +{ + int res = 0; + int loop; + char fn[256]; + + /* Check for the VoiceMail2 greeting first */ + snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet", + ast_config_AST_SPOOL_DIR, context, ext); +#ifdef ODBC_STORAGE + retrieve_file(fn); +#endif + + if (ast_fileexists(fn, NULL, chan->language) <= 0) { + /* no file, check for an old-style Voicemail greeting */ + snprintf(fn, sizeof(fn), "%s/vm/%s/greet", + ast_config_AST_SPOOL_DIR, ext); + } +#ifdef ODBC_STORAGE + retrieve_file(fn); +#endif + + if (ast_fileexists(fn, NULL, chan->language) > 0) { + res = ast_stream_and_wait(chan, fn, chan->language, AST_DIGIT_ANY); + ast_stopstream(chan); + /* If Option 'e' was specified, also read the extension number with the name */ + if (readext) { + ast_stream_and_wait(chan, "vm-extension", chan->language, AST_DIGIT_ANY); + res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language); + } + } else { + res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language); + if (!ast_strlen_zero(name) && readext) { + ast_stream_and_wait(chan, "vm-extension", chan->language, AST_DIGIT_ANY); + res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language); + } + } +#ifdef ODBC_STORAGE + ast_filedelete(fn, NULL); +#endif + + for (loop = 3 ; loop > 0; loop--) { + if (!res) + res = ast_stream_and_wait(chan, "dir-instr", chan->language, AST_DIGIT_ANY); + if (!res) + res = ast_waitfordigit(chan, 3000); + ast_stopstream(chan); + + if (res < 0) /* User hungup, so jump out now */ + break; + if (res == '1') { /* Name selected */ + if (fromappvm) { + /* We still want to set the exten though */ + ast_copy_string(chan->exten, ext, sizeof(chan->exten)); + } else { + if (ast_goto_if_exists(chan, dialcontext, ext, 1)) { + ast_log(LOG_WARNING, + "Can't find extension '%s' in context '%s'. " + "Did you pass the wrong context to Directory?\n", + ext, dialcontext); + res = -1; + } + } + break; + } + if (res == '*') /* Skip to next match in list */ + break; + + /* Not '1', or '*', so decrement number of tries */ + res = 0; + } + + return(res); +} + +static struct ast_config *realtime_directory(char *context) +{ + struct ast_config *cfg; + struct ast_config *rtdata; + struct ast_category *cat; + struct ast_variable *var; + char *mailbox; + const char *fullname; + const char *hidefromdir; + char tmp[100]; + + /* Load flat file config. */ + cfg = ast_config_load(VOICEMAIL_CONFIG); + + if (!cfg) { + /* Loading config failed. */ + ast_log(LOG_WARNING, "Loading config failed.\n"); + return NULL; + } + + /* Get realtime entries, categorized by their mailbox number + and present in the requested context */ + rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL); + + /* if there are no results, just return the entries from the config file */ + if (!rtdata) + return cfg; + + /* Does the context exist within the config file? If not, make one */ + cat = ast_category_get(cfg, context); + if (!cat) { + cat = ast_category_new(context); + if (!cat) { + ast_log(LOG_WARNING, "Out of memory\n"); + ast_config_destroy(cfg); + return NULL; + } + ast_category_append(cfg, cat); + } + + mailbox = NULL; + while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) { + fullname = ast_variable_retrieve(rtdata, mailbox, "fullname"); + hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir"); + snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s", + fullname ? fullname : "", + hidefromdir ? hidefromdir : "no"); + var = ast_variable_new(mailbox, tmp); + if (var) + ast_variable_append(cat, var); + else + ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox); + } + ast_config_destroy(rtdata); + + return cfg; +} + +static int do_directory(struct ast_channel *chan, struct ast_config *cfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, int last, int readext, int fromappvm) +{ + /* Read in the first three digits.. "digit" is the first digit, already read */ + char ext[NUMDIGITS + 1], *cat; + char name[80] = ""; + struct ast_variable *v; + int res; + int found=0; + int lastuserchoice = 0; + char *start, *conv, *stringp = NULL; + const char *pos; + int breakout = 0; + + if (ast_strlen_zero(context)) { + ast_log(LOG_WARNING, + "Directory must be called with an argument " + "(context in which to interpret extensions)\n"); + return -1; + } + if (digit == '0') { + if (!ast_goto_if_exists(chan, dialcontext, "o", 1) || + (!ast_strlen_zero(chan->macrocontext) && + !ast_goto_if_exists(chan, chan->macrocontext, "o", 1))) { + return 0; + } else { + ast_log(LOG_WARNING, "Can't find extension 'o' in current context. " + "Not Exiting the Directory!\n"); + res = 0; + } + } + if (digit == '*') { + if (!ast_goto_if_exists(chan, dialcontext, "a", 1) || + (!ast_strlen_zero(chan->macrocontext) && + !ast_goto_if_exists(chan, chan->macrocontext, "a", 1))) { + return 0; + } else { + ast_log(LOG_WARNING, "Can't find extension 'a' in current context. " + "Not Exiting the Directory!\n"); + res = 0; + } + } + memset(ext, 0, sizeof(ext)); + ext[0] = digit; + res = 0; + if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1; + if (!res) { + /* Search for all names which start with those digits */ + v = ast_variable_browse(cfg, context); + while(v && !res) { + /* Find all candidate extensions */ + while(v) { + /* Find a candidate extension */ + start = strdup(v->value); + if (start && !strcasestr(start, "hidefromdir=yes")) { + stringp=start; + strsep(&stringp, ","); + pos = strsep(&stringp, ","); + if (pos) { + ast_copy_string(name, pos, sizeof(name)); + /* Grab the last name */ + if (last && strrchr(pos,' ')) + pos = strrchr(pos, ' ') + 1; + conv = convert(pos); + if (conv) { + if (!strncmp(conv, ext, strlen(ext))) { + /* Match! */ + found++; + free(conv); + free(start); + break; + } + free(conv); + } + } + free(start); + } + v = v->next; + } + + if (v) { + /* We have a match -- play a greeting if they have it */ + res = play_mailbox_owner(chan, context, dialcontext, v->name, name, readext, fromappvm); + switch (res) { + case -1: + /* user pressed '1' but extension does not exist, or + * user hungup + */ + lastuserchoice = 0; + break; + case '1': + /* user pressed '1' and extensions exists; + play_mailbox_owner will already have done + a goto() on the channel + */ + lastuserchoice = res; + break; + case '*': + /* user pressed '*' to skip something found */ + lastuserchoice = res; + res = 0; + break; + default: + break; + } + v = v->next; + } + } + + if (!res && ucfg) { + /* Search users.conf for all names which start with those digits */ + for (cat = ast_category_browse(ucfg, NULL); cat && !res ; cat = ast_category_browse(ucfg, cat)) { + if (!strcasecmp(cat, "general")) + continue; + if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory"))) + continue; + + /* Find all candidate extensions */ + if ((pos = ast_variable_retrieve(ucfg, cat, "fullname"))) { + ast_copy_string(name, pos, sizeof(name)); + /* Grab the last name */ + if (last && strrchr(pos,' ')) + pos = strrchr(pos, ' ') + 1; + conv = convert(pos); + if (conv) { + if (!strcmp(conv, ext)) { + /* Match! */ + found++; + /* We have a match -- play a greeting if they have it */ + res = play_mailbox_owner(chan, context, dialcontext, cat, name, readext, fromappvm); + switch (res) { + case -1: + /* user pressed '1' but extension does not exist, or + * user hungup + */ + lastuserchoice = 0; + breakout = 1; + break; + case '1': + /* user pressed '1' and extensions exists; + play_mailbox_owner will already have done + a goto() on the channel + */ + lastuserchoice = res; + breakout = 1; + break; + case '*': + /* user pressed '*' to skip something found */ + lastuserchoice = res; + breakout = 0; + res = 0; + break; + default: + breakout = 1; + break; + } + free(conv); + if (breakout) + break; + } + else + free(conv); + } + } + } + } + + if (lastuserchoice != '1') { + res = ast_streamfile(chan, found ? "dir-nomore" : "dir-nomatch", chan->language); + if (!res) + res = 1; + return res; + } + return 0; + } + return res; +} + +static int directory_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u; + struct ast_config *cfg, *ucfg; + int last = 1; + int readext = 0; + int fromappvm = 0; + const char *dirintro; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(vmcontext); + AST_APP_ARG(dialcontext); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n"); + return -1; + } + + u = ast_module_user_add(chan); + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (args.options) { + if (strchr(args.options, 'f')) + last = 0; + if (strchr(args.options, 'e')) + readext = 1; + if (strchr(args.options, 'v')) + fromappvm = 1; + } + + if (ast_strlen_zero(args.dialcontext)) + args.dialcontext = args.vmcontext; + + cfg = realtime_directory(args.vmcontext); + if (!cfg) { + ast_log(LOG_ERROR, "Unable to read the configuration data!\n"); + ast_module_user_remove(u); + return -1; + } + + ucfg = ast_config_load("users.conf"); + + dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro"); + if (ast_strlen_zero(dirintro)) + dirintro = ast_variable_retrieve(cfg, "general", "directoryintro"); + if (ast_strlen_zero(dirintro)) + dirintro = last ? "dir-intro" : "dir-intro-fn"; + + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + + for (;;) { + if (!res) + res = ast_stream_and_wait(chan, dirintro, chan->language, AST_DIGIT_ANY); + ast_stopstream(chan); + if (!res) + res = ast_waitfordigit(chan, 5000); + if (res > 0) { + res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, last, readext, fromappvm); + if (res > 0) { + res = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + if (res >= 0) + continue; + } + } + break; + } + if (ucfg) + ast_config_destroy(ucfg); + ast_config_destroy(cfg); + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + res = ast_unregister_application(app); + return res; +} + +static int load_module(void) +{ +#ifdef ODBC_STORAGE + struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG); + const char *tmp; + + if (cfg) { + if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) { + ast_copy_string(odbc_database, tmp, sizeof(odbc_database)); + } + if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) { + ast_copy_string(odbc_table, tmp, sizeof(odbc_table)); + } + if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) { + ast_copy_string(vmfmts, tmp, sizeof(vmfmts)); + } + ast_config_destroy(cfg); + } else + ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n"); +#endif + + return ast_register_application(app, directory_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory"); diff --git a/asterisk/apps/app_disa.c b/asterisk/apps/app_disa.c new file mode 100644 index 00000000..ad1cb5b1 --- /dev/null +++ b/asterisk/apps/app_disa.c @@ -0,0 +1,393 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * + * Made only slightly more sane by 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 DISA -- Direct Inward System Access Application + * + * \author Jim Dixon + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/app.h" +#include "asterisk/indications.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/ulaw.h" +#include "asterisk/callerid.h" +#include "asterisk/stringfields.h" + +static char *app = "DISA"; + +static char *synopsis = "DISA (Direct Inward System Access)"; + +static char *descrip = + "DISA([|]) or DISA()\n" + "The DISA, Direct Inward System Access, application allows someone from \n" + "outside the telephone switch (PBX) to obtain an \"internal\" system \n" + "dialtone and to place calls from it as if they were placing a call from \n" + "within the switch.\n" + "DISA plays a dialtone. The user enters their numeric passcode, followed by\n" + "the pound sign (#). If the passcode is correct, the user is then given\n" + "system dialtone on which a call may be placed. Obviously, this type\n" + "of access has SERIOUS security implications, and GREAT care must be\n" + "taken NOT to compromise your security.\n\n" + "There is a possibility of accessing DISA without password. Simply\n" + "exchange your password with \"no-password\".\n\n" + " Example: exten => s,1,DISA(no-password|local)\n\n" + "Be aware that using this compromises the security of your PBX.\n\n" + "The arguments to this application (in extensions.conf) allow either\n" + "specification of a single global passcode (that everyone uses), or\n" + "individual passcodes contained in a file. It also allows specification\n" + "of the context on which the user will be dialing. If no context is\n" + "specified, the DISA application defaults the context to \"disa\".\n" + "Presumably a normal system will have a special context set up\n" + "for DISA use with some or a lot of restrictions. \n\n" + "The file that contains the passcodes (if used) allows specification\n" + "of either just a passcode (defaulting to the \"disa\" context, or\n" + "passcode|context on each line of the file. The file may contain blank\n" + "lines, or comments starting with \"#\" or \";\". In addition, the\n" + "above arguments may have |new-callerid-string appended to them, to\n" + "specify a new (different) callerid to be used for this call, for\n" + "example: numeric-passcode|context|\"My Phone\" <(234) 123-4567> or \n" + "full-pathname-of-passcode-file|\"My Phone\" <(234) 123-4567>. Last\n" + "but not least, |mailbox[@context] may be appended, which will cause\n" + "a stutter-dialtone (indication \"dialrecall\") to be used, if the\n" + "specified mailbox contains any new messages, for example:\n" + "numeric-passcode|context||1234 (w/a changing callerid). Note that\n" + "in the case of specifying the numeric-passcode, the context must be\n" + "specified if the callerid is specified also.\n\n" + "If login is successful, the application looks up the dialed number in\n" + "the specified (or default) context, and executes it if found.\n" + "If the user enters an invalid extension and extension \"i\" (invalid) \n" + "exists in the context, it will be used. Also, if you set the 5th argument\n" + "to 'NOANSWER', the DISA application will not answer initially.\n"; + + +static void play_dialtone(struct ast_channel *chan, char *mailbox) +{ + const struct ind_tone_zone_sound *ts = NULL; + if(ast_app_has_voicemail(mailbox, NULL)) + ts = ast_get_indication_tone(chan->zone, "dialrecall"); + else + ts = ast_get_indication_tone(chan->zone, "dial"); + if (ts) + ast_playtones_start(chan, 0, ts->data, 0); + else + ast_tonepair_start(chan, 350, 440, 0, 0); +} + +static int disa_exec(struct ast_channel *chan, void *data) +{ + int i,j,k,x,did_ignore,special_noanswer; + int firstdigittimeout = 20000; + int digittimeout = 10000; + struct ast_module_user *u; + char *tmp, exten[AST_MAX_EXTENSION],acctcode[20]=""; + char pwline[256]; + char ourcidname[256],ourcidnum[256]; + struct ast_frame *f; + struct timeval lastdigittime; + int res; + time_t rstart; + FILE *fp; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(passcode); + AST_APP_ARG(context); + AST_APP_ARG(cid); + AST_APP_ARG(mailbox); + AST_APP_ARG(noanswer); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (chan->pbx) { + firstdigittimeout = chan->pbx->rtimeout*1000; + digittimeout = chan->pbx->dtimeout*1000; + } + + if (ast_set_write_format(chan,AST_FORMAT_ULAW)) { + ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n", chan->name); + ast_module_user_remove(u); + return -1; + } + if (ast_set_read_format(chan,AST_FORMAT_ULAW)) { + ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n", chan->name); + ast_module_user_remove(u); + return -1; + } + + ast_log(LOG_DEBUG, "Digittimeout: %d\n", digittimeout); + ast_log(LOG_DEBUG, "Responsetimeout: %d\n", firstdigittimeout); + + tmp = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, tmp); + + if (ast_strlen_zero(args.context)) + args.context = "disa"; + if (ast_strlen_zero(args.mailbox)) + args.mailbox = ""; + + ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox); + + + special_noanswer = 0; + if ((!args.noanswer) || strcmp(args.noanswer,"NOANSWER")) + { + if (chan->_state != AST_STATE_UP) { + /* answer */ + ast_answer(chan); + } + } else special_noanswer = 1; + i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */ + did_ignore = 0; + exten[0] = 0; + acctcode[0] = 0; + /* can we access DISA without password? */ + + ast_log(LOG_DEBUG, "Context: %s\n",args.context); + + if (!strcasecmp(args.passcode, "no-password")) { + k |= 1; /* We have the password */ + ast_log(LOG_DEBUG, "DISA no-password login success\n"); + } + lastdigittime = ast_tvnow(); + + play_dialtone(chan, args.mailbox); + + for (;;) { + /* if outa time, give em reorder */ + if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > + ((k&2) ? digittimeout : firstdigittimeout)) { + ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n", + ((k&1) ? "extension" : "password"),chan->name); + break; + } + if ((res = ast_waitfor(chan, -1) < 0)) { + ast_log(LOG_DEBUG, "Waitfor returned %d\n", res); + continue; + } + + f = ast_read(chan); + if (f == NULL) { + ast_module_user_remove(u); + return -1; + } + if ((f->frametype == AST_FRAME_CONTROL) && + (f->subclass == AST_CONTROL_HANGUP)) { + ast_frfree(f); + ast_module_user_remove(u); + return -1; + } + if (f->frametype == AST_FRAME_VOICE) { + ast_frfree(f); + continue; + } + + /* if not DTMF, just do it again */ + if (f->frametype != AST_FRAME_DTMF) { + ast_frfree(f); + continue; + } + + j = f->subclass; /* save digit */ + ast_frfree(f); + if (i == 0) { + k|=2; /* We have the first digit */ + ast_playtones_stop(chan); + } + lastdigittime = ast_tvnow(); + /* got a DTMF tone */ + if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */ + if (!(k&1)) { /* if in password state */ + if (j == '#') { /* end of password */ + /* see if this is an integer */ + if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */ + fp = fopen(args.passcode,"r"); + if (!fp) { + ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name); + ast_module_user_remove(u); + return -1; + } + pwline[0] = 0; + while(fgets(pwline,sizeof(pwline) - 1,fp)) { + if (!pwline[0]) + continue; + if (pwline[strlen(pwline) - 1] == '\n') + pwline[strlen(pwline) - 1] = 0; + if (!pwline[0]) + continue; + /* skip comments */ + if (pwline[0] == '#') + continue; + if (pwline[0] == ';') + continue; + + AST_STANDARD_APP_ARGS(args, pwline); + + ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox); + + /* password must be in valid format (numeric) */ + if (sscanf(args.passcode,"%d", &j) < 1) + continue; + /* if we got it */ + if (!strcmp(exten,args.passcode)) { + if (ast_strlen_zero(args.context)) + args.context = "disa"; + if (ast_strlen_zero(args.mailbox)) + args.mailbox = ""; + break; + } + } + fclose(fp); + } + /* compare the two */ + if (strcmp(exten,args.passcode)) { + ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten); + goto reorder; + + } + /* password good, set to dial state */ + ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name); + play_dialtone(chan, args.mailbox); + + k|=1; /* In number mode */ + i = 0; /* re-set buffer pointer */ + exten[sizeof(acctcode)] = 0; + ast_copy_string(acctcode, exten, sizeof(acctcode)); + exten[0] = 0; + ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n", chan->name); + continue; + } + } else { + if (j == '#') { /* end of extension */ + break; + } + } + + exten[i++] = j; /* save digit */ + exten[i] = 0; + if (!(k&1)) + continue; /* if getting password, continue doing it */ + /* if this exists */ + + if (ast_ignore_pattern(args.context, exten)) { + play_dialtone(chan, ""); + did_ignore = 1; + } else + if (did_ignore) { + ast_playtones_stop(chan); + did_ignore = 0; + } + + /* if can do some more, do it */ + if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) { + break; + } + } + } + + if (k == 3) { + int recheck = 0; + struct ast_flags flags = { AST_CDR_FLAG_POSTED }; + + if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) { + pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten); + exten[0] = 'i'; + exten[1] = '\0'; + recheck = 1; + } + if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) { + ast_playtones_stop(chan); + /* We're authenticated and have a target extension */ + if (!ast_strlen_zero(args.cid)) { + ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum)); + ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum); + } + + if (!ast_strlen_zero(acctcode)) + ast_string_field_set(chan, accountcode, acctcode); + + if (special_noanswer) flags.flags = 0; + ast_cdr_reset(chan->cdr, &flags); + ast_explicit_goto(chan, args.context, exten, 1); + ast_module_user_remove(u); + return 0; + } + } + + /* Received invalid, but no "i" extension exists in the given context */ + +reorder: + + ast_indicate(chan,AST_CONTROL_CONGESTION); + /* something is invalid, give em reorder for several seconds */ + time(&rstart); + while(time(NULL) < rstart + 10) { + if (ast_waitfor(chan, -1) < 0) + break; + f = ast_read(chan); + if (!f) + break; + ast_frfree(f); + } + ast_playtones_stop(chan); + ast_module_user_remove(u); + return -1; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, disa_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application"); diff --git a/asterisk/apps/app_dumpchan.c b/asterisk/apps/app_dumpchan.c new file mode 100644 index 00000000..389aac5c --- /dev/null +++ b/asterisk/apps/app_dumpchan.c @@ -0,0 +1,176 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2004 - 2005, Anthony Minessale II. + * + * Anthony Minessale + * + * A license has been granted to Digium (via disclaimer) for the use of + * this code. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Application to dump channel variables + * + * \author Anthony Minessale + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40722 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/options.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" + +static char *app = "DumpChan"; +static char *synopsis = "Dump Info About The Calling Channel"; +static char *desc = + " DumpChan([])\n" + "Displays information on channel and listing of all channel\n" + "variables. If min_verbose_level is specified, output is only\n" + "displayed when the verbose level is currently set to that number\n" + "or greater. \n"; + + +static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) +{ + struct timeval now; + long elapsed_seconds = 0; + int hour = 0, min = 0, sec = 0; + char cgrp[BUFSIZ/2]; + char pgrp[BUFSIZ/2]; + char formatbuf[BUFSIZ/2]; + + now = ast_tvnow(); + memset(buf, 0, size); + if (!c) + return 0; + + if (c->cdr) { + elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec; + hour = elapsed_seconds / 3600; + min = (elapsed_seconds % 3600) / 60; + sec = elapsed_seconds % 60; + } + + snprintf(buf,size, + "Name= %s\n" + "Type= %s\n" + "UniqueID= %s\n" + "CallerID= %s\n" + "CallerIDName= %s\n" + "DNIDDigits= %s\n" + "RDNIS= %s\n" + "State= %s (%d)\n" + "Rings= %d\n" + "NativeFormat= %s\n" + "WriteFormat= %s\n" + "ReadFormat= %s\n" + "1stFileDescriptor= %d\n" + "Framesin= %d %s\n" + "Framesout= %d %s\n" + "TimetoHangup= %ld\n" + "ElapsedTime= %dh%dm%ds\n" + "Context= %s\n" + "Extension= %s\n" + "Priority= %d\n" + "CallGroup= %s\n" + "PickupGroup= %s\n" + "Application= %s\n" + "Data= %s\n" + "Blocking_in= %s\n", + c->name, + c->tech->type, + c->uniqueid, + S_OR(c->cid.cid_num, "(N/A)"), + S_OR(c->cid.cid_name, "(N/A)"), + S_OR(c->cid.cid_dnid, "(N/A)"), + S_OR(c->cid.cid_rdnis, "(N/A)"), + ast_state2str(c->_state), + c->_state, + c->rings, + ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->nativeformats), + ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->writeformat), + ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->readformat), + c->fds[0], c->fin & ~DEBUGCHAN_FLAG, (c->fin & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "", + c->fout & ~DEBUGCHAN_FLAG, (c->fout & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "", (long)c->whentohangup, + hour, + min, + sec, + c->context, + c->exten, + c->priority, + ast_print_group(cgrp, sizeof(cgrp), c->callgroup), + ast_print_group(pgrp, sizeof(pgrp), c->pickupgroup), + ( c->appl ? c->appl : "(N/A)" ), + ( c-> data ? S_OR(c->data, "(Empty)") : "(None)"), + (ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)")); + + return 0; +} + +static int dumpchan_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + char vars[BUFSIZ * 4]; + char info[1024]; + int level = 0; + static char *line = "================================================================================"; + + u = ast_module_user_add(chan); + + if (!ast_strlen_zero(data)) + level = atoi(data); + + pbx_builtin_serialize_variables(chan, vars, sizeof(vars)); + serialize_showchan(chan, info, sizeof(info)); + if (option_verbose >= level) + ast_verbose("\nDumping Info For Channel: %s:\n%s\nInfo:\n%s\nVariables:\n%s%s\n", chan->name, line, info, vars, line); + + ast_module_user_remove(u); + + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, dumpchan_exec, synopsis, desc); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dump Info About The Calling Channel"); diff --git a/asterisk/apps/app_echo.c b/asterisk/apps/app_echo.c new file mode 100644 index 00000000..f4aa71c5 --- /dev/null +++ b/asterisk/apps/app_echo.c @@ -0,0 +1,104 @@ +/* + * 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 Echo application -- play back what you hear to evaluate latency + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 53880 $") + +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" + +static char *app = "Echo"; + +static char *synopsis = "Echo audio, video, or DTMF back to the calling party"; + +static char *descrip = +" Echo(): This application will echo any audio, video, or DTMF frames read from\n" +"the calling channel back to itself. If the DTMF digit '#' is received, the\n" +"application will exit.\n"; + + +static int echo_exec(struct ast_channel *chan, void *data) +{ + int res = -1; + int format; + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + format = ast_best_codec(chan->nativeformats); + ast_set_write_format(chan, format); + ast_set_read_format(chan, format); + + while (ast_waitfor(chan, -1) > -1) { + struct ast_frame *f = ast_read(chan); + if (!f) + break; + f->delivery.tv_sec = 0; + f->delivery.tv_usec = 0; + if (ast_write(chan, f)) { + ast_frfree(f); + goto end; + } + if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) { + res = 0; + ast_frfree(f); + goto end; + } + ast_frfree(f); + } +end: + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, echo_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Echo Application"); diff --git a/asterisk/apps/app_exec.c b/asterisk/apps/app_exec.c new file mode 100644 index 00000000..d94c941b --- /dev/null +++ b/asterisk/apps/app_exec.c @@ -0,0 +1,221 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (c) 2004 - 2005, Tilghman Lesher. All rights reserved. + * Portions copyright (c) 2006, Philipp Dunkel. + * + * Tilghman Lesher + * + * This code is released by the author with no restrictions on usage. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + */ + +/*! \file + * + * \brief Exec application + * + * \author Tilghman Lesher + * \author Philipp Dunkel + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 107600 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" + +/* Maximum length of any variable */ +#define MAXRESULT 1024 + +/*! Note + * + * The key difference between these two apps is exit status. In a + * nutshell, Exec tries to be transparent as possible, behaving + * in exactly the same way as if the application it calls was + * directly invoked from the dialplan. + * + * TryExec, on the other hand, provides a way to execute applications + * and catch any possible fatal error without actually fatally + * affecting the dialplan. + */ + +static char *app_exec = "Exec"; +static char *exec_synopsis = "Executes dialplan application"; +static char *exec_descrip = +"Usage: Exec(appname(arguments))\n" +" Allows an arbitrary application to be invoked even when not\n" +"hardcoded into the dialplan. If the underlying application\n" +"terminates the dialplan, or if the application cannot be found,\n" +"Exec will terminate the dialplan.\n" +" To invoke external applications, see the application System.\n" +" If you would like to catch any error instead, see TryExec.\n"; + +static char *app_tryexec = "TryExec"; +static char *tryexec_synopsis = "Executes dialplan application, always returning"; +static char *tryexec_descrip = +"Usage: TryExec(appname(arguments))\n" +" Allows an arbitrary application to be invoked even when not\n" +"hardcoded into the dialplan. To invoke external applications\n" +"see the application System. Always returns to the dialplan.\n" +"The channel variable TRYSTATUS will be set to:\n" +" SUCCESS if the application returned zero\n" +" FAILED if the application returned non-zero\n" +" NOAPP if the application was not found or was not specified\n"; + +static char *app_execif = "ExecIf"; +static char *execif_synopsis = "Executes dialplan application, conditionally"; +static char *execif_descrip = +"Usage: ExecIF (||)\n" +"If is true, execute and return the result of ().\n" +"If is true, but is not found, then the application\n" +"will return a non-zero value.\n"; + +static int exec_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + char *s, *appname, *endargs, args[MAXRESULT] = ""; + struct ast_app *app; + + u = ast_module_user_add(chan); + + /* Check and parse arguments */ + if (data) { + s = ast_strdupa(data); + appname = strsep(&s, "("); + if (s) { + endargs = strrchr(s, ')'); + if (endargs) + *endargs = '\0'; + pbx_substitute_variables_helper(chan, s, args, MAXRESULT - 1); + } + if (appname) { + app = pbx_findapp(appname); + if (app) { + res = pbx_exec(chan, app, args); + } else { + ast_log(LOG_WARNING, "Could not find application (%s)\n", appname); + res = -1; + } + } + } + + ast_module_user_remove(u); + return res; +} + +static int tryexec_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + char *s, *appname, *endargs, args[MAXRESULT] = ""; + struct ast_app *app; + + u = ast_module_user_add(chan); + + /* Check and parse arguments */ + if (data) { + s = ast_strdupa(data); + appname = strsep(&s, "("); + if (s) { + endargs = strrchr(s, ')'); + if (endargs) + *endargs = '\0'; + pbx_substitute_variables_helper(chan, s, args, MAXRESULT - 1); + } + if (appname) { + app = pbx_findapp(appname); + if (app) { + res = pbx_exec(chan, app, args); + pbx_builtin_setvar_helper(chan, "TRYSTATUS", res ? "FAILED" : "SUCCESS"); + } else { + ast_log(LOG_WARNING, "Could not find application (%s)\n", appname); + pbx_builtin_setvar_helper(chan, "TRYSTATUS", "NOAPP"); + } + } + } + + ast_module_user_remove(u); + return 0; +} + +static int execif_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u; + char *myapp = NULL; + char *mydata = NULL; + char *expr = NULL; + struct ast_app *app = NULL; + + u = ast_module_user_add(chan); + + expr = ast_strdupa(data); + + if ((myapp = strchr(expr,'|'))) { + *myapp = '\0'; + myapp++; + if ((mydata = strchr(myapp,'|'))) { + *mydata = '\0'; + mydata++; + } else + mydata = ""; + + if (pbx_checkcondition(expr)) { + if ((app = pbx_findapp(myapp))) { + res = pbx_exec(chan, app, mydata); + } else { + ast_log(LOG_WARNING, "Could not find application! (%s)\n", myapp); + res = -1; + } + } + } else { + ast_log(LOG_ERROR,"Invalid Syntax.\n"); + res = -1; + } + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app_exec); + res |= ast_unregister_application(app_tryexec); + res |= ast_unregister_application(app_execif); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res = ast_register_application(app_exec, exec_exec, exec_synopsis, exec_descrip); + res |= ast_register_application(app_tryexec, tryexec_exec, tryexec_synopsis, tryexec_descrip); + res |= ast_register_application(app_execif, execif_exec, execif_synopsis, execif_descrip); + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Executes dialplan applications"); diff --git a/asterisk/apps/app_externalivr.c b/asterisk/apps/app_externalivr.c new file mode 100644 index 00000000..d739faf6 --- /dev/null +++ b/asterisk/apps/app_externalivr.c @@ -0,0 +1,585 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Kevin P. Fleming + * + * Portions taken from the file-based music-on-hold work + * created by Anthony Minessale II in res_musiconhold.c + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 External IVR application interface + * + * \author Kevin P. Fleming + * + * \note Portions taken from the file-based music-on-hold work + * created by Anthony Minessale II in res_musiconhold.c + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 116310 $") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/linkedlists.h" +#include "asterisk/app.h" +#include "asterisk/utils.h" +#include "asterisk/options.h" + +static const char *app = "ExternalIVR"; + +static const char *synopsis = "Interfaces with an external IVR application"; + +static const char *descrip = +" ExternalIVR(command[|arg[|arg...]]): Forks a process to run the supplied command,\n" +"and starts a generator on the channel. The generator's play list is\n" +"controlled by the external application, which can add and clear entries\n" +"via simple commands issued over its stdout. The external application\n" +"will receive all DTMF events received on the channel, and notification\n" +"if the channel is hung up. The application will not be forcibly terminated\n" +"when the channel is hung up.\n" +"See doc/externalivr.txt for a protocol specification.\n"; + +/* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */ +#define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__) + +struct playlist_entry { + AST_LIST_ENTRY(playlist_entry) list; + char filename[1]; +}; + +struct ivr_localuser { + struct ast_channel *chan; + AST_LIST_HEAD(playlist, playlist_entry) playlist; + AST_LIST_HEAD(finishlist, playlist_entry) finishlist; + int abort_current_sound; + int playing_silence; + int option_autoclear; +}; + + +struct gen_state { + struct ivr_localuser *u; + struct ast_filestream *stream; + struct playlist_entry *current; + int sample_queue; +}; + +static void send_child_event(FILE *handle, const char event, const char *data, + const struct ast_channel *chan) +{ + char tmp[256]; + + if (!data) { + snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL)); + } else { + snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data); + } + + fprintf(handle, "%s\n", tmp); + ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp); +} + +static void *gen_alloc(struct ast_channel *chan, void *params) +{ + struct ivr_localuser *u = params; + struct gen_state *state; + + if (!(state = ast_calloc(1, sizeof(*state)))) + return NULL; + + state->u = u; + + return state; +} + +static void gen_closestream(struct gen_state *state) +{ + if (!state->stream) + return; + + ast_closestream(state->stream); + state->u->chan->stream = NULL; + state->stream = NULL; +} + +static void gen_release(struct ast_channel *chan, void *data) +{ + struct gen_state *state = data; + + gen_closestream(state); + free(data); +} + +/* caller has the playlist locked */ +static int gen_nextfile(struct gen_state *state) +{ + struct ivr_localuser *u = state->u; + char *file_to_stream; + + u->abort_current_sound = 0; + u->playing_silence = 0; + gen_closestream(state); + + while (!state->stream) { + state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list); + if (state->current) { + file_to_stream = state->current->filename; + } else { + file_to_stream = "silence/10"; + u->playing_silence = 1; + } + + if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) { + ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno)); + if (!u->playing_silence) { + continue; + } else { + break; + } + } + } + + return (!state->stream); +} + +static struct ast_frame *gen_readframe(struct gen_state *state) +{ + struct ast_frame *f = NULL; + struct ivr_localuser *u = state->u; + + if (u->abort_current_sound || + (u->playing_silence && AST_LIST_FIRST(&u->playlist))) { + gen_closestream(state); + AST_LIST_LOCK(&u->playlist); + gen_nextfile(state); + AST_LIST_UNLOCK(&u->playlist); + } + + if (!(state->stream && (f = ast_readframe(state->stream)))) { + if (state->current) { + AST_LIST_LOCK(&u->finishlist); + AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list); + AST_LIST_UNLOCK(&u->finishlist); + state->current = NULL; + } + if (!gen_nextfile(state)) + f = ast_readframe(state->stream); + } + + return f; +} + +static int gen_generate(struct ast_channel *chan, void *data, int len, int samples) +{ + struct gen_state *state = data; + struct ast_frame *f = NULL; + int res = 0; + + state->sample_queue += samples; + + while (state->sample_queue > 0) { + if (!(f = gen_readframe(state))) + return -1; + + res = ast_write(chan, f); + ast_frfree(f); + if (res < 0) { + ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno)); + return -1; + } + state->sample_queue -= f->samples; + } + + return res; +} + +static struct ast_generator gen = +{ + alloc: gen_alloc, + release: gen_release, + generate: gen_generate, +}; + +static struct playlist_entry *make_entry(const char *filename) +{ + struct playlist_entry *entry; + + if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */ + return NULL; + + strcpy(entry->filename, filename); + + return entry; +} + +static int app_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *lu; + struct playlist_entry *entry; + const char *args = data; + int child_stdin[2] = { 0,0 }; + int child_stdout[2] = { 0,0 }; + int child_stderr[2] = { 0,0 }; + int res = -1; + int test_available_fd = -1; + int gen_active = 0; + int pid; + char *argv[32]; + int argc = 1; + char *buf, *command; + FILE *child_commands = NULL; + FILE *child_errors = NULL; + FILE *child_events = NULL; + struct ivr_localuser foo = { + .playlist = AST_LIST_HEAD_INIT_VALUE, + .finishlist = AST_LIST_HEAD_INIT_VALUE, + }; + struct ivr_localuser *u = &foo; + sigset_t fullset, oldset; + + lu = ast_module_user_add(chan); + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + u->abort_current_sound = 0; + u->chan = chan; + + if (ast_strlen_zero(args)) { + ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n"); + ast_module_user_remove(lu); + return -1; + } + + buf = ast_strdupa(data); + + argc = ast_app_separate_args(buf, '|', argv, sizeof(argv) / sizeof(argv[0])); + + if (pipe(child_stdin)) { + ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno)); + goto exit; + } + + if (pipe(child_stdout)) { + ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno)); + goto exit; + } + + if (pipe(child_stderr)) { + ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno)); + goto exit; + } + + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } + + if (ast_activate_generator(chan, &gen, u) < 0) { + ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n"); + goto exit; + } else + gen_active = 1; + + pid = fork(); + if (pid < 0) { + ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno)); + goto exit; + } + + if (!pid) { + /* child process */ + int i; + + signal(SIGPIPE, SIG_DFL); + pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); + + if (ast_opt_high_priority) + ast_set_priority(0); + + dup2(child_stdin[0], STDIN_FILENO); + dup2(child_stdout[1], STDOUT_FILENO); + dup2(child_stderr[1], STDERR_FILENO); + for (i = STDERR_FILENO + 1; i < 1024; i++) + close(i); + execv(argv[0], argv); + fprintf(stderr, "Failed to execute '%s': %s\n", argv[0], strerror(errno)); + _exit(1); + } else { + /* parent process */ + int child_events_fd = child_stdin[1]; + int child_commands_fd = child_stdout[0]; + int child_errors_fd = child_stderr[0]; + struct ast_frame *f; + int ms; + int exception; + int ready_fd; + int waitfds[2] = { child_errors_fd, child_commands_fd }; + struct ast_channel *rchan; + + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + + close(child_stdin[0]); + child_stdin[0] = 0; + close(child_stdout[1]); + child_stdout[1] = 0; + close(child_stderr[1]); + child_stderr[1] = 0; + + if (!(child_events = fdopen(child_events_fd, "w"))) { + ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n"); + goto exit; + } + + if (!(child_commands = fdopen(child_commands_fd, "r"))) { + ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n"); + goto exit; + } + + if (!(child_errors = fdopen(child_errors_fd, "r"))) { + ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n"); + goto exit; + } + + test_available_fd = open("/dev/null", O_RDONLY); + + setvbuf(child_events, NULL, _IONBF, 0); + setvbuf(child_commands, NULL, _IONBF, 0); + setvbuf(child_errors, NULL, _IONBF, 0); + + res = 0; + + while (1) { + if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) { + ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n"); + res = -1; + break; + } + + if (ast_check_hangup(chan)) { + ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n"); + send_child_event(child_events, 'H', NULL, chan); + res = -1; + break; + } + + ready_fd = 0; + ms = 100; + errno = 0; + exception = 0; + + rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms); + + if (!AST_LIST_EMPTY(&u->finishlist)) { + AST_LIST_LOCK(&u->finishlist); + while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) { + send_child_event(child_events, 'F', entry->filename, chan); + free(entry); + } + AST_LIST_UNLOCK(&u->finishlist); + } + + if (rchan) { + /* the channel has something */ + f = ast_read(chan); + if (!f) { + ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n"); + send_child_event(child_events, 'H', NULL, chan); + res = -1; + break; + } + + if (f->frametype == AST_FRAME_DTMF) { + send_child_event(child_events, f->subclass, NULL, chan); + if (u->option_autoclear) { + if (!u->abort_current_sound && !u->playing_silence) + send_child_event(child_events, 'T', NULL, chan); + AST_LIST_LOCK(&u->playlist); + while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) { + send_child_event(child_events, 'D', entry->filename, chan); + free(entry); + } + if (!u->playing_silence) + u->abort_current_sound = 1; + AST_LIST_UNLOCK(&u->playlist); + } + } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) { + ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n"); + send_child_event(child_events, 'H', NULL, chan); + ast_frfree(f); + res = -1; + break; + } + ast_frfree(f); + } else if (ready_fd == child_commands_fd) { + char input[1024]; + + if (exception || feof(child_commands)) { + ast_chan_log(LOG_WARNING, chan, "Child process went away\n"); + res = -1; + break; + } + + if (!fgets(input, sizeof(input), child_commands)) + continue; + + command = ast_strip(input); + + ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input); + + if (strlen(input) < 4) + continue; + + if (input[0] == 'S') { + if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) { + ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]); + send_child_event(child_events, 'Z', NULL, chan); + strcpy(&input[2], "exception"); + } + if (!u->abort_current_sound && !u->playing_silence) + send_child_event(child_events, 'T', NULL, chan); + AST_LIST_LOCK(&u->playlist); + while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) { + send_child_event(child_events, 'D', entry->filename, chan); + free(entry); + } + if (!u->playing_silence) + u->abort_current_sound = 1; + entry = make_entry(&input[2]); + if (entry) + AST_LIST_INSERT_TAIL(&u->playlist, entry, list); + AST_LIST_UNLOCK(&u->playlist); + } else if (input[0] == 'A') { + if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) { + ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]); + send_child_event(child_events, 'Z', NULL, chan); + strcpy(&input[2], "exception"); + } + entry = make_entry(&input[2]); + if (entry) { + AST_LIST_LOCK(&u->playlist); + AST_LIST_INSERT_TAIL(&u->playlist, entry, list); + AST_LIST_UNLOCK(&u->playlist); + } + } else if (input[0] == 'H') { + ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]); + send_child_event(child_events, 'H', NULL, chan); + break; + } else if (input[0] == 'O') { + if (!strcasecmp(&input[2], "autoclear")) + u->option_autoclear = 1; + else if (!strcasecmp(&input[2], "noautoclear")) + u->option_autoclear = 0; + else + ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]); + } + } else if (ready_fd == child_errors_fd) { + char input[1024]; + + if (exception || (dup2(child_commands_fd, test_available_fd) == -1) || feof(child_errors)) { + ast_chan_log(LOG_WARNING, chan, "Child process went away\n"); + res = -1; + break; + } + + if (fgets(input, sizeof(input), child_errors)) { + command = ast_strip(input); + ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command); + } + } else if ((ready_fd < 0) && ms) { + if (errno == 0 || errno == EINTR) + continue; + + ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno)); + break; + } + } + } + + exit: + if (gen_active) + ast_deactivate_generator(chan); + + if (child_events) + fclose(child_events); + + if (child_commands) + fclose(child_commands); + + if (child_errors) + fclose(child_errors); + + if (test_available_fd > -1) { + close(test_available_fd); + } + + if (child_stdin[0]) + close(child_stdin[0]); + + if (child_stdin[1]) + close(child_stdin[1]); + + if (child_stdout[0]) + close(child_stdout[0]); + + if (child_stdout[1]) + close(child_stdout[1]); + + if (child_stderr[0]) + close(child_stderr[0]); + + if (child_stderr[1]) + close(child_stderr[1]); + + while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) + free(entry); + + ast_module_user_remove(lu); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, app_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application"); diff --git a/asterisk/apps/app_festival.c b/asterisk/apps/app_festival.c new file mode 100644 index 00000000..7cf05315 --- /dev/null +++ b/asterisk/apps/app_festival.c @@ -0,0 +1,553 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2002, Christos Ricudis + * + * Christos Ricudis + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Connect to festival + * + * \author Christos Ricudis + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 65853 $") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/md5.h" +#include "asterisk/config.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/options.h" + +#define FESTIVAL_CONFIG "festival.conf" + +static char *app = "Festival"; + +static char *synopsis = "Say text to the user"; + +static char *descrip = +" Festival(text[|intkeys]): Connect to Festival, send the argument, get back the waveform," +"play it to the user, allowing any given interrupt keys to immediately terminate and return\n" +"the value, or 'any' to allow any number back (useful in dialplan)\n"; + + +static char *socket_receive_file_to_buff(int fd,int *size) +{ + /* Receive file (probably a waveform file) from socket using */ + /* Festival key stuff technique, but long winded I know, sorry */ + /* but will receive any file without closeing the stream or */ + /* using OOB data */ + static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */ + char *buff; + int bufflen; + int n,k,i; + char c; + + bufflen = 1024; + if (!(buff = ast_malloc(bufflen))) + { + /* TODO: Handle memory allocation failure */ + } + *size=0; + + for (k=0; file_stuff_key[k] != '\0';) + { + n = read(fd,&c,1); + if (n==0) break; /* hit stream eof before end of file */ + if ((*size)+k+1 >= bufflen) + { /* +1 so you can add a NULL if you want */ + bufflen += bufflen/4; + if (!(buff = ast_realloc(buff, bufflen))) + { + /* TODO: Handle memory allocation failure */ + } + } + if (file_stuff_key[k] == c) + k++; + else if ((c == 'X') && (file_stuff_key[k+1] == '\0')) + { /* It looked like the key but wasn't */ + for (i=0; i < k; i++,(*size)++) + buff[*size] = file_stuff_key[i]; + k=0; + /* omit the stuffed 'X' */ + } + else + { + for (i=0; i < k; i++,(*size)++) + buff[*size] = file_stuff_key[i]; + k=0; + buff[*size] = c; + (*size)++; + } + + } + + return buff; +} + +static int send_waveform_to_fd(char *waveform, int length, int fd) { + + int res; + int x; +#ifdef __PPC__ + char c; +#endif + sigset_t fullset, oldset; + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + res = fork(); + if (res < 0) + ast_log(LOG_WARNING, "Fork failed\n"); + if (res) { + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + return res; + } + for (x=0;x<256;x++) { + if (x != fd) + close(x); + } + if (ast_opt_high_priority) + ast_set_priority(0); + signal(SIGPIPE, SIG_DFL); + pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); +/*IAS */ +#ifdef __PPC__ + for( x=0; x_state != AST_STATE_UP) + ast_answer(chan); + ast_stopstream(chan); + ast_indicate(chan, -1); + + owriteformat = chan->writeformat; + res = ast_set_write_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); + return -1; + } + + res=send_waveform_to_fd(waveform,length,fds[1]); + if (res >= 0) { + pid = res; + /* Order is important -- there's almost always going to be mp3... we want to prioritize the + user */ + for (;;) { + ms = 1000; + res = ast_waitfor(chan, ms); + if (res < 1) { + res = -1; + break; + } + f = ast_read(chan); + if (!f) { + ast_log(LOG_WARNING, "Null frame == hangup() detected\n"); + res = -1; + break; + } + if (f->frametype == AST_FRAME_DTMF) { + ast_log(LOG_DEBUG, "User pressed a key\n"); + if (intkeys && strchr(intkeys, f->subclass)) { + res = f->subclass; + ast_frfree(f); + break; + } + } + if (f->frametype == AST_FRAME_VOICE) { + /* Treat as a generator */ + needed = f->samples * 2; + if (needed > sizeof(myf.frdata)) { + ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n", + (int)sizeof(myf.frdata) / 2, needed/2); + needed = sizeof(myf.frdata); + } + res = read(fds[0], myf.frdata, needed); + if (res > 0) { + myf.f.frametype = AST_FRAME_VOICE; + myf.f.subclass = AST_FORMAT_SLINEAR; + myf.f.datalen = res; + myf.f.samples = res / 2; + myf.f.offset = AST_FRIENDLY_OFFSET; + myf.f.src = __PRETTY_FUNCTION__; + myf.f.data = myf.frdata; + if (ast_write(chan, &myf.f) < 0) { + res = -1; + ast_frfree(f); + break; + } + if (res < needed) { /* last frame */ + ast_log(LOG_DEBUG, "Last frame\n"); + res=0; + ast_frfree(f); + break; + } + } else { + ast_log(LOG_DEBUG, "No more waveform\n"); + res = 0; + } + } + ast_frfree(f); + } + } + close(fds[0]); + close(fds[1]); + +/* if (pid > -1) */ +/* kill(pid, SIGKILL); */ + if (!res && owriteformat) + ast_set_write_format(chan, owriteformat); + return res; +} + +#define MAXLEN 180 +#define MAXFESTLEN 2048 + + + + +static int festival_exec(struct ast_channel *chan, void *vdata) +{ + int usecache; + int res=0; + struct ast_module_user *u; + struct sockaddr_in serv_addr; + struct hostent *serverhost; + struct ast_hostent ahp; + int fd; + FILE *fs; + const char *host; + const char *cachedir; + const char *temp; + const char *festivalcommand; + int port=1314; + int n; + char ack[4]; + char *waveform; + int filesize; + int wave; + char bigstring[MAXFESTLEN]; + int i; + struct MD5Context md5ctx; + unsigned char MD5Res[16]; + char MD5Hex[33] = ""; + char koko[4] = ""; + char cachefile[MAXFESTLEN]=""; + int readcache=0; + int writecache=0; + int strln; + int fdesc = -1; + char buffer[16384]; + int seekpos = 0; + char *data; + char *intstr; + struct ast_config *cfg; + char *newfestivalcommand; + + if (ast_strlen_zero(vdata)) { + ast_log(LOG_WARNING, "festival requires an argument (text)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + cfg = ast_config_load(FESTIVAL_CONFIG); + if (!cfg) { + ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); + ast_module_user_remove(u); + return -1; + } + if (!(host = ast_variable_retrieve(cfg, "general", "host"))) { + host = "localhost"; + } + if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) { + port = 1314; + } else { + port = atoi(temp); + } + if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) { + usecache=0; + } else { + usecache = ast_true(temp); + } + if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) { + cachedir = "/tmp/"; + } + if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) { + festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n"; + } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */ + int i, j; + newfestivalcommand = alloca(strlen(festivalcommand) + 1); + + for (i = 0, j = 0; i < strlen(festivalcommand); i++) { + if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') { + newfestivalcommand[j++] = '\n'; + i++; + } else if (festivalcommand[i] == '\\') { + newfestivalcommand[j++] = festivalcommand[i + 1]; + i++; + } else + newfestivalcommand[j++] = festivalcommand[i]; + } + newfestivalcommand[j] = '\0'; + festivalcommand = newfestivalcommand; + } + + data = ast_strdupa(vdata); + + intstr = strchr(data, '|'); + if (intstr) { + *intstr = '\0'; + intstr++; + if (!strcasecmp(intstr, "any")) + intstr = AST_DIGIT_ANY; + } + + ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data); + /* Connect to local festival server */ + + fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (fd < 0) { + ast_log(LOG_WARNING,"festival_client: can't get socket\n"); + ast_config_destroy(cfg); + ast_module_user_remove(u); + return -1; + } + memset(&serv_addr, 0, sizeof(serv_addr)); + if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) { + /* its a name rather than an ipnum */ + serverhost = ast_gethostbyname(host, &ahp); + if (serverhost == (struct hostent *)0) { + ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n"); + ast_config_destroy(cfg); + ast_module_user_remove(u); + return -1; + } + memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length); + } + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); + + if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { + ast_log(LOG_WARNING,"festival_client: connect to server failed\n"); + ast_config_destroy(cfg); + ast_module_user_remove(u); + return -1; + } + + /* Compute MD5 sum of string */ + MD5Init(&md5ctx); + MD5Update(&md5ctx,(unsigned char const *)data,strlen(data)); + MD5Final(MD5Res,&md5ctx); + MD5Hex[0] = '\0'; + + /* Convert to HEX and look if there is any matching file in the cache + directory */ + for (i=0;i<16;i++) { + snprintf(koko, sizeof(koko), "%X",MD5Res[i]); + strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1); + } + readcache=0; + writecache=0; + if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) { + snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex); + fdesc=open(cachefile,O_RDWR); + if (fdesc==-1) { + fdesc=open(cachefile,O_CREAT|O_RDWR,0777); + if (fdesc!=-1) { + writecache=1; + strln=strlen((char *)data); + ast_log(LOG_DEBUG,"line length : %d\n",strln); + write(fdesc,&strln,sizeof(int)); + write(fdesc,data,strln); + seekpos=lseek(fdesc,0,SEEK_CUR); + ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos); + } + } else { + read(fdesc,&strln,sizeof(int)); + ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data)); + if (strlen((char *)data)==strln) { + ast_log(LOG_DEBUG,"Size OK\n"); + read(fdesc,&bigstring,strln); + bigstring[strln] = 0; + if (strcmp(bigstring,data)==0) { + readcache=1; + } else { + ast_log(LOG_WARNING,"Strings do not match\n"); + } + } else { + ast_log(LOG_WARNING,"Size mismatch\n"); + } + } + } + + if (readcache==1) { + close(fd); + fd=fdesc; + ast_log(LOG_DEBUG,"Reading from cache...\n"); + } else { + ast_log(LOG_DEBUG,"Passing text to festival...\n"); + fs=fdopen(dup(fd),"wb"); + fprintf(fs,festivalcommand,(char *)data); + fflush(fs); + fclose(fs); + } + + /* Write to cache and then pass it down */ + if (writecache==1) { + ast_log(LOG_DEBUG,"Writing result to cache...\n"); + while ((strln=read(fd,buffer,16384))!=0) { + write(fdesc,buffer,strln); + } + close(fd); + close(fdesc); + fd=open(cachefile,O_RDWR); + lseek(fd,seekpos,SEEK_SET); + } + + ast_log(LOG_DEBUG,"Passing data to channel...\n"); + + /* Read back info from server */ + /* This assumes only one waveform will come back, also LP is unlikely */ + wave = 0; + do { + int read_data; + for (n=0; n < 3; ) + { + read_data = read(fd,ack+n,3-n); + /* this avoids falling in infinite loop + * in case that festival server goes down + * */ + if ( read_data == -1 ) + { + ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n"); + close(fd); + ast_config_destroy(cfg); + ast_module_user_remove(u); + return -1; + } + n += read_data; + } + ack[3] = '\0'; + if (strcmp(ack,"WV\n") == 0) { /* receive a waveform */ + ast_log(LOG_DEBUG,"Festival WV command\n"); + waveform = socket_receive_file_to_buff(fd,&filesize); + res = send_waveform_to_channel(chan,waveform,filesize, intstr); + free(waveform); + break; + } + else if (strcmp(ack,"LP\n") == 0) { /* receive an s-expr */ + ast_log(LOG_DEBUG,"Festival LP command\n"); + waveform = socket_receive_file_to_buff(fd,&filesize); + waveform[filesize]='\0'; + ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform); + free(waveform); + } else if (strcmp(ack,"ER\n") == 0) { /* server got an error */ + ast_log(LOG_WARNING,"Festival returned ER\n"); + res=-1; + break; + } + } while (strcmp(ack,"OK\n") != 0); + close(fd); + ast_config_destroy(cfg); + ast_module_user_remove(u); + return res; + +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG); + if (!cfg) { + ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); + return AST_MODULE_LOAD_DECLINE; + } + ast_config_destroy(cfg); + return ast_register_application(app, festival_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface"); diff --git a/asterisk/apps/app_flash.c b/asterisk/apps/app_flash.c new file mode 100644 index 00000000..d00542eb --- /dev/null +++ b/asterisk/apps/app_flash.c @@ -0,0 +1,136 @@ +/* + * 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 App to flash a DAHDI trunk + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + dahdi + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/image.h" +#include "asterisk/options.h" + +#include "asterisk/dahdi_compat.h" + +static char *app = "Flash"; + +static char *dahdi_synopsis = "Flashes a DAHDI trunk"; + +static char *dahdi_descrip = +"Performs a flash on a DAHDI trunk. This can be used\n" +"to access features provided on an incoming analogue circuit\n" +"such as conference and call waiting. Use with SendDTMF() to\n" +"perform external transfers\n"; + +static char *zap_synopsis = "Flashes a Zap trunk"; + +static char *zap_descrip = +"Performs a flash on a Zap trunk. This can be used\n" +"to access features provided on an incoming analogue circuit\n" +"such as conference and call waiting. Use with SendDTMF() to\n" +"perform external transfers\n"; + +static inline int zt_wait_event(int fd) +{ + /* Avoid the silly zt_waitevent which ignores a bunch of events */ + 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; +} + +static int flash_exec(struct ast_channel *chan, void *data) +{ + int res = -1; + int x; + struct ast_module_user *u; + struct dahdi_params ztp; + u = ast_module_user_add(chan); + if (!strcasecmp(chan->tech->type, dahdi_chan_name)) { + memset(&ztp, 0, sizeof(ztp)); + res = ioctl(chan->fds[0], DAHDI_GET_PARAMS, &ztp); + if (!res) { + if (ztp.sigtype & __DAHDI_SIG_FXS) { + x = DAHDI_FLASH; + res = ioctl(chan->fds[0], DAHDI_HOOK, &x); + if (!res || (errno == EINPROGRESS)) { + if (res) { + /* Wait for the event to finish */ + zt_wait_event(chan->fds[0]); + } + res = ast_safe_sleep(chan, 1000); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Flashed channel %s\n", chan->name); + } else + ast_log(LOG_WARNING, "Unable to flash channel %s: %s\n", chan->name, strerror(errno)); + } else + ast_log(LOG_WARNING, "%s is not an FXO Channel\n", chan->name); + } else + ast_log(LOG_WARNING, "Unable to get parameters of %s: %s\n", chan->name, strerror(errno)); + } else + ast_log(LOG_WARNING, "%s is not a DAHDI channel\n", chan->name); + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + if (dahdi_chan_mode == CHAN_ZAP_MODE) { + return ast_register_application(app, flash_exec, zap_synopsis, zap_descrip); + } else { + return ast_register_application(app, flash_exec, dahdi_synopsis, dahdi_descrip); + } +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Flash channel application"); diff --git a/asterisk/apps/app_followme.c b/asterisk/apps/app_followme.c new file mode 100644 index 00000000..12d824c6 --- /dev/null +++ b/asterisk/apps/app_followme.c @@ -0,0 +1,1113 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * A full-featured Find-Me/Follow-Me Application + * + * Copyright (C) 2005-2006, BJ Weschke All Rights Reserved. + * + * BJ Weschke + * + * This code is released by the author with no restrictions on usage. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + */ + +/*! \file + * + * \brief Find-Me Follow-Me application + * + * \author BJ Weschke + * + * \arg See \ref Config_followme + * + * \ingroup applications + */ + +/*** MODULEINFO + chan_local + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 114611 $") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/options.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/say.h" +#include "asterisk/features.h" +#include "asterisk/musiconhold.h" +#include "asterisk/cli.h" +#include "asterisk/manager.h" +#include "asterisk/config.h" +#include "asterisk/monitor.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/astdb.h" +#include "asterisk/app.h" + +static char *app = "FollowMe"; +static char *synopsis = "Find-Me/Follow-Me application"; +static char *descrip = +" FollowMe(followmeid|options):\n" +"This application performs Find-Me/Follow-Me functionality for the caller\n" +"as defined in the profile matching the parameter in\n" +"followme.conf. If the specified profile doesn't exist in\n" +"followme.conf, execution will be returned to the dialplan and call\n" +"execution will continue at the next priority.\n\n" +" Options:\n" +" s - Playback the incoming status message prior to starting the follow-me step(s)\n" +" a - Record the caller's name so it can be announced to the callee on each step\n" +" n - Playback the unreachable status message if we've run out of steps to reach the\n" +" or the callee has elected not to be reachable.\n" +"Returns -1 on hangup\n"; + +/*! \brief Number structure */ +struct number { + char number[512]; /*!< Phone Number(s) and/or Extension(s) */ + long timeout; /*!< Dial Timeout, if used. */ + char language[MAX_LANGUAGE]; /*!< The language to be used on this dial, if used. */ + int order; /*!< The order to dial in */ + AST_LIST_ENTRY(number) entry; /*!< Next Number record */ +}; + +/*! \brief Data structure for followme scripts */ +struct call_followme { + ast_mutex_t lock; + char name[AST_MAX_EXTENSION]; /*!< Name - FollowMeID */ + char moh[AST_MAX_CONTEXT]; /*!< Music On Hold Class to be used */ + char context[AST_MAX_CONTEXT]; /*!< Context to dial from */ + unsigned int active; /*!< Profile is active (1), or disabled (0). */ + char takecall[20]; /*!< Digit mapping to take a call */ + char nextindp[20]; /*!< Digit mapping to decline a call */ + char callfromprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char norecordingprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char optionsprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char plsholdprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char statusprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char sorryprompt[PATH_MAX]; /*!< Sound prompt name and path */ + + AST_LIST_HEAD_NOLOCK(numbers, number) numbers; /*!< Head of the list of follow-me numbers */ + AST_LIST_HEAD_NOLOCK(blnumbers, number) blnumbers; /*!< Head of the list of black-listed numbers */ + AST_LIST_HEAD_NOLOCK(wlnumbers, number) wlnumbers; /*!< Head of the list of white-listed numbers */ + AST_LIST_ENTRY(call_followme) entry; /*!< Next Follow-Me record */ +}; + +struct fm_args { + struct ast_channel *chan; + char *mohclass; + AST_LIST_HEAD_NOLOCK(cnumbers, number) cnumbers; + int status; + char context[AST_MAX_CONTEXT]; + char namerecloc[AST_MAX_CONTEXT]; + struct ast_channel *outbound; + char takecall[20]; /*!< Digit mapping to take a call */ + char nextindp[20]; /*!< Digit mapping to decline a call */ + char callfromprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char norecordingprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char optionsprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char plsholdprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char statusprompt[PATH_MAX]; /*!< Sound prompt name and path */ + char sorryprompt[PATH_MAX]; /*!< Sound prompt name and path */ + struct ast_flags followmeflags; +}; + +struct findme_user { + struct ast_channel *ochan; + int state; + char dialarg[256]; + char yn[10]; + int ynidx; + long digts; + int cleared; + AST_LIST_ENTRY(findme_user) entry; +}; + +enum { + FOLLOWMEFLAG_STATUSMSG = (1 << 0), + FOLLOWMEFLAG_RECORDNAME = (1 << 1), + FOLLOWMEFLAG_UNREACHABLEMSG = (1 << 2) +}; + +AST_APP_OPTIONS(followme_opts, { + AST_APP_OPTION('s', FOLLOWMEFLAG_STATUSMSG ), + AST_APP_OPTION('a', FOLLOWMEFLAG_RECORDNAME ), + AST_APP_OPTION('n', FOLLOWMEFLAG_UNREACHABLEMSG ), +}); + +static int ynlongest = 0; +static time_t start_time, answer_time, end_time; + +static const char *featuredigittostr; +static int featuredigittimeout = 5000; /*!< Feature Digit Timeout */ +static const char *defaultmoh = "default"; /*!< Default Music-On-Hold Class */ + +static char takecall[20] = "1", nextindp[20] = "2"; +static char callfromprompt[PATH_MAX] = "followme/call-from"; +static char norecordingprompt[PATH_MAX] = "followme/no-recording"; +static char optionsprompt[PATH_MAX] = "followme/options"; +static char plsholdprompt[PATH_MAX] = "followme/pls-hold-while-try"; +static char statusprompt[PATH_MAX] = "followme/status"; +static char sorryprompt[PATH_MAX] = "followme/sorry"; + + +static AST_LIST_HEAD_STATIC(followmes, call_followme); +AST_LIST_HEAD_NOLOCK(findme_user_listptr, findme_user); + +static void free_numbers(struct call_followme *f) +{ + /* Free numbers attached to the profile */ + struct number *prev; + + while ((prev = AST_LIST_REMOVE_HEAD(&f->numbers, entry))) + /* Free the number */ + free(prev); + AST_LIST_HEAD_INIT_NOLOCK(&f->numbers); + + while ((prev = AST_LIST_REMOVE_HEAD(&f->blnumbers, entry))) + /* Free the blacklisted number */ + free(prev); + AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers); + + while ((prev = AST_LIST_REMOVE_HEAD(&f->wlnumbers, entry))) + /* Free the whitelisted number */ + free(prev); + AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers); + +} + + +/*! \brief Allocate and initialize followme profile */ +static struct call_followme *alloc_profile(const char *fmname) +{ + struct call_followme *f; + + if (!(f = ast_calloc(1, sizeof(*f)))) + return NULL; + + ast_mutex_init(&f->lock); + ast_copy_string(f->name, fmname, sizeof(f->name)); + f->moh[0] = '\0'; + f->context[0] = '\0'; + ast_copy_string(f->takecall, takecall, sizeof(f->takecall)); + ast_copy_string(f->nextindp, nextindp, sizeof(f->nextindp)); + ast_copy_string(f->callfromprompt, callfromprompt, sizeof(f->callfromprompt)); + ast_copy_string(f->norecordingprompt, norecordingprompt, sizeof(f->norecordingprompt)); + ast_copy_string(f->optionsprompt, optionsprompt, sizeof(f->optionsprompt)); + ast_copy_string(f->plsholdprompt, plsholdprompt, sizeof(f->plsholdprompt)); + ast_copy_string(f->statusprompt, statusprompt, sizeof(f->statusprompt)); + ast_copy_string(f->sorryprompt, sorryprompt, sizeof(f->sorryprompt)); + AST_LIST_HEAD_INIT_NOLOCK(&f->numbers); + AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers); + AST_LIST_HEAD_INIT_NOLOCK(&f->wlnumbers); + return f; +} + +static void init_profile(struct call_followme *f) +{ + f->active = 1; + ast_copy_string(f->moh, defaultmoh, sizeof(f->moh)); +} + + + +/*! \brief Set parameter in profile from configuration file */ +static void profile_set_param(struct call_followme *f, const char *param, const char *val, int linenum, int failunknown) +{ + + if (!strcasecmp(param, "musicclass") || !strcasecmp(param, "musiconhold") || !strcasecmp(param, "music")) + ast_copy_string(f->moh, val, sizeof(f->moh)); + else if (!strcasecmp(param, "context")) + ast_copy_string(f->context, val, sizeof(f->context)); + else if (!strcasecmp(param, "takecall")) + ast_copy_string(f->takecall, val, sizeof(f->takecall)); + else if (!strcasecmp(param, "declinecall")) + ast_copy_string(f->nextindp, val, sizeof(f->nextindp)); + else if (!strcasecmp(param, "call-from-prompt")) + ast_copy_string(f->callfromprompt, val, sizeof(f->callfromprompt)); + else if (!strcasecmp(param, "followme-norecording-prompt")) + ast_copy_string(f->norecordingprompt, val, sizeof(f->norecordingprompt)); + else if (!strcasecmp(param, "followme-options-prompt")) + ast_copy_string(f->optionsprompt, val, sizeof(f->optionsprompt)); + else if (!strcasecmp(param, "followme-pls-hold-prompt")) + ast_copy_string(f->plsholdprompt, val, sizeof(f->plsholdprompt)); + else if (!strcasecmp(param, "followme-status-prompt")) + ast_copy_string(f->statusprompt, val, sizeof(f->statusprompt)); + else if (!strcasecmp(param, "followme-sorry-prompt")) + ast_copy_string(f->sorryprompt, val, sizeof(f->sorryprompt)); + else if (failunknown) { + if (linenum >= 0) + ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s at line %d of followme.conf\n", f->name, param, linenum); + else + ast_log(LOG_WARNING, "Unknown keyword in profile '%s': %s\n", f->name, param); + } +} + +/*! \brief Add a new number */ +static struct number *create_followme_number(char *number, char *language, int timeout, int numorder) +{ + struct number *cur; + char *tmp; + + + if (!(cur = ast_calloc(1, sizeof(*cur)))) + return NULL; + + cur->timeout = timeout; + if ((tmp = strchr(number, ','))) + *tmp = '\0'; + ast_copy_string(cur->number, number, sizeof(cur->number)); + ast_copy_string(cur->language, language, sizeof(cur->language)); + cur->order = numorder; + if (option_debug) + ast_log(LOG_DEBUG, "Created a number, %s, order of , %d, with a timeout of %ld.\n", cur->number, cur->order, cur->timeout); + + return cur; +} + +/*! \brief Reload followme application module */ +static int reload_followme(void) +{ + struct call_followme *f; + struct ast_config *cfg; + char *cat = NULL, *tmp; + struct ast_variable *var; + struct number *cur, *nm; + int new, idx; + char numberstr[90]; + int timeout; + char *timeoutstr; + int numorder; + const char *takecallstr; + const char *declinecallstr; + const char *tmpstr; + + cfg = ast_config_load("followme.conf"); + if (!cfg) { + ast_log(LOG_WARNING, "No follow me config file (followme.conf), so no follow me\n"); + return 0; + } + + AST_LIST_LOCK(&followmes); + + /* Reset Global Var Values */ + featuredigittimeout = 5000; + + /* Mark all profiles as inactive for the moment */ + AST_LIST_TRAVERSE(&followmes, f, entry) { + f->active = 0; + } + featuredigittostr = ast_variable_retrieve(cfg, "general", "featuredigittimeout"); + + if (!ast_strlen_zero(featuredigittostr)) { + if (!sscanf(featuredigittostr, "%d", &featuredigittimeout)) + featuredigittimeout = 5000; + } + + takecallstr = ast_variable_retrieve(cfg, "general", "takecall"); + if (!ast_strlen_zero(takecallstr)) + ast_copy_string(takecall, takecallstr, sizeof(takecall)); + + declinecallstr = ast_variable_retrieve(cfg, "general", "declinecall"); + if (!ast_strlen_zero(declinecallstr)) + ast_copy_string(nextindp, declinecallstr, sizeof(nextindp)); + + tmpstr = ast_variable_retrieve(cfg, "general", "call-from-prompt"); + if (!ast_strlen_zero(tmpstr)) + ast_copy_string(callfromprompt, tmpstr, sizeof(callfromprompt)); + + tmpstr = ast_variable_retrieve(cfg, "general", "norecording-prompt"); + if (!ast_strlen_zero(tmpstr)) + ast_copy_string(norecordingprompt, tmpstr, sizeof(norecordingprompt)); + + tmpstr = ast_variable_retrieve(cfg, "general", "options-prompt"); + if (!ast_strlen_zero(tmpstr)) + ast_copy_string(optionsprompt, tmpstr, sizeof(optionsprompt)); + + tmpstr = ast_variable_retrieve(cfg, "general", "pls-hold-prompt"); + if (!ast_strlen_zero(tmpstr)) + ast_copy_string(plsholdprompt, tmpstr, sizeof(plsholdprompt)); + + tmpstr = ast_variable_retrieve(cfg, "general", "status-prompt"); + if (!ast_strlen_zero(tmpstr)) + ast_copy_string(statusprompt, tmpstr, sizeof(statusprompt)); + + tmpstr = ast_variable_retrieve(cfg, "general", "sorry-prompt"); + if (!ast_strlen_zero(tmpstr)) + ast_copy_string(sorryprompt, tmpstr, sizeof(sorryprompt)); + + /* Chug through config file */ + while ((cat = ast_category_browse(cfg, cat))) { + if (!strcasecmp(cat, "general")) + continue; + /* Define a new profile */ + /* Look for an existing one */ + AST_LIST_TRAVERSE(&followmes, f, entry) { + if (!strcasecmp(f->name, cat)) + break; + } + if (option_debug) + ast_log(LOG_DEBUG, "New profile %s.\n", cat); + if (!f) { + /* Make one then */ + f = alloc_profile(cat); + new = 1; + } else + new = 0; + + if (f) { + if (!new) + ast_mutex_lock(&f->lock); + /* Re-initialize the profile */ + init_profile(f); + free_numbers(f); + var = ast_variable_browse(cfg, cat); + while(var) { + if (!strcasecmp(var->name, "number")) { + /* Add a new number */ + ast_copy_string(numberstr, var->value, sizeof(numberstr)); + if ((tmp = strchr(numberstr, ','))) { + *tmp = '\0'; + tmp++; + timeoutstr = ast_strdupa(tmp); + if ((tmp = strchr(timeoutstr, ','))) { + *tmp = '\0'; + tmp++; + numorder = atoi(tmp); + if (numorder < 0) + numorder = 0; + } else + numorder = 0; + timeout = atoi(timeoutstr); + if (timeout < 0) + timeout = 25; + } else { + timeout = 25; + numorder = 0; + } + + if (!numorder) { + idx = 1; + AST_LIST_TRAVERSE(&f->numbers, nm, entry) + idx++; + numorder = idx; + } + cur = create_followme_number(numberstr, "", timeout, numorder); + AST_LIST_INSERT_TAIL(&f->numbers, cur, entry); + } else { + profile_set_param(f, var->name, var->value, var->lineno, 1); + if (option_debug > 1) + ast_log(LOG_DEBUG, "Logging parameter %s with value %s from lineno %d\n", var->name, var->value, var->lineno); + } + var = var->next; + } /* End while(var) loop */ + + if (!new) + ast_mutex_unlock(&f->lock); + else + AST_LIST_INSERT_HEAD(&followmes, f, entry); + } + } + ast_config_destroy(cfg); + + AST_LIST_UNLOCK(&followmes); + + return 1; +} + +static void clear_caller(struct findme_user *tmpuser) +{ + struct ast_channel *outbound; + + if (tmpuser && tmpuser->ochan && tmpuser->state >= 0) { + outbound = tmpuser->ochan; + if (!outbound->cdr) { + outbound->cdr = ast_cdr_alloc(); + if (outbound->cdr) + ast_cdr_init(outbound->cdr, outbound); + } + if (outbound->cdr) { + char tmp[256]; + + snprintf(tmp, sizeof(tmp), "%s/%s", "Local", tmpuser->dialarg); + ast_cdr_setapp(outbound->cdr, "FollowMe", tmp); + ast_cdr_update(outbound); + ast_cdr_start(outbound->cdr); + ast_cdr_end(outbound->cdr); + /* If the cause wasn't handled properly */ + if (ast_cdr_disposition(outbound->cdr, outbound->hangupcause)) + ast_cdr_failed(outbound->cdr); + } else + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + ast_hangup(tmpuser->ochan); + } + +} + +static void clear_calling_tree(struct findme_user_listptr *findme_user_list) +{ + struct findme_user *tmpuser; + + AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) { + clear_caller(tmpuser); + tmpuser->cleared = 1; + } + +} + + + +static struct ast_channel *wait_for_winner(struct findme_user_listptr *findme_user_list, struct number *nm, struct ast_channel *caller, char *namerecloc, int *status, struct fm_args *tpargs) +{ + struct ast_channel *watchers[256]; + int pos; + struct ast_channel *winner; + struct ast_frame *f; + int ctstatus; + int dg; + struct findme_user *tmpuser; + int to = 0; + int livechannels = 0; + int tmpto; + long totalwait = 0, wtd, towas = 0; + char *callfromname; + char *pressbuttonname; + + /* ------------ wait_for_winner_channel start --------------- */ + + callfromname = ast_strdupa(tpargs->callfromprompt); + pressbuttonname = ast_strdupa(tpargs->optionsprompt); + + if (!AST_LIST_EMPTY(findme_user_list)) { + if (!caller) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Original caller hungup. Cleanup.\n"); + clear_calling_tree(findme_user_list); + return NULL; + } + ctstatus = 0; + totalwait = nm->timeout * 1000; + wtd = 0; + while (!ctstatus) { + to = 1000; + pos = 1; + livechannels = 0; + watchers[0] = caller; + + dg = 0; + winner = NULL; + AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) { + if (tmpuser->state >= 0 && tmpuser->ochan) { + if (tmpuser->state == 3) + tmpuser->digts += (towas - wtd); + if (tmpuser->digts && (tmpuser->digts > featuredigittimeout)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "We've been waiting for digits longer than we should have.\n"); + if (!ast_strlen_zero(namerecloc)) { + tmpuser->state = 1; + tmpuser->digts = 0; + if (!ast_streamfile(tmpuser->ochan, callfromname, tmpuser->ochan->language)) { + ast_sched_runq(tmpuser->ochan->sched); + } else { + ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname); + return NULL; + } + } else { + tmpuser->state = 2; + tmpuser->digts = 0; + if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, tmpuser->ochan->language)) + ast_sched_runq(tmpuser->ochan->sched); + else { + ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt); + return NULL; + } + } + } + if (tmpuser->ochan->stream) { + ast_sched_runq(tmpuser->ochan->sched); + tmpto = ast_sched_wait(tmpuser->ochan->sched); + if (tmpto > 0 && tmpto < to) + to = tmpto; + else if (tmpto < 0 && !tmpuser->ochan->timingfunc) { + ast_stopstream(tmpuser->ochan); + if (tmpuser->state == 1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Playback of the call-from file appears to be done.\n"); + if (!ast_streamfile(tmpuser->ochan, namerecloc, tmpuser->ochan->language)) { + tmpuser->state = 2; + } else { + ast_log(LOG_NOTICE, "Unable to playback %s. Maybe the caller didn't record their name?\n", namerecloc); + memset(tmpuser->yn, 0, sizeof(tmpuser->yn)); + tmpuser->ynidx = 0; + if (!ast_streamfile(tmpuser->ochan, pressbuttonname, tmpuser->ochan->language)) + tmpuser->state = 3; + else { + ast_log(LOG_WARNING, "Unable to playback %s.\n", pressbuttonname); + return NULL; + } + } + } else if (tmpuser->state == 2) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Playback of name file appears to be done.\n"); + memset(tmpuser->yn, 0, sizeof(tmpuser->yn)); + tmpuser->ynidx = 0; + if (!ast_streamfile(tmpuser->ochan, pressbuttonname, tmpuser->ochan->language)) { + tmpuser->state = 3; + + } else { + return NULL; + } + } else if (tmpuser->state == 3) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Playback of the next step file appears to be done.\n"); + tmpuser->digts = 0; + } + } + } + watchers[pos++] = tmpuser->ochan; + livechannels++; + } + } + + tmpto = to; + if (to < 0) { + to = 1000; + tmpto = 1000; + } + towas = to; + winner = ast_waitfor_n(watchers, pos, &to); + tmpto -= to; + totalwait -= tmpto; + wtd = to; + if (totalwait <= 0) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "We've hit our timeout for this step. Drop everyone and move on to the next one. %ld\n", totalwait); + clear_calling_tree(findme_user_list); + return NULL; + } + if (winner) { + /* Need to find out which channel this is */ + dg = 0; + while ((winner != watchers[dg]) && (dg < 256)) + dg++; + AST_LIST_TRAVERSE(findme_user_list, tmpuser, entry) + if (tmpuser->ochan == winner) + break; + f = ast_read(winner); + if (f) { + if (f->frametype == AST_FRAME_CONTROL) { + switch(f->subclass) { + case AST_CONTROL_HANGUP: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s received a hangup frame.\n", winner->name); + if (dg == 0) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "The calling channel hungup. Need to drop everyone else.\n"); + clear_calling_tree(findme_user_list); + ctstatus = -1; + } + break; + case AST_CONTROL_ANSWER: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", winner->name, caller->name); + /* If call has been answered, then the eventual hangup is likely to be normal hangup */ + winner->hangupcause = AST_CAUSE_NORMAL_CLEARING; + caller->hangupcause = AST_CAUSE_NORMAL_CLEARING; + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Starting playback of %s\n", callfromname); + if (dg > 0) { + if (!ast_strlen_zero(namerecloc)) { + if (!ast_streamfile(winner, callfromname, winner->language)) { + ast_sched_runq(winner->sched); + tmpuser->state = 1; + } else { + ast_log(LOG_WARNING, "Unable to playback %s.\n", callfromname); + ast_frfree(f); + return NULL; + } + } else { + tmpuser->state = 2; + if (!ast_streamfile(tmpuser->ochan, tpargs->norecordingprompt, tmpuser->ochan->language)) + ast_sched_runq(tmpuser->ochan->sched); + else { + ast_log(LOG_WARNING, "Unable to playback %s.\n", tpargs->norecordingprompt); + ast_frfree(f); + return NULL; + } + } + } + break; + case AST_CONTROL_BUSY: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", winner->name); + break; + case AST_CONTROL_CONGESTION: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", winner->name); + break; + case AST_CONTROL_RINGING: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", winner->name); + break; + case AST_CONTROL_PROGRESS: + if (option_verbose > 2) + ast_verbose ( VERBOSE_PREFIX_3 "%s is making progress passing it to %s\n", winner->name, caller->name); + break; + case AST_CONTROL_VIDUPDATE: + if (option_verbose > 2) + ast_verbose ( VERBOSE_PREFIX_3 "%s requested a video update, passing it to %s\n", winner->name, caller->name); + break; + case AST_CONTROL_SRCUPDATE: + if (option_verbose > 2) + ast_verbose ( VERBOSE_PREFIX_3 "%s requested a source update, passing it to %s\n", winner->name, caller->name); + break; + case AST_CONTROL_PROCEEDING: + if (option_verbose > 2) + ast_verbose ( VERBOSE_PREFIX_3 "%s is proceeding passing it to %s\n", winner->name,caller->name); + break; + case AST_CONTROL_HOLD: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Call on %s placed on hold\n", winner->name); + break; + case AST_CONTROL_UNHOLD: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Call on %s left from hold\n", winner->name); + break; + case AST_CONTROL_OFFHOOK: + case AST_CONTROL_FLASH: + /* Ignore going off hook and flash */ + break; + case -1: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s stopped sounds\n", winner->name); + break; + default: + if (option_debug) + ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass); + break; + } + } + if (tmpuser && tmpuser->state == 3 && f->frametype == AST_FRAME_DTMF) { + if (winner->stream) + ast_stopstream(winner); + tmpuser->digts = 0; + if (option_debug) + ast_log(LOG_DEBUG, "DTMF received: %c\n",(char) f->subclass); + tmpuser->yn[tmpuser->ynidx] = (char) f->subclass; + tmpuser->ynidx++; + if (option_debug) + ast_log(LOG_DEBUG, "DTMF string: %s\n", tmpuser->yn); + if (tmpuser->ynidx >= ynlongest) { + if (option_debug) + ast_log(LOG_DEBUG, "reached longest possible match - doing evals\n"); + if (!strcmp(tmpuser->yn, tpargs->takecall)) { + if (option_debug) + ast_log(LOG_DEBUG, "Match to take the call!\n"); + ast_frfree(f); + return tmpuser->ochan; + } + if (!strcmp(tmpuser->yn, tpargs->nextindp)) { + if (option_debug) + ast_log(LOG_DEBUG, "Next in dial plan step requested.\n"); + *status = 1; + ast_frfree(f); + return NULL; + } + + } + } + + ast_frfree(f); + } else { + if (winner) { + if (option_debug) + ast_log(LOG_DEBUG, "we didn't get a frame. hanging up. dg is %d\n",dg); + if (!dg) { + clear_calling_tree(findme_user_list); + return NULL; + } else { + tmpuser->state = -1; + ast_hangup(winner); + livechannels--; + if (option_debug) + ast_log(LOG_DEBUG, "live channels left %d\n", livechannels); + if (!livechannels) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "no live channels left. exiting.\n"); + return NULL; + } + } + } + } + + } else + if (option_debug) + ast_log(LOG_DEBUG, "timed out waiting for action\n"); + } + + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "couldn't reach at this number.\n"); + } + + /* --- WAIT FOR WINNER NUMBER END! -----------*/ + return NULL; +} + +static void findmeexec(struct fm_args *tpargs) +{ + struct number *nm; + struct ast_channel *outbound; + struct ast_channel *caller; + struct ast_channel *winner = NULL; + char dialarg[512]; + int dg, idx; + char *rest, *number; + struct findme_user *tmpuser; + struct findme_user *fmuser; + struct findme_user *headuser; + struct findme_user_listptr *findme_user_list; + int status; + + findme_user_list = ast_calloc(1, sizeof(*findme_user_list)); + AST_LIST_HEAD_INIT_NOLOCK(findme_user_list); + + /* We're going to figure out what the longest possible string of digits to collect is */ + ynlongest = 0; + if (strlen(tpargs->takecall) > ynlongest) + ynlongest = strlen(tpargs->takecall); + if (strlen(tpargs->nextindp) > ynlongest) + ynlongest = strlen(tpargs->nextindp); + + idx = 1; + caller = tpargs->chan; + AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry) + if (nm->order == idx) + break; + + while (nm) { + + if (option_debug > 1) + ast_log(LOG_DEBUG, "Number %s timeout %ld\n", nm->number,nm->timeout); + time(&start_time); + + number = ast_strdupa(nm->number); + if (option_debug > 2) + ast_log(LOG_DEBUG, "examining %s\n", number); + do { + rest = strchr(number, '&'); + if (rest) { + *rest = 0; + rest++; + } + + if (!strcmp(tpargs->context, "")) + snprintf(dialarg, sizeof(dialarg), "%s", number); + else + snprintf(dialarg, sizeof(dialarg), "%s@%s", number, tpargs->context); + + tmpuser = ast_calloc(1, sizeof(*tmpuser)); + if (!tmpuser) { + ast_log(LOG_WARNING, "Out of memory!\n"); + free(findme_user_list); + return; + } + + outbound = ast_request("Local", ast_best_codec(caller->nativeformats), dialarg, &dg); + if (outbound) { + ast_set_callerid(outbound, caller->cid.cid_num, caller->cid.cid_name, caller->cid.cid_num); + ast_channel_inherit_variables(tpargs->chan, outbound); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "calling %s\n", dialarg); + if (!ast_call(outbound,dialarg,0)) { + tmpuser->ochan = outbound; + tmpuser->state = 0; + tmpuser->cleared = 0; + ast_copy_string(tmpuser->dialarg, dialarg, sizeof(dialarg)); + AST_LIST_INSERT_TAIL(findme_user_list, tmpuser, entry); + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "couldn't reach at this number.\n"); + if (outbound) { + if (!outbound->cdr) + outbound->cdr = ast_cdr_alloc(); + if (outbound->cdr) { + char tmp[256]; + + ast_cdr_init(outbound->cdr, outbound); + snprintf(tmp, sizeof(tmp), "%s/%s", "Local", dialarg); + ast_cdr_setapp(outbound->cdr, "FollowMe", tmp); + ast_cdr_update(outbound); + ast_cdr_start(outbound->cdr); + ast_cdr_end(outbound->cdr); + /* If the cause wasn't handled properly */ + if (ast_cdr_disposition(outbound->cdr,outbound->hangupcause)) + ast_cdr_failed(outbound->cdr); + } else { + ast_log(LOG_ERROR, "Unable to create Call Detail Record\n"); + ast_hangup(outbound); + outbound = NULL; + } + } + + } + } else + ast_log(LOG_WARNING, "Unable to allocate a channel for Local/%s cause: %s\n", dialarg, ast_cause2str(dg)); + + number = rest; + } while (number); + + status = 0; + if (!AST_LIST_EMPTY(findme_user_list)) + winner = wait_for_winner(findme_user_list, nm, caller, tpargs->namerecloc, &status, tpargs); + + + AST_LIST_TRAVERSE_SAFE_BEGIN(findme_user_list, fmuser, entry) { + if (!fmuser->cleared && fmuser->ochan != winner) + clear_caller(fmuser); + AST_LIST_REMOVE_CURRENT(findme_user_list, entry); + free(fmuser); + } + AST_LIST_TRAVERSE_SAFE_END + fmuser = NULL; + tmpuser = NULL; + headuser = NULL; + if (winner) + break; + + if (!caller) { + tpargs->status = 1; + free(findme_user_list); + return; + } + + idx++; + AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry) + if (nm->order == idx) + break; + + } + free(findme_user_list); + if (!winner) + tpargs->status = 1; + else { + tpargs->status = 100; + tpargs->outbound = winner; + } + + + return; + +} + +static int app_exec(struct ast_channel *chan, void *data) +{ + struct fm_args targs; + struct ast_bridge_config config; + struct call_followme *f; + struct number *nm, *newnm; + int res = 0; + struct ast_module_user *u; + char *argstr; + char namerecloc[255]; + char *fname = NULL; + int duration = 0; + struct ast_channel *caller; + struct ast_channel *outbound; + static char toast[80]; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(followmeid); + AST_APP_ARG(options); + ); + + if (!(argstr = ast_strdupa((char *)data))) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return -1; + } + + if (!data) { + ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n",app); + return -1; + } + + u = ast_module_user_add(chan); + + AST_STANDARD_APP_ARGS(args, argstr); + + if (!ast_strlen_zero(args.followmeid)) + AST_LIST_LOCK(&followmes); + AST_LIST_TRAVERSE(&followmes, f, entry) { + if (!strcasecmp(f->name, args.followmeid) && (f->active)) + break; + } + AST_LIST_UNLOCK(&followmes); + + if (option_debug) + ast_log(LOG_DEBUG, "New profile %s.\n", args.followmeid); + if (!f) { + ast_log(LOG_WARNING, "Profile requested, %s, not found in the configuration.\n", args.followmeid); + res = 0; + } else { + /* XXX TODO: Reinsert the db check value to see whether or not follow-me is on or off */ + + + if (args.options) + ast_app_parse_options(followme_opts, &targs.followmeflags, NULL, args.options); + + /* Lock the profile lock and copy out everything we need to run with before unlocking it again */ + ast_mutex_lock(&f->lock); + targs.mohclass = ast_strdupa(f->moh); + ast_copy_string(targs.context, f->context, sizeof(targs.context)); + ast_copy_string(targs.takecall, f->takecall, sizeof(targs.takecall)); + ast_copy_string(targs.nextindp, f->nextindp, sizeof(targs.nextindp)); + ast_copy_string(targs.callfromprompt, f->callfromprompt, sizeof(targs.callfromprompt)); + ast_copy_string(targs.norecordingprompt, f->norecordingprompt, sizeof(targs.norecordingprompt)); + ast_copy_string(targs.optionsprompt, f->optionsprompt, sizeof(targs.optionsprompt)); + ast_copy_string(targs.plsholdprompt, f->plsholdprompt, sizeof(targs.plsholdprompt)); + ast_copy_string(targs.statusprompt, f->statusprompt, sizeof(targs.statusprompt)); + ast_copy_string(targs.sorryprompt, f->sorryprompt, sizeof(targs.sorryprompt)); + /* Copy the numbers we're going to use into another list in case the master list should get modified + (and locked) while we're trying to do a follow-me */ + AST_LIST_HEAD_INIT_NOLOCK(&targs.cnumbers); + AST_LIST_TRAVERSE(&f->numbers, nm, entry) { + newnm = create_followme_number(nm->number, "", nm->timeout, nm->order); + AST_LIST_INSERT_TAIL(&targs.cnumbers, newnm, entry); + } + ast_mutex_unlock(&f->lock); + + if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_STATUSMSG)) + ast_stream_and_wait(chan, targs.statusprompt, chan->language, ""); + + snprintf(namerecloc,sizeof(namerecloc),"%s/followme.%s",ast_config_AST_SPOOL_DIR,chan->uniqueid); + duration = 5; + + if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_RECORDNAME)) + if (ast_play_and_record(chan, "vm-rec-name", namerecloc, 5, "sln", &duration, 128, 0, NULL) < 0) + goto outrun; + + if (!ast_fileexists(namerecloc, NULL, chan->language)) + ast_copy_string(namerecloc, "", sizeof(namerecloc)); + + if (ast_streamfile(chan, targs.plsholdprompt, chan->language)) + goto outrun; + if (ast_waitstream(chan, "") < 0) + goto outrun; + ast_moh_start(chan, S_OR(targs.mohclass, NULL), NULL); + + targs.status = 0; + targs.chan = chan; + ast_copy_string(targs.namerecloc, namerecloc, sizeof(targs.namerecloc)); + + findmeexec(&targs); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&targs.cnumbers, nm, entry) { + AST_LIST_REMOVE_CURRENT(&targs.cnumbers, entry); + free(nm); + } + AST_LIST_TRAVERSE_SAFE_END + if (targs.status != 100) { + ast_moh_stop(chan); + if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_UNREACHABLEMSG)) + ast_stream_and_wait(chan, targs.sorryprompt, chan->language, ""); + res = 0; + } else { + caller = chan; + outbound = targs.outbound; + /* Bridge the two channels. */ + + memset(&config,0,sizeof(struct ast_bridge_config)); + ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); + ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON); + ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON); + + ast_moh_stop(caller); + /* Be sure no generators are left on it */ + ast_deactivate_generator(caller); + /* Make sure channels are compatible */ + res = ast_channel_make_compatible(caller, outbound); + if (res < 0) { + ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", caller->name, outbound->name); + ast_hangup(outbound); + goto outrun; + } + time(&answer_time); + res = ast_bridge_call(caller,outbound,&config); + time(&end_time); + snprintf(toast, sizeof(toast), "%ld", (long)(end_time - start_time)); + pbx_builtin_setvar_helper(caller, "DIALEDTIME", toast); + snprintf(toast, sizeof(toast), "%ld", (long)(end_time - answer_time)); + pbx_builtin_setvar_helper(caller, "ANSWEREDTIME", toast); + if (outbound) + ast_hangup(outbound); + } + } + outrun: + + if (!ast_strlen_zero(namerecloc)){ + fname = alloca(strlen(namerecloc) + 5); + sprintf(fname, "%s.sln", namerecloc); + unlink(fname); + } + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + struct call_followme *f; + + ast_module_user_hangup_all(); + + ast_unregister_application(app); + + /* Free Memory. Yeah! I'm free! */ + AST_LIST_LOCK(&followmes); + while ((f = AST_LIST_REMOVE_HEAD(&followmes, entry))) { + free_numbers(f); + free(f); + } + + AST_LIST_UNLOCK(&followmes); + + return 0; +} + +static int load_module(void) +{ + if(!reload_followme()) + return AST_MODULE_LOAD_DECLINE; + + return ast_register_application(app, app_exec, synopsis, descrip); +} + +static int reload(void) +{ + reload_followme(); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Find-Me/Follow-Me Application", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/asterisk/apps/app_forkcdr.c b/asterisk/apps/app_forkcdr.c new file mode 100644 index 00000000..aaf3af51 --- /dev/null +++ b/asterisk/apps/app_forkcdr.c @@ -0,0 +1,267 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Anthony Minessale anthmct@yahoo.com + * Development of this app Sponsered/Funded by TAAN Softworks Corp + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Fork CDR application + * + * \author Anthony Minessale anthmct@yahoo.com + * + * \note Development of this app Sponsored/Funded by TAAN Softworks Corp + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/cdr.h" +#include "asterisk/app.h" +#include "asterisk/module.h" + +static char *app = "ForkCDR"; +static char *synopsis = +"Forks the Call Data Record"; +static char *descrip = +" ForkCDR([options]): Causes the Call Data Record to fork an additional\n" +"cdr record starting from the time of the fork call. This new cdr record will\n" +"be linked to end of the list of cdr records attached to the channel. The original CDR is\n" +"has a LOCKED flag set, which forces most cdr operations to skip it, except\n" +"for the functions that set the answer and end times, which ignore the LOCKED\n" +"flag. This allows all the cdr records in the channel to be 'ended' together\n" +"when the channel is closed.\n" +"The CDR() func (when setting CDR values) normally ignores the LOCKED flag also,\n" +"but has options to vary its behavior. The 'T' option (described below), can\n" +"override this behavior, but beware the risks.\n" +"\n" +"Detailed Behavior Description:\n" +"First, this app finds the last cdr record in the list, and makes\n" +"a copy of it. This new copy will be the newly forked cdr record.\n" +"Next, this new record is linked to the end of the cdr record list.\n" +"Next, The new cdr record is RESET (unless you use an option to prevent this)\n" +"This means that:\n" +" 1. All flags are unset on the cdr record\n" +" 2. the start, end, and answer times are all set to zero.\n" +" 3. the billsec and duration fields are set to zero.\n" +" 4. the start time is set to the current time.\n" +" 5. the disposition is set to NULL.\n" +"Next, unless you specified the 'v' option, all variables will be\n" +"removed from the original cdr record. Thus, the 'v' option allows\n" +"any CDR variables to be replicated to all new forked cdr records.\n" +"Without the 'v' option, the variables on the original are effectively\n" +"moved to the new forked cdr record.\n" +"Next, if the 's' option is set, the provided variable and value\n" +"are set on the original cdr record.\n" +"Next, if the 'a' option is given, and the original cdr record has an\n" +"answer time set, then the new forked cdr record will have its answer\n" +"time set to its start time. If the old answer time were carried forward,\n" +"the answer time would be earlier than the start time, giving strange\n" +"duration and billsec times.\n" +"Next, if the 'd' option was specified, the disposition is copied from\n" +"the original cdr record to the new forked cdr.\n" +"Next, if the 'D' option was specified, the destination channel field\n" +"in the new forked CDR is erased.\n" +"Next, if the 'e' option was specified, the 'end' time for the original\n" +"cdr record is set to the current time. Future hang-up or ending events\n" +"will not override this time stamp.\n" +"Next, If the 'A' option is specified, the original cdr record will have\n" +"it ANS_LOCKED flag set, which prevent future answer events\n" +"from updating the original cdr record's disposition. Normally, an\n" +"'ANSWERED' event would mark all cdr records in the chain as 'ANSWERED'.\n" +"Next, if the 'T' option is specified, the original cdr record will have\n" +"its 'DONT_TOUCH' flag set, which will force the cdr_answer, cdr_end, and\n" +"cdr_setvar functions to leave that cdr record alone.\n" +"And, last but not least, the original cdr record has its LOCKED flag\n" +"set. Almost all internal CDR functions (except for the funcs that set\n" +"the end, and answer times, and set a variable) will honor this flag\n" +"and leave a LOCKED cdr record alone.\n" +"This means that the newly created forked cdr record will affected\n" +"by events transpiring within Asterisk, with the previously noted\n" +"exceptions.\n" +" Options:\n" +" a - update the answer time on the NEW CDR just after it's been inited..\n" +" The new CDR may have been answered already, the reset that forkcdr.\n" +" does will erase the answer time. This will bring it back, but\n" +" the answer time will be a copy of the fork/start time. It will.\n" +" only do this if the initial cdr was indeed already answered..\n" +" A - Lock the original CDR against the answer time being updated.\n" +" This will allow the disposition on the original CDR to remain the same.\n" +" d - Copy the disposition forward from the old cdr, after the .\n" +" init..\n" +" D - Clear the dstchannel on the new CDR after reset..\n" +" e - end the original CDR. Do this after all the necc. data.\n" +" is copied from the original CDR to the new forked CDR..\n" +" R - do NOT reset the new cdr..\n" +" s(name=val) - Set the CDR var 'name' in the original CDR, with value.\n" +" 'val'.\n" +" T - Mark the original CDR with a DONT_TOUCH flag. setvar, answer, and end\n" +" cdr funcs will obey this flag; normally they don't honor the LOCKED\n" +" flag set on the original CDR record.\n" +" Beware-- using this flag may cause CDR's not to have their end times\n" +" updated! It is suggested that if you specify this flag, you might\n" +" wish to use the 'e' flag as well!\n" +" v - When the new CDR is forked, it gets a copy of the vars attached\n" +" to the current CDR. The vars attached to the original CDR are removed\n" +" unless this option is specified.\n"; + + +enum { + OPT_SETANS = (1 << 0), + OPT_SETDISP = (1 << 1), + OPT_RESETDEST = (1 << 2), + OPT_ENDCDR = (1 << 3), + OPT_NORESET = (1 << 4), + OPT_KEEPVARS = (1 << 5), + OPT_VARSET = (1 << 6), + OPT_ANSLOCK = (1 << 7), + OPT_DONTOUCH = (1 << 8), +}; + +enum { + OPT_ARG_VARSET = 0, + /* note: this entry _MUST_ be the last one in the enum */ + OPT_ARG_ARRAY_SIZE, +}; + +AST_APP_OPTIONS(forkcdr_exec_options, { + AST_APP_OPTION('a', OPT_SETANS), + AST_APP_OPTION('A', OPT_ANSLOCK), + AST_APP_OPTION('d', OPT_SETDISP), + AST_APP_OPTION('D', OPT_RESETDEST), + AST_APP_OPTION('e', OPT_ENDCDR), + AST_APP_OPTION('R', OPT_NORESET), + AST_APP_OPTION_ARG('s', OPT_VARSET, OPT_ARG_VARSET), + AST_APP_OPTION('T', OPT_DONTOUCH), + AST_APP_OPTION('v', OPT_KEEPVARS), +}); + +static void ast_cdr_fork(struct ast_channel *chan, struct ast_flags optflags, char *set) +{ + struct ast_cdr *cdr; + struct ast_cdr *newcdr; + struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS }; + + cdr = chan->cdr; + + while (cdr->next) + cdr = cdr->next; + + if (!(newcdr = ast_cdr_dup(cdr))) + return; + + ast_cdr_append(cdr, newcdr); + + if (!ast_test_flag(&optflags, OPT_NORESET)) + ast_cdr_reset(newcdr, &flags); + + if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS)) + ast_cdr_free_vars(cdr, 0); + + if (!ast_strlen_zero(set)) { + char *varname = ast_strdupa(set), *varval; + varval = strchr(varname,'='); + if (varval) { + *varval = 0; + varval++; + ast_cdr_setvar(cdr, varname, varval, 0); + } + } + + if (ast_test_flag(&optflags, OPT_SETANS) && !ast_tvzero(cdr->answer)) + newcdr->answer = newcdr->start; + + if (ast_test_flag(&optflags, OPT_SETDISP)) + newcdr->disposition = cdr->disposition; + + if (ast_test_flag(&optflags, OPT_RESETDEST)) + newcdr->dstchannel[0] = 0; + + if (ast_test_flag(&optflags, OPT_ENDCDR)) + ast_cdr_end(cdr); + + if (ast_test_flag(&optflags, OPT_ANSLOCK)) + ast_set_flag(cdr, AST_CDR_FLAG_ANSLOCKED); + + if (ast_test_flag(&optflags, OPT_DONTOUCH)) + ast_set_flag(cdr, AST_CDR_FLAG_DONT_TOUCH); + + ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED); +} + +static int forkcdr_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u; + char *argcopy = NULL; + struct ast_flags flags = {0}; + char *opts[OPT_ARG_ARRAY_SIZE]; + AST_DECLARE_APP_ARGS(arglist, + AST_APP_ARG(options); + ); + + if (!chan->cdr) { + ast_log(LOG_WARNING, "Channel does not have a CDR\n"); + return 0; + } + + u = ast_module_user_add(chan); + + argcopy = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(arglist, argcopy); + + opts[OPT_ARG_VARSET] = 0; + + if (!ast_strlen_zero(arglist.options)) + ast_app_parse_options(forkcdr_exec_options, &flags, opts, arglist.options); + + if (!ast_strlen_zero(data)) + ast_set2_flag(chan->cdr, ast_test_flag(&flags, OPT_KEEPVARS), AST_CDR_FLAG_KEEP_VARS); + + ast_cdr_fork(chan, flags, opts[OPT_ARG_VARSET]); + + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, forkcdr_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Fork The CDR into 2 separate entities"); diff --git a/asterisk/apps/app_getcpeid.c b/asterisk/apps/app_getcpeid.c new file mode 100644 index 00000000..13573126 --- /dev/null +++ b/asterisk/apps/app_getcpeid.c @@ -0,0 +1,148 @@ +/* + * 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 Get ADSI CPE ID + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/adsi.h" +#include "asterisk/options.h" + +static char *app = "GetCPEID"; + +static char *synopsis = "Get ADSI CPE ID"; + +static char *descrip = +" GetCPEID: Obtains and displays ADSI CPE ID and other information in order\n" +"to properly setup chan_dahdi.conf for on-hook operations.\n"; + + +static int cpeid_setstatus(struct ast_channel *chan, char *stuff[], int voice) +{ + int justify[5] = { ADSI_JUST_CENT, ADSI_JUST_LEFT, ADSI_JUST_LEFT, ADSI_JUST_LEFT }; + char *tmp[5]; + int x; + for (x=0;x<4;x++) + tmp[x] = stuff[x]; + tmp[4] = NULL; + return ast_adsi_print(chan, tmp, justify, voice); +} + +static int cpeid_exec(struct ast_channel *chan, void *idata) +{ + int res=0; + struct ast_module_user *u; + unsigned char cpeid[4]; + int gotgeometry = 0; + int gotcpeid = 0; + int width, height, buttons; + char *data[4]; + unsigned int x; + + u = ast_module_user_add(chan); + + for (x = 0; x < 4; x++) + data[x] = alloca(80); + + strcpy(data[0], "** CPE Info **"); + strcpy(data[1], "Identifying CPE..."); + strcpy(data[2], "Please wait..."); + res = ast_adsi_load_session(chan, NULL, 0, 1); + if (res > 0) { + cpeid_setstatus(chan, data, 0); + res = ast_adsi_get_cpeid(chan, cpeid, 0); + if (res > 0) { + gotcpeid = 1; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Got CPEID of '%02x:%02x:%02x:%02x' on '%s'\n", cpeid[0], cpeid[1], cpeid[2], cpeid[3], chan->name); + } + if (res > -1) { + strcpy(data[1], "Measuring CPE..."); + strcpy(data[2], "Please wait..."); + cpeid_setstatus(chan, data, 0); + res = ast_adsi_get_cpeinfo(chan, &width, &height, &buttons, 0); + if (res > -1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "CPE has %d lines, %d columns, and %d buttons on '%s'\n", height, width, buttons, chan->name); + gotgeometry = 1; + } + } + if (res > -1) { + if (gotcpeid) + snprintf(data[1], 80, "CPEID: %02x:%02x:%02x:%02x", cpeid[0], cpeid[1], cpeid[2], cpeid[3]); + else + strcpy(data[1], "CPEID Unknown"); + if (gotgeometry) + snprintf(data[2], 80, "Geom: %dx%d, %d buttons", width, height, buttons); + else + strcpy(data[2], "Geometry unknown"); + strcpy(data[3], "Press # to exit"); + cpeid_setstatus(chan, data, 1); + for(;;) { + res = ast_waitfordigit(chan, 1000); + if (res < 0) + break; + if (res == '#') { + res = 0; + break; + } + } + ast_adsi_unload_session(chan); + } + } + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, cpeid_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Get ADSI CPE ID"); diff --git a/asterisk/apps/app_hasnewvoicemail.c b/asterisk/apps/app_hasnewvoicemail.c new file mode 100644 index 00000000..e7a6d9fe --- /dev/null +++ b/asterisk/apps/app_hasnewvoicemail.c @@ -0,0 +1,225 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Changes Copyright (c) 2004 - 2006 Todd Freeman + * + * 95% based on HasNewVoicemail by: + * + * Copyright (c) 2003 Tilghman Lesher. All rights reserved. + * + * Tilghman Lesher + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 HasVoicemail application + * + * \author Todd Freeman + * + * \note 95% based on HasNewVoicemail by + * Tilghman Lesher + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 85864 $") + +#include +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" +#include "asterisk/options.h" + +static char *app_hasvoicemail = "HasVoicemail"; +static char *hasvoicemail_synopsis = "Conditionally branches to priority + 101 with the right options set"; +static char *hasvoicemail_descrip = +"HasVoicemail(vmbox[/folder][@context][|varname[|options]])\n" +" Optionally sets to the number of messages in that folder." +" Assumes folder of INBOX if not specified.\n" +" The option string may contain zero or the following character:\n" +" 'j' -- jump to priority n+101, if there is voicemail in the folder indicated.\n" +" This application sets the following channel variable upon completion:\n" +" HASVMSTATUS The result of the voicemail check returned as a text string as follows\n" +" <# of messages in the folder, 0 for NONE>\n" +"\nThis application has been deprecated in favor of the VMCOUNT() function\n"; + +static char *app_hasnewvoicemail = "HasNewVoicemail"; +static char *hasnewvoicemail_synopsis = "Conditionally branches to priority + 101 with the right options set"; +static char *hasnewvoicemail_descrip = +"HasNewVoicemail(vmbox[/folder][@context][|varname[|options]])\n" +"Assumes folder 'INBOX' if folder is not specified. Optionally sets to the number of messages\n" +"in that folder.\n" +" The option string may contain zero of the following character:\n" +" 'j' -- jump to priority n+101, if there is new voicemail in folder 'folder' or INBOX\n" +" This application sets the following channel variable upon completion:\n" +" HASVMSTATUS The result of the new voicemail check returned as a text string as follows\n" +" <# of messages in the folder, 0 for NONE>\n" +"\nThis application has been deprecated in favor of the VMCOUNT() function\n"; + + +static int hasvoicemail_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + char *input, *varname = NULL, *vmbox, *context = "default"; + char *vmfolder; + int vmcount = 0; + static int dep_warning = 0; + int priority_jump = 0; + char tmp[12]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(vmbox); + AST_APP_ARG(varname); + AST_APP_ARG(options); + ); + + if (!dep_warning) { + ast_log(LOG_WARNING, "The applications HasVoicemail and HasNewVoicemail have been deprecated. Please use the VMCOUNT() function instead.\n"); + dep_warning = 1; + } + + if (!data) { + ast_log(LOG_WARNING, "HasVoicemail requires an argument (vm-box[/folder][@context][|varname[|options]])\n"); + return -1; + } + + u = ast_module_user_add(chan); + + input = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, input); + + vmbox = strsep(&args.vmbox, "@"); + + if (!ast_strlen_zero(args.vmbox)) + context = args.vmbox; + + vmfolder = strchr(vmbox, '/'); + if (vmfolder) { + *vmfolder = '\0'; + vmfolder++; + } else { + vmfolder = "INBOX"; + } + + if (args.options) { + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + vmcount = ast_app_messagecount(context, vmbox, vmfolder); + /* Set the count in the channel variable */ + if (varname) { + snprintf(tmp, sizeof(tmp), "%d", vmcount); + pbx_builtin_setvar_helper(chan, varname, tmp); + } + + if (vmcount > 0) { + /* Branch to the next extension */ + if (priority_jump || ast_opt_priority_jumping) { + if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) + ast_log(LOG_WARNING, "VM box %s@%s has new voicemail, but extension %s, priority %d doesn't exist\n", vmbox, context, chan->exten, chan->priority + 101); + } + } + + snprintf(tmp, sizeof(tmp), "%d", vmcount); + pbx_builtin_setvar_helper(chan, "HASVMSTATUS", tmp); + + ast_module_user_remove(u); + + return 0; +} + +static int acf_vmcount_exec(struct ast_channel *chan, char *cmd, char *argsstr, char *buf, size_t len) +{ + struct ast_module_user *u; + char *context; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(vmbox); + AST_APP_ARG(folder); + ); + + if (ast_strlen_zero(argsstr)) + return -1; + + u = ast_module_user_add(chan); + + buf[0] = '\0'; + + AST_STANDARD_APP_ARGS(args, argsstr); + + if (strchr(args.vmbox, '@')) { + context = args.vmbox; + args.vmbox = strsep(&context, "@"); + } else { + context = "default"; + } + + if (ast_strlen_zero(args.folder)) { + args.folder = "INBOX"; + } + + snprintf(buf, len, "%d", ast_app_messagecount(context, args.vmbox, args.folder)); + + ast_module_user_remove(u); + + return 0; +} + +struct ast_custom_function acf_vmcount = { + .name = "VMCOUNT", + .synopsis = "Counts the voicemail in a specified mailbox", + .syntax = "VMCOUNT(vmbox[@context][|folder])", + .desc = + " context - defaults to \"default\"\n" + " folder - defaults to \"INBOX\"\n", + .read = acf_vmcount_exec, +}; + +static int unload_module(void) +{ + int res; + + res = ast_custom_function_unregister(&acf_vmcount); + res |= ast_unregister_application(app_hasvoicemail); + res |= ast_unregister_application(app_hasnewvoicemail); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res; + + res = ast_custom_function_register(&acf_vmcount); + res |= ast_register_application(app_hasvoicemail, hasvoicemail_exec, hasvoicemail_synopsis, hasvoicemail_descrip); + res |= ast_register_application(app_hasnewvoicemail, hasvoicemail_exec, hasnewvoicemail_synopsis, hasnewvoicemail_descrip); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Indicator for whether a voice mailbox has messages in a given folder."); diff --git a/asterisk/apps/app_ices.c b/asterisk/apps/app_ices.c new file mode 100644 index 00000000..d7a3e4ef --- /dev/null +++ b/asterisk/apps/app_ices.c @@ -0,0 +1,233 @@ +/* + * 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 Stream to an icecast server via ICES (see contrib/asterisk-ices.xml) + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/frame.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/options.h" + +#define path_BIN "/usr/bin/" +#define path_LOCAL "/usr/local/bin/" + +static char *app = "ICES"; + +static char *synopsis = "Encode and stream using 'ices'"; + +static char *descrip = +" ICES(config.xml) Streams to an icecast server using ices\n" +"(available separately). A configuration file must be supplied\n" +"for ices (see contrib/asterisk-ices.xml). \n"; + +static int icesencode(char *filename, int fd) +{ + int res; + int x; + sigset_t fullset, oldset; + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + res = fork(); + if (res < 0) + ast_log(LOG_WARNING, "Fork failed\n"); + if (res) { + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + return res; + } + + /* Stop ignoring PIPE */ + signal(SIGPIPE, SIG_DFL); + pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); + + if (ast_opt_high_priority) + ast_set_priority(0); + dup2(fd, STDIN_FILENO); + for (x=STDERR_FILENO + 1;x<1024;x++) { + if ((x != STDIN_FILENO) && (x != STDOUT_FILENO)) + close(x); + } + + /* Most commonly installed in /usr/local/bin + * But many places has it in /usr/bin + * As a last-ditch effort, try to use PATH + */ + execl(path_LOCAL "ices2", "ices", filename, (char *)NULL); + execl(path_BIN "ices2", "ices", filename, (char *)NULL); + execlp("ices2", "ices", filename, (char *)NULL); + + if (option_debug) + ast_log(LOG_DEBUG, "Couldn't find ices version 2, attempting to use ices version 1."); + + execl(path_LOCAL "ices", "ices", filename, (char *)NULL); + execl(path_BIN "ices", "ices", filename, (char *)NULL); + execlp("ices", "ices", filename, (char *)NULL); + + ast_log(LOG_WARNING, "Execute of ices failed, could not be found.\n"); + close(fd); + _exit(0); +} + +static int ices_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + int fds[2]; + int ms = -1; + int pid = -1; + int flags; + int oreadformat; + struct timeval last; + struct ast_frame *f; + char filename[256]=""; + char *c; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ICES requires an argument (configfile.xml)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + last = ast_tv(0, 0); + + if (pipe(fds)) { + ast_log(LOG_WARNING, "Unable to create pipe\n"); + ast_module_user_remove(u); + return -1; + } + flags = fcntl(fds[1], F_GETFL); + fcntl(fds[1], F_SETFL, flags | O_NONBLOCK); + + ast_stopstream(chan); + + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + + if (res) { + close(fds[0]); + close(fds[1]); + ast_log(LOG_WARNING, "Answer failed!\n"); + ast_module_user_remove(u); + return -1; + } + + oreadformat = chan->readformat; + res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + close(fds[0]); + close(fds[1]); + ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); + ast_module_user_remove(u); + return -1; + } + if (((char *)data)[0] == '/') + ast_copy_string(filename, (char *) data, sizeof(filename)); + else + snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, (char *)data); + /* Placeholder for options */ + c = strchr(filename, '|'); + if (c) + *c = '\0'; + res = icesencode(filename, fds[0]); + if (res >= 0) { + pid = res; + for (;;) { + /* Wait for audio, and stream */ + ms = ast_waitfor(chan, -1); + if (ms < 0) { + ast_log(LOG_DEBUG, "Hangup detected\n"); + res = -1; + break; + } + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "Null frame == hangup() detected\n"); + res = -1; + break; + } + if (f->frametype == AST_FRAME_VOICE) { + res = write(fds[1], f->data, f->datalen); + if (res < 0) { + if (errno != EAGAIN) { + ast_log(LOG_WARNING, "Write failed to pipe: %s\n", strerror(errno)); + res = -1; + ast_frfree(f); + break; + } + } + } + ast_frfree(f); + } + } + close(fds[0]); + close(fds[1]); + + if (pid > -1) + kill(pid, SIGKILL); + if (!res && oreadformat) + ast_set_read_format(chan, oreadformat); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, ices_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Encode and Stream via icecast and ices"); diff --git a/asterisk/apps/app_image.c b/asterisk/apps/app_image.c new file mode 100644 index 00000000..a02bb0f9 --- /dev/null +++ b/asterisk/apps/app_image.c @@ -0,0 +1,125 @@ +/* + * 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 App to transmit an image + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40722 $") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/image.h" +#include "asterisk/app.h" +#include "asterisk/options.h" + +static char *app = "SendImage"; + +static char *synopsis = "Send an image file"; + +static char *descrip = +" SendImage(filename): Sends an image on a channel. \n" +"If the channel supports image transport but the image send\n" +"fails, the channel will be hung up. Otherwise, the dialplan\n" +"continues execution.\n" +"The option string may contain the following character:\n" +" 'j' -- jump to priority n+101 if the channel doesn't support image transport\n" +"This application sets the following channel variable upon completion:\n" +" SENDIMAGESTATUS The status is the result of the attempt as a text string, one of\n" +" OK | NOSUPPORT \n"; + + +static int sendimage_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u; + char *parse; + int priority_jump = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filename); + AST_APP_ARG(options); + ); + + u = ast_module_user_add(chan); + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.filename)) { + ast_log(LOG_WARNING, "SendImage requires an argument (filename[|options])\n"); + return -1; + } + + if (args.options) { + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + if (!ast_supports_images(chan)) { + /* Does not support transport */ + if (priority_jump || ast_opt_priority_jumping) + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "NOSUPPORT"); + ast_module_user_remove(u); + return 0; + } + + res = ast_send_image(chan, args.filename); + + if (!res) + pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "OK"); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, sendimage_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Image Transmission Application"); diff --git a/asterisk/apps/app_ivrdemo.c b/asterisk/apps/app_ivrdemo.c new file mode 100644 index 00000000..adba24e4 --- /dev/null +++ b/asterisk/apps/app_ivrdemo.c @@ -0,0 +1,132 @@ +/* + * 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 IVR Demo application + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + no + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40722 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/app.h" + +static char *tdesc = "IVR Demo Application"; +static char *app = "IVRDemo"; +static char *synopsis = +" This is a skeleton application that shows you the basic structure to create your\n" +"own asterisk applications and demonstrates the IVR demo.\n"; + +static int ivr_demo_func(struct ast_channel *chan, void *data) +{ + ast_verbose("IVR Demo, data is %s!\n", (char *)data); + return 0; +} + +AST_IVR_DECLARE_MENU(ivr_submenu, "IVR Demo Sub Menu", 0, +{ + { "s", AST_ACTION_BACKGROUND, "demo-abouttotry" }, + { "s", AST_ACTION_WAITOPTION }, + { "1", AST_ACTION_PLAYBACK, "digits/1" }, + { "1", AST_ACTION_PLAYBACK, "digits/1" }, + { "1", AST_ACTION_RESTART }, + { "2", AST_ACTION_PLAYLIST, "digits/2;digits/3" }, + { "3", AST_ACTION_CALLBACK, ivr_demo_func }, + { "4", AST_ACTION_TRANSFER, "demo|s|1" }, + { "*", AST_ACTION_REPEAT }, + { "#", AST_ACTION_UPONE }, + { NULL } +}); + +AST_IVR_DECLARE_MENU(ivr_demo, "IVR Demo Main Menu", 0, +{ + { "s", AST_ACTION_BACKGROUND, "demo-congrats" }, + { "g", AST_ACTION_BACKGROUND, "demo-instruct" }, + { "g", AST_ACTION_WAITOPTION }, + { "1", AST_ACTION_PLAYBACK, "digits/1" }, + { "1", AST_ACTION_RESTART }, + { "2", AST_ACTION_MENU, &ivr_submenu }, + { "2", AST_ACTION_RESTART }, + { "i", AST_ACTION_PLAYBACK, "invalid" }, + { "i", AST_ACTION_REPEAT, (void *)(unsigned long)2 }, + { "#", AST_ACTION_EXIT }, + { NULL }, +}); + + +static int skel_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "skel requires an argument (filename)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + /* Do our thing here */ + + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + if (!res) + res = ast_ivr_menu_run(chan, &ivr_demo, data); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, skel_exec, tdesc, synopsis); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "IVR Demo Application"); diff --git a/asterisk/apps/app_lookupblacklist.c b/asterisk/apps/app_lookupblacklist.c new file mode 100644 index 00000000..da7647ec --- /dev/null +++ b/asterisk/apps/app_lookupblacklist.c @@ -0,0 +1,160 @@ +/* + * 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 App to lookup the callerid number, and see if it is blacklisted + * + * \author Mark Spencer + * + * \ingroup applications + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 56922 $") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/image.h" +#include "asterisk/callerid.h" +#include "asterisk/astdb.h" +#include "asterisk/options.h" + +static char *app = "LookupBlacklist"; + +static char *synopsis = "Look up Caller*ID name/number from blacklist database"; + +static char *descrip = + " LookupBlacklist(options): Looks up the Caller*ID number on the active\n" + "channel in the Asterisk database (family 'blacklist'). \n" + "The option string may contain the following character:\n" + " 'j' -- jump to n+101 priority if the number/name is found in the blacklist\n" + "This application sets the following channel variable upon completion:\n" + " LOOKUPBLSTATUS The status of the Blacklist lookup as a text string, one of\n" + " FOUND | NOTFOUND\n" + "Example: exten => 1234,1,LookupBlacklist()\n\n" + "This application is deprecated and may be removed from a future release.\n" + "Please use the dialplan function BLACKLIST() instead.\n"; + + +static int blacklist_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + char blacklist[1]; + int bl = 0; + + if (chan->cid.cid_num) { + if (!ast_db_get("blacklist", chan->cid.cid_num, blacklist, sizeof (blacklist))) + bl = 1; + } + if (chan->cid.cid_name) { + if (!ast_db_get("blacklist", chan->cid.cid_name, blacklist, sizeof (blacklist))) + bl = 1; + } + + snprintf(buf, len, "%d", bl); + return 0; +} + +static struct ast_custom_function blacklist_function = { + .name = "BLACKLIST", + .synopsis = "Check if the callerid is on the blacklist", + .desc = "Uses astdb to check if the Caller*ID is in family 'blacklist'. Returns 1 or 0.\n", + .syntax = "BLACKLIST()", + .read = blacklist_read, +}; + +static int +lookupblacklist_exec (struct ast_channel *chan, void *data) +{ + char blacklist[1]; + struct ast_module_user *u; + int bl = 0; + int priority_jump = 0; + static int dep_warning = 0; + + u = ast_module_user_add(chan); + + if (!dep_warning) { + dep_warning = 1; + ast_log(LOG_WARNING, "LookupBlacklist is deprecated. Please use ${BLACKLIST()} instead.\n"); + } + + if (!ast_strlen_zero(data)) { + if (strchr(data, 'j')) + priority_jump = 1; + } + + if (chan->cid.cid_num) { + if (!ast_db_get("blacklist", chan->cid.cid_num, blacklist, sizeof (blacklist))) { + if (option_verbose > 2) + ast_log(LOG_NOTICE, "Blacklisted number %s found\n",chan->cid.cid_num); + bl = 1; + } + } + if (chan->cid.cid_name) { + if (!ast_db_get("blacklist", chan->cid.cid_name, blacklist, sizeof (blacklist))) { + if (option_verbose > 2) + ast_log (LOG_NOTICE,"Blacklisted name \"%s\" found\n",chan->cid.cid_name); + bl = 1; + } + } + + if (bl) { + if (priority_jump || ast_opt_priority_jumping) + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + pbx_builtin_setvar_helper(chan, "LOOKUPBLSTATUS", "FOUND"); + } else + pbx_builtin_setvar_helper(chan, "LOOKUPBLSTATUS", "NOTFOUND"); + + ast_module_user_remove(u); + + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + res |= ast_custom_function_unregister(&blacklist_function); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res = ast_custom_function_register(&blacklist_function); + res |= ast_register_application (app, lookupblacklist_exec, synopsis,descrip); + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Look up Caller*ID name/number from blacklist database"); diff --git a/asterisk/apps/app_lookupcidname.c b/asterisk/apps/app_lookupcidname.c new file mode 100644 index 00000000..238b1a1d --- /dev/null +++ b/asterisk/apps/app_lookupcidname.c @@ -0,0 +1,103 @@ +/* + * 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 App to set callerid name from database, based on directory number + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 56922 $") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/image.h" +#include "asterisk/callerid.h" +#include "asterisk/astdb.h" + +static char *app = "LookupCIDName"; + +static char *synopsis = "Look up CallerID Name from local database"; + +static char *descrip = + " LookupCIDName: Looks up the Caller*ID number on the active\n" + "channel in the Asterisk database (family 'cidname') and sets the\n" + "Caller*ID name. Does nothing if no Caller*ID was received on the\n" + "channel. This is useful if you do not subscribe to Caller*ID\n" + "name delivery, or if you want to change the names on some incoming\n" + "calls.\n\n" + "LookupCIDName is deprecated. Please use ${DB(cidname/${CALLERID(num)})}\n" + "instead.\n"; + + +static int lookupcidname_exec (struct ast_channel *chan, void *data) +{ + char dbname[64]; + struct ast_module_user *u; + static int dep_warning = 0; + + u = ast_module_user_add(chan); + if (!dep_warning) { + dep_warning = 1; + ast_log(LOG_WARNING, "LookupCIDName is deprecated. Please use ${DB(cidname/${CALLERID(num)})} instead.\n"); + } + if (chan->cid.cid_num) { + if (!ast_db_get ("cidname", chan->cid.cid_num, dbname, sizeof (dbname))) { + ast_set_callerid (chan, NULL, dbname, NULL); + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "Changed Caller*ID name to %s\n", + dbname); + } + } + ast_module_user_remove(u); + + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application (app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application (app, lookupcidname_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Look up CallerID Name from local database"); diff --git a/asterisk/apps/app_macro.c b/asterisk/apps/app_macro.c new file mode 100644 index 00000000..32f41682 --- /dev/null +++ b/asterisk/apps/app_macro.c @@ -0,0 +1,557 @@ +/* + * 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 Dial plan macro Implementation + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 96042 $") + +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/options.h" +#include "asterisk/config.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" + +#define MAX_ARGS 80 + +/* special result value used to force macro exit */ +#define MACRO_EXIT_RESULT 1024 + +static char *descrip = +" Macro(macroname|arg1|arg2...): Executes a macro using the context\n" +"'macro-', jumping to the 's' extension of that context and\n" +"executing each step, then returning when the steps end. \n" +"The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n" +"${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n" +"${ARG1}, ${ARG2}, etc in the macro context.\n" +"If you Goto out of the Macro context, the Macro will terminate and control\n" +"will be returned at the location of the Goto.\n" +"If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n" +"at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n" +"Extensions: While a macro is being executed, it becomes the current context.\n" +" This means that if a hangup occurs, for instance, that the macro\n" +" will be searched for an 'h' extension, NOT the context from which\n" +" the macro was called. So, make sure to define all appropriate\n" +" extensions in your macro! (you can use 'catch' in AEL) \n" +"WARNING: Because of the way Macro is implemented (it executes the priorities\n" +" contained within it via sub-engine), and a fixed per-thread\n" +" memory stack allowance, macros are limited to 7 levels\n" +" of nesting (macro calling macro calling macro, etc.); It\n" +" may be possible that stack-intensive applications in deeply nested macros\n" +" could cause asterisk to crash earlier than this limit.\n"; + +static char *if_descrip = +" MacroIf(?macroname_a[|arg1][:macroname_b[|arg1]])\n" +"Executes macro defined in if is true\n" +"(otherwise if provided)\n" +"Arguments and return values as in application macro()\n"; + +static char *exclusive_descrip = +" MacroExclusive(macroname|arg1|arg2...):\n" +"Executes macro defined in the context 'macro-macroname'\n" +"Only one call at a time may run the macro.\n" +"(we'll wait if another call is busy executing in the Macro)\n" +"Arguments and return values as in application Macro()\n"; + +static char *exit_descrip = +" MacroExit():\n" +"Causes the currently running macro to exit as if it had\n" +"ended normally by running out of priorities to execute.\n" +"If used outside a macro, will likely cause unexpected\n" +"behavior.\n"; + +static char *app = "Macro"; +static char *if_app = "MacroIf"; +static char *exclusive_app = "MacroExclusive"; +static char *exit_app = "MacroExit"; + +static char *synopsis = "Macro Implementation"; +static char *if_synopsis = "Conditional Macro Implementation"; +static char *exclusive_synopsis = "Exclusive Macro Implementation"; +static char *exit_synopsis = "Exit From Macro"; + + +static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid) +{ + struct ast_exten *e; + struct ast_include *i; + struct ast_context *c2; + + for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) { + if (ast_extension_match(ast_get_extension_name(e), exten)) { + int needmatch = ast_get_extension_matchcid(e); + if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) || + (!needmatch)) { + /* This is the matching extension we want */ + struct ast_exten *p; + for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) { + if (priority != ast_get_extension_priority(p)) + continue; + return p; + } + } + } + } + + /* No match; run through includes */ + for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) { + for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) { + if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) { + e = find_matching_priority(c2, exten, priority, callerid); + if (e) + return e; + } + } + } + return NULL; +} + +static int _macro_exec(struct ast_channel *chan, void *data, int exclusive) +{ + const char *s; + char *tmp; + char *cur, *rest; + char *macro; + char fullmacro[80]; + char varname[80]; + char runningapp[80], runningdata[1024]; + char *oldargs[MAX_ARGS + 1] = { NULL, }; + int argc, x; + int res=0; + char oldexten[256]=""; + int oldpriority, gosub_level = 0; + char pc[80], depthc[12]; + char oldcontext[AST_MAX_CONTEXT] = ""; + const char *inhangupc; + int offset, depth = 0, maxdepth = 7; + int setmacrocontext=0; + int autoloopflag, dead = 0, inhangup = 0; + + char *save_macro_exten; + char *save_macro_context; + char *save_macro_priority; + char *save_macro_offset; + struct ast_module_user *u; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n"); + return -1; + } + + u = ast_module_user_add(chan); + + /* does the user want a deeper rabbit hole? */ + s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"); + if (s) + sscanf(s, "%d", &maxdepth); + + /* Count how many levels deep the rabbit hole goes */ + s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"); + if (s) + sscanf(s, "%d", &depth); + /* Used for detecting whether to return when a Macro is called from another Macro after hangup */ + if (strcmp(chan->exten, "h") == 0) + pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1"); + inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP"); + if (!ast_strlen_zero(inhangupc)) + sscanf(inhangupc, "%d", &inhangup); + + if (depth >= maxdepth) { + ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n"); + ast_module_user_remove(u); + return 0; + } + snprintf(depthc, sizeof(depthc), "%d", depth + 1); + pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); + + tmp = ast_strdupa(data); + rest = tmp; + macro = strsep(&rest, "|"); + if (ast_strlen_zero(macro)) { + ast_log(LOG_WARNING, "Invalid macro name specified\n"); + ast_module_user_remove(u); + return 0; + } + + snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro); + if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) { + if (!ast_context_find(fullmacro)) + ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro); + else + ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro); + ast_module_user_remove(u); + return 0; + } + + /* If we are to run the macro exclusively, take the mutex */ + if (exclusive) { + ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro); + ast_autoservice_start(chan); + if (ast_context_lockmacro(fullmacro)) { + ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro); + ast_autoservice_stop(chan); + ast_module_user_remove(u); + + return 0; + } + ast_autoservice_stop(chan); + } + + /* Save old info */ + oldpriority = chan->priority; + ast_copy_string(oldexten, chan->exten, sizeof(oldexten)); + ast_copy_string(oldcontext, chan->context, sizeof(oldcontext)); + if (ast_strlen_zero(chan->macrocontext)) { + ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext)); + ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten)); + chan->macropriority = chan->priority; + setmacrocontext=1; + } + argc = 1; + /* Save old macro variables */ + save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN")); + pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten); + + save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT")); + pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext); + + save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY")); + snprintf(pc, sizeof(pc), "%d", oldpriority); + pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc); + + save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET")); + pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL); + + /* Setup environment for new run */ + chan->exten[0] = 's'; + chan->exten[1] = '\0'; + ast_copy_string(chan->context, fullmacro, sizeof(chan->context)); + chan->priority = 1; + + while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) { + const char *s; + /* Save copy of old arguments if we're overwriting some, otherwise + let them pass through to the other macro */ + snprintf(varname, sizeof(varname), "ARG%d", argc); + s = pbx_builtin_getvar_helper(chan, varname); + if (s) + oldargs[argc] = ast_strdup(s); + pbx_builtin_setvar_helper(chan, varname, cur); + argc++; + } + autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP); + ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP); + while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) { + struct ast_context *c; + struct ast_exten *e; + runningapp[0] = '\0'; + runningdata[0] = '\0'; + + /* What application will execute? */ + if (ast_rdlock_contexts()) { + ast_log(LOG_WARNING, "Failed to lock contexts list\n"); + } else { + for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) { + if (!strcmp(ast_get_context_name(c), chan->context)) { + if (ast_lock_context(c)) { + ast_log(LOG_WARNING, "Unable to lock context?\n"); + } else { + e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num); + if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */ + ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp)); + ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata)); + } + ast_unlock_context(c); + } + break; + } + } + } + ast_unlock_contexts(); + + /* Reset the macro depth, if it was changed in the last iteration */ + pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); + + if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) { + /* Something bad happened, or a hangup has been requested. */ + if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) || + (res == '*') || (res == '#')) { + /* Just return result as to the previous application as if it had been dialed */ + ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res); + break; + } + switch(res) { + case MACRO_EXIT_RESULT: + res = 0; + goto out; + case AST_PBX_KEEPALIVE: + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name); + goto out; + break; + default: + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro); + dead = 1; + goto out; + } + } + + ast_log(LOG_DEBUG, "Executed application: %s\n", runningapp); + + if (!strcasecmp(runningapp, "GOSUB")) { + gosub_level++; + ast_log(LOG_DEBUG, "Incrementing gosub_level\n"); + } else if (!strcasecmp(runningapp, "GOSUBIF")) { + char tmp2[1024] = "", *cond, *app, *app2 = tmp2; + pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1); + cond = strsep(&app2, "?"); + app = strsep(&app2, ":"); + if (pbx_checkcondition(cond)) { + if (!ast_strlen_zero(app)) { + gosub_level++; + ast_log(LOG_DEBUG, "Incrementing gosub_level\n"); + } + } else { + if (!ast_strlen_zero(app2)) { + gosub_level++; + ast_log(LOG_DEBUG, "Incrementing gosub_level\n"); + } + } + } else if (!strcasecmp(runningapp, "RETURN")) { + gosub_level--; + ast_log(LOG_DEBUG, "Decrementing gosub_level\n"); + } else if (!strcasecmp(runningapp, "STACKPOP")) { + gosub_level--; + ast_log(LOG_DEBUG, "Decrementing gosub_level\n"); + } else if (!strncasecmp(runningapp, "EXEC", 4)) { + /* Must evaluate args to find actual app */ + char tmp2[1024] = "", *tmp3 = NULL; + pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1); + if (!strcasecmp(runningapp, "EXECIF")) { + tmp3 = strchr(tmp2, '|'); + if (tmp3) + *tmp3++ = '\0'; + if (!pbx_checkcondition(tmp2)) + tmp3 = NULL; + } else + tmp3 = tmp2; + + if (tmp3) + ast_log(LOG_DEBUG, "Last app: %s\n", tmp3); + + if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) { + gosub_level++; + ast_log(LOG_DEBUG, "Incrementing gosub_level\n"); + } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) { + gosub_level--; + ast_log(LOG_DEBUG, "Decrementing gosub_level\n"); + } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) { + gosub_level--; + ast_log(LOG_DEBUG, "Decrementing gosub_level\n"); + } + } + + if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro); + break; + } + + /* don't stop executing extensions when we're in "h" */ + if (chan->_softhangup && !inhangup) { + ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", + chan->exten, chan->macroexten, chan->priority); + goto out; + } + chan->priority++; + } + out: + /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */ + snprintf(depthc, sizeof(depthc), "%d", depth); + if (!dead) { + pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); + ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP); + } + + for (x = 1; x < argc; x++) { + /* Restore old arguments and delete ours */ + snprintf(varname, sizeof(varname), "ARG%d", x); + if (oldargs[x]) { + if (!dead) + pbx_builtin_setvar_helper(chan, varname, oldargs[x]); + free(oldargs[x]); + } else if (!dead) { + pbx_builtin_setvar_helper(chan, varname, NULL); + } + } + + /* Restore macro variables */ + if (!dead) { + pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten); + pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context); + pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority); + } + if (save_macro_exten) + free(save_macro_exten); + if (save_macro_context) + free(save_macro_context); + if (save_macro_priority) + free(save_macro_priority); + + if (!dead && setmacrocontext) { + chan->macrocontext[0] = '\0'; + chan->macroexten[0] = '\0'; + chan->macropriority = 0; + } + + if (!dead && !strcasecmp(chan->context, fullmacro)) { + /* If we're leaving the macro normally, restore original information */ + chan->priority = oldpriority; + ast_copy_string(chan->context, oldcontext, sizeof(chan->context)); + if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) { + /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */ + const char *offsets; + ast_copy_string(chan->exten, oldexten, sizeof(chan->exten)); + if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) { + /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue + normally if there is any problem */ + if (sscanf(offsets, "%d", &offset) == 1) { + if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) { + chan->priority += offset; + } + } + } + } + } + + if (!dead) + pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset); + if (save_macro_offset) + free(save_macro_offset); + + /* Unlock the macro */ + if (exclusive) { + ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro); + if (ast_context_unlockmacro(fullmacro)) { + ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro); + res = 0; + } + } + + ast_module_user_remove(u); + + return res; +} + +static int macro_exec(struct ast_channel *chan, void *data) +{ + return _macro_exec(chan, data, 0); +} + +static int macroexclusive_exec(struct ast_channel *chan, void *data) +{ + return _macro_exec(chan, data, 1); +} + +static int macroif_exec(struct ast_channel *chan, void *data) +{ + char *expr = NULL, *label_a = NULL, *label_b = NULL; + int res = 0; + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + if (!(expr = ast_strdupa(data))) { + ast_module_user_remove(u); + return -1; + } + + if ((label_a = strchr(expr, '?'))) { + *label_a = '\0'; + label_a++; + if ((label_b = strchr(label_a, ':'))) { + *label_b = '\0'; + label_b++; + } + if (pbx_checkcondition(expr)) + res = macro_exec(chan, label_a); + else if (label_b) + res = macro_exec(chan, label_b); + } else + ast_log(LOG_WARNING, "Invalid Syntax.\n"); + + ast_module_user_remove(u); + + return res; +} + +static int macro_exit_exec(struct ast_channel *chan, void *data) +{ + return MACRO_EXIT_RESULT; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(if_app); + res |= ast_unregister_application(exit_app); + res |= ast_unregister_application(app); + res |= ast_unregister_application(exclusive_app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res; + + res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip); + res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip); + res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip); + res |= ast_register_application(app, macro_exec, synopsis, descrip); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros"); diff --git a/asterisk/apps/app_meetme.c b/asterisk/apps/app_meetme.c new file mode 100644 index 00000000..c8fa0ef5 --- /dev/null +++ b/asterisk/apps/app_meetme.c @@ -0,0 +1,4894 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2007, Digium, Inc. + * + * Mark Spencer + * + * SLA Implementation by: + * 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 Meet me conference bridge and Shared Line Appearances + * + * \author Mark Spencer + * \author (SLA) Russell Bryant + * + * \ingroup applications + */ + +/*** MODULEINFO + dahdi + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/app.h" +#include "asterisk/dsp.h" +#include "asterisk/musiconhold.h" +#include "asterisk/manager.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/say.h" +#include "asterisk/utils.h" +#include "asterisk/translate.h" +#include "asterisk/ulaw.h" +#include "asterisk/astobj.h" +#include "asterisk/devicestate.h" +#include "asterisk/dial.h" +#include "asterisk/causes.h" + +#include "asterisk/dahdi_compat.h" + +#include "enter.h" +#include "leave.h" + +#define CONFIG_FILE_NAME "meetme.conf" +#define SLA_CONFIG_FILE "sla.conf" + +/*! each buffer is 20ms, so this is 640ms total */ +#define DEFAULT_AUDIO_BUFFERS 32 + +enum { + ADMINFLAG_MUTED = (1 << 1), /*!< User is muted */ + ADMINFLAG_SELFMUTED = (1 << 2), /*!< User muted self */ + ADMINFLAG_KICKME = (1 << 3) /*!< User has been kicked */ +}; + +#define MEETME_DELAYDETECTTALK 300 +#define MEETME_DELAYDETECTENDTALK 1000 + +#define AST_FRAME_BITS 32 + +enum volume_action { + VOL_UP, + VOL_DOWN +}; + +enum entrance_sound { + ENTER, + LEAVE +}; + +enum recording_state { + MEETME_RECORD_OFF, + MEETME_RECORD_STARTED, + MEETME_RECORD_ACTIVE, + MEETME_RECORD_TERMINATE +}; + +#define CONF_SIZE 320 + +enum { + /*! user has admin access on the conference */ + CONFFLAG_ADMIN = (1 << 0), + /*! If set the user can only receive audio from the conference */ + CONFFLAG_MONITOR = (1 << 1), + /*! If set asterisk will exit conference when '#' is pressed */ + CONFFLAG_POUNDEXIT = (1 << 2), + /*! If set asterisk will provide a menu to the user when '*' is pressed */ + CONFFLAG_STARMENU = (1 << 3), + /*! If set the use can only send audio to the conference */ + CONFFLAG_TALKER = (1 << 4), + /*! If set there will be no enter or leave sounds */ + CONFFLAG_QUIET = (1 << 5), + /*! If set, when user joins the conference, they will be told the number + * of users that are already in */ + CONFFLAG_ANNOUNCEUSERCOUNT = (1 << 6), + /*! Set to run AGI Script in Background */ + CONFFLAG_AGI = (1 << 7), + /*! Set to have music on hold when user is alone in conference */ + CONFFLAG_MOH = (1 << 8), + /*! If set the MeetMe will return if all marked with this flag left */ + CONFFLAG_MARKEDEXIT = (1 << 9), + /*! If set, the MeetMe will wait until a marked user enters */ + CONFFLAG_WAITMARKED = (1 << 10), + /*! If set, the MeetMe will exit to the specified context */ + CONFFLAG_EXIT_CONTEXT = (1 << 11), + /*! If set, the user will be marked */ + CONFFLAG_MARKEDUSER = (1 << 12), + /*! If set, user will be ask record name on entry of conference */ + CONFFLAG_INTROUSER = (1 << 13), + /*! If set, the MeetMe will be recorded */ + CONFFLAG_RECORDCONF = (1<< 14), + /*! If set, the user will be monitored if the user is talking or not */ + CONFFLAG_MONITORTALKER = (1 << 15), + CONFFLAG_DYNAMIC = (1 << 16), + CONFFLAG_DYNAMICPIN = (1 << 17), + CONFFLAG_EMPTY = (1 << 18), + CONFFLAG_EMPTYNOPIN = (1 << 19), + CONFFLAG_ALWAYSPROMPT = (1 << 20), + /*! If set, treats talking users as muted users */ + CONFFLAG_OPTIMIZETALKER = (1 << 21), + /*! If set, won't speak the extra prompt when the first person + * enters the conference */ + CONFFLAG_NOONLYPERSON = (1 << 22), + /*! If set, user will be asked to record name on entry of conference + * without review */ + CONFFLAG_INTROUSERNOREVIEW = (1 << 23), + /*! If set, the user will be initially self-muted */ + CONFFLAG_STARTMUTED = (1 << 24), + /*! Pass DTMF through the conference */ + CONFFLAG_PASS_DTMF = (1 << 25), + /*! This is a SLA station. (Only for use by the SLA applications.) */ + CONFFLAG_SLA_STATION = (1 << 26), + /*! This is a SLA trunk. (Only for use by the SLA applications.) */ + CONFFLAG_SLA_TRUNK = (1 << 27), +}; + +enum { + OPT_ARG_WAITMARKED = 0, + OPT_ARG_ARRAY_SIZE = 1, +}; + +AST_APP_OPTIONS(meetme_opts, BEGIN_OPTIONS + AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ), + AST_APP_OPTION('a', CONFFLAG_ADMIN ), + AST_APP_OPTION('b', CONFFLAG_AGI ), + AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ), + AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ), + AST_APP_OPTION('d', CONFFLAG_DYNAMIC ), + AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ), + AST_APP_OPTION('e', CONFFLAG_EMPTY ), + AST_APP_OPTION('F', CONFFLAG_PASS_DTMF ), + AST_APP_OPTION('i', CONFFLAG_INTROUSER ), + AST_APP_OPTION('I', CONFFLAG_INTROUSERNOREVIEW ), + AST_APP_OPTION('M', CONFFLAG_MOH ), + AST_APP_OPTION('m', CONFFLAG_STARTMUTED ), + AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ), + AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ), + AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ), + AST_APP_OPTION('q', CONFFLAG_QUIET ), + AST_APP_OPTION('r', CONFFLAG_RECORDCONF ), + AST_APP_OPTION('s', CONFFLAG_STARMENU ), + AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ), + AST_APP_OPTION('l', CONFFLAG_MONITOR ), + AST_APP_OPTION('t', CONFFLAG_TALKER ), + AST_APP_OPTION_ARG('w', CONFFLAG_WAITMARKED, OPT_ARG_WAITMARKED ), + AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ), + AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ), + AST_APP_OPTION('1', CONFFLAG_NOONLYPERSON ), +END_OPTIONS ); + +static const char *app = "MeetMe"; +static const char *app2 = "MeetMeCount"; +static const char *app3 = "MeetMeAdmin"; +static const char *slastation_app = "SLAStation"; +static const char *slatrunk_app = "SLATrunk"; + +static const char *synopsis = "MeetMe conference bridge"; +static const char *synopsis2 = "MeetMe participant count"; +static const char *synopsis3 = "MeetMe conference Administration"; +static const char *slastation_synopsis = "Shared Line Appearance Station"; +static const char *slatrunk_synopsis = "Shared Line Appearance Trunk"; + +static const char *descrip = +" MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe\n" +"conference. If the conference number is omitted, the user will be prompted\n" +"to enter one. User can exit the conference by hangup, or if the 'p' option\n" +"is specified, by pressing '#'.\n" +"Please note: The DAHDI kernel modules and at least one hardware driver (or dahdi_dummy)\n" +" must be present for conferencing to operate properly. In addition, the chan_dahdi\n" +" channel driver must be loaded for the 'i' and 'r' options to operate at all.\n\n" +"The option string may contain zero or more of the following characters:\n" +" 'a' -- set admin mode\n" +" 'A' -- set marked mode\n" +" 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n" +" Default: conf-background.agi (Note: This does not work with\n" +" non-DAHDI channels in the same conference)\n" +" 'c' -- announce user(s) count on joining a conference\n" +" 'd' -- dynamically add conference\n" +" 'D' -- dynamically add conference, prompting for a PIN\n" +" 'e' -- select an empty conference\n" +" 'E' -- select an empty pinless conference\n" +" 'F' -- Pass DTMF through the conference.\n" +" 'i' -- announce user join/leave with review\n" +" 'I' -- announce user join/leave without review\n" +" 'l' -- set listen only mode (Listen only, no talking)\n" +" 'm' -- set initially muted\n" +" 'M' -- enable music on hold when the conference has a single caller\n" +" 'o' -- set talker optimization - treats talkers who aren't speaking as\n" +" being muted, meaning (a) No encode is done on transmission and\n" +" (b) Received audio that is not registered as talking is omitted\n" +" causing no buildup in background noise. Note that this option\n" +" will be removed in 1.6 and enabled by default.\n" +" 'p' -- allow user to exit the conference by pressing '#'\n" +" 'P' -- always prompt for the pin even if it is specified\n" +" 'q' -- quiet mode (don't play enter/leave sounds)\n" +" 'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n" +" using format ${MEETME_RECORDINGFORMAT}). Default filename is\n" +" meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is\n" +" wav.\n" +" 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n" +" 't' -- set talk only mode. (Talk only, no listening)\n" +" 'T' -- set talker detection (sent to manager interface and meetme list)\n" +" 'w[()]'\n" +" -- wait until the marked user enters the conference\n" +" 'x' -- close the conference when last marked user exits\n" +" 'X' -- allow user to exit the conference by entering a valid single\n" +" digit extension ${MEETME_EXIT_CONTEXT} or the current context\n" +" if that variable is not defined.\n" +" '1' -- do not play message when first person enters\n"; + +static const char *descrip2 = +" MeetMeCount(confno[|var]): Plays back the number of users in the specified\n" +"MeetMe conference. If var is specified, playback will be skipped and the value\n" +"will be returned in the variable. Upon app completion, MeetMeCount will hangup\n" +"the channel, unless priority n+1 exists, in which case priority progress will\n" +"continue.\n"; + +static const char *descrip3 = +" MeetMeAdmin(confno,command[,user]): Run admin command for conference\n" +" 'e' -- Eject last user that joined\n" +" 'k' -- Kick one user out of conference\n" +" 'K' -- Kick all users out of conference\n" +" 'l' -- Unlock conference\n" +" 'L' -- Lock conference\n" +" 'm' -- Unmute one user\n" +" 'M' -- Mute one user\n" +" 'n' -- Unmute all users in the conference\n" +" 'N' -- Mute all non-admin users in the conference\n" +" 'r' -- Reset one user's volume settings\n" +" 'R' -- Reset all users volume settings\n" +" 's' -- Lower entire conference speaking volume\n" +" 'S' -- Raise entire conference speaking volume\n" +" 't' -- Lower one user's talk volume\n" +" 'T' -- Raise one user's talk volume\n" +" 'u' -- Lower one user's listen volume\n" +" 'U' -- Raise one user's listen volume\n" +" 'v' -- Lower entire conference listening volume\n" +" 'V' -- Raise entire conference listening volume\n" +""; + +static const char *slastation_desc = +" SLAStation(station):\n" +"This application should be executed by an SLA station. The argument depends\n" +"on how the call was initiated. If the phone was just taken off hook, then\n" +"the argument \"station\" should be just the station name. If the call was\n" +"initiated by pressing a line key, then the station name should be preceded\n" +"by an underscore and the trunk name associated with that line button.\n" +"For example: \"station1_line1\"." +" On exit, this application will set the variable SLASTATION_STATUS to\n" +"one of the following values:\n" +" FAILURE | CONGESTION | SUCCESS\n" +""; + +static const char *slatrunk_desc = +" SLATrunk(trunk):\n" +"This application should be executed by an SLA trunk on an inbound call.\n" +"The channel calling this application should correspond to the SLA trunk\n" +"with the name \"trunk\" that is being passed as an argument.\n" +" On exit, this application will set the variable SLATRUNK_STATUS to\n" +"one of the following values:\n" +" FAILURE | SUCCESS | UNANSWERED | RINGTIMEOUT\n" +""; + +#define MAX_CONFNUM 80 +#define MAX_PIN 80 + +/*! \brief The MeetMe Conference object */ +struct ast_conference { + ast_mutex_t playlock; /*!< Conference specific lock (players) */ + ast_mutex_t listenlock; /*!< Conference specific lock (listeners) */ + char confno[MAX_CONFNUM]; /*!< Conference */ + struct ast_channel *chan; /*!< Announcements channel */ + struct ast_channel *lchan; /*!< Listen/Record channel */ + int fd; /*!< Announcements fd */ + int zapconf; /*!< Zaptel Conf # */ + int users; /*!< Number of active users */ + int markedusers; /*!< Number of marked users */ + time_t start; /*!< Start time (s) */ + int refcount; /*!< reference count of usage */ + enum recording_state recording:2; /*!< recording status */ + unsigned int isdynamic:1; /*!< Created on the fly? */ + unsigned int locked:1; /*!< Is the conference locked? */ + pthread_t recordthread; /*!< thread for recording */ + ast_mutex_t recordthreadlock; /*!< control threads trying to start recordthread */ + pthread_attr_t attr; /*!< thread attribute */ + const char *recordingfilename; /*!< Filename to record the Conference into */ + const char *recordingformat; /*!< Format to record the Conference in */ + char pin[MAX_PIN]; /*!< If protected by a PIN */ + char pinadmin[MAX_PIN]; /*!< If protected by a admin PIN */ + struct ast_frame *transframe[32]; + struct ast_frame *origframe; + struct ast_trans_pvt *transpath[32]; + AST_LIST_HEAD_NOLOCK(, ast_conf_user) userlist; + AST_LIST_ENTRY(ast_conference) list; +}; + +static AST_LIST_HEAD_STATIC(confs, ast_conference); + +static unsigned int conf_map[1024] = {0, }; + +struct volume { + int desired; /*!< Desired volume adjustment */ + int actual; /*!< Actual volume adjustment (for channels that can't adjust) */ +}; + +struct ast_conf_user { + int user_no; /*!< User Number */ + int userflags; /*!< Flags as set in the conference */ + int adminflags; /*!< Flags set by the Admin */ + struct ast_channel *chan; /*!< Connected channel */ + int talking; /*!< Is user talking */ + int zapchannel; /*!< Is a Zaptel channel */ + char usrvalue[50]; /*!< Custom User Value */ + char namerecloc[PATH_MAX]; /*!< Name Recorded file Location */ + time_t jointime; /*!< Time the user joined the conference */ + struct volume talk; + struct volume listen; + AST_LIST_ENTRY(ast_conf_user) list; +}; + +enum sla_which_trunk_refs { + ALL_TRUNK_REFS, + INACTIVE_TRUNK_REFS, +}; + +enum sla_trunk_state { + SLA_TRUNK_STATE_IDLE, + SLA_TRUNK_STATE_RINGING, + SLA_TRUNK_STATE_UP, + SLA_TRUNK_STATE_ONHOLD, + SLA_TRUNK_STATE_ONHOLD_BYME, +}; + +enum sla_hold_access { + /*! This means that any station can put it on hold, and any station + * can retrieve the call from hold. */ + SLA_HOLD_OPEN, + /*! This means that only the station that put the call on hold may + * retrieve it from hold. */ + SLA_HOLD_PRIVATE, +}; + +struct sla_trunk_ref; + +struct sla_station { + AST_RWLIST_ENTRY(sla_station) entry; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); + AST_STRING_FIELD(device); + AST_STRING_FIELD(autocontext); + ); + AST_LIST_HEAD_NOLOCK(, sla_trunk_ref) trunks; + struct ast_dial *dial; + /*! Ring timeout for this station, for any trunk. If a ring timeout + * is set for a specific trunk on this station, that will take + * priority over this value. */ + unsigned int ring_timeout; + /*! Ring delay for this station, for any trunk. If a ring delay + * is set for a specific trunk on this station, that will take + * priority over this value. */ + unsigned int ring_delay; + /*! This option uses the values in the sla_hold_access enum and sets the + * access control type for hold on this station. */ + unsigned int hold_access:1; +}; + +struct sla_station_ref { + AST_LIST_ENTRY(sla_station_ref) entry; + struct sla_station *station; +}; + +struct sla_trunk { + AST_RWLIST_ENTRY(sla_trunk) entry; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); + AST_STRING_FIELD(device); + AST_STRING_FIELD(autocontext); + ); + AST_LIST_HEAD_NOLOCK(, sla_station_ref) stations; + /*! Number of stations that use this trunk */ + unsigned int num_stations; + /*! Number of stations currently on a call with this trunk */ + unsigned int active_stations; + /*! Number of stations that have this trunk on hold. */ + unsigned int hold_stations; + struct ast_channel *chan; + unsigned int ring_timeout; + /*! If set to 1, no station will be able to join an active call with + * this trunk. */ + unsigned int barge_disabled:1; + /*! This option uses the values in the sla_hold_access enum and sets the + * access control type for hold on this trunk. */ + unsigned int hold_access:1; + /*! Whether this trunk is currently on hold, meaning that once a station + * connects to it, the trunk channel needs to have UNHOLD indicated to it. */ + unsigned int on_hold:1; +}; + +struct sla_trunk_ref { + AST_LIST_ENTRY(sla_trunk_ref) entry; + struct sla_trunk *trunk; + enum sla_trunk_state state; + struct ast_channel *chan; + /*! Ring timeout to use when this trunk is ringing on this specific + * station. This takes higher priority than a ring timeout set at + * the station level. */ + unsigned int ring_timeout; + /*! Ring delay to use when this trunk is ringing on this specific + * station. This takes higher priority than a ring delay set at + * the station level. */ + unsigned int ring_delay; +}; + +static AST_RWLIST_HEAD_STATIC(sla_stations, sla_station); +static AST_RWLIST_HEAD_STATIC(sla_trunks, sla_trunk); + +static const char sla_registrar[] = "SLA"; + +/*! \brief Event types that can be queued up for the SLA thread */ +enum sla_event_type { + /*! A station has put the call on hold */ + SLA_EVENT_HOLD, + /*! The state of a dial has changed */ + SLA_EVENT_DIAL_STATE, + /*! The state of a ringing trunk has changed */ + SLA_EVENT_RINGING_TRUNK, +}; + +struct sla_event { + enum sla_event_type type; + struct sla_station *station; + struct sla_trunk_ref *trunk_ref; + AST_LIST_ENTRY(sla_event) entry; +}; + +/*! \brief A station that failed to be dialed + * \note Only used by the SLA thread. */ +struct sla_failed_station { + struct sla_station *station; + struct timeval last_try; + AST_LIST_ENTRY(sla_failed_station) entry; +}; + +/*! \brief A trunk that is ringing */ +struct sla_ringing_trunk { + struct sla_trunk *trunk; + /*! The time that this trunk started ringing */ + struct timeval ring_begin; + AST_LIST_HEAD_NOLOCK(, sla_station_ref) timed_out_stations; + AST_LIST_ENTRY(sla_ringing_trunk) entry; +}; + +enum sla_station_hangup { + SLA_STATION_HANGUP_NORMAL, + SLA_STATION_HANGUP_TIMEOUT, +}; + +/*! \brief A station that is ringing */ +struct sla_ringing_station { + struct sla_station *station; + /*! The time that this station started ringing */ + struct timeval ring_begin; + AST_LIST_ENTRY(sla_ringing_station) entry; +}; + +/*! + * \brief A structure for data used by the sla thread + */ +static struct { + /*! The SLA thread ID */ + pthread_t thread; + ast_cond_t cond; + ast_mutex_t lock; + AST_LIST_HEAD_NOLOCK(, sla_ringing_trunk) ringing_trunks; + AST_LIST_HEAD_NOLOCK(, sla_ringing_station) ringing_stations; + AST_LIST_HEAD_NOLOCK(, sla_failed_station) failed_stations; + AST_LIST_HEAD_NOLOCK(, sla_event) event_q; + unsigned int stop:1; + /*! Attempt to handle CallerID, even though it is known not to work + * properly in some situations. */ + unsigned int attempt_callerid:1; +} sla = { + .thread = AST_PTHREADT_NULL, +}; + +/*! The number of audio buffers to be allocated on pseudo channels + * when in a conference */ +static int audio_buffers; + +/*! Map 'volume' levels from -5 through +5 into + * decibel (dB) settings for channel drivers + * Note: these are not a straight linear-to-dB + * conversion... the numbers have been modified + * to give the user a better level of adjustability + */ +static char const gain_map[] = { + -15, + -13, + -10, + -6, + 0, + 0, + 0, + 6, + 10, + 13, + 15, +}; + + +static int admin_exec(struct ast_channel *chan, void *data); +static void *recordthread(void *args); + +static char *istalking(int x) +{ + if (x > 0) + return "(talking)"; + else if (x < 0) + return "(unmonitored)"; + else + return "(not talking)"; +} + +static int careful_write(int fd, unsigned char *data, int len, int block) +{ + int res; + int x; + + while (len) { + if (block) { + x = DAHDI_IOMUX_WRITE | DAHDI_IOMUX_SIGEVENT; + res = ioctl(fd, DAHDI_IOMUX, &x); + } else + res = 0; + if (res >= 0) + res = write(fd, data, len); + if (res < 1) { + if (errno != EAGAIN) { + ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno)); + return -1; + } else + return 0; + } + len -= res; + data += res; + } + + return 0; +} + +static int set_talk_volume(struct ast_conf_user *user, int volume) +{ + char gain_adjust; + + /* attempt to make the adjustment in the channel driver; + if successful, don't adjust in the frame reading routine + */ + gain_adjust = gain_map[volume + 5]; + + return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0); +} + +static int set_listen_volume(struct ast_conf_user *user, int volume) +{ + char gain_adjust; + + /* attempt to make the adjustment in the channel driver; + if successful, don't adjust in the frame reading routine + */ + gain_adjust = gain_map[volume + 5]; + + return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0); +} + +static void tweak_volume(struct volume *vol, enum volume_action action) +{ + switch (action) { + case VOL_UP: + switch (vol->desired) { + case 5: + break; + case 0: + vol->desired = 2; + break; + case -2: + vol->desired = 0; + break; + default: + vol->desired++; + break; + } + break; + case VOL_DOWN: + switch (vol->desired) { + case -5: + break; + case 2: + vol->desired = 0; + break; + case 0: + vol->desired = -2; + break; + default: + vol->desired--; + break; + } + } +} + +static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action) +{ + tweak_volume(&user->talk, action); + /* attempt to make the adjustment in the channel driver; + if successful, don't adjust in the frame reading routine + */ + if (!set_talk_volume(user, user->talk.desired)) + user->talk.actual = 0; + else + user->talk.actual = user->talk.desired; +} + +static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action) +{ + tweak_volume(&user->listen, action); + /* attempt to make the adjustment in the channel driver; + if successful, don't adjust in the frame reading routine + */ + if (!set_listen_volume(user, user->listen.desired)) + user->listen.actual = 0; + else + user->listen.actual = user->listen.desired; +} + +static void reset_volumes(struct ast_conf_user *user) +{ + signed char zero_volume = 0; + + ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0); + ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0); +} + +static void conf_play(struct ast_channel *chan, struct ast_conference *conf, enum entrance_sound sound) +{ + unsigned char *data; + int len; + int res = -1; + + if (!chan->_softhangup) + res = ast_autoservice_start(chan); + + AST_LIST_LOCK(&confs); + + switch(sound) { + case ENTER: + data = enter; + len = sizeof(enter); + break; + case LEAVE: + data = leave; + len = sizeof(leave); + break; + default: + data = NULL; + len = 0; + } + if (data) { + careful_write(conf->fd, data, len, 1); + } + + AST_LIST_UNLOCK(&confs); + + if (!res) + ast_autoservice_stop(chan); +} + +/*! + * \brief Find or create a conference + * + * \param confno The conference name/number + * \param pin The regular user pin + * \param pinadmin The admin pin + * \param make Make the conf if it doesn't exist + * \param dynamic Mark the newly created conference as dynamic + * \param refcount How many references to mark on the conference + * + * \return A pointer to the conference struct, or NULL if it wasn't found and + * make or dynamic were not set. + */ +static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount) +{ + struct ast_conference *cnf; + struct dahdi_confinfo ztc = { 0, }; + int confno_int = 0; + + AST_LIST_LOCK(&confs); + + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strcmp(confno, cnf->confno)) + break; + } + + if (cnf || (!make && !dynamic)) + goto cnfout; + + /* Make a new one */ + if (!(cnf = ast_calloc(1, sizeof(*cnf)))) + goto cnfout; + + ast_mutex_init(&cnf->playlock); + ast_mutex_init(&cnf->listenlock); + cnf->recordthread = AST_PTHREADT_NULL; + ast_mutex_init(&cnf->recordthreadlock); + ast_copy_string(cnf->confno, confno, sizeof(cnf->confno)); + ast_copy_string(cnf->pin, pin, sizeof(cnf->pin)); + ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin)); + + /* Setup a new zap conference */ + ztc.confno = -1; + ztc.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON; +#ifdef HAVE_ZAPTEL + cnf->fd = open("/dev/zap/pseudo", O_RDWR); +#else + cnf->fd = open("/dev/dahdi/pseudo", O_RDWR); +#endif + if (cnf->fd < 0 || ioctl(cnf->fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Unable to open pseudo device\n"); + if (cnf->fd >= 0) + close(cnf->fd); + free(cnf); + cnf = NULL; + goto cnfout; + } + + cnf->zapconf = ztc.confno; + + /* Setup a new channel for playback of audio files */ + cnf->chan = ast_request(dahdi_chan_name, AST_FORMAT_SLINEAR, "pseudo", NULL); + if (cnf->chan) { + ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR); + ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR); + ztc.chan = 0; + ztc.confno = cnf->zapconf; + ztc.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON; + if (ioctl(cnf->chan->fds[0], DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + if (cnf->chan) + ast_hangup(cnf->chan); + else + close(cnf->fd); + free(cnf); + cnf = NULL; + goto cnfout; + } + } + + /* Fill the conference struct */ + cnf->start = time(NULL); + cnf->isdynamic = dynamic ? 1 : 0; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno); + AST_LIST_INSERT_HEAD(&confs, cnf, list); + + /* Reserve conference number in map */ + if ((sscanf(cnf->confno, "%d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024)) + conf_map[confno_int] = 1; + +cnfout: + if (cnf) + ast_atomic_fetchadd_int(&cnf->refcount, refcount); + + AST_LIST_UNLOCK(&confs); + + return cnf; +} + +static int meetme_cmd(int fd, int argc, char **argv) +{ + /* Process the command */ + struct ast_conference *cnf; + struct ast_conf_user *user; + int hr, min, sec; + int i = 0, total = 0; + time_t now; + char *header_format = "%-14s %-14s %-10s %-8s %-8s\n"; + char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n"; + char cmdline[1024] = ""; + + if (argc > 8) + ast_cli(fd, "Invalid Arguments.\n"); + /* Check for length so no buffer will overflow... */ + for (i = 0; i < argc; i++) { + if (strlen(argv[i]) > 100) + ast_cli(fd, "Invalid Arguments.\n"); + } + if (argc == 1) { + /* 'MeetMe': List all the conferences */ + now = time(NULL); + AST_LIST_LOCK(&confs); + if (AST_LIST_EMPTY(&confs)) { + ast_cli(fd, "No active MeetMe conferences.\n"); + AST_LIST_UNLOCK(&confs); + return RESULT_SUCCESS; + } + ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation"); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (cnf->markedusers == 0) + strcpy(cmdline, "N/A "); + else + snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers); + hr = (now - cnf->start) / 3600; + min = ((now - cnf->start) % 3600) / 60; + sec = (now - cnf->start) % 60; + + ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static"); + + total += cnf->users; + } + AST_LIST_UNLOCK(&confs); + ast_cli(fd, "* Total number of MeetMe users: %d\n", total); + return RESULT_SUCCESS; + } + if (argc < 3) + return RESULT_SHOWUSAGE; + ast_copy_string(cmdline, argv[2], sizeof(cmdline)); /* Argv 2: conference number */ + if (strstr(argv[1], "lock")) { + if (strcmp(argv[1], "lock") == 0) { + /* Lock */ + strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1); + } else { + /* Unlock */ + strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1); + } + } else if (strstr(argv[1], "mute")) { + if (argc < 4) + return RESULT_SHOWUSAGE; + if (strcmp(argv[1], "mute") == 0) { + /* Mute */ + if (strcmp(argv[3], "all") == 0) { + strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1); + } else { + strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1); + } + } else { + /* Unmute */ + if (strcmp(argv[3], "all") == 0) { + strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1); + } else { + strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1); + } + } + } else if (strcmp(argv[1], "kick") == 0) { + if (argc < 4) + return RESULT_SHOWUSAGE; + if (strcmp(argv[3], "all") == 0) { + /* Kick all */ + strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1); + } else { + /* Kick a single user */ + strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1); + } + } else if(strcmp(argv[1], "list") == 0) { + int concise = ( 4 == argc && ( !strcasecmp(argv[3], "concise") ) ); + /* List all the users in a conference */ + if (AST_LIST_EMPTY(&confs)) { + if ( !concise ) + ast_cli(fd, "No active conferences.\n"); + return RESULT_SUCCESS; + } + /* Find the right conference */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (strcmp(cnf->confno, argv[2]) == 0) + break; + } + if (!cnf) { + if ( !concise ) + ast_cli(fd, "No such conference: %s.\n",argv[2]); + AST_LIST_UNLOCK(&confs); + return RESULT_SUCCESS; + } + /* Show all the users */ + time(&now); + AST_LIST_TRAVERSE(&cnf->userlist, user, list) { + hr = (now - user->jointime) / 3600; + min = ((now - user->jointime) % 3600) / 60; + sec = (now - user->jointime) % 60; + if ( !concise ) + ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %02d:%02d:%02d\n", + user->user_no, + S_OR(user->chan->cid.cid_num, ""), + S_OR(user->chan->cid.cid_name, ""), + user->chan->name, + user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "", + user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "", + user->adminflags & ADMINFLAG_MUTED ? "(Admin Muted)" : user->adminflags & ADMINFLAG_SELFMUTED ? "(Muted)" : "", + istalking(user->talking), hr, min, sec); + else + ast_cli(fd, "%d!%s!%s!%s!%s!%s!%s!%d!%02d:%02d:%02d\n", + user->user_no, + S_OR(user->chan->cid.cid_num, ""), + S_OR(user->chan->cid.cid_name, ""), + user->chan->name, + user->userflags & CONFFLAG_ADMIN ? "1" : "", + user->userflags & CONFFLAG_MONITOR ? "1" : "", + user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED) ? "1" : "", + user->talking, hr, min, sec); + + } + if ( !concise ) + ast_cli(fd,"%d users in that conference.\n",cnf->users); + AST_LIST_UNLOCK(&confs); + return RESULT_SUCCESS; + } else + return RESULT_SHOWUSAGE; + ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline); + admin_exec(NULL, cmdline); + + return 0; +} + +static char *complete_meetmecmd(const char *line, const char *word, int pos, int state) +{ + static char *cmds[] = {"lock", "unlock", "mute", "unmute", "kick", "list", NULL}; + + int len = strlen(word); + int which = 0; + struct ast_conference *cnf = NULL; + struct ast_conf_user *usr = NULL; + char *confno = NULL; + char usrno[50] = ""; + char *myline, *ret = NULL; + + if (pos == 1) { /* Command */ + return ast_cli_complete(word, cmds, state); + } else if (pos == 2) { /* Conference Number */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strncasecmp(word, cnf->confno, len) && ++which > state) { + ret = cnf->confno; + break; + } + } + ret = ast_strdup(ret); /* dup before releasing the lock */ + AST_LIST_UNLOCK(&confs); + return ret; + } else if (pos == 3) { + /* User Number || Conf Command option*/ + if (strstr(line, "mute") || strstr(line, "kick")) { + if (state == 0 && (strstr(line, "kick") || strstr(line,"mute")) && !strncasecmp(word, "all", len)) + return strdup("all"); + which++; + AST_LIST_LOCK(&confs); + + /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */ + myline = ast_strdupa(line); + if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) { + while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0)) + ; + } + + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strcmp(confno, cnf->confno)) + break; + } + + if (cnf) { + /* Search for the user */ + AST_LIST_TRAVERSE(&cnf->userlist, usr, list) { + snprintf(usrno, sizeof(usrno), "%d", usr->user_no); + if (!strncasecmp(word, usrno, len) && ++which > state) + break; + } + } + AST_LIST_UNLOCK(&confs); + return usr ? strdup(usrno) : NULL; + } else if ( strstr(line, "list") && ( 0 == state ) ) + return strdup("concise"); + } + + return NULL; +} + +static char meetme_usage[] = +"Usage: meetme (un)lock|(un)mute|kick|list [concise] \n" +" Executes a command for the conference or on a conferee\n"; + +static const char *sla_hold_str(unsigned int hold_access) +{ + const char *hold = "Unknown"; + + switch (hold_access) { + case SLA_HOLD_OPEN: + hold = "Open"; + break; + case SLA_HOLD_PRIVATE: + hold = "Private"; + default: + break; + } + + return hold; +} + +static int sla_show_trunks(int fd, int argc, char **argv) +{ + const struct sla_trunk *trunk; + + ast_cli(fd, "\n" + "=============================================================\n" + "=== Configured SLA Trunks ===================================\n" + "=============================================================\n" + "===\n"); + AST_RWLIST_RDLOCK(&sla_trunks); + AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) { + struct sla_station_ref *station_ref; + char ring_timeout[16] = "(none)"; + if (trunk->ring_timeout) + snprintf(ring_timeout, sizeof(ring_timeout), "%u Seconds", trunk->ring_timeout); + ast_cli(fd, "=== ---------------------------------------------------------\n" + "=== Trunk Name: %s\n" + "=== ==> Device: %s\n" + "=== ==> AutoContext: %s\n" + "=== ==> RingTimeout: %s\n" + "=== ==> BargeAllowed: %s\n" + "=== ==> HoldAccess: %s\n" + "=== ==> Stations ...\n", + trunk->name, trunk->device, + S_OR(trunk->autocontext, "(none)"), + ring_timeout, + trunk->barge_disabled ? "No" : "Yes", + sla_hold_str(trunk->hold_access)); + AST_RWLIST_RDLOCK(&sla_stations); + AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) + ast_cli(fd, "=== ==> Station name: %s\n", station_ref->station->name); + AST_RWLIST_UNLOCK(&sla_stations); + ast_cli(fd, "=== ---------------------------------------------------------\n" + "===\n"); + } + AST_RWLIST_UNLOCK(&sla_trunks); + ast_cli(fd, "=============================================================\n" + "\n"); + + return RESULT_SUCCESS; +} + +static const char *trunkstate2str(enum sla_trunk_state state) +{ +#define S(e) case e: return # e; + switch (state) { + S(SLA_TRUNK_STATE_IDLE) + S(SLA_TRUNK_STATE_RINGING) + S(SLA_TRUNK_STATE_UP) + S(SLA_TRUNK_STATE_ONHOLD) + S(SLA_TRUNK_STATE_ONHOLD_BYME) + } + return "Uknown State"; +#undef S +} + +static const char sla_show_trunks_usage[] = +"Usage: sla show trunks\n" +" This will list all trunks defined in sla.conf\n"; + +static int sla_show_stations(int fd, int argc, char **argv) +{ + const struct sla_station *station; + + ast_cli(fd, "\n" + "=============================================================\n" + "=== Configured SLA Stations =================================\n" + "=============================================================\n" + "===\n"); + AST_RWLIST_RDLOCK(&sla_stations); + AST_RWLIST_TRAVERSE(&sla_stations, station, entry) { + struct sla_trunk_ref *trunk_ref; + char ring_timeout[16] = "(none)"; + char ring_delay[16] = "(none)"; + if (station->ring_timeout) { + snprintf(ring_timeout, sizeof(ring_timeout), + "%u", station->ring_timeout); + } + if (station->ring_delay) { + snprintf(ring_delay, sizeof(ring_delay), + "%u", station->ring_delay); + } + ast_cli(fd, "=== ---------------------------------------------------------\n" + "=== Station Name: %s\n" + "=== ==> Device: %s\n" + "=== ==> AutoContext: %s\n" + "=== ==> RingTimeout: %s\n" + "=== ==> RingDelay: %s\n" + "=== ==> HoldAccess: %s\n" + "=== ==> Trunks ...\n", + station->name, station->device, + S_OR(station->autocontext, "(none)"), + ring_timeout, ring_delay, + sla_hold_str(station->hold_access)); + AST_RWLIST_RDLOCK(&sla_trunks); + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->ring_timeout) { + snprintf(ring_timeout, sizeof(ring_timeout), + "%u", trunk_ref->ring_timeout); + } else + strcpy(ring_timeout, "(none)"); + if (trunk_ref->ring_delay) { + snprintf(ring_delay, sizeof(ring_delay), + "%u", trunk_ref->ring_delay); + } else + strcpy(ring_delay, "(none)"); + ast_cli(fd, "=== ==> Trunk Name: %s\n" + "=== ==> State: %s\n" + "=== ==> RingTimeout: %s\n" + "=== ==> RingDelay: %s\n", + trunk_ref->trunk->name, + trunkstate2str(trunk_ref->state), + ring_timeout, ring_delay); + } + AST_RWLIST_UNLOCK(&sla_trunks); + ast_cli(fd, "=== ---------------------------------------------------------\n" + "===\n"); + } + AST_RWLIST_UNLOCK(&sla_stations); + ast_cli(fd, "============================================================\n" + "\n"); + + return RESULT_SUCCESS; +} + +static const char sla_show_stations_usage[] = +"Usage: sla show stations\n" +" This will list all stations defined in sla.conf\n"; + +static struct ast_cli_entry cli_meetme[] = { + { { "meetme", NULL, NULL }, + meetme_cmd, "Execute a command on a conference or conferee", + meetme_usage, complete_meetmecmd }, + + { { "sla", "show", "trunks", NULL }, + sla_show_trunks, "Show SLA Trunks", + sla_show_trunks_usage, NULL }, + + { { "sla", "show", "stations", NULL }, + sla_show_stations, "Show SLA Stations", + sla_show_stations_usage, NULL }, +}; + +static void conf_flush(int fd, struct ast_channel *chan) +{ + int x; + + /* read any frames that may be waiting on the channel + and throw them away + */ + if (chan) { + struct ast_frame *f; + + /* when no frames are available, this will wait + for 1 millisecond maximum + */ + while (ast_waitfor(chan, 1)) { + f = ast_read(chan); + if (f) + ast_frfree(f); + else /* channel was hung up or something else happened */ + break; + } + } + + /* flush any data sitting in the pseudo channel */ + x = DAHDI_FLUSH_ALL; + if (ioctl(fd, DAHDI_FLUSH, &x)) + ast_log(LOG_WARNING, "Error flushing channel\n"); + +} + +/* Remove the conference from the list and free it. + We assume that this was called while holding conflock. */ +static int conf_free(struct ast_conference *conf) +{ + int x; + + AST_LIST_REMOVE(&confs, conf, list); + + if (conf->recording == MEETME_RECORD_ACTIVE) { + conf->recording = MEETME_RECORD_TERMINATE; + AST_LIST_UNLOCK(&confs); + while (1) { + usleep(1); + AST_LIST_LOCK(&confs); + if (conf->recording == MEETME_RECORD_OFF) + break; + AST_LIST_UNLOCK(&confs); + } + } + + for (x=0;xtransframe[x]) + ast_frfree(conf->transframe[x]); + if (conf->transpath[x]) + ast_translator_free_path(conf->transpath[x]); + } + if (conf->origframe) + ast_frfree(conf->origframe); + if (conf->lchan) + ast_hangup(conf->lchan); + if (conf->chan) + ast_hangup(conf->chan); + if (conf->fd >= 0) + close(conf->fd); + + ast_mutex_destroy(&conf->playlock); + ast_mutex_destroy(&conf->listenlock); + ast_mutex_destroy(&conf->recordthreadlock); + free(conf); + + return 0; +} + +static void conf_queue_dtmf(const struct ast_conference *conf, + const struct ast_conf_user *sender, struct ast_frame *f) +{ + struct ast_conf_user *user; + + AST_LIST_TRAVERSE(&conf->userlist, user, list) { + if (user == sender) + continue; + if (ast_write(user->chan, f) < 0) + ast_log(LOG_WARNING, "Error writing frame to channel %s\n", user->chan->name); + } +} + +static void sla_queue_event_full(enum sla_event_type type, + struct sla_trunk_ref *trunk_ref, struct sla_station *station, int lock) +{ + struct sla_event *event; + + if (!(event = ast_calloc(1, sizeof(*event)))) + return; + + event->type = type; + event->trunk_ref = trunk_ref; + event->station = station; + + if (!lock) { + AST_LIST_INSERT_TAIL(&sla.event_q, event, entry); + return; + } + + ast_mutex_lock(&sla.lock); + AST_LIST_INSERT_TAIL(&sla.event_q, event, entry); + ast_cond_signal(&sla.cond); + ast_mutex_unlock(&sla.lock); +} + +static void sla_queue_event_nolock(enum sla_event_type type) +{ + sla_queue_event_full(type, NULL, NULL, 0); +} + +static void sla_queue_event(enum sla_event_type type) +{ + sla_queue_event_full(type, NULL, NULL, 1); +} + +/*! \brief Queue a SLA event from the conference */ +static void sla_queue_event_conf(enum sla_event_type type, struct ast_channel *chan, + struct ast_conference *conf) +{ + struct sla_station *station; + struct sla_trunk_ref *trunk_ref = NULL; + char *trunk_name; + + trunk_name = ast_strdupa(conf->confno); + strsep(&trunk_name, "_"); + if (ast_strlen_zero(trunk_name)) { + ast_log(LOG_ERROR, "Invalid conference name for SLA - '%s'!\n", conf->confno); + return; + } + + AST_RWLIST_RDLOCK(&sla_stations); + AST_RWLIST_TRAVERSE(&sla_stations, station, entry) { + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->chan == chan && !strcmp(trunk_ref->trunk->name, trunk_name)) + break; + } + if (trunk_ref) + break; + } + AST_RWLIST_UNLOCK(&sla_stations); + + if (!trunk_ref) { + ast_log(LOG_DEBUG, "Trunk not found for event!\n"); + return; + } + + sla_queue_event_full(type, trunk_ref, station, 1); +} + +/* Decrement reference counts, as incremented by find_conf() */ +static int dispose_conf(struct ast_conference *conf) +{ + int res = 0; + int confno_int = 0; + + AST_LIST_LOCK(&confs); + if (ast_atomic_dec_and_test(&conf->refcount)) { + /* Take the conference room number out of an inuse state */ + if ((sscanf(conf->confno, "%d", &confno_int) == 1) && (confno_int >= 0 && confno_int < 1024)) + conf_map[confno_int] = 0; + conf_free(conf); + res = 1; + } + AST_LIST_UNLOCK(&confs); + + return res; +} + + +static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags, char *optargs[]) +{ + struct ast_conf_user *user = NULL; + struct ast_conf_user *usr = NULL; + int fd; + struct dahdi_confinfo ztc, ztc_empty; + struct ast_frame *f; + struct ast_channel *c; + struct ast_frame fr; + int outfd; + int ms; + int nfds; + int res; + int flags; + int retryzap; + int origfd; + int musiconhold = 0; + int firstpass = 0; + int lastmarked = 0; + int currentmarked = 0; + int ret = -1; + int x; + int menu_active = 0; + int using_pseudo = 0; + int duration=20; + int hr, min, sec; + int sent_event = 0; + time_t now; + struct ast_dsp *dsp=NULL; + struct ast_app *app; + const char *agifile; + const char *agifiledefault = "conf-background.agi"; + char meetmesecs[30] = ""; + char exitcontext[AST_MAX_CONTEXT] = ""; + char recordingtmp[AST_MAX_EXTENSION] = ""; + char members[10] = ""; + int dtmf, opt_waitmarked_timeout = 0; + time_t timeout = 0; + struct dahdi_bufferinfo bi; + char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET]; + char *buf = __buf + AST_FRIENDLY_OFFSET; + int setusercount = 0; + + if (!(user = ast_calloc(1, sizeof(*user)))) + return ret; + + /* Possible timeout waiting for marked user */ + if ((confflags & CONFFLAG_WAITMARKED) && + !ast_strlen_zero(optargs[OPT_ARG_WAITMARKED]) && + (sscanf(optargs[OPT_ARG_WAITMARKED], "%d", &opt_waitmarked_timeout) == 1) && + (opt_waitmarked_timeout > 0)) { + timeout = time(NULL) + opt_waitmarked_timeout; + } + + if (confflags & CONFFLAG_RECORDCONF) { + if (!conf->recordingfilename) { + conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE"); + if (!conf->recordingfilename) { + snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid); + conf->recordingfilename = ast_strdupa(recordingtmp); + } + conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT"); + if (!conf->recordingformat) { + snprintf(recordingtmp, sizeof(recordingtmp), "wav"); + conf->recordingformat = ast_strdupa(recordingtmp); + } + ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n", + conf->confno, conf->recordingfilename, conf->recordingformat); + } + } + + ast_mutex_lock(&conf->recordthreadlock); + if ((conf->recordthread == AST_PTHREADT_NULL) && (confflags & CONFFLAG_RECORDCONF) && ((conf->lchan = ast_request(dahdi_chan_name, AST_FORMAT_SLINEAR, "pseudo", NULL)))) { + ast_set_read_format(conf->lchan, AST_FORMAT_SLINEAR); + ast_set_write_format(conf->lchan, AST_FORMAT_SLINEAR); + ztc.chan = 0; + ztc.confno = conf->zapconf; + ztc.confmode = DAHDI_CONF_CONFANN | DAHDI_CONF_CONFANNMON; + if (ioctl(conf->lchan->fds[0], DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error starting listen channel\n"); + ast_hangup(conf->lchan); + conf->lchan = NULL; + } else { + pthread_attr_init(&conf->attr); + pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED); + ast_pthread_create_background(&conf->recordthread, &conf->attr, recordthread, conf); + pthread_attr_destroy(&conf->attr); + } + } + ast_mutex_unlock(&conf->recordthreadlock); + + time(&user->jointime); + + if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) { + /* Sorry, but this confernce is locked! */ + if (!ast_streamfile(chan, "conf-locked", chan->language)) + ast_waitstream(chan, ""); + goto outrun; + } + + ast_mutex_lock(&conf->playlock); + + if (AST_LIST_EMPTY(&conf->userlist)) + user->user_no = 1; + else + user->user_no = AST_LIST_LAST(&conf->userlist)->user_no + 1; + + AST_LIST_INSERT_TAIL(&conf->userlist, user, list); + + user->chan = chan; + user->userflags = confflags; + user->adminflags = (confflags & CONFFLAG_STARTMUTED) ? ADMINFLAG_SELFMUTED : 0; + user->talking = -1; + + ast_mutex_unlock(&conf->playlock); + + if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW))) { + char destdir[PATH_MAX]; + + snprintf(destdir, sizeof(destdir), "%s/meetme", ast_config_AST_SPOOL_DIR); + + if (mkdir(destdir, 0777) && errno != EEXIST) { + ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno)); + goto outrun; + } + + snprintf(user->namerecloc, sizeof(user->namerecloc), + "%s/meetme-username-%s-%d", destdir, + conf->confno, user->user_no); + if (confflags & CONFFLAG_INTROUSERNOREVIEW) + res = ast_play_and_record(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, 128, 0, NULL); + else + res = ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL); + if (res == -1) + goto outrun; + } + + ast_mutex_lock(&conf->playlock); + + if (confflags & CONFFLAG_MARKEDUSER) + conf->markedusers++; + conf->users++; + /* Update table */ + snprintf(members, sizeof(members), "%d", conf->users); + ast_update_realtime("meetme", "confno", conf->confno, "members", members , NULL); + setusercount = 1; + + /* This device changed state now - if this is the first user */ + if (conf->users == 1) + ast_device_state_changed("meetme:%s", conf->confno); + + ast_mutex_unlock(&conf->playlock); + + if (confflags & CONFFLAG_EXIT_CONTEXT) { + if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT"))) + ast_copy_string(exitcontext, agifile, sizeof(exitcontext)); + else if (!ast_strlen_zero(chan->macrocontext)) + ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext)); + else + ast_copy_string(exitcontext, chan->context, sizeof(exitcontext)); + } + + if ( !(confflags & (CONFFLAG_QUIET | CONFFLAG_NOONLYPERSON)) ) { + if (conf->users == 1 && !(confflags & CONFFLAG_WAITMARKED)) + if (!ast_streamfile(chan, "conf-onlyperson", chan->language)) + ast_waitstream(chan, ""); + if ((confflags & CONFFLAG_WAITMARKED) && conf->markedusers == 0) + if (!ast_streamfile(chan, "conf-waitforleader", chan->language)) + ast_waitstream(chan, ""); + } + + if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) { + int keepplaying = 1; + + if (conf->users == 2) { + if (!ast_streamfile(chan,"conf-onlyone",chan->language)) { + res = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + if (res > 0) + keepplaying=0; + else if (res == -1) + goto outrun; + } + } else { + if (!ast_streamfile(chan, "conf-thereare", chan->language)) { + res = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + if (res > 0) + keepplaying=0; + else if (res == -1) + goto outrun; + } + if (keepplaying) { + res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL); + if (res > 0) + keepplaying=0; + else if (res == -1) + goto outrun; + } + if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) { + res = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + if (res > 0) + keepplaying=0; + else if (res == -1) + goto outrun; + } + } + } + + ast_indicate(chan, -1); + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) { + ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name); + goto outrun; + } + + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) { + ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name); + goto outrun; + } + + retryzap = (strcasecmp(chan->tech->type, dahdi_chan_name) || (chan->audiohooks || chan->monitor) ? 1 : 0); + user->zapchannel = !retryzap; + + zapretry: + origfd = chan->fds[0]; + if (retryzap) { +#ifdef HAVE_ZAPTEL + fd = open("/dev/zap/pseudo", O_RDWR); +#else + fd = open("/dev/dahdi/pseudo", O_RDWR); +#endif + if (fd < 0) { + ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno)); + goto outrun; + } + using_pseudo = 1; + /* Make non-blocking */ + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { + ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + /* Setup buffering information */ + memset(&bi, 0, sizeof(bi)); + bi.bufsize = CONF_SIZE/2; + bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE; + bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE; + bi.numbufs = audio_buffers; + if (ioctl(fd, DAHDI_SET_BUFINFO, &bi)) { + ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + x = 1; + if (ioctl(fd, DAHDI_SETLINEAR, &x)) { + ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno)); + close(fd); + goto outrun; + } + nfds = 1; + } else { + /* XXX Make sure we're not running on a pseudo channel XXX */ + fd = chan->fds[0]; + nfds = 0; + } + memset(&ztc, 0, sizeof(ztc)); + memset(&ztc_empty, 0, sizeof(ztc_empty)); + /* Check to see if we're in a conference... */ + ztc.chan = 0; + if (ioctl(fd, DAHDI_GETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error getting conference\n"); + close(fd); + goto outrun; + } + if (ztc.confmode) { + /* Whoa, already in a conference... Retry... */ + if (!retryzap) { + ast_log(LOG_DEBUG, "%s channel is in a conference already, retrying with pseudo\n", dahdi_chan_name); + retryzap = 1; + goto zapretry; + } + } + memset(&ztc, 0, sizeof(ztc)); + /* Add us to the conference */ + ztc.chan = 0; + ztc.confno = conf->zapconf; + + ast_mutex_lock(&conf->playlock); + + if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW)) && conf->users > 1) { + if (conf->chan && ast_fileexists(user->namerecloc, NULL, NULL)) { + if (!ast_streamfile(conf->chan, user->namerecloc, chan->language)) + ast_waitstream(conf->chan, ""); + if (!ast_streamfile(conf->chan, "conf-hasjoin", chan->language)) + ast_waitstream(conf->chan, ""); + } + } + + if (confflags & CONFFLAG_WAITMARKED && !conf->markedusers) + ztc.confmode = DAHDI_CONF_CONF; + else if (confflags & CONFFLAG_MONITOR) + ztc.confmode = DAHDI_CONF_CONFMON | DAHDI_CONF_LISTENER; + else if (confflags & CONFFLAG_TALKER) + ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER; + else + ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER; + + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + ast_mutex_unlock(&conf->playlock); + goto outrun; + } + ast_log(LOG_DEBUG, "Placed channel %s in %s conf %d\n", chan->name, dahdi_chan_name, conf->zapconf); + + if (!sent_event) { + manager_event(EVENT_FLAG_CALL, "MeetmeJoin", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Meetme: %s\r\n" + "Usernum: %d\r\n", + chan->name, chan->uniqueid, conf->confno, user->user_no); + sent_event = 1; + } + + if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) { + firstpass = 1; + if (!(confflags & CONFFLAG_QUIET)) + if (!(confflags & CONFFLAG_WAITMARKED) || ((confflags & CONFFLAG_MARKEDUSER) && (conf->markedusers >= 1))) + conf_play(chan, conf, ENTER); + } + + ast_mutex_unlock(&conf->playlock); + + conf_flush(fd, chan); + + if (confflags & CONFFLAG_AGI) { + /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND) + or use default filename of conf-background.agi */ + + agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND"); + if (!agifile) + agifile = agifiledefault; + + if (user->zapchannel) { + /* Set CONFMUTE mode on Zap channel to mute DTMF tones */ + x = 1; + ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0); + } + /* Find a pointer to the agi app and execute the script */ + app = pbx_findapp("agi"); + if (app) { + char *s = ast_strdupa(agifile); + ret = pbx_exec(chan, app, s); + } else { + ast_log(LOG_WARNING, "Could not find application (agi)\n"); + ret = -2; + } + if (user->zapchannel) { + /* Remove CONFMUTE mode on Zap channel */ + x = 0; + ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0); + } + } else { + if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) { + /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */ + x = 1; + ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0); + } + if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER) && !(dsp = ast_dsp_new())) { + ast_log(LOG_WARNING, "Unable to allocate DSP!\n"); + res = -1; + } + for(;;) { + int menu_was_active = 0; + + outfd = -1; + ms = -1; + + if (timeout && time(NULL) >= timeout) + break; + + /* if we have just exited from the menu, and the user had a channel-driver + volume adjustment, restore it + */ + if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual) + set_talk_volume(user, user->listen.desired); + + menu_was_active = menu_active; + + currentmarked = conf->markedusers; + if (!(confflags & CONFFLAG_QUIET) && + (confflags & CONFFLAG_MARKEDUSER) && + (confflags & CONFFLAG_WAITMARKED) && + lastmarked == 0) { + if (currentmarked == 1 && conf->users > 1) { + ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL); + if (conf->users - 1 == 1) { + if (!ast_streamfile(chan, "conf-userwilljoin", chan->language)) + ast_waitstream(chan, ""); + } else { + if (!ast_streamfile(chan, "conf-userswilljoin", chan->language)) + ast_waitstream(chan, ""); + } + } + if (conf->users == 1 && ! (confflags & CONFFLAG_MARKEDUSER)) + if (!ast_streamfile(chan, "conf-onlyperson", chan->language)) + ast_waitstream(chan, ""); + } + + c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms); + + + /* Update the struct with the actual confflags */ + user->userflags = confflags; + + if (confflags & CONFFLAG_WAITMARKED) { + if(currentmarked == 0) { + if (lastmarked != 0) { + if (!(confflags & CONFFLAG_QUIET)) + if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language)) + ast_waitstream(chan, ""); + if(confflags & CONFFLAG_MARKEDEXIT) + break; + else { + ztc.confmode = DAHDI_CONF_CONF; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + goto outrun; + } + } + } + if (musiconhold == 0 && (confflags & CONFFLAG_MOH)) { + ast_moh_start(chan, NULL, NULL); + musiconhold = 1; + } + } else if(currentmarked >= 1 && lastmarked == 0) { + /* Marked user entered, so cancel timeout */ + timeout = 0; + if (confflags & CONFFLAG_MONITOR) + ztc.confmode = DAHDI_CONF_CONFMON | DAHDI_CONF_LISTENER; + else if (confflags & CONFFLAG_TALKER) + ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER; + else + ztc.confmode = DAHDI_CONF_CONF | DAHDI_CONF_TALKER | DAHDI_CONF_LISTENER; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + goto outrun; + } + if (musiconhold && (confflags & CONFFLAG_MOH)) { + ast_moh_stop(chan); + musiconhold = 0; + } + if ( !(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MARKEDUSER)) { + if (!ast_streamfile(chan, "conf-placeintoconf", chan->language)) + ast_waitstream(chan, ""); + conf_play(chan, conf, ENTER); + } + } + } + + /* trying to add moh for single person conf */ + if ((confflags & CONFFLAG_MOH) && !(confflags & CONFFLAG_WAITMARKED)) { + if (conf->users == 1) { + if (musiconhold == 0) { + ast_moh_start(chan, NULL, NULL); + musiconhold = 1; + } + } else { + if (musiconhold) { + ast_moh_stop(chan); + musiconhold = 0; + } + } + } + + /* Leave if the last marked user left */ + if (currentmarked == 0 && lastmarked != 0 && (confflags & CONFFLAG_MARKEDEXIT)) { + ret = -1; + break; + } + + /* Check if my modes have changed */ + + /* If I should be muted but am still talker, mute me */ + if ((user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && (ztc.confmode & DAHDI_CONF_TALKER)) { + ztc.confmode ^= DAHDI_CONF_TALKER; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n"); + ret = -1; + break; + } + + manager_event(EVENT_FLAG_CALL, "MeetmeMute", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Meetme: %s\r\n" + "Usernum: %i\r\n" + "Status: on\r\n", + chan->name, chan->uniqueid, conf->confno, user->user_no); + } + + /* If I should be un-muted but am not talker, un-mute me */ + if (!(user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & DAHDI_CONF_TALKER)) { + ztc.confmode |= DAHDI_CONF_TALKER; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n"); + ret = -1; + break; + } + + manager_event(EVENT_FLAG_CALL, "MeetmeMute", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Meetme: %s\r\n" + "Usernum: %i\r\n" + "Status: off\r\n", + chan->name, chan->uniqueid, conf->confno, user->user_no); + } + + /* If I have been kicked, exit the conference */ + if (user->adminflags & ADMINFLAG_KICKME) { + //You have been kicked. + if (!(confflags & CONFFLAG_QUIET) && + !ast_streamfile(chan, "conf-kicked", chan->language)) { + ast_waitstream(chan, ""); + } + ret = 0; + break; + } + + /* Perform an extra hangup check just in case */ + if (ast_check_hangup(chan)) + break; + + if (c) { + char dtmfstr[2] = ""; + + if (c->fds[0] != origfd || (user->zapchannel && (c->audiohooks || c->monitor))) { + if (using_pseudo) { + /* Kill old pseudo */ + close(fd); + using_pseudo = 0; + } + ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n"); + retryzap = (strcasecmp(c->tech->type, dahdi_chan_name) || (c->audiohooks || c->monitor) ? 1 : 0); + user->zapchannel = !retryzap; + goto zapretry; + } + if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) + f = ast_read_noaudio(c); + else + f = ast_read(c); + if (!f) + break; + if (f->frametype == AST_FRAME_DTMF) { + dtmfstr[0] = f->subclass; + dtmfstr[1] = '\0'; + } + + if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) { + if (user->talk.actual) + ast_frame_adjust_volume(f, user->talk.actual); + + if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER)) { + int totalsilence; + + if (user->talking == -1) + user->talking = 0; + + res = ast_dsp_silence(dsp, f, &totalsilence); + if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) { + user->talking = 1; + if (confflags & CONFFLAG_MONITORTALKER) + manager_event(EVENT_FLAG_CALL, "MeetmeTalking", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Meetme: %s\r\n" + "Usernum: %d\r\n" + "Status: on\r\n", + chan->name, chan->uniqueid, conf->confno, user->user_no); + } + if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) { + user->talking = 0; + if (confflags & CONFFLAG_MONITORTALKER) + manager_event(EVENT_FLAG_CALL, "MeetmeTalking", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Meetme: %s\r\n" + "Usernum: %d\r\n" + "Status: off\r\n", + chan->name, chan->uniqueid, conf->confno, user->user_no); + } + } + if (using_pseudo) { + /* Absolutely do _not_ use careful_write here... + it is important that we read data from the channel + as fast as it arrives, and feed it into the conference. + The buffering in the pseudo channel will take care of any + timing differences, unless they are so drastic as to lose + audio frames (in which case carefully writing would only + have delayed the audio even further). + */ + /* As it turns out, we do want to use careful write. We just + don't want to block, but we do want to at least *try* + to write out all the samples. + */ + if (user->talking || !(confflags & CONFFLAG_OPTIMIZETALKER)) + careful_write(fd, f->data, f->datalen, 0); + } + } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) { + if (confflags & CONFFLAG_PASS_DTMF) + conf_queue_dtmf(conf, user, f); + ret = 0; + ast_frfree(f); + break; + } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) { + if (confflags & CONFFLAG_PASS_DTMF) + conf_queue_dtmf(conf, user, f); + if (ioctl(fd, DAHDI_SETCONF, &ztc_empty)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + ast_frfree(f); + goto outrun; + } + + /* if we are entering the menu, and the user has a channel-driver + volume adjustment, clear it + */ + if (!menu_active && user->talk.desired && !user->talk.actual) + set_talk_volume(user, 0); + + if (musiconhold) { + ast_moh_stop(chan); + } + if ((confflags & CONFFLAG_ADMIN)) { + /* Admin menu */ + if (!menu_active) { + menu_active = 1; + /* Record this sound! */ + if (!ast_streamfile(chan, "conf-adminmenu", chan->language)) { + dtmf = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + } else + dtmf = 0; + } else + dtmf = f->subclass; + if (dtmf) { + switch(dtmf) { + case '1': /* Un/Mute */ + menu_active = 0; + + /* for admin, change both admin and use flags */ + if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) + user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED); + else + user->adminflags |= (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED); + + if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) { + if (!ast_streamfile(chan, "conf-muted", chan->language)) + ast_waitstream(chan, ""); + } else { + if (!ast_streamfile(chan, "conf-unmuted", chan->language)) + ast_waitstream(chan, ""); + } + break; + case '2': /* Un/Lock the Conference */ + menu_active = 0; + if (conf->locked) { + conf->locked = 0; + if (!ast_streamfile(chan, "conf-unlockednow", chan->language)) + ast_waitstream(chan, ""); + } else { + conf->locked = 1; + if (!ast_streamfile(chan, "conf-lockednow", chan->language)) + ast_waitstream(chan, ""); + } + break; + case '3': /* Eject last user */ + menu_active = 0; + usr = AST_LIST_LAST(&conf->userlist); + if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) { + if(!ast_streamfile(chan, "conf-errormenu", chan->language)) + ast_waitstream(chan, ""); + } else + usr->adminflags |= ADMINFLAG_KICKME; + ast_stopstream(chan); + break; + case '4': + tweak_listen_volume(user, VOL_DOWN); + break; + case '6': + tweak_listen_volume(user, VOL_UP); + break; + case '7': + tweak_talk_volume(user, VOL_DOWN); + break; + case '8': + menu_active = 0; + break; + case '9': + tweak_talk_volume(user, VOL_UP); + break; + default: + menu_active = 0; + /* Play an error message! */ + if (!ast_streamfile(chan, "conf-errormenu", chan->language)) + ast_waitstream(chan, ""); + break; + } + } + } else { + /* User menu */ + if (!menu_active) { + menu_active = 1; + if (!ast_streamfile(chan, "conf-usermenu", chan->language)) { + dtmf = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + } else + dtmf = 0; + } else + dtmf = f->subclass; + if (dtmf) { + switch(dtmf) { + case '1': /* Un/Mute */ + menu_active = 0; + + /* user can only toggle the self-muted state */ + user->adminflags ^= ADMINFLAG_SELFMUTED; + + /* they can't override the admin mute state */ + if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED))) { + if (!ast_streamfile(chan, "conf-muted", chan->language)) + ast_waitstream(chan, ""); + } else { + if (!ast_streamfile(chan, "conf-unmuted", chan->language)) + ast_waitstream(chan, ""); + } + break; + case '4': + tweak_listen_volume(user, VOL_DOWN); + break; + case '6': + tweak_listen_volume(user, VOL_UP); + break; + case '7': + tweak_talk_volume(user, VOL_DOWN); + break; + case '8': + menu_active = 0; + break; + case '9': + tweak_talk_volume(user, VOL_UP); + break; + default: + menu_active = 0; + if (!ast_streamfile(chan, "conf-errormenu", chan->language)) + ast_waitstream(chan, ""); + break; + } + } + } + if (musiconhold) + ast_moh_start(chan, NULL, NULL); + + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + ast_frfree(f); + goto outrun; + } + + conf_flush(fd, chan); + /* Since this option could absorb dtmf for the previous, we have to check this one last */ + } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT) && ast_exists_extension(chan, exitcontext, dtmfstr, 1, "")) { + if (confflags & CONFFLAG_PASS_DTMF) + conf_queue_dtmf(conf, user, f); + + if (!ast_goto_if_exists(chan, exitcontext, dtmfstr, 1)) { + ast_log(LOG_DEBUG, "Got DTMF %c, goto context %s\n", dtmfstr[0], exitcontext); + ret = 0; + ast_frfree(f); + break; + } else if (option_debug > 1) + ast_log(LOG_DEBUG, "Exit by single digit did not work in meetme. Extension '%s' does not exist in context '%s'\n", dtmfstr, exitcontext); + } else if ((f->frametype == AST_FRAME_DTMF_BEGIN || f->frametype == AST_FRAME_DTMF_END) + && confflags & CONFFLAG_PASS_DTMF) { + conf_queue_dtmf(conf, user, f); + } else if ((confflags & CONFFLAG_SLA_STATION) && f->frametype == AST_FRAME_CONTROL) { + switch (f->subclass) { + case AST_CONTROL_HOLD: + sla_queue_event_conf(SLA_EVENT_HOLD, chan, conf); + break; + default: + break; + } + } else if (f->frametype == AST_FRAME_NULL) { + /* Ignore NULL frames. It is perfectly normal to get these if the person is muted. */ + } else if (option_debug) { + ast_log(LOG_DEBUG, + "Got unrecognized frame on channel %s, f->frametype=%d,f->subclass=%d\n", + chan->name, f->frametype, f->subclass); + } + ast_frfree(f); + } else if (outfd > -1) { + res = read(outfd, buf, CONF_SIZE); + if (res > 0) { + memset(&fr, 0, sizeof(fr)); + fr.frametype = AST_FRAME_VOICE; + fr.subclass = AST_FORMAT_SLINEAR; + fr.datalen = res; + fr.samples = res/2; + fr.data = buf; + fr.offset = AST_FRIENDLY_OFFSET; + if (!user->listen.actual && + ((confflags & CONFFLAG_MONITOR) || + (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) || + (!user->talking && (confflags & CONFFLAG_OPTIMIZETALKER)) + )) { + int index; + for (index=0;indexrawwriteformat & (1 << index)) + break; + if (index >= AST_FRAME_BITS) + goto bailoutandtrynormal; + ast_mutex_lock(&conf->listenlock); + if (!conf->transframe[index]) { + if (conf->origframe) { + if (!conf->transpath[index]) + conf->transpath[index] = ast_translator_build_path((1 << index), AST_FORMAT_SLINEAR); + if (conf->transpath[index]) { + conf->transframe[index] = ast_translate(conf->transpath[index], conf->origframe, 0); + if (!conf->transframe[index]) + conf->transframe[index] = &ast_null_frame; + } + } + } + if (conf->transframe[index]) { + if (conf->transframe[index]->frametype != AST_FRAME_NULL) { + if (ast_write(chan, conf->transframe[index])) + ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name); + } + } else { + ast_mutex_unlock(&conf->listenlock); + goto bailoutandtrynormal; + } + ast_mutex_unlock(&conf->listenlock); + } else { +bailoutandtrynormal: + if (user->listen.actual) + ast_frame_adjust_volume(&fr, user->listen.actual); + if (ast_write(chan, &fr) < 0) { + ast_log(LOG_WARNING, "Unable to write frame to channel %s\n", chan->name); + } + } + } else + ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno)); + } + lastmarked = currentmarked; + } + } + + if (musiconhold) + ast_moh_stop(chan); + + if (using_pseudo) + close(fd); + else { + /* Take out of conference */ + ztc.chan = 0; + ztc.confno = 0; + ztc.confmode = 0; + if (ioctl(fd, DAHDI_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + } + } + + reset_volumes(user); + + AST_LIST_LOCK(&confs); + if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) + conf_play(chan, conf, LEAVE); + + if (!(confflags & CONFFLAG_QUIET) && ((confflags & CONFFLAG_INTROUSER) || (confflags & CONFFLAG_INTROUSERNOREVIEW))) { + if (ast_fileexists(user->namerecloc, NULL, NULL)) { + if ((conf->chan) && (conf->users > 1)) { + if (!ast_streamfile(conf->chan, user->namerecloc, chan->language)) + ast_waitstream(conf->chan, ""); + if (!ast_streamfile(conf->chan, "conf-hasleft", chan->language)) + ast_waitstream(conf->chan, ""); + } + ast_filedelete(user->namerecloc, NULL); + } + } + AST_LIST_UNLOCK(&confs); + + outrun: + AST_LIST_LOCK(&confs); + + if (dsp) + ast_dsp_free(dsp); + + if (user->user_no) { /* Only cleanup users who really joined! */ + now = time(NULL); + hr = (now - user->jointime) / 3600; + min = ((now - user->jointime) % 3600) / 60; + sec = (now - user->jointime) % 60; + + if (sent_event) { + manager_event(EVENT_FLAG_CALL, "MeetmeLeave", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Meetme: %s\r\n" + "Usernum: %d\r\n" + "CallerIDnum: %s\r\n" + "CallerIDname: %s\r\n" + "Duration: %ld\r\n", + chan->name, chan->uniqueid, conf->confno, + user->user_no, + S_OR(user->chan->cid.cid_num, ""), + S_OR(user->chan->cid.cid_name, ""), + (long)(now - user->jointime)); + } + + if (setusercount) { + conf->users--; + /* Update table */ + snprintf(members, sizeof(members), "%d", conf->users); + ast_update_realtime("meetme", "confno", conf->confno, "members", members, NULL); + if (confflags & CONFFLAG_MARKEDUSER) + conf->markedusers--; + } + /* Remove ourselves from the list */ + AST_LIST_REMOVE(&conf->userlist, user, list); + + /* Change any states */ + if (!conf->users) + ast_device_state_changed("meetme:%s", conf->confno); + + /* Return the number of seconds the user was in the conf */ + snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime)); + pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs); + } + free(user); + AST_LIST_UNLOCK(&confs); + + return ret; +} + +static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char *confno, int make, int dynamic, + char *dynamic_pin, size_t pin_buf_len, int refcount, struct ast_flags *confflags) +{ + struct ast_variable *var; + struct ast_conference *cnf; + + /* Check first in the conference list */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strcmp(confno, cnf->confno)) + break; + } + if (cnf) { + cnf->refcount += refcount; + } + AST_LIST_UNLOCK(&confs); + + if (!cnf) { + char *pin = NULL, *pinadmin = NULL; /* For temp use */ + + var = ast_load_realtime("meetme", "confno", confno, NULL); + + if (!var) + return NULL; + + while (var) { + if (!strcasecmp(var->name, "pin")) { + pin = ast_strdupa(var->value); + } else if (!strcasecmp(var->name, "adminpin")) { + pinadmin = ast_strdupa(var->value); + } + var = var->next; + } + ast_variables_destroy(var); + + cnf = build_conf(confno, pin ? pin : "", pinadmin ? pinadmin : "", make, dynamic, refcount); + } + + if (cnf) { + if (confflags && !cnf->chan && + !ast_test_flag(confflags, CONFFLAG_QUIET) && + ast_test_flag(confflags, CONFFLAG_INTROUSER)) { + ast_log(LOG_WARNING, "No %s channel available for conference, user introduction disabled\n", dahdi_chan_name); + ast_clear_flag(confflags, CONFFLAG_INTROUSER); + } + + if (confflags && !cnf->chan && + ast_test_flag(confflags, CONFFLAG_RECORDCONF)) { + ast_log(LOG_WARNING, "No %s channel available for conference, conference recording disabled\n", dahdi_chan_name); + ast_clear_flag(confflags, CONFFLAG_RECORDCONF); + } + } + + return cnf; +} + + +static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, + char *dynamic_pin, size_t pin_buf_len, int refcount, struct ast_flags *confflags) +{ + struct ast_config *cfg; + struct ast_variable *var; + struct ast_conference *cnf; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(confno); + AST_APP_ARG(pin); + AST_APP_ARG(pinadmin); + ); + + /* Check first in the conference list */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strcmp(confno, cnf->confno)) + break; + } + if (cnf){ + cnf->refcount += refcount; + } + AST_LIST_UNLOCK(&confs); + + if (!cnf) { + if (dynamic) { + /* No need to parse meetme.conf */ + ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno); + if (dynamic_pin) { + if (dynamic_pin[0] == 'q') { + /* Query the user to enter a PIN */ + if (ast_app_getdata(chan, "conf-getpin", dynamic_pin, pin_buf_len - 1, 0) < 0) + return NULL; + } + cnf = build_conf(confno, dynamic_pin, "", make, dynamic, refcount); + } else { + cnf = build_conf(confno, "", "", make, dynamic, refcount); + } + } else { + /* Check the config */ + cfg = ast_config_load(CONFIG_FILE_NAME); + if (!cfg) { + ast_log(LOG_WARNING, "No %s file :(\n", CONFIG_FILE_NAME); + return NULL; + } + for (var = ast_variable_browse(cfg, "rooms"); var; var = var->next) { + if (strcasecmp(var->name, "conf")) + continue; + + if (!(parse = ast_strdupa(var->value))) + return NULL; + + AST_NONSTANDARD_APP_ARGS(args, parse, ','); + if (!strcasecmp(args.confno, confno)) { + /* Bingo it's a valid conference */ + cnf = build_conf(args.confno, + S_OR(args.pin, ""), + S_OR(args.pinadmin, ""), + make, dynamic, refcount); + break; + } + } + if (!var) { + ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno); + } + ast_config_destroy(cfg); + } + } else if (dynamic_pin) { + /* Correct for the user selecting 'D' instead of 'd' to have + someone join into a conference that has already been created + with a pin. */ + if (dynamic_pin[0] == 'q') + dynamic_pin[0] = '\0'; + } + + if (cnf) { + if (confflags && !cnf->chan && + !ast_test_flag(confflags, CONFFLAG_QUIET) && + ast_test_flag(confflags, CONFFLAG_INTROUSER)) { + ast_log(LOG_WARNING, "No %s channel available for conference, user introduction disabled\n", dahdi_chan_name); + ast_clear_flag(confflags, CONFFLAG_INTROUSER); + } + + if (confflags && !cnf->chan && + ast_test_flag(confflags, CONFFLAG_RECORDCONF)) { + ast_log(LOG_WARNING, "No %s channel available for conference, conference recording disabled\n", dahdi_chan_name); + ast_clear_flag(confflags, CONFFLAG_RECORDCONF); + } + } + + return cnf; +} + +/*! \brief The MeetmeCount application */ +static int count_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + int res = 0; + struct ast_conference *conf; + int count; + char *localdata; + char val[80] = "0"; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(confno); + AST_APP_ARG(varname); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (!(localdata = ast_strdupa(data))) { + ast_module_user_remove(u); + return -1; + } + + AST_STANDARD_APP_ARGS(args, localdata); + + conf = find_conf(chan, args.confno, 0, 0, NULL, 0, 1, NULL); + + if (conf) { + count = conf->users; + dispose_conf(conf); + conf = NULL; + } else + count = 0; + + if (!ast_strlen_zero(args.varname)){ + /* have var so load it and exit */ + snprintf(val, sizeof(val), "%d",count); + pbx_builtin_setvar_helper(chan, args.varname, val); + } else { + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */ + } + ast_module_user_remove(u); + + return res; +} + +/*! \brief The meetme() application */ +static int conf_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + struct ast_module_user *u; + char confno[MAX_CONFNUM] = ""; + int allowretry = 0; + int retrycnt = 0; + struct ast_conference *cnf = NULL; + struct ast_flags confflags = {0}; + int dynamic = 0; + int empty = 0, empty_no_pin = 0; + int always_prompt = 0; + char *notdata, *info, the_pin[MAX_PIN] = ""; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(confno); + AST_APP_ARG(options); + AST_APP_ARG(pin); + ); + char *optargs[OPT_ARG_ARRAY_SIZE] = { NULL, }; + + u = ast_module_user_add(chan); + + if (ast_strlen_zero(data)) { + allowretry = 1; + notdata = ""; + } else { + notdata = data; + } + + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + info = ast_strdupa(notdata); + + AST_STANDARD_APP_ARGS(args, info); + + if (args.confno) { + ast_copy_string(confno, args.confno, sizeof(confno)); + if (ast_strlen_zero(confno)) { + allowretry = 1; + } + } + + if (args.pin) + ast_copy_string(the_pin, args.pin, sizeof(the_pin)); + + if (args.options) { + ast_app_parse_options(meetme_opts, &confflags, optargs, args.options); + dynamic = ast_test_flag(&confflags, CONFFLAG_DYNAMIC | CONFFLAG_DYNAMICPIN); + if (ast_test_flag(&confflags, CONFFLAG_DYNAMICPIN) && !args.pin) + strcpy(the_pin, "q"); + + empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN); + empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN); + always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT); + } + + do { + if (retrycnt > 3) + allowretry = 0; + if (empty) { + int i; + struct ast_config *cfg; + struct ast_variable *var; + int confno_int; + + /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */ + if ((empty_no_pin) || (!dynamic)) { + cfg = ast_config_load(CONFIG_FILE_NAME); + if (cfg) { + var = ast_variable_browse(cfg, "rooms"); + while (var) { + if (!strcasecmp(var->name, "conf")) { + char *stringp = ast_strdupa(var->value); + if (stringp) { + char *confno_tmp = strsep(&stringp, "|,"); + int found = 0; + if (!dynamic) { + /* For static: run through the list and see if this conference is empty */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strcmp(confno_tmp, cnf->confno)) { + /* The conference exists, therefore it's not empty */ + found = 1; + break; + } + } + AST_LIST_UNLOCK(&confs); + if (!found) { + /* At this point, we have a confno_tmp (static conference) that is empty */ + if ((empty_no_pin && ast_strlen_zero(stringp)) || (!empty_no_pin)) { + /* Case 1: empty_no_pin and pin is nonexistent (NULL) + * Case 2: empty_no_pin and pin is blank (but not NULL) + * Case 3: not empty_no_pin + */ + ast_copy_string(confno, confno_tmp, sizeof(confno)); + break; + /* XXX the map is not complete (but we do have a confno) */ + } + } + } + } + } + var = var->next; + } + ast_config_destroy(cfg); + } + } + + /* Select first conference number not in use */ + if (ast_strlen_zero(confno) && dynamic) { + AST_LIST_LOCK(&confs); + for (i = 0; i < sizeof(conf_map) / sizeof(conf_map[0]); i++) { + if (!conf_map[i]) { + snprintf(confno, sizeof(confno), "%d", i); + conf_map[i] = 1; + break; + } + } + AST_LIST_UNLOCK(&confs); + } + + /* Not found? */ + if (ast_strlen_zero(confno)) { + res = ast_streamfile(chan, "conf-noempty", chan->language); + if (!res) + ast_waitstream(chan, ""); + } else { + if (sscanf(confno, "%d", &confno_int) == 1) { + res = ast_streamfile(chan, "conf-enteringno", chan->language); + if (!res) { + ast_waitstream(chan, ""); + res = ast_say_digits(chan, confno_int, "", chan->language); + } + } else { + ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno); + } + } + } + + while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) { + /* Prompt user for conference number */ + res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0); + if (res < 0) { + /* Don't try to validate when we catch an error */ + confno[0] = '\0'; + allowretry = 0; + break; + } + } + if (!ast_strlen_zero(confno)) { + /* Check the validity of the conference */ + cnf = find_conf(chan, confno, 1, dynamic, the_pin, + sizeof(the_pin), 1, &confflags); + if (!cnf) { + cnf = find_conf_realtime(chan, confno, 1, dynamic, + the_pin, sizeof(the_pin), 1, &confflags); + } + + if (!cnf) { + res = ast_streamfile(chan, "conf-invalid", chan->language); + if (!res) + ast_waitstream(chan, ""); + res = -1; + if (allowretry) + confno[0] = '\0'; + } else { + if ((!ast_strlen_zero(cnf->pin) && + !ast_test_flag(&confflags, CONFFLAG_ADMIN)) || + (!ast_strlen_zero(cnf->pinadmin) && + ast_test_flag(&confflags, CONFFLAG_ADMIN))) { + char pin[MAX_PIN] = ""; + int j; + + /* Allow the pin to be retried up to 3 times */ + for (j = 0; j < 3; j++) { + if (*the_pin && (always_prompt == 0)) { + ast_copy_string(pin, the_pin, sizeof(pin)); + res = 0; + } else { + /* Prompt user for pin if pin is required */ + res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0); + } + if (res >= 0) { + if (!strcasecmp(pin, cnf->pin) || + (!ast_strlen_zero(cnf->pinadmin) && + !strcasecmp(pin, cnf->pinadmin))) { + /* Pin correct */ + allowretry = 0; + if (!ast_strlen_zero(cnf->pinadmin) && !strcasecmp(pin, cnf->pinadmin)) + ast_set_flag(&confflags, CONFFLAG_ADMIN); + /* Run the conference */ + res = conf_run(chan, cnf, confflags.flags, optargs); + break; + } else { + /* Pin invalid */ + if (!ast_streamfile(chan, "conf-invalidpin", chan->language)) { + res = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + } + else { + ast_log(LOG_WARNING, "Couldn't play invalid pin msg!\n"); + break; + } + if (res < 0) + break; + pin[0] = res; + pin[1] = '\0'; + res = -1; + if (allowretry) + confno[0] = '\0'; + } + } else { + /* failed when getting the pin */ + res = -1; + allowretry = 0; + /* see if we need to get rid of the conference */ + break; + } + + /* Don't retry pin with a static pin */ + if (*the_pin && (always_prompt==0)) { + break; + } + } + } else { + /* No pin required */ + allowretry = 0; + + /* Run the conference */ + res = conf_run(chan, cnf, confflags.flags, optargs); + } + dispose_conf(cnf); + cnf = NULL; + } + } + } while (allowretry); + + if (cnf) + dispose_conf(cnf); + + ast_module_user_remove(u); + + return res; +} + +static struct ast_conf_user *find_user(struct ast_conference *conf, char *callerident) +{ + struct ast_conf_user *user = NULL; + int cid; + + sscanf(callerident, "%i", &cid); + if (conf && callerident) { + AST_LIST_TRAVERSE(&conf->userlist, user, list) { + if (cid == user->user_no) + return user; + } + } + return NULL; +} + +/*! \brief The MeetMeadmin application */ +/* MeetMeAdmin(confno, command, caller) */ +static int admin_exec(struct ast_channel *chan, void *data) { + char *params; + struct ast_conference *cnf; + struct ast_conf_user *user = NULL; + struct ast_module_user *u; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(confno); + AST_APP_ARG(command); + AST_APP_ARG(user); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "MeetMeAdmin requires an argument!\n"); + return -1; + } + + u = ast_module_user_add(chan); + + AST_LIST_LOCK(&confs); + + params = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, params); + + if (!args.command) { + ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n"); + AST_LIST_UNLOCK(&confs); + ast_module_user_remove(u); + return -1; + } + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (!strcmp(cnf->confno, args.confno)) + break; + } + + if (!cnf) { + ast_log(LOG_WARNING, "Conference number '%s' not found!\n", args.confno); + AST_LIST_UNLOCK(&confs); + ast_module_user_remove(u); + return 0; + } + + ast_atomic_fetchadd_int(&cnf->refcount, 1); + + if (args.user) + user = find_user(cnf, args.user); + + switch (*args.command) { + case 76: /* L: Lock */ + cnf->locked = 1; + break; + case 108: /* l: Unlock */ + cnf->locked = 0; + break; + case 75: /* K: kick all users */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + user->adminflags |= ADMINFLAG_KICKME; + break; + case 101: /* e: Eject last user*/ + user = AST_LIST_LAST(&cnf->userlist); + if (!(user->userflags & CONFFLAG_ADMIN)) + user->adminflags |= ADMINFLAG_KICKME; + else + ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n"); + break; + case 77: /* M: Mute */ + if (user) { + user->adminflags |= ADMINFLAG_MUTED; + } else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 78: /* N: Mute all (non-admin) users */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) { + if (!(user->userflags & CONFFLAG_ADMIN)) + user->adminflags |= ADMINFLAG_MUTED; + } + break; + case 109: /* m: Unmute */ + if (user) { + user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED); + } else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 110: /* n: Unmute all users */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED); + break; + case 107: /* k: Kick user */ + if (user) + user->adminflags |= ADMINFLAG_KICKME; + else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 118: /* v: Lower all users listen volume */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + tweak_listen_volume(user, VOL_DOWN); + break; + case 86: /* V: Raise all users listen volume */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + tweak_listen_volume(user, VOL_UP); + break; + case 115: /* s: Lower all users speaking volume */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + tweak_talk_volume(user, VOL_DOWN); + break; + case 83: /* S: Raise all users speaking volume */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + tweak_talk_volume(user, VOL_UP); + break; + case 82: /* R: Reset all volume levels */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) + reset_volumes(user); + break; + case 114: /* r: Reset user's volume level */ + if (user) + reset_volumes(user); + else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 85: /* U: Raise user's listen volume */ + if (user) + tweak_listen_volume(user, VOL_UP); + else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 117: /* u: Lower user's listen volume */ + if (user) + tweak_listen_volume(user, VOL_DOWN); + else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 84: /* T: Raise user's talk volume */ + if (user) + tweak_talk_volume(user, VOL_UP); + else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + case 116: /* t: Lower user's talk volume */ + if (user) + tweak_talk_volume(user, VOL_DOWN); + else + ast_log(LOG_NOTICE, "Specified User not found!\n"); + break; + } + + AST_LIST_UNLOCK(&confs); + + dispose_conf(cnf); + + ast_module_user_remove(u); + + return 0; +} + +static int meetmemute(struct mansession *s, const struct message *m, int mute) +{ + struct ast_conference *conf; + struct ast_conf_user *user; + const char *confid = astman_get_header(m, "Meetme"); + char *userid = ast_strdupa(astman_get_header(m, "Usernum")); + int userno; + + if (ast_strlen_zero(confid)) { + astman_send_error(s, m, "Meetme conference not specified"); + return 0; + } + + if (ast_strlen_zero(userid)) { + astman_send_error(s, m, "Meetme user number not specified"); + return 0; + } + + userno = strtoul(userid, &userid, 10); + + if (*userid) { + astman_send_error(s, m, "Invalid user number"); + return 0; + } + + /* Look in the conference list */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, conf, list) { + if (!strcmp(confid, conf->confno)) + break; + } + + if (!conf) { + AST_LIST_UNLOCK(&confs); + astman_send_error(s, m, "Meetme conference does not exist"); + return 0; + } + + AST_LIST_TRAVERSE(&conf->userlist, user, list) + if (user->user_no == userno) + break; + + if (!user) { + AST_LIST_UNLOCK(&confs); + astman_send_error(s, m, "User number not found"); + return 0; + } + + if (mute) + user->adminflags |= ADMINFLAG_MUTED; /* request user muting */ + else + user->adminflags &= ~(ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED); /* request user unmuting */ + + AST_LIST_UNLOCK(&confs); + + ast_log(LOG_NOTICE, "Requested to %smute conf %s user %d userchan %s uniqueid %s\n", mute ? "" : "un", conf->confno, user->user_no, user->chan->name, user->chan->uniqueid); + + astman_send_ack(s, m, mute ? "User muted" : "User unmuted"); + return 0; +} + +static int action_meetmemute(struct mansession *s, const struct message *m) +{ + return meetmemute(s, m, 1); +} + +static int action_meetmeunmute(struct mansession *s, const struct message *m) +{ + return meetmemute(s, m, 0); +} + +static void *recordthread(void *args) +{ + struct ast_conference *cnf = args; + struct ast_frame *f=NULL; + int flags; + struct ast_filestream *s=NULL; + int res=0; + int x; + const char *oldrecordingfilename = NULL; + + if (!cnf || !cnf->lchan) { + pthread_exit(0); + } + + ast_stopstream(cnf->lchan); + flags = O_CREAT|O_TRUNC|O_WRONLY; + + + cnf->recording = MEETME_RECORD_ACTIVE; + while (ast_waitfor(cnf->lchan, -1) > -1) { + if (cnf->recording == MEETME_RECORD_TERMINATE) { + AST_LIST_LOCK(&confs); + AST_LIST_UNLOCK(&confs); + break; + } + if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) { + s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644); + oldrecordingfilename = cnf->recordingfilename; + } + + f = ast_read(cnf->lchan); + if (!f) { + res = -1; + break; + } + if (f->frametype == AST_FRAME_VOICE) { + ast_mutex_lock(&cnf->listenlock); + for (x=0;xtransframe[x]) { + ast_frfree(cnf->transframe[x]); + cnf->transframe[x] = NULL; + } + } + if (cnf->origframe) + ast_frfree(cnf->origframe); + cnf->origframe = ast_frdup(f); + ast_mutex_unlock(&cnf->listenlock); + if (s) + res = ast_writestream(s, f); + if (res) { + ast_frfree(f); + break; + } + } + ast_frfree(f); + } + cnf->recording = MEETME_RECORD_OFF; + if (s) + ast_closestream(s); + + pthread_exit(0); +} + +/*! \brief Callback for devicestate providers */ +static int meetmestate(const char *data) +{ + struct ast_conference *conf; + + /* Find conference */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, conf, list) { + if (!strcmp(data, conf->confno)) + break; + } + AST_LIST_UNLOCK(&confs); + if (!conf) + return AST_DEVICE_INVALID; + + + /* SKREP to fill */ + if (!conf->users) + return AST_DEVICE_NOT_INUSE; + + return AST_DEVICE_INUSE; +} + +static void load_config_meetme(void) +{ + struct ast_config *cfg; + const char *val; + + audio_buffers = DEFAULT_AUDIO_BUFFERS; + + if (!(cfg = ast_config_load(CONFIG_FILE_NAME))) + return; + + if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) { + if ((sscanf(val, "%d", &audio_buffers) != 1)) { + ast_log(LOG_WARNING, "audiobuffers setting must be a number, not '%s'\n", val); + audio_buffers = DEFAULT_AUDIO_BUFFERS; + } else if ((audio_buffers < DAHDI_DEFAULT_NUM_BUFS) || (audio_buffers > DAHDI_MAX_NUM_BUFS)) { + ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n", + DAHDI_DEFAULT_NUM_BUFS, DAHDI_MAX_NUM_BUFS); + audio_buffers = DEFAULT_AUDIO_BUFFERS; + } + if (audio_buffers != DEFAULT_AUDIO_BUFFERS) + ast_log(LOG_NOTICE, "Audio buffers per channel set to %d\n", audio_buffers); + } + + ast_config_destroy(cfg); +} + +/*! \brief Find an SLA trunk by name + * \note This must be called with the sla_trunks container locked + */ +static struct sla_trunk *sla_find_trunk(const char *name) +{ + struct sla_trunk *trunk = NULL; + + AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) { + if (!strcasecmp(trunk->name, name)) + break; + } + + return trunk; +} + +/*! \brief Find an SLA station by name + * \note This must be called with the sla_stations container locked + */ +static struct sla_station *sla_find_station(const char *name) +{ + struct sla_station *station = NULL; + + AST_RWLIST_TRAVERSE(&sla_stations, station, entry) { + if (!strcasecmp(station->name, name)) + break; + } + + return station; +} + +static int sla_check_station_hold_access(const struct sla_trunk *trunk, + const struct sla_station *station) +{ + struct sla_station_ref *station_ref; + struct sla_trunk_ref *trunk_ref; + + /* For each station that has this call on hold, check for private hold. */ + AST_LIST_TRAVERSE(&trunk->stations, station_ref, entry) { + AST_LIST_TRAVERSE(&station_ref->station->trunks, trunk_ref, entry) { + if (trunk_ref->trunk != trunk || station_ref->station == station) + continue; + if (trunk_ref->state == SLA_TRUNK_STATE_ONHOLD_BYME && + station_ref->station->hold_access == SLA_HOLD_PRIVATE) + return 1; + return 0; + } + } + + return 0; +} + +/*! \brief Find a trunk reference on a station by name + * \param station the station + * \param name the trunk's name + * \return a pointer to the station's trunk reference. If the trunk + * is not found, it is not idle and barge is disabled, or if + * it is on hold and private hold is set, then NULL will be returned. + */ +static struct sla_trunk_ref *sla_find_trunk_ref_byname(const struct sla_station *station, + const char *name) +{ + struct sla_trunk_ref *trunk_ref = NULL; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (strcasecmp(trunk_ref->trunk->name, name)) + continue; + + if ( (trunk_ref->trunk->barge_disabled + && trunk_ref->state == SLA_TRUNK_STATE_UP) || + (trunk_ref->trunk->hold_stations + && trunk_ref->trunk->hold_access == SLA_HOLD_PRIVATE + && trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) || + sla_check_station_hold_access(trunk_ref->trunk, station) ) + { + trunk_ref = NULL; + } + + break; + } + + return trunk_ref; +} + +static struct sla_station_ref *sla_create_station_ref(struct sla_station *station) +{ + struct sla_station_ref *station_ref; + + if (!(station_ref = ast_calloc(1, sizeof(*station_ref)))) + return NULL; + + station_ref->station = station; + + return station_ref; +} + +static struct sla_ringing_station *sla_create_ringing_station(struct sla_station *station) +{ + struct sla_ringing_station *ringing_station; + + if (!(ringing_station = ast_calloc(1, sizeof(*ringing_station)))) + return NULL; + + ringing_station->station = station; + ringing_station->ring_begin = ast_tvnow(); + + return ringing_station; +} + +static void sla_change_trunk_state(const struct sla_trunk *trunk, enum sla_trunk_state state, + enum sla_which_trunk_refs inactive_only, const struct sla_trunk_ref *exclude) +{ + struct sla_station *station; + struct sla_trunk_ref *trunk_ref; + + AST_LIST_TRAVERSE(&sla_stations, station, entry) { + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->trunk != trunk || (inactive_only ? trunk_ref->chan : 0) + || trunk_ref == exclude) + continue; + trunk_ref->state = state; + ast_device_state_changed("SLA:%s_%s", station->name, trunk->name); + break; + } + } +} + +struct run_station_args { + struct sla_station *station; + struct sla_trunk_ref *trunk_ref; + ast_mutex_t *cond_lock; + ast_cond_t *cond; +}; + +static void *run_station(void *data) +{ + struct sla_station *station; + struct sla_trunk_ref *trunk_ref; + char conf_name[MAX_CONFNUM]; + struct ast_flags conf_flags = { 0 }; + struct ast_conference *conf; + + { + struct run_station_args *args = data; + station = args->station; + trunk_ref = args->trunk_ref; + ast_mutex_lock(args->cond_lock); + ast_cond_signal(args->cond); + ast_mutex_unlock(args->cond_lock); + /* args is no longer valid here. */ + } + + ast_atomic_fetchadd_int((int *) &trunk_ref->trunk->active_stations, 1); + snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name); + ast_set_flag(&conf_flags, + CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF | CONFFLAG_SLA_STATION); + ast_answer(trunk_ref->chan); + conf = build_conf(conf_name, "", "", 0, 0, 1); + if (conf) { + conf_run(trunk_ref->chan, conf, conf_flags.flags, NULL); + dispose_conf(conf); + conf = NULL; + } + trunk_ref->chan = NULL; + if (ast_atomic_dec_and_test((int *) &trunk_ref->trunk->active_stations) && + trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) { + strncat(conf_name, "|K", sizeof(conf_name) - strlen(conf_name) - 1); + admin_exec(NULL, conf_name); + trunk_ref->trunk->hold_stations = 0; + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + } + + ast_dial_join(station->dial); + ast_dial_destroy(station->dial); + station->dial = NULL; + + return NULL; +} + +static void sla_stop_ringing_trunk(struct sla_ringing_trunk *ringing_trunk) +{ + char buf[80]; + struct sla_station_ref *station_ref; + + snprintf(buf, sizeof(buf), "SLA_%s|K", ringing_trunk->trunk->name); + admin_exec(NULL, buf); + sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + + while ((station_ref = AST_LIST_REMOVE_HEAD(&ringing_trunk->timed_out_stations, entry))) + free(station_ref); + + free(ringing_trunk); +} + +static void sla_stop_ringing_station(struct sla_ringing_station *ringing_station, + enum sla_station_hangup hangup) +{ + struct sla_ringing_trunk *ringing_trunk; + struct sla_trunk_ref *trunk_ref; + struct sla_station_ref *station_ref; + + ast_dial_join(ringing_station->station->dial); + ast_dial_destroy(ringing_station->station->dial); + ringing_station->station->dial = NULL; + + if (hangup == SLA_STATION_HANGUP_NORMAL) + goto done; + + /* If the station is being hung up because of a timeout, then add it to the + * list of timed out stations on each of the ringing trunks. This is so + * that when doing further processing to figure out which stations should be + * ringing, which trunk to answer, determining timeouts, etc., we know which + * ringing trunks we should ignore. */ + AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) { + AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) { + if (ringing_trunk->trunk == trunk_ref->trunk) + break; + } + if (!trunk_ref) + continue; + if (!(station_ref = sla_create_station_ref(ringing_station->station))) + continue; + AST_LIST_INSERT_TAIL(&ringing_trunk->timed_out_stations, station_ref, entry); + } + +done: + free(ringing_station); +} + +static void sla_dial_state_callback(struct ast_dial *dial) +{ + sla_queue_event(SLA_EVENT_DIAL_STATE); +} + +/*! \brief Check to see if dialing this station already timed out for this ringing trunk + * \note Assumes sla.lock is locked + */ +static int sla_check_timed_out_station(const struct sla_ringing_trunk *ringing_trunk, + const struct sla_station *station) +{ + struct sla_station_ref *timed_out_station; + + AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, timed_out_station, entry) { + if (station == timed_out_station->station) + return 1; + } + + return 0; +} + +/*! \brief Choose the highest priority ringing trunk for a station + * \param station the station + * \param remove remove the ringing trunk once selected + * \param trunk_ref a place to store the pointer to this stations reference to + * the selected trunk + * \return a pointer to the selected ringing trunk, or NULL if none found + * \note Assumes that sla.lock is locked + */ +static struct sla_ringing_trunk *sla_choose_ringing_trunk(struct sla_station *station, + struct sla_trunk_ref **trunk_ref, int remove) +{ + struct sla_trunk_ref *s_trunk_ref; + struct sla_ringing_trunk *ringing_trunk = NULL; + + AST_LIST_TRAVERSE(&station->trunks, s_trunk_ref, entry) { + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { + /* Make sure this is the trunk we're looking for */ + if (s_trunk_ref->trunk != ringing_trunk->trunk) + continue; + + /* This trunk on the station is ringing. But, make sure this station + * didn't already time out while this trunk was ringing. */ + if (sla_check_timed_out_station(ringing_trunk, station)) + continue; + + if (remove) + AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); + + if (trunk_ref) + *trunk_ref = s_trunk_ref; + + break; + } + AST_LIST_TRAVERSE_SAFE_END + + if (ringing_trunk) + break; + } + + return ringing_trunk; +} + +static void sla_handle_dial_state_event(void) +{ + struct sla_ringing_station *ringing_station; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) { + struct sla_trunk_ref *s_trunk_ref = NULL; + struct sla_ringing_trunk *ringing_trunk = NULL; + struct run_station_args args; + enum ast_dial_result dial_res; + pthread_attr_t attr; + pthread_t dont_care; + ast_mutex_t cond_lock; + ast_cond_t cond; + + switch ((dial_res = ast_dial_state(ringing_station->station->dial))) { + case AST_DIAL_RESULT_HANGUP: + case AST_DIAL_RESULT_INVALID: + case AST_DIAL_RESULT_FAILED: + case AST_DIAL_RESULT_TIMEOUT: + case AST_DIAL_RESULT_UNANSWERED: + AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry); + sla_stop_ringing_station(ringing_station, SLA_STATION_HANGUP_NORMAL); + break; + case AST_DIAL_RESULT_ANSWERED: + AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry); + /* Find the appropriate trunk to answer. */ + ast_mutex_lock(&sla.lock); + ringing_trunk = sla_choose_ringing_trunk(ringing_station->station, &s_trunk_ref, 1); + ast_mutex_unlock(&sla.lock); + if (!ringing_trunk) { + ast_log(LOG_DEBUG, "Found no ringing trunk for station '%s' to answer!\n", + ringing_station->station->name); + break; + } + /* Track the channel that answered this trunk */ + s_trunk_ref->chan = ast_dial_answered(ringing_station->station->dial); + /* Actually answer the trunk */ + ast_answer(ringing_trunk->trunk->chan); + sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL); + /* Now, start a thread that will connect this station to the trunk. The rest of + * the code here sets up the thread and ensures that it is able to save the arguments + * before they are no longer valid since they are allocated on the stack. */ + args.trunk_ref = s_trunk_ref; + args.station = ringing_station->station; + args.cond = &cond; + args.cond_lock = &cond_lock; + free(ringing_trunk); + free(ringing_station); + ast_mutex_init(&cond_lock); + ast_cond_init(&cond, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ast_mutex_lock(&cond_lock); + ast_pthread_create_background(&dont_care, &attr, run_station, &args); + ast_cond_wait(&cond, &cond_lock); + ast_mutex_unlock(&cond_lock); + ast_mutex_destroy(&cond_lock); + ast_cond_destroy(&cond); + pthread_attr_destroy(&attr); + break; + case AST_DIAL_RESULT_TRYING: + case AST_DIAL_RESULT_RINGING: + case AST_DIAL_RESULT_PROGRESS: + case AST_DIAL_RESULT_PROCEEDING: + break; + } + if (dial_res == AST_DIAL_RESULT_ANSWERED) { + /* Queue up reprocessing ringing trunks, and then ringing stations again */ + sla_queue_event(SLA_EVENT_RINGING_TRUNK); + sla_queue_event(SLA_EVENT_DIAL_STATE); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END +} + +/*! \brief Check to see if this station is already ringing + * \note Assumes sla.lock is locked + */ +static int sla_check_ringing_station(const struct sla_station *station) +{ + struct sla_ringing_station *ringing_station; + + AST_LIST_TRAVERSE(&sla.ringing_stations, ringing_station, entry) { + if (station == ringing_station->station) + return 1; + } + + return 0; +} + +/*! \brief Check to see if this station has failed to be dialed in the past minute + * \note assumes sla.lock is locked + */ +static int sla_check_failed_station(const struct sla_station *station) +{ + struct sla_failed_station *failed_station; + int res = 0; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.failed_stations, failed_station, entry) { + if (station != failed_station->station) + continue; + if (ast_tvdiff_ms(ast_tvnow(), failed_station->last_try) > 1000) { + AST_LIST_REMOVE_CURRENT(&sla.failed_stations, entry); + free(failed_station); + break; + } + res = 1; + } + AST_LIST_TRAVERSE_SAFE_END + + return res; +} + +/*! \brief Ring a station + * \note Assumes sla.lock is locked + */ +static int sla_ring_station(struct sla_ringing_trunk *ringing_trunk, struct sla_station *station) +{ + char *tech, *tech_data; + struct ast_dial *dial; + struct sla_ringing_station *ringing_station; + const char *cid_name = NULL, *cid_num = NULL; + enum ast_dial_result res; + + if (!(dial = ast_dial_create())) + return -1; + + ast_dial_set_state_callback(dial, sla_dial_state_callback); + tech_data = ast_strdupa(station->device); + tech = strsep(&tech_data, "/"); + + if (ast_dial_append(dial, tech, tech_data) == -1) { + ast_dial_destroy(dial); + return -1; + } + + if (!sla.attempt_callerid && !ast_strlen_zero(ringing_trunk->trunk->chan->cid.cid_name)) { + cid_name = ast_strdupa(ringing_trunk->trunk->chan->cid.cid_name); + free(ringing_trunk->trunk->chan->cid.cid_name); + ringing_trunk->trunk->chan->cid.cid_name = NULL; + } + if (!sla.attempt_callerid && !ast_strlen_zero(ringing_trunk->trunk->chan->cid.cid_num)) { + cid_num = ast_strdupa(ringing_trunk->trunk->chan->cid.cid_num); + free(ringing_trunk->trunk->chan->cid.cid_num); + ringing_trunk->trunk->chan->cid.cid_num = NULL; + } + + res = ast_dial_run(dial, ringing_trunk->trunk->chan, 1); + + if (cid_name) + ringing_trunk->trunk->chan->cid.cid_name = ast_strdup(cid_name); + if (cid_num) + ringing_trunk->trunk->chan->cid.cid_num = ast_strdup(cid_num); + + if (res != AST_DIAL_RESULT_TRYING) { + struct sla_failed_station *failed_station; + ast_dial_destroy(dial); + if (!(failed_station = ast_calloc(1, sizeof(*failed_station)))) + return -1; + failed_station->station = station; + failed_station->last_try = ast_tvnow(); + AST_LIST_INSERT_HEAD(&sla.failed_stations, failed_station, entry); + return -1; + } + if (!(ringing_station = sla_create_ringing_station(station))) { + ast_dial_join(dial); + ast_dial_destroy(dial); + return -1; + } + + station->dial = dial; + + AST_LIST_INSERT_HEAD(&sla.ringing_stations, ringing_station, entry); + + return 0; +} + +/*! \brief Check to see if a station is in use + */ +static int sla_check_inuse_station(const struct sla_station *station) +{ + struct sla_trunk_ref *trunk_ref; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->chan) + return 1; + } + + return 0; +} + +static struct sla_trunk_ref *sla_find_trunk_ref(const struct sla_station *station, + const struct sla_trunk *trunk) +{ + struct sla_trunk_ref *trunk_ref = NULL; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->trunk == trunk) + break; + } + + return trunk_ref; +} + +/*! \brief Calculate the ring delay for a given ringing trunk on a station + * \param station the station + * \param trunk the trunk. If NULL, the highest priority ringing trunk will be used + * \return the number of ms left before the delay is complete, or INT_MAX if there is no delay + */ +static int sla_check_station_delay(struct sla_station *station, + struct sla_ringing_trunk *ringing_trunk) +{ + struct sla_trunk_ref *trunk_ref; + unsigned int delay = UINT_MAX; + int time_left, time_elapsed; + + if (!ringing_trunk) + ringing_trunk = sla_choose_ringing_trunk(station, &trunk_ref, 0); + else + trunk_ref = sla_find_trunk_ref(station, ringing_trunk->trunk); + + if (!ringing_trunk || !trunk_ref) + return delay; + + /* If this station has a ring delay specific to the highest priority + * ringing trunk, use that. Otherwise, use the ring delay specified + * globally for the station. */ + delay = trunk_ref->ring_delay; + if (!delay) + delay = station->ring_delay; + if (!delay) + return INT_MAX; + + time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin); + time_left = (delay * 1000) - time_elapsed; + + return time_left; +} + +/*! \brief Ring stations based on current set of ringing trunks + * \note Assumes that sla.lock is locked + */ +static void sla_ring_stations(void) +{ + struct sla_station_ref *station_ref; + struct sla_ringing_trunk *ringing_trunk; + + /* Make sure that every station that uses at least one of the ringing + * trunks, is ringing. */ + AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) { + AST_LIST_TRAVERSE(&ringing_trunk->trunk->stations, station_ref, entry) { + int time_left; + + /* Is this station already ringing? */ + if (sla_check_ringing_station(station_ref->station)) + continue; + + /* Is this station already in a call? */ + if (sla_check_inuse_station(station_ref->station)) + continue; + + /* Did we fail to dial this station earlier? If so, has it been + * a minute since we tried? */ + if (sla_check_failed_station(station_ref->station)) + continue; + + /* If this station already timed out while this trunk was ringing, + * do not dial it again for this ringing trunk. */ + if (sla_check_timed_out_station(ringing_trunk, station_ref->station)) + continue; + + /* Check for a ring delay in progress */ + time_left = sla_check_station_delay(station_ref->station, ringing_trunk); + if (time_left != INT_MAX && time_left > 0) + continue; + + /* It is time to make this station begin to ring. Do it! */ + sla_ring_station(ringing_trunk, station_ref->station); + } + } + /* Now, all of the stations that should be ringing, are ringing. */ +} + +static void sla_hangup_stations(void) +{ + struct sla_trunk_ref *trunk_ref; + struct sla_ringing_station *ringing_station; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) { + AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) { + struct sla_ringing_trunk *ringing_trunk; + ast_mutex_lock(&sla.lock); + AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) { + if (trunk_ref->trunk == ringing_trunk->trunk) + break; + } + ast_mutex_unlock(&sla.lock); + if (ringing_trunk) + break; + } + if (!trunk_ref) { + AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry); + ast_dial_join(ringing_station->station->dial); + ast_dial_destroy(ringing_station->station->dial); + ringing_station->station->dial = NULL; + free(ringing_station); + } + } + AST_LIST_TRAVERSE_SAFE_END +} + +static void sla_handle_ringing_trunk_event(void) +{ + ast_mutex_lock(&sla.lock); + sla_ring_stations(); + ast_mutex_unlock(&sla.lock); + + /* Find stations that shouldn't be ringing anymore. */ + sla_hangup_stations(); +} + +static void sla_handle_hold_event(struct sla_event *event) +{ + ast_atomic_fetchadd_int((int *) &event->trunk_ref->trunk->hold_stations, 1); + event->trunk_ref->state = SLA_TRUNK_STATE_ONHOLD_BYME; + ast_device_state_changed("SLA:%s_%s", + event->station->name, event->trunk_ref->trunk->name); + sla_change_trunk_state(event->trunk_ref->trunk, SLA_TRUNK_STATE_ONHOLD, + INACTIVE_TRUNK_REFS, event->trunk_ref); + + if (event->trunk_ref->trunk->active_stations == 1) { + /* The station putting it on hold is the only one on the call, so start + * Music on hold to the trunk. */ + event->trunk_ref->trunk->on_hold = 1; + ast_indicate(event->trunk_ref->trunk->chan, AST_CONTROL_HOLD); + } + + ast_softhangup(event->trunk_ref->chan, AST_CAUSE_NORMAL); + event->trunk_ref->chan = NULL; +} + +/*! \brief Process trunk ring timeouts + * \note Called with sla.lock locked + * \return non-zero if a change to the ringing trunks was made + */ +static int sla_calc_trunk_timeouts(unsigned int *timeout) +{ + struct sla_ringing_trunk *ringing_trunk; + int res = 0; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { + int time_left, time_elapsed; + if (!ringing_trunk->trunk->ring_timeout) + continue; + time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin); + time_left = (ringing_trunk->trunk->ring_timeout * 1000) - time_elapsed; + if (time_left <= 0) { + pbx_builtin_setvar_helper(ringing_trunk->trunk->chan, "SLATRUNK_STATUS", "RINGTIMEOUT"); + AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); + sla_stop_ringing_trunk(ringing_trunk); + res = 1; + continue; + } + if (time_left < *timeout) + *timeout = time_left; + } + AST_LIST_TRAVERSE_SAFE_END + + return res; +} + +/*! \brief Process station ring timeouts + * \note Called with sla.lock locked + * \return non-zero if a change to the ringing stations was made + */ +static int sla_calc_station_timeouts(unsigned int *timeout) +{ + struct sla_ringing_trunk *ringing_trunk; + struct sla_ringing_station *ringing_station; + int res = 0; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_stations, ringing_station, entry) { + unsigned int ring_timeout = 0; + int time_elapsed, time_left = INT_MAX, final_trunk_time_left = INT_MIN; + struct sla_trunk_ref *trunk_ref; + + /* If there are any ring timeouts specified for a specific trunk + * on the station, then use the highest per-trunk ring timeout. + * Otherwise, use the ring timeout set for the entire station. */ + AST_LIST_TRAVERSE(&ringing_station->station->trunks, trunk_ref, entry) { + struct sla_station_ref *station_ref; + int trunk_time_elapsed, trunk_time_left; + + AST_LIST_TRAVERSE(&sla.ringing_trunks, ringing_trunk, entry) { + if (ringing_trunk->trunk == trunk_ref->trunk) + break; + } + if (!ringing_trunk) + continue; + + /* If there is a trunk that is ringing without a timeout, then the + * only timeout that could matter is a global station ring timeout. */ + if (!trunk_ref->ring_timeout) + break; + + /* This trunk on this station is ringing and has a timeout. + * However, make sure this trunk isn't still ringing from a + * previous timeout. If so, don't consider it. */ + AST_LIST_TRAVERSE(&ringing_trunk->timed_out_stations, station_ref, entry) { + if (station_ref->station == ringing_station->station) + break; + } + if (station_ref) + continue; + + trunk_time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_trunk->ring_begin); + trunk_time_left = (trunk_ref->ring_timeout * 1000) - trunk_time_elapsed; + if (trunk_time_left > final_trunk_time_left) + final_trunk_time_left = trunk_time_left; + } + + /* No timeout was found for ringing trunks, and no timeout for the entire station */ + if (final_trunk_time_left == INT_MIN && !ringing_station->station->ring_timeout) + continue; + + /* Compute how much time is left for a global station timeout */ + if (ringing_station->station->ring_timeout) { + ring_timeout = ringing_station->station->ring_timeout; + time_elapsed = ast_tvdiff_ms(ast_tvnow(), ringing_station->ring_begin); + time_left = (ring_timeout * 1000) - time_elapsed; + } + + /* If the time left based on the per-trunk timeouts is smaller than the + * global station ring timeout, use that. */ + if (final_trunk_time_left > INT_MIN && final_trunk_time_left < time_left) + time_left = final_trunk_time_left; + + /* If there is no time left, the station needs to stop ringing */ + if (time_left <= 0) { + AST_LIST_REMOVE_CURRENT(&sla.ringing_stations, entry); + sla_stop_ringing_station(ringing_station, SLA_STATION_HANGUP_TIMEOUT); + res = 1; + continue; + } + + /* There is still some time left for this station to ring, so save that + * timeout if it is the first event scheduled to occur */ + if (time_left < *timeout) + *timeout = time_left; + } + AST_LIST_TRAVERSE_SAFE_END + + return res; +} + +/*! \brief Calculate the ring delay for a station + * \note Assumes sla.lock is locked + */ +static int sla_calc_station_delays(unsigned int *timeout) +{ + struct sla_station *station; + int res = 0; + + AST_LIST_TRAVERSE(&sla_stations, station, entry) { + struct sla_ringing_trunk *ringing_trunk; + int time_left; + + /* Ignore stations already ringing */ + if (sla_check_ringing_station(station)) + continue; + + /* Ignore stations already on a call */ + if (sla_check_inuse_station(station)) + continue; + + /* Ignore stations that don't have one of their trunks ringing */ + if (!(ringing_trunk = sla_choose_ringing_trunk(station, NULL, 0))) + continue; + + if ((time_left = sla_check_station_delay(station, ringing_trunk)) == INT_MAX) + continue; + + /* If there is no time left, then the station needs to start ringing. + * Return non-zero so that an event will be queued up an event to + * make that happen. */ + if (time_left <= 0) { + res = 1; + continue; + } + + if (time_left < *timeout) + *timeout = time_left; + } + + return res; +} + +/*! \brief Calculate the time until the next known event + * \note Called with sla.lock locked */ +static int sla_process_timers(struct timespec *ts) +{ + unsigned int timeout = UINT_MAX; + struct timeval tv; + unsigned int change_made = 0; + + /* Check for ring timeouts on ringing trunks */ + if (sla_calc_trunk_timeouts(&timeout)) + change_made = 1; + + /* Check for ring timeouts on ringing stations */ + if (sla_calc_station_timeouts(&timeout)) + change_made = 1; + + /* Check for station ring delays */ + if (sla_calc_station_delays(&timeout)) + change_made = 1; + + /* queue reprocessing of ringing trunks */ + if (change_made) + sla_queue_event_nolock(SLA_EVENT_RINGING_TRUNK); + + /* No timeout */ + if (timeout == UINT_MAX) + return 0; + + if (ts) { + tv = ast_tvadd(ast_tvnow(), ast_samp2tv(timeout, 1000)); + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; + } + + return 1; +} + +static void *sla_thread(void *data) +{ + struct sla_failed_station *failed_station; + struct sla_ringing_station *ringing_station; + + ast_mutex_lock(&sla.lock); + + while (!sla.stop) { + struct sla_event *event; + struct timespec ts = { 0, }; + unsigned int have_timeout = 0; + + if (AST_LIST_EMPTY(&sla.event_q)) { + if ((have_timeout = sla_process_timers(&ts))) + ast_cond_timedwait(&sla.cond, &sla.lock, &ts); + else + ast_cond_wait(&sla.cond, &sla.lock); + if (sla.stop) + break; + } + + if (have_timeout) + sla_process_timers(NULL); + + while ((event = AST_LIST_REMOVE_HEAD(&sla.event_q, entry))) { + ast_mutex_unlock(&sla.lock); + switch (event->type) { + case SLA_EVENT_HOLD: + sla_handle_hold_event(event); + break; + case SLA_EVENT_DIAL_STATE: + sla_handle_dial_state_event(); + break; + case SLA_EVENT_RINGING_TRUNK: + sla_handle_ringing_trunk_event(); + break; + } + free(event); + ast_mutex_lock(&sla.lock); + } + } + + ast_mutex_unlock(&sla.lock); + + while ((ringing_station = AST_LIST_REMOVE_HEAD(&sla.ringing_stations, entry))) + free(ringing_station); + + while ((failed_station = AST_LIST_REMOVE_HEAD(&sla.failed_stations, entry))) + free(failed_station); + + return NULL; +} + +struct dial_trunk_args { + struct sla_trunk_ref *trunk_ref; + struct sla_station *station; + ast_mutex_t *cond_lock; + ast_cond_t *cond; +}; + +static void *dial_trunk(void *data) +{ + struct dial_trunk_args *args = data; + struct ast_dial *dial; + char *tech, *tech_data; + enum ast_dial_result dial_res; + char conf_name[MAX_CONFNUM]; + struct ast_conference *conf; + struct ast_flags conf_flags = { 0 }; + struct sla_trunk_ref *trunk_ref = args->trunk_ref; + const char *cid_name = NULL, *cid_num = NULL; + + if (!(dial = ast_dial_create())) { + ast_mutex_lock(args->cond_lock); + ast_cond_signal(args->cond); + ast_mutex_unlock(args->cond_lock); + return NULL; + } + + tech_data = ast_strdupa(trunk_ref->trunk->device); + tech = strsep(&tech_data, "/"); + if (ast_dial_append(dial, tech, tech_data) == -1) { + ast_mutex_lock(args->cond_lock); + ast_cond_signal(args->cond); + ast_mutex_unlock(args->cond_lock); + ast_dial_destroy(dial); + return NULL; + } + + if (!sla.attempt_callerid && !ast_strlen_zero(trunk_ref->chan->cid.cid_name)) { + cid_name = ast_strdupa(trunk_ref->chan->cid.cid_name); + free(trunk_ref->chan->cid.cid_name); + trunk_ref->chan->cid.cid_name = NULL; + } + if (!sla.attempt_callerid && !ast_strlen_zero(trunk_ref->chan->cid.cid_num)) { + cid_num = ast_strdupa(trunk_ref->chan->cid.cid_num); + free(trunk_ref->chan->cid.cid_num); + trunk_ref->chan->cid.cid_num = NULL; + } + + dial_res = ast_dial_run(dial, trunk_ref->chan, 1); + + if (cid_name) + trunk_ref->chan->cid.cid_name = ast_strdup(cid_name); + if (cid_num) + trunk_ref->chan->cid.cid_num = ast_strdup(cid_num); + + if (dial_res != AST_DIAL_RESULT_TRYING) { + ast_mutex_lock(args->cond_lock); + ast_cond_signal(args->cond); + ast_mutex_unlock(args->cond_lock); + ast_dial_destroy(dial); + return NULL; + } + + for (;;) { + unsigned int done = 0; + switch ((dial_res = ast_dial_state(dial))) { + case AST_DIAL_RESULT_ANSWERED: + trunk_ref->trunk->chan = ast_dial_answered(dial); + case AST_DIAL_RESULT_HANGUP: + case AST_DIAL_RESULT_INVALID: + case AST_DIAL_RESULT_FAILED: + case AST_DIAL_RESULT_TIMEOUT: + case AST_DIAL_RESULT_UNANSWERED: + done = 1; + case AST_DIAL_RESULT_TRYING: + case AST_DIAL_RESULT_RINGING: + case AST_DIAL_RESULT_PROGRESS: + case AST_DIAL_RESULT_PROCEEDING: + break; + } + if (done) + break; + } + + if (!trunk_ref->trunk->chan) { + ast_mutex_lock(args->cond_lock); + ast_cond_signal(args->cond); + ast_mutex_unlock(args->cond_lock); + ast_dial_join(dial); + ast_dial_destroy(dial); + return NULL; + } + + snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name); + ast_set_flag(&conf_flags, + CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | + CONFFLAG_PASS_DTMF | CONFFLAG_SLA_TRUNK); + conf = build_conf(conf_name, "", "", 1, 1, 1); + + ast_mutex_lock(args->cond_lock); + ast_cond_signal(args->cond); + ast_mutex_unlock(args->cond_lock); + + if (conf) { + conf_run(trunk_ref->trunk->chan, conf, conf_flags.flags, NULL); + dispose_conf(conf); + conf = NULL; + } + + /* If the trunk is going away, it is definitely now IDLE. */ + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + + trunk_ref->trunk->chan = NULL; + trunk_ref->trunk->on_hold = 0; + + ast_dial_join(dial); + ast_dial_destroy(dial); + + return NULL; +} + +/*! \brief For a given station, choose the highest priority idle trunk + */ +static struct sla_trunk_ref *sla_choose_idle_trunk(const struct sla_station *station) +{ + struct sla_trunk_ref *trunk_ref = NULL; + + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (trunk_ref->state == SLA_TRUNK_STATE_IDLE) + break; + } + + return trunk_ref; +} + +static int sla_station_exec(struct ast_channel *chan, void *data) +{ + char *station_name, *trunk_name; + struct sla_station *station; + struct sla_trunk_ref *trunk_ref = NULL; + char conf_name[MAX_CONFNUM]; + struct ast_flags conf_flags = { 0 }; + struct ast_conference *conf; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Invalid Arguments to SLAStation!\n"); + pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE"); + return 0; + } + + trunk_name = ast_strdupa(data); + station_name = strsep(&trunk_name, "_"); + + if (ast_strlen_zero(station_name)) { + ast_log(LOG_WARNING, "Invalid Arguments to SLAStation!\n"); + pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE"); + return 0; + } + + AST_RWLIST_RDLOCK(&sla_stations); + station = sla_find_station(station_name); + AST_RWLIST_UNLOCK(&sla_stations); + + if (!station) { + ast_log(LOG_WARNING, "Station '%s' not found!\n", station_name); + pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "FAILURE"); + return 0; + } + + AST_RWLIST_RDLOCK(&sla_trunks); + if (!ast_strlen_zero(trunk_name)) { + trunk_ref = sla_find_trunk_ref_byname(station, trunk_name); + } else + trunk_ref = sla_choose_idle_trunk(station); + AST_RWLIST_UNLOCK(&sla_trunks); + + if (!trunk_ref) { + if (ast_strlen_zero(trunk_name)) + ast_log(LOG_NOTICE, "No trunks available for call.\n"); + else { + ast_log(LOG_NOTICE, "Can't join existing call on trunk " + "'%s' due to access controls.\n", trunk_name); + } + pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION"); + return 0; + } + + if (trunk_ref->state == SLA_TRUNK_STATE_ONHOLD_BYME) { + if (ast_atomic_dec_and_test((int *) &trunk_ref->trunk->hold_stations) == 1) + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL); + else { + trunk_ref->state = SLA_TRUNK_STATE_UP; + ast_device_state_changed("SLA:%s_%s", station->name, trunk_ref->trunk->name); + } + } else if (trunk_ref->state == SLA_TRUNK_STATE_RINGING) { + struct sla_ringing_trunk *ringing_trunk; + + ast_mutex_lock(&sla.lock); + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { + if (ringing_trunk->trunk == trunk_ref->trunk) { + AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + ast_mutex_unlock(&sla.lock); + + if (ringing_trunk) { + ast_answer(ringing_trunk->trunk->chan); + sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL); + + free(ringing_trunk); + + /* Queue up reprocessing ringing trunks, and then ringing stations again */ + sla_queue_event(SLA_EVENT_RINGING_TRUNK); + sla_queue_event(SLA_EVENT_DIAL_STATE); + } + } + + trunk_ref->chan = chan; + + if (!trunk_ref->trunk->chan) { + ast_mutex_t cond_lock; + ast_cond_t cond; + pthread_t dont_care; + pthread_attr_t attr; + struct dial_trunk_args args = { + .trunk_ref = trunk_ref, + .station = station, + .cond_lock = &cond_lock, + .cond = &cond, + }; + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL); + /* Create a thread to dial the trunk and dump it into the conference. + * However, we want to wait until the trunk has been dialed and the + * conference is created before continuing on here. */ + ast_autoservice_start(chan); + ast_mutex_init(&cond_lock); + ast_cond_init(&cond, NULL); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ast_mutex_lock(&cond_lock); + ast_pthread_create_background(&dont_care, &attr, dial_trunk, &args); + ast_cond_wait(&cond, &cond_lock); + ast_mutex_unlock(&cond_lock); + ast_mutex_destroy(&cond_lock); + ast_cond_destroy(&cond); + pthread_attr_destroy(&attr); + ast_autoservice_stop(chan); + if (!trunk_ref->trunk->chan) { + ast_log(LOG_DEBUG, "Trunk didn't get created. chan: %lx\n", (long) trunk_ref->trunk->chan); + pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION"); + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + trunk_ref->chan = NULL; + return 0; + } + } + + if (ast_atomic_fetchadd_int((int *) &trunk_ref->trunk->active_stations, 1) == 0 && + trunk_ref->trunk->on_hold) { + trunk_ref->trunk->on_hold = 0; + ast_indicate(trunk_ref->trunk->chan, AST_CONTROL_UNHOLD); + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL); + } + + snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_ref->trunk->name); + ast_set_flag(&conf_flags, + CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_PASS_DTMF | CONFFLAG_SLA_STATION); + ast_answer(chan); + conf = build_conf(conf_name, "", "", 0, 0, 1); + if (conf) { + conf_run(chan, conf, conf_flags.flags, NULL); + dispose_conf(conf); + conf = NULL; + } + trunk_ref->chan = NULL; + if (ast_atomic_dec_and_test((int *) &trunk_ref->trunk->active_stations) && + trunk_ref->state != SLA_TRUNK_STATE_ONHOLD_BYME) { + strncat(conf_name, "|K", sizeof(conf_name) - strlen(conf_name) - 1); + admin_exec(NULL, conf_name); + trunk_ref->trunk->hold_stations = 0; + sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + } + + pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "SUCCESS"); + + return 0; +} + +static struct sla_trunk_ref *create_trunk_ref(struct sla_trunk *trunk) +{ + struct sla_trunk_ref *trunk_ref; + + if (!(trunk_ref = ast_calloc(1, sizeof(*trunk_ref)))) + return NULL; + + trunk_ref->trunk = trunk; + + return trunk_ref; +} + +static struct sla_ringing_trunk *queue_ringing_trunk(struct sla_trunk *trunk) +{ + struct sla_ringing_trunk *ringing_trunk; + + if (!(ringing_trunk = ast_calloc(1, sizeof(*ringing_trunk)))) + return NULL; + + ringing_trunk->trunk = trunk; + ringing_trunk->ring_begin = ast_tvnow(); + + sla_change_trunk_state(trunk, SLA_TRUNK_STATE_RINGING, ALL_TRUNK_REFS, NULL); + + ast_mutex_lock(&sla.lock); + AST_LIST_INSERT_HEAD(&sla.ringing_trunks, ringing_trunk, entry); + ast_mutex_unlock(&sla.lock); + + sla_queue_event(SLA_EVENT_RINGING_TRUNK); + + return ringing_trunk; +} + +static int sla_trunk_exec(struct ast_channel *chan, void *data) +{ + const char *trunk_name = data; + char conf_name[MAX_CONFNUM]; + struct ast_conference *conf; + struct ast_flags conf_flags = { 0 }; + struct sla_trunk *trunk; + struct sla_ringing_trunk *ringing_trunk; + + AST_RWLIST_RDLOCK(&sla_trunks); + trunk = sla_find_trunk(trunk_name); + AST_RWLIST_UNLOCK(&sla_trunks); + if (!trunk) { + ast_log(LOG_ERROR, "SLA Trunk '%s' not found!\n", trunk_name); + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + return 0; + } + if (trunk->chan) { + ast_log(LOG_ERROR, "Call came in on %s, but the trunk is already in use!\n", + trunk_name); + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + return 0; + } + trunk->chan = chan; + + if (!(ringing_trunk = queue_ringing_trunk(trunk))) { + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + return 0; + } + + snprintf(conf_name, sizeof(conf_name), "SLA_%s", trunk_name); + conf = build_conf(conf_name, "", "", 1, 1, 1); + if (!conf) { + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + return 0; + } + ast_set_flag(&conf_flags, + CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | CONFFLAG_PASS_DTMF); + ast_indicate(chan, AST_CONTROL_RINGING); + conf_run(chan, conf, conf_flags.flags, NULL); + dispose_conf(conf); + conf = NULL; + trunk->chan = NULL; + trunk->on_hold = 0; + + sla_change_trunk_state(trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + + if (!pbx_builtin_getvar_helper(chan, "SLATRUNK_STATUS")) + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "SUCCESS"); + + /* Remove the entry from the list of ringing trunks if it is still there. */ + ast_mutex_lock(&sla.lock); + AST_LIST_TRAVERSE_SAFE_BEGIN(&sla.ringing_trunks, ringing_trunk, entry) { + if (ringing_trunk->trunk == trunk) { + AST_LIST_REMOVE_CURRENT(&sla.ringing_trunks, entry); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + ast_mutex_unlock(&sla.lock); + if (ringing_trunk) { + free(ringing_trunk); + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "UNANSWERED"); + /* Queue reprocessing of ringing trunks to make stations stop ringing + * that shouldn't be ringing after this trunk stopped. */ + sla_queue_event(SLA_EVENT_RINGING_TRUNK); + } + + return 0; +} + +static int sla_state(const char *data) +{ + char *buf, *station_name, *trunk_name; + struct sla_station *station; + struct sla_trunk_ref *trunk_ref; + int res = AST_DEVICE_INVALID; + + trunk_name = buf = ast_strdupa(data); + station_name = strsep(&trunk_name, "_"); + + AST_RWLIST_RDLOCK(&sla_stations); + AST_LIST_TRAVERSE(&sla_stations, station, entry) { + if (strcasecmp(station_name, station->name)) + continue; + AST_RWLIST_RDLOCK(&sla_trunks); + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + if (!strcasecmp(trunk_name, trunk_ref->trunk->name)) + break; + } + if (!trunk_ref) { + AST_RWLIST_UNLOCK(&sla_trunks); + break; + } + switch (trunk_ref->state) { + case SLA_TRUNK_STATE_IDLE: + res = AST_DEVICE_NOT_INUSE; + break; + case SLA_TRUNK_STATE_RINGING: + res = AST_DEVICE_RINGING; + break; + case SLA_TRUNK_STATE_UP: + res = AST_DEVICE_INUSE; + break; + case SLA_TRUNK_STATE_ONHOLD: + case SLA_TRUNK_STATE_ONHOLD_BYME: + res = AST_DEVICE_ONHOLD; + break; + } + AST_RWLIST_UNLOCK(&sla_trunks); + } + AST_RWLIST_UNLOCK(&sla_stations); + + if (res == AST_DEVICE_INVALID) { + ast_log(LOG_ERROR, "Could not determine state for trunk %s on station %s!\n", + trunk_name, station_name); + } + + return res; +} + +static void destroy_trunk(struct sla_trunk *trunk) +{ + struct sla_station_ref *station_ref; + + if (!ast_strlen_zero(trunk->autocontext)) + ast_context_remove_extension(trunk->autocontext, "s", 1, sla_registrar); + + while ((station_ref = AST_LIST_REMOVE_HEAD(&trunk->stations, entry))) + free(station_ref); + + ast_string_field_free_memory(trunk); + free(trunk); +} + +static void destroy_station(struct sla_station *station) +{ + struct sla_trunk_ref *trunk_ref; + + if (!ast_strlen_zero(station->autocontext)) { + AST_RWLIST_RDLOCK(&sla_trunks); + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + char exten[AST_MAX_EXTENSION]; + char hint[AST_MAX_APP]; + snprintf(exten, sizeof(exten), "%s_%s", station->name, trunk_ref->trunk->name); + snprintf(hint, sizeof(hint), "SLA:%s", exten); + ast_context_remove_extension(station->autocontext, exten, + 1, sla_registrar); + ast_context_remove_extension(station->autocontext, hint, + PRIORITY_HINT, sla_registrar); + } + AST_RWLIST_UNLOCK(&sla_trunks); + } + + while ((trunk_ref = AST_LIST_REMOVE_HEAD(&station->trunks, entry))) + free(trunk_ref); + + ast_string_field_free_memory(station); + free(station); +} + +static void sla_destroy(void) +{ + struct sla_trunk *trunk; + struct sla_station *station; + + AST_RWLIST_WRLOCK(&sla_trunks); + while ((trunk = AST_RWLIST_REMOVE_HEAD(&sla_trunks, entry))) + destroy_trunk(trunk); + AST_RWLIST_UNLOCK(&sla_trunks); + + AST_RWLIST_WRLOCK(&sla_stations); + while ((station = AST_RWLIST_REMOVE_HEAD(&sla_stations, entry))) + destroy_station(station); + AST_RWLIST_UNLOCK(&sla_stations); + + if (sla.thread != AST_PTHREADT_NULL) { + ast_mutex_lock(&sla.lock); + sla.stop = 1; + ast_cond_signal(&sla.cond); + ast_mutex_unlock(&sla.lock); + pthread_join(sla.thread, NULL); + } + + /* Drop any created contexts from the dialplan */ + ast_context_destroy(NULL, sla_registrar); + + ast_mutex_destroy(&sla.lock); + ast_cond_destroy(&sla.cond); +} + +static int sla_check_device(const char *device) +{ + char *tech, *tech_data; + + tech_data = ast_strdupa(device); + tech = strsep(&tech_data, "/"); + + if (ast_strlen_zero(tech) || ast_strlen_zero(tech_data)) + return -1; + + return 0; +} + +static int sla_build_trunk(struct ast_config *cfg, const char *cat) +{ + struct sla_trunk *trunk; + struct ast_variable *var; + const char *dev; + + if (!(dev = ast_variable_retrieve(cfg, cat, "device"))) { + ast_log(LOG_ERROR, "SLA Trunk '%s' defined with no device!\n", cat); + return -1; + } + + if (sla_check_device(dev)) { + ast_log(LOG_ERROR, "SLA Trunk '%s' define with invalid device '%s'!\n", + cat, dev); + return -1; + } + + if (!(trunk = ast_calloc(1, sizeof(*trunk)))) + return -1; + if (ast_string_field_init(trunk, 32)) { + free(trunk); + return -1; + } + + ast_string_field_set(trunk, name, cat); + ast_string_field_set(trunk, device, dev); + + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "autocontext")) + ast_string_field_set(trunk, autocontext, var->value); + else if (!strcasecmp(var->name, "ringtimeout")) { + if (sscanf(var->value, "%u", &trunk->ring_timeout) != 1) { + ast_log(LOG_WARNING, "Invalid ringtimeout '%s' specified for trunk '%s'\n", + var->value, trunk->name); + trunk->ring_timeout = 0; + } + } else if (!strcasecmp(var->name, "barge")) + trunk->barge_disabled = ast_false(var->value); + else if (!strcasecmp(var->name, "hold")) { + if (!strcasecmp(var->value, "private")) + trunk->hold_access = SLA_HOLD_PRIVATE; + else if (!strcasecmp(var->value, "open")) + trunk->hold_access = SLA_HOLD_OPEN; + else { + ast_log(LOG_WARNING, "Invalid value '%s' for hold on trunk %s\n", + var->value, trunk->name); + } + } else if (strcasecmp(var->name, "type") && strcasecmp(var->name, "device")) { + ast_log(LOG_ERROR, "Invalid option '%s' specified at line %d of %s!\n", + var->name, var->lineno, SLA_CONFIG_FILE); + } + } + + if (!ast_strlen_zero(trunk->autocontext)) { + struct ast_context *context; + context = ast_context_find_or_create(NULL, trunk->autocontext, sla_registrar); + if (!context) { + ast_log(LOG_ERROR, "Failed to automatically find or create " + "context '%s' for SLA!\n", trunk->autocontext); + destroy_trunk(trunk); + return -1; + } + if (ast_add_extension2(context, 0 /* don't replace */, "s", 1, + NULL, NULL, slatrunk_app, ast_strdup(trunk->name), ast_free, sla_registrar)) { + ast_log(LOG_ERROR, "Failed to automatically create extension " + "for trunk '%s'!\n", trunk->name); + destroy_trunk(trunk); + return -1; + } + } + + AST_RWLIST_WRLOCK(&sla_trunks); + AST_RWLIST_INSERT_TAIL(&sla_trunks, trunk, entry); + AST_RWLIST_UNLOCK(&sla_trunks); + + return 0; +} + +static void sla_add_trunk_to_station(struct sla_station *station, struct ast_variable *var) +{ + struct sla_trunk *trunk; + struct sla_trunk_ref *trunk_ref; + struct sla_station_ref *station_ref; + char *trunk_name, *options, *cur; + + options = ast_strdupa(var->value); + trunk_name = strsep(&options, ","); + + AST_RWLIST_RDLOCK(&sla_trunks); + AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) { + if (!strcasecmp(trunk->name, trunk_name)) + break; + } + + AST_RWLIST_UNLOCK(&sla_trunks); + if (!trunk) { + ast_log(LOG_ERROR, "Trunk '%s' not found!\n", var->value); + return; + } + if (!(trunk_ref = create_trunk_ref(trunk))) + return; + trunk_ref->state = SLA_TRUNK_STATE_IDLE; + + while ((cur = strsep(&options, ","))) { + char *name, *value = cur; + name = strsep(&value, "="); + if (!strcasecmp(name, "ringtimeout")) { + if (sscanf(value, "%u", &trunk_ref->ring_timeout) != 1) { + ast_log(LOG_WARNING, "Invalid ringtimeout value '%s' for " + "trunk '%s' on station '%s'\n", value, trunk->name, station->name); + trunk_ref->ring_timeout = 0; + } + } else if (!strcasecmp(name, "ringdelay")) { + if (sscanf(value, "%u", &trunk_ref->ring_delay) != 1) { + ast_log(LOG_WARNING, "Invalid ringdelay value '%s' for " + "trunk '%s' on station '%s'\n", value, trunk->name, station->name); + trunk_ref->ring_delay = 0; + } + } else { + ast_log(LOG_WARNING, "Invalid option '%s' for " + "trunk '%s' on station '%s'\n", name, trunk->name, station->name); + } + } + + if (!(station_ref = sla_create_station_ref(station))) { + free(trunk_ref); + return; + } + ast_atomic_fetchadd_int((int *) &trunk->num_stations, 1); + AST_RWLIST_WRLOCK(&sla_trunks); + AST_LIST_INSERT_TAIL(&trunk->stations, station_ref, entry); + AST_RWLIST_UNLOCK(&sla_trunks); + AST_LIST_INSERT_TAIL(&station->trunks, trunk_ref, entry); +} + +static int sla_build_station(struct ast_config *cfg, const char *cat) +{ + struct sla_station *station; + struct ast_variable *var; + const char *dev; + + if (!(dev = ast_variable_retrieve(cfg, cat, "device"))) { + ast_log(LOG_ERROR, "SLA Station '%s' defined with no device!\n", cat); + return -1; + } + + if (!(station = ast_calloc(1, sizeof(*station)))) + return -1; + if (ast_string_field_init(station, 32)) { + free(station); + return -1; + } + + ast_string_field_set(station, name, cat); + ast_string_field_set(station, device, dev); + + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "trunk")) + sla_add_trunk_to_station(station, var); + else if (!strcasecmp(var->name, "autocontext")) + ast_string_field_set(station, autocontext, var->value); + else if (!strcasecmp(var->name, "ringtimeout")) { + if (sscanf(var->value, "%u", &station->ring_timeout) != 1) { + ast_log(LOG_WARNING, "Invalid ringtimeout '%s' specified for station '%s'\n", + var->value, station->name); + station->ring_timeout = 0; + } + } else if (!strcasecmp(var->name, "ringdelay")) { + if (sscanf(var->value, "%u", &station->ring_delay) != 1) { + ast_log(LOG_WARNING, "Invalid ringdelay '%s' specified for station '%s'\n", + var->value, station->name); + station->ring_delay = 0; + } + } else if (!strcasecmp(var->name, "hold")) { + if (!strcasecmp(var->value, "private")) + station->hold_access = SLA_HOLD_PRIVATE; + else if (!strcasecmp(var->value, "open")) + station->hold_access = SLA_HOLD_OPEN; + else { + ast_log(LOG_WARNING, "Invalid value '%s' for hold on station %s\n", + var->value, station->name); + } + + } else if (strcasecmp(var->name, "type") && strcasecmp(var->name, "device")) { + ast_log(LOG_ERROR, "Invalid option '%s' specified at line %d of %s!\n", + var->name, var->lineno, SLA_CONFIG_FILE); + } + } + + if (!ast_strlen_zero(station->autocontext)) { + struct ast_context *context; + struct sla_trunk_ref *trunk_ref; + context = ast_context_find_or_create(NULL, station->autocontext, sla_registrar); + if (!context) { + ast_log(LOG_ERROR, "Failed to automatically find or create " + "context '%s' for SLA!\n", station->autocontext); + destroy_station(station); + return -1; + } + /* The extension for when the handset goes off-hook. + * exten => station1,1,SLAStation(station1) */ + if (ast_add_extension2(context, 0 /* don't replace */, station->name, 1, + NULL, NULL, slastation_app, ast_strdup(station->name), ast_free, sla_registrar)) { + ast_log(LOG_ERROR, "Failed to automatically create extension " + "for trunk '%s'!\n", station->name); + destroy_station(station); + return -1; + } + AST_RWLIST_RDLOCK(&sla_trunks); + AST_LIST_TRAVERSE(&station->trunks, trunk_ref, entry) { + char exten[AST_MAX_EXTENSION]; + char hint[AST_MAX_APP]; + snprintf(exten, sizeof(exten), "%s_%s", station->name, trunk_ref->trunk->name); + snprintf(hint, sizeof(hint), "SLA:%s", exten); + /* Extension for this line button + * exten => station1_line1,1,SLAStation(station1_line1) */ + if (ast_add_extension2(context, 0 /* don't replace */, exten, 1, + NULL, NULL, slastation_app, ast_strdup(exten), ast_free, sla_registrar)) { + ast_log(LOG_ERROR, "Failed to automatically create extension " + "for trunk '%s'!\n", station->name); + destroy_station(station); + return -1; + } + /* Hint for this line button + * exten => station1_line1,hint,SLA:station1_line1 */ + if (ast_add_extension2(context, 0 /* don't replace */, exten, PRIORITY_HINT, + NULL, NULL, hint, NULL, NULL, sla_registrar)) { + ast_log(LOG_ERROR, "Failed to automatically create hint " + "for trunk '%s'!\n", station->name); + destroy_station(station); + return -1; + } + } + AST_RWLIST_UNLOCK(&sla_trunks); + } + + AST_RWLIST_WRLOCK(&sla_stations); + AST_RWLIST_INSERT_TAIL(&sla_stations, station, entry); + AST_RWLIST_UNLOCK(&sla_stations); + + return 0; +} + +static int sla_load_config(void) +{ + struct ast_config *cfg; + const char *cat = NULL; + int res = 0; + const char *val; + + ast_mutex_init(&sla.lock); + ast_cond_init(&sla.cond, NULL); + + if (!(cfg = ast_config_load(SLA_CONFIG_FILE))) + return 0; /* Treat no config as normal */ + + if ((val = ast_variable_retrieve(cfg, "general", "attemptcallerid"))) + sla.attempt_callerid = ast_true(val); + + while ((cat = ast_category_browse(cfg, cat)) && !res) { + const char *type; + if (!strcasecmp(cat, "general")) + continue; + if (!(type = ast_variable_retrieve(cfg, cat, "type"))) { + ast_log(LOG_WARNING, "Invalid entry in %s defined with no type!\n", + SLA_CONFIG_FILE); + continue; + } + if (!strcasecmp(type, "trunk")) + res = sla_build_trunk(cfg, cat); + else if (!strcasecmp(type, "station")) + res = sla_build_station(cfg, cat); + else { + ast_log(LOG_WARNING, "Entry in %s defined with invalid type '%s'!\n", + SLA_CONFIG_FILE, type); + } + } + + ast_config_destroy(cfg); + + if (!AST_LIST_EMPTY(&sla_stations) || !AST_LIST_EMPTY(&sla_stations)) + ast_pthread_create(&sla.thread, NULL, sla_thread, NULL); + + return res; +} + +static int load_config(int reload) +{ + int res = 0; + + load_config_meetme(); + if (!reload) + res = sla_load_config(); + + return res; +} + +static int unload_module(void) +{ + int res = 0; + + ast_cli_unregister_multiple(cli_meetme, ARRAY_LEN(cli_meetme)); + res = ast_manager_unregister("MeetmeMute"); + res |= ast_manager_unregister("MeetmeUnmute"); + res |= ast_unregister_application(app3); + res |= ast_unregister_application(app2); + res |= ast_unregister_application(app); + res |= ast_unregister_application(slastation_app); + res |= ast_unregister_application(slatrunk_app); + + ast_devstate_prov_del("Meetme"); + ast_devstate_prov_del("SLA"); + + ast_module_user_hangup_all(); + + sla_destroy(); + + return res; +} + +static int load_module(void) +{ + int res = 0; + + res |= load_config(0); + + ast_cli_register_multiple(cli_meetme, ARRAY_LEN(cli_meetme)); + res |= ast_manager_register("MeetmeMute", EVENT_FLAG_CALL, + action_meetmemute, "Mute a Meetme user"); + res |= ast_manager_register("MeetmeUnmute", EVENT_FLAG_CALL, + action_meetmeunmute, "Unmute a Meetme user"); + res |= ast_register_application(app3, admin_exec, synopsis3, descrip3); + res |= ast_register_application(app2, count_exec, synopsis2, descrip2); + res |= ast_register_application(app, conf_exec, synopsis, descrip); + res |= ast_register_application(slastation_app, sla_station_exec, + slastation_synopsis, slastation_desc); + res |= ast_register_application(slatrunk_app, sla_trunk_exec, + slatrunk_synopsis, slatrunk_desc); + + res |= ast_devstate_prov_add("Meetme", meetmestate); + res |= ast_devstate_prov_add("SLA", sla_state); + + return res; +} + +static int reload(void) +{ + return load_config(1); +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MeetMe conference bridge", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); + diff --git a/asterisk/apps/app_milliwatt.c b/asterisk/apps/app_milliwatt.c new file mode 100644 index 00000000..07d969f8 --- /dev/null +++ b/asterisk/apps/app_milliwatt.c @@ -0,0 +1,194 @@ +/* + * 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 Digital Milliwatt Test + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + res_indications + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 119028 $") + +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/utils.h" + +static char *app = "Milliwatt"; + +static char *synopsis = "Generate a Constant 1004Hz tone at 0dbm (mu-law)"; + +static char *descrip = +" Milliwatt([options]): Generate a Constant 1004Hz tone at 0dbm.\n" +"Previous versions of this application generated the tone at 1000Hz. If for\n" +"some reason you would prefer that behavior, supply the 'o' option to get the\n" +"old behavior.\n" +""; + + +static char digital_milliwatt[] = {0x1e,0x0b,0x0b,0x1e,0x9e,0x8b,0x8b,0x9e} ; + +static void *milliwatt_alloc(struct ast_channel *chan, void *params) +{ + return ast_calloc(1, sizeof(int)); +} + +static void milliwatt_release(struct ast_channel *chan, void *data) +{ + free(data); + return; +} + +static int milliwatt_generate(struct ast_channel *chan, void *data, int len, int samples) +{ + unsigned char buf[AST_FRIENDLY_OFFSET + 640]; + const int maxsamples = sizeof (buf) / sizeof (buf[0]); + int i, *indexp = (int *) data; + struct ast_frame wf = { + .frametype = AST_FRAME_VOICE, + .subclass = AST_FORMAT_ULAW, + .offset = AST_FRIENDLY_OFFSET, + .data = buf + AST_FRIENDLY_OFFSET, + .src = __FUNCTION__, + }; + + /* Instead of len, use samples, because channel.c generator_force + * generate(chan, tmp, 0, 160) ignores len. In any case, len is + * a multiple of samples, given by number of samples times bytes per + * sample. In the case of ulaw, len = samples. for signed linear + * len = 2 * samples */ + + if (samples > maxsamples) { + ast_log(LOG_WARNING, "Only doing %d samples (%d requested)\n", maxsamples, samples); + samples = maxsamples; + } + + len = samples * sizeof (buf[0]); + wf.datalen = len; + wf.samples = samples; + + /* create a buffer containing the digital milliwatt pattern */ + for (i = 0; i < len; i++) { + buf[AST_FRIENDLY_OFFSET + i] = digital_milliwatt[(*indexp)++]; + *indexp &= 7; + } + + if (ast_write(chan,&wf) < 0) { + ast_log(LOG_WARNING,"Failed to write frame to '%s': %s\n",chan->name,strerror(errno)); + return -1; + } + + return 0; +} + +static struct ast_generator milliwattgen = { + alloc: milliwatt_alloc, + release: milliwatt_release, + generate: milliwatt_generate, +}; + +static int old_milliwatt_exec(struct ast_channel *chan) +{ + ast_set_write_format(chan, AST_FORMAT_ULAW); + ast_set_read_format(chan, AST_FORMAT_ULAW); + + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } + + if (ast_activate_generator(chan,&milliwattgen,"milliwatt") < 0) { + ast_log(LOG_WARNING,"Failed to activate generator on '%s'\n",chan->name); + return -1; + } + + while (!ast_safe_sleep(chan, 10000)) + ; + + ast_deactivate_generator(chan); + + return -1; +} + +static int milliwatt_exec(struct ast_channel *chan, void *data) +{ + const char *options = data; + struct ast_app *playtones_app; + struct ast_module_user *u; + int res = -1; + + u = ast_module_user_add(chan); + + if (!ast_strlen_zero(options) && strchr(options, 'o')) { + res = old_milliwatt_exec(chan); + goto exit_app; + } + + if (!(playtones_app = pbx_findapp("Playtones"))) { + ast_log(LOG_ERROR, "The Playtones application is required to run Milliwatt()\n"); + goto exit_app; + } + + res = pbx_exec(chan, playtones_app, "1004/1000"); + + while (!res) { + res = ast_safe_sleep(chan, 10000); + } + + res = 0; + +exit_app: + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, milliwatt_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Digital Milliwatt (mu-law) Test Application"); diff --git a/asterisk/apps/app_mixmonitor.c b/asterisk/apps/app_mixmonitor.c new file mode 100644 index 00000000..7213dc7c --- /dev/null +++ b/asterisk/apps/app_mixmonitor.c @@ -0,0 +1,446 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Anthony Minessale II + * Copyright (C) 2005 - 2006, Digium, Inc. + * + * Mark Spencer + * Kevin P. Fleming + * + * Based on app_muxmon.c provided by + * Anthony Minessale II + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 MixMonitor() - Record a call and mix the audio during the recording + * \ingroup applications + * + * \author Mark Spencer + * \author Kevin P. Fleming + * + * \note Based on app_muxmon.c provided by + * Anthony Minessale II + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 114611 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/audiohook.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/cli.h" +#include "asterisk/options.h" +#include "asterisk/app.h" +#include "asterisk/linkedlists.h" +#include "asterisk/utils.h" + +#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0 + +static const char *app = "MixMonitor"; +static const char *synopsis = "Record a call and mix the audio during the recording"; +static const char *desc = "" +" MixMonitor(.[|[|]])\n\n" +"Records the audio on the current channel to the specified file.\n" +"If the filename is an absolute path, uses that path, otherwise\n" +"creates the file in the configured monitoring directory from\n" +"asterisk.conf.\n\n" +"Valid options:\n" +" a - Append to the file instead of overwriting it.\n" +" b - Only save audio to the file while the channel is bridged.\n" +" Note: Does not include conferences or sounds played to each bridged\n" +" party.\n" +" v() - Adjust the heard volume by a factor of (range -4 to 4)\n" +" V() - Adjust the spoken volume by a factor of (range -4 to 4)\n" +" W() - Adjust the both heard and spoken volumes by a factor of \n" +" (range -4 to 4)\n\n" +" will be executed when the recording is over\n" +"Any strings matching ^{X} will be unescaped to ${X}.\n" +"All variables will be evaluated at the time MixMonitor is called.\n" +"The variable MIXMONITOR_FILENAME will contain the filename used to record.\n" +""; + +static const char *stop_app = "StopMixMonitor"; +static const char *stop_synopsis = "Stop recording a call through MixMonitor"; +static const char *stop_desc = "" +" StopMixMonitor()\n\n" +"Stops the audio recording that was started with a call to MixMonitor()\n" +"on the current channel.\n" +""; + +struct module_symbols *me; + +static const char *mixmonitor_spy_type = "MixMonitor"; + +struct mixmonitor { + struct ast_audiohook audiohook; + char *filename; + char *post_process; + char *name; + unsigned int flags; + struct ast_channel *chan; +}; + +enum { + MUXFLAG_APPEND = (1 << 1), + MUXFLAG_BRIDGED = (1 << 2), + MUXFLAG_VOLUME = (1 << 3), + MUXFLAG_READVOLUME = (1 << 4), + MUXFLAG_WRITEVOLUME = (1 << 5), +} mixmonitor_flags; + +enum { + OPT_ARG_READVOLUME = 0, + OPT_ARG_WRITEVOLUME, + OPT_ARG_VOLUME, + OPT_ARG_ARRAY_SIZE, +} mixmonitor_args; + +AST_APP_OPTIONS(mixmonitor_opts, { + AST_APP_OPTION('a', MUXFLAG_APPEND), + AST_APP_OPTION('b', MUXFLAG_BRIDGED), + AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME), + AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME), + AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME), +}); + +static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) +{ + struct ast_channel *peer; + int res; + + if (!chan) + return -1; + + res = ast_audiohook_attach(chan, audiohook); + + if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) + ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE); + + return res; +} + +#define SAMPLES_PER_FRAME 160 + +static void *mixmonitor_thread(void *obj) +{ + struct mixmonitor *mixmonitor = obj; + struct ast_filestream *fs = NULL; + unsigned int oflags; + char *ext; + int errflag = 0; + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Begin MixMonitor Recording %s\n", mixmonitor->name); + + ast_audiohook_lock(&mixmonitor->audiohook); + + while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) { + struct ast_frame *fr = NULL; + + ast_audiohook_trigger_wait(&mixmonitor->audiohook); + + if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) + break; + + if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR))) + continue; + + if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) { + /* Initialize the file if not already done so */ + if (!fs && !errflag) { + oflags = O_CREAT | O_WRONLY; + oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC; + + if ((ext = strrchr(mixmonitor->filename, '.'))) + *(ext++) = '\0'; + else + ext = "raw"; + + if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) { + ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext); + errflag = 1; + } + } + + /* Write out the frame */ + if (fs) + ast_writestream(fs, fr); + } + + /* All done! free it. */ + ast_frame_free(fr, 0); + } + + ast_audiohook_detach(&mixmonitor->audiohook); + ast_audiohook_unlock(&mixmonitor->audiohook); + ast_audiohook_destroy(&mixmonitor->audiohook); + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "End MixMonitor Recording %s\n", mixmonitor->name); + + if (fs) + ast_closestream(fs); + + if (mixmonitor->post_process) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", mixmonitor->post_process); + ast_safe_system(mixmonitor->post_process); + } + + free(mixmonitor); + + + return NULL; +} + +static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags, + int readvol, int writevol, const char *post_process) +{ + pthread_attr_t attr; + pthread_t thread; + struct mixmonitor *mixmonitor; + char postprocess2[1024] = ""; + size_t len; + + len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2; + + /* If a post process system command is given attach it to the structure */ + if (!ast_strlen_zero(post_process)) { + char *p1, *p2; + + p1 = ast_strdupa(post_process); + for (p2 = p1; *p2 ; p2++) { + if (*p2 == '^' && *(p2+1) == '{') { + *p2 = '$'; + } + } + + pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1); + if (!ast_strlen_zero(postprocess2)) + len += strlen(postprocess2) + 1; + } + + /* Pre-allocate mixmonitor structure and spy */ + if (!(mixmonitor = calloc(1, len))) { + return; + } + + /* Copy over flags and channel name */ + mixmonitor->flags = flags; + mixmonitor->chan = chan; + mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor); + strcpy(mixmonitor->name, chan->name); + if (!ast_strlen_zero(postprocess2)) { + mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2; + strcpy(mixmonitor->post_process, postprocess2); + } + + mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1; + strcpy(mixmonitor->filename, filename); + + /* Setup the actual spy before creating our thread */ + if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) { + free(mixmonitor); + return; + } + + ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC); + + if (readvol) + mixmonitor->audiohook.options.read_volume = readvol; + if (writevol) + mixmonitor->audiohook.options.write_volume = writevol; + + if (startmon(chan, &mixmonitor->audiohook)) { + ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n", + mixmonitor_spy_type, chan->name); + /* Since we couldn't add ourselves - bail out! */ + ast_audiohook_destroy(&mixmonitor->audiohook); + free(mixmonitor); + return; + } + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ast_pthread_create_background(&thread, &attr, mixmonitor_thread, mixmonitor); + pthread_attr_destroy(&attr); + +} + +static int mixmonitor_exec(struct ast_channel *chan, void *data) +{ + int x, readvol = 0, writevol = 0; + struct ast_module_user *u; + struct ast_flags flags = {0}; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filename); + AST_APP_ARG(options); + AST_APP_ARG(post_process); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.filename)) { + ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n"); + ast_module_user_remove(u); + return -1; + } + + if (args.options) { + char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, }; + + ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options); + + if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) { + if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) { + ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n"); + } else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) { + ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]); + } else { + readvol = get_volfactor(x); + } + } + + if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) { + if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) { + ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n"); + } else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) { + ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]); + } else { + writevol = get_volfactor(x); + } + } + + if (ast_test_flag(&flags, MUXFLAG_VOLUME)) { + if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) { + ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n"); + } else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) { + ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]); + } else { + readvol = writevol = get_volfactor(x); + } + } + } + + /* if not provided an absolute path, use the system-configured monitoring directory */ + if (args.filename[0] != '/') { + char *build; + + build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3); + sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename); + args.filename = build; + } + + pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename); + launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process); + + ast_module_user_remove(u); + + return 0; +} + +static int stop_mixmonitor_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + ast_audiohook_detach_source(chan, mixmonitor_spy_type); + + ast_module_user_remove(u); + + return 0; +} + +static int mixmonitor_cli(int fd, int argc, char **argv) +{ + struct ast_channel *chan; + + if (argc < 3) + return RESULT_SHOWUSAGE; + + if (!(chan = ast_get_channel_by_name_prefix_locked(argv[2], strlen(argv[2])))) { + ast_cli(fd, "No channel matching '%s' found.\n", argv[2]); + return RESULT_SUCCESS; + } + + if (!strcasecmp(argv[1], "start")) + mixmonitor_exec(chan, argv[3]); + else if (!strcasecmp(argv[1], "stop")) + ast_audiohook_detach_source(chan, mixmonitor_spy_type); + + ast_channel_unlock(chan); + + return RESULT_SUCCESS; +} + +static char *complete_mixmonitor_cli(const char *line, const char *word, int pos, int state) +{ + return ast_complete_channels(line, word, pos, state, 2); +} + +static struct ast_cli_entry cli_mixmonitor[] = { + { { "mixmonitor", NULL, NULL }, + mixmonitor_cli, "Execute a MixMonitor command.", + "mixmonitor [args]\n\n" + "The optional arguments are passed to the\n" + "MixMonitor application when the 'start' command is used.\n", + complete_mixmonitor_cli }, +}; + +static int unload_module(void) +{ + int res; + + ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry)); + res = ast_unregister_application(stop_app); + res |= ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + int res; + + ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry)); + res = ast_register_application(app, mixmonitor_exec, synopsis, desc); + res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc); + + return res; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application"); diff --git a/asterisk/apps/app_morsecode.c b/asterisk/apps/app_morsecode.c new file mode 100644 index 00000000..a8540458 --- /dev/null +++ b/asterisk/apps/app_morsecode.c @@ -0,0 +1,179 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (c) 2006, Tilghman Lesher. All rights reserved. + * + * Tilghman Lesher + * + * This code is released by the author with no restrictions on usage. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + */ + +/*! \file + * + * \brief Morsecode application + * + * \author Tilghman Lesher + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 76618 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/indications.h" + +static char *app_morsecode = "Morsecode"; + +static char *morsecode_synopsis = "Plays morse code"; + +static char *morsecode_descrip = +"Usage: Morsecode()\n" +"Plays the Morse code equivalent of the passed string. If the variable\n" +"MORSEDITLEN is set, it will use that value for the length (in ms) of the dit\n" +"(defaults to 80). Additionally, if MORSETONE is set, it will use that tone\n" +"(in Hz). The tone default is 800.\n"; + + +static char *morsecode[] = { + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 0-15 */ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 16-31 */ + " ", /* 32 - */ + ".-.-.-", /* 33 - ! */ + ".-..-.", /* 34 - " */ + "", /* 35 - # */ + "", /* 36 - $ */ + "", /* 37 - % */ + "", /* 38 - & */ + ".----.", /* 39 - ' */ + "-.--.-", /* 40 - ( */ + "-.--.-", /* 41 - ) */ + "", /* 42 - * */ + "", /* 43 - + */ + "--..--", /* 44 - , */ + "-....-", /* 45 - - */ + ".-.-.-", /* 46 - . */ + "-..-.", /* 47 - / */ + "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.", /* 48-57 - 0-9 */ + "---...", /* 58 - : */ + "-.-.-.", /* 59 - ; */ + "", /* 60 - < */ + "-...-", /* 61 - = */ + "", /* 62 - > */ + "..--..", /* 63 - ? */ + ".--.-.", /* 64 - @ */ + ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", + "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", + "-.--.-", /* 91 - [ (really '(') */ + "-..-.", /* 92 - \ (really '/') */ + "-.--.-", /* 93 - ] (really ')') */ + "", /* 94 - ^ */ + "..--.-", /* 95 - _ */ + ".----.", /* 96 - ` */ + ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", + "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", + "-.--.-", /* 123 - { (really '(') */ + "", /* 124 - | */ + "-.--.-", /* 125 - } (really ')') */ + "-..-.", /* 126 - ~ (really bar) */ + ". . .", /* 127 - (error) */ +}; + +static void playtone(struct ast_channel *chan, int tone, int len) +{ + char dtmf[20]; + snprintf(dtmf, sizeof(dtmf), "%d/%d", tone, len); + ast_playtones_start(chan, 0, dtmf, 0); + ast_safe_sleep(chan, len); + ast_playtones_stop(chan); +} + +static int morsecode_exec(struct ast_channel *chan, void *data) +{ + int res=0, ditlen, tone; + char *digit; + const char *ditlenc, *tonec; + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Syntax: Morsecode() - no argument found\n"); + ast_module_user_remove(u); + return 0; + } + + /* Use variable MORESEDITLEN, if set (else 80) */ + ditlenc = pbx_builtin_getvar_helper(chan, "MORSEDITLEN"); + if (ast_strlen_zero(ditlenc) || (sscanf(ditlenc, "%d", &ditlen) != 1)) { + ditlen = 80; + } + + /* Use variable MORSETONE, if set (else 800) */ + tonec = pbx_builtin_getvar_helper(chan, "MORSETONE"); + if (ast_strlen_zero(tonec) || (sscanf(tonec, "%d", &tone) != 1)) { + tone = 800; + } + + for (digit = data; *digit; digit++) { + int digit2 = *digit; + char *dahdit; + if (digit2 < 0) { + continue; + } + for (dahdit = morsecode[digit2]; *dahdit; dahdit++) { + if (*dahdit == '-') { + playtone(chan, tone, 3 * ditlen); + } else if (*dahdit == '.') { + playtone(chan, tone, 1 * ditlen); + } else { + /* Account for ditlen of silence immediately following */ + playtone(chan, 0, 2 * ditlen); + } + + /* Pause slightly between each dit and dah */ + playtone(chan, 0, 1 * ditlen); + } + /* Pause between characters */ + playtone(chan, 0, 2 * ditlen); + } + + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app_morsecode); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app_morsecode, morsecode_exec, morsecode_synopsis, morsecode_descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Morse code"); diff --git a/asterisk/apps/app_mp3.c b/asterisk/apps/app_mp3.c new file mode 100644 index 00000000..771c5f8e --- /dev/null +++ b/asterisk/apps/app_mp3.c @@ -0,0 +1,255 @@ +/* + * 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 Silly application to play an MP3 file -- uses mpg123 + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 48375 $") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/frame.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/options.h" + +#define LOCAL_MPG_123 "/usr/local/bin/mpg123" +#define MPG_123 "/usr/bin/mpg123" + +static char *app = "MP3Player"; + +static char *synopsis = "Play an MP3 file or stream"; + +static char *descrip = +" MP3Player(location) Executes mpg123 to play the given location,\n" +"which typically would be a filename or a URL. User can exit by pressing\n" +"any key on the dialpad, or by hanging up."; + + +static int mp3play(char *filename, int fd) +{ + int res; + int x; + sigset_t fullset, oldset; + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + res = fork(); + if (res < 0) + ast_log(LOG_WARNING, "Fork failed\n"); + if (res) { + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + return res; + } + if (ast_opt_high_priority) + ast_set_priority(0); + signal(SIGPIPE, SIG_DFL); + pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); + + dup2(fd, STDOUT_FILENO); + for (x=STDERR_FILENO + 1;x<256;x++) { + if (x != STDOUT_FILENO) + close(x); + } + /* Execute mpg123, but buffer if it's a net connection */ + if (!strncasecmp(filename, "http://", 7)) { + /* Most commonly installed in /usr/local/bin */ + execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-b", "1024", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL); + /* But many places has it in /usr/bin */ + execl(MPG_123, "mpg123", "-q", "-s", "-b", "1024","-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL); + /* As a last-ditch effort, try to use PATH */ + execlp("mpg123", "mpg123", "-q", "-s", "-b", "1024", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL); + } + else { + /* Most commonly installed in /usr/local/bin */ + execl(MPG_123, "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL); + /* But many places has it in /usr/bin */ + execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL); + /* As a last-ditch effort, try to use PATH */ + execlp("mpg123", "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL); + } + ast_log(LOG_WARNING, "Execute of mpg123 failed\n"); + _exit(0); +} + +static int timed_read(int fd, void *data, int datalen, int timeout) +{ + int res; + struct pollfd fds[1]; + fds[0].fd = fd; + fds[0].events = POLLIN; + res = poll(fds, 1, timeout); + if (res < 1) { + ast_log(LOG_NOTICE, "Poll timed out/errored out with %d\n", res); + return -1; + } + return read(fd, data, datalen); + +} + +static int mp3_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + int fds[2]; + int ms = -1; + int pid = -1; + int owriteformat; + int timeout = 2000; + struct timeval next; + struct ast_frame *f; + struct myframe { + struct ast_frame f; + char offset[AST_FRIENDLY_OFFSET]; + short frdata[160]; + } myf; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "MP3 Playback requires an argument (filename)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (pipe(fds)) { + ast_log(LOG_WARNING, "Unable to create pipe\n"); + ast_module_user_remove(u); + return -1; + } + + ast_stopstream(chan); + + owriteformat = chan->writeformat; + res = ast_set_write_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); + ast_module_user_remove(u); + return -1; + } + + res = mp3play((char *)data, fds[1]); + if (!strncasecmp((char *)data, "http://", 7)) { + timeout = 10000; + } + /* Wait 1000 ms first */ + next = ast_tvnow(); + next.tv_sec += 1; + if (res >= 0) { + pid = res; + /* Order is important -- there's almost always going to be mp3... we want to prioritize the + user */ + for (;;) { + ms = ast_tvdiff_ms(next, ast_tvnow()); + if (ms <= 0) { + res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata), timeout); + if (res > 0) { + myf.f.frametype = AST_FRAME_VOICE; + myf.f.subclass = AST_FORMAT_SLINEAR; + myf.f.datalen = res; + myf.f.samples = res / 2; + myf.f.mallocd = 0; + myf.f.offset = AST_FRIENDLY_OFFSET; + myf.f.src = __PRETTY_FUNCTION__; + myf.f.delivery.tv_sec = 0; + myf.f.delivery.tv_usec = 0; + myf.f.data = myf.frdata; + if (ast_write(chan, &myf.f) < 0) { + res = -1; + break; + } + } else { + ast_log(LOG_DEBUG, "No more mp3\n"); + res = 0; + break; + } + next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000)); + } else { + ms = ast_waitfor(chan, ms); + if (ms < 0) { + ast_log(LOG_DEBUG, "Hangup detected\n"); + res = -1; + break; + } + if (ms) { + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "Null frame == hangup() detected\n"); + res = -1; + break; + } + if (f->frametype == AST_FRAME_DTMF) { + ast_log(LOG_DEBUG, "User pressed a key\n"); + ast_frfree(f); + res = 0; + break; + } + ast_frfree(f); + } + } + } + } + close(fds[0]); + close(fds[1]); + + if (pid > -1) + kill(pid, SIGKILL); + if (!res && owriteformat) + ast_set_write_format(chan, owriteformat); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, mp3_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Silly MP3 Application"); diff --git a/asterisk/apps/app_nbscat.c b/asterisk/apps/app_nbscat.c new file mode 100644 index 00000000..6c25baf8 --- /dev/null +++ b/asterisk/apps/app_nbscat.c @@ -0,0 +1,237 @@ +/* + * 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 Silly application to play an NBScat file -- uses nbscat8k + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 48375 $") + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/frame.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/options.h" + +#define LOCAL_NBSCAT "/usr/local/bin/nbscat8k" +#define NBSCAT "/usr/bin/nbscat8k" + +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +static char *app = "NBScat"; + +static char *synopsis = "Play an NBS local stream"; + +static char *descrip = +" NBScat: Executes nbscat to listen to the local NBS stream.\n" +"User can exit by pressing any key\n."; + + +static int NBScatplay(int fd) +{ + int res; + int x; + sigset_t fullset, oldset; + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + res = fork(); + if (res < 0) + ast_log(LOG_WARNING, "Fork failed\n"); + if (res) { + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + return res; + } + signal(SIGPIPE, SIG_DFL); + pthread_sigmask(SIG_UNBLOCK, &fullset, NULL); + + if (ast_opt_high_priority) + ast_set_priority(0); + + dup2(fd, STDOUT_FILENO); + for (x = STDERR_FILENO + 1; x < 1024; x++) { + if (x != STDOUT_FILENO) + close(x); + } + /* Most commonly installed in /usr/local/bin */ + execl(NBSCAT, "nbscat8k", "-d", (char *)NULL); + execl(LOCAL_NBSCAT, "nbscat8k", "-d", (char *)NULL); + ast_log(LOG_WARNING, "Execute of nbscat8k failed\n"); + _exit(0); +} + +static int timed_read(int fd, void *data, int datalen) +{ + int res; + struct pollfd fds[1]; + fds[0].fd = fd; + fds[0].events = POLLIN; + res = poll(fds, 1, 2000); + if (res < 1) { + ast_log(LOG_NOTICE, "Selected timed out/errored out with %d\n", res); + return -1; + } + return read(fd, data, datalen); + +} + +static int NBScat_exec(struct ast_channel *chan, void *data) +{ + int res=0; + struct ast_module_user *u; + int fds[2]; + int ms = -1; + int pid = -1; + int owriteformat; + struct timeval next; + struct ast_frame *f; + struct myframe { + struct ast_frame f; + char offset[AST_FRIENDLY_OFFSET]; + short frdata[160]; + } myf; + + u = ast_module_user_add(chan); + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) { + ast_log(LOG_WARNING, "Unable to create socketpair\n"); + ast_module_user_remove(u); + return -1; + } + + ast_stopstream(chan); + + owriteformat = chan->writeformat; + res = ast_set_write_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set write format to signed linear\n"); + ast_module_user_remove(u); + return -1; + } + + res = NBScatplay(fds[1]); + /* Wait 1000 ms first */ + next = ast_tvnow(); + next.tv_sec += 1; + if (res >= 0) { + pid = res; + /* Order is important -- there's almost always going to be mp3... we want to prioritize the + user */ + for (;;) { + ms = ast_tvdiff_ms(next, ast_tvnow()); + if (ms <= 0) { + res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata)); + if (res > 0) { + myf.f.frametype = AST_FRAME_VOICE; + myf.f.subclass = AST_FORMAT_SLINEAR; + myf.f.datalen = res; + myf.f.samples = res / 2; + myf.f.mallocd = 0; + myf.f.offset = AST_FRIENDLY_OFFSET; + myf.f.src = __PRETTY_FUNCTION__; + myf.f.delivery.tv_sec = 0; + myf.f.delivery.tv_usec = 0; + myf.f.data = myf.frdata; + if (ast_write(chan, &myf.f) < 0) { + res = -1; + break; + } + } else { + ast_log(LOG_DEBUG, "No more mp3\n"); + res = 0; + break; + } + next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000)); + } else { + ms = ast_waitfor(chan, ms); + if (ms < 0) { + ast_log(LOG_DEBUG, "Hangup detected\n"); + res = -1; + break; + } + if (ms) { + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "Null frame == hangup() detected\n"); + res = -1; + break; + } + if (f->frametype == AST_FRAME_DTMF) { + ast_log(LOG_DEBUG, "User pressed a key\n"); + ast_frfree(f); + res = 0; + break; + } + ast_frfree(f); + } + } + } + } + close(fds[0]); + close(fds[1]); + + if (pid > -1) + kill(pid, SIGKILL); + if (!res && owriteformat) + ast_set_write_format(chan, owriteformat); + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app, NBScat_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Silly NBS Stream Application"); diff --git a/asterisk/apps/app_osplookup.c b/asterisk/apps/app_osplookup.c new file mode 100644 index 00000000..89744230 --- /dev/null +++ b/asterisk/apps/app_osplookup.c @@ -0,0 +1,1677 @@ +/* + * 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 Open Settlement Protocol (OSP) Applications + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + osptk + ssl + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 86450 $") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/config.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/channel.h" +#include "asterisk/app.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" +#include "asterisk/astosp.h" + +/* OSP Buffer Sizes */ +#define OSP_INTSTR_SIZE ((unsigned int)16) /* OSP signed/unsigned int string buffer size */ +#define OSP_NORSTR_SIZE ((unsigned int)256) /* OSP normal string buffer size */ +#define OSP_TOKSTR_SIZE ((unsigned int)4096) /* OSP token string buffer size */ + +/* OSP Constants */ +#define OSP_INVALID_HANDLE ((int)-1) /* Invalid OSP handle, provider, transaction etc. */ +#define OSP_CONFIG_FILE ((const char*)"osp.conf") /* OSP configuration file name */ +#define OSP_GENERAL_CAT ((const char*)"general") /* OSP global configuration context name */ +#define OSP_DEF_PROVIDER ((const char*)"default") /* OSP default provider context name */ +#define OSP_MAX_CERTS ((unsigned int)10) /* OSP max number of cacerts */ +#define OSP_MAX_SRVS ((unsigned int)10) /* OSP max number of service points */ +#define OSP_DEF_MAXCONNECTIONS ((unsigned int)20) /* OSP default max_connections */ +#define OSP_MIN_MAXCONNECTIONS ((unsigned int)1) /* OSP min max_connections */ +#define OSP_MAX_MAXCONNECTIONS ((unsigned int)1000) /* OSP max max_connections */ +#define OSP_DEF_RETRYDELAY ((unsigned int)0) /* OSP default retry delay */ +#define OSP_MIN_RETRYDELAY ((unsigned int)0) /* OSP min retry delay */ +#define OSP_MAX_RETRYDELAY ((unsigned int)10) /* OSP max retry delay */ +#define OSP_DEF_RETRYLIMIT ((unsigned int)2) /* OSP default retry times */ +#define OSP_MIN_RETRYLIMIT ((unsigned int)0) /* OSP min retry times */ +#define OSP_MAX_RETRYLIMIT ((unsigned int)100) /* OSP max retry times */ +#define OSP_DEF_TIMEOUT ((unsigned int)500) /* OSP default timeout in ms */ +#define OSP_MIN_TIMEOUT ((unsigned int)200) /* OSP min timeout in ms */ +#define OSP_MAX_TIMEOUT ((unsigned int)10000) /* OSP max timeout in ms */ +#define OSP_DEF_AUTHPOLICY ((enum osp_authpolicy)OSP_AUTH_YES) +#define OSP_AUDIT_URL ((const char*)"localhost") /* OSP default Audit URL */ +#define OSP_LOCAL_VALIDATION ((int)1) /* Validate OSP token locally */ +#define OSP_SSL_LIFETIME ((unsigned int)300) /* SSL life time, in seconds */ +#define OSP_HTTP_PERSISTENCE ((int)1) /* In seconds */ +#define OSP_CUSTOMER_ID ((const char*)"") /* OSP customer ID */ +#define OSP_DEVICE_ID ((const char*)"") /* OSP device ID */ +#define OSP_DEF_DESTINATIONS ((unsigned int)5) /* OSP default max number of destinations */ +#define OSP_DEF_TIMELIMIT ((unsigned int)0) /* OSP default duration limit, no limit */ + +/* OSP Authentication Policy */ +enum osp_authpolicy { + OSP_AUTH_NO, /* Accept any call */ + OSP_AUTH_YES, /* Accept call with valid OSP token or without OSP token */ + OSP_AUTH_EXCLUSIVE /* Only accept call with valid OSP token */ +}; + +/* OSP Provider */ +struct osp_provider { + char name[OSP_NORSTR_SIZE]; /* OSP provider context name */ + char privatekey[OSP_NORSTR_SIZE]; /* OSP private key file name */ + char localcert[OSP_NORSTR_SIZE]; /* OSP local cert file name */ + unsigned int cacount; /* Number of cacerts */ + char cacerts[OSP_MAX_CERTS][OSP_NORSTR_SIZE]; /* Cacert file names */ + unsigned int spcount; /* Number of service points */ + char srvpoints[OSP_MAX_SRVS][OSP_NORSTR_SIZE]; /* Service point URLs */ + int maxconnections; /* Max number of connections */ + int retrydelay; /* Retry delay */ + int retrylimit; /* Retry limit */ + int timeout; /* Timeout in ms */ + char source[OSP_NORSTR_SIZE]; /* IP of self */ + enum osp_authpolicy authpolicy; /* OSP authentication policy */ + OSPTPROVHANDLE handle; /* OSP provider handle */ + struct osp_provider* next; /* Pointer to next OSP provider */ +}; + +/* OSP Application In/Output Results */ +struct osp_result { + int inhandle; /* Inbound transaction handle */ + int outhandle; /* Outbound transaction handle */ + unsigned int intimelimit; /* Inbound duration limit */ + unsigned int outtimelimit; /* Outbound duration limit */ + char tech[20]; /* Asterisk TECH string */ + char dest[OSP_NORSTR_SIZE]; /* Destination in called@IP format */ + char calling[OSP_NORSTR_SIZE]; /* Calling number, may be translated */ + char token[OSP_TOKSTR_SIZE]; /* Outbound OSP token */ + unsigned int numresults; /* Number of remain destinations */ +}; + +/* OSP Module Global Variables */ +AST_MUTEX_DEFINE_STATIC(osplock); /* Lock of OSP provider list */ +static int osp_initialized = 0; /* Init flag */ +static int osp_hardware = 0; /* Hardware accelleration flag */ +static struct osp_provider* ospproviders = NULL; /* OSP provider list */ +static unsigned int osp_tokenformat = TOKEN_ALGO_SIGNED; /* Token format supported */ + +/* OSP Client Wrapper APIs */ + +/*! + * \brief Create OSP provider handle according to configuration + * \param cfg OSP configuration + * \param provider OSP provider context name + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_create_provider(struct ast_config* cfg, const char* provider) +{ + int res; + unsigned int t, i, j; + struct osp_provider* p; + struct ast_variable* v; + OSPTPRIVATEKEY privatekey; + OSPTCERT localcert; + const char* psrvpoints[OSP_MAX_SRVS]; + OSPTCERT cacerts[OSP_MAX_CERTS]; + const OSPTCERT* pcacerts[OSP_MAX_CERTS]; + int error = OSPC_ERR_NO_ERROR; + + if (!(p = ast_calloc(1, sizeof(*p)))) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + ast_copy_string(p->name, provider, sizeof(p->name)); + snprintf(p->privatekey, sizeof(p->privatekey), "%s/%s-privatekey.pem", ast_config_AST_KEY_DIR, provider); + snprintf(p->localcert, sizeof(p->localcert), "%s/%s-localcert.pem", ast_config_AST_KEY_DIR, provider); + p->maxconnections = OSP_DEF_MAXCONNECTIONS; + p->retrydelay = OSP_DEF_RETRYDELAY; + p->retrylimit = OSP_DEF_RETRYLIMIT; + p->timeout = OSP_DEF_TIMEOUT; + p->authpolicy = OSP_DEF_AUTHPOLICY; + p->handle = OSP_INVALID_HANDLE; + + v = ast_variable_browse(cfg, provider); + while(v) { + if (!strcasecmp(v->name, "privatekey")) { + if (v->value[0] == '/') { + ast_copy_string(p->privatekey, v->value, sizeof(p->privatekey)); + } else { + snprintf(p->privatekey, sizeof(p->privatekey), "%s/%s", ast_config_AST_KEY_DIR, v->value); + } + ast_log(LOG_DEBUG, "OSP: privatekey '%s'\n", p->privatekey); + } else if (!strcasecmp(v->name, "localcert")) { + if (v->value[0] == '/') { + ast_copy_string(p->localcert, v->value, sizeof(p->localcert)); + } else { + snprintf(p->localcert, sizeof(p->localcert), "%s/%s", ast_config_AST_KEY_DIR, v->value); + } + ast_log(LOG_DEBUG, "OSP: localcert '%s'\n", p->localcert); + } else if (!strcasecmp(v->name, "cacert")) { + if (p->cacount < OSP_MAX_CERTS) { + if (v->value[0] == '/') { + ast_copy_string(p->cacerts[p->cacount], v->value, sizeof(p->cacerts[0])); + } else { + snprintf(p->cacerts[p->cacount], sizeof(p->cacerts[0]), "%s/%s", ast_config_AST_KEY_DIR, v->value); + } + ast_log(LOG_DEBUG, "OSP: cacert[%d]: '%s'\n", p->cacount, p->cacerts[p->cacount]); + p->cacount++; + } else { + ast_log(LOG_WARNING, "OSP: Too many CA Certificates at line %d\n", v->lineno); + } + } else if (!strcasecmp(v->name, "servicepoint")) { + if (p->spcount < OSP_MAX_SRVS) { + ast_copy_string(p->srvpoints[p->spcount], v->value, sizeof(p->srvpoints[0])); + ast_log(LOG_DEBUG, "OSP: servicepoint[%d]: '%s'\n", p->spcount, p->srvpoints[p->spcount]); + p->spcount++; + } else { + ast_log(LOG_WARNING, "OSP: Too many Service Points at line %d\n", v->lineno); + } + } else if (!strcasecmp(v->name, "maxconnections")) { + if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_MAXCONNECTIONS) && (t <= OSP_MAX_MAXCONNECTIONS)) { + p->maxconnections = t; + ast_log(LOG_DEBUG, "OSP: maxconnections '%d'\n", t); + } else { + ast_log(LOG_WARNING, "OSP: maxconnections should be an integer from %d to %d, not '%s' at line %d\n", + OSP_MIN_MAXCONNECTIONS, OSP_MAX_MAXCONNECTIONS, v->value, v->lineno); + } + } else if (!strcasecmp(v->name, "retrydelay")) { + if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_RETRYDELAY) && (t <= OSP_MAX_RETRYDELAY)) { + p->retrydelay = t; + ast_log(LOG_DEBUG, "OSP: retrydelay '%d'\n", t); + } else { + ast_log(LOG_WARNING, "OSP: retrydelay should be an integer from %d to %d, not '%s' at line %d\n", + OSP_MIN_RETRYDELAY, OSP_MAX_RETRYDELAY, v->value, v->lineno); + } + } else if (!strcasecmp(v->name, "retrylimit")) { + if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_RETRYLIMIT) && (t <= OSP_MAX_RETRYLIMIT)) { + p->retrylimit = t; + ast_log(LOG_DEBUG, "OSP: retrylimit '%d'\n", t); + } else { + ast_log(LOG_WARNING, "OSP: retrylimit should be an integer from %d to %d, not '%s' at line %d\n", + OSP_MIN_RETRYLIMIT, OSP_MAX_RETRYLIMIT, v->value, v->lineno); + } + } else if (!strcasecmp(v->name, "timeout")) { + if ((sscanf(v->value, "%d", &t) == 1) && (t >= OSP_MIN_TIMEOUT) && (t <= OSP_MAX_TIMEOUT)) { + p->timeout = t; + ast_log(LOG_DEBUG, "OSP: timeout '%d'\n", t); + } else { + ast_log(LOG_WARNING, "OSP: timeout should be an integer from %d to %d, not '%s' at line %d\n", + OSP_MIN_TIMEOUT, OSP_MAX_TIMEOUT, v->value, v->lineno); + } + } else if (!strcasecmp(v->name, "source")) { + ast_copy_string(p->source, v->value, sizeof(p->source)); + ast_log(LOG_DEBUG, "OSP: source '%s'\n", p->source); + } else if (!strcasecmp(v->name, "authpolicy")) { + if ((sscanf(v->value, "%d", &t) == 1) && ((t == OSP_AUTH_NO) || (t == OSP_AUTH_YES) || (t == OSP_AUTH_EXCLUSIVE))) { + p->authpolicy = t; + ast_log(LOG_DEBUG, "OSP: authpolicy '%d'\n", t); + } else { + ast_log(LOG_WARNING, "OSP: authpolicy should be %d, %d or %d, not '%s' at line %d\n", + OSP_AUTH_NO, OSP_AUTH_YES, OSP_AUTH_EXCLUSIVE, v->value, v->lineno); + } + } + v = v->next; + } + + error = OSPPUtilLoadPEMPrivateKey((unsigned char *) p->privatekey, &privatekey); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_WARNING, "OSP: Unable to load privatekey '%s', error '%d'\n", p->privatekey, error); + free(p); + return 0; + } + + error = OSPPUtilLoadPEMCert((unsigned char *) p->localcert, &localcert); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_WARNING, "OSP: Unable to load localcert '%s', error '%d'\n", p->localcert, error); + if (privatekey.PrivateKeyData) { + free(privatekey.PrivateKeyData); + } + free(p); + return 0; + } + + if (p->cacount < 1) { + snprintf(p->cacerts[p->cacount], sizeof(p->cacerts[0]), "%s/%s-cacert.pem", ast_config_AST_KEY_DIR, provider); + ast_log(LOG_DEBUG, "OSP: cacert[%d]: '%s'\n", p->cacount, p->cacerts[p->cacount]); + p->cacount++; + } + for (i = 0; i < p->cacount; i++) { + error = OSPPUtilLoadPEMCert((unsigned char *) p->cacerts[i], &cacerts[i]); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_WARNING, "OSP: Unable to load cacert '%s', error '%d'\n", p->cacerts[i], error); + for (j = 0; j < i; j++) { + if (cacerts[j].CertData) { + free(cacerts[j].CertData); + } + } + if (localcert.CertData) { + free(localcert.CertData); + } + if (privatekey.PrivateKeyData) { + free(privatekey.PrivateKeyData); + } + free(p); + return 0; + } + pcacerts[i] = &cacerts[i]; + } + + for (i = 0; i < p->spcount; i++) { + psrvpoints[i] = p->srvpoints[i]; + } + + error = OSPPProviderNew(p->spcount, psrvpoints, NULL, OSP_AUDIT_URL, &privatekey, &localcert, p->cacount, pcacerts, OSP_LOCAL_VALIDATION, + OSP_SSL_LIFETIME, p->maxconnections, OSP_HTTP_PERSISTENCE, p->retrydelay, p->retrylimit,p->timeout, OSP_CUSTOMER_ID, + OSP_DEVICE_ID, &p->handle); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_WARNING, "OSP: Unable to create provider '%s', error '%d'\n", provider, error); + free(p); + res = -1; + } else { + ast_log(LOG_DEBUG, "OSP: provider '%s'\n", provider); + ast_mutex_lock(&osplock); + p->next = ospproviders; + ospproviders = p; + ast_mutex_unlock(&osplock); + res = 1; + } + + for (i = 0; i < p->cacount; i++) { + if (cacerts[i].CertData) { + free(cacerts[i].CertData); + } + } + if (localcert.CertData) { + free(localcert.CertData); + } + if (privatekey.PrivateKeyData) { + free(privatekey.PrivateKeyData); + } + + return res; +} + +/*! + * \brief Get OSP authenticiation policy of provider + * \param provider OSP provider context name + * \param policy OSP authentication policy, output + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_get_policy(const char* provider, int* policy) +{ + int res = 0; + struct osp_provider* p; + + ast_mutex_lock(&osplock); + p = ospproviders; + while(p) { + if (!strcasecmp(p->name, provider)) { + *policy = p->authpolicy; + ast_log(LOG_DEBUG, "OSP: authpolicy '%d'\n", *policy); + res = 1; + break; + } + p = p->next; + } + ast_mutex_unlock(&osplock); + + return res; +} + +/*! + * \brief Create OSP transaction handle + * \param provider OSP provider context name + * \param transaction OSP transaction handle, output + * \param sourcesize Size of source buffer, in/output + * \param source Source of provider, output + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_create_transaction(const char* provider, int* transaction, unsigned int sourcesize, char* source) +{ + int res = 0; + struct osp_provider* p; + int error; + + ast_mutex_lock(&osplock); + p = ospproviders; + while(p) { + if (!strcasecmp(p->name, provider)) { + error = OSPPTransactionNew(p->handle, transaction); + if (error == OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: transaction '%d'\n", *transaction); + ast_copy_string(source, p->source, sourcesize); + ast_log(LOG_DEBUG, "OSP: source '%s'\n", source); + res = 1; + } else { + *transaction = OSP_INVALID_HANDLE; + ast_log(LOG_DEBUG, "OSP: Unable to create transaction handle, error '%d'\n", error); + res = -1; + } + break; + } + p = p->next; + } + ast_mutex_unlock(&osplock); + + return res; +} + +/*! + * \brief Convert address to "[x.x.x.x]" or "host.domain" format + * \param src Source address string + * \param dst Destination address string + * \param buffersize Size of dst buffer + */ +static void osp_convert_address( + const char* src, + char* dst, + int buffersize) +{ + struct in_addr inp; + + if (inet_aton(src, &inp) != 0) { + snprintf(dst, buffersize, "[%s]", src); + } else { + snprintf(dst, buffersize, "%s", src); + } +} + +/*! + * \brief Validate OSP token of inbound call + * \param transaction OSP transaction handle + * \param source Source of inbound call + * \param dest Destination of inbound call + * \param calling Calling number + * \param called Called number + * \param token OSP token, may be empty + * \param timelimit Call duration limit, output + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_validate_token(int transaction, const char* source, const char* dest, const char* calling, const char* called, const char* token, unsigned int* timelimit) +{ + int res; + int tokenlen; + unsigned char tokenstr[OSP_TOKSTR_SIZE]; + char src[OSP_NORSTR_SIZE]; + char dst[OSP_NORSTR_SIZE]; + unsigned int authorised; + unsigned int dummy = 0; + int error; + + tokenlen = ast_base64decode(tokenstr, token, strlen(token)); + osp_convert_address(source, src, sizeof(src)); + osp_convert_address(dest, dst, sizeof(dst)); + error = OSPPTransactionValidateAuthorisation( + transaction, + src, dst, NULL, NULL, + calling ? calling : "", OSPC_E164, + called, OSPC_E164, + 0, NULL, + tokenlen, (char *) tokenstr, + &authorised, + timelimit, + &dummy, NULL, + osp_tokenformat); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: Unable to validate inbound token\n"); + res = -1; + } else if (authorised) { + ast_log(LOG_DEBUG, "OSP: Authorised\n"); + res = 1; + } else { + ast_log(LOG_DEBUG, "OSP: Unauthorised\n"); + res = 0; + } + + return res; +} + +/*! + * \brief Choose min duration limit + * \param in Inbound duration limit + * \param out Outbound duration limit + * \return min duration limit + */ +static unsigned int osp_choose_timelimit(unsigned int in, unsigned int out) +{ + if (in == OSP_DEF_TIMELIMIT) { + return out; + } else if (out == OSP_DEF_TIMELIMIT) { + return in; + } else { + return in < out ? in : out; + } +} + +/*! + * \brief Choose min duration limit + * \param called Called number + * \param calling Calling number + * \param destination Destination IP in '[x.x.x.x]' format + * \param tokenlen OSP token length + * \param token OSP token + * \param reason Failure reason, output + * \param result OSP lookup results, in/output + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_check_destination(const char* called, const char* calling, char* destination, unsigned int tokenlen, const char* token, enum OSPEFAILREASON* reason, struct osp_result* result) +{ + int res; + OSPE_DEST_OSP_ENABLED enabled; + OSPE_DEST_PROT protocol; + int error; + + if (strlen(destination) <= 2) { + ast_log(LOG_DEBUG, "OSP: Wrong destination format '%s'\n", destination); + *reason = OSPC_FAIL_NORMAL_UNSPECIFIED; + return -1; + } + + if ((error = OSPPTransactionIsDestOSPEnabled(result->outhandle, &enabled)) != OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: Unable to get destination OSP version, error '%d'\n", error); + *reason = OSPC_FAIL_NORMAL_UNSPECIFIED; + return -1; + } + + if (enabled == OSPE_OSP_FALSE) { + result->token[0] = '\0'; + } else { + ast_base64encode(result->token, (const unsigned char *) token, tokenlen, sizeof(result->token) - 1); + } + + if ((error = OSPPTransactionGetDestProtocol(result->outhandle, &protocol)) != OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: Unable to get destination protocol, error '%d'\n", error); + *reason = OSPC_FAIL_NORMAL_UNSPECIFIED; + result->token[0] = '\0'; + return -1; + } + + res = 1; + /* Strip leading and trailing brackets */ + destination[strlen(destination) - 1] = '\0'; + switch(protocol) { + case OSPE_DEST_PROT_H323_SETUP: + ast_log(LOG_DEBUG, "OSP: protocol '%d'\n", protocol); + ast_copy_string(result->tech, "H323", sizeof(result->tech)); + snprintf(result->dest, sizeof(result->dest), "%s@%s", called, destination + 1); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + case OSPE_DEST_PROT_SIP: + ast_log(LOG_DEBUG, "OSP: protocol '%d'\n", protocol); + ast_copy_string(result->tech, "SIP", sizeof(result->tech)); + snprintf(result->dest, sizeof(result->dest), "%s@%s", called, destination + 1); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + case OSPE_DEST_PROT_IAX: + ast_log(LOG_DEBUG, "OSP: protocol '%d'\n", protocol); + ast_copy_string(result->tech, "IAX", sizeof(result->tech)); + snprintf(result->dest, sizeof(result->dest), "%s@%s", called, destination + 1); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + default: + ast_log(LOG_DEBUG, "OSP: Unknown protocol '%d'\n", protocol); + *reason = OSPC_FAIL_PROTOCOL_ERROR; + result->token[0] = '\0'; + res = 0; + } + + return res; +} + +/*! + * \brief Convert Asterisk status to TC code + * \param cause Asterisk hangup cause + * \return OSP TC code + */ +static enum OSPEFAILREASON asterisk2osp(int cause) +{ + return (enum OSPEFAILREASON)cause; +} + +/*! + * \brief OSP Authentication function + * \param provider OSP provider context name + * \param transaction OSP transaction handle, output + * \param source Source of inbound call + * \param calling Calling number + * \param called Called number + * \param token OSP token, may be empty + * \param timelimit Call duration limit, output + * \return 1 Authenricated, 0 Unauthenticated, -1 Error + */ +static int osp_auth(const char* provider, int* transaction, const char* source, const char* calling, const char* called, const char* token, unsigned int* timelimit) +{ + int res; + int policy = OSP_AUTH_YES; + char dest[OSP_NORSTR_SIZE]; + + *transaction = OSP_INVALID_HANDLE; + *timelimit = OSP_DEF_TIMELIMIT; + res = osp_get_policy(provider, &policy); + if (!res) { + ast_log(LOG_DEBUG, "OSP: Unabe to find OSP authentication policy\n"); + return res; + } + + switch (policy) { + case OSP_AUTH_NO: + res = 1; + break; + case OSP_AUTH_EXCLUSIVE: + if (ast_strlen_zero(token)) { + res = 0; + } else if ((res = osp_create_transaction(provider, transaction, sizeof(dest), dest)) <= 0) { + ast_log(LOG_DEBUG, "OSP: Unable to generate transaction handle\n"); + *transaction = OSP_INVALID_HANDLE; + res = 0; + } else if((res = osp_validate_token(*transaction, source, dest, calling, called, token, timelimit)) <= 0) { + OSPPTransactionRecordFailure(*transaction, OSPC_FAIL_CALL_REJECTED); + } + break; + case OSP_AUTH_YES: + default: + if (ast_strlen_zero(token)) { + res = 1; + } else if ((res = osp_create_transaction(provider, transaction, sizeof(dest), dest)) <= 0) { + ast_log(LOG_DEBUG, "OSP: Unable to generate transaction handle\n"); + *transaction = OSP_INVALID_HANDLE; + res = 0; + } else if((res = osp_validate_token(*transaction, source, dest, calling, called, token, timelimit)) <= 0) { + OSPPTransactionRecordFailure(*transaction, OSPC_FAIL_CALL_REJECTED); + } + break; + } + + return res; +} + +/*! + * \brief OSP Lookup function + * \param provider OSP provider context name + * \param srcdev Source device of outbound call + * \param calling Calling number + * \param called Called number + * \param result Lookup results + * \return 1 Found , 0 No route, -1 Error + */ +static int osp_lookup(const char* provider, const char* srcdev, const char* calling, const char* called, struct osp_result* result) +{ + int res; + char source[OSP_NORSTR_SIZE]; + unsigned int callidlen; + char callid[OSPC_CALLID_MAXSIZE]; + char callingnum[OSP_NORSTR_SIZE]; + char callednum[OSP_NORSTR_SIZE]; + char destination[OSP_NORSTR_SIZE]; + unsigned int tokenlen; + char token[OSP_TOKSTR_SIZE]; + char src[OSP_NORSTR_SIZE]; + char dev[OSP_NORSTR_SIZE]; + unsigned int dummy = 0; + enum OSPEFAILREASON reason; + int error; + + result->outhandle = OSP_INVALID_HANDLE; + result->tech[0] = '\0'; + result->dest[0] = '\0'; + result->calling[0] = '\0'; + result->token[0] = '\0'; + result->numresults = 0; + result->outtimelimit = OSP_DEF_TIMELIMIT; + + if ((res = osp_create_transaction(provider, &result->outhandle, sizeof(source), source)) <= 0) { + ast_log(LOG_DEBUG, "OSP: Unable to generate transaction handle\n"); + result->outhandle = OSP_INVALID_HANDLE; + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED); + } + return -1; + } + + osp_convert_address(source, src, sizeof(src)); + osp_convert_address(srcdev, dev, sizeof(dev)); + result->numresults = OSP_DEF_DESTINATIONS; + error = OSPPTransactionRequestAuthorisation(result->outhandle, src, dev, calling ? calling : "", + OSPC_E164, called, OSPC_E164, NULL, 0, NULL, NULL, &result->numresults, &dummy, NULL); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: Unable to request authorization\n"); + result->numresults = 0; + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED); + } + return -1; + } + + if (!result->numresults) { + ast_log(LOG_DEBUG, "OSP: No more destination\n"); + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + return 0; + } + + callidlen = sizeof(callid); + tokenlen = sizeof(token); + error = OSPPTransactionGetFirstDestination(result->outhandle, 0, NULL, NULL, &result->outtimelimit, &callidlen, callid, + sizeof(callednum), callednum, sizeof(callingnum), callingnum, sizeof(destination), destination, 0, NULL, &tokenlen, token); + if (error != OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: Unable to get first route\n"); + result->numresults = 0; + result->outtimelimit = OSP_DEF_TIMELIMIT; + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + return -1; + } + + result->numresults--; + result->outtimelimit = osp_choose_timelimit(result->intimelimit, result->outtimelimit); + ast_log(LOG_DEBUG, "OSP: outtimelimit '%d'\n", result->outtimelimit); + ast_log(LOG_DEBUG, "OSP: called '%s'\n", callednum); + ast_log(LOG_DEBUG, "OSP: calling '%s'\n", callingnum); + ast_log(LOG_DEBUG, "OSP: destination '%s'\n", destination); + ast_log(LOG_DEBUG, "OSP: token size '%d'\n", tokenlen); + + if ((res = osp_check_destination(callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) { + return 1; + } + + if (!result->numresults) { + ast_log(LOG_DEBUG, "OSP: No more destination\n"); + result->outtimelimit = OSP_DEF_TIMELIMIT; + OSPPTransactionRecordFailure(result->outhandle, reason); + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + return 0; + } + + while(result->numresults) { + callidlen = sizeof(callid); + tokenlen = sizeof(token); + error = OSPPTransactionGetNextDestination(result->outhandle, reason, 0, NULL, NULL, &result->outtimelimit, &callidlen, callid, + sizeof(callednum), callednum, sizeof(callingnum), callingnum, sizeof(destination), destination, 0, NULL, &tokenlen, token); + if (error == OSPC_ERR_NO_ERROR) { + result->numresults--; + result->outtimelimit = osp_choose_timelimit(result->intimelimit, result->outtimelimit); + ast_log(LOG_DEBUG, "OSP: outtimelimit '%d'\n", result->outtimelimit); + ast_log(LOG_DEBUG, "OSP: called '%s'\n", callednum); + ast_log(LOG_DEBUG, "OSP: calling '%s'\n", callingnum); + ast_log(LOG_DEBUG, "OSP: destination '%s'\n", destination); + ast_log(LOG_DEBUG, "OSP: token size '%d'\n", tokenlen); + if ((res = osp_check_destination(callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) { + break; + } else if (!result->numresults) { + ast_log(LOG_DEBUG, "OSP: No more destination\n"); + OSPPTransactionRecordFailure(result->outhandle, reason); + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + res = 0; + break; + } + } else { + ast_log(LOG_DEBUG, "OSP: Unable to get route, error '%d'\n", error); + result->numresults = 0; + result->outtimelimit = OSP_DEF_TIMELIMIT; + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED); + } + res = -1; + break; + } + } + return res; +} + +/*! + * \brief OSP Lookup Next function + * \param cause Asterisk hangup cuase + * \param result Lookup results, in/output + * \return 1 Found , 0 No route, -1 Error + */ +static int osp_next(int cause, struct osp_result* result) +{ + int res; + unsigned int callidlen; + char callid[OSPC_CALLID_MAXSIZE]; + char callingnum[OSP_NORSTR_SIZE]; + char callednum[OSP_NORSTR_SIZE]; + char destination[OSP_NORSTR_SIZE]; + unsigned int tokenlen; + char token[OSP_TOKSTR_SIZE]; + enum OSPEFAILREASON reason; + int error; + + result->tech[0] = '\0'; + result->dest[0] = '\0'; + result->calling[0] = '\0'; + result->token[0] = '\0'; + result->outtimelimit = OSP_DEF_TIMELIMIT; + + if (result->outhandle == OSP_INVALID_HANDLE) { + ast_log(LOG_DEBUG, "OSP: Transaction handle undefined\n"); + result->numresults = 0; + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED); + } + return -1; + } + + reason = asterisk2osp(cause); + + if (!result->numresults) { + ast_log(LOG_DEBUG, "OSP: No more destination\n"); + OSPPTransactionRecordFailure(result->outhandle, reason); + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + return 0; + } + + while(result->numresults) { + callidlen = sizeof(callid); + tokenlen = sizeof(token); + error = OSPPTransactionGetNextDestination(result->outhandle, reason, 0, NULL, NULL, &result->outtimelimit, &callidlen, + callid, sizeof(callednum), callednum, sizeof(callingnum), callingnum, sizeof(destination), destination, 0, NULL, &tokenlen, token); + if (error == OSPC_ERR_NO_ERROR) { + result->numresults--; + result->outtimelimit = osp_choose_timelimit(result->intimelimit, result->outtimelimit); + ast_log(LOG_DEBUG, "OSP: outtimelimit '%d'\n", result->outtimelimit); + ast_log(LOG_DEBUG, "OSP: called '%s'\n", callednum); + ast_log(LOG_DEBUG, "OSP: calling '%s'\n", callingnum); + ast_log(LOG_DEBUG, "OSP: destination '%s'\n", destination); + ast_log(LOG_DEBUG, "OSP: token size '%d'\n", tokenlen); + if ((res = osp_check_destination(callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) { + res = 1; + break; + } else if (!result->numresults) { + ast_log(LOG_DEBUG, "OSP: No more destination\n"); + OSPPTransactionRecordFailure(result->outhandle, reason); + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + res = 0; + break; + } + } else { + ast_log(LOG_DEBUG, "OSP: Unable to get route, error '%d'\n", error); + result->token[0] = '\0'; + result->numresults = 0; + result->outtimelimit = OSP_DEF_TIMELIMIT; + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NORMAL_UNSPECIFIED); + } + res = -1; + break; + } + } + + return res; +} + +/*! + * \brief OSP Finish function + * \param handle OSP in/outbound transaction handle + * \param recorded If failure reason has been recorded + * \param cause Asterisk hangup cause + * \param start Call start time + * \param connect Call connect time + * \param end Call end time + * \param release Who release first, 0 source, 1 destination + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_finish(int handle, int recorded, int cause, time_t start, time_t connect, time_t end, unsigned int release) +{ + int res; + enum OSPEFAILREASON reason; + time_t alert = 0; + unsigned isPddInfoPresent = 0; + unsigned pdd = 0; + unsigned int dummy = 0; + int error; + + if (handle == OSP_INVALID_HANDLE) { + return 0; + } + + if (!recorded) { + reason = asterisk2osp(cause); + OSPPTransactionRecordFailure(handle, reason); + } + + error = OSPPTransactionReportUsage(handle, difftime(end, connect), start, end, alert, connect, isPddInfoPresent, pdd, + release, (unsigned char *) "", 0, 0, 0, 0, &dummy, NULL); + if (error == OSPC_ERR_NO_ERROR) { + ast_log(LOG_DEBUG, "OSP: Usage reported\n"); + res = 1; + } else { + ast_log(LOG_DEBUG, "OSP: Unable to report usage, error '%d'\n", error); + res = -1; + } + OSPPTransactionDelete(handle); + + return res; +} + +/* OSP Application APIs */ + +/*! + * \brief OSP Application OSPAuth + * \param chan Channel + * \param data Parameter + * \return 0 Success, -1 Failed + */ +static int ospauth_exec(struct ast_channel* chan, void* data) +{ + int res; + struct ast_module_user *u; + const char* provider = OSP_DEF_PROVIDER; + int priority_jump = 0; + struct varshead *headp; + struct ast_var_t *current; + const char *source = ""; + const char *token = ""; + int handle; + unsigned int timelimit; + char buffer[OSP_INTSTR_SIZE]; + const char *status; + char *tmp; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(provider); + AST_APP_ARG(options); + ); + + u = ast_module_user_add(chan); + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + ast_module_user_remove(u); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + if (!ast_strlen_zero(args.provider)) { + provider = args.provider; + } + ast_log(LOG_DEBUG, "OSPAuth: provider '%s'\n", provider); + + if ((args.options) && (strchr(args.options, 'j'))) { + priority_jump = 1; + } + ast_log(LOG_DEBUG, "OSPAuth: priority jump '%d'\n", priority_jump); + + headp = &chan->varshead; + AST_LIST_TRAVERSE(headp, current, entries) { + if (!strcasecmp(ast_var_name(current), "OSPPEERIP")) { + source = ast_var_value(current); + } else if (!strcasecmp(ast_var_name(current), "OSPINTOKEN")) { + token = ast_var_value(current); + } + } + ast_log(LOG_DEBUG, "OSPAuth: source '%s'\n", source); + ast_log(LOG_DEBUG, "OSPAuth: token size '%zd'\n", strlen(token)); + + + if ((res = osp_auth(provider, &handle, source, chan->cid.cid_num, chan->exten, token, &timelimit)) > 0) { + status = AST_OSP_SUCCESS; + } else { + timelimit = OSP_DEF_TIMELIMIT; + if (!res) { + status = AST_OSP_FAILED; + } else { + status = AST_OSP_ERROR; + } + } + + snprintf(buffer, sizeof(buffer), "%d", handle); + pbx_builtin_setvar_helper(chan, "OSPINHANDLE", buffer); + ast_log(LOG_DEBUG, "OSPAuth: OSPINHANDLE '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", timelimit); + pbx_builtin_setvar_helper(chan, "OSPINTIMELIMIT", buffer); + ast_log(LOG_DEBUG, "OSPAuth: OSPINTIMELIMIT '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPAUTHSTATUS", status); + ast_log(LOG_DEBUG, "OSPAuth: %s\n", status); + + if(res <= 0) { + if (priority_jump || ast_opt_priority_jumping) { + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + res = 0; + } else { + res = -1; + } + } else { + res = 0; + } + + ast_module_user_remove(u); + + return res; +} + +/*! + * \brief OSP Application OSPLookup + * \param chan Channel + * \param data Parameter + * \return 0 Success, -1 Failed + */ +static int osplookup_exec(struct ast_channel* chan, void* data) +{ + int res, cres; + struct ast_module_user *u; + const char *provider = OSP_DEF_PROVIDER; + int priority_jump = 0; + struct varshead *headp; + struct ast_var_t* current; + const char *srcdev = ""; + char buffer[OSP_TOKSTR_SIZE]; + struct osp_result result; + const char *status; + char *tmp; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(exten); + AST_APP_ARG(provider); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "OSPLookup: Arg required, OSPLookup(exten[|provider[|options]])\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + ast_module_user_remove(u); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + ast_log(LOG_DEBUG, "OSPLookup: exten '%s'\n", args.exten); + + if (!ast_strlen_zero(args.provider)) { + provider = args.provider; + } + ast_log(LOG_DEBUG, "OSPlookup: provider '%s'\n", provider); + + if ((args.options) && (strchr(args.options, 'j'))) { + priority_jump = 1; + } + ast_log(LOG_DEBUG, "OSPLookup: priority jump '%d'\n", priority_jump); + + result.inhandle = OSP_INVALID_HANDLE; + result.intimelimit = OSP_DEF_TIMELIMIT; + + headp = &chan->varshead; + AST_LIST_TRAVERSE(headp, current, entries) { + if (!strcasecmp(ast_var_name(current), "OSPINHANDLE")) { + if (sscanf(ast_var_value(current), "%d", &result.inhandle) != 1) { + result.inhandle = OSP_INVALID_HANDLE; + } + } else if (!strcasecmp(ast_var_name(current), "OSPINTIMELIMIT")) { + if (sscanf(ast_var_value(current), "%d", &result.intimelimit) != 1) { + result.intimelimit = OSP_DEF_TIMELIMIT; + } + } else if (!strcasecmp(ast_var_name(current), "OSPPEERIP")) { + srcdev = ast_var_value(current); + } + } + ast_log(LOG_DEBUG, "OSPLookup: OSPINHANDLE '%d'\n", result.inhandle); + ast_log(LOG_DEBUG, "OSPLookup: OSPINTIMELIMIT '%d'\n", result.intimelimit); + ast_log(LOG_DEBUG, "OSPLookup: source device '%s'\n", srcdev); + + if ((cres = ast_autoservice_start(chan)) < 0) { + ast_module_user_remove(u); + return -1; + } + + if ((res = osp_lookup(provider, srcdev, chan->cid.cid_num, args.exten, &result)) > 0) { + status = AST_OSP_SUCCESS; + } else { + result.tech[0] = '\0'; + result.dest[0] = '\0'; + result.calling[0] = '\0'; + result.token[0] = '\0'; + result.numresults = 0; + result.outtimelimit = OSP_DEF_TIMELIMIT; + if (!res) { + status = AST_OSP_FAILED; + } else { + status = AST_OSP_ERROR; + } + } + + snprintf(buffer, sizeof(buffer), "%d", result.outhandle); + pbx_builtin_setvar_helper(chan, "OSPOUTHANDLE", buffer); + ast_log(LOG_DEBUG, "OSPLookup: OSPOUTHANDLE '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPTECH", result.tech); + ast_log(LOG_DEBUG, "OSPLookup: OSPTECH '%s'\n", result.tech); + pbx_builtin_setvar_helper(chan, "OSPDEST", result.dest); + ast_log(LOG_DEBUG, "OSPLookup: OSPDEST '%s'\n", result.dest); + pbx_builtin_setvar_helper(chan, "OSPCALLING", result.calling); + ast_log(LOG_DEBUG, "OSPLookup: OSPCALLING '%s'\n", result.calling); + pbx_builtin_setvar_helper(chan, "OSPOUTTOKEN", result.token); + ast_log(LOG_DEBUG, "OSPLookup: OSPOUTTOKEN size '%zd'\n", strlen(result.token)); + snprintf(buffer, sizeof(buffer), "%d", result.numresults); + pbx_builtin_setvar_helper(chan, "OSPRESULTS", buffer); + ast_log(LOG_DEBUG, "OSPLookup: OSPRESULTS '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", result.outtimelimit); + pbx_builtin_setvar_helper(chan, "OSPOUTTIMELIMIT", buffer); + ast_log(LOG_DEBUG, "OSPLookup: OSPOUTTIMELIMIT '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPLOOKUPSTATUS", status); + ast_log(LOG_DEBUG, "OSPLookup: %s\n", status); + + if (!strcasecmp(result.tech, "SIP")) { + if (!ast_strlen_zero(result.token)) { + snprintf(buffer, sizeof(buffer), "P-OSP-Auth-Token: %s", result.token); + pbx_builtin_setvar_helper(chan, "_SIPADDHEADER", buffer); + ast_log(LOG_DEBUG, "OSPLookup: SIPADDHEADER size '%zd'\n", strlen(buffer)); + } + } else if (!strcasecmp(result.tech, "H323")) { + } else if (!strcasecmp(result.tech, "IAX")) { + } + + if ((cres = ast_autoservice_stop(chan)) < 0) { + ast_module_user_remove(u); + return -1; + } + + if(res <= 0) { + if (priority_jump || ast_opt_priority_jumping) { + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + res = 0; + } else { + res = -1; + } + } else { + res = 0; + } + + ast_module_user_remove(u); + + return res; +} + +/*! + * \brief OSP Application OSPNext + * \param chan Channel + * \param data Parameter + * \return 0 Success, -1 Failed + */ +static int ospnext_exec(struct ast_channel* chan, void* data) +{ + int res; + struct ast_module_user *u; + int priority_jump = 0; + int cause = 0; + struct varshead* headp; + struct ast_var_t* current; + struct osp_result result; + char buffer[OSP_TOKSTR_SIZE]; + const char* status; + char* tmp; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(cause); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "OSPNext: Arg required, OSPNext(cause[|options])\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + ast_module_user_remove(u); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + if (!ast_strlen_zero(args.cause) && sscanf(args.cause, "%d", &cause) != 1) { + cause = 0; + } + ast_log(LOG_DEBUG, "OSPNext: cause '%d'\n", cause); + + if ((args.options) && (strchr(args.options, 'j'))) { + priority_jump = 1; + } + ast_log(LOG_DEBUG, "OSPNext: priority jump '%d'\n", priority_jump); + + result.inhandle = OSP_INVALID_HANDLE; + result.outhandle = OSP_INVALID_HANDLE; + result.intimelimit = OSP_DEF_TIMELIMIT; + result.numresults = 0; + + headp = &chan->varshead; + AST_LIST_TRAVERSE(headp, current, entries) { + if (!strcasecmp(ast_var_name(current), "OSPINHANDLE")) { + if (sscanf(ast_var_value(current), "%d", &result.inhandle) != 1) { + result.inhandle = OSP_INVALID_HANDLE; + } + } else if (!strcasecmp(ast_var_name(current), "OSPOUTHANDLE")) { + if (sscanf(ast_var_value(current), "%d", &result.outhandle) != 1) { + result.outhandle = OSP_INVALID_HANDLE; + } + } else if (!strcasecmp(ast_var_name(current), "OSPINTIMELIMIT")) { + if (sscanf(ast_var_value(current), "%d", &result.intimelimit) != 1) { + result.intimelimit = OSP_DEF_TIMELIMIT; + } + } else if (!strcasecmp(ast_var_name(current), "OSPRESULTS")) { + if (sscanf(ast_var_value(current), "%d", &result.numresults) != 1) { + result.numresults = 0; + } + } + } + ast_log(LOG_DEBUG, "OSPNext: OSPINHANDLE '%d'\n", result.inhandle); + ast_log(LOG_DEBUG, "OSPNext: OSPOUTHANDLE '%d'\n", result.outhandle); + ast_log(LOG_DEBUG, "OSPNext: OSPINTIMELIMIT '%d'\n", result.intimelimit); + ast_log(LOG_DEBUG, "OSPNext: OSPRESULTS '%d'\n", result.numresults); + + if ((res = osp_next(cause, &result)) > 0) { + status = AST_OSP_SUCCESS; + } else { + result.tech[0] = '\0'; + result.dest[0] = '\0'; + result.calling[0] = '\0'; + result.token[0] = '\0'; + result.numresults = 0; + result.outtimelimit = OSP_DEF_TIMELIMIT; + if (!res) { + status = AST_OSP_FAILED; + } else { + status = AST_OSP_ERROR; + } + } + + pbx_builtin_setvar_helper(chan, "OSPTECH", result.tech); + ast_log(LOG_DEBUG, "OSPNext: OSPTECH '%s'\n", result.tech); + pbx_builtin_setvar_helper(chan, "OSPDEST", result.dest); + ast_log(LOG_DEBUG, "OSPNext: OSPDEST '%s'\n", result.dest); + pbx_builtin_setvar_helper(chan, "OSPCALLING", result.calling); + ast_log(LOG_DEBUG, "OSPNext: OSPCALLING '%s'\n", result.calling); + pbx_builtin_setvar_helper(chan, "OSPOUTTOKEN", result.token); + ast_log(LOG_DEBUG, "OSPNext: OSPOUTTOKEN size '%zd'\n", strlen(result.token)); + snprintf(buffer, sizeof(buffer), "%d", result.numresults); + pbx_builtin_setvar_helper(chan, "OSPRESULTS", buffer); + ast_log(LOG_DEBUG, "OSPNext: OSPRESULTS '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", result.outtimelimit); + pbx_builtin_setvar_helper(chan, "OSPOUTTIMELIMIT", buffer); + ast_log(LOG_DEBUG, "OSPNext: OSPOUTTIMELIMIT '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPNEXTSTATUS", status); + ast_log(LOG_DEBUG, "OSPNext: %s\n", status); + + if (!strcasecmp(result.tech, "SIP")) { + if (!ast_strlen_zero(result.token)) { + snprintf(buffer, sizeof(buffer), "P-OSP-Auth-Token: %s", result.token); + pbx_builtin_setvar_helper(chan, "_SIPADDHEADER", buffer); + ast_log(LOG_DEBUG, "OSPLookup: SIPADDHEADER size '%zd'\n", strlen(buffer)); + } + } else if (!strcasecmp(result.tech, "H323")) { + } else if (!strcasecmp(result.tech, "IAX")) { + } + + if(res <= 0) { + if (priority_jump || ast_opt_priority_jumping) { + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + res = 0; + } else { + res = -1; + } + } else { + res = 0; + } + + ast_module_user_remove(u); + + return res; +} + +/*! + * \brief OSP Application OSPFinish + * \param chan Channel + * \param data Parameter + * \return 0 Success, -1 Failed + */ +static int ospfinished_exec(struct ast_channel* chan, void* data) +{ + int res = 1; + struct ast_module_user *u; + int priority_jump = 0; + int cause = 0; + struct varshead *headp; + struct ast_var_t *current; + int inhandle = OSP_INVALID_HANDLE; + int outhandle = OSP_INVALID_HANDLE; + int recorded = 0; + time_t start, connect, end; + unsigned int release; + char buffer[OSP_INTSTR_SIZE]; + const char *status; + char *tmp; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(cause); + AST_APP_ARG(options); + ); + + u = ast_module_user_add(chan); + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + ast_module_user_remove(u); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + if ((args.options) && (strchr(args.options, 'j'))) { + priority_jump = 1; + } + ast_log(LOG_DEBUG, "OSPFinish: priority jump '%d'\n", priority_jump); + + headp = &chan->varshead; + AST_LIST_TRAVERSE(headp, current, entries) { + if (!strcasecmp(ast_var_name(current), "OSPINHANDLE")) { + if (sscanf(ast_var_value(current), "%d", &inhandle) != 1) { + inhandle = OSP_INVALID_HANDLE; + } + } else if (!strcasecmp(ast_var_name(current), "OSPOUTHANDLE")) { + if (sscanf(ast_var_value(current), "%d", &outhandle) != 1) { + outhandle = OSP_INVALID_HANDLE; + } + } else if (!recorded && + (!strcasecmp(ast_var_name(current), "OSPAUTHSTATUS") || + !strcasecmp(ast_var_name(current), "OSPLOOKUPSTATUS") || + !strcasecmp(ast_var_name(current), "OSPNEXTSTATUS"))) + { + if (strcasecmp(ast_var_value(current), AST_OSP_SUCCESS)) { + recorded = 1; + } + } + } + ast_log(LOG_DEBUG, "OSPFinish: OSPINHANDLE '%d'\n", inhandle); + ast_log(LOG_DEBUG, "OSPFinish: OSPOUTHANDLE '%d'\n", outhandle); + ast_log(LOG_DEBUG, "OSPFinish: recorded '%d'\n", recorded); + + if (!ast_strlen_zero(args.cause) && sscanf(args.cause, "%d", &cause) != 1) { + cause = 0; + } + ast_log(LOG_DEBUG, "OSPFinish: cause '%d'\n", cause); + + if (chan->cdr) { + start = chan->cdr->start.tv_sec; + connect = chan->cdr->answer.tv_sec; + if (connect) { + end = time(NULL); + } else { + end = connect; + } + } else { + start = 0; + connect = 0; + end = 0; + } + ast_log(LOG_DEBUG, "OSPFinish: start '%ld'\n", start); + ast_log(LOG_DEBUG, "OSPFinish: connect '%ld'\n", connect); + ast_log(LOG_DEBUG, "OSPFinish: end '%ld'\n", end); + + release = chan->_softhangup ? 0 : 1; + + if (osp_finish(outhandle, recorded, cause, start, connect, end, release) <= 0) { + ast_log(LOG_DEBUG, "OSPFinish: Unable to report usage for outbound call\n"); + } + switch (cause) { + case AST_CAUSE_NORMAL_CLEARING: + break; + default: + cause = AST_CAUSE_NO_ROUTE_DESTINATION; + break; + } + if (osp_finish(inhandle, recorded, cause, start, connect, end, release) <= 0) { + ast_log(LOG_DEBUG, "OSPFinish: Unable to report usage for inbound call\n"); + } + snprintf(buffer, sizeof(buffer), "%d", OSP_INVALID_HANDLE); + pbx_builtin_setvar_helper(chan, "OSPOUTHANDLE", buffer); + pbx_builtin_setvar_helper(chan, "OSPINHANDLE", buffer); + + if (res > 0) { + status = AST_OSP_SUCCESS; + } else if (!res) { + status = AST_OSP_FAILED; + } else { + status = AST_OSP_ERROR; + } + pbx_builtin_setvar_helper(chan, "OSPFINISHSTATUS", status); + + if(!res) { + if (priority_jump || ast_opt_priority_jumping) { + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + res = 0; + } else { + res = -1; + } + } else { + res = 0; + } + + ast_module_user_remove(u); + + return res; +} + +/* OSP Module APIs */ + +static int osp_load(void) +{ + const char* t; + unsigned int v; + struct ast_config* cfg; + int error = OSPC_ERR_NO_ERROR; + + cfg = ast_config_load(OSP_CONFIG_FILE); + if (cfg) { + t = ast_variable_retrieve(cfg, OSP_GENERAL_CAT, "accelerate"); + if (t && ast_true(t)) { + if ((error = OSPPInit(1)) != OSPC_ERR_NO_ERROR) { + ast_log(LOG_WARNING, "OSP: Unable to enable hardware accelleration\n"); + OSPPInit(0); + } else { + osp_hardware = 1; + } + } else { + OSPPInit(0); + } + ast_log(LOG_DEBUG, "OSP: osp_hardware '%d'\n", osp_hardware); + + t = ast_variable_retrieve(cfg, OSP_GENERAL_CAT, "tokenformat"); + if (t) { + if ((sscanf(t, "%d", &v) == 1) && + ((v == TOKEN_ALGO_SIGNED) || (v == TOKEN_ALGO_UNSIGNED) || (v == TOKEN_ALGO_BOTH))) + { + osp_tokenformat = v; + } else { + ast_log(LOG_WARNING, "tokenformat should be an integer from %d, %d or %d, not '%s'\n", + TOKEN_ALGO_SIGNED, TOKEN_ALGO_UNSIGNED, TOKEN_ALGO_BOTH, t); + } + } + ast_log(LOG_DEBUG, "OSP: osp_tokenformat '%d'\n", osp_tokenformat); + + t = ast_category_browse(cfg, NULL); + while(t) { + if (strcasecmp(t, OSP_GENERAL_CAT)) { + osp_create_provider(cfg, t); + } + t = ast_category_browse(cfg, t); + } + + osp_initialized = 1; + + ast_config_destroy(cfg); + } else { + ast_log(LOG_WARNING, "OSP: Unable to find configuration. OSP support disabled\n"); + return 0; + } + ast_log(LOG_DEBUG, "OSP: osp_initialized '%d'\n", osp_initialized); + + return 1; +} + +static int osp_unload(void) +{ + struct osp_provider* p; + struct osp_provider* next; + + if (osp_initialized) { + ast_mutex_lock(&osplock); + p = ospproviders; + while(p) { + next = p->next; + OSPPProviderDelete(p->handle, 0); + free(p); + p = next; + } + ospproviders = NULL; + ast_mutex_unlock(&osplock); + + OSPPCleanup(); + + osp_tokenformat = TOKEN_ALGO_SIGNED; + osp_hardware = 0; + osp_initialized = 0; + } + return 0; +} + +static int osp_show(int fd, int argc, char* argv[]) +{ + int i; + int found = 0; + struct osp_provider* p; + const char* provider = NULL; + const char* tokenalgo; + + if ((argc < 2) || (argc > 3)) { + return RESULT_SHOWUSAGE; + } + if (argc > 2) { + provider = argv[2]; + } + if (!provider) { + switch (osp_tokenformat) { + case TOKEN_ALGO_BOTH: + tokenalgo = "Both"; + break; + case TOKEN_ALGO_UNSIGNED: + tokenalgo = "Unsigned"; + break; + case TOKEN_ALGO_SIGNED: + default: + tokenalgo = "Signed"; + break; + } + ast_cli(fd, "OSP: %s %s %s\n", + osp_initialized ? "Initialized" : "Uninitialized", osp_hardware ? "Accelerated" : "Normal", tokenalgo); + } + + ast_mutex_lock(&osplock); + p = ospproviders; + while(p) { + if (!provider || !strcasecmp(p->name, provider)) { + if (found) { + ast_cli(fd, "\n"); + } + ast_cli(fd, " == OSP Provider '%s' == \n", p->name); + ast_cli(fd, "Local Private Key: %s\n", p->privatekey); + ast_cli(fd, "Local Certificate: %s\n", p->localcert); + for (i = 0; i < p->cacount; i++) { + ast_cli(fd, "CA Certificate %d: %s\n", i + 1, p->cacerts[i]); + } + for (i = 0; i < p->spcount; i++) { + ast_cli(fd, "Service Point %d: %s\n", i + 1, p->srvpoints[i]); + } + ast_cli(fd, "Max Connections: %d\n", p->maxconnections); + ast_cli(fd, "Retry Delay: %d seconds\n", p->retrydelay); + ast_cli(fd, "Retry Limit: %d\n", p->retrylimit); + ast_cli(fd, "Timeout: %d milliseconds\n", p->timeout); + ast_cli(fd, "Source: %s\n", strlen(p->source) ? p->source : ""); + ast_cli(fd, "Auth Policy %d\n", p->authpolicy); + ast_cli(fd, "OSP Handle: %d\n", p->handle); + found++; + } + p = p->next; + } + ast_mutex_unlock(&osplock); + + if (!found) { + if (provider) { + ast_cli(fd, "Unable to find OSP provider '%s'\n", provider); + } else { + ast_cli(fd, "No OSP providers configured\n"); + } + } + return RESULT_SUCCESS; +} + +static const char* app1= "OSPAuth"; +static const char* synopsis1 = "OSP authentication"; +static const char* descrip1 = +" OSPAuth([provider[|options]]): Authenticate a SIP INVITE by OSP and sets\n" +"the variables:\n" +" ${OSPINHANDLE}: The inbound call transaction handle\n" +" ${OSPINTIMELIMIT}: The inbound call duration limit in seconds\n" +"\n" +"The option string may contain the following character:\n" +" 'j' -- jump to n+101 priority if the authentication was NOT successful\n" +"This application sets the following channel variable upon completion:\n" +" OSPAUTHSTATUS The status of the OSP Auth attempt as a text string, one of\n" +" SUCCESS | FAILED | ERROR\n"; + +static const char* app2= "OSPLookup"; +static const char* synopsis2 = "Lookup destination by OSP"; +static const char* descrip2 = +" OSPLookup(exten[|provider[|options]]): Looks up an extension via OSP and sets\n" +"the variables, where 'n' is the number of the result beginning with 1:\n" +" ${OSPOUTHANDLE}: The OSP Handle for anything remaining\n" +" ${OSPTECH}: The technology to use for the call\n" +" ${OSPDEST}: The destination to use for the call\n" +" ${OSPCALLING}: The calling number to use for the call\n" +" ${OSPOUTTOKEN}: The actual OSP token as a string\n" +" ${OSPOUTTIMELIMIT}: The outbound call duration limit in seconds\n" +" ${OSPRESULTS}: The number of OSP results total remaining\n" +"\n" +"The option string may contain the following character:\n" +" 'j' -- jump to n+101 priority if the lookup was NOT successful\n" +"This application sets the following channel variable upon completion:\n" +" OSPLOOKUPSTATUS The status of the OSP Lookup attempt as a text string, one of\n" +" SUCCESS | FAILED | ERROR\n"; + +static const char* app3 = "OSPNext"; +static const char* synopsis3 = "Lookup next destination by OSP"; +static const char* descrip3 = +" OSPNext(cause[|options]): Looks up the next OSP Destination for ${OSPOUTHANDLE}\n" +"See OSPLookup for more information\n" +"\n" +"The option string may contain the following character:\n" +" 'j' -- jump to n+101 priority if the lookup was NOT successful\n" +"This application sets the following channel variable upon completion:\n" +" OSPNEXTSTATUS The status of the OSP Next attempt as a text string, one of\n" +" SUCCESS | FAILED |ERROR\n"; + +static const char* app4 = "OSPFinish"; +static const char* synopsis4 = "Record OSP entry"; +static const char* descrip4 = +" OSPFinish([status[|options]]): Records call state for ${OSPINHANDLE}, according to\n" +"status, which should be one of BUSY, CONGESTION, ANSWER, NOANSWER, or CHANUNAVAIL\n" +"or coincidentally, just what the Dial application stores in its ${DIALSTATUS}.\n" +"\n" +"The option string may contain the following character:\n" +" 'j' -- jump to n+101 priority if the finish attempt was NOT successful\n" +"This application sets the following channel variable upon completion:\n" +" OSPFINISHSTATUS The status of the OSP Finish attempt as a text string, one of\n" +" SUCCESS | FAILED |ERROR \n"; + +static const char osp_usage[] = +"Usage: osp show\n" +" Displays information on Open Settlement Protocol support\n"; + +static struct ast_cli_entry cli_osp[] = { + { { "osp", "show", NULL}, + osp_show, "Displays OSP information", + osp_usage }, +}; + +static int load_module(void) +{ + int res; + + if(!osp_load()) + return AST_MODULE_LOAD_DECLINE; + + ast_cli_register_multiple(cli_osp, sizeof(cli_osp) / sizeof(struct ast_cli_entry)); + res = ast_register_application(app1, ospauth_exec, synopsis1, descrip1); + res |= ast_register_application(app2, osplookup_exec, synopsis2, descrip2); + res |= ast_register_application(app3, ospnext_exec, synopsis3, descrip3); + res |= ast_register_application(app4, ospfinished_exec, synopsis4, descrip4); + + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app4); + res |= ast_unregister_application(app3); + res |= ast_unregister_application(app2); + res |= ast_unregister_application(app1); + ast_cli_unregister_multiple(cli_osp, sizeof(cli_osp) / sizeof(struct ast_cli_entry)); + osp_unload(); + + ast_module_user_hangup_all(); + + return res; +} + +static int reload(void) +{ + osp_unload(); + osp_load(); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Open Settlement Protocol Applications", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/asterisk/apps/app_page.c b/asterisk/apps/app_page.c new file mode 100644 index 00000000..23bee7cb --- /dev/null +++ b/asterisk/apps/app_page.c @@ -0,0 +1,203 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (c) 2004 - 2006 Digium, Inc. All rights reserved. + * + * Mark Spencer + * + * This code is released under the GNU General Public License + * version 2.0. See LICENSE for more information. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + */ + +/*! \file + * + * \brief page() - Paging application + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + dahdi + app_meetme + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include + +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/file.h" +#include "asterisk/app.h" +#include "asterisk/chanvars.h" +#include "asterisk/utils.h" +#include "asterisk/dial.h" +#include "asterisk/devicestate.h" + +static const char *app_page= "Page"; + +static const char *page_synopsis = "Pages phones"; + +static const char *page_descrip = +"Page(Technology/Resource&Technology2/Resource2[|options])\n" +" Places outbound calls to the given technology / resource and dumps\n" +"them into a conference bridge as muted participants. The original\n" +"caller is dumped into the conference as a speaker and the room is\n" +"destroyed when the original caller leaves. Valid options are:\n" +" d - full duplex audio\n" +" q - quiet, do not play beep to caller\n" +" r - record the page into a file (see 'r' for app_meetme)\n"; + +enum { + PAGE_DUPLEX = (1 << 0), + PAGE_QUIET = (1 << 1), + PAGE_RECORD = (1 << 2), +} page_opt_flags; + +AST_APP_OPTIONS(page_opts, { + AST_APP_OPTION('d', PAGE_DUPLEX), + AST_APP_OPTION('q', PAGE_QUIET), + AST_APP_OPTION('r', PAGE_RECORD), +}); + +#define MAX_DIALS 128 + +static int page_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + char *options, *tech, *resource, *tmp; + char meetmeopts[88], originator[AST_CHANNEL_NAME]; + struct ast_flags flags = { 0 }; + unsigned int confid = ast_random(); + struct ast_app *app; + int res = 0, pos = 0, i = 0; + struct ast_dial *dials[MAX_DIALS]; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "This application requires at least one argument (destination(s) to page)\n"); + return -1; + } + + u = ast_module_user_add(chan); + + if (!(app = pbx_findapp("MeetMe"))) { + ast_log(LOG_WARNING, "There is no MeetMe application available!\n"); + ast_module_user_remove(u); + return -1; + }; + + options = ast_strdupa(data); + + ast_copy_string(originator, chan->name, sizeof(originator)); + if ((tmp = strchr(originator, '-'))) + *tmp = '\0'; + + tmp = strsep(&options, "|"); + if (options) + ast_app_parse_options(page_opts, &flags, NULL, options); + + snprintf(meetmeopts, sizeof(meetmeopts), "MeetMe|%ud|%s%sqxdw(5)", confid, (ast_test_flag(&flags, PAGE_DUPLEX) ? "" : "m"), + (ast_test_flag(&flags, PAGE_RECORD) ? "r" : "") ); + + /* Go through parsing/calling each device */ + while ((tech = strsep(&tmp, "&"))) { + struct ast_dial *dial = NULL; + + /* don't call the originating device */ + if (!strcasecmp(tech, originator)) + continue; + + /* If no resource is available, continue on */ + if (!(resource = strchr(tech, '/'))) { + ast_log(LOG_WARNING, "Incomplete destination '%s' supplied.\n", tech); + continue; + } + + *resource++ = '\0'; + + /* Create a dialing structure */ + if (!(dial = ast_dial_create())) { + ast_log(LOG_WARNING, "Failed to create dialing structure.\n"); + continue; + } + + /* Append technology and resource */ + ast_dial_append(dial, tech, resource); + + /* Set ANSWER_EXEC as global option */ + ast_dial_option_global_enable(dial, AST_DIAL_OPTION_ANSWER_EXEC, meetmeopts); + + /* Run this dial in async mode */ + ast_dial_run(dial, chan, 1); + + /* Put in our dialing array */ + dials[pos++] = dial; + } + + if (!ast_test_flag(&flags, PAGE_QUIET)) { + res = ast_streamfile(chan, "beep", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + } + + if (!res) { + snprintf(meetmeopts, sizeof(meetmeopts), "%ud|A%s%sqxd", confid, (ast_test_flag(&flags, PAGE_DUPLEX) ? "" : "t"), + (ast_test_flag(&flags, PAGE_RECORD) ? "r" : "") ); + pbx_exec(chan, app, meetmeopts); + } + + /* Go through each dial attempt cancelling, joining, and destroying */ + for (i = 0; i < pos; i++) { + struct ast_dial *dial = dials[i]; + + /* We have to wait for the async thread to exit as it's possible Meetme won't throw them out immediately */ + ast_dial_join(dial); + + /* Hangup all channels */ + ast_dial_hangup(dial); + + /* Destroy dialing structure */ + ast_dial_destroy(dial); + } + + ast_module_user_remove(u); + + return -1; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app_page); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application(app_page, page_exec, page_synopsis, page_descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Page Multiple Phones"); + diff --git a/asterisk/apps/app_parkandannounce.c b/asterisk/apps/app_parkandannounce.c new file mode 100644 index 00000000..40c27995 --- /dev/null +++ b/asterisk/apps/app_parkandannounce.c @@ -0,0 +1,260 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer + * + * Author: Ben Miller + * With TONS of help from Mark! + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 ParkAndAnnounce application for Asterisk + * + * \author Ben Miller + * \arg With TONS of help from Mark! + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 122213 $") + +#include +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/features.h" +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/say.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" + +static char *app = "ParkAndAnnounce"; + +static char *synopsis = "Park and Announce"; + +static char *descrip = +" ParkAndAnnounce(announce:template|timeout|dial|[return_context]):\n" +"Park a call into the parkinglot and announce the call to another channel.\n" +"\n" +"announce template: Colon-separated list of files to announce. The word PARKED\n" +" will be replaced by a say_digits of the extension in which\n" +" the call is parked.\n" +"timeout: Time in seconds before the call returns into the return\n" +" context.\n" +"dial: The app_dial style resource to call to make the\n" +" announcement. Console/dsp calls the console.\n" +"return_context: The goto-style label to jump the call back into after\n" +" timeout. Default .\n" +"\n" +"The variable ${PARKEDAT} will contain the parking extension into which the\n" +"call was placed. Use with the Local channel to allow the dialplan to make\n" +"use of this information.\n"; + + +static int parkandannounce_exec(struct ast_channel *chan, void *data) +{ + char *return_context; + int lot, timeout = 0, dres; + char *working, *context, *exten, *priority, *dial, *dialtech, *dialstr; + char *template, *tpl_working, *tpl_current; + char *tmp[100]; + char buf[13]; + int looptemp = 0,i = 0, res = 0; + char *s; + + struct ast_channel *dchan; + struct outgoing_helper oh; + int outstate; + + struct ast_module_user *u; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ParkAndAnnounce requires arguments: (announce:template|timeout|dial|[return_context])\n"); + return -1; + } + + u = ast_module_user_add(chan); + + s = ast_strdupa(data); + + template = strsep(&s,"|"); + if(! template) { + ast_log(LOG_WARNING, "PARK: An announce template must be defined\n"); + ast_module_user_remove(u); + return -1; + } + + if(s) { + timeout = atoi(strsep(&s, "|")); + timeout *= 1000; + } + dial = strsep(&s, "|"); + if(!dial) { + ast_log(LOG_WARNING, "PARK: A dial resource must be specified i.e: Console/dsp or Zap/g1/5551212\n"); + ast_module_user_remove(u); + return -1; + } else { + dialtech = strsep(&dial, "/"); + dialstr = dial; + ast_verbose( VERBOSE_PREFIX_3 "Dial Tech,String: (%s,%s)\n", dialtech,dialstr); + } + + return_context = s; + + if(return_context != NULL) { + /* set the return context. Code borrowed from the Goto builtin */ + + working = return_context; + context = strsep(&working, "|"); + exten = strsep(&working, "|"); + if(!exten) { + /* Only a priority in this one */ + priority = context; + exten = NULL; + context = NULL; + } else { + priority = strsep(&working, "|"); + if(!priority) { + /* Only an extension and priority in this one */ + priority = exten; + exten = context; + context = NULL; + } + } + if(atoi(priority) < 0) { + ast_log(LOG_WARNING, "Priority '%s' must be a number > 0\n", priority); + ast_module_user_remove(u); + return -1; + } + /* At this point we have a priority and maybe an extension and a context */ + chan->priority = atoi(priority); + if (exten) + ast_copy_string(chan->exten, exten, sizeof(chan->exten)); + if (context) + ast_copy_string(chan->context, context, sizeof(chan->context)); + } else { /* increment the priority by default*/ + chan->priority++; + } + + if(option_verbose > 2) { + ast_verbose( VERBOSE_PREFIX_3 "Return Context: (%s,%s,%d) ID: %s\n", chan->context,chan->exten, chan->priority, chan->cid.cid_num); + if(!ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) { + ast_verbose( VERBOSE_PREFIX_3 "Warning: Return Context Invalid, call will return to default|s\n"); + } + } + + /* we are using masq_park here to protect * from touching the channel once we park it. If the channel comes out of timeout + before we are done announcing and the channel is messed with, Kablooeee. So we use Masq to prevent this. */ + + res = ast_masq_park_call(chan, NULL, timeout, &lot); + if (res == -1) { + goto finish; + } + + ast_verbose( VERBOSE_PREFIX_3 "Call Parking Called, lot: %d, timeout: %d, context: %s\n", lot, timeout, return_context); + + /* Now place the call to the extention */ + + snprintf(buf, sizeof(buf), "%d", lot); + memset(&oh, 0, sizeof(oh)); + oh.parent_channel = chan; + oh.vars = ast_variable_new("_PARKEDAT", buf); + dchan = __ast_request_and_dial(dialtech, AST_FORMAT_SLINEAR, dialstr,30000, &outstate, chan->cid.cid_num, chan->cid.cid_name, &oh); + + if(dchan) { + if(dchan->_state == AST_STATE_UP) { + if(option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", dchan->name); + } else { + if(option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", dchan->name); + ast_log(LOG_WARNING, "PARK: Channel %s was never answered for the announce.\n", dchan->name); + ast_hangup(dchan); + ast_module_user_remove(u); + return -1; + } + } else { + ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n"); + ast_module_user_remove(u); + return -1; + } + + ast_stopstream(dchan); + + /* now we have the call placed and are ready to play stuff to it */ + + ast_verbose(VERBOSE_PREFIX_4 "Announce Template:%s\n", template); + + tpl_working = template; + tpl_current = strsep(&tpl_working, ":"); + + while(tpl_current && looptemp < ARRAY_LEN(tmp)) { + tmp[looptemp]=tpl_current; + looptemp++; + tpl_current = strsep(&tpl_working,":"); + } + + for(i = 0; i < looptemp; i++) { + ast_verbose(VERBOSE_PREFIX_4 "Announce:%s\n", tmp[i]); + if(!strcmp(tmp[i], "PARKED")) { + ast_say_digits(dchan, lot, "", dchan->language); + } else { + dres = ast_streamfile(dchan, tmp[i], dchan->language); + if(!dres) { + dres = ast_waitstream(dchan, ""); + } else { + ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", tmp[i], dchan->name); + dres = 0; + } + } + } + + ast_stopstream(dchan); + ast_hangup(dchan); + +finish: + ast_module_user_remove(u); + return res; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + /* return ast_register_application(app, park_exec); */ + return ast_register_application(app, parkandannounce_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Parking and Announce Application"); diff --git a/asterisk/apps/app_playback.c b/asterisk/apps/app_playback.c new file mode 100644 index 00000000..5535b485 --- /dev/null +++ b/asterisk/apps/app_playback.c @@ -0,0 +1,494 @@ +/* + * 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 Trivial application to playback a sound file + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 114611 $") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/utils.h" +#include "asterisk/options.h" +#include "asterisk/app.h" +#include "asterisk/cli.h" +#include "asterisk/localtime.h" +#include "asterisk/say.h" + +static char *app = "Playback"; + +static char *synopsis = "Play a file"; + +static char *descrip = +" Playback(filename[&filename2...][|option]): Plays back given filenames (do not put\n" +"extension). Options may also be included following a pipe symbol. The 'skip'\n" +"option causes the playback of the message to be skipped if the channel\n" +"is not in the 'up' state (i.e. it hasn't been answered yet). If 'skip' is \n" +"specified, the application will return immediately should the channel not be\n" +"off hook. Otherwise, unless 'noanswer' is specified, the channel will\n" +"be answered before the sound is played. Not all channels support playing\n" +"messages while still on hook. If 'j' is specified, the application\n" +"will jump to priority n+101 if present when a file specified to be played\n" +"does not exist.\n" +"This application sets the following channel variable upon completion:\n" +" PLAYBACKSTATUS The status of the playback attempt as a text string, one of\n" +" SUCCESS | FAILED\n" +"See Also: Background (application) -- for playing soundfiles that are interruptible\n" +" WaitExten (application) -- wait for digits from caller, optionally play music on hold\n" +; + + +static struct ast_config *say_cfg = NULL; +/* save the say' api calls. + * The first entry is NULL if we have the standard source, + * otherwise we are sourcing from here. + * 'say load [new|old]' will enable the new or old method, or report status + */ +static const void * say_api_buf[40]; +static const char *say_old = "old"; +static const char *say_new = "new"; + +static void save_say_mode(const void *arg) +{ + int i = 0; + say_api_buf[i++] = arg; + + say_api_buf[i++] = ast_say_number_full; + say_api_buf[i++] = ast_say_enumeration_full; + say_api_buf[i++] = ast_say_digit_str_full; + say_api_buf[i++] = ast_say_character_str_full; + say_api_buf[i++] = ast_say_phonetic_str_full; + say_api_buf[i++] = ast_say_datetime; + say_api_buf[i++] = ast_say_time; + say_api_buf[i++] = ast_say_date; + say_api_buf[i++] = ast_say_datetime_from_now; + say_api_buf[i++] = ast_say_date_with_format; +} + +static void restore_say_mode(void *arg) +{ + int i = 0; + say_api_buf[i++] = arg; + + ast_say_number_full = say_api_buf[i++]; + ast_say_enumeration_full = say_api_buf[i++]; + ast_say_digit_str_full = say_api_buf[i++]; + ast_say_character_str_full = say_api_buf[i++]; + ast_say_phonetic_str_full = say_api_buf[i++]; + ast_say_datetime = say_api_buf[i++]; + ast_say_time = say_api_buf[i++]; + ast_say_date = say_api_buf[i++]; + ast_say_datetime_from_now = say_api_buf[i++]; + ast_say_date_with_format = say_api_buf[i++]; +} + +/* + * Typical 'say' arguments in addition to the date or number or string + * to say. We do not include 'options' because they may be different + * in recursive calls, and so they are better left as an external + * parameter. + */ +typedef struct { + struct ast_channel *chan; + const char *ints; + const char *language; + int audiofd; + int ctrlfd; +} say_args_t; + +static int s_streamwait3(const say_args_t *a, const char *fn) +{ + int res = ast_streamfile(a->chan, fn, a->language); + if (res) { + ast_log(LOG_WARNING, "Unable to play message %s\n", fn); + return res; + } + res = (a->audiofd > -1 && a->ctrlfd > -1) ? + ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) : + ast_waitstream(a->chan, a->ints); + ast_stopstream(a->chan); + return res; +} + +/* + * the string is 'prefix:data' or prefix:fmt:data' + * with ':' being invalid in strings. + */ +static int do_say(say_args_t *a, const char *s, const char *options, int depth) +{ + struct ast_variable *v; + char *lang, *x, *rule = NULL; + int ret = 0; + struct varshead head = { .first = NULL, .last = NULL }; + struct ast_var_t *n; + + if (depth++ > 10) { + ast_log(LOG_WARNING, "recursion too deep, exiting\n"); + return -1; + } else if (!say_cfg) { + ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", s); + return -1; + } + + /* scan languages same as in file.c */ + if (a->language == NULL) + a->language = "en"; /* default */ + lang = ast_strdupa(a->language); + for (;;) { + for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) { + if (ast_extension_match(v->name, s)) { + rule = ast_strdupa(v->value); + break; + } + } + if (rule) + break; + if ( (x = strchr(lang, '_')) ) + *x = '\0'; /* try without suffix */ + else if (strcmp(lang, "en")) + lang = "en"; /* last resort, try 'en' if not done yet */ + else + break; + } + if (!rule) + return 0; + + /* skip up to two prefixes to get the value */ + if ( (x = strchr(s, ':')) ) + s = x + 1; + if ( (x = strchr(s, ':')) ) + s = x + 1; + n = ast_var_assign("SAY", s); + AST_LIST_INSERT_HEAD(&head, n, entries); + + /* scan the body, one piece at a time */ + while ( !ret && (x = strsep(&rule, ",")) ) { /* exit on key */ + char fn[128]; + const char *p, *fmt, *data; /* format and data pointers */ + + /* prepare a decent file name */ + x = ast_skip_blanks(x); + ast_trim_blanks(x); + + /* replace variables */ + memset(fn, 0, sizeof(fn)); /* XXX why isn't done in pbx_substitute_variables_helper! */ + pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn)); + + /* locate prefix and data, if any */ + fmt = index(fn, ':'); + if (!fmt || fmt == fn) { /* regular filename */ + ret = s_streamwait3(a, fn); + continue; + } + fmt++; + data = index(fmt, ':'); /* colon before data */ + if (!data || data == fmt) { /* simple prefix-fmt */ + ret = do_say(a, fn, options, depth); + continue; + } + /* prefix:fmt:data */ + for (p = fmt; p < data && ret <= 0; p++) { + char fn2[sizeof(fn)]; + if (*p == ' ' || *p == '\t') /* skip blanks */ + continue; + if (*p == '\'') {/* file name - we trim them */ + char *y; + strcpy(fn2, ast_skip_blanks(p+1)); /* make a full copy */ + y = index(fn2, '\''); + if (!y) { + p = data; /* invalid. prepare to end */ + break; + } + *y = '\0'; + ast_trim_blanks(fn2); + p = index(p+1, '\''); + ret = s_streamwait3(a, fn2); + } else { + int l = fmt-fn; + strcpy(fn2, fn); /* copy everything */ + /* after prefix, append the format */ + fn2[l++] = *p; + strcpy(fn2 + l, data); + ret = do_say(a, fn2, options, depth); + } + + if (ret) { + break; + } + } + } + ast_var_delete(n); + return ret; +} + +static int say_full(struct ast_channel *chan, const char *string, + const char *ints, const char *lang, const char *options, + int audiofd, int ctrlfd) +{ + say_args_t a = { chan, ints, lang, audiofd, ctrlfd }; + return do_say(&a, string, options, 0); +} + +static int say_number_full(struct ast_channel *chan, int num, + const char *ints, const char *lang, const char *options, + int audiofd, int ctrlfd) +{ + char buf[64]; + say_args_t a = { chan, ints, lang, audiofd, ctrlfd }; + snprintf(buf, sizeof(buf), "num:%d", num); + return do_say(&a, buf, options, 0); +} + +static int say_enumeration_full(struct ast_channel *chan, int num, + const char *ints, const char *lang, const char *options, + int audiofd, int ctrlfd) +{ + char buf[64]; + say_args_t a = { chan, ints, lang, audiofd, ctrlfd }; + snprintf(buf, sizeof(buf), "enum:%d", num); + return do_say(&a, buf, options, 0); +} + +static int say_date_generic(struct ast_channel *chan, time_t t, + const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix) +{ + char buf[128]; + struct tm tm; + say_args_t a = { chan, ints, lang, -1, -1 }; + if (format == NULL) + format = ""; + + ast_localtime(&t, &tm, NULL); + snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d", + prefix, + format, + tm.tm_year+1900, + tm.tm_mon+1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + tm.tm_wday, + tm.tm_yday); + return do_say(&a, buf, NULL, 0); +} + +static int say_date_with_format(struct ast_channel *chan, time_t t, + const char *ints, const char *lang, const char *format, const char *timezone) +{ + return say_date_generic(chan, t, ints, lang, format, timezone, "datetime"); +} + +static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang) +{ + return say_date_generic(chan, t, ints, lang, "", NULL, "date"); +} + +static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang) +{ + return say_date_generic(chan, t, ints, lang, "", NULL, "time"); +} + +static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang) +{ + return say_date_generic(chan, t, ints, lang, "", NULL, "datetime"); +} + +/* + * remap the 'say' functions to use those in this file + */ +static int __say_init(int fd, int argc, char *argv[]) +{ + const char *old_mode = say_api_buf[0] ? say_new : say_old; + char *mode; + + if (argc == 2) { + ast_cli(fd, "say mode is [%s]\n", old_mode); + return RESULT_SUCCESS; + } else if (argc != 3) + return RESULT_SHOWUSAGE; + mode = argv[2]; + + ast_log(LOG_WARNING, "init say.c from %s to %s\n", old_mode, mode); + + if (!strcmp(mode, old_mode)) { + ast_log(LOG_WARNING, "say mode is %s already\n", mode); + } else if (!strcmp(mode, say_new)) { + if (say_cfg == NULL) + say_cfg = ast_config_load("say.conf"); + save_say_mode(say_new); + ast_say_number_full = say_number_full; + + ast_say_enumeration_full = say_enumeration_full; +#if 0 + ast_say_digits_full = say_digits_full; + ast_say_digit_str_full = say_digit_str_full; + ast_say_character_str_full = say_character_str_full; + ast_say_phonetic_str_full = say_phonetic_str_full; + ast_say_datetime_from_now = say_datetime_from_now; +#endif + ast_say_datetime = say_datetime; + ast_say_time = say_time; + ast_say_date = say_date; + ast_say_date_with_format = say_date_with_format; + } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) { + restore_say_mode(NULL); + } else { + ast_log(LOG_WARNING, "unrecognized mode %s\n", mode); + } + return RESULT_SUCCESS; +} + +static struct ast_cli_entry cli_playback[] = { + { { "say", "load", NULL }, + __say_init, "set/show the say mode", + "Usage: say load [new|old]\n Set/show the say mode\n" }, +}; + +static int playback_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + int mres = 0; + struct ast_module_user *u; + char *tmp; + int option_skip=0; + int option_say=0; + int option_noanswer = 0; + int priority_jump = 0; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filenames); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Playback requires an argument (filename)\n"); + return -1; + } + + tmp = ast_strdupa(data); + + u = ast_module_user_add(chan); + AST_STANDARD_APP_ARGS(args, tmp); + + if (args.options) { + if (strcasestr(args.options, "skip")) + option_skip = 1; + if (strcasestr(args.options, "say")) + option_say = 1; + if (strcasestr(args.options, "noanswer")) + option_noanswer = 1; + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + if (chan->_state != AST_STATE_UP) { + if (option_skip) { + /* At the user's option, skip if the line is not up */ + goto done; + } else if (!option_noanswer) + /* Otherwise answer unless we're supposed to send this while on-hook */ + res = ast_answer(chan); + } + if (!res) { + char *back = args.filenames; + char *front; + + ast_stopstream(chan); + while (!res && (front = strsep(&back, "&"))) { + if (option_say) + res = say_full(chan, front, "", chan->language, NULL, -1, -1); + else + res = ast_streamfile(chan, front, chan->language); + if (!res) { + res = ast_waitstream(chan, ""); + ast_stopstream(chan); + } else { + ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data); + if (priority_jump || ast_opt_priority_jumping) + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + res = 0; + mres = 1; + } + } + } +done: + pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS"); + ast_module_user_remove(u); + return res; +} + +static int reload(void) +{ + if (say_cfg) { + ast_config_destroy(say_cfg); + ast_log(LOG_NOTICE, "Reloading say.conf\n"); + } + say_cfg = ast_config_load("say.conf"); + /* + * XXX here we should sort rules according to the same order + * we have in pbx.c so we have the same matching behaviour. + */ + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app); + + ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry)); + + ast_module_user_hangup_all(); + + if (say_cfg) + ast_config_destroy(say_cfg); + + return res; +} + +static int load_module(void) +{ + say_cfg = ast_config_load("say.conf"); + ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry)); + return ast_register_application(app, playback_exec, synopsis, descrip); +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/asterisk/apps/app_privacy.c b/asterisk/apps/app_privacy.c new file mode 100644 index 00000000..2a707a99 --- /dev/null +++ b/asterisk/apps/app_privacy.c @@ -0,0 +1,232 @@ +/* + * 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 Block all calls without Caller*ID, require phone # to be entered + * + * \author Mark Spencer + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 43364 $") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/utils.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/image.h" +#include "asterisk/callerid.h" +#include "asterisk/app.h" +#include "asterisk/config.h" + +#define PRIV_CONFIG "privacy.conf" + +static char *app = "PrivacyManager"; + +static char *synopsis = "Require phone number to be entered, if no CallerID sent"; + +static char *descrip = + " PrivacyManager([maxretries[|minlength[|options]]]): If no Caller*ID \n" + "is sent, PrivacyManager answers the channel and asks the caller to\n" + "enter their phone number. The caller is given 3 attempts to do so.\n" + "The application does nothing if Caller*ID was received on the channel.\n" + " Configuration file privacy.conf contains two variables:\n" + " maxretries default 3 -maximum number of attempts the caller is allowed \n" + " to input a callerid.\n" + " minlength default 10 -minimum allowable digits in the input callerid number.\n" + "If you don't want to use the config file and have an i/o operation with\n" + "every call, you can also specify maxretries and minlength as application\n" + "parameters. Doing so supercedes any values set in privacy.conf.\n" + "The option string may contain the following character: \n" + " 'j' -- jump to n+101 priority after failed attempts to collect\n" + " the minlength number of digits.\n" + "The application sets the following channel variable upon completion: \n" + "PRIVACYMGRSTATUS The status of the privacy manager's attempt to collect \n" + " a phone number from the user. A text string that is either:\n" + " SUCCESS | FAILED \n" +; + + +static int privacy_exec (struct ast_channel *chan, void *data) +{ + int res=0; + int retries; + int maxretries = 3; + int minlength = 10; + int x = 0; + const char *s; + char phone[30]; + struct ast_module_user *u; + struct ast_config *cfg = NULL; + char *parse = NULL; + int priority_jump = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(maxretries); + AST_APP_ARG(minlength); + AST_APP_ARG(options); + ); + + u = ast_module_user_add(chan); + + if (!ast_strlen_zero(chan->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose (VERBOSE_PREFIX_3 "CallerID Present: Skipping\n"); + } else { + /*Answer the channel if it is not already*/ + if (chan->_state != AST_STATE_UP) { + res = ast_answer(chan); + if (res) { + ast_module_user_remove(u); + return -1; + } + } + + if (!ast_strlen_zero(data)) { + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (args.maxretries) { + if (sscanf(args.maxretries, "%d", &x) == 1) + maxretries = x; + else + ast_log(LOG_WARNING, "Invalid max retries argument\n"); + } + if (args.minlength) { + if (sscanf(args.minlength, "%d", &x) == 1) + minlength = x; + else + ast_log(LOG_WARNING, "Invalid min length argument\n"); + } + if (args.options) + if (strchr(args.options, 'j')) + priority_jump = 1; + + } + + if (!x) + { + /*Read in the config file*/ + cfg = ast_config_load(PRIV_CONFIG); + + if (cfg && (s = ast_variable_retrieve(cfg, "general", "maxretries"))) { + if (sscanf(s, "%d", &x) == 1) + maxretries = x; + else + ast_log(LOG_WARNING, "Invalid max retries argument\n"); + } + + if (cfg && (s = ast_variable_retrieve(cfg, "general", "minlength"))) { + if (sscanf(s, "%d", &x) == 1) + minlength = x; + else + ast_log(LOG_WARNING, "Invalid min length argument\n"); + } + } + + /*Play unidentified call*/ + res = ast_safe_sleep(chan, 1000); + if (!res) + res = ast_streamfile(chan, "privacy-unident", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + + /*Ask for 10 digit number, give 3 attempts*/ + for (retries = 0; retries < maxretries; retries++) { + if (!res) + res = ast_streamfile(chan, "privacy-prompt", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + + if (!res ) + res = ast_readstring(chan, phone, sizeof(phone) - 1, /* digit timeout ms */ 3200, /* first digit timeout */ 5000, "#"); + + if (res < 0) + break; + + /*Make sure we get at least digits*/ + if (strlen(phone) >= minlength ) + break; + else { + res = ast_streamfile(chan, "privacy-incorrect", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + } + } + + /*Got a number, play sounds and send them on their way*/ + if ((retries < maxretries) && res >= 0 ) { + res = ast_streamfile(chan, "privacy-thankyou", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + + ast_set_callerid (chan, phone, "Privacy Manager", NULL); + + /* Clear the unavailable presence bit so if it came in on PRI + * the caller id will now be passed out to other channels + */ + chan->cid.cid_pres &= (AST_PRES_UNAVAILABLE ^ 0xFF); + + if (option_verbose > 2) { + ast_verbose (VERBOSE_PREFIX_3 "Changed Caller*ID to %s, callerpres to %d\n",phone,chan->cid.cid_pres); + } + pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "SUCCESS"); + } else { + if (priority_jump || ast_opt_priority_jumping) + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "FAILED"); + } + if (cfg) + ast_config_destroy(cfg); + } + + ast_module_user_remove(u); + + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application (app); + + ast_module_user_hangup_all(); + + return res; +} + +static int load_module(void) +{ + return ast_register_application (app, privacy_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Require phone number to be entered, if no CallerID sent"); diff --git a/asterisk/apps/app_queue.c b/asterisk/apps/app_queue.c new file mode 100644 index 00000000..241d5767 --- /dev/null +++ b/asterisk/apps/app_queue.c @@ -0,0 +1,5118 @@ +/* + * 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 True call queues with optional send URL on answer + * + * \author Mark Spencer + * + * \arg Config in \ref Config_qu queues.conf + * + * \par Development notes + * \note 2004-11-25: Persistent Dynamic Members added by: + * NetNation Communications (www.netnation.com) + * Kevin Lindsay + * + * Each dynamic agent in each queue is now stored in the astdb. + * When asterisk is restarted, each agent will be automatically + * readded into their recorded queues. This feature can be + * configured with the 'persistent_members=<1|0>' setting in the + * '[general]' category in queues.conf. The default is on. + * + * \note 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr). + * + * \note These features added by David C. Troy : + * - Per-queue holdtime calculation + * - Estimated holdtime announcement + * - Position announcement + * - Abandoned/completed call counters + * - Failout timer passed as optional app parameter + * - Optional monitoring of calls, started when call is answered + * + * Patch Version 1.07 2003-12-24 01 + * + * Added servicelevel statistic by Michiel Betel + * Added Priority jumping code for adding and removing queue members by Jonathan Stanton + * + * Fixed to work with CVS as of 2004-02-25 and released as 1.07a + * by Matthew Enger + * + * \ingroup applications + */ + +/*** MODULEINFO + res_monitor + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 147386 $") + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/options.h" +#include "asterisk/app.h" +#include "asterisk/linkedlists.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/say.h" +#include "asterisk/features.h" +#include "asterisk/musiconhold.h" +#include "asterisk/cli.h" +#include "asterisk/manager.h" +#include "asterisk/config.h" +#include "asterisk/monitor.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/astdb.h" +#include "asterisk/devicestate.h" +#include "asterisk/stringfields.h" +#include "asterisk/astobj2.h" +#include "asterisk/global_datastores.h" + +/* Please read before modifying this file. + * There are three locks which are regularly used + * throughout this file, the queue list lock, the lock + * for each individual queue, and the interface list lock. + * Please be extra careful to always lock in the following order + * 1) queue list lock + * 2) individual queue lock + * 3) interface list lock + * This order has sort of "evolved" over the lifetime of this + * application, but it is now in place this way, so please adhere + * to this order! + */ + + +enum { + QUEUE_STRATEGY_RINGALL = 0, + QUEUE_STRATEGY_ROUNDROBIN, + QUEUE_STRATEGY_LEASTRECENT, + QUEUE_STRATEGY_FEWESTCALLS, + QUEUE_STRATEGY_RANDOM, + QUEUE_STRATEGY_RRMEMORY +}; + +static struct strategy { + int strategy; + char *name; +} strategies[] = { + { QUEUE_STRATEGY_RINGALL, "ringall" }, + { QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" }, + { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" }, + { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, + { QUEUE_STRATEGY_RANDOM, "random" }, + { QUEUE_STRATEGY_RRMEMORY, "rrmemory" }, +}; + +#define DEFAULT_RETRY 5 +#define DEFAULT_TIMEOUT 15 +#define RECHECK 1 /* Recheck every second to see we we're at the top yet */ +#define MAX_PERIODIC_ANNOUNCEMENTS 10 /* The maximum periodic announcements we can have */ + +#define RES_OKAY 0 /* Action completed */ +#define RES_EXISTS (-1) /* Entry already exists */ +#define RES_OUTOFMEMORY (-2) /* Out of memory */ +#define RES_NOSUCHQUEUE (-3) /* No such queue */ +#define RES_NOT_DYNAMIC (-4) /* Member is not dynamic */ + +static char *app = "Queue"; + +static char *synopsis = "Queue a call for a call queue"; + +static char *descrip = +" Queue(queuename[|options[|URL][|announceoverride][|timeout][|AGI]):\n" +"Queues an incoming call in a particular call queue as defined in queues.conf.\n" +"This application will return to the dialplan if the queue does not exist, or\n" +"any of the join options cause the caller to not enter the queue.\n" +"The option string may contain zero or more of the following characters:\n" +" 'd' -- data-quality (modem) call (minimum delay).\n" +" 'h' -- allow callee to hang up by hitting *.\n" +" 'H' -- allow caller to hang up by hitting *.\n" +" 'n' -- no retries on the timeout; will exit this application and \n" +" go to the next step.\n" +" 'i' -- ignore call forward requests from queue members and do nothing\n" +" when they are requested.\n" +" 'r' -- ring instead of playing MOH\n" +" 't' -- allow the called user transfer the calling user\n" +" 'T' -- to allow the calling user to transfer the call.\n" +" 'w' -- allow the called user to write the conversation to disk via Monitor\n" +" 'W' -- allow the calling user to write the conversation to disk via Monitor\n" +" In addition to transferring the call, a call may be parked and then picked\n" +"up by another user.\n" +" The optional URL will be sent to the called party if the channel supports\n" +"it.\n" +" The optional AGI parameter will setup an AGI script to be executed on the \n" +"calling party's channel once they are connected to a queue member.\n" +" The timeout will cause the queue to fail out after a specified number of\n" +"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n" +" This application sets the following channel variable upon completion:\n" +" QUEUESTATUS The status of the call as a text string, one of\n" +" TIMEOUT | FULL | JOINEMPTY | LEAVEEMPTY | JOINUNAVAIL | LEAVEUNAVAIL\n"; + +static char *app_aqm = "AddQueueMember" ; +static char *app_aqm_synopsis = "Dynamically adds queue members" ; +static char *app_aqm_descrip = +" AddQueueMember(queuename[|interface[|penalty[|options[|membername]]]]):\n" +"Dynamically adds interface to an existing queue.\n" +"If the interface is already in the queue and there exists an n+101 priority\n" +"then it will then jump to this priority. Otherwise it will return an error\n" +"The option string may contain zero or more of the following characters:\n" +" 'j' -- jump to +101 priority when appropriate.\n" +" This application sets the following channel variable upon completion:\n" +" AQMSTATUS The status of the attempt to add a queue member as a \n" +" text string, one of\n" +" ADDED | MEMBERALREADY | NOSUCHQUEUE \n" +"Example: AddQueueMember(techsupport|SIP/3000)\n" +""; + +static char *app_rqm = "RemoveQueueMember" ; +static char *app_rqm_synopsis = "Dynamically removes queue members" ; +static char *app_rqm_descrip = +" RemoveQueueMember(queuename[|interface[|options]]):\n" +"Dynamically removes interface to an existing queue\n" +"If the interface is NOT in the queue and there exists an n+101 priority\n" +"then it will then jump to this priority. Otherwise it will return an error\n" +"The option string may contain zero or more of the following characters:\n" +" 'j' -- jump to +101 priority when appropriate.\n" +" This application sets the following channel variable upon completion:\n" +" RQMSTATUS The status of the attempt to remove a queue member as a\n" +" text string, one of\n" +" REMOVED | NOTINQUEUE | NOSUCHQUEUE \n" +"Example: RemoveQueueMember(techsupport|SIP/3000)\n" +""; + +static char *app_pqm = "PauseQueueMember" ; +static char *app_pqm_synopsis = "Pauses a queue member" ; +static char *app_pqm_descrip = +" PauseQueueMember([queuename]|interface[|options]):\n" +"Pauses (blocks calls for) a queue member.\n" +"The given interface will be paused in the given queue. This prevents\n" +"any calls from being sent from the queue to the interface until it is\n" +"unpaused with UnpauseQueueMember or the manager interface. If no\n" +"queuename is given, the interface is paused in every queue it is a\n" +"member of. If the interface is not in the named queue, or if no queue\n" +"is given and the interface is not in any queue, it will jump to\n" +"priority n+101, if it exists and the appropriate options are set.\n" +"The application will fail if the interface is not found and no extension\n" +"to jump to exists.\n" +"The option string may contain zero or more of the following characters:\n" +" 'j' -- jump to +101 priority when appropriate.\n" +" This application sets the following channel variable upon completion:\n" +" PQMSTATUS The status of the attempt to pause a queue member as a\n" +" text string, one of\n" +" PAUSED | NOTFOUND\n" +"Example: PauseQueueMember(|SIP/3000)\n"; + +static char *app_upqm = "UnpauseQueueMember" ; +static char *app_upqm_synopsis = "Unpauses a queue member" ; +static char *app_upqm_descrip = +" UnpauseQueueMember([queuename]|interface[|options]):\n" +"Unpauses (resumes calls to) a queue member.\n" +"This is the counterpart to PauseQueueMember and operates exactly the\n" +"same way, except it unpauses instead of pausing the given interface.\n" +"The option string may contain zero or more of the following characters:\n" +" 'j' -- jump to +101 priority when appropriate.\n" +" This application sets the following channel variable upon completion:\n" +" UPQMSTATUS The status of the attempt to unpause a queue \n" +" member as a text string, one of\n" +" UNPAUSED | NOTFOUND\n" +"Example: UnpauseQueueMember(|SIP/3000)\n"; + +static char *app_ql = "QueueLog" ; +static char *app_ql_synopsis = "Writes to the queue_log" ; +static char *app_ql_descrip = +" QueueLog(queuename|uniqueid|agent|event[|additionalinfo]):\n" +"Allows you to write your own events into the queue log\n" +"Example: QueueLog(101|${UNIQUEID}|${AGENT}|WENTONBREAK|600)\n"; + +/*! \brief Persistent Members astdb family */ +static const char *pm_family = "Queue/PersistentMembers"; +/* The maximum length of each persistent member queue database entry */ +#define PM_MAX_LEN 8192 + +/*! \brief queues.conf [general] option */ +static int queue_persistent_members = 0; + +/*! \brief queues.conf per-queue weight option */ +static int use_weight = 0; + +/*! \brief queues.conf [general] option */ +static int autofill_default = 0; + +/*! \brief queues.conf [general] option */ +static int montype_default = 0; + +enum queue_result { + QUEUE_UNKNOWN = 0, + QUEUE_TIMEOUT = 1, + QUEUE_JOINEMPTY = 2, + QUEUE_LEAVEEMPTY = 3, + QUEUE_JOINUNAVAIL = 4, + QUEUE_LEAVEUNAVAIL = 5, + QUEUE_FULL = 6, +}; + +const struct { + enum queue_result id; + char *text; +} queue_results[] = { + { QUEUE_UNKNOWN, "UNKNOWN" }, + { QUEUE_TIMEOUT, "TIMEOUT" }, + { QUEUE_JOINEMPTY,"JOINEMPTY" }, + { QUEUE_LEAVEEMPTY, "LEAVEEMPTY" }, + { QUEUE_JOINUNAVAIL, "JOINUNAVAIL" }, + { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" }, + { QUEUE_FULL, "FULL" }, +}; + +/*! \brief We define a custom "local user" structure because we + use it not only for keeping track of what is in use but + also for keeping track of who we're dialing. + + There are two "links" defined in this structure, q_next and call_next. + q_next links ALL defined callattempt structures into a linked list. call_next is + a link which allows for a subset of the callattempts to be traversed. This subset + is used in wait_for_answer so that irrelevant callattempts are not traversed. This + also is helpful so that queue logs are always accurate in the case where a call to + a member times out, especially if using the ringall strategy. */ + +struct callattempt { + struct callattempt *q_next; + struct callattempt *call_next; + struct ast_channel *chan; + char interface[256]; + int stillgoing; + int metric; + int oldstatus; + time_t lastcall; + struct member *member; +}; + + +struct queue_ent { + struct call_queue *parent; /*!< What queue is our parent */ + char moh[80]; /*!< Name of musiconhold to be used */ + char announce[80]; /*!< Announcement to play for member when call is answered */ + char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */ + char digits[AST_MAX_EXTENSION]; /*!< Digits entered while in queue */ + int valid_digits; /*!< Digits entered correspond to valid extension. Exited */ + int pos; /*!< Where we are in the queue */ + int prio; /*!< Our priority */ + int last_pos_said; /*!< Last position we told the user */ + time_t last_periodic_announce_time; /*!< The last time we played a periodic announcement */ + int last_periodic_announce_sound; /*!< The last periodic announcement we made */ + time_t last_pos; /*!< Last time we told the user their position */ + int opos; /*!< Where we started in the queue */ + int handled; /*!< Whether our call was handled */ + int pending; /*!< Non-zero if we are attempting to call a member */ + int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */ + time_t start; /*!< When we started holding */ + time_t expire; /*!< When this entry should expire (time out of queue) */ + struct ast_channel *chan; /*!< Our channel */ + struct queue_ent *next; /*!< The next queue entry */ +}; + +struct member { + char interface[80]; /*!< Technology/Location */ + char membername[80]; /*!< Member name to use in queue logs */ + int penalty; /*!< Are we a last resort? */ + int calls; /*!< Number of calls serviced by this member */ + int dynamic; /*!< Are we dynamically added? */ + int realtime; /*!< Is this member realtime? */ + int status; /*!< Status of queue member */ + int paused; /*!< Are we paused (not accepting calls)? */ + time_t lastcall; /*!< When last successful call was hungup */ + unsigned int dead:1; /*!< Used to detect members deleted in realtime */ + unsigned int delme:1; /*!< Flag to delete entry on reload */ +}; + +struct member_interface { + char interface[80]; + AST_LIST_ENTRY(member_interface) list; /*!< Next call queue */ +}; + +static AST_LIST_HEAD_STATIC(interfaces, member_interface); + +/* values used in multi-bit flags in call_queue */ +#define QUEUE_EMPTY_NORMAL 1 +#define QUEUE_EMPTY_STRICT 2 +#define ANNOUNCEHOLDTIME_ALWAYS 1 +#define ANNOUNCEHOLDTIME_ONCE 2 +#define QUEUE_EVENT_VARIABLES 3 + +struct call_queue { + ast_mutex_t lock; + char name[80]; /*!< Name */ + char moh[80]; /*!< Music On Hold class to be used */ + char announce[80]; /*!< Announcement to play when call is answered */ + char context[AST_MAX_CONTEXT]; /*!< Exit context */ + unsigned int monjoin:1; + unsigned int dead:1; + unsigned int joinempty:2; + unsigned int eventwhencalled:2; + unsigned int leavewhenempty:2; + unsigned int ringinuse:1; + unsigned int setinterfacevar:1; + unsigned int reportholdtime:1; + unsigned int wrapped:1; + unsigned int timeoutrestart:1; + unsigned int announceholdtime:2; + int strategy:4; + unsigned int maskmemberstatus:1; + unsigned int realtime:1; + unsigned int found:1; + int announcefrequency; /*!< How often to announce their position */ + int periodicannouncefrequency; /*!< How often to play periodic announcement */ + int roundingseconds; /*!< How many seconds do we round to? */ + int holdtime; /*!< Current avg holdtime, based on an exponential average */ + int callscompleted; /*!< Number of queue calls completed */ + int callsabandoned; /*!< Number of queue calls abandoned */ + int servicelevel; /*!< seconds setting for servicelevel*/ + int callscompletedinsl; /*!< Number of calls answered with servicelevel*/ + char monfmt[8]; /*!< Format to use when recording calls */ + int montype; /*!< Monitor type Monitor vs. MixMonitor */ + char sound_next[80]; /*!< Sound file: "Your call is now first in line" (def. queue-youarenext) */ + char sound_thereare[80]; /*!< Sound file: "There are currently" (def. queue-thereare) */ + char sound_calls[80]; /*!< Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/ + char sound_holdtime[80]; /*!< Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */ + char sound_minutes[80]; /*!< Sound file: "minutes." (def. queue-minutes) */ + char sound_lessthan[80]; /*!< Sound file: "less-than" (def. queue-lessthan) */ + char sound_seconds[80]; /*!< Sound file: "seconds." (def. queue-seconds) */ + char sound_thanks[80]; /*!< Sound file: "Thank you for your patience." (def. queue-thankyou) */ + char sound_reporthold[80]; /*!< Sound file: "Hold time" (def. queue-reporthold) */ + char sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS][80];/*!< Sound files: Custom announce, no default */ + + int count; /*!< How many entries */ + int maxlen; /*!< Max number of entries */ + int wrapuptime; /*!< Wrapup Time */ + + int retry; /*!< Retry calling everyone after this amount of time */ + int timeout; /*!< How long to wait for an answer */ + int weight; /*!< Respective weight */ + int autopause; /*!< Auto pause queue members if they fail to answer */ + + /* Queue strategy things */ + int rrpos; /*!< Round Robin - position */ + int memberdelay; /*!< Seconds to delay connecting member to caller */ + int autofill; /*!< Ignore the head call status and ring an available agent */ + + struct ao2_container *members; /*!< Head of the list of members */ + /*! + * \brief Number of members _logged in_ + * \note There will be members in the members container that are not logged + * in, so this can not simply be replaced with ao2_container_count(). + */ + int membercount; + struct queue_ent *head; /*!< Head of the list of callers */ + AST_LIST_ENTRY(call_queue) list; /*!< Next call queue */ +}; + +static AST_LIST_HEAD_STATIC(queues, call_queue); + +static int set_member_paused(const char *queuename, const char *interface, int paused); + +static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); + +static void rr_dep_warning(void) +{ + static unsigned int warned = 0; + + if (!warned) { + ast_log(LOG_NOTICE, "The 'roundrobin' queue strategy is deprecated. Please use the 'rrmemory' strategy instead.\n"); + warned = 1; + } +} + +static void monjoin_dep_warning(void) +{ + static unsigned int warned = 0; + if (!warned) { + ast_log(LOG_NOTICE, "The 'monitor-join' queue option is deprecated. Please use monitor-type=mixmonitor instead.\n"); + warned = 1; + } +} +/*! \brief sets the QUEUESTATUS channel variable */ +static void set_queue_result(struct ast_channel *chan, enum queue_result res) +{ + int i; + + for (i = 0; i < sizeof(queue_results) / sizeof(queue_results[0]); i++) { + if (queue_results[i].id == res) { + pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text); + return; + } + } +} + +static char *int2strat(int strategy) +{ + int x; + + for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) { + if (strategy == strategies[x].strategy) + return strategies[x].name; + } + + return ""; +} + +static int strat2int(const char *strategy) +{ + int x; + + for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) { + if (!strcasecmp(strategy, strategies[x].name)) + return strategies[x].strategy; + } + + return -1; +} + +/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */ +static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos) +{ + struct queue_ent *cur; + + if (!q || !new) + return; + if (prev) { + cur = prev->next; + prev->next = new; + } else { + cur = q->head; + q->head = new; + } + new->next = cur; + new->parent = q; + new->pos = ++(*pos); + new->opos = *pos; +} + +enum queue_member_status { + QUEUE_NO_MEMBERS, + QUEUE_NO_REACHABLE_MEMBERS, + QUEUE_NORMAL +}; + +/*! \brief Check if members are available + * + * This function checks to see if members are available to be called. If any member + * is available, the function immediately returns QUEUE_NORMAL. If no members are available, + * the appropriate reason why is returned + */ +static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty) +{ + struct member *member; + struct ao2_iterator mem_iter; + enum queue_member_status result = QUEUE_NO_MEMBERS; + + ast_mutex_lock(&q->lock); + mem_iter = ao2_iterator_init(q->members, 0); + while ((member = ao2_iterator_next(&mem_iter))) { + if (max_penalty && (member->penalty > max_penalty)) { + ao2_ref(member, -1); + continue; + } + + if (member->paused) { + ao2_ref(member, -1); + continue; + } + + switch (member->status) { + case AST_DEVICE_INVALID: + /* nothing to do */ + ao2_ref(member, -1); + break; + case AST_DEVICE_UNAVAILABLE: + result = QUEUE_NO_REACHABLE_MEMBERS; + ao2_ref(member, -1); + break; + default: + ast_mutex_unlock(&q->lock); + ao2_ref(member, -1); + return QUEUE_NORMAL; + } + } + + ast_mutex_unlock(&q->lock); + return result; +} + +struct statechange { + AST_LIST_ENTRY(statechange) entry; + int state; + char dev[0]; +}; + +static int update_status(const char *interface, const int status) +{ + struct member *cur; + struct ao2_iterator mem_iter; + struct call_queue *q; + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + mem_iter = ao2_iterator_init(q->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + char *tmp_interface; + char *slash_pos; + tmp_interface = ast_strdupa(cur->interface); + if ((slash_pos = strchr(tmp_interface, '/'))) + if ((slash_pos = strchr(slash_pos + 1, '/'))) + *slash_pos = '\0'; + + if (strcasecmp(interface, tmp_interface)) { + ao2_ref(cur, -1); + continue; + } + + if (cur->status != status) { + cur->status = status; + if (q->maskmemberstatus) { + ao2_ref(cur, -1); + continue; + } + + manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus", + "Queue: %s\r\n" + "Location: %s\r\n" + "MemberName: %s\r\n" + "Membership: %s\r\n" + "Penalty: %d\r\n" + "CallsTaken: %d\r\n" + "LastCall: %d\r\n" + "Status: %d\r\n" + "Paused: %d\r\n", + q->name, cur->interface, cur->membername, cur->dynamic ? "dynamic" : cur->realtime ? "realtime" : "static", + cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused); + } + ao2_ref(cur, -1); + } + ast_mutex_unlock(&q->lock); + } + AST_LIST_UNLOCK(&queues); + + return 0; +} + +/*! \brief set a member's status based on device state of that member's interface*/ +static void *handle_statechange(struct statechange *sc) +{ + struct member_interface *curint; + char *loc; + char *technology; + + technology = ast_strdupa(sc->dev); + loc = strchr(technology, '/'); + if (loc) { + *loc++ = '\0'; + } else { + return NULL; + } + + AST_LIST_LOCK(&interfaces); + AST_LIST_TRAVERSE(&interfaces, curint, list) { + char *interface; + char *slash_pos; + interface = ast_strdupa(curint->interface); + if ((slash_pos = strchr(interface, '/'))) + if ((slash_pos = strchr(slash_pos + 1, '/'))) + *slash_pos = '\0'; + + if (!strcasecmp(interface, sc->dev)) + break; + } + AST_LIST_UNLOCK(&interfaces); + + if (!curint) { + if (option_debug > 2) + ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", technology, loc, sc->state, devstate2str(sc->state)); + return NULL; + } + + if (option_debug) + ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state)); + + update_status(sc->dev, sc->state); + + return NULL; +} + +/*! + * \brief Data used by the device state thread + */ +static struct { + /*! Set to 1 to stop the thread */ + unsigned int stop:1; + /*! The device state monitoring thread */ + pthread_t thread; + /*! Lock for the state change queue */ + ast_mutex_t lock; + /*! Condition for the state change queue */ + ast_cond_t cond; + /*! Queue of state changes */ + AST_LIST_HEAD_NOLOCK(, statechange) state_change_q; +} device_state = { + .thread = AST_PTHREADT_NULL, +}; + +/*! \brief Consumer of the statechange queue */ +static void *device_state_thread(void *data) +{ + struct statechange *sc = NULL; + + while (!device_state.stop) { + ast_mutex_lock(&device_state.lock); + if (!(sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) { + ast_cond_wait(&device_state.cond, &device_state.lock); + sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry); + } + ast_mutex_unlock(&device_state.lock); + + /* Check to see if we were woken up to see the request to stop */ + if (device_state.stop) + break; + + if (!sc) + continue; + + handle_statechange(sc); + + free(sc); + sc = NULL; + } + + if (sc) + free(sc); + + while ((sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) + free(sc); + + return NULL; +} +/*! \brief Producer of the statechange queue */ +static int statechange_queue(const char *dev, int state, void *ign) +{ + struct statechange *sc; + + if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(dev) + 1))) + return 0; + + sc->state = state; + strcpy(sc->dev, dev); + + ast_mutex_lock(&device_state.lock); + AST_LIST_INSERT_TAIL(&device_state.state_change_q, sc, entry); + ast_cond_signal(&device_state.cond); + ast_mutex_unlock(&device_state.lock); + + return 0; +} +/*! \brief allocate space for new queue member and set fields based on parameters passed */ +static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused) +{ + struct member *cur; + + if ((cur = ao2_alloc(sizeof(*cur), NULL))) { + cur->penalty = penalty; + cur->paused = paused; + ast_copy_string(cur->interface, interface, sizeof(cur->interface)); + if (!ast_strlen_zero(membername)) + ast_copy_string(cur->membername, membername, sizeof(cur->membername)); + else + ast_copy_string(cur->membername, interface, sizeof(cur->membername)); + if (!strchr(cur->interface, '/')) + ast_log(LOG_WARNING, "No location at interface '%s'\n", interface); + cur->status = ast_device_state(interface); + } + + return cur; +} + +static struct call_queue *alloc_queue(const char *queuename) +{ + struct call_queue *q; + + if ((q = ast_calloc(1, sizeof(*q)))) { + ast_mutex_init(&q->lock); + ast_copy_string(q->name, queuename, sizeof(q->name)); + } + return q; +} + +static int compress_char(const char c) +{ + if (c < 32) + return 0; + else if (c > 96) + return c - 64; + else + return c - 32; +} + +static int member_hash_fn(const void *obj, const int flags) +{ + const struct member *mem = obj; + const char *chname = strchr(mem->interface, '/'); + int ret = 0, i; + if (!chname) + chname = mem->interface; + for (i = 0; i < 5 && chname[i]; i++) + ret += compress_char(chname[i]) << (i * 6); + return ret; +} + +static int member_cmp_fn(void *obj1, void *obj2, int flags) +{ + struct member *mem1 = obj1, *mem2 = obj2; + return strcmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH | CMP_STOP; +} + +static void init_queue(struct call_queue *q) +{ + int i; + + q->dead = 0; + q->retry = DEFAULT_RETRY; + q->timeout = -1; + q->maxlen = 0; + q->announcefrequency = 0; + q->announceholdtime = 0; + q->roundingseconds = 0; /* Default - don't announce seconds */ + q->servicelevel = 0; + q->ringinuse = 1; + q->setinterfacevar = 0; + q->autofill = autofill_default; + q->montype = montype_default; + q->moh[0] = '\0'; + q->announce[0] = '\0'; + q->context[0] = '\0'; + q->monfmt[0] = '\0'; + q->periodicannouncefrequency = 0; + q->reportholdtime = 0; + q->monjoin = 0; + q->wrapuptime = 0; + q->joinempty = 0; + q->leavewhenempty = 0; + q->memberdelay = 0; + q->maskmemberstatus = 0; + q->eventwhencalled = 0; + q->weight = 0; + q->timeoutrestart = 0; + if (!q->members) + q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn); + q->membercount = 0; + q->found = 1; + ast_copy_string(q->sound_next, "queue-youarenext", sizeof(q->sound_next)); + ast_copy_string(q->sound_thereare, "queue-thereare", sizeof(q->sound_thereare)); + ast_copy_string(q->sound_calls, "queue-callswaiting", sizeof(q->sound_calls)); + ast_copy_string(q->sound_holdtime, "queue-holdtime", sizeof(q->sound_holdtime)); + ast_copy_string(q->sound_minutes, "queue-minutes", sizeof(q->sound_minutes)); + ast_copy_string(q->sound_seconds, "queue-seconds", sizeof(q->sound_seconds)); + ast_copy_string(q->sound_thanks, "queue-thankyou", sizeof(q->sound_thanks)); + ast_copy_string(q->sound_lessthan, "queue-less-than", sizeof(q->sound_lessthan)); + ast_copy_string(q->sound_reporthold, "queue-reporthold", sizeof(q->sound_reporthold)); + ast_copy_string(q->sound_periodicannounce[0], "queue-periodic-announce", sizeof(q->sound_periodicannounce[0])); + for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) { + q->sound_periodicannounce[i][0]='\0'; + } +} + +static void clear_queue(struct call_queue *q) +{ + q->holdtime = 0; + q->callscompleted = 0; + q->callsabandoned = 0; + q->callscompletedinsl = 0; + q->wrapuptime = 0; +} + +static int add_to_interfaces(const char *interface) +{ + struct member_interface *curint; + + AST_LIST_LOCK(&interfaces); + AST_LIST_TRAVERSE(&interfaces, curint, list) { + if (!strcasecmp(curint->interface, interface)) + break; + } + + if (curint) { + AST_LIST_UNLOCK(&interfaces); + return 0; + } + + if (option_debug) + ast_log(LOG_DEBUG, "Adding %s to the list of interfaces that make up all of our queue members.\n", interface); + + if ((curint = ast_calloc(1, sizeof(*curint)))) { + ast_copy_string(curint->interface, interface, sizeof(curint->interface)); + AST_LIST_INSERT_HEAD(&interfaces, curint, list); + } + AST_LIST_UNLOCK(&interfaces); + + return 0; +} + +static int interface_exists_global(const char *interface) +{ + struct call_queue *q; + struct member *mem, tmpmem; + int ret = 0; + + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { + ao2_ref(mem, -1); + ret = 1; + } + ast_mutex_unlock(&q->lock); + if (ret) + break; + } + AST_LIST_UNLOCK(&queues); + + return ret; +} + +static int remove_from_interfaces(const char *interface) +{ + struct member_interface *curint; + + if (interface_exists_global(interface)) + return 0; + + AST_LIST_LOCK(&interfaces); + AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) { + if (!strcasecmp(curint->interface, interface)) { + if (option_debug) + ast_log(LOG_DEBUG, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface); + AST_LIST_REMOVE_CURRENT(&interfaces, list); + free(curint); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&interfaces); + + return 0; +} + +static void clear_and_free_interfaces(void) +{ + struct member_interface *curint; + + AST_LIST_LOCK(&interfaces); + while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list))) + free(curint); + AST_LIST_UNLOCK(&interfaces); +} + +/*! \brief Configure a queue parameter. +\par + For error reporting, line number is passed for .conf static configuration. + For Realtime queues, linenum is -1. + The failunknown flag is set for config files (and static realtime) to show + errors for unknown parameters. It is cleared for dynamic realtime to allow + extra fields in the tables. */ +static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown) +{ + if (!strcasecmp(param, "musicclass") || + !strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) { + ast_copy_string(q->moh, val, sizeof(q->moh)); + } else if (!strcasecmp(param, "announce")) { + ast_copy_string(q->announce, val, sizeof(q->announce)); + } else if (!strcasecmp(param, "context")) { + ast_copy_string(q->context, val, sizeof(q->context)); + } else if (!strcasecmp(param, "timeout")) { + q->timeout = atoi(val); + if (q->timeout < 0) + q->timeout = DEFAULT_TIMEOUT; + } else if (!strcasecmp(param, "ringinuse")) { + q->ringinuse = ast_true(val); + } else if (!strcasecmp(param, "setinterfacevar")) { + q->setinterfacevar = ast_true(val); + } else if (!strcasecmp(param, "monitor-join")) { + monjoin_dep_warning(); + q->monjoin = ast_true(val); + } else if (!strcasecmp(param, "monitor-format")) { + ast_copy_string(q->monfmt, val, sizeof(q->monfmt)); + } else if (!strcasecmp(param, "queue-youarenext")) { + ast_copy_string(q->sound_next, val, sizeof(q->sound_next)); + } else if (!strcasecmp(param, "queue-thereare")) { + ast_copy_string(q->sound_thereare, val, sizeof(q->sound_thereare)); + } else if (!strcasecmp(param, "queue-callswaiting")) { + ast_copy_string(q->sound_calls, val, sizeof(q->sound_calls)); + } else if (!strcasecmp(param, "queue-holdtime")) { + ast_copy_string(q->sound_holdtime, val, sizeof(q->sound_holdtime)); + } else if (!strcasecmp(param, "queue-minutes")) { + ast_copy_string(q->sound_minutes, val, sizeof(q->sound_minutes)); + } else if (!strcasecmp(param, "queue-seconds")) { + ast_copy_string(q->sound_seconds, val, sizeof(q->sound_seconds)); + } else if (!strcasecmp(param, "queue-lessthan")) { + ast_copy_string(q->sound_lessthan, val, sizeof(q->sound_lessthan)); + } else if (!strcasecmp(param, "queue-thankyou")) { + ast_copy_string(q->sound_thanks, val, sizeof(q->sound_thanks)); + } else if (!strcasecmp(param, "queue-reporthold")) { + ast_copy_string(q->sound_reporthold, val, sizeof(q->sound_reporthold)); + } else if (!strcasecmp(param, "announce-frequency")) { + q->announcefrequency = atoi(val); + } else if (!strcasecmp(param, "announce-round-seconds")) { + q->roundingseconds = atoi(val); + if (q->roundingseconds>60 || q->roundingseconds<0) { + if (linenum >= 0) { + ast_log(LOG_WARNING, "'%s' isn't a valid value for %s " + "using 0 instead for queue '%s' at line %d of queues.conf\n", + val, param, q->name, linenum); + } else { + ast_log(LOG_WARNING, "'%s' isn't a valid value for %s " + "using 0 instead for queue '%s'\n", val, param, q->name); + } + q->roundingseconds=0; + } + } else if (!strcasecmp(param, "announce-holdtime")) { + if (!strcasecmp(val, "once")) + q->announceholdtime = ANNOUNCEHOLDTIME_ONCE; + else if (ast_true(val)) + q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS; + else + q->announceholdtime = 0; + } else if (!strcasecmp(param, "periodic-announce")) { + if (strchr(val, '|')) { + char *s, *buf = ast_strdupa(val); + unsigned int i = 0; + + while ((s = strsep(&buf, "|"))) { + ast_copy_string(q->sound_periodicannounce[i], s, sizeof(q->sound_periodicannounce[i])); + i++; + if (i == MAX_PERIODIC_ANNOUNCEMENTS) + break; + } + } else { + ast_copy_string(q->sound_periodicannounce[0], val, sizeof(q->sound_periodicannounce[0])); + } + } else if (!strcasecmp(param, "periodic-announce-frequency")) { + q->periodicannouncefrequency = atoi(val); + } else if (!strcasecmp(param, "retry")) { + q->retry = atoi(val); + if (q->retry <= 0) + q->retry = DEFAULT_RETRY; + } else if (!strcasecmp(param, "wrapuptime")) { + q->wrapuptime = atoi(val); + } else if (!strcasecmp(param, "autofill")) { + q->autofill = ast_true(val); + } else if (!strcasecmp(param, "monitor-type")) { + if (!strcasecmp(val, "mixmonitor")) + q->montype = 1; + } else if (!strcasecmp(param, "autopause")) { + q->autopause = ast_true(val); + } else if (!strcasecmp(param, "maxlen")) { + q->maxlen = atoi(val); + if (q->maxlen < 0) + q->maxlen = 0; + } else if (!strcasecmp(param, "servicelevel")) { + q->servicelevel= atoi(val); + } else if (!strcasecmp(param, "strategy")) { + q->strategy = strat2int(val); + if (q->strategy < 0) { + ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n", + val, q->name); + q->strategy = QUEUE_STRATEGY_RINGALL; + } + } else if (!strcasecmp(param, "joinempty")) { + if (!strcasecmp(val, "strict")) + q->joinempty = QUEUE_EMPTY_STRICT; + else if (ast_true(val)) + q->joinempty = QUEUE_EMPTY_NORMAL; + else + q->joinempty = 0; + } else if (!strcasecmp(param, "leavewhenempty")) { + if (!strcasecmp(val, "strict")) + q->leavewhenempty = QUEUE_EMPTY_STRICT; + else if (ast_true(val)) + q->leavewhenempty = QUEUE_EMPTY_NORMAL; + else + q->leavewhenempty = 0; + } else if (!strcasecmp(param, "eventmemberstatus")) { + q->maskmemberstatus = !ast_true(val); + } else if (!strcasecmp(param, "eventwhencalled")) { + if (!strcasecmp(val, "vars")) { + q->eventwhencalled = QUEUE_EVENT_VARIABLES; + } else { + q->eventwhencalled = ast_true(val) ? 1 : 0; + } + } else if (!strcasecmp(param, "reportholdtime")) { + q->reportholdtime = ast_true(val); + } else if (!strcasecmp(param, "memberdelay")) { + q->memberdelay = atoi(val); + } else if (!strcasecmp(param, "weight")) { + q->weight = atoi(val); + if (q->weight) + use_weight++; + /* With Realtime queues, if the last queue using weights is deleted in realtime, + we will not see any effect on use_weight until next reload. */ + } else if (!strcasecmp(param, "timeoutrestart")) { + q->timeoutrestart = ast_true(val); + } else if (failunknown) { + if (linenum >= 0) { + ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n", + q->name, param, linenum); + } else { + ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param); + } + } +} + +static void rt_handle_member_record(struct call_queue *q, char *interface, const char *membername, const char *penalty_str, const char *paused_str) +{ + struct member *m, tmpmem; + int penalty = 0; + int paused = 0; + + if (penalty_str) { + penalty = atoi(penalty_str); + if (penalty < 0) + penalty = 0; + } + + if (paused_str) { + paused = atoi(paused_str); + if (paused < 0) + paused = 0; + } + + /* Find the member, or the place to put a new one. */ + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + m = ao2_find(q->members, &tmpmem, OBJ_POINTER); + + /* Create a new one if not found, else update penalty */ + if (!m) { + if ((m = create_queue_member(interface, membername, penalty, paused))) { + m->dead = 0; + m->realtime = 1; + add_to_interfaces(interface); + ao2_link(q->members, m); + ao2_ref(m, -1); + m = NULL; + q->membercount++; + } + } else { + m->dead = 0; /* Do not delete this one. */ + if (paused_str) + m->paused = paused; + m->penalty = penalty; + ao2_ref(m, -1); + } +} + +static void free_members(struct call_queue *q, int all) +{ + /* Free non-dynamic members */ + struct member *cur; + struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0); + + while ((cur = ao2_iterator_next(&mem_iter))) { + if (all || !cur->dynamic) { + ao2_unlink(q->members, cur); + remove_from_interfaces(cur->interface); + q->membercount--; + } + ao2_ref(cur, -1); + } +} + +static void destroy_queue(struct call_queue *q) +{ + free_members(q, 1); + ast_mutex_destroy(&q->lock); + ao2_ref(q->members, -1); + free(q); +} + +/*!\brief Reload a single queue via realtime. + \return Return the queue, or NULL if it doesn't exist. + \note Should be called with the global qlock locked. */ +static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config) +{ + struct ast_variable *v; + struct call_queue *q; + struct member *m; + struct ao2_iterator mem_iter; + char *interface = NULL; + char *tmp, *tmp_name; + char tmpbuf[64]; /* Must be longer than the longest queue param name. */ + + /* Find the queue in the in-core list (we will create a new one if not found). */ + AST_LIST_TRAVERSE(&queues, q, list) { + if (!strcasecmp(q->name, queuename)) + break; + } + + /* Static queues override realtime. */ + if (q) { + ast_mutex_lock(&q->lock); + if (!q->realtime) { + if (q->dead) { + ast_mutex_unlock(&q->lock); + return NULL; + } else { + ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name); + ast_mutex_unlock(&q->lock); + return q; + } + } + } else if (!member_config) + /* Not found in the list, and it's not realtime ... */ + return NULL; + + /* Check if queue is defined in realtime. */ + if (!queue_vars) { + /* Delete queue from in-core list if it has been deleted in realtime. */ + if (q) { + /*! \note Hmm, can't seem to distinguish a DB failure from a not + found condition... So we might delete an in-core queue + in case of DB failure. */ + ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename); + + q->dead = 1; + /* Delete if unused (else will be deleted when last caller leaves). */ + if (!q->count) { + /* Delete. */ + AST_LIST_REMOVE(&queues, q, list); + ast_mutex_unlock(&q->lock); + destroy_queue(q); + } else + ast_mutex_unlock(&q->lock); + } + return NULL; + } + + /* Create a new queue if an in-core entry does not exist yet. */ + if (!q) { + if (!(q = alloc_queue(queuename))) + return NULL; + ast_mutex_lock(&q->lock); + clear_queue(q); + q->realtime = 1; + AST_LIST_INSERT_HEAD(&queues, q, list); + } + init_queue(q); /* Ensure defaults for all parameters not set explicitly. */ + + memset(tmpbuf, 0, sizeof(tmpbuf)); + for (v = queue_vars; v; v = v->next) { + /* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */ + if ((tmp = strchr(v->name, '_'))) { + ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf)); + tmp_name = tmpbuf; + tmp = tmp_name; + while ((tmp = strchr(tmp, '_'))) + *tmp++ = '-'; + } else + tmp_name = v->name; + + if (!ast_strlen_zero(v->value)) { + /* Don't want to try to set the option if the value is empty */ + queue_set_param(q, tmp_name, v->value, -1, 0); + } + } + + if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN) + rr_dep_warning(); + + /* Temporarily set realtime members dead so we can detect deleted ones. + * Also set the membercount correctly for realtime*/ + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + q->membercount++; + if (m->realtime) + m->dead = 1; + ao2_ref(m, -1); + } + + while ((interface = ast_category_browse(member_config, interface))) { + rt_handle_member_record(q, interface, + ast_variable_retrieve(member_config, interface, "membername"), + ast_variable_retrieve(member_config, interface, "penalty"), + ast_variable_retrieve(member_config, interface, "paused")); + } + + /* Delete all realtime members that have been deleted in DB. */ + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + if (m->dead) { + ao2_unlink(q->members, m); + ast_mutex_unlock(&q->lock); + remove_from_interfaces(m->interface); + ast_mutex_lock(&q->lock); + q->membercount--; + } + ao2_ref(m, -1); + } + + ast_mutex_unlock(&q->lock); + + return q; +} + +static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value) +{ + struct ast_variable *var; + int ret = -1; + + if (!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL))) + return ret; + while (var) { + if (!strcmp(var->name, "uniqueid")) + break; + var = var->next; + } + if (var && !ast_strlen_zero(var->value)) { + if ((ast_update_realtime("queue_members", "uniqueid", var->value, field, value, NULL)) > -1) + ret = 0; + } + return ret; +} + +static void update_realtime_members(struct call_queue *q) +{ + struct ast_config *member_config = NULL; + struct member *m; + char *interface = NULL; + struct ao2_iterator mem_iter; + + if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL))) { + /*This queue doesn't have realtime members*/ + if (option_debug > 2) + ast_log(LOG_DEBUG, "Queue %s has no realtime members defined. No need for update\n", q->name); + return; + } + + ast_mutex_lock(&q->lock); + + /* Temporarily set realtime members dead so we can detect deleted ones.*/ + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + if (m->realtime) + m->dead = 1; + ao2_ref(m, -1); + } + + while ((interface = ast_category_browse(member_config, interface))) { + rt_handle_member_record(q, interface, + S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface), + ast_variable_retrieve(member_config, interface, "penalty"), + ast_variable_retrieve(member_config, interface, "paused")); + } + + /* Delete all realtime members that have been deleted in DB. */ + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + if (m->dead) { + ao2_unlink(q->members, m); + ast_mutex_unlock(&q->lock); + remove_from_interfaces(m->interface); + ast_mutex_lock(&q->lock); + q->membercount--; + } + ao2_ref(m, -1); + } + ast_mutex_unlock(&q->lock); + ast_config_destroy(member_config); +} + +static struct call_queue *load_realtime_queue(const char *queuename) +{ + struct ast_variable *queue_vars; + struct ast_config *member_config = NULL; + struct call_queue *q; + + /* Find the queue in the in-core list first. */ + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + if (!strcasecmp(q->name, queuename)) { + break; + } + } + AST_LIST_UNLOCK(&queues); + + if (!q || q->realtime) { + /*! \note Load from realtime before taking the global qlock, to avoid blocking all + queue operations while waiting for the DB. + + This will be two separate database transactions, so we might + see queue parameters as they were before another process + changed the queue and member list as it was after the change. + Thus we might see an empty member list when a queue is + deleted. In practise, this is unlikely to cause a problem. */ + + queue_vars = ast_load_realtime("queues", "name", queuename, NULL); + if (queue_vars) { + member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL); + if (!member_config) { + ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n"); + ast_variables_destroy(queue_vars); + return NULL; + } + } + + AST_LIST_LOCK(&queues); + + q = find_queue_by_name_rt(queuename, queue_vars, member_config); + if (member_config) + ast_config_destroy(member_config); + if (queue_vars) + ast_variables_destroy(queue_vars); + + AST_LIST_UNLOCK(&queues); + } else { + update_realtime_members(q); + } + return q; +} + +static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason) +{ + struct call_queue *q; + struct queue_ent *cur, *prev = NULL; + int res = -1; + int pos = 0; + int inserted = 0; + enum queue_member_status stat; + + if (!(q = load_realtime_queue(queuename))) + return res; + + AST_LIST_LOCK(&queues); + ast_mutex_lock(&q->lock); + + /* This is our one */ + stat = get_member_status(q, qe->max_penalty); + if (!q->joinempty && (stat == QUEUE_NO_MEMBERS)) + *reason = QUEUE_JOINEMPTY; + else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS)) + *reason = QUEUE_JOINUNAVAIL; + else if (q->maxlen && (q->count >= q->maxlen)) + *reason = QUEUE_FULL; + else { + /* There's space for us, put us at the right position inside + * the queue. + * Take into account the priority of the calling user */ + inserted = 0; + prev = NULL; + cur = q->head; + while (cur) { + /* We have higher priority than the current user, enter + * before him, after all the other users with priority + * higher or equal to our priority. */ + if ((!inserted) && (qe->prio > cur->prio)) { + insert_entry(q, prev, qe, &pos); + inserted = 1; + } + cur->pos = ++pos; + prev = cur; + cur = cur->next; + } + /* No luck, join at the end of the queue */ + if (!inserted) + insert_entry(q, prev, qe, &pos); + ast_copy_string(qe->moh, q->moh, sizeof(qe->moh)); + ast_copy_string(qe->announce, q->announce, sizeof(qe->announce)); + ast_copy_string(qe->context, q->context, sizeof(qe->context)); + q->count++; + res = 0; + manager_event(EVENT_FLAG_CALL, "Join", + "Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\nUniqueid: %s\r\n", + qe->chan->name, + S_OR(qe->chan->cid.cid_num, "unknown"), /* XXX somewhere else it is */ + S_OR(qe->chan->cid.cid_name, "unknown"), + q->name, qe->pos, q->count, qe->chan->uniqueid ); + if (option_debug) + ast_log(LOG_DEBUG, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos ); + } + ast_mutex_unlock(&q->lock); + AST_LIST_UNLOCK(&queues); + + return res; +} + +static int play_file(struct ast_channel *chan, char *filename) +{ + int res; + + ast_stopstream(chan); + + res = ast_streamfile(chan, filename, chan->language); + if (!res) + res = ast_waitstream(chan, AST_DIGIT_ANY); + + ast_stopstream(chan); + + return res; +} + +static int valid_exit(struct queue_ent *qe, char digit) +{ + int digitlen = strlen(qe->digits); + + /* Prevent possible buffer overflow */ + if (digitlen < sizeof(qe->digits) - 2) { + qe->digits[digitlen] = digit; + qe->digits[digitlen + 1] = '\0'; + } else { + qe->digits[0] = '\0'; + return 0; + } + + /* If there's no context to goto, short-circuit */ + if (ast_strlen_zero(qe->context)) + return 0; + + /* If the extension is bad, then reset the digits to blank */ + if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1, qe->chan->cid.cid_num)) { + qe->digits[0] = '\0'; + return 0; + } + + /* We have an exact match */ + if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) { + qe->valid_digits = 1; + /* Return 1 on a successful goto */ + return 1; + } + + return 0; +} + +static int say_position(struct queue_ent *qe) +{ + int res = 0, avgholdmins, avgholdsecs; + time_t now; + + /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/ + time(&now); + if ((now - qe->last_pos) < 15) + return 0; + + /* If either our position has changed, or we are over the freq timer, say position */ + if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency)) + return 0; + + ast_moh_stop(qe->chan); + /* Say we're next, if we are */ + if (qe->pos == 1) { + res = play_file(qe->chan, qe->parent->sound_next); + if (res) + goto playout; + else + goto posout; + } else { + res = play_file(qe->chan, qe->parent->sound_thereare); + if (res) + goto playout; + res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */ + if (res) + goto playout; + res = play_file(qe->chan, qe->parent->sound_calls); + if (res) + goto playout; + } + /* Round hold time to nearest minute */ + avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60); + + /* If they have specified a rounding then round the seconds as well */ + if (qe->parent->roundingseconds) { + avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds; + avgholdsecs *= qe->parent->roundingseconds; + } else { + avgholdsecs = 0; + } + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs); + + /* If the hold time is >1 min, if it's enabled, and if it's not + supposed to be only once and we have already said it, say it */ + if ((avgholdmins+avgholdsecs) > 0 && (qe->parent->announceholdtime) && + (!(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE) && qe->last_pos)) { + res = play_file(qe->chan, qe->parent->sound_holdtime); + if (res) + goto playout; + + if (avgholdmins > 0) { + if (avgholdmins < 2) { + res = play_file(qe->chan, qe->parent->sound_lessthan); + if (res) + goto playout; + + res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL); + if (res) + goto playout; + } else { + res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL); + if (res) + goto playout; + } + + res = play_file(qe->chan, qe->parent->sound_minutes); + if (res) + goto playout; + } + if (avgholdsecs>0) { + res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL); + if (res) + goto playout; + + res = play_file(qe->chan, qe->parent->sound_seconds); + if (res) + goto playout; + } + + } + +posout: + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n", + qe->chan->name, qe->parent->name, qe->pos); + res = play_file(qe->chan, qe->parent->sound_thanks); + +playout: + if ((res > 0 && !valid_exit(qe, res)) || res < 0) + res = 0; + + /* Set our last_pos indicators */ + qe->last_pos = now; + qe->last_pos_said = qe->pos; + + /* Don't restart music on hold if we're about to exit the caller from the queue */ + if (!res) + ast_moh_start(qe->chan, qe->moh, NULL); + + return res; +} + +static void recalc_holdtime(struct queue_ent *qe, int newholdtime) +{ + int oldvalue; + + /* Calculate holdtime using an exponential average */ + /* Thanks to SRT for this contribution */ + /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */ + + ast_mutex_lock(&qe->parent->lock); + oldvalue = qe->parent->holdtime; + qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2; + ast_mutex_unlock(&qe->parent->lock); +} + + +static void leave_queue(struct queue_ent *qe) +{ + struct call_queue *q; + struct queue_ent *cur, *prev = NULL; + int pos = 0; + + if (!(q = qe->parent)) + return; + ast_mutex_lock(&q->lock); + + prev = NULL; + for (cur = q->head; cur; cur = cur->next) { + if (cur == qe) { + q->count--; + + /* Take us out of the queue */ + manager_event(EVENT_FLAG_CALL, "Leave", + "Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n", + qe->chan->name, q->name, q->count, qe->chan->uniqueid); + if (option_debug) + ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name ); + /* Take us out of the queue */ + if (prev) + prev->next = cur->next; + else + q->head = cur->next; + } else { + /* Renumber the people after us in the queue based on a new count */ + cur->pos = ++pos; + prev = cur; + } + } + ast_mutex_unlock(&q->lock); + + if (q->dead && !q->count) { + /* It's dead and nobody is in it, so kill it */ + AST_LIST_LOCK(&queues); + AST_LIST_REMOVE(&queues, q, list); + AST_LIST_UNLOCK(&queues); + destroy_queue(q); + } +} + +/* Hang up a list of outgoing calls */ +static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception) +{ + struct callattempt *oo; + + while (outgoing) { + /* Hangup any existing lines we have open */ + if (outgoing->chan && (outgoing->chan != exception)) + ast_hangup(outgoing->chan); + oo = outgoing; + outgoing = outgoing->q_next; + if (oo->member) + ao2_ref(oo->member, -1); + free(oo); + } +} + + +/* traverse all defined queues which have calls waiting and contain this member + return 0 if no other queue has precedence (higher weight) or 1 if found */ +static int compare_weight(struct call_queue *rq, struct member *member) +{ + struct call_queue *q; + struct member *mem; + int found = 0; + + /* &qlock and &rq->lock already set by try_calling() + * to solve deadlock */ + AST_LIST_TRAVERSE(&queues, q, list) { + if (q == rq) /* don't check myself, could deadlock */ + continue; + ast_mutex_lock(&q->lock); + if (q->count && q->members) { + if ((mem = ao2_find(q->members, member, OBJ_POINTER))) { + ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name); + if (q->weight > rq->weight) { + ast_log(LOG_DEBUG, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count); + found = 1; + } + ao2_ref(mem, -1); + } + } + ast_mutex_unlock(&q->lock); + if (found) + break; + } + return found; +} + +/*! \brief common hangup actions */ +static void do_hang(struct callattempt *o) +{ + o->stillgoing = 0; + ast_hangup(o->chan); + o->chan = NULL; +} + +static char *vars2manager(struct ast_channel *chan, char *vars, size_t len) +{ + char *tmp = alloca(len); + + if (pbx_builtin_serialize_variables(chan, tmp, len)) { + int i, j; + + /* convert "\n" to "\nVariable: " */ + strcpy(vars, "Variable: "); + + for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) { + vars[j] = tmp[i]; + + if (tmp[i + 1] == '\0') + break; + if (tmp[i] == '\n') { + vars[j++] = '\r'; + vars[j++] = '\n'; + + ast_copy_string(&(vars[j]), "Variable: ", len - j); + j += 9; + } + } + if (j > len - 3) + j = len - 3; + vars[j++] = '\r'; + vars[j++] = '\n'; + vars[j] = '\0'; + } else { + /* there are no channel variables; leave it blank */ + *vars = '\0'; + } + return vars; +} + +/*! \brief Part 2 of ring_one + * + * Does error checking before attempting to request a channel and call a member. This + * function is only called from ring_one + */ +static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies) +{ + int res; + int status; + char tech[256]; + char *location; + const char *macrocontext, *macroexten; + + /* on entry here, we know that tmp->chan == NULL */ + if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) { + if (option_debug) + ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface); + if (qe->chan->cdr) + ast_cdr_busy(qe->chan->cdr); + tmp->stillgoing = 0; + (*busies)++; + return 0; + } + + if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) { + if (option_debug) + ast_log(LOG_DEBUG, "%s in use, can't receive call\n", tmp->interface); + if (qe->chan->cdr) + ast_cdr_busy(qe->chan->cdr); + tmp->stillgoing = 0; + return 0; + } + + if (tmp->member->paused) { + if (option_debug) + ast_log(LOG_DEBUG, "%s paused, can't receive call\n", tmp->interface); + if (qe->chan->cdr) + ast_cdr_busy(qe->chan->cdr); + tmp->stillgoing = 0; + return 0; + } + if (use_weight && compare_weight(qe->parent,tmp->member)) { + ast_log(LOG_DEBUG, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface); + if (qe->chan->cdr) + ast_cdr_busy(qe->chan->cdr); + tmp->stillgoing = 0; + (*busies)++; + return 0; + } + + ast_copy_string(tech, tmp->interface, sizeof(tech)); + if ((location = strchr(tech, '/'))) + *location++ = '\0'; + else + location = ""; + + /* Request the peer */ + tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status); + if (!tmp->chan) { /* If we can't, just go on to the next call */ + if (qe->chan->cdr) + ast_cdr_busy(qe->chan->cdr); + tmp->stillgoing = 0; + + update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); + + ast_mutex_lock(&qe->parent->lock); + qe->parent->rrpos++; + ast_mutex_unlock(&qe->parent->lock); + + (*busies)++; + return 0; + } + + tmp->chan->appl = "AppQueue"; + tmp->chan->data = "(Outgoing Line)"; + tmp->chan->whentohangup = 0; + if (tmp->chan->cid.cid_num) + free(tmp->chan->cid.cid_num); + tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num); + if (tmp->chan->cid.cid_name) + free(tmp->chan->cid.cid_name); + tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name); + if (tmp->chan->cid.cid_ani) + free(tmp->chan->cid.cid_ani); + tmp->chan->cid.cid_ani = ast_strdup(qe->chan->cid.cid_ani); + + /* Inherit specially named variables from parent channel */ + ast_channel_inherit_variables(qe->chan, tmp->chan); + + /* Presense of ADSI CPE on outgoing channel follows ours */ + tmp->chan->adsicpe = qe->chan->adsicpe; + + /* Inherit context and extension */ + ast_channel_lock(qe->chan); + macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT"); + if (!ast_strlen_zero(macrocontext)) + ast_copy_string(tmp->chan->dialcontext, macrocontext, sizeof(tmp->chan->dialcontext)); + else + ast_copy_string(tmp->chan->dialcontext, qe->chan->context, sizeof(tmp->chan->dialcontext)); + macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN"); + if (!ast_strlen_zero(macroexten)) + ast_copy_string(tmp->chan->exten, macroexten, sizeof(tmp->chan->exten)); + else + ast_copy_string(tmp->chan->exten, qe->chan->exten, sizeof(tmp->chan->exten)); + ast_channel_unlock(qe->chan); + + /* Place the call, but don't wait on the answer */ + if ((res = ast_call(tmp->chan, location, 0))) { + /* Again, keep going even if there's an error */ + if (option_debug) + ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface); + do_hang(tmp); + (*busies)++; + update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); + return 0; + } else if (qe->parent->eventwhencalled) { + char vars[2048]; + + manager_event(EVENT_FLAG_AGENT, "AgentCalled", + "AgentCalled: %s\r\n" + "AgentName: %s\r\n" + "ChannelCalling: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n" + "Priority: %d\r\n" + "%s", + tmp->interface, tmp->member->membername, qe->chan->name, + tmp->chan->cid.cid_num ? tmp->chan->cid.cid_num : "unknown", + tmp->chan->cid.cid_name ? tmp->chan->cid.cid_name : "unknown", + qe->chan->context, qe->chan->exten, qe->chan->priority, + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface); + } + + update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); + return 1; +} + +/*! \brief find the entry with the best metric, or NULL */ +static struct callattempt *find_best(struct callattempt *outgoing) +{ + struct callattempt *best = NULL, *cur; + + for (cur = outgoing; cur; cur = cur->q_next) { + if (cur->stillgoing && /* Not already done */ + !cur->chan && /* Isn't already going */ + (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */ + best = cur; + } + } + + return best; +} + +/*! \brief Place a call to a queue member + * + * Once metrics have been calculated for each member, this function is used + * to place a call to the appropriate member (or members). The low-level + * channel-handling and error detection is handled in ring_entry + * + * Returns 1 if a member was called successfully, 0 otherwise + */ +static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies) +{ + int ret = 0; + + while (ret == 0) { + struct callattempt *best = find_best(outgoing); + if (!best) { + if (option_debug) + ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n"); + break; + } + if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { + struct callattempt *cur; + /* Ring everyone who shares this best metric (for ringall) */ + for (cur = outgoing; cur; cur = cur->q_next) { + if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) { + if (option_debug) + ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric); + ret |= ring_entry(qe, cur, busies); + } + } + } else { + /* Ring just the best channel */ + if (option_debug) + ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric); + ret = ring_entry(qe, best, busies); + } + } + + return ret; +} + +static int store_next(struct queue_ent *qe, struct callattempt *outgoing) +{ + struct callattempt *best = find_best(outgoing); + + if (best) { + /* Ring just the best channel */ + if (option_debug) + ast_log(LOG_DEBUG, "Next is '%s' with metric %d\n", best->interface, best->metric); + qe->parent->rrpos = best->metric % 1000; + } else { + /* Just increment rrpos */ + if (qe->parent->wrapped) { + /* No more channels, start over */ + qe->parent->rrpos = 0; + } else { + /* Prioritize next entry */ + qe->parent->rrpos++; + } + } + qe->parent->wrapped = 0; + + return 0; +} + +static int say_periodic_announcement(struct queue_ent *qe) +{ + int res = 0; + time_t now; + + /* Get the current time */ + time(&now); + + /* Check to see if it is time to announce */ + if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency) + return 0; + + /* Stop the music on hold so we can play our own file */ + ast_moh_stop(qe->chan); + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Playing periodic announcement\n"); + + /* Check to make sure we have a sound file. If not, reset to the first sound file */ + if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || !strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) { + qe->last_periodic_announce_sound = 0; + } + + /* play the announcement */ + res = play_file(qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]); + + if ((res > 0 && !valid_exit(qe, res)) || res < 0) + res = 0; + + /* Resume Music on Hold if the caller is going to stay in the queue */ + if (!res) + ast_moh_start(qe->chan, qe->moh, NULL); + + /* update last_periodic_announce_time */ + qe->last_periodic_announce_time = now; + + /* Update the current periodic announcement to the next announcement */ + qe->last_periodic_announce_sound++; + + return res; +} + +static void record_abandoned(struct queue_ent *qe) +{ + ast_mutex_lock(&qe->parent->lock); + manager_event(EVENT_FLAG_AGENT, "QueueCallerAbandon", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Position: %d\r\n" + "OriginalPosition: %d\r\n" + "HoldTime: %d\r\n", + qe->parent->name, qe->chan->uniqueid, qe->pos, qe->opos, (int)(time(NULL) - qe->start)); + + qe->parent->callsabandoned++; + ast_mutex_unlock(&qe->parent->lock); +} + +/*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */ +static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername) +{ + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", rnatime); + ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime); + if (qe->parent->autopause) { + if (!set_member_paused(qe->parent->name, interface, 1)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", interface, qe->parent->name); + } else { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name); + } + } + return; +} + +#define AST_MAX_WATCHERS 256 +/*! \brief Wait for a member to answer the call + * + * \param[in] qe the queue_ent corresponding to the caller in the queue + * \param[in] outgoing the list of callattempts. Relevant ones will have their chan and stillgoing parameters non-zero + * \param[in] to the amount of time (in milliseconds) to wait for a response + * \param[out] digit if a user presses a digit to exit the queue, this is the digit the caller pressed + * \param[in] prebusies number of busy members calculated prior to calling wait_for_answer + * \param[in] caller_disconnect if the 'H' option is used when calling Queue(), this is used to detect if the caller pressed * to disconnect the call + * \param[in] forwardsallowed used to detect if we should allow call forwarding, based on the 'i' option to Queue() + */ +static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed) +{ + char *queue = qe->parent->name; + struct callattempt *o, *start = NULL, *prev = NULL; + int status; + int numbusies = prebusies; + int numnochan = 0; + int stillgoing = 0; + int orig = *to; + struct ast_frame *f; + struct callattempt *peer = NULL; + struct ast_channel *winner; + struct ast_channel *in = qe->chan; + char on[80] = ""; + char membername[80] = ""; + long starttime = 0; + long endtime = 0; + + starttime = (long) time(NULL); + + while (*to && !peer) { + int numlines, retry, pos = 1; + struct ast_channel *watchers[AST_MAX_WATCHERS]; + watchers[0] = in; + start = NULL; + + for (retry = 0; retry < 2; retry++) { + numlines = 0; + for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */ + if (o->stillgoing) { /* Keep track of important channels */ + stillgoing = 1; + if (o->chan) { + watchers[pos++] = o->chan; + if (!start) + start = o; + else + prev->call_next = o; + prev = o; + } + } + numlines++; + } + if (pos > 1 /* found */ || !stillgoing /* nobody listening */ || + (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */) + break; + /* On "ringall" strategy we only move to the next penalty level + when *all* ringing phones are done in the current penalty level */ + ring_one(qe, outgoing, &numbusies); + /* and retry... */ + } + if (pos == 1 /* not found */) { + if (numlines == (numbusies + numnochan)) { + ast_log(LOG_DEBUG, "Everyone is busy at this time\n"); + } else { + ast_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan); + } + *to = 0; + return NULL; + } + winner = ast_waitfor_n(watchers, pos, to); + for (o = start; o; o = o->call_next) { + if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) { + if (!peer) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name); + peer = o; + } + } else if (o->chan && (o->chan == winner)) { + + ast_copy_string(on, o->member->interface, sizeof(on)); + ast_copy_string(membername, o->member->membername, sizeof(membername)); + + if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Forwarding %s to '%s' prevented.\n", in->name, o->chan->call_forward); + numnochan++; + do_hang(o); + winner = NULL; + continue; + } else if (!ast_strlen_zero(o->chan->call_forward)) { + char tmpchan[256]; + char *stuff; + char *tech; + + ast_copy_string(tmpchan, o->chan->call_forward, sizeof(tmpchan)); + if ((stuff = strchr(tmpchan, '/'))) { + *stuff++ = '\0'; + tech = tmpchan; + } else { + snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context); + stuff = tmpchan; + tech = "Local"; + } + /* Before processing channel, go ahead and check for forwarding */ + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name); + /* Setup parameters */ + o->chan = ast_request(tech, in->nativeformats, stuff, &status); + if (!o->chan) { + ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff); + o->stillgoing = 0; + numnochan++; + } else { + ast_channel_inherit_variables(in, o->chan); + ast_channel_datastore_inherit(in, o->chan); + if (o->chan->cid.cid_num) + free(o->chan->cid.cid_num); + o->chan->cid.cid_num = ast_strdup(in->cid.cid_num); + + if (o->chan->cid.cid_name) + free(o->chan->cid.cid_name); + o->chan->cid.cid_name = ast_strdup(in->cid.cid_name); + + ast_string_field_set(o->chan, accountcode, in->accountcode); + o->chan->cdrflags = in->cdrflags; + + if (in->cid.cid_ani) { + if (o->chan->cid.cid_ani) + free(o->chan->cid.cid_ani); + o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani); + } + if (o->chan->cid.cid_rdnis) + free(o->chan->cid.cid_rdnis); + o->chan->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten)); + if (ast_call(o->chan, tmpchan, 0)) { + ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan); + do_hang(o); + numnochan++; + } + } + /* Hangup the original channel now, in case we needed it */ + ast_hangup(winner); + continue; + } + f = ast_read(winner); + if (f) { + if (f->frametype == AST_FRAME_CONTROL) { + switch (f->subclass) { + case AST_CONTROL_ANSWER: + /* This is our guy if someone answered. */ + if (!peer) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name); + peer = o; + } + break; + case AST_CONTROL_BUSY: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name); + if (in->cdr) + ast_cdr_busy(in->cdr); + do_hang(o); + endtime = (long)time(NULL); + endtime -= starttime; + rna(endtime*1000, qe, on, membername); + if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { + if (qe->parent->timeoutrestart) + *to = orig; + ring_one(qe, outgoing, &numbusies); + } + numbusies++; + break; + case AST_CONTROL_CONGESTION: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name); + if (in->cdr) + ast_cdr_busy(in->cdr); + endtime = (long)time(NULL); + endtime -= starttime; + rna(endtime*1000, qe, on, membername); + do_hang(o); + if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { + if (qe->parent->timeoutrestart) + *to = orig; + ring_one(qe, outgoing, &numbusies); + } + numbusies++; + break; + case AST_CONTROL_RINGING: + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name); + break; + case AST_CONTROL_OFFHOOK: + /* Ignore going off hook */ + break; + default: + ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass); + } + } + ast_frfree(f); + } else { + endtime = (long) time(NULL) - starttime; + rna(endtime * 1000, qe, on, membername); + do_hang(o); + if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { + if (qe->parent->timeoutrestart) + *to = orig; + ring_one(qe, outgoing, &numbusies); + } + } + } + } + if (winner == in) { + f = ast_read(in); + if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) { + /* Got hung up */ + *to = -1; + if (f) + ast_frfree(f); + return NULL; + } + if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass); + *to = 0; + ast_frfree(f); + return NULL; + } + if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c\n", f->subclass); + *to = 0; + *digit = f->subclass; + ast_frfree(f); + return NULL; + } + ast_frfree(f); + } + if (!*to) { + for (o = start; o; o = o->call_next) + rna(orig, qe, o->interface, o->member->membername); + } + } + + return peer; +} +/*! \brief Check if we should start attempting to call queue members + * + * The behavior of this function is dependent first on whether autofill is enabled + * and second on whether the ring strategy is ringall. If autofill is not enabled, + * then return true if we're the head of the queue. If autofill is enabled, then + * we count the available members and see if the number of available members is enough + * that given our position in the queue, we would theoretically be able to connect to + * one of those available members + */ +static int is_our_turn(struct queue_ent *qe) +{ + struct queue_ent *ch; + struct member *cur; + int avl = 0; + int idx = 0; + int res; + + if (!qe->parent->autofill) { + /* Atomically read the parent head -- does not need a lock */ + ch = qe->parent->head; + /* If we are now at the top of the head, break out */ + if (ch == qe) { + if (option_debug) + ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + if (option_debug) + ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); + res = 0; + } + + } else { + /* This needs a lock. How many members are available to be served? */ + ast_mutex_lock(&qe->parent->lock); + + ch = qe->parent->head; + + if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { + if (option_debug) + ast_log(LOG_DEBUG, "Even though there may be multiple members available, the strategy is ringall so only the head call is allowed in\n"); + avl = 1; + } else { + struct ao2_iterator mem_iter = ao2_iterator_init(qe->parent->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + switch (cur->status) { + case AST_DEVICE_INUSE: + if (!qe->parent->ringinuse) + break; + /* else fall through */ + case AST_DEVICE_NOT_INUSE: + case AST_DEVICE_UNKNOWN: + if (!cur->paused) + avl++; + break; + } + ao2_ref(cur, -1); + } + } + + if (option_debug) + ast_log(LOG_DEBUG, "There are %d available members.\n", avl); + + while ((idx < avl) && (ch) && (ch != qe)) { + if (!ch->pending) + idx++; + ch = ch->next; + } + + /* If the queue entry is within avl [the number of available members] calls from the top ... */ + if (ch && idx < avl) { + if (option_debug) + ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + if (option_debug) + ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); + res = 0; + } + + ast_mutex_unlock(&qe->parent->lock); + } + + return res; +} +/*! \brief The waiting areas for callers who are not actively calling members + * + * This function is one large loop. This function will return if a caller + * either exits the queue or it becomes that caller's turn to attempt calling + * queue members. Inside the loop, we service the caller with periodic announcements, + * holdtime announcements, etc. as configured in queues.conf + * + * \retval 0 if the caller's turn has arrived + * \retval -1 if the caller should exit the queue. + */ +static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason) +{ + int res = 0; + + /* This is the holding pen for callers 2 through maxlen */ + for (;;) { + enum queue_member_status stat; + + if (is_our_turn(qe)) + break; + + /* If we have timed out, break out */ + if (qe->expire && (time(NULL) >= qe->expire)) { + *reason = QUEUE_TIMEOUT; + break; + } + + stat = get_member_status(qe->parent, qe->max_penalty); + + /* leave the queue if no agents, if enabled */ + if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) { + *reason = QUEUE_LEAVEEMPTY; + ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start); + leave_queue(qe); + break; + } + + /* leave the queue if no reachable agents, if enabled */ + if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + *reason = QUEUE_LEAVEUNAVAIL; + ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start); + leave_queue(qe); + break; + } + + /* Make a position announcement, if enabled */ + if (qe->parent->announcefrequency && !ringing && + (res = say_position(qe))) + break; + + /* If we have timed out, break out */ + if (qe->expire && (time(NULL) >= qe->expire)) { + *reason = QUEUE_TIMEOUT; + break; + } + + /* Make a periodic announcement, if enabled */ + if (qe->parent->periodicannouncefrequency && !ringing && + (res = say_periodic_announcement(qe))) + break; + + /* If we have timed out, break out */ + if (qe->expire && (time(NULL) >= qe->expire)) { + *reason = QUEUE_TIMEOUT; + break; + } + + /* Wait a second before checking again */ + if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) { + if (res > 0 && !valid_exit(qe, res)) + res = 0; + else + break; + } + + /* If we have timed out, break out */ + if (qe->expire && (time(NULL) >= qe->expire)) { + *reason = QUEUE_TIMEOUT; + break; + } + } + + return res; +} + +static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl) +{ + ast_mutex_lock(&q->lock); + time(&member->lastcall); + member->calls++; + q->callscompleted++; + if (callcompletedinsl) + q->callscompletedinsl++; + ast_mutex_unlock(&q->lock); + return 0; +} + +/*! \brief Calculate the metric of each member in the outgoing callattempts + * + * A numeric metric is given to each member depending on the ring strategy used + * by the queue. Members with lower metrics will be called before members with + * higher metrics + */ +static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp) +{ + if (qe->max_penalty && (mem->penalty > qe->max_penalty)) + return -1; + + switch (q->strategy) { + case QUEUE_STRATEGY_RINGALL: + /* Everyone equal, except for penalty */ + tmp->metric = mem->penalty * 1000000; + break; + case QUEUE_STRATEGY_ROUNDROBIN: + if (!pos) { + if (!q->wrapped) { + /* No more channels, start over */ + q->rrpos = 0; + } else { + /* Prioritize next entry */ + q->rrpos++; + } + q->wrapped = 0; + } + /* Fall through */ + case QUEUE_STRATEGY_RRMEMORY: + if (pos < q->rrpos) { + tmp->metric = 1000 + pos; + } else { + if (pos > q->rrpos) + /* Indicate there is another priority */ + q->wrapped = 1; + tmp->metric = pos; + } + tmp->metric += mem->penalty * 1000000; + break; + case QUEUE_STRATEGY_RANDOM: + tmp->metric = ast_random() % 1000; + tmp->metric += mem->penalty * 1000000; + break; + case QUEUE_STRATEGY_FEWESTCALLS: + tmp->metric = mem->calls; + tmp->metric += mem->penalty * 1000000; + break; + case QUEUE_STRATEGY_LEASTRECENT: + if (!mem->lastcall) + tmp->metric = 0; + else + tmp->metric = 1000000 - (time(NULL) - mem->lastcall); + tmp->metric += mem->penalty * 1000000; + break; + default: + ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy); + break; + } + return 0; +} + +struct queue_transfer_ds { + struct queue_ent *qe; + struct member *member; + int starttime; +}; + +static void queue_transfer_destroy(void *data) +{ + struct queue_transfer_ds *qtds = data; + ast_free(qtds); +} + +/*! \brief a datastore used to help correctly log attended transfers of queue callers + */ +static const struct ast_datastore_info queue_transfer_info = { + .type = "queue_transfer", + .chan_fixup = queue_transfer_fixup, + .destroy = queue_transfer_destroy, +}; + +/*! \brief Log an attended transfer when a queue caller channel is masqueraded + * + * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when + * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan + * to new_chan. This is why new_chan is referenced for exten, context, and datastore information. + * + * At the end of this, we want to remove the datastore so that this fixup function is not called on any + * future masquerades of the caller during the current call. + */ +static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + struct queue_transfer_ds *qtds = data; + struct queue_ent *qe = qtds->qe; + struct member *member = qtds->member; + int callstart = qtds->starttime; + struct ast_datastore *datastore; + + ast_queue_log(qe->parent->name, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", + new_chan->exten, new_chan->context, (long) (callstart - qe->start), + (long) (time(NULL) - callstart)); + + if (!(datastore = ast_channel_datastore_find(new_chan, &queue_transfer_info, NULL))) { + ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n"); + return; + } + + ast_channel_datastore_remove(new_chan, datastore); + ast_channel_datastore_free(datastore); +} + +/*! \brief mechanism to tell if a queue caller was atxferred by a queue member. + * + * When a caller is atxferred, then the queue_transfer_info datastore + * is removed from the channel. If it's still there after the bridge is + * broken, then the caller was not atxferred. + */ +static int attended_transfer_occurred(struct ast_channel *chan) +{ + return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1; +} + +/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log + */ +static void setup_transfer_datastore(struct queue_ent *qe, struct member *member, int starttime) +{ + struct ast_datastore *ds; + struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds)); + + if (!qtds) { + ast_log(LOG_WARNING, "Memory allocation error!\n"); + return; + } + + ast_channel_lock(qe->chan); + if (!(ds = ast_channel_datastore_alloc(&queue_transfer_info, NULL))) { + ast_channel_unlock(qe->chan); + ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n"); + return; + } + + qtds->qe = qe; + /* This member is refcounted in try_calling, so no need to add it here, too */ + qtds->member = member; + qtds->starttime = starttime; + ds->data = qtds; + ast_channel_datastore_add(qe->chan, ds); + ast_channel_unlock(qe->chan); +} + + +/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member + * + * Here is the process of this function + * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue() + * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this + * iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this + * member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also + * during each iteration, we call calc_metric to determine which members should be rung when. + * 3. Call ring_one to place a call to the appropriate member(s) + * 4. Call wait_for_answer to wait for an answer. If no one answers, return. + * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered. + * 6. Start the monitor or mixmonitor if the option is set + * 7. Remove the caller from the queue to allow other callers to advance + * 8. Bridge the call. + * 9. Do any post processing after the call has disconnected. + * + * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members + * \param[in] options the options passed as the third parameter to the Queue() application + * \param[in] url the url passed as the fourth parameter to the Queue() application + * \param[in,out] tries the number of times we have tried calling queue members + * \param[out] noption set if the call to Queue() has the 'n' option set. + * \param[in] agi the agi passed as the fifth parameter to the Queue() application + */ + +static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi) +{ + struct member *cur; + struct callattempt *outgoing = NULL; /* the list of calls we are building */ + int to; + char oldexten[AST_MAX_EXTENSION]=""; + char oldcontext[AST_MAX_CONTEXT]=""; + char queuename[256]=""; + struct ast_channel *peer; + struct ast_channel *which; + struct callattempt *lpeer; + struct member *member; + struct ast_app *app; + int res = 0, bridge = 0; + int numbusies = 0; + int x=0; + char *announce = NULL; + char digit = 0; + time_t callstart; + time_t now = time(NULL); + struct ast_bridge_config bridge_config; + char nondataquality = 1; + char *agiexec = NULL; + int ret = 0; + const char *monitorfilename; + const char *monitor_exec; + const char *monitor_options; + char tmpid[256], tmpid2[256]; + char meid[1024], meid2[1024]; + char mixmonargs[1512]; + struct ast_app *mixmonapp = NULL; + char *p; + char vars[2048]; + int forwardsallowed = 1; + int callcompletedinsl; + struct ao2_iterator memi; + struct ast_datastore *datastore; + + ast_channel_lock(qe->chan); + datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL); + ast_channel_unlock(qe->chan); + + memset(&bridge_config, 0, sizeof(bridge_config)); + time(&now); + + /* If we've already exceeded our timeout, then just stop + * This should be extremely rare. queue_exec will take care + * of removing the caller and reporting the timeout as the reason. + */ + if (qe->expire && now >= qe->expire) { + res = 0; + goto out; + } + + for (; options && *options; options++) + switch (*options) { + case 't': + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT); + break; + case 'T': + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT); + break; + case 'w': + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON); + break; + case 'W': + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON); + break; + case 'd': + nondataquality = 0; + break; + case 'h': + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT); + break; + case 'H': + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT); + break; + case 'n': + if (qe->parent->strategy == QUEUE_STRATEGY_ROUNDROBIN || qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) + (*tries)++; + else + *tries = qe->parent->membercount; + *noption = 1; + break; + case 'i': + forwardsallowed = 0; + break; + } + + /* Hold the lock while we setup the outgoing calls */ + if (use_weight) + AST_LIST_LOCK(&queues); + ast_mutex_lock(&qe->parent->lock); + if (option_debug) + ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n", + qe->chan->name); + ast_copy_string(queuename, qe->parent->name, sizeof(queuename)); + if (!ast_strlen_zero(qe->announce)) + announce = qe->announce; + if (!ast_strlen_zero(announceoverride)) + announce = announceoverride; + + memi = ao2_iterator_init(qe->parent->members, 0); + while ((cur = ao2_iterator_next(&memi))) { + struct callattempt *tmp = ast_calloc(1, sizeof(*tmp)); + struct ast_dialed_interface *di; + AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces; + if (!tmp) { + ao2_ref(cur, -1); + ast_mutex_unlock(&qe->parent->lock); + if (use_weight) + AST_LIST_UNLOCK(&queues); + goto out; + } + if (!datastore) { + if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) { + ao2_ref(cur, -1); + ast_mutex_unlock(&qe->parent->lock); + if (use_weight) + AST_LIST_UNLOCK(&queues); + free(tmp); + goto out; + } + datastore->inheritance = DATASTORE_INHERIT_FOREVER; + if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) { + ao2_ref(cur, -1); + ast_mutex_unlock(&qe->parent->lock); + if (use_weight) + AST_LIST_UNLOCK(&queues); + free(tmp); + goto out; + } + datastore->data = dialed_interfaces; + AST_LIST_HEAD_INIT(dialed_interfaces); + + ast_channel_lock(qe->chan); + ast_channel_datastore_add(qe->chan, datastore); + ast_channel_unlock(qe->chan); + } else + dialed_interfaces = datastore->data; + + AST_LIST_LOCK(dialed_interfaces); + AST_LIST_TRAVERSE(dialed_interfaces, di, list) { + if (!strcasecmp(cur->interface, di->interface)) { + ast_log(LOG_DEBUG, "Skipping dialing interface '%s' since it has already been dialed\n", + di->interface); + break; + } + } + AST_LIST_UNLOCK(dialed_interfaces); + + if (di) { + free(tmp); + continue; + } + + /* It is always ok to dial a Local interface. We only keep track of + * which "real" interfaces have been dialed. The Local channel will + * inherit this list so that if it ends up dialing a real interface, + * it won't call one that has already been called. */ + if (strncasecmp(cur->interface, "Local/", 6)) { + if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) { + ao2_ref(cur, -1); + ast_mutex_unlock(&qe->parent->lock); + if (use_weight) + AST_LIST_UNLOCK(&queues); + free(tmp); + goto out; + } + strcpy(di->interface, cur->interface); + + AST_LIST_LOCK(dialed_interfaces); + AST_LIST_INSERT_TAIL(dialed_interfaces, di, list); + AST_LIST_UNLOCK(dialed_interfaces); + } + + tmp->stillgoing = -1; + tmp->member = cur; + tmp->oldstatus = cur->status; + tmp->lastcall = cur->lastcall; + ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface)); + /* Special case: If we ring everyone, go ahead and ring them, otherwise + just calculate their metric for the appropriate strategy */ + if (!calc_metric(qe->parent, cur, x++, qe, tmp)) { + /* Put them in the list of outgoing thingies... We're ready now. + XXX If we're forcibly removed, these outgoing calls won't get + hung up XXX */ + tmp->q_next = outgoing; + outgoing = tmp; + /* If this line is up, don't try anybody else */ + if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP)) + break; + } else { + ao2_ref(cur, -1); + free(tmp); + } + } + if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout)) + to = (qe->expire - now) * 1000; + else + to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1; + ++qe->pending; + ast_mutex_unlock(&qe->parent->lock); + ring_one(qe, outgoing, &numbusies); + if (use_weight) + AST_LIST_UNLOCK(&queues); + lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed); + /* The ast_channel_datastore_remove() function could fail here if the + * datastore was moved to another channel during a masquerade. If this is + * the case, don't free the datastore here because later, when the channel + * to which the datastore was moved hangs up, it will attempt to free this + * datastore again, causing a crash + */ + if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) { + ast_channel_datastore_free(datastore); + } + ast_mutex_lock(&qe->parent->lock); + if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) { + store_next(qe, outgoing); + } + ast_mutex_unlock(&qe->parent->lock); + peer = lpeer ? lpeer->chan : NULL; + if (!peer) { + qe->pending = 0; + if (to) { + /* Must gotten hung up */ + res = -1; + } else { + /* User exited by pressing a digit */ + res = digit; + } + if (option_debug && res == -1) + ast_log(LOG_DEBUG, "%s: Nobody answered.\n", qe->chan->name); + } else { /* peer is valid */ + /* Ah ha! Someone answered within the desired timeframe. Of course after this + we will always return with -1 so that it is hung up properly after the + conversation. */ + if (!strcmp(qe->chan->tech->type, "Zap")) + ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0); + if (!strcmp(peer->tech->type, "Zap")) + ast_channel_setoption(peer, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0); + /* Update parameters for the queue */ + time(&now); + recalc_holdtime(qe, (now - qe->start)); + ast_mutex_lock(&qe->parent->lock); + callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel); + ast_mutex_unlock(&qe->parent->lock); + member = lpeer->member; + /* Increment the refcount for this member, since we're going to be using it for awhile in here. */ + ao2_ref(member, 1); + hangupcalls(outgoing, peer); + outgoing = NULL; + if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) { + int res2; + + res2 = ast_autoservice_start(qe->chan); + if (!res2) { + if (qe->parent->memberdelay) { + ast_log(LOG_NOTICE, "Delaying member connect for %d seconds\n", qe->parent->memberdelay); + res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000); + } + if (!res2 && announce) { + play_file(peer, announce); + } + if (!res2 && qe->parent->reportholdtime) { + if (!play_file(peer, qe->parent->sound_reporthold)) { + int holdtime; + + time(&now); + holdtime = abs((now - qe->start) / 60); + if (holdtime < 2) { + play_file(peer, qe->parent->sound_lessthan); + ast_say_number(peer, 2, AST_DIGIT_ANY, peer->language, NULL); + } else + ast_say_number(peer, holdtime, AST_DIGIT_ANY, peer->language, NULL); + play_file(peer, qe->parent->sound_minutes); + } + } + } + res2 |= ast_autoservice_stop(qe->chan); + if (peer->_softhangup) { + /* Agent must have hung up */ + ast_log(LOG_WARNING, "Agent on %s hungup on the customer.\n", peer->name); + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "AGENTDUMP", "%s", ""); + if (qe->parent->eventwhencalled) + manager_event(EVENT_FLAG_AGENT, "AgentDump", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Channel: %s\r\n" + "Member: %s\r\n" + "MemberName: %s\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); + ast_hangup(peer); + ao2_ref(member, -1); + goto out; + } else if (res2) { + /* Caller must have hung up just before being connected*/ + ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", peer->name); + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start); + record_abandoned(qe); + ast_hangup(peer); + ao2_ref(member, -1); + return -1; + } + } + /* Stop music on hold */ + ast_moh_stop(qe->chan); + /* If appropriate, log that we have a destination channel */ + if (qe->chan->cdr) + ast_cdr_setdestchan(qe->chan->cdr, peer->name); + /* Make sure channels are compatible */ + res = ast_channel_make_compatible(qe->chan, peer); + if (res < 0) { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "SYSCOMPAT", "%s", ""); + ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name); + record_abandoned(qe); + ast_hangup(peer); + ao2_ref(member, -1); + return -1; + } + + if (qe->parent->setinterfacevar) + pbx_builtin_setvar_helper(qe->chan, "MEMBERINTERFACE", member->interface); + + /* Begin Monitoring */ + if (qe->parent->monfmt && *qe->parent->monfmt) { + if (!qe->parent->montype) { + if (option_debug) + ast_log(LOG_DEBUG, "Starting Monitor as requested.\n"); + monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"); + if (pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC") || pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC_ARGS")) + which = qe->chan; + else + which = peer; + if (monitorfilename) + ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1 ); + else if (qe->chan->cdr) + ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 ); + else { + /* Last ditch effort -- no CDR, make up something */ + snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); + ast_monitor_start(which, qe->parent->monfmt, tmpid, 1 ); + } + if (qe->parent->monjoin) + ast_monitor_setjoinfiles(which, 1); + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Starting MixMonitor as requested.\n"); + monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"); + if (!monitorfilename) { + if (qe->chan->cdr) + ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid)-1); + else + snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); + } else { + ast_copy_string(tmpid2, monitorfilename, sizeof(tmpid2)-1); + for (p = tmpid2; *p ; p++) { + if (*p == '^' && *(p+1) == '{') { + *p = '$'; + } + } + + memset(tmpid, 0, sizeof(tmpid)); + pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1); + } + + monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC"); + monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS"); + + if (monitor_exec) { + ast_copy_string(meid2, monitor_exec, sizeof(meid2)-1); + for (p = meid2; *p ; p++) { + if (*p == '^' && *(p+1) == '{') { + *p = '$'; + } + } + + memset(meid, 0, sizeof(meid)); + pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1); + } + + snprintf(tmpid2, sizeof(tmpid2)-1, "%s.%s", tmpid, qe->parent->monfmt); + + mixmonapp = pbx_findapp("MixMonitor"); + + if (strchr(tmpid2, '|')) { + ast_log(LOG_WARNING, "monitor-format (in queues.conf) and MONITOR_FILENAME cannot contain a '|'! Not recording.\n"); + mixmonapp = NULL; + } + + if (!monitor_options) + monitor_options = ""; + + if (strchr(monitor_options, '|')) { + ast_log(LOG_WARNING, "MONITOR_OPTIONS cannot contain a '|'! Not recording.\n"); + mixmonapp = NULL; + } + + if (mixmonapp) { + if (!ast_strlen_zero(monitor_exec)) + snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s|%s", tmpid2, monitor_options, monitor_exec); + else + snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s", tmpid2, monitor_options); + + if (option_debug) + ast_log(LOG_DEBUG, "Arguments being passed to MixMonitor: %s\n", mixmonargs); + /* We purposely lock the CDR so that pbx_exec does not update the application data */ + if (qe->chan->cdr) + ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); + ret = pbx_exec(qe->chan, mixmonapp, mixmonargs); + if (qe->chan->cdr) + ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); + + } else + ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n"); + + } + } + /* Drop out of the queue at this point, to prepare for next caller */ + leave_queue(qe); + if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) { + if (option_debug) + ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url); + ast_channel_sendurl(peer, url); + } + if (!ast_strlen_zero(agi)) { + if (option_debug) + ast_log(LOG_DEBUG, "app_queue: agi=%s.\n", agi); + app = pbx_findapp("agi"); + if (app) { + agiexec = ast_strdupa(agi); + ret = pbx_exec(qe->chan, app, agiexec); + } else + ast_log(LOG_WARNING, "Asked to execute an AGI on this channel, but could not find application (agi)!\n"); + } + qe->handled++; + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s", (long)time(NULL) - qe->start, peer->uniqueid); + if (qe->parent->eventwhencalled) + manager_event(EVENT_FLAG_AGENT, "AgentConnect", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Channel: %s\r\n" + "Member: %s\r\n" + "MemberName: %s\r\n" + "Holdtime: %ld\r\n" + "BridgedChannel: %s\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, + (long)time(NULL) - qe->start, peer->uniqueid, + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); + ast_copy_string(oldcontext, qe->chan->context, sizeof(oldcontext)); + ast_copy_string(oldexten, qe->chan->exten, sizeof(oldexten)); + time(&callstart); + + if (member->status == AST_DEVICE_NOT_INUSE) + ast_log(LOG_WARNING, "The device state of this queue member, %s, is still 'Not in Use' when it probably should not be! Please check UPGRADE.txt for correct configuration settings.\n", member->membername); + + setup_transfer_datastore(qe, member, callstart); + bridge = ast_bridge_call(qe->chan,peer, &bridge_config); + + if (!attended_transfer_occurred(qe->chan)) { + struct ast_datastore *transfer_ds; + if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", + qe->chan->exten, qe->chan->context, (long) (callstart - qe->start), + (long) (time(NULL) - callstart)); + } else if (qe->chan->_softhangup) { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d", + (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); + if (qe->parent->eventwhencalled) + manager_event(EVENT_FLAG_AGENT, "AgentComplete", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Channel: %s\r\n" + "Member: %s\r\n" + "MemberName: %s\r\n" + "HoldTime: %ld\r\n" + "TalkTime: %ld\r\n" + "Reason: caller\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, + (long)(callstart - qe->start), (long)(time(NULL) - callstart), + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); + } else { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d", + (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); + if (qe->parent->eventwhencalled) + manager_event(EVENT_FLAG_AGENT, "AgentComplete", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Channel: %s\r\n" + "MemberName: %s\r\n" + "HoldTime: %ld\r\n" + "TalkTime: %ld\r\n" + "Reason: agent\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->membername, (long)(callstart - qe->start), + (long)(time(NULL) - callstart), + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); + } + ast_channel_lock(qe->chan); + transfer_ds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL); + if (transfer_ds) { + ast_channel_datastore_remove(qe->chan, transfer_ds); + ast_channel_datastore_free(transfer_ds); + } + ast_channel_unlock(qe->chan); + } + + if (bridge != AST_PBX_NO_HANGUP_PEER) + ast_hangup(peer); + update_queue(qe->parent, member, callcompletedinsl); + res = bridge ? bridge : 1; + ao2_ref(member, -1); + } +out: + hangupcalls(outgoing, NULL); + + return res; +} + +static int wait_a_bit(struct queue_ent *qe) +{ + /* Don't need to hold the lock while we setup the outgoing calls */ + int retrywait = qe->parent->retry * 1000; + + int res = ast_waitfordigit(qe->chan, retrywait); + if (res > 0 && !valid_exit(qe, res)) + res = 0; + + return res; +} + +static struct member *interface_exists(struct call_queue *q, const char *interface) +{ + struct member *mem; + struct ao2_iterator mem_iter; + + if (!q) + return NULL; + + mem_iter = ao2_iterator_init(q->members, 0); + while ((mem = ao2_iterator_next(&mem_iter))) { + if (!strcasecmp(interface, mem->interface)) + return mem; + ao2_ref(mem, -1); + } + + return NULL; +} + + +/* Dump all members in a specific queue to the database + * + * / = ;;[|...] + * + */ +static void dump_queue_members(struct call_queue *pm_queue) +{ + struct member *cur_member; + char value[PM_MAX_LEN]; + int value_len = 0; + int res; + struct ao2_iterator mem_iter; + + memset(value, 0, sizeof(value)); + + if (!pm_queue) + return; + + mem_iter = ao2_iterator_init(pm_queue->members, 0); + while ((cur_member = ao2_iterator_next(&mem_iter))) { + if (!cur_member->dynamic) { + ao2_ref(cur_member, -1); + continue; + } + + res = snprintf(value + value_len, sizeof(value) - value_len, "%s%s;%d;%d;%s", + value_len ? "|" : "", cur_member->interface, cur_member->penalty, cur_member->paused, cur_member->membername); + + ao2_ref(cur_member, -1); + + if (res != strlen(value + value_len)) { + ast_log(LOG_WARNING, "Could not create persistent member string, out of space\n"); + break; + } + value_len += res; + } + + if (value_len && !cur_member) { + if (ast_db_put(pm_family, pm_queue->name, value)) + ast_log(LOG_WARNING, "failed to create persistent dynamic entry!\n"); + } else + /* Delete the entry if the queue is empty or there is an error */ + ast_db_del(pm_family, pm_queue->name); +} + +static int remove_from_queue(const char *queuename, const char *interface) +{ + struct call_queue *q; + struct member *mem, tmpmem; + int res = RES_NOSUCHQUEUE; + + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + if (strcmp(q->name, queuename)) { + ast_mutex_unlock(&q->lock); + continue; + } + + if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { + /* XXX future changes should beware of this assumption!! */ + if (!mem->dynamic) { + res = RES_NOT_DYNAMIC; + ao2_ref(mem, -1); + ast_mutex_unlock(&q->lock); + break; + } + q->membercount--; + manager_event(EVENT_FLAG_AGENT, "QueueMemberRemoved", + "Queue: %s\r\n" + "Location: %s\r\n" + "MemberName: %s\r\n", + q->name, mem->interface, mem->membername); + ao2_unlink(q->members, mem); + ao2_ref(mem, -1); + + if (queue_persistent_members) + dump_queue_members(q); + + res = RES_OKAY; + } else { + res = RES_EXISTS; + } + ast_mutex_unlock(&q->lock); + break; + } + + if (res == RES_OKAY) + remove_from_interfaces(interface); + + AST_LIST_UNLOCK(&queues); + + return res; +} + + +static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump) +{ + struct call_queue *q; + struct member *new_member, *old_member; + int res = RES_NOSUCHQUEUE; + + /* \note Ensure the appropriate realtime queue is loaded. Note that this + * short-circuits if the queue is already in memory. */ + if (!(q = load_realtime_queue(queuename))) + return res; + + AST_LIST_LOCK(&queues); + + ast_mutex_lock(&q->lock); + if ((old_member = interface_exists(q, interface)) == NULL) { + add_to_interfaces(interface); + if ((new_member = create_queue_member(interface, membername, penalty, paused))) { + new_member->dynamic = 1; + ao2_link(q->members, new_member); + q->membercount++; + manager_event(EVENT_FLAG_AGENT, "QueueMemberAdded", + "Queue: %s\r\n" + "Location: %s\r\n" + "MemberName: %s\r\n" + "Membership: %s\r\n" + "Penalty: %d\r\n" + "CallsTaken: %d\r\n" + "LastCall: %d\r\n" + "Status: %d\r\n" + "Paused: %d\r\n", + q->name, new_member->interface, new_member->membername, + "dynamic", + new_member->penalty, new_member->calls, (int) new_member->lastcall, + new_member->status, new_member->paused); + + ao2_ref(new_member, -1); + new_member = NULL; + + if (dump) + dump_queue_members(q); + + res = RES_OKAY; + } else { + res = RES_OUTOFMEMORY; + } + } else { + ao2_ref(old_member, -1); + res = RES_EXISTS; + } + ast_mutex_unlock(&q->lock); + AST_LIST_UNLOCK(&queues); + + return res; +} + +static int set_member_paused(const char *queuename, const char *interface, int paused) +{ + int found = 0; + struct call_queue *q; + struct member *mem; + + /* Special event for when all queues are paused - individual events still generated */ + /* XXX In all other cases, we use the membername, but since this affects all queues, we cannot */ + if (ast_strlen_zero(queuename)) + ast_queue_log("NONE", "NONE", interface, (paused ? "PAUSEALL" : "UNPAUSEALL"), "%s", ""); + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) { + if ((mem = interface_exists(q, interface))) { + found++; + if (mem->paused == paused) + ast_log(LOG_DEBUG, "%spausing already-%spaused queue member %s:%s\n", (paused ? "" : "un"), (paused ? "" : "un"), q->name, interface); + mem->paused = paused; + + if (queue_persistent_members) + dump_queue_members(q); + + if (mem->realtime) + update_realtime_member_field(mem, q->name, "paused", paused ? "1" : "0"); + + ast_queue_log(q->name, "NONE", mem->membername, (paused ? "PAUSE" : "UNPAUSE"), "%s", ""); + + manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused", + "Queue: %s\r\n" + "Location: %s\r\n" + "MemberName: %s\r\n" + "Paused: %d\r\n", + q->name, mem->interface, mem->membername, paused); + ao2_ref(mem, -1); + } + } + ast_mutex_unlock(&q->lock); + } + AST_LIST_UNLOCK(&queues); + + return found ? RESULT_SUCCESS : RESULT_FAILURE; +} + +/* Reload dynamic queue members persisted into the astdb */ +static void reload_queue_members(void) +{ + char *cur_ptr; + char *queue_name; + char *member; + char *interface; + char *membername = NULL; + char *penalty_tok; + int penalty = 0; + char *paused_tok; + int paused = 0; + struct ast_db_entry *db_tree; + struct ast_db_entry *entry; + struct call_queue *cur_queue; + char queue_data[PM_MAX_LEN]; + + AST_LIST_LOCK(&queues); + + /* Each key in 'pm_family' is the name of a queue */ + db_tree = ast_db_gettree(pm_family, NULL); + for (entry = db_tree; entry; entry = entry->next) { + + queue_name = entry->key + strlen(pm_family) + 2; + + AST_LIST_TRAVERSE(&queues, cur_queue, list) { + ast_mutex_lock(&cur_queue->lock); + if (!strcmp(queue_name, cur_queue->name)) + break; + ast_mutex_unlock(&cur_queue->lock); + } + + if (!cur_queue) + cur_queue = load_realtime_queue(queue_name); + + if (!cur_queue) { + /* If the queue no longer exists, remove it from the + * database */ + ast_log(LOG_WARNING, "Error loading persistent queue: '%s': it does not exist\n", queue_name); + ast_db_del(pm_family, queue_name); + continue; + } else + ast_mutex_unlock(&cur_queue->lock); + + if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN)) + continue; + + cur_ptr = queue_data; + while ((member = strsep(&cur_ptr, "|"))) { + if (ast_strlen_zero(member)) + continue; + + interface = strsep(&member, ";"); + penalty_tok = strsep(&member, ";"); + paused_tok = strsep(&member, ";"); + membername = strsep(&member, ";"); + + if (!penalty_tok) { + ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (penalty)\n", queue_name); + break; + } + penalty = strtol(penalty_tok, NULL, 10); + if (errno == ERANGE) { + ast_log(LOG_WARNING, "Error converting penalty: %s: Out of range.\n", penalty_tok); + break; + } + + if (!paused_tok) { + ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (paused)\n", queue_name); + break; + } + paused = strtol(paused_tok, NULL, 10); + if ((errno == ERANGE) || paused < 0 || paused > 1) { + ast_log(LOG_WARNING, "Error converting paused: %s: Expected 0 or 1.\n", paused_tok); + break; + } + if (ast_strlen_zero(membername)) + membername = interface; + + if (option_debug) + ast_log(LOG_DEBUG, "Reload Members: Queue: %s Member: %s Name: %s Penalty: %d Paused: %d\n", queue_name, interface, membername, penalty, paused); + + if (add_to_queue(queue_name, interface, membername, penalty, paused, 0) == RES_OUTOFMEMORY) { + ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n"); + break; + } + } + } + + AST_LIST_UNLOCK(&queues); + if (db_tree) { + ast_log(LOG_NOTICE, "Queue members successfully reloaded from database.\n"); + ast_db_freetree(db_tree); + } +} + +static int pqm_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *lu; + char *parse; + int priority_jump = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface[|options])\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + lu = ast_module_user_add(chan); + + if (args.options) { + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + if (ast_strlen_zero(args.interface)) { + ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n"); + ast_module_user_remove(lu); + return -1; + } + + if (set_member_paused(args.queuename, args.interface, 1)) { + ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface); + if (priority_jump || ast_opt_priority_jumping) { + if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) { + pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND"); + ast_module_user_remove(lu); + return 0; + } + } + ast_module_user_remove(lu); + pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND"); + return 0; + } + + ast_module_user_remove(lu); + pbx_builtin_setvar_helper(chan, "PQMSTATUS", "PAUSED"); + + return 0; +} + +static int upqm_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *lu; + char *parse; + int priority_jump = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options])\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + lu = ast_module_user_add(chan); + + if (args.options) { + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + if (ast_strlen_zero(args.interface)) { + ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n"); + ast_module_user_remove(lu); + return -1; + } + + if (set_member_paused(args.queuename, args.interface, 0)) { + ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface); + if (priority_jump || ast_opt_priority_jumping) { + if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) { + pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND"); + ast_module_user_remove(lu); + return 0; + } + } + ast_module_user_remove(lu); + pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND"); + return 0; + } + + ast_module_user_remove(lu); + pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED"); + + return 0; +} + +static int rqm_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + struct ast_module_user *lu; + char *parse, *temppos = NULL; + int priority_jump = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + AST_APP_ARG(options); + ); + + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface[|options]])\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + lu = ast_module_user_add(chan); + + if (ast_strlen_zero(args.interface)) { + args.interface = ast_strdupa(chan->name); + temppos = strrchr(args.interface, '-'); + if (temppos) + *temppos = '\0'; + } + + if (args.options) { + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + switch (remove_from_queue(args.queuename, args.interface)) { + case RES_OKAY: + ast_queue_log(args.queuename, chan->uniqueid, args.interface, "REMOVEMEMBER", "%s", ""); + ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename); + pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED"); + res = 0; + break; + case RES_EXISTS: + ast_log(LOG_DEBUG, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename); + if (priority_jump || ast_opt_priority_jumping) + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTINQUEUE"); + res = 0; + break; + case RES_NOSUCHQUEUE: + ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", args.queuename); + pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOSUCHQUEUE"); + res = 0; + break; + case RES_NOT_DYNAMIC: + ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': '%s' is not a dynamic member\n", args.queuename, args.interface); + pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTDYNAMIC"); + res = 0; + break; + } + + ast_module_user_remove(lu); + + return res; +} + +static int aqm_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + struct ast_module_user *lu; + char *parse, *temppos = NULL; + int priority_jump = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + AST_APP_ARG(penalty); + AST_APP_ARG(options); + AST_APP_ARG(membername); + ); + int penalty = 0; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options][|membername]])\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + lu = ast_module_user_add(chan); + + if (ast_strlen_zero(args.interface)) { + args.interface = ast_strdupa(chan->name); + temppos = strrchr(args.interface, '-'); + if (temppos) + *temppos = '\0'; + } + + if (!ast_strlen_zero(args.penalty)) { + if ((sscanf(args.penalty, "%d", &penalty) != 1) || penalty < 0) { + ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty); + penalty = 0; + } + } + + if (args.options) { + if (strchr(args.options, 'j')) + priority_jump = 1; + } + + switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, 0, queue_persistent_members)) { + case RES_OKAY: + ast_queue_log(args.queuename, chan->uniqueid, args.interface, "ADDMEMBER", "%s", ""); + ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, args.queuename); + pbx_builtin_setvar_helper(chan, "AQMSTATUS", "ADDED"); + res = 0; + break; + case RES_EXISTS: + ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, args.queuename); + if (priority_jump || ast_opt_priority_jumping) + ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101); + pbx_builtin_setvar_helper(chan, "AQMSTATUS", "MEMBERALREADY"); + res = 0; + break; + case RES_NOSUCHQUEUE: + ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", args.queuename); + pbx_builtin_setvar_helper(chan, "AQMSTATUS", "NOSUCHQUEUE"); + res = 0; + break; + case RES_OUTOFMEMORY: + ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, args.queuename); + break; + } + + ast_module_user_remove(lu); + + return res; +} + +static int ql_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *u; + char *parse; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(uniqueid); + AST_APP_ARG(membername); + AST_APP_ARG(event); + AST_APP_ARG(params); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo]\n"); + return -1; + } + + u = ast_module_user_add(chan); + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.queuename) || ast_strlen_zero(args.uniqueid) + || ast_strlen_zero(args.membername) || ast_strlen_zero(args.event)) { + ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo])\n"); + ast_module_user_remove(u); + return -1; + } + + ast_queue_log(args.queuename, args.uniqueid, args.membername, args.event, + "%s", args.params ? args.params : ""); + + ast_module_user_remove(u); + + return 0; +} + +/*!\brief The starting point for all queue calls + * + * The process involved here is to + * 1. Parse the options specified in the call to Queue() + * 2. Join the queue + * 3. Wait in a loop until it is our turn to try calling a queue member + * 4. Attempt to call a queue member + * 5. If 4. did not result in a bridged call, then check for between + * call options such as periodic announcements etc. + * 6. Try 4 again uless some condition (such as an expiration time) causes us to + * exit the queue. + */ +static int queue_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + int ringing=0; + struct ast_module_user *lu; + const char *user_priority; + const char *max_penalty_str; + int prio; + int max_penalty; + enum queue_result reason = QUEUE_UNKNOWN; + /* whether to exit Queue application after the timeout hits */ + int tries = 0; + int noption = 0; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(options); + AST_APP_ARG(url); + AST_APP_ARG(announceoverride); + AST_APP_ARG(queuetimeoutstr); + AST_APP_ARG(agi); + ); + /* Our queue entry */ + struct queue_ent qe; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Queue requires an argument: queuename[|options[|URL[|announceoverride[|timeout[|agi]]]]]\n"); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + lu = ast_module_user_add(chan); + + /* Setup our queue entry */ + memset(&qe, 0, sizeof(qe)); + qe.start = time(NULL); + + /* set the expire time based on the supplied timeout; */ + if (!ast_strlen_zero(args.queuetimeoutstr)) + qe.expire = qe.start + atoi(args.queuetimeoutstr); + else + qe.expire = 0; + + /* Get the priority from the variable ${QUEUE_PRIO} */ + user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO"); + if (user_priority) { + if (sscanf(user_priority, "%d", &prio) == 1) { + if (option_debug) + ast_log(LOG_DEBUG, "%s: Got priority %d from ${QUEUE_PRIO}.\n", + chan->name, prio); + } else { + ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n", + user_priority, chan->name); + prio = 0; + } + } else { + if (option_debug > 2) + ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n"); + prio = 0; + } + + /* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */ + if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) { + if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) { + if (option_debug) + ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n", + chan->name, max_penalty); + } else { + ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n", + max_penalty_str, chan->name); + max_penalty = 0; + } + } else { + max_penalty = 0; + } + + if (args.options && (strchr(args.options, 'r'))) + ringing = 1; + + if (option_debug) + ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n", + args.queuename, args.options, args.url, args.announceoverride, (long)qe.expire, prio); + + qe.chan = chan; + qe.prio = prio; + qe.max_penalty = max_penalty; + qe.last_pos_said = 0; + qe.last_pos = 0; + qe.last_periodic_announce_time = time(NULL); + qe.last_periodic_announce_sound = 0; + qe.valid_digits = 0; + if (!join_queue(args.queuename, &qe, &reason)) { + int makeannouncement = 0; + + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""), + S_OR(chan->cid.cid_num, "")); +check_turns: + if (ringing) { + ast_indicate(chan, AST_CONTROL_RINGING); + } else { + ast_moh_start(chan, qe.moh, NULL); + } + + /* This is the wait loop for callers 2 through maxlen */ + res = wait_our_turn(&qe, ringing, &reason); + if (res) + goto stop; + + for (;;) { + /* This is the wait loop for the head caller*/ + /* To exit, they may get their call answered; */ + /* they may dial a digit from the queue context; */ + /* or, they may timeout. */ + + enum queue_member_status stat; + + /* Leave if we have exceeded our queuetimeout */ + if (qe.expire && (time(NULL) >= qe.expire)) { + record_abandoned(&qe); + reason = QUEUE_TIMEOUT; + res = 0; + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); + break; + } + + if (makeannouncement) { + /* Make a position announcement, if enabled */ + if (qe.parent->announcefrequency && !ringing) + if ((res = say_position(&qe))) + goto stop; + + } + makeannouncement = 1; + + /* Leave if we have exceeded our queuetimeout */ + if (qe.expire && (time(NULL) >= qe.expire)) { + record_abandoned(&qe); + reason = QUEUE_TIMEOUT; + res = 0; + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); + break; + } + /* Make a periodic announcement, if enabled */ + if (qe.parent->periodicannouncefrequency && !ringing) + if ((res = say_periodic_announcement(&qe))) + goto stop; + + /* Leave if we have exceeded our queuetimeout */ + if (qe.expire && (time(NULL) >= qe.expire)) { + record_abandoned(&qe); + reason = QUEUE_TIMEOUT; + res = 0; + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); + break; + } + /* Try calling all queue members for 'timeout' seconds */ + res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi); + if (res) + goto stop; + + stat = get_member_status(qe.parent, qe.max_penalty); + + /* exit after 'timeout' cycle if 'n' option enabled */ + if (noption && tries >= qe.parent->membercount) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n"); + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); + record_abandoned(&qe); + reason = QUEUE_TIMEOUT; + res = 0; + break; + } + + /* leave the queue if no agents, if enabled */ + if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) { + record_abandoned(&qe); + reason = QUEUE_LEAVEEMPTY; + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start)); + res = 0; + break; + } + + /* leave the queue if no reachable agents, if enabled */ + if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + record_abandoned(&qe); + reason = QUEUE_LEAVEUNAVAIL; + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start)); + res = 0; + break; + } + + /* Leave if we have exceeded our queuetimeout */ + if (qe.expire && (time(NULL) >= qe.expire)) { + record_abandoned(&qe); + reason = QUEUE_TIMEOUT; + res = 0; + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); + break; + } + + /* If using dynamic realtime members, we should regenerate the member list for this queue */ + update_realtime_members(qe.parent); + + /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */ + res = wait_a_bit(&qe); + if (res) + goto stop; + + /* Since this is a priority queue and + * it is not sure that we are still at the head + * of the queue, go and check for our turn again. + */ + if (!is_our_turn(&qe)) { + if (option_debug) + ast_log(LOG_DEBUG, "Darn priorities, going back in queue (%s)!\n", + qe.chan->name); + goto check_turns; + } + } + +stop: + if (res) { + if (res < 0) { + if (!qe.handled) { + record_abandoned(&qe); + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON", + "%d|%d|%ld", qe.pos, qe.opos, + (long) time(NULL) - qe.start); + } + res = -1; + } else if (qe.valid_digits) { + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", + "%s|%d", qe.digits, qe.pos); + } + } + + /* Don't allow return code > 0 */ + if (res >= 0 && res != AST_PBX_KEEPALIVE) { + res = 0; + if (ringing) { + ast_indicate(chan, -1); + } else { + ast_moh_stop(chan); + } + ast_stopstream(chan); + } + leave_queue(&qe); + if (reason != QUEUE_UNKNOWN) + set_queue_result(chan, reason); + } else { + ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename); + set_queue_result(chan, reason); + res = 0; + } + ast_module_user_remove(lu); + + return res; +} + +static int queue_function_qac(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + int count = 0; + struct call_queue *q; + struct ast_module_user *lu; + struct member *m; + struct ao2_iterator mem_iter; + + buf[0] = '\0'; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); + return -1; + } + + lu = ast_module_user_add(chan); + + if ((q = load_realtime_queue(data))) { + ast_mutex_lock(&q->lock); + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + /* Count the agents who are logged in and presently answering calls */ + if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) { + count++; + } + ao2_ref(m, -1); + } + ast_mutex_unlock(&q->lock); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + snprintf(buf, len, "%d", count); + ast_module_user_remove(lu); + + return 0; +} + +static int queue_function_queuewaitingcount(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + int count = 0; + struct call_queue *q; + struct ast_module_user *lu; + struct ast_variable *var = NULL; + + buf[0] = '\0'; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); + return -1; + } + + lu = ast_module_user_add(chan); + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + if (!strcasecmp(q->name, data)) { + ast_mutex_lock(&q->lock); + break; + } + } + AST_LIST_UNLOCK(&queues); + + if (q) { + count = q->count; + ast_mutex_unlock(&q->lock); + } else if ((var = ast_load_realtime("queues", "name", data, NULL))) { + /* if the queue is realtime but was not found in memory, this + * means that the queue had been deleted from memory since it was + * "dead." This means it has a 0 waiting count + */ + count = 0; + ast_variables_destroy(var); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + snprintf(buf, len, "%d", count); + ast_module_user_remove(lu); + return 0; +} + +static int queue_function_queuememberlist(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) +{ + struct ast_module_user *u; + struct call_queue *q; + struct member *m; + + /* Ensure an otherwise empty list doesn't return garbage */ + buf[0] = '\0'; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "QUEUE_MEMBER_LIST requires an argument: queuename\n"); + return -1; + } + + u = ast_module_user_add(chan); + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + if (!strcasecmp(q->name, data)) { + ast_mutex_lock(&q->lock); + break; + } + } + AST_LIST_UNLOCK(&queues); + + if (q) { + int buflen = 0, count = 0; + struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0); + + while ((m = ao2_iterator_next(&mem_iter))) { + /* strcat() is always faster than printf() */ + if (count++) { + strncat(buf + buflen, ",", len - buflen - 1); + buflen++; + } + strncat(buf + buflen, m->membername, len - buflen - 1); + buflen += strlen(m->membername); + /* Safeguard against overflow (negative length) */ + if (buflen >= len - 2) { + ao2_ref(m, -1); + ast_log(LOG_WARNING, "Truncating list\n"); + break; + } + ao2_ref(m, -1); + } + ast_mutex_unlock(&q->lock); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + /* We should already be terminated, but let's make sure. */ + buf[len - 1] = '\0'; + ast_module_user_remove(u); + + return 0; +} + +static struct ast_custom_function queueagentcount_function = { + .name = "QUEUEAGENTCOUNT", + .synopsis = "Count number of agents answering a queue", + .syntax = "QUEUEAGENTCOUNT()", + .desc = +"Returns the number of members currently associated with the specified queue.\n" +"This function is deprecated. You should use QUEUE_MEMBER_COUNT() instead.\n", + .read = queue_function_qac, +}; + +static struct ast_custom_function queuemembercount_function = { + .name = "QUEUE_MEMBER_COUNT", + .synopsis = "Count number of members answering a queue", + .syntax = "QUEUE_MEMBER_COUNT()", + .desc = +"Returns the number of members currently associated with the specified queue.\n", + .read = queue_function_qac, +}; + +static struct ast_custom_function queuewaitingcount_function = { + .name = "QUEUE_WAITING_COUNT", + .synopsis = "Count number of calls currently waiting in a queue", + .syntax = "QUEUE_WAITING_COUNT()", + .desc = +"Returns the number of callers currently waiting in the specified queue.\n", + .read = queue_function_queuewaitingcount, +}; + +static struct ast_custom_function queuememberlist_function = { + .name = "QUEUE_MEMBER_LIST", + .synopsis = "Returns a list of interfaces on a queue", + .syntax = "QUEUE_MEMBER_LIST()", + .desc = +"Returns a comma-separated list of members associated with the specified queue.\n", + .read = queue_function_queuememberlist, +}; + +static int reload_queues(void) +{ + struct call_queue *q; + struct ast_config *cfg; + char *cat, *tmp; + struct ast_variable *var; + struct member *cur, *newm; + struct ao2_iterator mem_iter; + int new; + const char *general_val = NULL; + char parse[80]; + char *interface; + char *membername = NULL; + int penalty; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(interface); + AST_APP_ARG(penalty); + AST_APP_ARG(membername); + ); + + if (!(cfg = ast_config_load("queues.conf"))) { + ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n"); + return 0; + } + AST_LIST_LOCK(&queues); + use_weight=0; + /* Mark all non-realtime queues as dead for the moment */ + AST_LIST_TRAVERSE(&queues, q, list) { + if (!q->realtime) { + q->dead = 1; + q->found = 0; + } + } + + /* Chug through config file */ + cat = NULL; + while ((cat = ast_category_browse(cfg, cat)) ) { + if (!strcasecmp(cat, "general")) { + /* Initialize global settings */ + queue_persistent_members = 0; + if ((general_val = ast_variable_retrieve(cfg, "general", "persistentmembers"))) + queue_persistent_members = ast_true(general_val); + autofill_default = 0; + if ((general_val = ast_variable_retrieve(cfg, "general", "autofill"))) + autofill_default = ast_true(general_val); + montype_default = 0; + if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type"))) + if (!strcasecmp(general_val, "mixmonitor")) + montype_default = 1; + } else { /* Define queue */ + /* Look for an existing one */ + AST_LIST_TRAVERSE(&queues, q, list) { + if (!strcmp(q->name, cat)) + break; + } + if (!q) { + /* Make one then */ + if (!(q = alloc_queue(cat))) { + /* TODO: Handle memory allocation failure */ + } + new = 1; + } else + new = 0; + if (q) { + if (!new) + ast_mutex_lock(&q->lock); + /* Check if a queue with this name already exists */ + if (q->found) { + ast_log(LOG_WARNING, "Queue '%s' already defined! Skipping!\n", cat); + if (!new) + ast_mutex_unlock(&q->lock); + continue; + } + /* Re-initialize the queue, and clear statistics */ + init_queue(q); + clear_queue(q); + mem_iter = ao2_iterator_init(q->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + if (!cur->dynamic) { + cur->delme = 1; + } + ao2_ref(cur, -1); + } + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "member")) { + struct member tmpmem; + membername = NULL; + + /* Add a new member */ + ast_copy_string(parse, var->value, sizeof(parse)); + + AST_NONSTANDARD_APP_ARGS(args, parse, ','); + + interface = args.interface; + if (!ast_strlen_zero(args.penalty)) { + tmp = args.penalty; + while (*tmp && *tmp < 33) tmp++; + penalty = atoi(tmp); + if (penalty < 0) { + penalty = 0; + } + } else + penalty = 0; + + if (!ast_strlen_zero(args.membername)) { + membername = args.membername; + while (*membername && *membername < 33) membername++; + } + + /* Find the old position in the list */ + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK); + + newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0); + ao2_link(q->members, newm); + ao2_ref(newm, -1); + newm = NULL; + + if (cur) + ao2_ref(cur, -1); + else { + /* Add them to the master int list if necessary */ + add_to_interfaces(interface); + q->membercount++; + } + } else { + queue_set_param(q, var->name, var->value, var->lineno, 1); + } + } + + /* Free remaining members marked as delme */ + mem_iter = ao2_iterator_init(q->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + if (! cur->delme) { + ao2_ref(cur, -1); + continue; + } + + q->membercount--; + ao2_unlink(q->members, cur); + remove_from_interfaces(cur->interface); + ao2_ref(cur, -1); + } + + if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN) + rr_dep_warning(); + + if (new) { + AST_LIST_INSERT_HEAD(&queues, q, list); + } else + ast_mutex_unlock(&q->lock); + } + } + } + ast_config_destroy(cfg); + AST_LIST_TRAVERSE_SAFE_BEGIN(&queues, q, list) { + if (q->dead) { + AST_LIST_REMOVE_CURRENT(&queues, list); + if (!q->count) + destroy_queue(q); + else + ast_log(LOG_DEBUG, "XXX Leaking a little memory :( XXX\n"); + } else { + ast_mutex_lock(&q->lock); + mem_iter = ao2_iterator_init(q->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + if (cur->dynamic) + q->membercount++; + cur->status = ast_device_state(cur->interface); + ao2_ref(cur, -1); + } + ast_mutex_unlock(&q->lock); + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&queues); + return 1; +} + +static int __queues_show(struct mansession *s, int manager, int fd, int argc, char **argv) +{ + struct call_queue *q; + struct queue_ent *qe; + struct member *mem; + int pos, queue_show; + time_t now; + char max_buf[150]; + char *max; + size_t max_left; + float sl = 0; + char *term = manager ? "\r\n" : "\n"; + struct ao2_iterator mem_iter; + + time(&now); + if (argc == 2) + queue_show = 0; + else if (argc == 3) + queue_show = 1; + else + return RESULT_SHOWUSAGE; + + /* We only want to load realtime queues when a specific queue is asked for. */ + if (queue_show) { + load_realtime_queue(argv[2]); + } else if (ast_check_realtime("queues")) { + struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", (char *) NULL); + char *queuename; + if (cfg) { + for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) { + load_realtime_queue(queuename); + } + ast_config_destroy(cfg); + } + } + + AST_LIST_LOCK(&queues); + if (AST_LIST_EMPTY(&queues)) { + AST_LIST_UNLOCK(&queues); + if (queue_show) { + if (s) + astman_append(s, "No such queue: %s.%s",argv[2], term); + else + ast_cli(fd, "No such queue: %s.%s",argv[2], term); + } else { + if (s) + astman_append(s, "No queues.%s", term); + else + ast_cli(fd, "No queues.%s", term); + } + return RESULT_SUCCESS; + } + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + if (queue_show) { + if (strcasecmp(q->name, argv[2]) != 0) { + ast_mutex_unlock(&q->lock); + if (!AST_LIST_NEXT(q, list)) { + ast_cli(fd, "No such queue: %s.%s",argv[2], term); + break; + } + continue; + } + } + max_buf[0] = '\0'; + max = max_buf; + max_left = sizeof(max_buf); + if (q->maxlen) + ast_build_string(&max, &max_left, "%d", q->maxlen); + else + ast_build_string(&max, &max_left, "unlimited"); + sl = 0; + if (q->callscompleted > 0) + sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted); + if (s) + astman_append(s, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s", + q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, + q->callscompleted, q->callsabandoned,sl,q->servicelevel, term); + else + ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s", + q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, q->callscompleted, q->callsabandoned,sl,q->servicelevel, term); + if (ao2_container_count(q->members)) { + if (s) + astman_append(s, " Members: %s", term); + else + ast_cli(fd, " Members: %s", term); + mem_iter = ao2_iterator_init(q->members, 0); + while ((mem = ao2_iterator_next(&mem_iter))) { + max_buf[0] = '\0'; + max = max_buf; + max_left = sizeof(max_buf); + if (strcasecmp(mem->membername, mem->interface)) { + ast_build_string(&max, &max_left, " (%s)", mem->interface); + } + if (mem->penalty) + ast_build_string(&max, &max_left, " with penalty %d", mem->penalty); + if (mem->dynamic) + ast_build_string(&max, &max_left, " (dynamic)"); + if (mem->realtime) + ast_build_string(&max, &max_left, " (realtime)"); + if (mem->paused) + ast_build_string(&max, &max_left, " (paused)"); + ast_build_string(&max, &max_left, " (%s)", devstate2str(mem->status)); + if (mem->calls) { + ast_build_string(&max, &max_left, " has taken %d calls (last was %ld secs ago)", + mem->calls, (long) (time(NULL) - mem->lastcall)); + } else + ast_build_string(&max, &max_left, " has taken no calls yet"); + if (s) + astman_append(s, " %s%s%s", mem->membername, max_buf, term); + else + ast_cli(fd, " %s%s%s", mem->membername, max_buf, term); + ao2_ref(mem, -1); + } + } else if (s) + astman_append(s, " No Members%s", term); + else + ast_cli(fd, " No Members%s", term); + if (q->head) { + pos = 1; + if (s) + astman_append(s, " Callers: %s", term); + else + ast_cli(fd, " Callers: %s", term); + for (qe = q->head; qe; qe = qe->next) { + if (s) + astman_append(s, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", + pos++, qe->chan->name, (long) (now - qe->start) / 60, + (long) (now - qe->start) % 60, qe->prio, term); + else + ast_cli(fd, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", pos++, + qe->chan->name, (long) (now - qe->start) / 60, + (long) (now - qe->start) % 60, qe->prio, term); + } + } else if (s) + astman_append(s, " No Callers%s", term); + else + ast_cli(fd, " No Callers%s", term); + if (s) + astman_append(s, "%s", term); + else + ast_cli(fd, "%s", term); + ast_mutex_unlock(&q->lock); + if (queue_show) + break; + } + AST_LIST_UNLOCK(&queues); + return RESULT_SUCCESS; +} + +static int queue_show(int fd, int argc, char **argv) +{ + return __queues_show(NULL, 0, fd, argc, argv); +} + +static char *complete_queue(const char *line, const char *word, int pos, int state) +{ + struct call_queue *q; + char *ret = NULL; + int which = 0; + int wordlen = strlen(word); + + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, q, list) { + if (!strncasecmp(word, q->name, wordlen) && ++which > state) { + ret = ast_strdup(q->name); + break; + } + } + AST_LIST_UNLOCK(&queues); + + return ret; +} + +static char *complete_queue_show(const char *line, const char *word, int pos, int state) +{ + if (pos == 2) + return complete_queue(line, word, pos, state); + return NULL; +} + +/*!\brief callback to display queues status in manager + \addtogroup Group_AMI + */ +static int manager_queues_show(struct mansession *s, const struct message *m) +{ + char *a[] = { "queue", "show" }; + + __queues_show(s, 1, -1, 2, a); + astman_append(s, "\r\n\r\n"); /* Properly terminate Manager output */ + + return RESULT_SUCCESS; +} + +/* Dump queue status */ +static int manager_queues_status(struct mansession *s, const struct message *m) +{ + time_t now; + int pos; + const char *id = astman_get_header(m,"ActionID"); + const char *queuefilter = astman_get_header(m,"Queue"); + const char *memberfilter = astman_get_header(m,"Member"); + char idText[256] = ""; + struct call_queue *q; + struct queue_ent *qe; + float sl = 0; + struct member *mem; + struct ao2_iterator mem_iter; + + astman_send_ack(s, m, "Queue status will follow"); + time(&now); + AST_LIST_LOCK(&queues); + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + + /* List queue properties */ + if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) { + sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0); + astman_append(s, "Event: QueueParams\r\n" + "Queue: %s\r\n" + "Max: %d\r\n" + "Calls: %d\r\n" + "Holdtime: %d\r\n" + "Completed: %d\r\n" + "Abandoned: %d\r\n" + "ServiceLevel: %d\r\n" + "ServicelevelPerf: %2.1f\r\n" + "Weight: %d\r\n" + "%s" + "\r\n", + q->name, q->maxlen, q->count, q->holdtime, q->callscompleted, + q->callsabandoned, q->servicelevel, sl, q->weight, idText); + /* List Queue Members */ + mem_iter = ao2_iterator_init(q->members, 0); + while ((mem = ao2_iterator_next(&mem_iter))) { + if (ast_strlen_zero(memberfilter) || !strcmp(mem->interface, memberfilter)) { + astman_append(s, "Event: QueueMember\r\n" + "Queue: %s\r\n" + "Name: %s\r\n" + "Location: %s\r\n" + "Membership: %s\r\n" + "Penalty: %d\r\n" + "CallsTaken: %d\r\n" + "LastCall: %d\r\n" + "Status: %d\r\n" + "Paused: %d\r\n" + "%s" + "\r\n", + q->name, mem->membername, mem->interface, mem->dynamic ? "dynamic" : "static", + mem->penalty, mem->calls, (int)mem->lastcall, mem->status, mem->paused, idText); + } + ao2_ref(mem, -1); + } + /* List Queue Entries */ + pos = 1; + for (qe = q->head; qe; qe = qe->next) { + astman_append(s, "Event: QueueEntry\r\n" + "Queue: %s\r\n" + "Position: %d\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Wait: %ld\r\n" + "%s" + "\r\n", + q->name, pos++, qe->chan->name, + S_OR(qe->chan->cid.cid_num, "unknown"), + S_OR(qe->chan->cid.cid_name, "unknown"), + (long) (now - qe->start), idText); + } + } + ast_mutex_unlock(&q->lock); + } + + astman_append(s, + "Event: QueueStatusComplete\r\n" + "%s" + "\r\n",idText); + + AST_LIST_UNLOCK(&queues); + + + return RESULT_SUCCESS; +} + +static int manager_add_queue_member(struct mansession *s, const struct message *m) +{ + const char *queuename, *interface, *penalty_s, *paused_s, *membername; + int paused, penalty = 0; + + queuename = astman_get_header(m, "Queue"); + interface = astman_get_header(m, "Interface"); + penalty_s = astman_get_header(m, "Penalty"); + paused_s = astman_get_header(m, "Paused"); + membername = astman_get_header(m, "MemberName"); + + if (ast_strlen_zero(queuename)) { + astman_send_error(s, m, "'Queue' not specified."); + return 0; + } + + if (ast_strlen_zero(interface)) { + astman_send_error(s, m, "'Interface' not specified."); + return 0; + } + + if (ast_strlen_zero(penalty_s)) + penalty = 0; + else if (sscanf(penalty_s, "%d", &penalty) != 1 || penalty < 0) + penalty = 0; + + if (ast_strlen_zero(paused_s)) + paused = 0; + else + paused = abs(ast_true(paused_s)); + + switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members)) { + case RES_OKAY: + ast_queue_log(queuename, "MANAGER", interface, "ADDMEMBER", "%s", ""); + astman_send_ack(s, m, "Added interface to queue"); + break; + case RES_EXISTS: + astman_send_error(s, m, "Unable to add interface: Already there"); + break; + case RES_NOSUCHQUEUE: + astman_send_error(s, m, "Unable to add interface to queue: No such queue"); + break; + case RES_OUTOFMEMORY: + astman_send_error(s, m, "Out of memory"); + break; + } + + return 0; +} + +static int manager_remove_queue_member(struct mansession *s, const struct message *m) +{ + const char *queuename, *interface; + + queuename = astman_get_header(m, "Queue"); + interface = astman_get_header(m, "Interface"); + + if (ast_strlen_zero(queuename) || ast_strlen_zero(interface)) { + astman_send_error(s, m, "Need 'Queue' and 'Interface' parameters."); + return 0; + } + + switch (remove_from_queue(queuename, interface)) { + case RES_OKAY: + ast_queue_log(queuename, "MANAGER", interface, "REMOVEMEMBER", "%s", ""); + astman_send_ack(s, m, "Removed interface from queue"); + break; + case RES_EXISTS: + astman_send_error(s, m, "Unable to remove interface: Not there"); + break; + case RES_NOSUCHQUEUE: + astman_send_error(s, m, "Unable to remove interface from queue: No such queue"); + break; + case RES_OUTOFMEMORY: + astman_send_error(s, m, "Out of memory"); + break; + case RES_NOT_DYNAMIC: + astman_send_error(s, m, "Member not dynamic"); + break; + } + + return 0; +} + +static int manager_pause_queue_member(struct mansession *s, const struct message *m) +{ + const char *queuename, *interface, *paused_s; + int paused; + + interface = astman_get_header(m, "Interface"); + paused_s = astman_get_header(m, "Paused"); + queuename = astman_get_header(m, "Queue"); /* Optional - if not supplied, pause the given Interface in all queues */ + + if (ast_strlen_zero(interface) || ast_strlen_zero(paused_s)) { + astman_send_error(s, m, "Need 'Interface' and 'Paused' parameters."); + return 0; + } + + paused = abs(ast_true(paused_s)); + + if (set_member_paused(queuename, interface, paused)) + astman_send_error(s, m, "Interface not found"); + else + astman_send_ack(s, m, paused ? "Interface paused successfully" : "Interface unpaused successfully"); + return 0; +} + +static int handle_queue_add_member(int fd, int argc, char *argv[]) +{ + char *queuename, *interface, *membername = NULL; + int penalty; + + if ((argc != 6) && (argc != 8) && (argc != 10)) { + return RESULT_SHOWUSAGE; + } else if (strcmp(argv[4], "to")) { + return RESULT_SHOWUSAGE; + } else if ((argc == 8) && strcmp(argv[6], "penalty")) { + return RESULT_SHOWUSAGE; + } else if ((argc == 10) && strcmp(argv[8], "as")) { + return RESULT_SHOWUSAGE; + } + + queuename = argv[5]; + interface = argv[3]; + if (argc >= 8) { + if (sscanf(argv[7], "%d", &penalty) == 1) { + if (penalty < 0) { + ast_cli(fd, "Penalty must be >= 0\n"); + penalty = 0; + } + } else { + ast_cli(fd, "Penalty must be an integer >= 0\n"); + penalty = 0; + } + } else { + penalty = 0; + } + + if (argc >= 10) { + membername = argv[9]; + } + + switch (add_to_queue(queuename, interface, membername, penalty, 0, queue_persistent_members)) { + case RES_OKAY: + ast_queue_log(queuename, "CLI", interface, "ADDMEMBER", "%s", ""); + ast_cli(fd, "Added interface '%s' to queue '%s'\n", interface, queuename); + return RESULT_SUCCESS; + case RES_EXISTS: + ast_cli(fd, "Unable to add interface '%s' to queue '%s': Already there\n", interface, queuename); + return RESULT_FAILURE; + case RES_NOSUCHQUEUE: + ast_cli(fd, "Unable to add interface to queue '%s': No such queue\n", queuename); + return RESULT_FAILURE; + case RES_OUTOFMEMORY: + ast_cli(fd, "Out of memory\n"); + return RESULT_FAILURE; + default: + return RESULT_FAILURE; + } +} + +static char *complete_queue_add_member(const char *line, const char *word, int pos, int state) +{ + /* 0 - queue; 1 - add; 2 - member; 3 - ; 4 - to; 5 - ; 6 - penalty; 7 - ; 8 - as; 9 - */ + switch (pos) { + case 3: /* Don't attempt to complete name of interface (infinite possibilities) */ + return NULL; + case 4: /* only one possible match, "to" */ + return state == 0 ? ast_strdup("to") : NULL; + case 5: /* */ + return complete_queue(line, word, pos, state); + case 6: /* only one possible match, "penalty" */ + return state == 0 ? ast_strdup("penalty") : NULL; + case 7: + if (state < 100) { /* 0-99 */ + char *num; + if ((num = ast_malloc(3))) { + sprintf(num, "%d", state); + } + return num; + } else { + return NULL; + } + case 8: /* only one possible match, "as" */ + return state == 0 ? ast_strdup("as") : NULL; + case 9: /* Don't attempt to complete name of member (infinite possibilities) */ + return NULL; + default: + return NULL; + } +} + +static int handle_queue_remove_member(int fd, int argc, char *argv[]) +{ + char *queuename, *interface; + + if (argc != 6) { + return RESULT_SHOWUSAGE; + } else if (strcmp(argv[4], "from")) { + return RESULT_SHOWUSAGE; + } + + queuename = argv[5]; + interface = argv[3]; + + switch (remove_from_queue(queuename, interface)) { + case RES_OKAY: + ast_queue_log(queuename, "CLI", interface, "REMOVEMEMBER", "%s", ""); + ast_cli(fd, "Removed interface '%s' from queue '%s'\n", interface, queuename); + return RESULT_SUCCESS; + case RES_EXISTS: + ast_cli(fd, "Unable to remove interface '%s' from queue '%s': Not there\n", interface, queuename); + return RESULT_FAILURE; + case RES_NOSUCHQUEUE: + ast_cli(fd, "Unable to remove interface from queue '%s': No such queue\n", queuename); + return RESULT_FAILURE; + case RES_OUTOFMEMORY: + ast_cli(fd, "Out of memory\n"); + return RESULT_FAILURE; + case RES_NOT_DYNAMIC: + ast_cli(fd, "Member not dynamic\n"); + return RESULT_FAILURE; + default: + return RESULT_FAILURE; + } +} + +static char *complete_queue_remove_member(const char *line, const char *word, int pos, int state) +{ + int which = 0; + struct call_queue *q; + struct member *m; + struct ao2_iterator mem_iter; + + /* 0 - queue; 1 - remove; 2 - member; 3 - ; 4 - from; 5 - */ + if (pos > 5 || pos < 3) + return NULL; + if (pos == 4) /* only one possible match, 'from' */ + return state == 0 ? ast_strdup("from") : NULL; + + if (pos == 5) /* No need to duplicate code */ + return complete_queue(line, word, pos, state); + + /* here is the case for 3, */ + if (!AST_LIST_EMPTY(&queues)) { /* XXX unnecessary ? the traverse does that for us */ + AST_LIST_TRAVERSE(&queues, q, list) { + ast_mutex_lock(&q->lock); + mem_iter = ao2_iterator_init(q->members, 0); + while ((m = ao2_iterator_next(&mem_iter))) { + if (++which > state) { + char *tmp; + ast_mutex_unlock(&q->lock); + tmp = ast_strdup(m->interface); + ao2_ref(m, -1); + return tmp; + } + ao2_ref(m, -1); + } + ast_mutex_unlock(&q->lock); + } + } + + return NULL; +} + +static char queue_show_usage[] = +"Usage: queue show\n" +" Provides summary information on a specified queue.\n"; + +static char qam_cmd_usage[] = +"Usage: queue add member to [penalty ]\n"; + +static char qrm_cmd_usage[] = +"Usage: queue remove member from \n"; + +static struct ast_cli_entry cli_show_queue_deprecated = { + { "show", "queue", NULL }, + queue_show, NULL, + NULL, complete_queue_show }; + +static struct ast_cli_entry cli_add_queue_member_deprecated = { + { "add", "queue", "member", NULL }, + handle_queue_add_member, NULL, + NULL, complete_queue_add_member }; + +static struct ast_cli_entry cli_remove_queue_member_deprecated = { + { "remove", "queue", "member", NULL }, + handle_queue_remove_member, NULL, + NULL, complete_queue_remove_member }; + +static struct ast_cli_entry cli_queue[] = { + /* Deprecated */ + { { "show", "queues", NULL }, + queue_show, NULL, + NULL, NULL }, + + { { "queue", "show", NULL }, + queue_show, "Show status of a specified queue", + queue_show_usage, complete_queue_show, &cli_show_queue_deprecated }, + + { { "queue", "add", "member", NULL }, + handle_queue_add_member, "Add a channel to a specified queue", + qam_cmd_usage, complete_queue_add_member, &cli_add_queue_member_deprecated }, + + { { "queue", "remove", "member", NULL }, + handle_queue_remove_member, "Removes a channel from a specified queue", + qrm_cmd_usage, complete_queue_remove_member, &cli_remove_queue_member_deprecated }, +}; + +static int unload_module(void) +{ + int res; + + if (device_state.thread != AST_PTHREADT_NULL) { + device_state.stop = 1; + ast_mutex_lock(&device_state.lock); + ast_cond_signal(&device_state.cond); + ast_mutex_unlock(&device_state.lock); + pthread_join(device_state.thread, NULL); + } + + ast_cli_unregister_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry)); + res = ast_manager_unregister("QueueStatus"); + res |= ast_manager_unregister("Queues"); + res |= ast_manager_unregister("QueueAdd"); + res |= ast_manager_unregister("QueueRemove"); + res |= ast_manager_unregister("QueuePause"); + res |= ast_unregister_application(app_aqm); + res |= ast_unregister_application(app_rqm); + res |= ast_unregister_application(app_pqm); + res |= ast_unregister_application(app_upqm); + res |= ast_unregister_application(app_ql); + res |= ast_unregister_application(app); + res |= ast_custom_function_unregister(&queueagentcount_function); + res |= ast_custom_function_unregister(&queuemembercount_function); + res |= ast_custom_function_unregister(&queuememberlist_function); + res |= ast_custom_function_unregister(&queuewaitingcount_function); + ast_devstate_del(statechange_queue, NULL); + + ast_module_user_hangup_all(); + + clear_and_free_interfaces(); + + return res; +} + +static int load_module(void) +{ + int res; + + if (!reload_queues()) + return AST_MODULE_LOAD_DECLINE; + + if (queue_persistent_members) + reload_queue_members(); + + ast_mutex_init(&device_state.lock); + ast_cond_init(&device_state.cond, NULL); + ast_pthread_create(&device_state.thread, NULL, device_state_thread, NULL); + + ast_cli_register_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry)); + res = ast_register_application(app, queue_exec, synopsis, descrip); + res |= ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip); + res |= ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip); + res |= ast_register_application(app_pqm, pqm_exec, app_pqm_synopsis, app_pqm_descrip); + res |= ast_register_application(app_upqm, upqm_exec, app_upqm_synopsis, app_upqm_descrip); + res |= ast_register_application(app_ql, ql_exec, app_ql_synopsis, app_ql_descrip); + res |= ast_manager_register("Queues", 0, manager_queues_show, "Queues"); + res |= ast_manager_register("QueueStatus", 0, manager_queues_status, "Queue Status"); + res |= ast_manager_register("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue."); + res |= ast_manager_register("QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue."); + res |= ast_manager_register("QueuePause", EVENT_FLAG_AGENT, manager_pause_queue_member, "Makes a queue member temporarily unavailable"); + res |= ast_custom_function_register(&queueagentcount_function); + res |= ast_custom_function_register(&queuemembercount_function); + res |= ast_custom_function_register(&queuememberlist_function); + res |= ast_custom_function_register(&queuewaitingcount_function); + res |= ast_devstate_add(statechange_queue, NULL); + + return res; +} + +static int reload(void) +{ + reload_queues(); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "True Call Queueing", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); + diff --git a/asterisk/apps/app_random.c b/asterisk/apps/app_random.c new file mode 100644 index 00000000..0cfdc500 --- /dev/null +++ b/asterisk/apps/app_random.c @@ -0,0 +1,108 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (c) 2003 - 2005 Tilghman Lesher. All rights reserved. + * + * Tilghman Lesher + * + * This code is released by the author with no restrictions on usage or distribution. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + */ + +/*! \file + * + * \brief Random application + * + * \author Tilghman Lesher + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 40722 $") + +#include +#include +#include +#include + +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" + +/*! \todo The Random() app should be removed from trunk following the release of 1.4 */ + +static char *app_random = "Random"; + +static char *random_synopsis = "Conditionally branches, based upon a probability"; + +static char *random_descrip = +"Random([probability]:[[context|]extension|]priority)\n" +" probability := INTEGER in the range 1 to 100\n" +"DEPRECATED: Use GotoIf($[${RAND(1,100)} > ]?