diff --git a/trunk/.cleancount b/trunk/.cleancount new file mode 100644 index 0000000000..bb95160cb6 --- /dev/null +++ b/trunk/.cleancount @@ -0,0 +1 @@ +33 diff --git a/trunk/BUGS b/trunk/BUGS new file mode 100644 index 0000000000..96b55e655b --- /dev/null +++ b/trunk/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/trunk/CHANGES b/trunk/CHANGES new file mode 100644 index 0000000000..00757c4b15 --- /dev/null +++ b/trunk/CHANGES @@ -0,0 +1,496 @@ +------------------------------------------------------------------------------ +--- Functionality changes since Asterisk 1.4-beta was branched ---------------- +------------------------------------------------------------------------------- + +AMI - The manager (TCP/TLS/HTTP) +-------------------------------- + * Manager has undergone a lot of changes, all of them documented + in doc/manager_1_1.txt + * Manager version has changed to 1.1 + * Added a new action 'CoreShowChannels' to list currently defined channels + and some information about them. + * Added a new action 'SIPshowregistry' to list SIP registrations. + * Added TLS support for the manager interface and HTTP server + * Added the URI redirect option for the built-in HTTP server + * The output of CallerID in Manager events is now more consistent. + CallerIDNum is used for number and CallerIDName for name. + * Enable https support for builtin web server. + See configs/http.conf.sample for details. + * Added a new action, GetConfigJSON, which can return the contents of an + Asterisk configuration file in JSON format. This is intended to help + improve the performance of AJAX applications using the manager interface + over HTTP. + * SIP and IAX manager events now use "ChannelType" in all cases where we + indicate channel driver. Previously, we used a mixture of "Channel" + and "ChannelDriver" headers. + * Added a "Bridge" action which allows you to bridge any two channels that + are currently active on the system. + * Added a "ListAllVoicemailUsers" action that allows you to get a list of all + the voicemail users setup. + * Added 'DBDel' and 'DBDelTree' manager commands. + * cdr_manager now reports events via the "cdr" level, separating it from + the very verbose "call" level. + * Manager users are now stored in memory. If you change the manager account + list (delete or add accounts) you need to reload manager. + * Added Masquerade manager event for when a masquerade happens between + two channels. + * Added "manager reload" command for the CLI + * Lots of commands that only provided information are now allowed under the + Reporting privilege, instead of only under Call or System. + * The IAX* commands now require either System or Reporting privilege, to + mirror the privileges of the SIP* commands. + +Dialplan functions +------------------ + * Added the DEVICE_STATE() dialplan function which allows retrieving any device + state in the dialplan, as well as creating custom device states that are + controllable from the dialplan. + * Extend CALLERID() function with "pres" and "ton" parameters to + fetch string representation of calling number presentation indicator + and numeric representation of type of calling number value. + * MailboxExists converted to dialplan function + * A new option to Dial() for telling IP phones not to count the call + as "missed" when dial times out and cancels. + * Added LOCK(), TRYLOCK(), and UNLOCK(), which provide a single level dialplan + mutex. No deadlocks are possible, as LOCK() only allows a single lock to be + held for any given channel. Also, locks are automatically freed when a + channel is hung up. + * Added HINT() dialplan function that allows retrieving hint information. + Hints are mappings between extensions and devices for the sake of + determining the state of an extension. This function can retrieve the list + of devices or the name associated with a hint. + * Added EXTENSION_STATE() dialplan function which allows retrieving the state + of any extension. + * Added SYSINFO() dialplan function which allows retrieval of system information + * Added a new dialplan function, DIALPLAN_EXISTS(), which allows you to check for + the existence of a dialplan target. + * Added two new dialplan functions, TOUPPER and TOLOWER, which convert a string to + upper and lower case, respectively. + +CLI Changes +----------- + * New CLI command "core show hint" (usage: core show hint ) + * New CLI command "core show settings" + * Added 'core show channels count' CLI command. + * Added the ability to set the core debug and verbose values on a per-file basis. + * Added 'queue pause member' and 'queue unpause member' CLI commands + * Ability to set process limits ("ulimit") without restarting Asterisk + * Enhanced "agi debug" to print the channel name as a prefix to the debug + output to make debugging on busy systems much easier. + * New CLI commands "dialplan set extenpatternmatching true/false" + * New CLI command: "core set chanvar" to set a channel variable from the CLI. + * Added an easy way to execute Asterisk CLI commands at startup. Any commands + listed in the startup_commands file in the Asterisk configuration directory + will get executed. + +SIP changes +----------- + * Improved NAT and STUN support. + chan_sip now can use port numbers in bindaddr, externip and externhost + options, as well as contact a STUN server to detect its external address + for the SIP socket. See sip.conf.sample, 'NAT' section. + * The default SIP useragent= identifier now includes the Asterisk version + * A new option, match_auth_username in sip.conf changes the matching of incoming requests. + If set, and the incoming request carries authentication info, + the username to match in the users list is taken from the Digest header + rather than from the From: field. This feature is considered experimental. + * The "musiconhold" and "musicclass" settings in sip.conf are now removed, + since they where replaced by "mohsuggest" and "mohinterpret" in version 1.4 + * The "localmask" setting was removed in version 1.2 and the reminder about it + being removed is now also removed. + * A new option "busylevel" for setting a level of calls where asterisk reports + a device as busy, to separate it from call-limit. This value is also added + to the SIP_PEER dialplan function. + * A new realtime family called "sipregs" is now supported to store SIP registration + data. If this family is defined, "sippeers" will be used for configuration and + "sipregs" for registrations. If it's not defined, "sippeers" will be used for + registration data, as before. + * The SIPPEER function have new options for port address, call and pickup groups + * Added support for T.140 realtime text in SIP/RTP + * The "checkmwi" option has been removed from sip.conf, as it is no longer + required due to the restructuring of how MWI is handled. See the descriptions + in this file of the "pollmailboxes" and "pollfreq" options to voicemail.conf + for more information. + * Added rtpdest option to CHANNEL() dialplan function. + * Added SIPREFERRINGCONTEXT and SIPREFERREDBYHDR variables which are set when a transfer takes place. + * SIP now adds a header to the CANCEL if the call was answered by another phone + in the same dial command, or if the new c option in dial() is used. + * The new default is that 100 Trying is not sent on REGISTER attempts as the RFC specifically + states it is not needed. For phones, however, that do require it the "registertrying" option + has been added so it can be enabled. + * A new option called "callcounter" (global/peer/user level) enables call counters needed + for better status reports needed for queues and SIP subscriptions. (Call-Limit was previously + used to enable this functionality). + * New settings for timer T1 and timer B on a global level or per device. This makes it + possible to force timeout faster on non-responsive SIP servers. These settings are + considered advanced, so don't use them unless you have a problem. + * Added a dial string option to be able to set the To: header in an INVITE to any + SIP uri. + * Added a new global and per-peer option, qualifyfreq, which allows you to configure + the qualify frequency. + * Added SIP Session Timers support (RFC 4028). This prevents stuck SIP sessions that + were not properly torn down due to network or endpoint failures during an established + SIP session. + * Added TCP and TLS support for SIP. See doc/siptls.txt and configs/sip.conf.sample for + more information on how it is used. + +IAX2 changes +------------ + * Added the trunkmaxsize configuration option to chan_iax2. + * Added the srvlookup option to iax.conf + * Added support for OSP. The token is set and retrieved through the CHANNEL() + dialplan function. + +XMPP Google Talk/Jingle changes +------------------------------- + * Added the bindaddr option to gtalk.conf. + +Skinny changes +------------- + * Added skinny show device, skinny show line, and skinny show settings CLI commands. + * Proper codec support in chan_skinny. + * Added settings for IP and Ethernet QoS requests + +MGCP changes +------------ + * Added separate settings for media QoS in mgcp.conf + +Console Channel Driver changes +------------------- + * Added experimental support for video send & receive to chan_oss. + This requires SDL and ffmpeg/avcodec, plus Video4Linux or X11 to act as + a video source. + +Phone channel changes (chan_phone) +---------------------------------- + * Added G729 passthrough support to chan_phone for Sigma Designs boards. + +H.323 channel Changes +--------------------- + * H323 remote hold notification support added (by NOTIFY message + and/or H.450 supplementary service) + +Local channel changes +--------------------- + * The device state functionality in the Local channel driver has been updated + to indicate INUSE or NOT_INUSE when a Local channel is being used as opposed + to just UNKNOWN if the extension exists. + * Added jitterbuffer support for chan_local. This allows you to use the + generic jitterbuffer on incoming calls going to Asterisk applications. + For example, this would allow you to use a jitterbuffer for an incoming + SIP call to Voicemail by putting a Local channel in the middle. This + feature is enabled by using the 'j' option in the Dial string to the Local + channel in conjunction with the existing 'n' option for local channels. + +Zaptel channel driver (chan_zap) Changes +---------------------------------------- + * SS7 support in chan_zap (via libss7 library) + * In India, some carriers transmit CID via dtmf. Some code has been added + that will handle some situations. The cidstart=polarity_IN choice has been added for + those carriers that transmit CID via dtmf after a polarity change. + * CID matching information is now shown when doing 'dialplan show'. + * Added zap show version CLI command to chan_zap. + * Added setvar support to zapata.conf channel entries. + * Added two new options: mwimonitor and mwimonitornotify. These options allow + you to enable MWI monitoring on FXO lines. When the MWI state changes, + the script specified in the mwimonitornotify option is executed. An internal + event indicating the new state of the mailbox is also generated, so that + the normal MWI facilities in Asterisk work as usual. + * Added signalling type 'auto', which attempts to use the same signalling type + for a channel as configured in Zaptel. This is primarily designed for analog + ports, but will also work for digital ports that are configured for FXS or FXO + signalling types. This mode is also the default now, so if your zapata.conf + does not specify signalling for a channel (which is unlikely as the sample + configuration file has always recommended specifying it for every channel) then + the 'auto' mode will be used for that channel if possible. + * Added a 'zap set dnd' command to allow CLI control of the Do-Not-Disturb + state for a channel; also ensured that the DNDState Manager event is + emitted no matter how the DND state is set or cleared. + +New Channel Drivers +------------------- + * Added a new channel driver, chan_unistim. See doc/unistim.txt and + configs/unistim.conf.sample for details. This new channel driver allows + you to use Nortel i2002, i2004, and i2050 phones with Asterisk. + * Added a new channel driver, chan_console, which uses portaudio as a cross + platform audio interface. It was written as a channel driver that would + work with Mac CoreAudio, but portaudio supports a number of other audio + interfaces, as well. Note that this channel driver requires v19 or higher + of portaudio; older versions have a different API. + +DUNDi changes +------------- + * Added the ability to specify arguments to the Dial application when using + the DUNDi switch in the dialplan. + * Added the ability to set weights for responses dynamically. This can be + done using a global variable or a dialplan function. Using the SHELL() + function would allow you to have an external script set the weight for + each response. + * Added two new dialplan functions, DUNDIQUERY and DUNDIRESULT. These + functions will allow you to initiate a DUNDi query from the dialplan, + find out how many results there are, and access each one. + +ENUM changes +------------ + * Added two new dialplan functions, ENUMQUERY and ENUMRESULT. These + functions will allow you to initiate an ENUM lookup from the dialplan, + and Asterisk will cache the results. ENUMRESULT can be used to access + the results without doing multiple DNS queries. + +Voicemail Changes +----------------- + * Added the ability to customize which sound files are used for some of the + prompts within the Voicemail application by changing them in voicemail.conf + * Added the ability for the "voicemail show users" CLI command to show users + configured by the dynamic realtime configuration method. + * MWI (Message Waiting Indication) handling has been significantly + restructured internally to Asterisk. It is now totally event based + instead of polling based. The voicemail application will notify other + modules that have subscribed to MWI events when something in the mailbox + changes. + This also means that if any other entity outside of Asterisk is changing + the contents of mailboxes, then the voicemail application still needs to + poll for changes. Examples of situations that would require this option + are web interfaces to voicemail or an email client in the case of using + IMAP storage. So, two new options have been added to voicemail.conf + to account for this: "pollmailboxes" and "pollfreq". See the sample + configuration file for details. + * Added "tw" language support + * Added support for storage of greetings using an IMAP server + * Added ability to customize forward, reverse, stop, and pause keys for message playback + * SMDI is now enabled in voicemail using the smdienable option. + * A "lockmode" option has been added to asterisk.conf to configure the file + locking method used for voicemail, and potentially other things in the + future. The default is the old behavior, lockfile. However, there is a + new method, "flock", that uses a different method for situations where the + lockfile will not work, such as on SMB/CIFS mounts. + * Added the ability to backup deleted messages, to ease recovery in the case + that a user accidentally deletes a message, and discovers that they need it. + +Queue changes +------------- + * Added the general option 'shared_lastcall' so that member's wrapuptime may be + used across multiple queues. + * Added QUEUE_VARIABLES function to set queue variables added setqueuevar and + setqueueentryvar options for each queue, see queues.conf.sample for details. + * Added keepstats option to queues.conf which will keep queue + statistics during a reload. + * setinterfacevar option in queues.conf also now sets a variable + called MEMBERNAME which contains the member's name. + * Added 'Strategy' field to manager event QueueParams which represents + the queue strategy in use. + * Added option to run macro when a queue member is connected to a caller, + see queues.conf.sample for details. + * app_queue now has a 'loose' option which is almost exactly like 'strict' except it + does not count paused queue members as unavailable. + * Added min-announce-frequency option to queues.conf which allows you to control the + minimum amount of time between queue announcements for use when the caller's queue + position changes frequently. + * Added additional information to EXITWITHTIMEOUT and EXITWITHKEY events in the + queue log. + * Added ability for non-realtime queues to have realtime members + * Added the "linear" strategy to queues. + * Added the "wrandom" strategy to queues. + * Added new channel variable QUEUE_MIN_PENALTY + * QUEUE_MAX_PENALTY and QUEUE_MIN_PENALTY may be adjusted in mid-call by defining + rules in queuerules.conf. See configs/queuerules.conf.sample for details + * Added a new parameter for member definition, called state_interface. This may be + used so that a member may be called via one interface but have a different interface's + device state reported. + +MeetMe Changes +-------------- + * The 'o' option to provide an optimization has been removed and its functionality + has been enabled by default. + * When a conference is created, the UNIQUEID of the channel that caused it to be + created is stored. Then, every channel that joins the conference will have the + MEETMEUNIQUEID channel variable set with this ID. This can be used to relate + callers that come and go from long standing conferences. + * Added a new application, MeetMeChannelAdmin, which is similar to MeetMeAdmin, + except it does operations on a channel by name, instead of number in a conference. + This is a very useful feature in combination with the 'X' option to ChanSpy. + * Added 'C' option to Meetme which causes a caller to continue in the dialplan + when kicked out. + * Added new RealTime functionality to provide support for scheduled conferencing. + This includes optional messages to the caller if they attempt to join before + the schedule start time, or to allow the caller to join the conference early. + Also included is optional support for limiting the number of callers per + RealTime conference. + * Added the S() and L() options to the MeetMe application. These are pretty + much identical to the S() and L() options to Dial(). They let you set + timeouts for the conference, as well as have warning sounds played to + let the caller know how much time is left, and when it is running out. + * Added the ability to do "meetme concise" with the "meetme" CLI command. + This extends the concise capabilities of this CLI command to include + listing all conferences, instead of an addition to the other sub commands + for the "meetme" command. + * Added the ability to specify the music on hold class used to play into the + conference when there is only one member and the M option is used. + +Other Dialplan Application Changes +---------------------------------- + * Argument support for Gosub application + * From the to-do lists: straighten out the app timeout args: + Wait() app now really does 0.3 seconds- was truncating arg to an int. + WaitExten() same as Wait(). + Congestion() - Now takes floating pt. argument. + Busy() - now takes floating pt. argument. + Read() - timeout now can be floating pt. + WaitForRing() now takes floating pt timeout arg. + SpeechBackground() -- clarified in the docstrings that the timeout is an integer seconds. + * Added 's' option to Page application. + * Added 'E' and 'V' commands to ExternalIVR. + * Added 'o' and 'X' options to Chanspy. + * Added a new dialplan application, Bridge, which allows you to bridge the + calling channel to any other active channel on the system. + * Added the ability to specify a music on hold class to play instead of ringing + for the SLATrunk application. + * The Read application no longer exits the dialplan on error. Instead, it sets + READSTATUS to ERROR, which you can catch and handle separately. + * Added 'm' option to Directory, which lists out names, 8 at a time, instead + of asking for verification of each name, one at a time. + * Privacy() no longer uses privacy.conf, as all options are specifyable as + direct options to the app. + * AMD() has a new "maximum word length" option. "show application AMD" from the CLI + for more details + +Music On Hold Changes +--------------------- + * A new option, "digit", has been added for music on hold classes in + musiconhold.conf. If this is set for a music on hold class, a caller + listening to music on hold can press this digit to switch to listening + to this music on hold class. + * Support for realtime music on hold has been added. + * In conjunction with the realtime music on hold, a general section has + been added to musiconhold.conf, its sole variable is cachertclasses. If this + is set, then music on hold classes found in realtime will be cached in memory. + +AEL Changes +----------- + * AEL upgraded to use the Gosub with Arguments instead + of Macro application, to hopefully reduce the problems + seen with the artificially low stack ceiling that + Macro bumps into. Macros can only call other Macros + to a depth of 7. Tests run using gosub, show depths + limited only by virtual memory. A small test demonstrated + recursive call depths of 100,000 without problems. + -- in addition to this, all apps that allowed a macro + to be called, as in Dial, queues, etc, are now allowing + a gosub call in similar fashion. + * AEL now generates LOCAL(argname) declarations when it + Set()'s the each arg name to the value of ${ARG1}, ${ARG2), + etc. That makes the arguments local in scope. The user + can define their own local variables in macros, now, + by saying "local myvar=someval;" or using Set() in this + fashion: Set(LOCAL(myvar)=someval); ("local" is now + an AEL keyword). + * utils/conf2ael introduced. Will convert an extensions.conf + file into extensions.ael. Very crude and unfinished, but + will be improved as time goes by. Should be useful for a + first pass at conversion. + * aelparse will now read extensions.conf to see if a referenced + macro or context is there before issueing a warning. + +Call Features (res_features) Changes +------------------------------------ + * Added the parkedcalltransfers option to features.conf + * The built-in method for doing attended transfers has been updated to + include some new options that allow you to have the transferee sent + back to the person that did the transfer if the transfer is not successful. + See the options "atxferdropcall", "atxferloopdelay", and "atxfercallbackretries" + in features.conf.sample. + * Added support for configuring named groups of custom call features in + features.conf. This means that features can be written a single time, and + then mapped into groups of features for different key mappings or easier + access control. + * Updated the ParkedCall application to allow you to not specify a parking + extension. If you don't specify a parking space to pick up, it will grab + the first one available. + +Language Support Changes +------------------------ + * Brazilian Portuguese (pt-BR) in VM, and say.c was added + * Added support for the Hungarian language for saying numbers, dates, and times. + +AGI Changes +----------- + * Added SPEECH commands for speech recognition. A complete listing can be found + using agi show. + +Logger changes +-------------- + * Added rotatestrategy option to logger.conf, along with two new options: + "timestamp" which will use the time to name the logger files instead of + sequence number; and "rotate", which rotates the names of the logfiles, + similar to the way syslog rotates files. + * Added exec_after_rotate option to logger.conf, which allows a system + command to be run after rotation. This is primarily useful with + rotatestrategry=rotate, to allow a limit on the number of logfiles kept + and to ensure that the oldest log file gets deleted. + * Added realtime support for the queue log + +Miscellaneous New Modules +------------------------- + * Added a new CDR module, cdr_sqlite3_custom. + * Added a new realtime configuration module, res_config_sqlite + * Added a new codec translation module, codec_resample, which re-samples + signed linear audio between 8 kHz and 16 kHz to help support wideband + codecs. + * Added a new module, res_phoneprov, which allows auto-provisioning of phones + based on configuration templates that use Asterisk dialplan function and + variable substitution. It should be possible to create phone profiles and + templates that work for the majority of phones provisioned over http. It + is currently only intended to provision a single user account per phone. + An example profile and set of templates for Polycom phones is provided. + NOTE: Polycom firmware is not included, but should be placed in + AST_DATA_DIR/phoneprov/configs to match up with the included templates. + * Added a new module, app_jack, which provides interfaces to JACK, the Jack + Audio Connection Kit (http://www.jackaudio.org/). Two interfaces are + provided; there is a JACK() application, and a JACK_HOOK() function. Both + interfaces create an input and output JACK port. The application makes + these ports the endpoint of the call. The audio coming from the channel + goes out the output port and whatever comes back in on the input port is + what gets sent to the channel. The JACK_HOOK() function turns on a JACK + audiohook on the channel. This lets you run the audio coming from a + channel through JACK, and whatever comes back in is what gets forwarded + on as the channel's audio. This is very useful for building custom + vocoders or doing recording or analysis of the channel's audio in another + application. + * Added a new module, res_config_curl, which permits using a HTTP POST url + to retrieve, create, update, and delete realtime information from a remote + web server. Note that this module requires func_curl.so to be loaded for + backend functionality. + +Miscellaneous +------------- + * Ability to use libcap to set high ToS bits when non-root + on Linux. If configure is unable to find libcap then you + can use --with-cap to specify the path. + * Added maxfiles option to options section of asterisk.conf which allows you to specify + what Asterisk should set as the maximum number of open files when it loads. + * Added the jittertargetextra configuration option. + * The cdr_manager module has a [mappings] feature, like cdr_custom, + to add fields to the manager event from the CDR variables. + * Added support for setting the CoS for VLAN traffic (802.1p). See the sample + configuration files for the IP channel drivers. The new option is "cos". + This information is also documented in doc/qos.tex, or the IP Quality of Service + section of asterisk.pdf. + * When originating a call using AMI or pbx_spool that fails the reason for failure + will now be available in the failed extension using the REASON dialplan variable. + * Added support for reading the TOUCH_MONITOR_PREFIX channel variable. + It allows you to configure a prefix for auto-monitor recordings. + * Added support for writing and running your dialplan in lua. See + configs/extensions.lua.sample for examples of how to do this. + * A new extension pattern matching algorithm, based on a trie, is introduced + here, that could noticeably speed up mid-sized to large dialplans. + It is NOT used by default, as duplicating the behaviour of the old pattern + matcher is still under development. A config file option, in extensions.conf, + in the [general] section, called "extenpatternmatchingnew", is by default + set to false; setting that to true will force the use of the new algorithm. + Also, the cli commands "dialplan set extenpatternmatchingnew true/false" can + be used to switch the algorithms at run time. + * A new option when starting a remote asterisk (rasterisk, asterisk -r) for + specifying which socket to use to connect to the running Asterisk daemon + (-s) + * Added logging to 'make update' command. See update.log + diff --git a/trunk/COPYING b/trunk/COPYING new file mode 100644 index 0000000000..aa2ebac66b --- /dev/null +++ b/trunk/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/trunk/CREDITS b/trunk/CREDITS new file mode 100644 index 0000000000..a7a432d694 --- /dev/null +++ b/trunk/CREDITS @@ -0,0 +1,244 @@ + +=== DEVELOPMENT SUPPORT === +We'd like to thank the following companies for helping fund development of +Asterisk: + +Pilosoft, Inc. - for supporting ADSI development in Asterisk + +Asterlink, Inc. - for supporting broad Asterisk development + +GFS - for supporting ALSA development + +Telesthetic - for supporting SIP development + +Christos Ricudis - for substantial code contributions + +nic.at - ENUM support in Asterisk + +Paul Bagyenda, Digital Solutions - for initial Voicetronix driver development + +John Todd, TalkPlus, Inc. and JR Richardson, Ntegrated Solutions. - for funding + the development of SIP Session Timers support. + +=== 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 release manager and countless enhancements and bug + fixes. + russell(AT)digium.com + +Anthony Minessale II - Countless big and small fixes, and relentless forward + push. ChanSpy, ForkCDR, ControlPlayback, While/EndWhile, DumpChan, Dictate, + MacroIf, ExecIf, ExecIfTime, RetryDial, MixMonitor applications; many + realtime concepts and implementation pieces, including res_config_odbc; + format_slin; cdr_custom; several features in Dial including L(), G() and + enhancements to M() and D(); several CDR enhancements including CDR + variables; attended transfer; one touch record; native MOH; manager + eventmask; command line '-t' flag to allow recording/voicemail on nfs + shares; #exec command and multiline comments in config files; setvar in iax + and sip configs. + anthmct(AT)yahoo.com http://www.asterlink.com + +James Golovich - Innumerable contributions, including SIP TCP and TLS support. + You can find him and asterisk-perl at http://asterisk.gnuinter.net + +Andre Bierwirth - Extension hints and status + +Jean-Denis Girard - Various contributions from the South Pacific Islands + jd-girard(AT)esoft.pf http://www.esoft.pf + +William Jordan / Vonage - MySQL enhancements to Voicemail + wjordan(AT)vonage.com + +Jac Kersing - Various fixes + +Steven Critchfield - Seek and Trunc functions for playback and recording + critch(AT)basesys.com + +Jefferson Noxon - app_lookupcidname, app_db, and various other contributions + +Klaus-Peter Junghanns - in-band DTMF on SIP and MGCP + +Ross Finlayson - Dynamic RTP payload support + +Mahmut Fettahlioglu - Audio recording, music-on-hold changes, alaw file + format, and various fixes. Can be contacted at mahmut(AT)oa.com.au + +James Dennis - Cisco SIP compatibility patches to work with SIP service + providers. Can be contacted at asterisk(AT)jdennis.net + +Tilghman Lesher - ast_localtime(); ast_say_date_with_format(); + GotoIfTime, SayUnixTime, HasNewVoicemail applications; + CUT, SORT, EVAL, CURL, FIELDQTY, STRFTIME, some QUEUE* functions; + func_odbc, cdr_adaptive_odbc, and other innumerable bug fixes. + tilghman(AT)digium.com http://asterisk.drunkcoder.com/ + +Jayson Vantuyl - Manager protocol changes, various other bugs. + jvantuyl(AT)computingedge.net + +Thorsten Lockert - OpenBSD, FreeBSD ports, making MacOS X port run on 10.3, + dialplan include verification, route lookup on OpenBSD, SNMP agent + support (res_snmp), various other bugs. tholo(AT)sigmasoft.com + +Josh Roberson - chan_zap reload support, Advanced Voicemail Features, & other + misc. patches. - josh(AT)asteriasgi.com, http://www.asteriasgi.com + +William Waites - syslog support, SIP NAT traversal for SIP-UA. ww(AT)styx.org + +Rich Murphey - Porting to FreeBSD, NetBSD, OpenBSD, and Darwin. + rich(AT)whiteoaklabs.com http://whiteoaklabs.com + +Simon Lockhart - Porting to Solaris (based on work of Logan ???) + simon(AT)slimey.org + +Olle E. Johansson - SIP RFC compliance, documentation and testing, testing, + testing; MiniVM - the small voicemail system, many documentation + updates/corrections, and many bug fixes. + oej(AT)edvina.net, http://edvina.net + +Steve Kann - new jitter buffer for IAX2 + stevek(AT)stevek.com + +Constantine Filin - major contributions to the Asterisk Realtime Architecture + +Steve Murphy - privacy support, $[ ] parser upgrade, AEL2 parser upgrade. + murf(AT)digium.com + +Claude Patry - bug fixes, feature enhancements, and bug marshalling + cpatry(AT)gmail.com + +Miroslav Nachev, miro(AT)space-comm.com COSMOS Software Enterprises, Ltd. + - for Variable for No Answer Timeout for Attended Transfer + +Slav Klenov & Vanheuverzwijn Joachim - development of the generic jitterbuffer + Securax Ltd. info(AT)securax.be + +Roy Sigurd Karlsbakk - providing funding for generic jitterbuffer development + roy(AT)karlsbakk.net, Briiz Telecom AS + +Voop AS, Nuvio Inc, Inotel S.A and Foniris Telecom A/S - funding for rewrite + of SIP transfers + +Philippe Sultan - RADIUS CDR module, many fixes to res_jabber and gtalk/jingle + channel drivers. + INRIA, http://www.inria.fr/ + +John Martin, Aupix - Improved video support in the SIP channel + T.140 text support in RTP/SIP + +Steve Underwood - Provided T.38 pass through support. + +George Konstantoulakis - Support for Greek in voicemail added by InAccess + Networks (work funded by HOL, www.hol.gr) gkon(AT)inaccessnetworks.com + +Daniel Nylander - Support for Swedish and Norwegian languages in voicemail. + http://www.danielnylander.se/ + +Stojan Sljivic - An option for maximum number of messsages per mailbox in + voicemail. Also an issue with voicemail synchronization has been fixed. + GDS Partners www.gdspartners.com . stojan.sljivic(AT)gdspartners.com + +Bartosz Supczinski - Support for Polish added by DIR (www.dir.pl) + Bartosz.Supczinski(AT)dir.pl + +James Rothenberger - Support for IMAP storage integration added by + OneBizTone LLC Work funded by University of Pennsylvania jar(AT)onebiztone.com + +Paul Cadach - Bringing chan_h323 up to date, bug fixes, and more! + +Voop AS - Financial support for a lot of work with the SIP driver and the IAX + trunk MTU patch + +Cedric Hans - Development of chan_unistim + cedric.hans(AT)mlkj.net + +Sergio Fadda - console_video: video support for chan_oss and chan_alsa + +Marta Carbone - console_video and the astobj2 framework + +Luigi Rizzo - astobj2, console_video, windows build, chan_oss cleanup, + and a bunch of infrastructure work (loader, new_cli, ...) + +Brett Bryant - digit option for musiconhold selection, ENUMQUERY and ENUMRESULT functions, + feature group configuration for features.conf, per-file CLI debug and verbose settings, + TCP and TLS support for SIP, and various bug fixes. + brettbryant(AT)gmail.com + +=== 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/trunk/LICENSE b/trunk/LICENSE new file mode 100644 index 0000000000..39e0bb8ae1 --- /dev/null +++ b/trunk/LICENSE @@ -0,0 +1,69 @@ +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 and +OpenH323 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/misery.digium.com/6000 (via IAX2) +licensing@digium.com (via email) + +Digium, Inc. +445 Jan Davis Drive +Huntsville, AL 35806 +USA diff --git a/trunk/Makefile b/trunk/Makefile new file mode 100644 index 0000000000..5b8951809a --- /dev/null +++ b/trunk/Makefile @@ -0,0 +1,834 @@ +# +# 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) +# 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 +# +# Initial values for ASTCFLAGS and ASTLDFLAGS can be specified in the +# environment when running make, as follows: +# +# $ ASTCFLAGS="-Werror" make ... +# +# note that this is different from +# +# $ make ASTCFLAGS="-Werror" ... +# +# If you need to pass compiler/linker flags as 'make' variables, please use +# +# $ make COPTS="..." LDOPTS="..." ... +# +# +# You can add the path of local module subdirs from the command line with +# make LOCAL_MOD_SUBDIRS= .... + +export ASTTOPDIR # Top level dir, used in subdirs' Makefiles +export ASTERISKVERSION +export ASTERISKVERSIONNUM + +#--- values used for default paths + +# DESTDIR is the staging (or final) directory where files are copied +# during the install process. Define it before 'export', otherwise +# export will set it to the empty string making ?= fail. +# WARNING: do not put spaces or comments after the value. +DESTDIR?=$(INSTALL_PATH) +export DESTDIR + +export INSTALL_PATH # Additional prefix for the following paths +export ASTETCDIR # Path for config files +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 OSARCH # Operating system +export PROC # Processor type + +export NOISY_BUILD # Used in Makefile.rules +export MENUSELECT_CFLAGS # Options selected in menuselect. +export AST_DEVMODE # Set to "yes" for additional compiler + # and runtime checks + +export SOLINK # linker flags for shared objects +export STATIC_BUILD # Additional cflags, set to -static + # for static builds. Probably + # should go directly to ASTLDFLAGS + +#--- paths to various commands +export CC +export CXX +export AR +export RANLIB +export HOST_CC +export INSTALL +export STRIP +export DOWNLOAD +export AWK +export GREP +export ID + +# 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 + +ASTTOPDIR:=$(CURDIR) + +# Overwite config files on "make samples" +OVERWRITE=y + +# Include debug and macro symbols in the executables (-g) and profiling info (-pg) +DEBUG=-g3 + + +# 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 + ASTDBDIR=$(ASTVARLIBDIR) + ASTKEYDIR=$(ASTVARLIBDIR) + 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 + ASTDBDIR=$(localstatedir)/db/asterisk +else + ASTVARLIBDIR=$(localstatedir)/lib/asterisk + ASTDBDIR=$(ASTVARLIBDIR) +endif + ASTKEYDIR=$(ASTVARLIBDIR) +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 + +# Create OPTIONS variable, but probably we can assign directly to ASTCFLAGS +OPTIONS= + +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) + +ASTCFLAGS+=-include $(ASTTOPDIR)/include/asterisk/autoconfig.h + +ifeq ($(AST_DEVMODE),yes) + ASTCFLAGS+=-Werror -Wunused -Wundef $(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) +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 -D_XPG4_2 +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) + RPMVERSION:=$(shell sed 's/[-\/:]/_/g' .version) +else + RPMVERSION=unknown +endif + +ifneq ($(wildcard .svn),) + ASTERISKVERSIONNUM:=999999 +endif + +# XXX MALLOC_DEBUG is probably unused, Makefile.moddir_rules adds the +# value directly to ASTCFLAGS +ASTCFLAGS+=$(MALLOC_DEBUG)$(OPTIONS) + +MOD_SUBDIRS:=channels pbx apps codecs formats cdr funcs tests main res $(LOCAL_MOD_SUBDIRS) +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__ + 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 +endif + +# comment to print directories during submakes +#PRINT_DIR=yes + +SILENTMAKE:=$(MAKE) --quiet --no-print-directory +ifneq ($(PRINT_DIR)$(NOISY_BUILD),) +SUBMAKE:=$(MAKE) --quiet +else +SUBMAKE:=$(MAKE) --quiet --no-print-directory +endif + +# This is used when generating the doxygen documentation +ifneq ($(DOT),:) + HAVEDOT=yes +else + HAVEDOT=no +endif + +# $(MAKE) is printed in several places, and we want it to be a +# fixed size string. Define a variable whose name has also the +# same size, so we can easily align text. +ifeq ($(MAKE), gmake) + mK="gmake" +else + mK=" make" +endif + +all: _all + @echo " +--------- Asterisk Build Complete ---------+" + @echo " + Asterisk has successfully been built, and +" + @echo " + can be installed by running: +" + @echo " + +" + @echo " + $(mK) install +" + @echo " +-------------------------------------------+" + +_all: cleantest makeopts $(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 $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts + +$(MOD_SUBDIRS_EMBED_LDSCRIPT): + @echo "EMBED_LDSCRIPTS+="`$(SILENTMAKE) -C $(@:-embed-ldscript=) SUBDIR=$(@:-embed-ldscript=) __embed_ldscript` >> makeopts.embed_rules + +$(MOD_SUBDIRS_EMBED_LDFLAGS): + @echo "EMBED_LDFLAGS+="`$(SILENTMAKE) -C $(@:-embed-ldflags=) SUBDIR=$(@:-embed-ldflags=) __embed_ldflags` >> makeopts.embed_rules + +$(MOD_SUBDIRS_EMBED_LIBS): + @echo "EMBED_LIBS+="`$(SILENTMAKE) -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) $(PRINT_DIR) $(MOD_SUBDIRS_EMBED_LDSCRIPT) + @$(MAKE) $(PRINT_DIR) $(MOD_SUBDIRS_EMBED_LDFLAGS) + @$(MAKE) $(PRINT_DIR) $(MOD_SUBDIRS_EMBED_LIBS) + +$(SUBDIRS): main/version.c include/asterisk/build.h include/asterisk/buildopts.h defaults.h makeopts.embed_rules + +ifeq ($(findstring $(OSARCH), mingw32 cygwin ),) + # Non-windows: + # 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)) +else + # Windows: we need to build main (i.e. the asterisk dll) first, + # followed by res, followed by the other directories, because + # dll symbols must be resolved during linking and not at runtime. +D1:= $(filter-out main,$(MOD_SUBDIRS)) +D1:= $(filter-out res,$(D1)) + +$(D1): res +res: main +endif + +$(MOD_SUBDIRS): + @ASTCFLAGS="$(MOD_SUBDIR_CFLAGS) $(ASTCFLAGS)" ASTLDFLAGS="$(ASTLDFLAGS)" $(MAKE) $(PRINT_DIR) --no-builtin-rules -C $@ SUBDIR=$@ all + +$(OTHER_SUBDIRS): + @ASTCFLAGS="$(OTHER_SUBDIR_CFLAGS) $(ASTCFLAGS)" ASTLDFLAGS="$(ASTLDFLAGS)" $(MAKE) $(PRINT_DIR) --no-builtin-rules -C $@ SUBDIR=$@ all + +defaults.h: makeopts + @build_tools/make_defaults_h > $@.tmp + @cmp -s $@.tmp $@ || mv $@.tmp $@ + @rm -f $@.tmp + +main/version.c: + @build_tools/make_version_c > $@.tmp + @cmp -s $@.tmp $@ || mv $@.tmp $@ + @rm -f $@.tmp + +include/asterisk/buildopts.h: menuselect.makeopts + @build_tools/make_buildopts_h > $@.tmp + @cmp -s $@.tmp $@ || mv $@.tmp $@ + @rm -f $@.tmp + +include/asterisk/build.h: + @build_tools/make_build_h > $@.tmp + @cmp -s $@.tmp $@ || mv $@.tmp $@ + @rm -f $@.tmp + +$(SUBDIRS_CLEAN): + @$(MAKE) $(PRINT_DIR) -C $(@:-clean=) clean + +$(SUBDIRS_DIST_CLEAN): + @$(MAKE) $(PRINT_DIR) -C $(@:-dist-clean=) dist-clean + +clean: $(SUBDIRS_CLEAN) + rm -f defaults.h + rm -f include/asterisk/build.h + rm -f main/version.c + @$(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 + 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)/phoneprov + for x in phoneprov/*; do \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/phoneprov ; \ + done + mkdir -p $(DESTDIR)$(ASTDATADIR)/static-http + for x in static-http/*; do \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/static-http ; \ + done + if [ -d doc/tex/asterisk ] ; then \ + mkdir -p $(DESTDIR)$(ASTDATADIR)/static-http/docs ; \ + for n in doc/tex/asterisk/* ; do \ + $(INSTALL) -m 644 $$n $(DESTDIR)$(ASTDATADIR)/static-http/docs ; \ + done \ + fi + 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..." ; \ + fromrev="`svn info | $(AWK) '/Revision: / {print $$2}'`"; \ + svn update | tee update.out; \ + torev="`svn info | $(AWK) '/Revision: / {print $$2}'`"; \ + echo "`date` Updated from revision $${fromrev} to $${torev}." >> update.log; \ + 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))) + +bininstall: _all + 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 + $(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) --quiet $(PRINT_DIR) -C $(@:-install=) install + +NEWMODS:=$(foreach d,$(MOD_SUBDIRS),$(notdir $(wildcard $(d)/*.so))) +OLDMODS=$(filter-out $(NEWMODS),$(notdir $(wildcard $(DESTDIR)$(MODULES_DIR)/*.so))) + +oldmodcheck: + @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 + +install: datafiles bininstall $(SUBDIRS_INSTALL) + @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 " + +" + @echo " + $(mK) samples +" + @echo " + +" + @echo " +----------------- or ---------------------+" + @echo " + +" + @echo " + You can go ahead and install the asterisk +" + @echo " + program documentation now or later run: +" + @echo " + +" + @echo " + $(mK) progdocs +" + @echo " + +" + @echo " + **Note** This requires that you have +" + @echo " + doxygen installed on your local system +" + @echo " +-------------------------------------------+" + @$(MAKE) -s oldmodcheck + +upgrade: bininstall + +# XXX why *.adsi is installed first ? +adsi: + @echo Installing adsi config files... + @mkdir -p $(DESTDIR)$(ASTETCDIR) + @for x in configs/*.adsi; do \ + dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x`" ; \ + if [ -f $${dst} ] ; then \ + echo "Overwriting $$x" ; \ + else \ + echo "Installing $$x" ; \ + fi ; \ + $(INSTALL) -m 644 $$x $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x` ; \ + done + +samples: adsi + @echo Installing other config files... + @mkdir -p $(DESTDIR)$(ASTETCDIR) + @for x in configs/*.sample; do \ + dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample`" ; \ + if [ -f $${dst} ]; then \ + if [ "$(OVERWRITE)" = "y" ]; then \ + if cmp -s $${dst} $$x ; then \ + echo "Config file $$x is unchanged"; \ + continue; \ + fi ; \ + mv -f $${dst} $${dst}.old ; \ + else \ + echo "Skipping config file $$x"; \ + continue; \ + fi ;\ + fi ; \ + echo "Installing file $$x"; \ + $(INSTALL) -m 644 $$x $${dst} ;\ + done + @if [ "$(OVERWRITE)" = "y" ] || [ ! -f $(DESTDIR)$(ASTCONFPATH) ]; then \ + echo "Creating asterisk.conf"; \ + ( \ + echo "[directories](!) ; remove the (!) to enable this" ; \ + echo "astetcdir => $(ASTETCDIR)" ; \ + echo "astmoddir => $(MODULES_DIR)" ; \ + echo "astvarlibdir => $(ASTVARLIBDIR)" ; \ + echo "astdbdir => $(ASTDBDIR)" ; \ + echo "astkeydir => $(ASTKEYDIR)" ; \ + echo "astdatadir => $(ASTDATADIR)" ; \ + echo "astagidir => $(AGI_DIR)" ; \ + echo "astspooldir => $(ASTSPOOLDIR)" ; \ + echo "astrundir => $(ASTVARRUNDIR)" ; \ + echo "astlogdir => $(ASTLOGDIR)" ; \ + echo "" ; \ + echo ";[options]" ; \ + 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 ";languageprefix = yes ; Use the new sound prefix path syntax" ; \ + echo ";internal_timing = yes" ; \ + echo ";systemname = my_system_name ; prefix uniqueid with a system name for global uniqueness issues" ; \ + echo ";autosystemname = yes ; automatically set systemname to hostname - uses 'localhost' on failure, or systemname if set" ; \ + 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 ";maxfiles = 1000 ; Maximum amount of openfiles" ; \ + echo ";minmemfree = 1 ; in MBs, Asterisk stops accepting new calls if the amount of free memory falls below this watermark" ; \ + 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 ";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 "" ; \ + 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 " +-------------------------------------------+" + +spec: + sed "s/^Version:.*/Version: $(RPMVERSION)/g" redhat/asterisk.spec > asterisk.spec ; \ + +rpm: __rpm + +__rpm: main/version.c include/asterisk/buildopts.h spec + rm -rf /tmp/asterisk ; \ + mkdir -p /tmp/asterisk/redhat/RPMS/i386 ; \ + $(MAKE) DESTDIR=/tmp/asterisk install ; \ + $(MAKE) DESTDIR=/tmp/asterisk samples ; \ + mkdir -p /tmp/asterisk/etc/rc.d/init.d ; \ + cp -f contrib/init.d/rc.redhat.asterisk /tmp/asterisk/etc/rc.d/init.d/asterisk ; \ + rpmbuild --rcfile /usr/lib/rpm/rpmrc:redhat/rpmrc -bb asterisk.spec + +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) $(PRINT_DIR) -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 " + +" + @echo " + $(mK) uninstall-all +" + @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 $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.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 $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts && (echo "menuselect changes saved!"; rm -f channels/h323/Makefile.ast main/asterisk) || echo "menuselect changes NOT saved!" + +# options for make in menuselect/ +MAKE_MENUSELECT=CC="$(HOST_CC)" CXX="$(CXX)" LD="" AR="" RANLIB="" CFLAGS="" $(MAKE) -C menuselect CONFIGURE_SILENT="--silent" + +menuselect/menuselect: menuselect/makeopts + $(MAKE_MENUSELECT) + +menuselect/gmenuselect: menuselect/makeopts + $(MAKE_MENUSELECT) gmenuselect + +menuselect/makeopts: + $(MAKE_MENUSELECT) makeopts + +menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(dir)/*.c) $(wildcard $(dir)/*.cc)) build_tools/cflags.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 >> $@ + @cat build_tools/embed_modules.xml >> $@ + @cat sounds/sounds.xml >> $@ + @echo "" >> $@ + +pdf: asterisk.pdf +asterisk.pdf: + $(MAKE) -C doc/tex asterisk.pdf + +.PHONY: menuselect main sounds clean dist-clean distclean all prereqs cleantest uninstall _uninstall uninstall-all pdf dont-optimize $(SUBDIRS_INSTALL) $(SUBDIRS_DIST_CLEAN) $(SUBDIRS_CLEAN) $(SUBDIRS_UNINSTALL) $(SUBDIRS) $(MOD_SUBDIRS_EMBED_LDSCRIPT) $(MOD_SUBDIRS_EMBED_LDFLAGS) $(MOD_SUBDIRS_EMBED_LIBS) main/version.c diff --git a/trunk/Makefile.moddir_rules b/trunk/Makefile.moddir_rules new file mode 100644 index 0000000000..9258e8d63b --- /dev/null +++ b/trunk/Makefile.moddir_rules @@ -0,0 +1,166 @@ +# +# 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 +# + +# Makefile rules for building modules. + +# In most cases, we set target-specific variables for certain targets +# (remember that they apply recursively to prerequisites). +# Also note that we can only set one variable per rule, so we have to +# repeat the left hand side to set multiple variables. + +ifneq ($(findstring MALLOC_DEBUG,$(MENUSELECT_CFLAGS)),) + ifeq ($(findstring astmm.h,$(ASTCFLAGS)),) + ASTCFLAGS+=-include $(ASTTOPDIR)/include/asterisk/astmm.h + endif +endif + +ifeq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),) + ASTCFLAGS+=${GC_CFLAGS} +endif + +ifneq ($(findstring STATIC_BUILD,$(MENUSELECT_CFLAGS)),) + STATIC_BUILD=-static +endif + +include $(ASTTOPDIR)/Makefile.rules + +# If MODULE_PREFIX is defined, use it to run the standard functions to set +# C_MODS, CC_MODS, LOADABLE_MODS and EMBEDDED_MODS. +# Each word of MODULE_PREFIX is a prefix for filenames that we consider +# valid C or CC modules (eg. app, func ...). Note that the underscore +# is added here, and does not need to be in MODULE_PREFIX +# +# Use MODULE_EXCLUDE to specify additional modules to exclude. + +ifneq ($(MODULE_PREFIX),) + ALL_C_MODS:= + ALL_C_MODS+=$(foreach p,$(MODULE_PREFIX),$(patsubst %.c,%,$(wildcard $(p)_*.c))) + ALL_CC_MODS:= + ALL_CC_MODS+=$(foreach p,$(MODULE_PREFIX),$(patsubst %.cc,%,$(wildcard $(p)_*.cc))) + + C_MODS:=$(filter-out $(MENUSELECT_$(MENUSELECT_CATEGORY)),$(ALL_C_MODS)) + CC_MODS:=$(filter-out $(MENUSELECT_$(MENUSELECT_CATEGORY)),$(ALL_CC_MODS)) + + # and store in the list of embedded or loadable modules + ifneq ($(findstring $(MENUSELECT_CATEGORY),$(MENUSELECT_EMBED)),) + EMBEDDED_MODS:=$(C_MODS) $(CC_MODS) + else + LOADABLE_MODS:=$(C_MODS) $(CC_MODS) + endif +endif + +# Both C++ and C++ sources need their module name in AST_MODULE +# We also pass whatever _INCLUDE list is generated by menuselect +# (they are stored in file 'makeopts') + +$(addsuffix .oo,$(CC_MODS)) $(addsuffix .o,$(C_MODS)): \ + ASTCFLAGS+= -DAST_MODULE=\"$*\" $(MENUSELECT_OPTS_$*:%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_INCLUDE)) + +ifeq ($(findstring $(OSARCH), mingw32 cygwin ),) + # don't define -fPIC on mingw32 and cygwin, it is the default + $(LOADABLE_MODS:%=%.so): ASTCFLAGS+=-fPIC +endif + +# For loadable modules, pass _LIB and _LDFLAGS from menuselect. +$(LOADABLE_MODS:%=%.so): LIBS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LIB)) +$(LOADABLE_MODS:%=%.so): ASTLDFLAGS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LDFLAGS)) + +$(EMBEDDED_MODS:%=%.o): ASTCFLAGS+=-DEMBEDDED_MODULE=$* + +$(addsuffix .so,$(filter $(LOADABLE_MODS),$(C_MODS))): %.so: %.o +$(addsuffix .so,$(filter $(LOADABLE_MODS),$(CC_MODS))): %.so: %.oo + +modules.link: $(addsuffix .eo,$(filter $(EMBEDDED_MODS),$(C_MODS))) + +.PHONY: clean uninstall _all moduleinfo makeopts + +ifneq ($(LOADABLE_MODS),) +_all: $(LOADABLE_MODS:%=%.so) +ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) + # linker options and extra libraries for cygwin + SOLINK=-Wl,--out-implib=lib$@.a -shared + LIBS+=-L$(ASTTOPDIR)/main -lasterisk -L$(ASTTOPDIR)/res $($@_LIBS) + # additional libraries in res/ +endif +endif + +ifneq ($(EMBEDDED_MODS),) +_all: modules.link +__embed_ldscript: + @echo "../$(SUBDIR)/modules.link" +__embed_ldflags: + @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(C_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LDFLAGS))" + @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(CC_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LDFLAGS))" +__embed_libs: + @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(C_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LIB))" + @echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(CC_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LIB))" +else +__embed_ldscript: +__embed_ldflags: +__embed_libs: +endif + +modules.link: + @rm -f $@ + @for file in $(patsubst %,$(SUBDIR)/%,$(filter %.eo,$^)); do echo "INPUT (../$${file})" >> $@; done + @for file in $(patsubst %,$(SUBDIR)/%,$(filter-out %.eo,$^)); do echo "INPUT (../$${file})" >> $@; done + +clean:: + rm -f *.so *.o *.oo *.eo + rm -f .*.o.d .*.oo.d + rm -f *.s *.i + rm -f modules.link + +install:: all + @echo "Installing modules from `basename $(CURDIR)`..." + @for x in $(LOADABLE_MODS:%=%.so); do $(INSTALL) -m 755 $$x $(DESTDIR)$(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/trunk/Makefile.rules b/trunk/Makefile.rules new file mode 100644 index 0000000000..ca21dcc04a --- /dev/null +++ b/trunk/Makefile.rules @@ -0,0 +1,98 @@ +# +# 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 +# + +# Rules for various build phases. +# 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 + +# extra cflags to build dependencies. Recursively expanded. +MAKE_DEPS= -MMD -MT $@ -MF .$(subst /,_,$@).d -MP + +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 + +# build rules for various targets +%.o: %.c + $(ECHO_PREFIX) echo " [CC] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS) + +%.o: %.i + $(ECHO_PREFIX) echo " [CCi] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS) + +%.i: %.c + $(ECHO_PREFIX) echo " [CPP] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -E $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS) + +%.o: %.s + $(ECHO_PREFIX) echo " [AS] $< -> $@" + $(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS) + +%.oo: %.cc + $(ECHO_PREFIX) echo " [CXX] $< -> $@" + $(CMD_PREFIX) $(CXX) -o $@ -c $< $(PTHREAD_CFLAGS) $(filter-out -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations,$(ASTCFLAGS)) $(MAKE_DEPS) + +%.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) + +%.eo: %.o + $(ECHO_PREFIX) echo " [EMBED] $< -> $@" + $(CMD_PREFIX) $(ASTTOPDIR)/build_tools/make_linker_eo_script $* > .$@.ld + $(CMD_PREFIX) $(LD) -r -T .$@.ld -o $@ $< + $(CMD_PREFIX) rm -f .$@.ld + +%.eo: %.oo + $(ECHO_PREFIX) echo " [EMBED] $< -> $@" + $(CMD_PREFIX) $(ASTTOPDIR)/build_tools/make_linker_eo_script $* > .$@.ld + $(CMD_PREFIX) $(LD) -r -T .$@.ld -o $@ $< + $(CMD_PREFIX) rm -f .$@.ld + +%: %.o + $(ECHO_PREFIX) echo " [LD] $^ -> $@" + $(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $^ $(PTHREAD_LIBS) $(LIBS) + +dist-clean:: diff --git a/trunk/README b/trunk/README new file mode 100644 index 0000000000..176bfca44c --- /dev/null +++ b/trunk/README @@ -0,0 +1,262 @@ +The Asterisk(R) Open Source PBX +by Mark Spencer +and the Asterisk.org developer community + +Copyright (C) 2001-2006 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 zapata.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/trunk/UPGRADE.txt b/trunk/UPGRADE.txt new file mode 100644 index 0000000000..dcde189812 --- /dev/null +++ b/trunk/UPGRADE.txt @@ -0,0 +1,163 @@ +Information for Upgrading From Previous Asterisk Releases +========================================================= + +AEL: + +* Macros are now implemented underneath with the Gosub() application. + Heaven Help You if you wrote code depending on any aspect of this! + Previous to 1.6, macros were implemented with the Macro() app, which + provided a nice feature of auto-returning. The compiler will do its + best to insert a Return() app call at the end of your macro if you did + not include it, but really, you should make sure that all execution + paths within your macros end in "return;". + +* The conf2ael program is 'introduced' in this release; it is in a rather + crude state, but deemed useful for making a first pass at converting + extensions.conf code into AEL. More intelligence will come with time. + +Core: + +* The 'languageprefix' option in asterisk.conf is now deprecated, and + the default sound file layout for non-English sounds is the 'new + style' layout introduced in Asterisk 1.4 (and used by the automatic + sound file installer in the Makefile). + +* The ast_expr2 stuff has been modified to handle floating-point numbers. + Numbers of the format D.D are now acceptable input for the expr parser, + Where D is a string of base-10 digits. All math is now done in "long double", + if it is available on your compiler/architecture. This was half-way between + a bug-fix (because the MATH func returns fp by default), and an enhancement. + Also, for those counting on, or needing, integer operations, a series of + 'functions' were also added to the expr language, to allow several styles + of rounding/truncation, along with a set of common floating point operations, + like sin, cos, tan, log, pow, etc. The ability to call external functions + like CDR(), etc. was also added, without having to use the ${...} notation. + +* The delimiter passed to applications has been changed to the comma (','), as + that is what people are used to using within extensions.conf. If you are + using realtime extensions, you will need to translate your existing dialplan + to use this separator. To use a literal comma, you need merely to escape it + with a backslash ('\'). Another possible side effect is that you may need to + remove the obscene level of backslashing that was necessary for the dialplan + to work correctly in 1.4 and previous versions. This should make writing + dialplans less painful in the future, albeit with the pain of a one-time + conversion. + +* The logger.conf option 'rotatetimestamp' has been deprecated in favor of + 'rotatestrategy'. This new option supports a 'rotate' strategy that more + closely mimics the system logger in terms of file rotation. + +* The concise versions of various CLI commands are now deprecated. We recommend + using the manager interface (AMI) for application integration with Asterisk. + +Voicemail: + +* The voicemail configuration values 'maxmessage' and 'minmessage' have + been changed to 'maxsecs' and 'minsecs' to clarify their purpose and + to make them more distinguishable from 'maxmsgs', which sets folder + size. The old variables will continue to work in this version, albeit + with a deprecation warning. +* If you use any interface for modifying voicemail aside from the built in + dialplan applications, then the option "pollmailboxes" *must* be set in + voicemail.conf for message waiting indication (MWI) to work properly. This + is because Voicemail notification is now event based instead of polling + based. The channel drivers are no longer responsible for constantly manually + checking mailboxes for changes so that they can send MWI information to users. + Examples of situations that would require this option are web interfaces to + voicemail or an email client in the case of using IMAP storage. + +Applications: + +* ChanIsAvail() now has a 't' option, which allows the specified device + to be queried for state without consulting the channel drivers. This + performs mostly a 'ChanExists' sort of function. +* SetCallerPres() has been replaced with the CALLERPRES() dialplan function + and is now deprecated. +* DISA()'s fifth argument is now an options argument. If you have previously + used 'NOANSWER' in this argument, you'll need to convert that to the new + option 'n'. +* Macro() is now deprecated. If you need subroutines, you should use the + Gosub()/Return() applications. To replace MacroExclusive(), we have + introduced dialplan functions LOCK(), TRYLOCK(), and UNLOCK(). You may use + these functions in any location where you desire to ensure that only one + channel is executing that path at any one time. +* Read() now sets a READSTATUS variable on exit. It does NOT automatically + return -1 (and hangup) anymore on error. If you want to hangup on error, + you need to do so explicitly in your dialplan. +* Privacy() no longer uses privacy.conf, so any options must be specified + directly in the application arguments. + +Dialplan Functions: + +* QUEUE_MEMBER_COUNT() has been deprecated in favor of the QUEUE_MEMBER() function. For + more information, issue a "show function QUEUE_MEMBER" from the CLI. + +CDR: + +* The cdr_sqlite module has been marked as deprecated in favor of + cdr_sqlite3_custom. It will potentially be removed from the tree + after Asterisk 1.6 is released. + +* The cdr_odbc module now uses res_odbc to manage its connections. The + username and password parameters in cdr_odbc.conf, therefore, are no + longer used. The dsn parameter now points to an entry in res_odbc.conf. + +Formats: + +* format_wav: The GAIN preprocessor definition and source code that used it + is removed. This change was made in response to user complaints of + choppiness or the clipping of loud signal peaks. To increase the volume + of voicemail messages, use the 'volgain' option in voicemail.conf + +Channel Drivers: + +* SIP: a small upgrade to support the "Record" button on the SNOM360, + which sends a sip INFO message with a "Record: on" or "Record: off" + header. If Asterisk is set up (via features.conf) to accept "One Touch Monitor" + requests (by default, via '*1'), then the user-configured dialpad sequence + is generated, and recording can be started and stopped via this button. The + file names and formats are all controlled via the normal mechanisms. If the + user has not configured the automon feature, the normal "415 Unsupported media type" + is returned, and nothing is done. +* SIP: The "call-limit" option is marked as deprecated. It still works in this version of + Asterisk, but will be removed in the following version. Please use the groupcount functions + in the dialplan to enforce call limits. The "limitonpeer" configuration option is + now renamed to "counteronpeer". +* SIP: The "username" option is now renamed to "defaultuser" to match "defaultip". + These are used only before registration to call a peer with the uri + sip:defaultuser@defaultip + The "username" setting still work, but is deprecated and will not work in + the next version of Asterisk. + +* chan_local.c: the comma delimiter inside the channel name has been changed to a + semicolon, in order to make the Local channel driver compatible with the comma + delimiter change in applications. +* H323: The "tos" setting has changed name to "tos_audio" and "cos" to "cos_audio" + to be compatible with settings in sip.conf. The "tos" and "cos" configuration + is deprecated and will stop working in the next release of Asterisk. + +* Console: A new console channel driver, chan_console, has been added to Asterisk. + This new module can not be loaded at the same time as chan_alsa or chan_oss. The + default modules.conf only loads one of them (chan_oss by default). So, unless you + have modified your modules.conf to not use the autoload option, then you will need + to modify modules.conf to add another "noload" line to ensure that only one of + these three modules gets loaded. + +Configuration: + +* pbx_dundi.c: tos parameter changed to use new values. Old values like lowdelay, + lowcost and other is not acceptable now. Look into qos.tex for description of + this parameter. + +Manager: + +* Manager has been upgraded to version 1.1 with a lot of changes. + Please check doc/manager_1_1.txt for information + +* The IAXpeers command output has been changed to more closely resemble the + output of the SIPpeers command. + +* cdr_manager now reports at the "cdr" level, not at "call" You may need to + change your manager.conf to add the level to existing AMI users, if they + want to see the CDR events generated. + diff --git a/trunk/acinclude.m4 b/trunk/acinclude.m4 new file mode 100644 index 0000000000..9b84af6142 --- /dev/null +++ b/trunk/acinclude.m4 @@ -0,0 +1,1106 @@ +# Various support functions for configure.ac in asterisk +# + +# Helper function to check for gcc attributes. +# AST_GCC_ATTRIBUTE([attribute name]) + +AC_DEFUN([AST_GCC_ATTRIBUTE], +[ +AC_MSG_CHECKING(for compiler 'attribute $1' support) +AC_COMPILE_IFELSE( + AC_LANG_PROGRAM([static int __attribute__(($1)) test(void) {}], + []), + 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)) +]) + +# Helper function to setup variables for a package. +# $1 -> the package name. Used in configure.ac and also as a prefix +# for the variables ($1_DIR, $1_INCLUDE, $1_LIB) in makeopts +# $3 -> option name, used in --with-$3 or --without-$3 when calling configure. +# $2 and $4 are just text describing the package (short and long form) + +# AST_EXT_LIB_SETUP([package], [short description], [configure option name], [long description]) + +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) + ac_mandatory_list="${ac_mandatory_list} $1" + ;; + *) + $1_DIR="${withval}" + ac_mandatory_list="${ac_mandatory_list} $1" + ;; + esac + ]) + PBX_$1=0 + AC_SUBST([$1_LIB]) + AC_SUBST([$1_INCLUDE]) + AC_SUBST([$1_DIR]) + AC_SUBST([PBX_$1]) +]) + +# Check whether any of the mandatory modules are not present, and +# print error messages in case. The mandatory list is built using +# --with-* arguments when invoking configure. + +AC_DEFUN([AST_CHECK_MANDATORY], +[ + AC_MSG_CHECKING([for mandatory modules: ${ac_mandatory_list}]) + err=0; + for i in ${ac_mandatory_list}; do + eval "a=\${PBX_$i}" + if test "x${a}" = "x1" ; then continue; fi + if test ${err} = "0" ; then AC_MSG_RESULT(fail) ; fi + AC_MSG_RESULT() + eval "a=\${${i}_OPTION}" + AC_MSG_NOTICE([***]) + AC_MSG_NOTICE([*** The $i installation appears to be missing or broken.]) + AC_MSG_NOTICE([*** Either correct the installation, or run configure]) + AC_MSG_NOTICE([*** including --without-${a}.]) + err=1 + done + if test $err = 1 ; then exit 1; fi + AC_MSG_RESULT(ok) +]) + +# 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" -a "${USE_$1}" != "no"; 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 +]) + + +# Check if a given expression will compile using a certain header. + +# AST_C_COMPILE_CHECK([package], [expression], [header file], [version]) +AC_DEFUN([AST_C_COMPILE_CHECK], +[ + if test "x${PBX_$1}" != "x1" -a "${USE_$1}" != "no"; then + AC_MSG_CHECKING([if "$2" compiles using $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>], + [ $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 +]) + + +# Check for existence of a given package ($1), either looking up a function +# in a library, or, if no function is supplied, only check for the +# existence of the header files. + +# AST_EXT_LIB_CHECK([package], [library], [function], [header], +# [extra libs], [extra cflags], [version]) +AC_DEFUN([AST_EXT_LIB_CHECK], +[ +if test "x${PBX_$1}" != "x1" -a "${USE_$1}" != "no"; then + pbxlibdir="" + # if --with-$1=DIR has been specified, use it. + 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 + pbxfuncname="$3" + if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers + AST_$1_FOUND=yes + else + AC_CHECK_LIB([$2], [${pbxfuncname}], [AST_$1_FOUND=yes], [AST_$1_FOUND=no], ${pbxlibdir} $5) + fi + + # now check for the header. + if test "${AST_$1_FOUND}" = "yes"; then + $1_LIB="${pbxlibdir} -l$2 $5" + # if --with-$1=DIR has been specified, use it. + if test "x${$1_DIR}" != "x"; then + $1_INCLUDE="-I${$1_DIR}/include" + fi + $1_INCLUDE="${$1_INCLUDE} $6" + if test "x$4" = "x" ; then # no header, assume found + $1_HEADER_FOUND="1" + else # check for the header + saved_cppflags="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${$1_INCLUDE} $6" + AC_CHECK_HEADER([$4], [$1_HEADER_FOUND=1], [$1_HEADER_FOUND=0]) + CPPFLAGS="${saved_cppflags}" + fi + if test "x${$1_HEADER_FOUND}" = "x0" ; then + $1_LIB="" + $1_INCLUDE="" + else + if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library + $1_LIB="" + fi + PBX_$1=1 + # XXX don't know how to evaluate the description (third argument) in AC_DEFINE_UNQUOTED + AC_DEFINE_UNQUOTED([HAVE_$1], 1, [Define this to indicate the ${$1_DESCRIP} library]) + AC_DEFINE_UNQUOTED([HAVE_$1_VERSION], [$7], [Define to indicate the ${$1_DESCRIP} library version]) + fi + fi +fi +]) + + +# Check for a package using $2-config. Similar to AST_EXT_LIB_CHECK, +# but use $2-config to determine cflags and libraries to use. +# $3 and $4 can be used to replace --cflags and --libs in the request + +# AST_EXT_TOOL_CHECK([package], [tool name], [--cflags], [--libs], [includes], [expression]) +AC_DEFUN([AST_EXT_TOOL_CHECK], +[ + if test "x${PBX_$1}" != "x1" -a "${USE_$1}" != "no"; then + PBX_$1=0 + AC_CHECK_TOOL(CONFIG_$1, $2-config, No) + if test ! "x${CONFIG_$1}" = xNo; then + if test x"$3" = x ; then A=--cflags ; else A="$3" ; fi + $1_INCLUDE=$(${CONFIG_$1} $A) + if test x"$4" = x ; then A=--libs ; else A="$4" ; fi + $1_LIB=$(${CONFIG_$1} $A) + if test x"$5" != x ; then + 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( [ $5 ], + [ $6; ] + )], + [ PBX_$1=1 + AC_DEFINE([HAVE_$1], 1, [Define if your system has the $1 headers.]) + ], + [] + ) + CPPFLAGS="${saved_cppflags}" + else + PBX_$1=1 + AC_DEFINE([HAVE_$1], 1, [Define if your system has the $1 libraries.]) + fi + fi + 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 | cut -f2 -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/trunk/agi/DialAnMp3.agi b/trunk/agi/DialAnMp3.agi new file mode 100644 index 0000000000..59a54265e6 --- /dev/null +++ b/trunk/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/trunk/agi/Makefile b/trunk/agi/Makefile new file mode 100644 index 0000000000..0cb6f3f028 --- /dev/null +++ b/trunk/agi/Makefile @@ -0,0 +1,52 @@ +# +# 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 + +ifeq ($(OSARCH),mingw32) + AGIS:= +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 + rm -f *.s *.i + rm -f strcompat.c + +ifneq ($(wildcard .*.d),) + include .*.d +endif diff --git a/trunk/agi/agi-test.agi b/trunk/agi/agi-test.agi new file mode 100644 index 0000000000..4fc36eda8a --- /dev/null +++ b/trunk/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/trunk/agi/eagi-sphinx-test.c b/trunk/agi/eagi-sphinx-test.c new file mode 100644 index 0000000000..d2898763c3 --- /dev/null +++ b/trunk/agi/eagi-sphinx-test.c @@ -0,0 +1,231 @@ +/* + * Extended AGI test application + * + * This code is released into public domain + * without any warranty of any kind. + * + */ + +/*! \file + * Extended AGI test application + * + * This code is released into public domain + * without any warranty of any kind. + * + * \ingroup agi + */ + +#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/trunk/agi/eagi-test.c b/trunk/agi/eagi-test.c new file mode 100644 index 0000000000..704fd7b22c --- /dev/null +++ b/trunk/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 "asterisk.h" + +#define AUDIO_FILENO (STDERR_FILENO + 1) + +/*! \file + * Extended AGI test application + * + * This code is released into the public domain + * with no warranty of any kind + * + * \ingroup agi + */ + +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/trunk/agi/fastagi-test b/trunk/agi/fastagi-test new file mode 100644 index 0000000000..d3f13cf6b0 --- /dev/null +++ b/trunk/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/trunk/agi/jukebox.agi b/trunk/agi/jukebox.agi new file mode 100755 index 0000000000..7bd9c10f97 --- /dev/null +++ b/trunk/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/trunk/agi/numeralize b/trunk/agi/numeralize new file mode 100644 index 0000000000..5ca51913da --- /dev/null +++ b/trunk/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/trunk/apps/Makefile b/trunk/apps/Makefile new file mode 100644 index 0000000000..22b8f6d7a3 --- /dev/null +++ b/trunk/apps/Makefile @@ -0,0 +1,41 @@ +# +# 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 $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps + +MODULE_PREFIX=app +MENUSELECT_CATEGORY=APPS +MENUSELECT_DESCRIPTION=Applications + +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 + +ifeq (SunOS,$(shell uname)) + MENUSELECT_DEPENDS_app_chanspy+=RT + RT_LIB=-lrt +endif + +all: _all + +include $(ASTTOPDIR)/Makefile.moddir_rules + +ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) + LIBS+= -lres_features.so -lres_ael_share.so -lres_monitor.so -lres_speech.so + LIBS+= -lres_smdi.so +endif + diff --git a/trunk/apps/app_adsiprog.c b/trunk/apps/app_adsiprog.c new file mode 100644 index 0000000000..1b09ce7b5e --- /dev/null +++ b/trunk/apps/app_adsiprog.c @@ -0,0 +1,1581 @@ +/* + * 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$") + +#include +#include + +#include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */ +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/adsi.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, *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], *a; + int bytes = 0; + + if (!(a = get_token(&args, script, lineno))) { + 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 = get_token(&args, script, lineno); + char *gline = get_token(&args, script, lineno); + int line; + unsigned char cmd; + + 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 = get_token(&args, script, lineno); + char *gline = get_token(&args, script, lineno); + int line; + unsigned char cmd; + + 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 = get_token(&args, script, lineno); + int ms; + + 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 = get_token(&args, script, lineno); + int state; + + 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 = 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; x < state->numflags; 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 = get_token(&args, script, lineno); + char sname[80]; + struct adsi_flag *flag; + + 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; + } + + if (!(flag = getflagbyname(state, sname, script, lineno, 0))) { + 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 = get_token(&args, script, lineno); + struct adsi_flag *flag; + char sname[80]; + + 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; + } + + if (!(flag = getflagbyname(state, sname, script, lineno, 0))) { + 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 = get_token(&args, script, lineno); + int secs; + + 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; x < sizeof(events) / sizeof(events[0]); x++) { + if (!strcasecmp(events[x].name, name)) + return events[x].id; + } + + return 0; +} + +static int getjustifybyname(char *name) +{ + int x; + + for (x = 0; x numkeys; 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; x < state->numsubs; 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; x numstates; 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; x < state->numdisplays; 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, newkey[80]; + int bytes, x, flagid = 0; + unsigned char keyid[6]; + struct adsi_soft_key *key; + struct adsi_flag *flag; + + for (x = 0; x < 7; x++) { + /* Up to 6 key arguments */ + if (!(tok = get_token(&args, script, lineno))) + break; + if (!strcasecmp(tok, "UNLESS")) { + /* Check for trailing UNLESS flag */ + if (!(tok = get_token(&args, script, lineno))) { + 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; + } + + if (!(key = getkeybyname(state, newkey, script, lineno))) + break; + keyid[x] = key->id; + } + buf[0] = id; + buf[1] = (flagid & 0x7) << 3 | (x & 0x7); + for (bytes = 0; bytes < x; bytes++) { + buf[bytes + 2] = keyid[bytes]; + } + return 2 + x; +} + +static int showdisplay(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno) +{ + char *tok, dispname[80]; + int line = 0, flag = 0, cmd = 3; + struct adsi_display *disp; + + /* Get display */ + if (!(tok = get_token(&args, script, lineno)) || process_token(dispname, tok, sizeof(dispname) - 1, ARG_STRING)) { + ast_log(LOG_WARNING, "Invalid display name: %s at line %d of %s\n", tok ? tok : "", lineno, script); + return 0; + } + + if (!(disp = getdisplaybyname(state, dispname, script, lineno, 0))) { + ast_log(LOG_WARNING, "Display '%s' is undefined at line %d of %s\n", dispname, lineno, script); + return 0; + } + + if (!(tok = get_token(&args, script, lineno)) || strcasecmp(tok, "AT")) { + ast_log(LOG_WARNING, "Missing token 'AT' at line %d of %s\n", lineno, script); + return 0; + } + + /* Get line number */ + if (!(tok = get_token(&args, script, lineno)) || 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; + } + + if ((tok = get_token(&args, script, lineno)) && !strcasecmp(tok, "NOUPDATE")) { + cmd = 1; + tok = get_token(&args, script, lineno); + } + + if (tok && !strcasecmp(tok, "UNLESS")) { + /* Check for trailing UNLESS flag */ + if (!(tok = get_token(&args, script, lineno))) { + 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 = 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 = 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 = 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 = 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 = get_token(&args, script, lineno); + char subscript[80]; + struct adsi_subscript *sub; + + 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; + } + + if (!(sub = getsubbyname(state, subscript, script, lineno))) + 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 = get_token(&args, script, lineno); + char subscript[80], sname[80]; + int sawin = 0, event, snums[8], scnt = 0, x; + struct adsi_subscript *sub; + + if (!tok) { + ast_log(LOG_WARNING, "Missing event for 'ONEVENT' at line %d of %s\n", lineno, script); + return 0; + } + + if ((event = geteventbyname(tok)) < 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++; + if (!(tok = get_token(&args, script, lineno))) + 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); + } + if (!(tok = get_token(&args, script, lineno))) { + 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; + } + if (!(sub = getsubbyname(state, subscript, script, lineno))) + return 0; + buf[0] = 8; + buf[1] = event; + buf[2] = sub->id | 0x80; + for (x = 0; x < scnt; x++) + buf[3 + x] = snums[x]; + return 3 + scnt; +} + +struct adsi_key_cmd { + char *name; + int id; + int (*add_args)(char *buf, char *name, int id, char *args, struct adsi_script *state, char *script, int lineno); +}; + +static struct adsi_key_cmd kcmds[] = { + { "SENDDTMF", 0, send_dtmf }, + /* Encoded DTMF would go here */ + { "ONHOOK", 0x81 }, + { "OFFHOOK", 0x82 }, + { "FLASH", 0x83 }, + { "WAITDIALTONE", 0x84 }, + /* Send line number */ + { "BLANK", 0x86 }, + { "SENDCHARS", 0x87 }, + { "CLEARCHARS", 0x88 }, + { "BACKSPACE", 0x89 }, + /* Tab column */ + { "GOTOLINE", 0x8b, goto_line }, + { "GOTOLINEREL", 0x8c, goto_line_rel }, + { "PAGEUP", 0x8d }, + { "PAGEDOWN", 0x8e }, + /* Extended DTMF */ + { "DELAY", 0x90, send_delay }, + { "DIALPULSEONE", 0x91 }, + { "DATAMODE", 0x92 }, + { "VOICEMODE", 0x93 }, + /* Display call buffer 'n' */ + /* Clear call buffer 'n' */ + { "CLEARCB1", 0x95, clearcbone }, + { "DIGITCOLLECT", 0x96, digitcollect }, + { "DIGITDIRECT", 0x96, digitdirect }, + { "CLEAR", 0x97 }, + { "SHOWDISPLAY", 0x98, showdisplay }, + { "CLEARDISPLAY", 0x98, cleardisplay }, + { "SHOWKEYS", 0x99, showkeys }, + { "SETSTATE", 0x9a, set_state }, + { "TIMERSTART", 0x9b, starttimer }, + { "TIMERCLEAR", 0x9b, cleartimer }, + { "SETFLAG", 0x9c, setflag }, + { "CLEARFLAG", 0x9c, clearflag }, + { "GOTO", 0x9d, subscript }, + { "EVENT22", 0x9e }, + { "EVENT23", 0x9f }, + { "EXIT", 0xa0 }, +}; + +static struct adsi_key_cmd opcmds[] = { + + /* 1 - Branch on event -- handled specially */ + { "SHOWKEYS", 2, showkeys }, + /* Display Control */ + { "SHOWDISPLAY", 3, showdisplay }, + { "CLEARDISPLAY", 3, cleardisplay }, + { "CLEAR", 5 }, + { "SETSTATE", 6, set_state }, + { "TIMERSTART", 7, starttimer }, + { "TIMERCLEAR", 7, cleartimer }, + { "ONEVENT", 8, onevent }, + /* 9 - Subroutine label, treated specially */ + { "SETFLAG", 10, setflag }, + { "CLEARFLAG", 10, clearflag }, + { "DELAY", 11, send_delay }, + { "EXIT", 12 }, +}; + + +static int process_returncode(struct adsi_soft_key *key, char *code, char *args, struct adsi_script *state, char *script, int lineno) +{ + int x, res; + char *unused; + + for (x = 0; x < sizeof(kcmds) / sizeof(kcmds[0]); x++) { + if ((kcmds[x].id > -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, res, max = sub->id ? MAX_SUB_LEN : MAX_MAIN_LEN; + char *unused; + + for (x = 0; x < sizeof(opcmds) / sizeof(opcmds[0]); x++) { + if ((opcmds[x].id > -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 = get_token(&buf, script, lineno); + char *args, vname[256], tmp[80], tmp2[80]; + int lrci, wi, event; + struct adsi_display *disp; + struct adsi_subscript *newsub; + + if (!keyword) + return 0; + + switch(state->state) { + case STATE_NORMAL: + if (!strcasecmp(keyword, "DESCRIPTION")) { + if ((args = get_token(&buf, script, lineno))) { + 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")) { + if ((args = get_token(&buf, script, lineno))) { + 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")) { + if ((args = get_token(&buf, script, lineno))) { + 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")) { + if ((args = get_token(&buf, script, lineno))) { + 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")) { + if (!(args = get_token(&buf, script, lineno))) { + 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; + } + if (!(state->key = getkeybyname(state, vname, script, lineno))) { + 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; + } + if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) { + ast_log(LOG_WARNING, "Expecting 'IS', but got '%s' at line %d of %s\n", args ? args : "", lineno, script); + break; + } + if (!(args = get_token(&buf, script, lineno))) { + 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; + } + if ((args = get_token(&buf, script, lineno))) { + if (strcasecmp(args, "OR")) { + ast_log(LOG_WARNING, "Expecting 'OR' but got '%s' instead at line %d of %s\n", args, lineno, script); + break; + } + if (!(args = get_token(&buf, script, lineno))) { + 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")) { + if (!(args = get_token(&buf, script, lineno))) { + 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 (!(state->sub = getsubbyname(state, vname, script, lineno))) { + 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; + } + if (!(args = get_token(&buf, script, lineno)) || 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")) { + if (!(args = get_token(&buf, script, lineno))) { + 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")) { + if (!(args = get_token(&buf, script, lineno))) { + 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; + if (!(args = get_token(&buf, script, lineno))) { + 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; + } + if (!(disp = getdisplaybyname(state, vname, script, lineno, 1))) + break; + if (!(args = get_token(&buf, script, lineno)) || strcasecmp(args, "IS")) { + ast_log(LOG_WARNING, "Missing 'IS' at line %d of %s\n", lineno, script); + break; + } + if (!(args = get_token(&buf, script, lineno))) { + 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")) { + if (!(args = get_token(&buf, script, lineno))) { + 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; + } + if (!(newsub = getsubbyname(state, tmp, script, lineno))) + 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")) { + if (!(args = get_token(&buf, script, lineno))) { + ast_log(LOG_WARNING, "IFEVENT clause missing Event name at line %d of %s\n", lineno, script); + break; + } + if ((event = geteventbyname(args)) < 1) { + ast_log(LOG_WARNING, "'%s' is not a valid event\n", args); + break; + } + if (!(args = get_token(&buf, script, lineno)) || 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], buf[256], *c; + int lineno = 0, x, err; + struct adsi_script *scr; + + if (script[0] == '/') + ast_copy_string(fn, script, sizeof(fn)); + else + snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, script); + + if (!(f = fopen(fn, "r"))) { + 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'; + /* Strip comments */ + if ((c = strchr(buf, ';'))) + *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); + ast_free(scr); + return NULL; + case STATE_INKEY: + ast_log(LOG_WARNING, "Missing ENDKEY at end of file %s\n", script); + ast_free(scr); + return NULL; + } + err = 0; + + /* Resolve all keys and record their lengths */ + for (x = 0; x < scr->numkeys; 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; x < scr->numsubs; 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) { + ast_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; x < buflen; x++) + printf("%02x ", buf[x]); + printf("]\n"); +} +#endif + +static int adsi_prog(struct ast_channel *chan, char *script) +{ + struct adsi_script *scr; + int x, bytes; + unsigned char buf[1024]; + + if (!(scr = compile_script(script))) + return -1; + + /* Start an empty ADSI Session */ + if (ast_adsi_load_session(chan, NULL, 0, 1) < 1) + return -1; + + /* Now begin the download attempt */ + if (ast_adsi_begin_download(chan, scr->desc, scr->fdn, scr->sec, scr->ver)) { + /* User rejected us for some reason */ + ast_verb(3, "User rejected download attempt\n"); + ast_log(LOG_NOTICE, "User rejected download on channel %s\n", chan->name); + ast_free(scr); + return -1; + } + + bytes = 0; + /* Start with key definitions */ + for (x = 0; x < scr->numkeys; 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; x < scr->numdisplays; 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; x < scr->numsubs; 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 */ + ast_verb(3, "Download attempt failed\n"); + ast_log(LOG_NOTICE, "Download failed on %s\n", chan->name); + ast_free(scr); + return -1; + } + ast_free(scr); + ast_adsi_unload_session(chan); + return 0; +} + +static int adsi_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + + if (ast_strlen_zero(data)) + data = "asterisk.adsi"; + + if (!ast_adsi_available(chan)) { + ast_verb(3, "ADSI Unavailable on CPE. Not bothering to try.\n"); + } else { + ast_verb(3, "ADSI Available on CPE. Attempting Upload.\n"); + res = adsi_prog(chan, data); + } + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + if (ast_register_application(app, adsi_exec, synopsis, descrip)) + return AST_MODULE_LOAD_FAILURE; + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Asterisk ADSI Programming Application"); diff --git a/trunk/apps/app_alarmreceiver.c b/trunk/apps/app_alarmreceiver.c new file mode 100644 index 0000000000..c4fa81109f --- /dev/null +++ b/trunk/apps/app_alarmreceiver.c @@ -0,0 +1,813 @@ +/* + * 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$") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/ulaw.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){ + ast_verb(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)){ + ast_verb(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_debug(1,"AlarmReceiver: DTMF timeout on chan %s\n",chan->name); + + res = 1; + break; + } + + if ((r = ast_waitfor(chan, -1) < 0)) { + ast_debug(1, "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; + struct timeval t; + struct ast_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 */ + + t = ast_tvnow(); + ast_localtime(&t, &now, NULL); + + /* Format the time */ + + ast_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){ + if (option_verbose >= 3 ) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't write metadata\n"); + + ast_debug(1,"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) { + if (option_verbose >= 3) + ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't make temporary file\n"); + ast_debug(1,"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; + + ast_verb(2, "AlarmReceiver: Received Event %s\n", event); + ast_debug(1, "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){ + ast_verb(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"); + ast_verb(2, "AlarmReceiver: Nonzero checksum\n"); + ast_debug(1, "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"); + ast_verb(2, "AlarmReceiver: Wrong message type\n"); + ast_debug(1, "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; + event_node_t *elp, *efree; + char signalling_type[64] = ""; + + event_node_t *event_head = NULL; + + /* Set write and read formats to ULAW */ + + ast_verb(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); + 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); + return -1; + } + + /* Set default values for this invocation of the application */ + + ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type)); + + + /* Answer the channel if it is not already */ + + ast_verb(4, "AlarmReceiver: Answering channel\n"); + + if (chan->_state != AST_STATE_UP) { + if ((res = ast_answer(chan))) + return -1; + } + + /* Wait for the connection to settle post-answer */ + + ast_verb(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_debug(1,"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; + ast_free(efree); + } + + return 0; +} + +/* +* Load the configuration from the configuration file +*/ + +static int load_config(void) +{ + struct ast_config *cfg; + const char *p; + struct ast_flags config_flags = { 0 }; + + /* Read in the config file */ + + cfg = ast_config_load(ALMRCV_CONFIG, config_flags); + + 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) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + if(load_config()) { + if (ast_register_application(app, alarmreceiver_exec, synopsis, descrip)) + return AST_MODULE_LOAD_FAILURE; + return AST_MODULE_LOAD_SUCCESS; + } + else + return AST_MODULE_LOAD_DECLINE; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk"); diff --git a/trunk/apps/app_amd.c b/trunk/apps/app_amd.c new file mode 100644 index 0000000000..4e440d3887 --- /dev/null +++ b/trunk/apps/app_amd.c @@ -0,0 +1,418 @@ +/* + * 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. + */ + +/*! \file + * + * \brief Answering machine detection + * + * \author Claude Klimos (claude.klimos@aheeva.com) + */ + + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/lock.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],[|maximumWordLength])\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" +"- 'maximumWordLength' is the maximum duration of a word to accept. If exceeded then MACHINE\n" +"This application sets the following channel variables 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" +" MAXWORDLENGTH-<%d consecutiveVoiceDuration>\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; +static int dfltMaximumWordLength = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */ + +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; + int inInitialSilence = 1; + int inGreeting = 0; + int voiceDuration = 0; + int silenceDuration = 0; + int iTotalTime = 0; + int iWordsCount = 0; + int currentState = STATE_IN_SILENCE; + 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 maximumWordLength = dfltMaximumWordLength; + + 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); + AST_APP_ARG(argMaximumWordLength); + ); + + ast_verb(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); + if (!ast_strlen_zero(args.argMaximumWordLength)) + maximumWordLength = atoi(args.argMaximumWordLength); + } else { + ast_debug(1, "AMD using the default parameters.\n"); + } + + /* Now we're ready to roll! */ + ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " + "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n", + initialSilence, greeting, afterGreetingSilence, totalAnalysisTime, + minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength); + + /* 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, totalAnalysisTime)) > -1) { + /* If we fail to read in a frame, that means they hung up */ + if (!(f = ast_read(chan))) { + ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name); + ast_debug(1, "Got hangup\n"); + strcpy(amdStatus, "HANGUP"); + break; + } + + if (f->frametype == AST_FRAME_VOICE) { + /* If the total time exceeds the analysis time then give up as we are not too sure */ + framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS); + iTotalTime += framelength; + if (iTotalTime >= totalAnalysisTime) { + ast_verb(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 */ + dspsilence = 0; + ast_dsp_silence(silenceDetector, f, &dspsilence); + if (dspsilence) { + silenceDuration = dspsilence; + + if (silenceDuration >= betweenWordsSilence) { + if (currentState != STATE_IN_SILENCE ) { + previousState = currentState; + ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name); + } + /* Find words less than word duration */ + if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){ + ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration); + } + currentState = STATE_IN_SILENCE; + consecutiveVoiceDuration = 0; + } + + if (inInitialSilence == 1 && silenceDuration >= initialSilence) { + ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", + chan->name, silenceDuration, initialSilence); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); + break; + } + + if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { + ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", + chan->name, silenceDuration, afterGreetingSilence); + ast_frfree(f); + strcpy(amdStatus , "HUMAN"); + sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); + 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++; + ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount); + previousState = currentState; + currentState = STATE_IN_WORD; + } + if (consecutiveVoiceDuration >= maximumWordLength){ + ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration); + break; + } + if (iWordsCount >= maximumNumberOfWords) { + ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords); + break; + } + + if (inGreeting == 1 && voiceDuration >= greeting) { + ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting); + ast_frfree(f); + strcpy(amdStatus , "MACHINE"); + sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting); + break; + } + + if (voiceDuration >= minimumWordLength ) { + if (silenceDuration > 0) + ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration); + silenceDuration = 0; + } + if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0){ + /* Only go in here once to change the greeting flag when we detect the 1st word */ + if (silenceDuration > 0) + ast_verb(3, "AMD: Channel [%s]. Before Greeting Time: silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration); + inInitialSilence = 0; + inGreeting = 1; + } + + } + } + ast_frfree(f); + } + + if (!res) { + /* It took too long to get a frame back. Giving up. */ + ast_verb(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) +{ + isAnsweringMachine(chan, data); + + return 0; +} + +static int load_config(int reload) +{ + struct ast_config *cfg = NULL; + char *cat = NULL; + struct ast_variable *var = NULL; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + if (!(cfg = ast_config_load("amd.conf", config_flags))) { + ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n"); + return -1; + } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + 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 if (!strcasecmp(var->name, "maximum_word_length")) { + dfltMaximumWordLength = 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); + + ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " + "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n", + dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime, + dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength); + + return 0; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + if (load_config(0)) + return AST_MODULE_LOAD_DECLINE; + if (ast_register_application(app, amd_exec, synopsis, descrip)) + return AST_MODULE_LOAD_FAILURE; + return AST_MODULE_LOAD_SUCCESS; +} + +static int reload(void) +{ + if (load_config(1)) + return AST_MODULE_LOAD_DECLINE; + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/apps/app_authenticate.c b/trunk/apps/app_authenticate.c new file mode 100644 index 0000000000..3a3e7582e4 --- /dev/null +++ b/trunk/apps/app_authenticate.c @@ -0,0 +1,212 @@ +/* + * 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$") + +#include "asterisk/lock.h" +#include "asterisk/file.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" + +enum { + OPT_ACCOUNT = (1 << 0), + OPT_DATABASE = (1 << 1), + 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('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.\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" +" 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, retries, maxdigits; + char passwd[256], *prompt = "agent-pass", *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; + } + + if (chan->_state != AST_STATE_UP) { + if ((res = ast_answer(chan))) + 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 */ + for (retries = 0; retries < 3; retries++) { + if ((res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0)) < 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; + char buf[256] = "", md5passwd[33] = "", *md5secret = NULL; + + if (!(f = fopen(arglist.password, "r"))) { + ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno)); + continue; + } + + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!feof(f) && !ast_strlen_zero(buf)) { + buf[strlen(buf) - 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 { + /* 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); + if (!(res = ast_streamfile(chan, "auth-thankyou", chan->language))) + res = ast_waitstream(chan, ""); + } else { + if (!ast_streamfile(chan, "vm-goodbye", chan->language)) + res = ast_waitstream(chan, ""); + res = -1; + } + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + if (ast_register_application(app, auth_exec, synopsis, descrip)) + return AST_MODULE_LOAD_FAILURE; + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Authentication Application"); diff --git a/trunk/apps/app_cdr.c b/trunk/apps/app_cdr.c new file mode 100644 index 0000000000..7804a806bf --- /dev/null +++ b/trunk/apps/app_cdr.c @@ -0,0 +1,63 @@ +/* + * 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 + * + * \author Martin Pycko + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/channel.h" +#include "asterisk/module.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) +{ + if (chan->cdr) + ast_set_flag(chan->cdr, AST_CDR_FLAG_POST_DISABLED); + + return 0; +} + +static int unload_module(void) +{ + return ast_unregister_application(nocdr_app); +} + +static int load_module(void) +{ + if (ast_register_application(nocdr_app, nocdr_exec, nocdr_synopsis, nocdr_descrip)) + return AST_MODULE_LOAD_FAILURE; + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Tell Asterisk to not maintain a CDR for the current call"); diff --git a/trunk/apps/app_chanisavail.c b/trunk/apps/app_chanisavail.c new file mode 100644 index 0000000000..67d29e6eea --- /dev/null +++ b/trunk/apps/app_chanisavail.c @@ -0,0 +1,157 @@ +/* +* 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$") + +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/app.h" +#include "asterisk/devicestate.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.\n" +" Options:\n" +" s - Consider the channel unavailable if the channel is in use at all.\n" +" t - Simply checks if specified channels exist in the channel list\n" +" (implies option s).\n" +"This application sets the following channel variable upon completion:\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"; + + +static int chanavail_exec(struct ast_channel *chan, void *data) +{ + int res=-1, inuse=-1, option_state=0, string_compare=0; + int status; + 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; + } + + 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, 't')) + string_compare = 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"); + return -1; + } + *number = '\0'; + number++; + + if (string_compare) { + /* ast_parse_device_state checks for "SIP/1234" as a channel name. + ast_device_state will ask the SIP driver for the channel state. */ + + snprintf(trychan, sizeof(trychan), "%s/%s",cur,number); + status = inuse = ast_parse_device_state(trychan); + } else 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", ""); + } + + return 0; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_channelredirect.c b/trunk/apps/app_channelredirect.c new file mode 100644 index 0000000000..325c681acb --- /dev/null +++ b/trunk/apps/app_channelredirect.c @@ -0,0 +1,93 @@ +/* + * 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$") + +#include "asterisk/file.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" + +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; + char *info; + 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; + } + + 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; + } + + res = ast_parseable_goto(chan2, args.label); + + ast_channel_unlock(chan2); +quit: + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + return ast_register_application(app, asyncgoto_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Redirects a given channel to a dialplan target"); diff --git a/trunk/apps/app_chanspy.c b/trunk/apps/app_chanspy.c new file mode 100644 index 0000000000..a29973ff4e --- /dev/null +++ b/trunk/apps/app_chanspy.c @@ -0,0 +1,730 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com) + * Copyright (C) 2005 - 2006, 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 + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include + +#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */ +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/audiohook.h" +#include "asterisk/features.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 + +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" +" Note: The X option supersedes the three features above in that if a valid\n" +" single digit extension exists in the correct context ChanSpy will\n" +" exit to it. This also disables choosing a channel based on 'chanprefix'\n" +" and a digit sequence.\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" +" o - Only listen to audio coming from this channel.\n" +" X - Allow the user to exit ChanSpy to a valid single digit\n" +" numeric extension in the current context or the context\n" +" specified by the SPY_EXIT_CONTEXT channel variable. The\n" +" name of the last channel that was spied on will be stored\n" +" in the SPY_CHANNEL variable.\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" +" Note: The X option superseeds the two features above in that if a valid\n" +" single digit extension exists in the correct context it ChanSpy will\n" +" exit to it.\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" +" o - Only listen to audio coming from this channel.\n" +" X - Allow the user to exit ChanSpy to a valid single digit\n" +" numeric extension in the current context or the context\n" +" specified by the SPY_EXIT_CONTEXT channel variable. The\n" +" name of the last channel that was spied on will be stored\n" +" in the SPY_CHANNEL variable.\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 */ + OPTION_READONLY = (1 << 7), /* Don't mix the two channels */ + OPTION_EXIT = (1 << 8), /* Exit to a valid single digit extension */ +} 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), + AST_APP_OPTION('o', OPTION_READONLY), + AST_APP_OPTION('X', OPTION_EXIT), +}); + + +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 = NULL; + + ast_audiohook_lock(&csth->spy_audiohook); + if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) { + /* Channel is already gone more than likely */ + 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, struct ast_channel *spychan, struct ast_audiohook *audiohook) +{ + int res = 0; + struct ast_channel *peer = NULL; + + 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; +} + +static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int *volfactor, int fd, + const struct ast_flags *flags, char *exitcontext) +{ + 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; + + if (ast_check_hangup(chan) || ast_check_hangup(spyee)) + return 0; + + name = ast_strdupa(spyee->name); + ast_verb(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, chan, &csth.spy_audiohook)) { + ast_audiohook_destroy(&csth.spy_audiohook); + return 0; + } + + if (ast_test_flag(flags, OPTION_WHISPER)) { + ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy"); + start_spying(spyee, chan, &csth.whisper_audiohook); + } + + 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 (ast_test_flag(flags, OPTION_EXIT)) { + char tmp[2]; + tmp[0] = res; + tmp[1] = '\0'; + if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) { + ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext); + pbx_builtin_setvar_helper(chan, "SPY_CHANNEL", name); + running = -2; + break; + } else { + ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext); + } + } else if (res >= '0' && res <= '9') { + inp[x++] = res; + } + + if (res == '*') { + running = 0; + break; + } else if (res == '#') { + if (!ast_strlen_zero(inp)) { + running = atoi(inp); + break; + } + + (*volfactor)++; + if (*volfactor > 4) + *volfactor = -4; + ast_verb(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; + } + } + + 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); + + ast_verb(2, "Done Spying on channel %s\n", name); + + return running; +} + +static struct ast_channel *next_channel(const struct ast_channel *last, const char *spec, + const char *exten, const char *context) +{ + struct ast_channel *this; + + 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) { + ast_channel_unlock(this); + if (!strncmp(this->name, "Zap/pseudo", 10)) + goto redo; + } + + return this; +} + +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) +{ + struct ast_channel *peer, *prev, *next; + char nameprefix[AST_NAME_STRLEN]; + char peer_name[AST_NAME_STRLEN + 5]; + char exitcontext[AST_MAX_CONTEXT] = ""; + signed char zero_volume = 0; + int waitms; + int res; + char *ptr; + int num; + + if (ast_test_flag(flags, OPTION_EXIT)) { + const char *c; + if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) + ast_copy_string(exitcontext, c, 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 (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 (;;) { + if (!ast_test_flag(flags, OPTION_QUIET)) { + 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; + } + if (!ast_strlen_zero(exitcontext)) { + char tmp[2]; + tmp[0] = res; + tmp[1] = '\0'; + if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) + goto exit; + else + ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext); + } + } + + res = ast_waitfordigit(chan, waitms); + if (res < 0) { + ast_clear_flag(chan, AST_FLAG_SPYING); + break; + } + if (!ast_strlen_zero(exitcontext)) { + char tmp[2]; + tmp[0] = res; + tmp[1] = '\0'; + if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) + goto exit; + else + ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext); + } + + /* reset for the next loop around, unless overridden later */ + waitms = 100; + peer = prev = next = NULL; + + for (peer = next_channel(peer, spec, exten, context); + peer; + prev = peer, peer = next ? next : next_channel(peer, spec, exten, context), next = NULL) { + const char *group; + int igrp = !mygroup; + char *groups[25]; + int num_groups = 0; + char *dup_group; + int x; + char *s; + + if (peer == prev) + break; + + if (peer == chan) + continue; + + if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer)) + continue; + + if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING)) + continue; + + if (mygroup) { + if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) { + dup_group = ast_strdupa(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) + continue; + + strcpy(peer_name, "spy-"); + strncat(peer_name, peer->name, AST_NAME_STRLEN); + ptr = strchr(peer_name, '/'); + *ptr++ = '\0'; + + for (s = peer_name; s < ptr; s++) + *s = tolower(*s); + + 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) + break; + } else + res = ast_say_character_str(chan, peer_name, "", chan->language); + if ((num = atoi(ptr))) + ast_say_digits(chan, atoi(ptr), "", chan->language); + } + + waitms = 5000; + res = channel_spy(chan, peer, &volfactor, fd, flags, exitcontext); + + if (res == -1) { + goto exit; + } else if (res == -2) { + res = 0; + goto exit; + } else if (res > 1 && spec) { + snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res); + if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) { + ast_channel_unlock(next); + } else { + /* stay on this channel */ + next = peer; + } + peer = NULL; + } + } + } +exit: + + ast_clear_flag(chan, AST_FLAG_SPYING); + + ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0); + + return res; +} + +static int chanspy_exec(struct ast_channel *chan, void *data) +{ + char *mygroup = NULL; + char *recbase = NULL; + int fd = 0; + struct ast_flags flags; + int oldwf = 0; + int volfactor = 0; + int res; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(spec); + AST_APP_ARG(options); + ); + char *opts[OPT_ARG_ARRAY_SIZE]; + + data = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, data); + + if (args.spec && !strcmp(args.spec, "all")) + args.spec = NULL; + + if (args.options) { + ast_app_parse_options(spy_opts, &flags, opts, args.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"); + return -1; + } + + if (recbase) { + char filename[512]; + + 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, AST_FILE_MODE)) <= 0) { + ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename); + fd = 0; + } + } + + res = common_exec(chan, &flags, volfactor, fd, mygroup, args.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"); + + return res; +} + +static int extenspy_exec(struct ast_channel *chan, void *data) +{ + char *ptr, *exten = NULL; + char *mygroup = NULL; + char *recbase = NULL; + int fd = 0; + struct ast_flags flags; + int oldwf = 0; + int volfactor = 0; + int res; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(context); + AST_APP_ARG(options); + ); + + data = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, data); + if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) { + exten = args.context; + *ptr++ = '\0'; + args.context = ptr; + } + + if (ast_strlen_zero(args.context)) + args.context = ast_strdupa(chan->context); + + if (args.options) { + char *opts[OPT_ARG_ARRAY_SIZE]; + + ast_app_parse_options(spy_opts, &flags, opts, args.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"); + return -1; + } + + if (recbase) { + char filename[512]; + + 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, AST_FILE_MODE)) <= 0) { + ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename); + fd = 0; + } + } + + res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, exten, args.context); + + if (fd) + close(fd); + + if (oldwf && ast_set_write_format(chan, oldwf) < 0) + ast_log(LOG_ERROR, "Could Not Set Write Format.\n"); + + return res; +} + +static int unload_module(void) +{ + int res = 0; + + res |= ast_unregister_application(app_chan); + res |= ast_unregister_application(app_ext); + + 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/trunk/apps/app_controlplayback.c b/trunk/apps/app_controlplayback.c new file mode 100644 index 0000000000..b6dfd898e0 --- /dev/null +++ b/trunk/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$") + +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/module.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" +" o(#) - Start at # ms from the beginning of the file.\n" +"This application sets the following channel variables upon completion:\n" +" CPLAYBACKSTATUS - This variable contains the status of the attempt as a text\n" +" string, one of: SUCCESS | USERSTOPPED | ERROR\n" +" CPLAYBACKOFFSET - This contains the offset in ms into the file where\n" +" playback was at when it stopped. -1 is end of file.\n" +" CPLAYBACKSTOPKEY - If the playback is stopped by the user this variable contains\n" +" the key that was pressed.\n"; + +enum { + OPT_OFFSET = (1 << 1), +}; + +enum { + OPT_ARG_OFFSET = 0, + /* must stay as the last entry ... */ + OPT_ARG_ARRAY_LEN, +}; + +AST_APP_OPTIONS(cpb_opts, BEGIN_OPTIONS + AST_APP_OPTION_ARG('o', OPT_OFFSET, OPT_ARG_OFFSET), +END_OPTIONS ); + +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; + int skipms = 0; + long offsetms = 0; + char offsetbuf[20]; + char stopkeybuf[2]; + char *tmp; + struct ast_flags opts = { 0, }; + char *opt_args[OPT_ARG_ARRAY_LEN]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filename); + AST_APP_ARG(skip); + AST_APP_ARG(fwd); + AST_APP_ARG(rev); + AST_APP_ARG(stop); + AST_APP_ARG(pause); + AST_APP_ARG(restart); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n"); + return -1; + } + + tmp = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, tmp); + + if (args.argc < 1) { + ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n"); + return -1; + } + + skipms = args.skip ? (atoi(args.skip) ? atoi(args.skip) : 3000) : 3000; + + if (!args.fwd || !is_on_phonepad(*args.fwd)) + args.fwd = "#"; + if (!args.rev || !is_on_phonepad(*args.rev)) + args.rev = "*"; + if (args.stop && !is_on_phonepad(*args.stop)) + args.stop = NULL; + if (args.pause && !is_on_phonepad(*args.pause)) + args.pause = NULL; + if (args.restart && !is_on_phonepad(*args.restart)) + args.restart = NULL; + + if (args.options) { + ast_app_parse_options(cpb_opts, &opts, opt_args, args.options); + if (ast_test_flag(&opts, OPT_OFFSET)) + offsetms = atol(opt_args[OPT_ARG_OFFSET]); + } + + res = ast_control_streamfile(chan, args.filename, args.fwd, args.rev, args.stop, args.pause, args.restart, skipms, &offsetms); + + /* If we stopped on one of our stop keys, return 0 */ + if (res > 0 && args.stop && strchr(args.stop, res)) { + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED"); + snprintf(stopkeybuf, sizeof(stopkeybuf), "%c", res); + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTOPKEY", stopkeybuf); + res = 0; + } else { + if (res < 0) { + res = 0; + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR"); + } else + pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS"); + } + + snprintf(offsetbuf, sizeof(offsetbuf), "%ld", offsetms); + pbx_builtin_setvar_helper(chan, "CPLAYBACKOFFSET", offsetbuf); + + 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/trunk/apps/app_db.c b/trunk/apps/app_db.c new file mode 100644 index 0000000000..0cc5043cea --- /dev/null +++ b/trunk/apps/app_db.c @@ -0,0 +1,139 @@ +/* + * 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$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/astdb.h" +#include "asterisk/lock.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; + + argv = ast_strdupa(data); + + if (strchr(argv, '/')) { + family = strsep(&argv, "/"); + keytree = strsep(&argv, "\0"); + if (!family || !keytree) { + ast_debug(1, "Ignoring; Syntax error in argument\n"); + return 0; + } + if (ast_strlen_zero(keytree)) + keytree = 0; + } else { + family = argv; + keytree = 0; + } + + if (keytree) + ast_verb(3, "DBdeltree: family=%s, keytree=%s\n", family, keytree); + else + ast_verb(3, "DBdeltree: family=%s\n", family); + + if (ast_db_deltree(family, keytree)) + ast_verb(3, "DBdeltree: Error deleting key from database.\n"); + + return 0; +} + +static int del_exec(struct ast_channel *chan, void *data) +{ + char *argv, *family, *key; + static int deprecation_warning = 0; + + 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_debug(1, "Ignoring; Syntax error in argument\n"); + return 0; + } + ast_verb(3, "DBdel: family=%s, key=%s\n", family, key); + if (ast_db_del(family, key)) + ast_verb(3, "DBdel: Error deleting key from database.\n"); + } else { + ast_debug(1, "Ignoring, no parameters\n"); + } + + 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/trunk/apps/app_dial.c b/trunk/apps/app_dial.c new file mode 100644 index 0000000000..db1f76c8d6 --- /dev/null +++ b/trunk/apps/app_dial.c @@ -0,0 +1,2047 @@ +/* + * 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 + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include + +#include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */ +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.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" +" c - If DIAL cancels this call, always set the flag to tell the channel\n" +" driver that the call is answered elsewhere.\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" +" e - execute the 'h' extension for peer after the call ends\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" +" 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.\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" +" U(x[^arg]) - Execute via Gosub the routine 'x' for the *called* channel before connecting\n" +" to the calling channel. Arguments can be specified to the Gosub\n" +" using '^' as a delimeter. The Gosub routine can set the variable\n" +" GOSUB_RESULT to specify the following actions after the Gosub returns.\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.\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 routine.\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" +" x - Allow the called party to enable recording of the call by sending\n" +" the DTMF sequence defined for one-touch automixmonitor in features.conf\n" +" X - Allow the calling party to enable recording of the call by sending\n" +" the DTMF sequence defined for one-touch automixmonitor 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 retying the call. After 'retires' 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_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), + OPT_CALLEE_GOSUB = (1 << 28), + OPT_CALLEE_MIXMONITOR = (1 << 29), + OPT_CALLER_MIXMONITOR = (1 << 30), +}; + +#define DIAL_STILLGOING (1 << 31) +#define DIAL_NOFORWARDHTML ((uint64_t)1 << 32) /* flags are now 64 bits, so keep it up! */ +#define OPT_CANCEL_ELSEWHERE ((uint64_t)1 << 33) +#define OPT_PEER_H ((uint64_t)1 << 34) + +enum { + OPT_ARG_ANNOUNCE = 0, + OPT_ARG_SENDDTMF, + OPT_ARG_GOTO, + OPT_ARG_DURATION_LIMIT, + OPT_ARG_MUSICBACK, + OPT_ARG_CALLEE_MACRO, + OPT_ARG_CALLEE_GOSUB, + 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, +}; + +AST_APP_OPTIONS(dial_exec_options, BEGIN_OPTIONS + AST_APP_OPTION_ARG('A', OPT_ANNOUNCE, OPT_ARG_ANNOUNCE), + AST_APP_OPTION('C', OPT_RESETCDR), + AST_APP_OPTION('c', OPT_CANCEL_ELSEWHERE), + AST_APP_OPTION('d', OPT_DTMF_EXIT), + AST_APP_OPTION_ARG('D', OPT_SENDDTMF, OPT_ARG_SENDDTMF), + AST_APP_OPTION('e', OPT_PEER_H), + 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('k', OPT_CALLEE_PARK), + AST_APP_OPTION('K', OPT_CALLER_PARK), + 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_ARG('U', OPT_CALLEE_GOSUB, OPT_ARG_CALLEE_GOSUB), + AST_APP_OPTION('w', OPT_CALLEE_MONITOR), + AST_APP_OPTION('W', OPT_CALLER_MONITOR), + AST_APP_OPTION('x', OPT_CALLEE_MIXMONITOR), + AST_APP_OPTION('X', OPT_CALLER_MIXMONITOR), +END_OPTIONS ); + +#define CAN_EARLY_BRIDGE(flags) (!ast_test_flag64(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)) + +/* + * The list of active channels + */ +struct chanlist { + struct chanlist *next; + struct ast_channel *chan; + uint64_t flags; +}; + + +static void hanguptree(struct chanlist *outgoing, struct ast_channel *exception, int answered_elsewhere) +{ + /* Hang up a tree of stuff */ + struct chanlist *oo; + while (outgoing) { + /* Hangup any existing lines we have open */ + if (outgoing->chan && (outgoing->chan != exception)) { + if (answered_elsewhere) + ast_set_flag(outgoing->chan, AST_FLAG_ANSWERED_ELSEWHERE); + ast_hangup(outgoing->chan); + } + oo = outgoing; + outgoing = outgoing->next; + ast_free(oo); + } +} + +#define AST_MAX_WATCHERS 256 + +/* + * argument to handle_cause() and other functions. + */ +struct cause_args { + struct ast_channel *chan; + int busy; + int congestion; + int nochan; +}; + +static void handle_cause(int cause, struct cause_args *num) +{ + struct ast_cdr *cdr = num->chan->cdr; + + switch(cause) { + case AST_CAUSE_BUSY: + if (cdr) + ast_cdr_busy(cdr); + num->busy++; + break; + + case AST_CAUSE_CONGESTION: + if (cdr) + ast_cdr_failed(cdr); + num->congestion++; + break; + + case AST_CAUSE_UNREGISTERED: + if (cdr) + ast_cdr_failed(cdr); + num->nochan++; + break; + + case AST_CAUSE_NORMAL_CLEARING: + break; + + default: + num->nochan++; + break; + } +} + +/* free the buffer if allocated, and set the pointer to the second arg */ +#define S_REPLACE(s, new_val) \ + do { \ + if (s) \ + ast_free(s); \ + s = (new_val); \ + } 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, const char *dialstring) +{ + manager_event(EVENT_FLAG_CALL, "Dial", + "SubEvent: Begin\r\n" + "Channel: %s\r\n" + "Destination: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "UniqueID: %s\r\n" + "DestUniqueID: %s\r\n" + "Dialstring: %s\r\n", + src->name, dst->name, S_OR(src->cid.cid_num, ""), + S_OR(src->cid.cid_name, ""), src->uniqueid, + dst->uniqueid, dialstring ? dialstring : ""); +} + +static void senddialendevent(const struct ast_channel *src, const char *dialstatus) +{ + manager_event(EVENT_FLAG_CALL, "Dial", + "SubEvent: End\r\n" + "Channel: %s\r\n" + "UniqueID: %s\r\n" + "DialStatus: %s\r\n", + src->name, src->uniqueid, dialstatus); +} + +/*! + * helper function for wait_for_answer() + * + * XXX this code is highly suspicious, as it essentially overwrites + * the outgoing channel without properly deleting it. + */ +static void do_forward(struct chanlist *o, + struct cause_args *num, struct ast_flags64 *peerflags, int single) +{ + char tmpchan[256]; + struct ast_channel *original = o->chan; + struct ast_channel *c = o->chan; /* the winner */ + struct ast_channel *in = num->chan; /* the input channel */ + 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 */ + ast_verb(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_flag64(peerflags, OPT_IGNORE_FORWARDING)) { + ast_verb(3, "Forwarding %s to '%s/%s' prevented.\n", in->name, tech, stuff); + c = o->chan = NULL; + cause = AST_CAUSE_BUSY; + } else { + /* Setup parameters */ + c = o->chan = ast_request(tech, in->nativeformats, stuff, &cause); + if (c) { + 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_flag64(o, DIAL_STILLGOING); + handle_cause(cause, num); + } else { + char *new_cid_num, *new_cid_name; + struct ast_channel *src; + + ast_rtp_make_compatible(c, in, single); + if (ast_test_flag64(o, OPT_FORCECLID)) { + new_cid_num = ast_strdup(S_OR(in->macroexten, in->exten)); + new_cid_name = NULL; /* XXX no name ? */ + src = c; /* XXX possible bug in previous code, which used 'winner' ? it may have changed */ + } else { + new_cid_num = ast_strdup(in->cid.cid_num); + new_cid_name = ast_strdup(in->cid.cid_name); + src = in; + } + ast_string_field_set(c, accountcode, src->accountcode); + c->cdrflags = src->cdrflags; + S_REPLACE(c->cid.cid_num, new_cid_num); + S_REPLACE(c->cid.cid_name, new_cid_name); + + if (in->cid.cid_ani) { /* XXX or maybe unconditional ? */ + S_REPLACE(c->cid.cid_ani, ast_strdup(in->cid.cid_ani)); + } + S_REPLACE(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_flag64(o, DIAL_STILLGOING); + ast_hangup(original); + c = o->chan = NULL; + num->nochan++; + } else { + senddialevent(in, c, stuff); + /* After calling, set callerid to extension */ + if (!ast_test_flag64(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(original); + } + } +} + +/* argument used for some functions. */ +struct privacy_args { + int sentringing; + int privdb_val; + char privcid[256]; + char privintro[1024]; + char status[256]; +}; + +static struct ast_channel *wait_for_answer(struct ast_channel *in, + struct chanlist *outgoing, int *to, struct ast_flags64 *peerflags, + struct privacy_args *pa, + const struct cause_args *num_in, int *result) +{ + struct cause_args num = *num_in; + int prestart = num.busy + num.congestion + num.nochan; + int orig = *to; + struct ast_channel *peer = NULL; + /* single is set if only one destination is enabled */ + int single = outgoing && !outgoing->next && !ast_test_flag64(outgoing, OPT_MUSICBACK | OPT_RINGBACK); +#ifdef HAVE_EPOLL + struct chanlist *epollo; +#endif + + 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); + } + +#ifdef HAVE_EPOLL + for (epollo = outgoing; epollo; epollo = epollo->next) + ast_poll_channel_add(in, epollo->chan); +#endif + + while (*to && !peer) { + struct chanlist *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_flag64(o, DIAL_STILLGOING) && o->chan) + watchers[pos++] = o->chan; + numlines++; + } + if (pos == 1) { /* only the input channel is available */ + if (numlines == (num.busy + num.congestion + num.nochan)) { + ast_verb(2, "Everyone is busy/congested at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan); + if (num.busy) + strcpy(pa->status, "BUSY"); + else if (num.congestion) + strcpy(pa->status, "CONGESTION"); + else if (num.nochan) + strcpy(pa->status, "CHANUNAVAIL"); + } else { + ast_verb(3, "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan); + } + *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_flag64(o, DIAL_STILLGOING) && c->_state == AST_STATE_UP) { + if (!peer) { + ast_verb(3, "%s answered %s\n", c->name, in->name); + peer = c; + ast_copy_flags64(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 | + OPT_CALLEE_MIXMONITOR | OPT_CALLER_MIXMONITOR | + DIAL_NOFORWARDHTML); + ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext)); + ast_copy_string(c->exten, "", sizeof(c->exten)); + } + continue; + } + if (c != winner) + continue; + /* here, o->chan == c == winner */ + if (!ast_strlen_zero(c->call_forward)) { + do_forward(o, &num, peerflags, single); + continue; + } + f = ast_read(winner); + if (!f) { + in->hangupcause = c->hangupcause; +#ifdef HAVE_EPOLL + ast_poll_channel_del(in, c); +#endif + ast_hangup(c); + c = o->chan = NULL; + ast_clear_flag64(o, DIAL_STILLGOING); + handle_cause(in->hangupcause, &num); + continue; + } + if (f->frametype == AST_FRAME_CONTROL) { + switch(f->subclass) { + case AST_CONTROL_ANSWER: + /* This is our guy if someone answered. */ + if (!peer) { + ast_verb(3, "%s answered %s\n", c->name, in->name); + peer = c; + ast_copy_flags64(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 | + OPT_CALLEE_MIXMONITOR | OPT_CALLER_MIXMONITOR | + DIAL_NOFORWARDHTML); + ast_copy_string(c->dialcontext, "", sizeof(c->dialcontext)); + ast_copy_string(c->exten, "", sizeof(c->exten)); + if (CAN_EARLY_BRIDGE(peerflags)) + /* Setup early bridge if appropriate */ + ast_channel_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: + ast_verb(3, "%s is busy\n", c->name); + in->hangupcause = c->hangupcause; + ast_hangup(c); + c = o->chan = NULL; + ast_clear_flag64(o, DIAL_STILLGOING); + handle_cause(AST_CAUSE_BUSY, &num); + break; + case AST_CONTROL_CONGESTION: + ast_verb(3, "%s is circuit-busy\n", c->name); + in->hangupcause = c->hangupcause; + ast_hangup(c); + c = o->chan = NULL; + ast_clear_flag64(o, DIAL_STILLGOING); + handle_cause(AST_CAUSE_CONGESTION, &num); + break; + case AST_CONTROL_RINGING: + ast_verb(3, "%s is ringing\n", c->name); + /* Setup early media if appropriate */ + if (single && CAN_EARLY_BRIDGE(peerflags)) + ast_channel_early_bridge(in, c); + if (!(pa->sentringing) && !ast_test_flag64(outgoing, OPT_MUSICBACK)) { + ast_indicate(in, AST_CONTROL_RINGING); + pa->sentringing++; + } + break; + case AST_CONTROL_PROGRESS: + ast_verb(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)) + ast_channel_early_bridge(in, c); + if (!ast_test_flag64(outgoing, OPT_RINGBACK)) + ast_indicate(in, AST_CONTROL_PROGRESS); + break; + case AST_CONTROL_VIDUPDATE: + ast_verb(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_PROCEEDING: + ast_verb(3, "%s is proceeding passing it to %s\n", c->name, in->name); + if (single && CAN_EARLY_BRIDGE(peerflags)) + ast_channel_early_bridge(in, c); + if (!ast_test_flag64(outgoing, OPT_RINGBACK)) + ast_indicate(in, AST_CONTROL_PROCEEDING); + break; + case AST_CONTROL_HOLD: + ast_verb(3, "Call on %s placed on hold\n", c->name); + ast_indicate(in, AST_CONTROL_HOLD); + break; + case AST_CONTROL_UNHOLD: + ast_verb(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_flag64(outgoing, OPT_RINGBACK | OPT_MUSICBACK)) { + ast_verb(3, "%s stopped sounds\n", c->name); + ast_indicate(in, -1); + pa->sentringing = 0; + } + break; + default: + ast_debug(1, "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_flag64(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_flag64(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_flag64(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_flag64(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; + strcpy(pa->status, "CANCEL"); + ast_cdr_noanswer(in->cdr); + if (f) + ast_frfree(f); + return NULL; + } + + /* now f is guaranteed non-NULL */ + if (f->frametype == AST_FRAME_DTMF) { + if (ast_test_flag64(peerflags, OPT_DTMF_EXIT)) { + const char *context = pbx_builtin_getvar_helper(in, "EXITCONTEXT"); + if (onedigit_goto(in, context, (char) f->subclass, 1)) { + ast_verb(3, "User hit %c to disconnect call.\n", f->subclass); + *to = 0; + ast_cdr_noanswer(in->cdr); + *result = f->subclass; + strcpy(pa->status, "CANCEL"); + ast_frfree(f); + return NULL; + } + } + + if (ast_test_flag64(peerflags, OPT_CALLER_HANGUP) && + (f->subclass == '*')) { /* hmm it it not guaranteed to be '*' anymore. */ + ast_verb(3, "User hit %c to disconnect call.\n", f->subclass); + *to = 0; + strcpy(pa->status, "CANCEL"); + ast_cdr_noanswer(in->cdr); + ast_frfree(f); + return NULL; + } + } + + /* Forward HTML stuff */ + if (single && (f->frametype == AST_FRAME_HTML) && !ast_test_flag64(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))) { + ast_verb(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) + ast_verb(3, "Nobody picked up in %d ms\n", orig); + if (!*to || ast_check_hangup(in)) { + ast_cdr_noanswer(in->cdr); + } + + } + if (peer && !ast_cdr_log_unanswered()) { + /* suppress the CDR's that didn't win */ + struct chanlist *o; + for (o = outgoing; o; o = o->next) { + struct ast_channel *c = o->chan; + if (c && c != peer && c->cdr) { + ast_set_flag(c->cdr, AST_CDR_FLAG_POST_DISABLED); + } + } + } else if (!peer && !ast_cdr_log_unanswered()) { + /* suppress the CDR's that didn't win */ + struct chanlist *o; + for (o = outgoing; o; o = o->next) { + struct ast_channel *c = o->chan; + if (c && c->cdr) { + ast_set_flag(c->cdr, AST_CDR_FLAG_POST_DISABLED); + } + } + } + +#ifdef HAVE_EPOLL + for (epollo = outgoing; epollo; epollo = epollo->next) { + if (epollo->chan) + ast_poll_channel_del(in, epollo->chan); + } +#endif + + 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_flags64 *opts, int res) +{ + if (res < '1') + return 0; + if (ast_test_flag64(opts, OPT_PRIVACY) && res <= '5') + return 1; + if (ast_test_flag64(opts, OPT_SCREENING) && res <= '4') + return 1; + return 0; +} + +static int do_timelimit(struct ast_channel *chan, struct ast_bridge_config *config, + char *parse, unsigned int *calldurationlimit) +{ + char *stringp = ast_strdupa(parse); + char *limit_str, *warning_str, *warnfreq_str; + const char *var; + int play_to_caller = 0, play_to_callee = 0; + int delta; + + limit_str = strsep(&stringp, ":"); + warning_str = strsep(&stringp, ":"); + warnfreq_str = strsep(&stringp, ":"); + + config->timelimit = atol(limit_str); + if (warning_str) + config->play_warning = atol(warning_str); + if (warnfreq_str) + config->warning_freq = atol(warnfreq_str); + + if (!config->timelimit) { + ast_log(LOG_WARNING, "Dial does not accept L(%s), hanging up.\n", limit_str); + config->timelimit = config->play_warning = config->warning_freq = 0; + config->warning_sound = NULL; + return -1; /* error */ + } else if ( (delta = config->play_warning - config->timelimit) > 0) { + int w = config->warning_freq; + + /* 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. + Graphically: + timelim->| delta |<-playwarning + 0__________________|_________________| + | w | | | | + + so the number of intervals to cut is 1+(delta-1)/w + */ + + if (w == 0) { + config->play_warning = 0; + } else { + config->play_warning -= w * ( 1 + (delta-1)/w ); + if (config->play_warning < 1) + config->play_warning = config->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"); + config->warning_sound = S_OR(var, "timeleft"); + + /* The code looking at config wants a NULL, not just "", to decide + * that the message should not be played, so we replace "" with NULL. + * Note, pbx_builtin_getvar_helper _can_ return NULL if the variable is + * not found. + */ + var = pbx_builtin_getvar_helper(chan, "LIMIT_TIMEOUT_FILE"); + config->end_sound = S_OR(var, NULL); + var = pbx_builtin_getvar_helper(chan, "LIMIT_CONNECT_FILE"); + config->start_sound = S_OR(var, NULL); + + /* 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 (!config->play_warning && !config->start_sound && !config->end_sound && config->timelimit) { + *calldurationlimit = config->timelimit / 1000; + ast_verb(3, "Setting call duration limit to %d seconds.\n", + *calldurationlimit); + config->timelimit = play_to_caller = play_to_callee = + config->play_warning = config->warning_freq = 0; + } else { + ast_verb(3, "Limit Data for this call:\n"); + ast_verb(4, "timelimit = %ld\n", config->timelimit); + ast_verb(4, "play_warning = %ld\n", config->play_warning); + ast_verb(4, "play_to_caller = %s\n", play_to_caller ? "yes" : "no"); + ast_verb(4, "play_to_callee = %s\n", play_to_callee ? "yes" : "no"); + ast_verb(4, "warning_freq = %ld\n", config->warning_freq); + ast_verb(4, "start_sound = %s\n", S_OR(config->start_sound, "")); + ast_verb(4, "warning_sound = %s\n", config->warning_sound); + ast_verb(4, "end_sound = %s\n", S_OR(config->end_sound, "")); + } + 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); + return 0; +} + +static int do_privacy(struct ast_channel *chan, struct ast_channel *peer, + struct ast_flags64 *opts, char **opt_args, struct privacy_args *pa) +{ + + 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_flag64(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_flag64(opts, OPT_RINGBACK)) { + ast_indicate(chan, AST_CONTROL_RINGING); + pa->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, pa->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_flag64(opts, OPT_PRIVACY)) + res2 = ast_play_and_wait(peer, "priv-callee-options"); + if (ast_test_flag64(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_flag64(opts, OPT_MUSICBACK)) { + ast_moh_stop(chan); + } else if (ast_test_flag64(opts, OPT_RINGBACK)) { + ast_indicate(chan, -1); + pa->sentringing = 0; + } + ast_autoservice_stop(chan); + if (ast_test_flag64(opts, OPT_PRIVACY) && (res2 >= '1' && res2 <= '5')) { + /* map keypresses to various things, the index is res2 - '1' */ + static const char *_val[] = { "ALLOW", "DENY", "TORTURE", "KILL", "ALLOW" }; + static const int _flag[] = { AST_PRIVACY_ALLOW, AST_PRIVACY_DENY, AST_PRIVACY_TORTURE, AST_PRIVACY_KILL, AST_PRIVACY_ALLOW}; + int i = res2 - '1'; + ast_verb(3, "--Set privacy database entry %s/%s to %s\n", + opt_args[OPT_ARG_PRIVACY], pa->privcid, _val[i]); + ast_privacy_set(opt_args[OPT_ARG_PRIVACY], pa->privcid, _flag[i]); + } + switch (res2) { + case '1': + break; + case '2': + ast_copy_string(pa->status, "NOANSWER", sizeof(pa->status)); + break; + case '3': + ast_copy_string(pa->status, "TORTURE", sizeof(pa->status)); + break; + case '4': + ast_copy_string(pa->status, "DONTCALL", sizeof(pa->status)); + break; + case '5': + /* XXX should we set status to DENY ? */ + if (ast_test_flag64(opts, OPT_PRIVACY)) + break; + /* 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"); + /* XXX should we set status to DENY ? */ + /* XXX what about the privacy flags ? */ + break; + } + + if (res2 == '1') { /* the only case where we actually connect */ + /* 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(pa->privcid, "NOCALLERID", 10) == 0 || ast_test_flag64(opts, OPT_SCREEN_NOINTRO)) { + ast_filedelete(pa->privintro, NULL); + if (ast_fileexists(pa->privintro, NULL, NULL) > 0) + ast_log(LOG_NOTICE, "privacy: ast_filedelete didn't do its job on %s\n", pa->privintro); + else + ast_verb(3, "Successfully deleted %s intro file\n", pa->privintro); + } + return 0; /* the good exit path */ + } else { + ast_hangup(peer); /* hang up on the callee -- he didn't want to talk anyway! */ + return -1; + } +} + +/*! \brief returns 1 if successful, 0 or <0 if the caller should 'goto out' */ +static int setup_privacy_args(struct privacy_args *pa, + struct ast_flags64 *opts, char *opt_args[], struct ast_channel *chan) +{ + char callerid[60]; + int res; + char *l; + + if (!ast_strlen_zero(chan->cid.cid_num)) { + l = ast_strdupa(chan->cid.cid_num); + ast_shrink_phone_number(l); + if (ast_test_flag64(opts, OPT_PRIVACY) ) { + ast_verb(3, "Privacy DB is '%s', clid is '%s'\n", opt_args[OPT_ARG_PRIVACY], l); + pa->privdb_val = ast_privacy_check(opt_args[OPT_ARG_PRIVACY], l); + } else { + ast_verb(3, "Privacy Screening, clid is '%s'\n", l); + pa->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 == '/') /* any other chars to be afraid of? */ + *tn2 = '='; + } + ast_verb(3, "Privacy-- callerid is empty\n"); + + snprintf(callerid, sizeof(callerid), "NOCALLERID_%s%s", chan->exten, tnam); + l = callerid; + pa->privdb_val = AST_PRIVACY_UNKNOWN; + } + + ast_copy_string(pa->privcid, l, sizeof(pa->privcid)); + + if (strncmp(pa->privcid, "NOCALLERID", 10) != 0 && ast_test_flag64(opts, OPT_SCREEN_NOCLID)) { + /* if callerid is set and OPT_SCREEN_NOCLID is set also */ + ast_verb(3, "CallerID set (%s); N option set; Screening should be off\n", pa->privcid); + pa->privdb_val = AST_PRIVACY_ALLOW; + } else if (ast_test_flag64(opts, OPT_SCREEN_NOCLID) && strncmp(pa->privcid, "NOCALLERID", 10) == 0) { + ast_verb(3, "CallerID blank; N option set; Screening should happen; dbval is %d\n", pa->privdb_val); + } + + if (pa->privdb_val == AST_PRIVACY_DENY) { + ast_verb(3, "Privacy DB reports PRIVACY_DENY for this callerid. Dial reports unavailable\n"); + ast_copy_string(pa->status, "NOANSWER", sizeof(pa->status)); + return 0; + } else if (pa->privdb_val == AST_PRIVACY_KILL) { + ast_copy_string(pa->status, "DONTCALL", sizeof(pa->status)); + return 0; /* Is this right? */ + } else if (pa->privdb_val == AST_PRIVACY_TORTURE) { + ast_copy_string(pa->status, "TORTURE", sizeof(pa->status)); + return 0; /* is this right??? */ + } else if (pa->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(pa->privintro, sizeof(pa->privintro), "%s/sounds/priv-callerintros", ast_config_AST_DATA_DIR); + if ((res = ast_mkdir(pa->privintro, 0755))) { + ast_log(LOG_WARNING, "privacy: can't create directory priv-callerintros: %s\n", strerror(res)); + return -1; + } + + snprintf(pa->privintro, sizeof(pa->privintro), "priv-callerintros/%s", pa->privcid); + if (ast_fileexists(pa->privintro, NULL, NULL ) > 0 && strncmp(pa->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", pa->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 pa.privintro file */ + if (res == -1) { + /* Delete the file regardless since they hung up during recording */ + ast_filedelete(pa->privintro, NULL); + if (ast_fileexists(pa->privintro, NULL, NULL) > 0) + ast_log(LOG_NOTICE, "privacy: ast_filedelete didn't do its job on %s\n", pa->privintro); + else + ast_verb(3, "Successfully deleted %s intro file\n", pa->privintro); + return -1; + } + if (!ast_streamfile(chan, "vm-dialout", chan->language) ) + ast_waitstream(chan, ""); + } + } + return 1; /* success */ +} + +static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags64 *peerflags, int *continue_exec) +{ + int res = -1; /* default: error */ + char *rest, *cur; /* scan the list of destinations */ + struct chanlist *outgoing = NULL; /* list of destinations */ + struct ast_channel *peer; + int to; /* timeout */ + struct cause_args num = { chan, 0, 0, 0 }; + int cause; + char numsubst[256]; + char cidname[AST_MAX_EXTENSION] = ""; + + struct ast_bridge_config config = { { 0, } }; + unsigned int calldurationlimit = 0; + char *dtmfcalled = NULL, *dtmfcalling = NULL; + struct privacy_args pa = { + .sentringing = 0, + .privdb_val = 0, + .status = "INVALIDARGS", + }; + int sentringing = 0, moh = 0; + const char *outbound_group = NULL; + int result = 0; + time_t start_time; + 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_flags64 opts = { 0, }; + char *opt_args[OPT_ARG_ARRAY_SIZE]; + struct ast_datastore *datastore = NULL; + 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", pa.status); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (!ast_strlen_zero(args.options) && + ast_app_parse_options64(dial_exec_options, &opts, opt_args, args.options)) { + pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.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", pa.status); + goto done; + } + + if (ast_test_flag64(&opts, OPT_OPERMODE)) { + opermode = ast_strlen_zero(opt_args[OPT_ARG_OPERMODE]) ? 1 : atoi(opt_args[OPT_ARG_OPERMODE]); + ast_verb(3, "Setting operator services mode to %d.\n", opermode); + } + + if (ast_test_flag64(&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", pa.status); + goto done; + } + ast_verb(3, "Setting call duration limit to %d seconds.\n", calldurationlimit); + } + + if (ast_test_flag64(&opts, OPT_SENDDTMF) && !ast_strlen_zero(opt_args[OPT_ARG_SENDDTMF])) { + dtmfcalling = opt_args[OPT_ARG_SENDDTMF]; + dtmfcalled = strsep(&dtmfcalling, ":"); + } + + if (ast_test_flag64(&opts, OPT_DURATION_LIMIT) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_LIMIT])) { + if (do_timelimit(chan, &config, opt_args[OPT_ARG_DURATION_LIMIT], &calldurationlimit)) + goto done; + } + + if (ast_test_flag64(&opts, OPT_RESETCDR) && chan->cdr) + ast_cdr_reset(chan->cdr, NULL); + if (ast_test_flag64(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY])) + opt_args[OPT_ARG_PRIVACY] = ast_strdupa(chan->exten); + + if (ast_test_flag64(&opts, OPT_PRIVACY) || ast_test_flag64(&opts, OPT_SCREENING)) { + res = setup_privacy_args(&pa, &opts, opt_args, chan); + if (res <= 0) + goto out; + res = -1; /* reset default */ + } + + 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_flags64(peerflags, &opts, OPT_DTMF_EXIT | OPT_GO_ON | OPT_ORIGINAL_CLID | OPT_CALLER_HANGUP | OPT_IGNORE_FORWARDING); + /* loop through the list of dial destinations */ + rest = args.peers; + while ((cur = strsep(&rest, "&")) ) { + struct chanlist *tmp; + struct ast_channel *tc; /* channel for this destination */ + /* 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; + 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_flags64(tmp, &opts, + OPT_CANCEL_ELSEWHERE | + 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_CALLEE_MIXMONITOR | OPT_CALLER_MIXMONITOR | + OPT_RINGBACK | OPT_MUSICBACK | OPT_FORCECLID); + ast_set2_flag64(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"); + ast_free(tmp); + goto out; + } + + datastore->inheritance = DATASTORE_INHERIT_FOREVER; + + if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) { + ast_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++; + ast_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); + ast_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); + } + + tc = ast_request(tech, chan->nativeformats, numsubst, &cause); + if (!tc) { + /* 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, &num); + if (!rest) /* we are on the last destination */ + chan->hangupcause = cause; + ast_free(tmp); + continue; + } + pbx_builtin_setvar_helper(tc, "DIALEDPEERNUMBER", numsubst); + + /* Setup outgoing SDP to match incoming one */ + ast_rtp_make_compatible(tc, chan, !outgoing && !rest); + + /* Inherit specially named variables from parent channel */ + ast_channel_inherit_variables(chan, tc); + + tc->appl = "AppDial"; + tc->data = "(Outgoing Line)"; + tc->whentohangup = 0; + + S_REPLACE(tc->cid.cid_num, ast_strdup(chan->cid.cid_num)); + S_REPLACE(tc->cid.cid_name, ast_strdup(chan->cid.cid_name)); + S_REPLACE(tc->cid.cid_ani, ast_strdup(chan->cid.cid_ani)); + S_REPLACE(tc->cid.cid_rdnis, ast_strdup(chan->cid.cid_rdnis)); + + /* Copy language from incoming to outgoing */ + ast_string_field_set(tc, language, chan->language); + ast_string_field_set(tc, accountcode, chan->accountcode); + tc->cdrflags = chan->cdrflags; + if (ast_strlen_zero(tc->musicclass)) + ast_string_field_set(tc, musicclass, chan->musicclass); + /* Pass callingpres, type of number, tns, ADSI CPE, transfer capability */ + tc->cid.cid_pres = chan->cid.cid_pres; + tc->cid.cid_ton = chan->cid.cid_ton; + tc->cid.cid_tns = chan->cid.cid_tns; + tc->cid.cid_ani2 = chan->cid.cid_ani2; + tc->adsicpe = chan->adsicpe; + tc->transfercapability = chan->transfercapability; + + /* If we have an outbound group, set this peer channel to it */ + if (outbound_group) + ast_app_group_set_channel(tc, outbound_group); + + /* Inherit context and extension */ + if (!ast_strlen_zero(chan->macrocontext)) + ast_copy_string(tc->dialcontext, chan->macrocontext, sizeof(tc->dialcontext)); + else + ast_copy_string(tc->dialcontext, chan->context, sizeof(tc->dialcontext)); + if (!ast_strlen_zero(chan->macroexten)) + ast_copy_string(tc->exten, chan->macroexten, sizeof(tc->exten)); + else + ast_copy_string(tc->exten, chan->exten, sizeof(tc->exten)); + + res = ast_call(tc, numsubst, 0); /* Place the call, but don't wait on the answer */ + + /* Save the info in cdr's that we called them */ + if (chan->cdr) + ast_cdr_setdestchan(chan->cdr, tc->name); + + /* check the results of ast_call */ + if (res) { + /* Again, keep going even if there's an error */ + ast_debug(1, "ast call on peer returned %d\n", res); + ast_verb(3, "Couldn't call %s\n", numsubst); + ast_hangup(tc); + tc = NULL; + ast_free(tmp); + continue; + } else { + senddialevent(chan, tc, numsubst); + ast_verb(3, "Called %s\n", numsubst); + if (!ast_test_flag64(peerflags, OPT_ORIGINAL_CLID)) + ast_set_callerid(tc, 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_flag64(tmp, DIAL_STILLGOING); + tmp->chan = tc; + 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(pa.status, "CHANUNAVAIL"); + if (fulldial == num_dialed) { + res = -1; + goto out; + } + } else { + /* Our status will at least be NOANSWER */ + strcpy(pa.status, "NOANSWER"); + if (ast_test_flag64(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_flag64(outgoing, OPT_RINGBACK)) { + ast_indicate(chan, AST_CONTROL_RINGING); + sentringing++; + } + } + + time(&start_time); + peer = wait_for_answer(chan, outgoing, &to, peerflags, &pa, &num, &result); + + 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); + char toast[80]; /* buffer to set variables */ + + strcpy(pa.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, 1); + 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) ) { + ast_debug(1, "app_dial: sendurl=%s.\n", args.url); + ast_channel_sendurl( peer, args.url ); + } + if ( (ast_test_flag64(&opts, OPT_PRIVACY) || ast_test_flag64(&opts, OPT_SCREENING)) && pa.privdb_val == AST_PRIVACY_UNKNOWN) { + if (do_privacy(chan, peer, &opts, opt_args, &pa)) { + res = 0; + goto out; + } + } + if (!ast_test_flag64(&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, 0); + else + res = digit; + + } + + if (chan && peer && ast_test_flag64(&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, ast_test_flag64(&opts, OPT_CANCEL_ELSEWHERE) ? 1 : 0); + if (continue_exec) + *continue_exec = 1; + res = 0; + goto done; + } + + if (ast_test_flag64(&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_debug(1, "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(pa.status, macro_result, sizeof(pa.status)); + ast_set_flag64(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(macro_result, "CONGESTION") || !strcasecmp(macro_result, "CHANUNAVAIL")) { + ast_copy_string(pa.status, macro_result, sizeof(pa.status)); + ast_set_flag64(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_flag64(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_flag64(peerflags, OPT_GO_ON); + } + } + } + } + + if (ast_test_flag64(&opts, OPT_CALLEE_GOSUB) && !ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GOSUB])) { + struct ast_app *theapp; + const char *gosub_result; + char *gosub_args, *gosub_argstart; + + res = ast_autoservice_start(chan); + if (res) { + ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n"); + res = -1; + } + + theapp = pbx_findapp("Gosub"); + + if (theapp && !res) { /* XXX why check res here ? */ + replace_macro_delimiter(opt_args[OPT_ARG_CALLEE_GOSUB]); + + /* Set where we came from */ + ast_copy_string(peer->context, "app_dial_gosub_virtual_context", sizeof(peer->context)); + ast_copy_string(peer->exten, "s", sizeof(peer->exten)); + peer->priority = 0; + + gosub_argstart = strchr(opt_args[OPT_ARG_CALLEE_GOSUB], '|'); + if (gosub_argstart) { + *gosub_argstart = 0; + asprintf(&gosub_args, "%s|s|1(%s)", opt_args[OPT_ARG_CALLEE_GOSUB], gosub_argstart + 1); + *gosub_argstart = '|'; + } else { + asprintf(&gosub_args, "%s|s|1", opt_args[OPT_ARG_CALLEE_GOSUB]); + } + + if (gosub_args) { + res = pbx_exec(peer, theapp, gosub_args); + ast_pbx_run(peer); + ast_free(gosub_args); + if (option_debug) + ast_log(LOG_DEBUG, "Gosub exited with status %d\n", res); + } else + ast_log(LOG_ERROR, "Could not Allocate string for Gosub arguments -- Gosub Call Aborted!\n"); + + res = 0; + } else { + ast_log(LOG_ERROR, "Could not find application Gosub\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 && (gosub_result = pbx_builtin_getvar_helper(peer, "GOSUB_RESULT"))) { + char *gosub_transfer_dest; + + if (!strcasecmp(gosub_result, "BUSY")) { + ast_copy_string(pa.status, gosub_result, sizeof(pa.status)); + ast_set_flag64(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(gosub_result, "CONGESTION") || !strcasecmp(gosub_result, "CHANUNAVAIL")) { + ast_copy_string(pa.status, gosub_result, sizeof(pa.status)); + ast_set_flag64(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(gosub_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_flag64(peerflags, OPT_GO_ON); + res = -1; + } else if (!strcasecmp(gosub_result, "ABORT")) { + /* Hangup both ends unless the caller has the g flag */ + res = -1; + } else if (!strncasecmp(gosub_result, "GOTO:", 5) && (gosub_transfer_dest = ast_strdupa(gosub_result + 5))) { + res = -1; + /* perform a transfer to a new extension */ + if (strchr(gosub_transfer_dest, '^')) { /* context^exten^priority*/ + replace_macro_delimiter(gosub_transfer_dest); + if (!ast_parseable_goto(chan, gosub_transfer_dest)) + ast_set_flag64(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, 0); + } + 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, 0); + } + } + + if (res) { /* some error */ + res = -1; + end_time = time(NULL); + } else { + if (ast_test_flag64(peerflags, OPT_CALLEE_TRANSFER)) + ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); + if (ast_test_flag64(peerflags, OPT_CALLER_TRANSFER)) + ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); + if (ast_test_flag64(peerflags, OPT_CALLEE_HANGUP)) + ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT); + if (ast_test_flag64(peerflags, OPT_CALLER_HANGUP)) + ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT); + if (ast_test_flag64(peerflags, OPT_CALLEE_MONITOR)) + ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON); + if (ast_test_flag64(peerflags, OPT_CALLER_MONITOR)) + ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON); + if (ast_test_flag64(peerflags, OPT_CALLEE_PARK)) + ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL); + if (ast_test_flag64(peerflags, OPT_CALLER_PARK)) + ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL); + if (ast_test_flag64(peerflags, OPT_CALLEE_MIXMONITOR)) + ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMIXMON); + if (ast_test_flag64(peerflags, OPT_CALLER_MIXMONITOR)) + ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMIXMON); + + 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 && !strncmp(chan->name, "Zap", 3) && !strncmp(peer->name, "Zap", 3)) { + /* what's this special handling for Zap <-> Zap ? + * A: Zap to Zap calls are natively bridged at the kernel driver + * level, so we need to ensure that this mode gets propagated + * all the way down. */ + struct oprmode oprmode; + + oprmode.peer = peer; + oprmode.mode = opermode; + + ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0); + } + res = ast_bridge_call(chan, peer, &config); + end_time = time(NULL); + snprintf(toast, sizeof(toast), "%ld", (long)(end_time - answer_time)); + pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", toast); + } + + snprintf(toast, sizeof(toast), "%ld", (long)(end_time - start_time)); + pbx_builtin_setvar_helper(chan, "DIALEDTIME", toast); + + + if (ast_test_flag64(&opts, OPT_PEER_H)) { + ast_log(LOG_NOTICE, "PEER context: %s; PEER exten: %s; PEER priority: %d\n", + peer->context, peer->exten, peer->priority); + } + + strcpy(peer->context, chan->context); + + if (ast_test_flag64(&opts, OPT_PEER_H) && ast_exists_extension(peer, peer->context, "h", 1, peer->cid.cid_num)) { + int autoloopflag; + int found; + strcpy(peer->exten, "h"); + peer->priority = 1; + autoloopflag = ast_test_flag(peer, AST_FLAG_IN_AUTOLOOP); /* save value to restore at the end */ + ast_set_flag(peer, AST_FLAG_IN_AUTOLOOP); + + while ((res = ast_spawn_extension(peer, peer->context, peer->exten, peer->priority, peer->cid.cid_num, &found, 1))) { + peer->priority++; + } + if (found && res) { + /* Something bad happened, or a hangup has been requested. */ + ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", peer->context, peer->exten, peer->priority, peer->name); + ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", peer->context, peer->exten, peer->priority, peer->name); + } + ast_set2_flag(peer, autoloopflag, AST_FLAG_IN_AUTOLOOP); /* set it back the way it was */ + } + if (res != AST_PBX_NO_HANGUP_PEER) { + if (!ast_check_hangup(chan)) + 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_channel_early_bridge(chan, NULL); + hanguptree(outgoing, NULL, 0); /* In this case, there's no answer anywhere */ + pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status); + senddialendevent(chan, pa.status); + ast_debug(1, "Exiting with DIALSTATUS=%s.\n", pa.status); + + if ((ast_test_flag64(peerflags, OPT_GO_ON)) && !ast_check_hangup(chan) && (res != AST_PBX_KEEPALIVE)) { + if (calldurationlimit) + chan->whentohangup = 0; + res = 0; + } + +done: + return res; +} + +static int dial_exec(struct ast_channel *chan, void *data) +{ + struct ast_flags64 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 *parse; + const char *context = NULL; + int sleep = 0, loops = 0, res = -1; + struct ast_flags64 peerflags = { 0, }; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(announce); + AST_APP_ARG(sleep); + AST_APP_ARG(retries); + AST_APP_ARG(dialdata); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "RetryDial requires an argument!\n"); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if ((sleep = atoi(args.sleep))) { + sleep *= 1000; + } + + loops = atoi(args.retries); + + if (!args.dialdata) { + ast_log(LOG_ERROR, "%s requires a 4th argument (dialdata)\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, args.dialdata, &peerflags, &continue_exec); + if (continue_exec) + break; + + if (res == 0) { + if (ast_test_flag64(&peerflags, OPT_DTMF_EXIT)) { + if (!ast_strlen_zero(args.announce)) { + if (ast_fileexists(args.announce, NULL, chan->language) > 0) { + if (!(res = ast_streamfile(chan, args.announce, chan->language))) + ast_waitstream(chan, AST_DIGIT_ANY); + } else + ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", args.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(args.announce)) { + if (ast_fileexists(args.announce, NULL, chan->language) > 0) { + if (!(res = ast_streamfile(chan, args.announce, chan->language))) + res = ast_waitstream(chan, ""); + } else + ast_log(LOG_WARNING, "Announce file \"%s\" specified in Retrydial does not exist\n", args.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: + return res; +} + +static int unload_module(void) +{ + int res; + struct ast_context *con; + + res = ast_unregister_application(app); + res |= ast_unregister_application(rapp); + + if ((con = ast_context_find("app_dial_gosub_virtual_context"))) + { + ast_context_remove_extension2(con, "s", 1, NULL); + ast_context_destroy(con, "app_dial"); /* leave nothing behind */ + } + + return res; +} + +static int load_module(void) +{ + int res; + struct ast_context *con; + + con = ast_context_find("app_dial_gosub_virtual_context"); + if (!con) + con = ast_context_create(NULL, "app_dial_gosub_virtual_context", "app_dial"); + if (!con) + ast_log(LOG_ERROR, "Dial virtual context 'app_dial_gosub_virtual_context' does not exist and unable to create\n"); + else + ast_add_extension2(con, 1, "s", 1, NULL, NULL, "KeepAlive", ast_strdup(""), ast_free_ptr, "app_dial"); + + 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/trunk/apps/app_dictate.c b/trunk/apps/app_dictate.c new file mode 100644 index 0000000000..58d61116a6 --- /dev/null +++ b/trunk/apps/app_dictate.c @@ -0,0 +1,338 @@ +/* + * 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$") + +#include + +#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */ +#include "asterisk/file.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/say.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; + int ffactor = 320 * 80, + res = 0, + done = 0, + oldr = 0, + lastop = 0, + samples = 0, + speed = 1, + digit = 0, + len = 0, + maxlen = 0, + mode = 0; + + 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"); + 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 = ""; + } + ast_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, AST_FILE_MODE); + 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, 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, AST_FILE_MODE); + 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); + } + 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/trunk/apps/app_directed_pickup.c b/trunk/apps/app_directed_pickup.c new file mode 100644 index 0000000000..07d81c8698 --- /dev/null +++ b/trunk/apps/app_directed_pickup.c @@ -0,0 +1,172 @@ +/* + * 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$") + +#include "asterisk/file.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" + +#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\n" +"pickup any ringing channel that is calling the specified extension. If no\n" +"context is specified, the current context will be used. If you use the special\n" +"string \"PICKUPMARK\" for the context parameter, for example 10@PICKUPMARK,\n" +"this application tries to find a channel which has defined a ${PICKUPMARK}\n" +"channel variable with the same value as \"extension\" (in this example, \"10\").\n" +"When no parameter is specified, the application will pickup a channel matching\n" +"the pickup group of the active channel."; + +/* Perform actual pickup between two channels */ +static int pickup_do(struct ast_channel *chan, struct ast_channel *target) +{ + int res = 0; + + ast_debug(1, "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, const char *exten, const 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, const 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; + char *tmp = ast_strdupa(data); + char *exten = NULL, *context = NULL; + + if (ast_strlen_zero(data)) { + res = ast_pickup_call(chan); + return res; + } + + /* Parse extension (and context if there) */ + while (!ast_strlen_zero(tmp) && (exten = strsep(&tmp, "&"))) { + if ((context = strchr(exten, '@'))) + *context++ = '\0'; + if (!ast_strlen_zero(context) && !strcasecmp(context, PICKUPMARK)) { + if (!pickup_by_mark(chan, exten)) + break; + } else { + if (!pickup_by_exten(chan, exten, !ast_strlen_zero(context) ? context : chan->context)) + break; + } + ast_log(LOG_NOTICE, "No target channel found for %s.\n", exten); + } + + 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/trunk/apps/app_directory.c b/trunk/apps/app_directory.c new file mode 100644 index 0000000000..08d5cf8420 --- /dev/null +++ b/trunk/apps/app_directory.c @@ -0,0 +1,842 @@ +/* + * 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$") + +#include + +#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */ +#include "asterisk/file.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/say.h" +#include "asterisk/app.h" + +#ifdef ODBC_STORAGE +#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" +" m - Instead of reading each name sequentially and asking for confirmation,\n" +" create a menu of up to 8 names.\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 + +enum { + OPT_LISTBYFIRSTNAME = (1 << 0), + OPT_SAYEXTENSION = (1 << 1), + OPT_FROMVOICEMAIL = (1 << 2), + OPT_SELECTFROMMENU = (1 << 3), +} directory_option_flags; + +struct directory_item { + char exten[AST_MAX_EXTENSION + 1]; + char name[AST_MAX_EXTENSION + 1]; + char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */ + + AST_LIST_ENTRY(directory_item) entry; +}; + +AST_APP_OPTIONS(directory_app_options, { + AST_APP_OPTION('f', OPT_LISTBYFIRSTNAME), + AST_APP_OPTION('e', OPT_SAYEXTENSION), + AST_APP_OPTION('v', OPT_FROMVOICEMAIL), + AST_APP_OPTION('m', OPT_SELECTFROMMENU), +}); + +#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, AST_FILE_MODE); + 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 int compare(const char *text, const char *template) +{ + char digit; + + while (*template) { + digit = toupper(*text++); + switch (digit) { + case 0: + return -1; + case '1': + digit = '1'; + break; + case '2': + case 'A': + case 'B': + case 'C': + digit = '2'; + break; + case '3': + case 'D': + case 'E': + case 'F': + digit = '3'; + break; + case '4': + case 'G': + case 'H': + case 'I': + digit = '4'; + break; + case '5': + case 'J': + case 'K': + case 'L': + digit = '5'; + break; + case '6': + case 'M': + case 'N': + case 'O': + digit = '6'; + break; + case '7': + case 'P': + case 'Q': + case 'R': + case 'S': + digit = '7'; + break; + case '8': + case 'T': + case 'U': + case 'V': + digit = '8'; + break; + case '9': + case 'W': + case 'X': + case 'Y': + case 'Z': + digit = '9'; + break; + + default: + if (digit > ' ') + return -1; + continue; + } + + if (*template++ != digit) + return -1; + } + + return 0; +} + +/* 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, const char *context, + const char *ext, const char *name, struct ast_flags *flags) +{ + int res = 0; + 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, AST_DIGIT_ANY); + ast_stopstream(chan); + /* If Option 'e' was specified, also read the extension number with the name */ + if (ast_test_flag(flags, OPT_SAYEXTENSION)) { + ast_stream_and_wait(chan, "vm-extension", 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) && ast_test_flag(flags, OPT_SAYEXTENSION)) { + ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY); + res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language); + } + } +#ifdef ODBC_STORAGE + ast_filedelete(fn, NULL); +#endif + + return res; +} + +static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags) +{ + ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext); + + if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) { + /* We still want to set the exten though */ + ast_copy_string(chan->exten, item->exten, sizeof(chan->exten)); + } else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) { + ast_log(LOG_WARNING, + "Can't find extension '%s' in context '%s'. " + "Did you pass the wrong context to Directory?\n", + item->exten, dialcontext); + return -1; + } + + return 0; +} + +static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags) +{ + struct directory_item *item, **ptr; + int i, res, loop; + + for (ptr = items, i = 0; i < count; i++, ptr++) { + item = *ptr; + + for (loop = 3 ; loop > 0; loop--) { + res = play_mailbox_owner(chan, context, item->exten, item->name, flags); + + if (!res) + res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY); + if (!res) + res = ast_waitfordigit(chan, 3000); + ast_stopstream(chan); + + if (res == '1') { /* Name selected */ + return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1; + } else if (res == '*') { + /* Skip to next match in list */ + break; + } + + if (res < 0) + return -1; + + res = 0; + } + } + + /* Nothing was selected */ + return 0; +} + +static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags) +{ + struct directory_item **block, *item; + int i, limit, res = 0; + char buf[9]; + + for (block = items; count; block += limit, count -= limit) { + limit = count; + if (limit > 8) + limit = 8; + + for (i = 0; i < limit && !res; i++) { + item = block[i]; + + snprintf(buf, sizeof(buf), "digits/%d", i + 1); + /* Press for , [ extension ] */ + res = ast_streamfile(chan, "dir-multi1", chan->language); + if (!res) + res = ast_waitstream(chan, AST_DIGIT_ANY); + if (!res) + res = ast_streamfile(chan, buf, chan->language); + if (!res) + res = ast_waitstream(chan, AST_DIGIT_ANY); + if (!res) + res = ast_streamfile(chan, "dir-multi2", chan->language); + if (!res) + res = ast_waitstream(chan, AST_DIGIT_ANY); + if (!res) + res = play_mailbox_owner(chan, context, item->exten, item->name, flags); + if (!res) + res = ast_waitstream(chan, AST_DIGIT_ANY); + if (!res) + res = ast_waitfordigit(chan, 800); + } + + /* Press "9" for more names. */ + if (!res && count > limit) { + res = ast_streamfile(chan, "dir-multi9", chan->language); + if (!res) + res = ast_waitstream(chan, AST_DIGIT_ANY); + } + + if (!res) { + res = ast_waitfordigit(chan, 3000); + } + + if (res && res > '0' && res < '1' + limit) { + return select_entry(chan, context, dialcontext, block[res - '1'], flags) ? -1 : 1; + } + + if (res < 0) + return -1; + + res = 0; + } + + /* Nothing was selected */ + return 0; +} + +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]; + struct ast_flags config_flags = { 0 }; + + /* Load flat file config. */ + cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags); + + 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, "", 99999); + 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 check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name) +{ + struct directory_item *item; + const char *key = NULL; + int namelen; + + + /* Set key to last name or first name depending on search mode */ + if (!use_first_name) + key = strchr(item_fullname, ' '); + + if (key) + key++; + else + key = item_fullname; + + if (compare(key, pattern_ext)) + return 0; + + /* Match */ + item = ast_calloc(1, sizeof(*item)); + if (!item) + return -1; + ast_copy_string(item->name, item_fullname, sizeof(item->name)); + ast_copy_string(item->exten, item_ext, sizeof(item->exten)); + + ast_copy_string(item->key, key, sizeof(item->key)); + if (key != item_fullname) { + /* Key is the last name. Append first name to key in order to sort Last,First */ + namelen = key - item_fullname - 1; + if (namelen > sizeof(item->key) - strlen(item->key) - 1) + namelen = sizeof(item->key) - strlen(item->key) - 1; + strncat(item->key, item_fullname, namelen); + } + + *result = item; + return 1; +} + +typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist; + +static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, int use_first_name, itemlist *alist) +{ + struct ast_variable *v; + char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat; + struct directory_item *item; + int res; + + ast_debug(2, "Pattern: %s\n", ext); + + for (v = ast_variable_browse(vmcfg, context); v; v = v->next) { + + /* Ignore hidden */ + if (strcasestr(v->value, "hidefromdir=yes")) + continue; + + ast_copy_string(buf, v->value, sizeof(buf)); + bufptr = buf; + + /* password,Full Name,email,pager,options */ + strsep(&bufptr, ","); + pos = strsep(&bufptr, ","); + + res = check_match(&item, pos, v->name, ext, use_first_name); + if (!res) + continue; + else if (res < 0) + return -1; + + AST_LIST_INSERT_TAIL(alist, item, entry); + } + + + if (ucfg) { + for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) { + const char *pos; + if (!strcasecmp(cat, "general")) + continue; + if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory"))) + continue; + + /* Find all candidate extensions */ + pos = ast_variable_retrieve(ucfg, cat, "fullname"); + if (!pos) + continue; + + res = check_match(&item, pos, cat, ext, use_first_name); + if (!res) + continue; + else if (res < 0) + return -1; + + AST_LIST_INSERT_TAIL(alist, item, entry); + } + } + + return 0; +} + +static void sort_items(struct directory_item **sorted, int count) +{ + int reordered, i; + struct directory_item **ptr, *tmp; + + if (count < 2) + return; + + /* Bubble-sort items by the key */ + do { + reordered = 0; + for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) { + if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) { + tmp = ptr[0]; + ptr[0] = ptr[1]; + ptr[1] = tmp; + reordered++; + } + } + } while (reordered); +} + +static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext) +{ + if (!ast_goto_if_exists(chan, dialcontext, ext, 1) || + (!ast_strlen_zero(chan->macrocontext) && + !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) { + return 0; + } else { + ast_log(LOG_WARNING, "Can't find extension '%s' in current context. " + "Not Exiting the Directory!\n", ext); + return -1; + } +} + +static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, struct ast_flags *flags) +{ + /* Read in the first three digits.. "digit" is the first digit, already read */ + int res = 0; + itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE; + struct directory_item *item, **ptr, **sorted = NULL; + int count, i; + char ext[NUMDIGITS + 1] = ""; + + 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' && !goto_exten(chan, dialcontext, "o")) { + return 0; + } + + if (digit == '*' && !goto_exten(chan, dialcontext, "a")) { + return 0; + } + + ext[0] = digit; + if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) + return -1; + + res = search_directory(context, vmcfg, ucfg, ext, ast_test_flag(flags, OPT_LISTBYFIRSTNAME), &alist); + if (res) + goto exit; + + /* Count items in the list */ + count = 0; + AST_LIST_TRAVERSE(&alist, item, entry) { + count++; + } + + if (count < 1) { + res = ast_streamfile(chan, "dir-nomatch", chan->language); + goto exit; + } + + + /* Create plain array of pointers to items (for sorting) */ + sorted = ast_calloc(count, sizeof(*sorted)); + + ptr = sorted; + AST_LIST_TRAVERSE(&alist, item, entry) { + *ptr++ = item; + } + + /* Sort items */ + sort_items(sorted, count); + + if (option_debug) { + ast_debug(2, "Listing matching entries:\n"); + for (ptr = sorted, i = 0; i < count; i++, ptr++) { + ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name); + } + } + + if (ast_test_flag(flags, OPT_SELECTFROMMENU)) { + /* Offer multiple entries at the same time */ + res = select_item_menu(chan, sorted, count, context, dialcontext, flags); + } else { + /* Offer entries one by one */ + res = select_item_seq(chan, sorted, count, context, dialcontext, flags); + } + + if (!res) { + res = ast_streamfile(chan, "dir-nomore", chan->language); + } + +exit: + if (sorted) + ast_free(sorted); + + while ((item = AST_LIST_REMOVE_HEAD(&alist, entry))) + ast_free(item); + + return res; +} + +static int directory_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_config *cfg, *ucfg; + const char *dirintro; + char *parse, *opts[0]; + struct ast_flags flags = { 0 }; + struct ast_flags config_flags = { 0 }; + 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; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options)) + return -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"); + return -1; + } + + ucfg = ast_config_load("users.conf", config_flags); + + 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 = ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) ? "dir-intro-fn" : "dir-intro"; + + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + + for (;;) { + if (!res) + res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY); + ast_stopstream(chan); + if (!res) + res = ast_waitfordigit(chan, 5000); + + if (res <= 0) + break; + + res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, &flags); + if (res) + break; + + res = ast_waitstream(chan, AST_DIGIT_ANY); + ast_stopstream(chan); + + if (res) + break; + } + + if (ucfg) + ast_config_destroy(ucfg); + ast_config_destroy(cfg); + + return res < 0 ? -1 : 0; +} + +static int unload_module(void) +{ + int res; + res = ast_unregister_application(app); + return res; +} + +static int load_module(void) +{ +#ifdef ODBC_STORAGE + struct ast_flags config_flags = { 0 }; + struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags); + 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/trunk/apps/app_disa.c b/trunk/apps/app_disa.c new file mode 100644 index 0000000000..ac74c78a4a --- /dev/null +++ b/trunk/apps/app_disa.c @@ -0,0 +1,367 @@ +/* + * 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$") + +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.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([,[,[,mailbox[,options]]]]) or\n" +"DISA([,,,,options])\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 within on which a call may be placed. If the user\n" +"enters an invalid extension and extension \"i\" exists in the specified\n" +"context, it will be used.\n" +"\n" +"If you need to present a DISA dialtone without entering a password, simply\n" +"set to \"no-password\".\n" +"\n" +"Be aware that using this may compromise 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.\n" +"\n" +"The file that contains the passcodes (if used) allows a complete\n" +"specification of all of the same arguments available on the command\n" +"line, with the sole exception of the options. The file may contain blank\n" +"lines, or comments starting with \"#\" or \";\".\n" +"\n" +" specifies the dialplan context in which the user-entered extension\n" +"will be matched. If no context is specified, the DISA application defaults\n" +"the context to \"disa\". Presumably a normal system will have a special\n" +"context set up for DISA use with some or a lot of restrictions.\n" +"\n" +" specifies a new (different) callerid to be used for this call.\n" +"\n" +" will cause a stutter-dialtone (indication \"dialrecall\")\n" +"to be used, if the specified mailbox contains any new messages.\n" +"\n" +"The following options are available:\n" +" n - the DISA application will not answer initially.\n" +" p - the extension entered will be considered complete when a '#' is entered.\n"; + +enum { + NOANSWER_FLAG = (1 << 0), + POUND_TO_END_FLAG = (1 << 1), +} option_flags; + +AST_APP_OPTIONS(app_opts, { + AST_APP_OPTION('n', NOANSWER_FLAG), + AST_APP_OPTION('p', POUND_TO_END_FLAG), +}); + +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 = 0, j, k = 0, did_ignore = 0, special_noanswer = 0; + int firstdigittimeout = (chan->pbx ? chan->pbx->rtimeout * 1000 : 20000); + int digittimeout = (chan->pbx ? chan->pbx->dtimeout * 1000 : 10000); + struct ast_flags flags; + 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; + 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(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n"); + return -1; + } + + ast_debug(1, "Digittimeout: %d\n", digittimeout); + ast_debug(1, "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 = ""; + if (!ast_strlen_zero(args.options)) + ast_app_parse_options(app_opts, &flags, NULL, args.options); + + ast_debug(1, "Mailbox: %s\n",args.mailbox); + + if (!ast_test_flag(&flags, NOANSWER_FLAG)) { + if (chan->_state != AST_STATE_UP) { + /* answer */ + ast_answer(chan); + } + } else + special_noanswer = 1; + + ast_debug(1, "Context: %s\n",args.context); + + if (!strcasecmp(args.passcode, "no-password")) { + k |= 1; /* We have the password */ + ast_debug(1, "DISA no-password login success\n"); + } + + lastdigittime = ast_tvnow(); + + play_dialtone(chan, args.mailbox); + + ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY); + + for (;;) { + /* if outa time, give em reorder */ + if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) { + ast_debug(1,"DISA %s entry timeout on chan %s\n", + ((k&1) ? "extension" : "password"),chan->name); + break; + } + + if ((res = ast_waitfor(chan, -1) < 0)) { + ast_debug(1, "Waitfor returned %d\n", res); + continue; + } + + if (!(f = ast_read(chan))) { + ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY); + return -1; + } + + if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) { + ast_frfree(f); + ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY); + return -1; + } + + /* If the frame coming in is not DTMF, just drop it and continue */ + if (f->frametype != AST_FRAME_DTMF) { + ast_frfree(f); + continue; + } + + j = f->subclass; /* save digit */ + ast_frfree(f); + + if (!i) { + 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_clear_flag(chan, AST_FLAG_END_DTMF_ONLY); + 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_debug(1, "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_debug(1,"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_debug(1,"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 */ + + /* user wants end of number, remove # */ + if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') { + exten[--i] = 0; + break; + } + + 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; + } + } + } + + ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY); + + 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); + return 0; + } + } + + /* Received invalid, but no "i" extension exists in the given context */ + +reorder: + /* Play congestion for a bit */ + ast_indicate(chan, AST_CONTROL_CONGESTION); + ast_safe_sleep(chan, 10*1000); + + ast_playtones_stop(chan); + + return -1; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_dumpchan.c b/trunk/apps/app_dumpchan.c new file mode 100644 index 0000000000..48fb6215d9 --- /dev/null +++ b/trunk/apps/app_dumpchan.c @@ -0,0 +1,160 @@ +/* + * 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$") + +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/channel.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" + "CallerIDNum= %s\n" + "CallerIDName= %s\n" + "DNIDDigits= %s\n" + "RDNIS= %s\n" + "Language= %s\n" + "State= %s (%d)\n" + "Rings= %d\n" + "NativeFormat= %s\n" + "WriteFormat= %s\n" + "ReadFormat= %s\n" + "RawWriteFormat= %s\n" + "RawReadFormat= %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)"), + c->language, + 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), + ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->rawwriteformat), + ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->rawreadformat), + 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_str *vars = ast_str_alloca(BUFSIZ * 4); /* XXX very large! */ + char info[1024]; + int level = 0; + static char *line = "================================================================================"; + + if (!ast_strlen_zero(data)) + level = atoi(data); + + pbx_builtin_serialize_variables(chan, &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->str, line); + + return 0; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_echo.c b/trunk/apps/app_echo.c new file mode 100644 index 0000000000..89242ffa0d --- /dev/null +++ b/trunk/apps/app_echo.c @@ -0,0 +1,86 @@ +/* + * 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$") + +#include "asterisk/file.h" +#include "asterisk/module.h" +#include "asterisk/channel.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; + + 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: + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_exec.c b/trunk/apps/app_exec.c new file mode 100644 index 0000000000..441e814586 --- /dev/null +++ b/trunk/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$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/app.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 = +" 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 = +" 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 one of:\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 = +" 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; + char *s, *appname, *endargs, args[MAXRESULT]; + struct ast_app *app; + + if (ast_strlen_zero(data)) + return 0; + + s = ast_strdupa(data); + args[0] = 0; + 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; + } + } + + return res; +} + +static int tryexec_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char *s, *appname, *endargs, args[MAXRESULT]; + struct ast_app *app; + + if (ast_strlen_zero(data)) + return 0; + + s = ast_strdupa(data); + args[0] = 0; + 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"); + } + } + + return 0; +} + +static int execif_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char *truedata = NULL, *falsedata = NULL, *end; + struct ast_app *app = NULL; + AST_DECLARE_APP_ARGS(expr, + AST_APP_ARG(expr); + AST_APP_ARG(remainder); + ); + AST_DECLARE_APP_ARGS(apps, + AST_APP_ARG(t); + AST_APP_ARG(f); + ); + char *parse = ast_strdupa(data); + + AST_NONSTANDARD_APP_ARGS(expr, parse, '?'); + if (ast_strlen_zero(expr.remainder)) { + ast_log(LOG_ERROR, "Usage: ExecIf(?()[:( + * + * 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$") + +#include + +#include "asterisk/lock.h" +#include "asterisk/file.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" + +static const char *app = "ExternalIVR"; + +static const char *synopsis = "Interfaces with an external IVR application"; + +static const char *descrip = +" ExternalIVR(command[,arg[,arg...]]): Forks an 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); + if (option_debug) + 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); + ast_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 playlist_entry *entry; + int child_stdin[2] = { 0,0 }; + int child_stdout[2] = { 0,0 }; + int child_stderr[2] = { 0,0 }; + int res = -1; + int gen_active = 0; + int pid; + 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; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(cmd)[32]; + ); + + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, &oldset); + + u->abort_current_sound = 0; + u->chan = chan; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n"); + return -1; + } + + buf = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, buf); + + 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(args.cmd[0], args.cmd); + fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[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; + } + + 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); + ast_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); + ast_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); + + if (option_debug) + 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); + ast_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] == 'E') { + ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]); + send_child_event(child_events, 'E', NULL, chan); + res = 0; + break; + } 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); + res = -1; + 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 (input[0] == 'V') { + char *c; + c = strchr(&input[2], '='); + if (!c) { + send_child_event(child_events, 'Z', NULL, chan); + } else { + *c++ = '\0'; + pbx_builtin_setvar_helper(chan, &input[2], c); + } + } + } else if (ready_fd == child_errors_fd) { + char input[1024]; + + if (exception || 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 (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))) + ast_free(entry); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_festival.c b/trunk/apps/app_festival.c new file mode 100644 index 0000000000..926638e573 --- /dev/null +++ b/trunk/apps/app_festival.c @@ -0,0 +1,523 @@ +/* + * 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 + * + * \extref The Festival Speech Synthesis System - http://www.cstr.ed.ac.uk/projects/festival/ + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include + +#include "asterisk/file.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/app.h" + +#define FESTIVAL_CONFIG "festival.conf" +#define MAXLEN 180 +#define MAXFESTLEN 2048 + +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,\n" +"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 closing the stream or + * using OOB data + */ + static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */ + char *buff, *tmp; + int bufflen; + int n,k,i; + char c; + + bufflen = 1024; + if (!(buff = ast_malloc(bufflen))) + return NULL; + *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 terminating NULL if you want */ + bufflen += bufflen / 4; + if (!(tmp = ast_realloc(buff, bufflen))) { + ast_free(buff); + return NULL; + } + buff = tmp; + } + 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); +#ifdef __PPC__ + for (x = 0; x < length; x += 2) { + c = *(waveform + x + 1); + *(waveform + x + 1) = *(waveform + x); + *(waveform + x) = c; + } +#endif + write(fd, waveform, length); + close(fd); + exit(0); +} + +static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) +{ + int res = 0; + int fds[2]; + int pid = -1; + int needed = 0; + int owriteformat; + struct ast_frame *f; + struct myframe { + struct ast_frame f; + char offset[AST_FRIENDLY_OFFSET]; + char frdata[2048]; + } myf = { + .f = { 0, }, + }; + + if (pipe(fds)) { + ast_log(LOG_WARNING, "Unable to create pipe\n"); + return -1; + } + + /* Answer if it's not already going */ + if (chan->_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 (;;) { + res = ast_waitfor(chan, 1000); + 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_debug(1, "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_debug(1, "Last frame\n"); + res = 0; + ast_frfree(f); + break; + } + } else { + ast_debug(1, "No more waveform\n"); + res = 0; + } + } + ast_frfree(f); + } + } + close(fds[0]); + close(fds[1]); + +#if 0 + if (pid > -1) + kill(pid, SIGKILL); +#endif + if (!res && owriteformat) + ast_set_write_format(chan, owriteformat); + return res; +} + +static int festival_exec(struct ast_channel *chan, void *vdata) +{ + int usecache; + int res = 0; + 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; + struct ast_config *cfg; + char *newfestivalcommand; + struct ast_flags config_flags = { 0 }; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(text); + AST_APP_ARG(interrupt); + ); + + if (ast_strlen_zero(vdata)) { + ast_log(LOG_WARNING, "festival requires an argument (text)\n"); + return -1; + } + + cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); + if (!cfg) { + ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG); + 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); + AST_STANDARD_APP_ARGS(args, data); + + if (args.interrupt && !strcasecmp(args.interrupt, "any")) + args.interrupt = AST_DIGIT_ANY; + + ast_debug(1, "Text passed to festival server : %s\n", args.text); + /* 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); + 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 == NULL) { + ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n"); + ast_config_destroy(cfg); + 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); + return -1; + } + + /* Compute MD5 sum of string */ + MD5Init(&md5ctx); + MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text)); + 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, AST_FILE_MODE); + if (fdesc != -1) { + writecache = 1; + strln = strlen(args.text); + ast_debug(1, "line length : %d\n", strln); + write(fdesc, &strln, sizeof(strln)); + write(fdesc, args.text, strln); + seekpos = lseek(fdesc, 0, SEEK_CUR); + ast_debug(1, "Seek position : %d\n", seekpos); + } + } else { + read(fdesc, &strln, sizeof(strln)); + ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text)); + if (strlen(args.text) == strln) { + ast_debug(1, "Size OK\n"); + read(fdesc, &bigstring, strln); + bigstring[strln] = 0; + if (strcmp(bigstring, args.text) == 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_debug(1, "Reading from cache...\n"); + } else { + ast_debug(1, "Passing text to festival...\n"); + fs = fdopen(dup(fd), "wb"); + fprintf(fs, festivalcommand, args.text); + fflush(fs); + fclose(fs); + } + + /* Write to cache and then pass it down */ + if (writecache == 1) { + ast_debug(1, "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_debug(1, "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); + return -1; + } + n += read_data; + } + ack[3] = '\0'; + if (strcmp(ack, "WV\n") == 0) { /* receive a waveform */ + ast_debug(1, "Festival WV command\n"); + if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { + res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt); + ast_free(waveform); + } + break; + } else if (strcmp(ack, "LP\n") == 0) { /* receive an s-expr */ + ast_debug(1, "Festival LP command\n"); + if ((waveform = socket_receive_file_to_buff(fd, &filesize))) { + waveform[filesize] = '\0'; + ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform); + ast_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); + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + struct ast_flags config_flags = { 0 }; + struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags); + 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/trunk/apps/app_flash.c b/trunk/apps/app_flash.c new file mode 100644 index 0000000000..d57feeb916 --- /dev/null +++ b/trunk/apps/app_flash.c @@ -0,0 +1,112 @@ +/* + * 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 zap trunk + * + * \author Mark Spencer + * + * \ingroup applications + */ + +/*** MODULEINFO + zaptel + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/zapata.h" + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/image.h" + +static char *app = "Flash"; + +static char *synopsis = "Flashes a Zap Trunk"; + +static char *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 = ZT_IOMUX_SIGEVENT; + if (ioctl(fd, ZT_IOMUX, &i) == -1) return -1; + if (ioctl(fd, ZT_GETEVENT, &j) == -1) return -1; + return j; +} + +static int flash_exec(struct ast_channel *chan, void *data) +{ + int res = -1; + int x; + struct zt_params ztp; + + if (strcasecmp(chan->tech->type, "Zap")) { + ast_log(LOG_WARNING, "%s is not a Zap channel\n", chan->name); + return -1; + } + + memset(&ztp, 0, sizeof(ztp)); + res = ioctl(chan->fds[0], ZT_GET_PARAMS, &ztp); + if (!res) { + if (ztp.sigtype & __ZT_SIG_FXS) { + x = ZT_FLASH; + res = ioctl(chan->fds[0], ZT_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); + ast_verb(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)); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + return ast_register_application(app, flash_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Flash channel application"); + diff --git a/trunk/apps/app_followme.c b/trunk/apps/app_followme.c new file mode 100644 index 0000000000..f670d65c07 --- /dev/null +++ b/trunk/apps/app_followme.c @@ -0,0 +1,1059 @@ +/* + * 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 + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include + +#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */ +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.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. */ + 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_RWLIST_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 */ + ast_free(prev); + AST_LIST_HEAD_INIT_NOLOCK(&f->numbers); + + while ((prev = AST_LIST_REMOVE_HEAD(&f->blnumbers, entry))) + /* Free the blacklisted number */ + ast_free(prev); + AST_LIST_HEAD_INIT_NOLOCK(&f->blnumbers); + + while ((prev = AST_LIST_REMOVE_HEAD(&f->wlnumbers, entry))) + /* Free the whitelisted number */ + ast_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, 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)); + cur->order = numorder; + ast_debug(1, "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(int reload) +{ + struct call_followme *f; + struct ast_config *cfg; + char *cat = NULL, *tmp; + struct ast_variable *var; + struct number *cur, *nm; + char numberstr[90]; + int timeout; + char *timeoutstr; + int numorder; + const char *takecallstr; + const char *declinecallstr; + const char *tmpstr; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + if (!(cfg = ast_config_load("followme.conf", config_flags))) { + ast_log(LOG_WARNING, "No follow me config file (followme.conf), so no follow me\n"); + return 0; + } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + AST_RWLIST_WRLOCK(&followmes); + + /* Reset Global Var Values */ + featuredigittimeout = 5000; + + /* Mark all profiles as inactive for the moment */ + AST_RWLIST_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))) { + int new = 0; + + if (!strcasecmp(cat, "general")) + continue; + + /* Look for an existing one */ + AST_LIST_TRAVERSE(&followmes, f, entry) { + if (!strcasecmp(f->name, cat)) + break; + } + + ast_debug(1, "New profile %s.\n", cat); + + if (!f) { + /* Make one then */ + f = alloc_profile(cat); + new = 1; + } + + /* Totally fail if we fail to find/create an entry */ + if (!f) + continue; + + 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")) { + int idx = 0; + + /* Add a new number */ + ast_copy_string(numberstr, var->value, sizeof(numberstr)); + if ((tmp = strchr(numberstr, ','))) { + *tmp++ = '\0'; + timeoutstr = ast_strdupa(tmp); + if ((tmp = strchr(timeoutstr, ','))) { + *tmp++ = '\0'; + 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); + ast_debug(2, "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_RWLIST_INSERT_HEAD(&followmes, f, entry); + } + + ast_config_destroy(cfg); + + AST_RWLIST_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 = 0; + int dg; + struct findme_user *tmpuser; + int to = 0; + int livechannels = 0; + int tmpto; + long totalwait = 0, wtd = 0, 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)) { + ast_verb(3, "couldn't reach at this number.\n"); + return NULL; + } + + if (!caller) { + ast_verb(3, "Original caller hungup. Cleanup.\n"); + clear_calling_tree(findme_user_list); + return NULL; + } + + totalwait = nm->timeout * 1000; + + 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)) { + ast_verb(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) { + ast_verb(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) { + ast_verb(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) { + ast_verb(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) { + ast_verb(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_verb(3, "%s received a hangup frame.\n", winner->name); + if (dg == 0) { + ast_verb(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_verb(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; + ast_verb(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: + ast_verb(3, "%s is busy\n", winner->name); + break; + case AST_CONTROL_CONGESTION: + ast_verb(3, "%s is circuit-busy\n", winner->name); + break; + case AST_CONTROL_RINGING: + ast_verb(3, "%s is ringing\n", winner->name); + break; + case AST_CONTROL_PROGRESS: + ast_verb(3, "%s is making progress passing it to %s\n", winner->name, caller->name); + break; + case AST_CONTROL_VIDUPDATE: + ast_verb(3, "%s requested a video update, passing it to %s\n", winner->name, caller->name); + break; + case AST_CONTROL_PROCEEDING: + ast_verb(3, "%s is proceeding passing it to %s\n", winner->name,caller->name); + break; + case AST_CONTROL_HOLD: + ast_verb(3, "Call on %s placed on hold\n", winner->name); + break; + case AST_CONTROL_UNHOLD: + ast_verb(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: + ast_verb(3, "%s stopped sounds\n", winner->name); + break; + default: + ast_debug(1, "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; + ast_debug(1, "DTMF received: %c\n",(char) f->subclass); + tmpuser->yn[tmpuser->ynidx] = (char) f->subclass; + tmpuser->ynidx++; + ast_debug(1, "DTMF string: %s\n", tmpuser->yn); + if (tmpuser->ynidx >= ynlongest) { + ast_debug(1, "reached longest possible match - doing evals\n"); + if (!strcmp(tmpuser->yn, tpargs->takecall)) { + ast_debug(1, "Match to take the call!\n"); + ast_frfree(f); + return tmpuser->ochan; + } + if (!strcmp(tmpuser->yn, tpargs->nextindp)) { + ast_debug(1, "Next in dial plan step requested.\n"); + *status = 1; + ast_frfree(f); + return NULL; + } + + } + } + + ast_frfree(f); + } else { + if (winner) { + ast_debug(1, "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--; + ast_debug(1, "live channels left %d\n", livechannels); + if (!livechannels) { + ast_verb(3, "no live channels left. exiting.\n"); + return NULL; + } + } + } + } + + } else + ast_debug(1, "timed out waiting for action\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) { + + ast_debug(2, "Number %s timeout %ld\n", nm->number,nm->timeout); + time(&start_time); + + number = ast_strdupa(nm->number); + ast_debug(3, "examining %s\n", number); + do { + rest = strchr(number, '&'); + if (rest) { + *rest = 0; + rest++; + } + + if (!strcmp(tpargs->context, "")) + sprintf(dialarg, "%s", number); + else + sprintf(dialarg, "%s@%s", number, tpargs->context); + + tmpuser = ast_calloc(1, sizeof(*tmpuser)); + if (!tmpuser) { + ast_log(LOG_WARNING, "Out of memory!\n"); + ast_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); + ast_verb(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 { + ast_verb(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); + + + while ((fmuser = AST_LIST_REMOVE_HEAD(findme_user_list, entry))) { + if (!fmuser->cleared && fmuser->ochan != winner) + clear_caller(fmuser); + ast_free(fmuser); + } + + fmuser = NULL; + tmpuser = NULL; + headuser = NULL; + if (winner) + break; + + if (!caller) { + tpargs->status = 1; + ast_free(findme_user_list); + return; + } + + idx++; + AST_LIST_TRAVERSE(&tpargs->cnumbers, nm, entry) + if (nm->order == idx) + break; + + } + ast_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; + char *argstr; + char namerecloc[255]; + 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 (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app); + return -1; + } + + if (!(argstr = ast_strdupa((char *)data))) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, argstr); + + if (ast_strlen_zero(args.followmeid)) { + ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app); + return -1; + } + + AST_RWLIST_RDLOCK(&followmes); + AST_RWLIST_TRAVERSE(&followmes, f, entry) { + if (!strcasecmp(f->name, args.followmeid) && (f->active)) + break; + } + AST_RWLIST_UNLOCK(&followmes); + + ast_debug(1, "New profile %s.\n", args.followmeid); + + if (!f) { + ast_log(LOG_WARNING, "Profile requested, %s, not found in the configuration.\n", args.followmeid); + return 0; + } + + /* 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, ""); + + 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); + + while ((nm = AST_LIST_REMOVE_HEAD(&targs.cnumbers, entry))) + ast_free(nm); + + if (!ast_strlen_zero(namerecloc)) + unlink(namerecloc); + + if (targs.status != 100) { + ast_moh_stop(chan); + if (ast_test_flag(&targs.followmeflags, FOLLOWMEFLAG_UNREACHABLEMSG)) + ast_stream_and_wait(chan, targs.sorryprompt, ""); + 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: + + return res; +} + +static int unload_module(void) +{ + struct call_followme *f; + + ast_unregister_application(app); + + /* Free Memory. Yeah! I'm free! */ + AST_RWLIST_WRLOCK(&followmes); + while ((f = AST_RWLIST_REMOVE_HEAD(&followmes, entry))) { + free_numbers(f); + ast_free(f); + } + + AST_RWLIST_UNLOCK(&followmes); + + return 0; +} + +static int load_module(void) +{ + if(!reload_followme(0)) + return AST_MODULE_LOAD_DECLINE; + + return ast_register_application(app, app_exec, synopsis, descrip); +} + +static int reload(void) +{ + reload_followme(1); + + 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/trunk/apps/app_forkcdr.c b/trunk/apps/app_forkcdr.c new file mode 100644 index 0000000000..c74de67b73 --- /dev/null +++ b/trunk/apps/app_forkcdr.c @@ -0,0 +1,99 @@ +/* + * 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$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/cdr.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\n" +" Options:\n" +" v - If the option is passed all cdr variables will be passed along also.\n"; + + +static void ast_cdr_fork(struct ast_channel *chan) +{ + 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); + ast_cdr_reset(newcdr, &flags); + + if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS)) + ast_cdr_free_vars(cdr, 0); + + 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; + + if (!chan->cdr) { + ast_log(LOG_WARNING, "Channel does not have a CDR\n"); + return 0; + } + + if (!ast_strlen_zero(data)) + ast_set2_flag(chan->cdr, strchr(data, 'v'), AST_CDR_FLAG_KEEP_VARS); + + ast_cdr_fork(chan); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_getcpeid.c b/trunk/apps/app_getcpeid.c new file mode 100644 index 0000000000..1bab819b7e --- /dev/null +++ b/trunk/apps/app_getcpeid.c @@ -0,0 +1,130 @@ +/* + * 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$") + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/adsi.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 zapata.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; + unsigned char cpeid[4]; + int gotgeometry = 0; + int gotcpeid = 0; + int width, height, buttons; + char *data[4]; + unsigned int x; + + 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; + ast_verb(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) { + ast_verb(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); + } + } + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_ices.c b/trunk/apps/app_ices.c new file mode 100644 index 0000000000..b2a4e9b914 --- /dev/null +++ b/trunk/apps/app_ices.c @@ -0,0 +1,205 @@ +/* + * 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 + * + * \extref ICES - http://www.icecast.org/ices.php + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include + +#include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */ +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/frame.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" + +#define ICES "/usr/bin/ices" +#define LOCAL_ICES "/usr/local/bin/ices" + +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 examples/asterisk-ices.conf). \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 */ + execl(ICES, "ices", filename, (char *)NULL); + /* But many places has it in /usr/bin */ + execl(LOCAL_ICES, "ices", filename, (char *)NULL); + /* As a last-ditch effort, try to use PATH */ + execlp("ices", "ices", filename, (char *)NULL); + ast_log(LOG_WARNING, "Execute of ices failed\n"); + _exit(0); +} + +static int ices_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + 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; + } + + last = ast_tv(0, 0); + + if (pipe(fds)) { + ast_log(LOG_WARNING, "Unable to create pipe\n"); + 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"); + 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"); + return -1; + } + if (((char *)data)[0] == '/') + ast_copy_string(filename, (char *) data, sizeof(filename)); + else + snprintf(filename, sizeof(filename), "%s/%s", ast_config_AST_CONFIG_DIR, (char *)data); + /* Placeholder for options */ + c = strchr(filename, '|'); + if (c) + *c = '\0'; + res = icesencode(filename, fds[0]); + close(fds[0]); + if (res >= 0) { + pid = res; + for (;;) { + /* Wait for audio, and stream */ + ms = ast_waitfor(chan, -1); + if (ms < 0) { + ast_debug(1, "Hangup detected\n"); + res = -1; + break; + } + f = ast_read(chan); + if (!f) { + ast_debug(1, "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[1]); + + if (pid > -1) + kill(pid, SIGKILL); + if (!res && oreadformat) + ast_set_read_format(chan, oreadformat); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_image.c b/trunk/apps/app_image.c new file mode 100644 index 0000000000..1e5b0c8575 --- /dev/null +++ b/trunk/apps/app_image.c @@ -0,0 +1,80 @@ +/* + * 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$") + +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/image.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 fails, the channel\n" +"will be hung up. Otherwise, the dialplan continues execution. This\n" +"application sets the following channel variable upon completion:\n" +" SENDIMAGESTATUS The status is the result of the attempt, one of:\n" +" OK | NOSUPPORT \n"; + + +static int sendimage_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SendImage requires an argument (filename)\n"); + return -1; + } + + if (!ast_supports_images(chan)) { + /* Does not support transport */ + pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "NOSUPPORT"); + return 0; + } + + if (!(res = ast_send_image(chan, data))) + pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "OK"); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_ivrdemo.c b/trunk/apps/app_ivrdemo.c new file mode 100644 index 0000000000..6dbbe7191e --- /dev/null +++ b/trunk/apps/app_ivrdemo.c @@ -0,0 +1,115 @@ +/* + * 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$") + +#include "asterisk/file.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; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "skel requires an argument (filename)\n"); + return -1; + } + + /* 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); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_jack.c b/trunk/apps/app_jack.c new file mode 100644 index 0000000000..8faecd1f29 --- /dev/null +++ b/trunk/apps/app_jack.c @@ -0,0 +1,971 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007 - 2008, Russell Bryant + * + * 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 Jack Application + * + * \author Russell Bryant + * + * This is an application to connect an Asterisk channel to an input + * and output jack port so that the audio can be processed through + * another application, or to play audio from another application. + * + * \arg http://www.jackaudio.org/ + * + * \ingroup applications + */ + +/*** MODULEINFO + jack + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include + +#include +#include + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/strings.h" +#include "asterisk/lock.h" +#include "asterisk/libresample.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/audiohook.h" + +#define RESAMPLE_QUALITY 0 + +#define RINGBUFFER_SIZE 16384 + +/*! \brief Common options between the Jack() app and JACK_HOOK() function */ +#define COMMON_OPTIONS \ +" s() - Connect to the specified jack server name.\n" \ +" i() - Connect the output port that gets created to the specified\n" \ +" jack input port.\n" \ +" o() - Connect the input port that gets created to the specified\n" \ +" jack output port.\n" \ +" n - Do not automatically start the JACK server if it is not already\n" \ +" running.\n" + +static char *jack_app = "JACK"; +static char *jack_synopsis = +"JACK (Jack Audio Connection Kit) Application"; +static char *jack_desc = +"JACK([options])\n" +" When this application is executed, two jack ports will be created; one input\n" +"and one output. Other applications can be hooked up to these ports to access\n" +"the audio coming from, or being sent to the channel.\n" +" Valid options:\n" +COMMON_OPTIONS +""; + +struct jack_data { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(server_name); + AST_STRING_FIELD(connect_input_port); + AST_STRING_FIELD(connect_output_port); + ); + jack_client_t *client; + jack_port_t *input_port; + jack_port_t *output_port; + jack_ringbuffer_t *input_rb; + jack_ringbuffer_t *output_rb; + void *output_resampler; + double output_resample_factor; + void *input_resampler; + double input_resample_factor; + unsigned int stop:1; + unsigned int has_audiohook:1; + unsigned int no_start_server:1; + /*! Only used with JACK_HOOK */ + struct ast_audiohook audiohook; +}; + +static const struct { + jack_status_t status; + const char *str; +} jack_status_table[] = { + { JackFailure, "Failure" }, + { JackInvalidOption, "Invalid Option" }, + { JackNameNotUnique, "Name Not Unique" }, + { JackServerStarted, "Server Started" }, + { JackServerFailed, "Server Failed" }, + { JackServerError, "Server Error" }, + { JackNoSuchClient, "No Such Client" }, + { JackLoadFailure, "Load Failure" }, + { JackInitFailure, "Init Failure" }, + { JackShmFailure, "Shared Memory Access Failure" }, + { JackVersionError, "Version Mismatch" }, +}; + +static const char *jack_status_to_str(jack_status_t status) +{ + int i; + + for (i = 0; i < ARRAY_LEN(jack_status_table); i++) { + if (jack_status_table[i].status == status) + return jack_status_table[i].str; + } + + return "Unknown Error"; +} + +static void log_jack_status(const char *prefix, jack_status_t status) +{ + struct ast_str *str = ast_str_alloca(512); + int i, first = 0; + + for (i = 0; i < (sizeof(status) * 8); i++) { + if (!(status & (1 << i))) + continue; + + if (!first) { + ast_str_set(&str, 0, "%s", jack_status_to_str((1 << i))); + first = 1; + } else + ast_str_append(&str, 0, ", %s", jack_status_to_str((1 << i))); + } + + ast_log(LOG_NOTICE, "%s: %s\n", prefix, str->str); +} + +static int alloc_resampler(struct jack_data *jack_data, int input) +{ + double from_srate, to_srate, jack_srate; + void **resampler; + double *resample_factor; + + if (input && jack_data->input_resampler) + return 0; + + if (!input && jack_data->output_resampler) + return 0; + + jack_srate = jack_get_sample_rate(jack_data->client); + + /* XXX Hard coded 8 kHz */ + + to_srate = input ? 8000.0 : jack_srate; + from_srate = input ? jack_srate : 8000.0; + + resample_factor = input ? &jack_data->input_resample_factor : + &jack_data->output_resample_factor; + + if (from_srate == to_srate) { + /* Awesome! The jack sample rate is the same as ours. + * Resampling isn't needed. */ + *resample_factor = 1.0; + return 0; + } + + *resample_factor = to_srate / from_srate; + + resampler = input ? &jack_data->input_resampler : + &jack_data->output_resampler; + + if (!(*resampler = resample_open(RESAMPLE_QUALITY, + *resample_factor, *resample_factor))) { + ast_log(LOG_ERROR, "Failed to open %s resampler\n", + input ? "input" : "output"); + return -1; + } + + return 0; +} + +/*! + * \brief Handle jack input port + * + * Read nframes number of samples from the input buffer, resample it + * if necessary, and write it into the appropriate ringbuffer. + */ +static void handle_input(void *buf, jack_nframes_t nframes, + struct jack_data *jack_data) +{ + short s_buf[nframes]; + float *in_buf = buf; + size_t res; + int i; + size_t write_len = sizeof(s_buf); + + if (jack_data->input_resampler) { + int total_in_buf_used = 0; + int total_out_buf_used = 0; + float f_buf[nframes + 1]; + + memset(f_buf, 0, sizeof(f_buf)); + + while (total_in_buf_used < nframes) { + int in_buf_used; + int out_buf_used; + + out_buf_used = resample_process(jack_data->input_resampler, + jack_data->input_resample_factor, + &in_buf[total_in_buf_used], nframes - total_in_buf_used, + 0, &in_buf_used, + &f_buf[total_out_buf_used], ARRAY_LEN(f_buf) - total_out_buf_used); + + if (out_buf_used < 0) + break; + + total_out_buf_used += out_buf_used; + total_in_buf_used += in_buf_used; + + if (total_out_buf_used == ARRAY_LEN(f_buf)) { + ast_log(LOG_ERROR, "Output buffer filled ... need to increase its size, " + "nframes '%d', total_out_buf_used '%d'\n", nframes, total_out_buf_used); + break; + } + } + + for (i = 0; i < total_out_buf_used; i++) + s_buf[i] = f_buf[i] * (SHRT_MAX / 1.0); + + write_len = total_out_buf_used * sizeof(int16_t); + } else { + /* No resampling needed */ + + for (i = 0; i < nframes; i++) + s_buf[i] = in_buf[i] * (SHRT_MAX / 1.0); + } + + res = jack_ringbuffer_write(jack_data->input_rb, (const char *) s_buf, write_len); + if (res != write_len) { + ast_debug(2, "Tried to write %d bytes to the ringbuffer, but only wrote %d\n", + (int) sizeof(s_buf), (int) res); + } +} + +/*! + * \brief Handle jack output port + * + * Read nframes number of samples from the ringbuffer and write it out to the + * output port buffer. + */ +static void handle_output(void *buf, jack_nframes_t nframes, + struct jack_data *jack_data) +{ + size_t res, len; + + len = nframes * sizeof(float); + + res = jack_ringbuffer_read(jack_data->output_rb, buf, len); + + if (len != res) { + ast_debug(2, "Wanted %d bytes to send to the output port, " + "but only got %d\n", (int) len, (int) res); + } +} + +static int jack_process(jack_nframes_t nframes, void *arg) +{ + struct jack_data *jack_data = arg; + void *input_port_buf, *output_port_buf; + + if (!jack_data->input_resample_factor) + alloc_resampler(jack_data, 1); + + input_port_buf = jack_port_get_buffer(jack_data->input_port, nframes); + handle_input(input_port_buf, nframes, jack_data); + + output_port_buf = jack_port_get_buffer(jack_data->output_port, nframes); + handle_output(output_port_buf, nframes, jack_data); + + return 0; +} + +static void jack_shutdown(void *arg) +{ + struct jack_data *jack_data = arg; + + jack_data->stop = 1; +} + +static struct jack_data *destroy_jack_data(struct jack_data *jack_data) +{ + if (jack_data->input_port) { + jack_port_unregister(jack_data->client, jack_data->input_port); + jack_data->input_port = NULL; + } + + if (jack_data->output_port) { + jack_port_unregister(jack_data->client, jack_data->output_port); + jack_data->output_port = NULL; + } + + if (jack_data->client) { + jack_client_close(jack_data->client); + jack_data->client = NULL; + } + + if (jack_data->input_rb) { + jack_ringbuffer_free(jack_data->input_rb); + jack_data->input_rb = NULL; + } + + if (jack_data->output_rb) { + jack_ringbuffer_free(jack_data->output_rb); + jack_data->output_rb = NULL; + } + + if (jack_data->output_resampler) { + resample_close(jack_data->output_resampler); + jack_data->output_resampler = NULL; + } + + if (jack_data->input_resampler) { + resample_close(jack_data->input_resampler); + jack_data->input_resampler = NULL; + } + + if (jack_data->has_audiohook) + ast_audiohook_destroy(&jack_data->audiohook); + + ast_string_field_free_memory(jack_data); + + ast_free(jack_data); + + return NULL; +} + +static int init_jack_data(struct ast_channel *chan, struct jack_data *jack_data) +{ + const char *chan_name; + jack_status_t status = 0; + jack_options_t jack_options = JackNullOption; + + ast_channel_lock(chan); + chan_name = ast_strdupa(chan->name); + ast_channel_unlock(chan); + + if (!(jack_data->output_rb = jack_ringbuffer_create(RINGBUFFER_SIZE))) + return -1; + + if (!(jack_data->input_rb = jack_ringbuffer_create(RINGBUFFER_SIZE))) + return -1; + + if (jack_data->no_start_server) + jack_options |= JackNoStartServer; + + if (!ast_strlen_zero(jack_data->server_name)) { + jack_options |= JackServerName; + jack_data->client = jack_client_open(chan_name, jack_options, &status, + jack_data->server_name); + } else { + jack_data->client = jack_client_open(chan_name, jack_options, &status); + } + + if (status) + log_jack_status("Client Open Status", status); + + if (!jack_data->client) + return -1; + + jack_data->input_port = jack_port_register(jack_data->client, "input", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsTerminal, 0); + if (!jack_data->input_port) { + ast_log(LOG_ERROR, "Failed to create input port for jack port\n"); + return -1; + } + + jack_data->output_port = jack_port_register(jack_data->client, "output", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); + if (!jack_data->output_port) { + ast_log(LOG_ERROR, "Failed to create output port for jack port\n"); + return -1; + } + + if (jack_set_process_callback(jack_data->client, jack_process, jack_data)) { + ast_log(LOG_ERROR, "Failed to register process callback with jack client\n"); + return -1; + } + + jack_on_shutdown(jack_data->client, jack_shutdown, jack_data); + + if (jack_activate(jack_data->client)) { + ast_log(LOG_ERROR, "Unable to activate jack client\n"); + return -1; + } + + while (!ast_strlen_zero(jack_data->connect_input_port)) { + const char **ports; + int i; + + ports = jack_get_ports(jack_data->client, jack_data->connect_input_port, + NULL, JackPortIsInput); + + if (!ports) { + ast_log(LOG_ERROR, "No input port matching '%s' was found\n", + jack_data->connect_input_port); + break; + } + + for (i = 0; ports[i]; i++) { + ast_debug(1, "Found port '%s' that matched specified input port '%s'\n", + ports[i], jack_data->connect_input_port); + } + + if (jack_connect(jack_data->client, jack_port_name(jack_data->output_port), ports[0])) { + ast_log(LOG_ERROR, "Failed to connect '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->output_port)); + } else { + ast_debug(1, "Connected '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->output_port)); + } + + free((void *) ports); + + break; + } + + while (!ast_strlen_zero(jack_data->connect_output_port)) { + const char **ports; + int i; + + ports = jack_get_ports(jack_data->client, jack_data->connect_output_port, + NULL, JackPortIsOutput); + + if (!ports) { + ast_log(LOG_ERROR, "No output port matching '%s' was found\n", + jack_data->connect_output_port); + break; + } + + for (i = 0; ports[i]; i++) { + ast_debug(1, "Found port '%s' that matched specified output port '%s'\n", + ports[i], jack_data->connect_output_port); + } + + if (jack_connect(jack_data->client, ports[0], jack_port_name(jack_data->input_port))) { + ast_log(LOG_ERROR, "Failed to connect '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->input_port)); + } else { + ast_debug(1, "Connected '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->input_port)); + } + + free((void *) ports); + + break; + } + + return 0; +} + +static int queue_voice_frame(struct jack_data *jack_data, struct ast_frame *f) +{ + float f_buf[f->samples * 8]; + size_t f_buf_used = 0; + int i; + int16_t *s_buf = f->data; + size_t res; + + memset(f_buf, 0, sizeof(f_buf)); + + if (!jack_data->output_resample_factor) + alloc_resampler(jack_data, 0); + + if (jack_data->output_resampler) { + float in_buf[f->samples]; + int total_in_buf_used = 0; + int total_out_buf_used = 0; + + memset(in_buf, 0, sizeof(in_buf)); + + for (i = 0; i < f->samples; i++) + in_buf[i] = s_buf[i] * (1.0 / SHRT_MAX); + + while (total_in_buf_used < ARRAY_LEN(in_buf)) { + int in_buf_used; + int out_buf_used; + + out_buf_used = resample_process(jack_data->output_resampler, + jack_data->output_resample_factor, + &in_buf[total_in_buf_used], ARRAY_LEN(in_buf) - total_in_buf_used, + 0, &in_buf_used, + &f_buf[total_out_buf_used], ARRAY_LEN(f_buf) - total_out_buf_used); + + if (out_buf_used < 0) + break; + + total_out_buf_used += out_buf_used; + total_in_buf_used += in_buf_used; + + if (total_out_buf_used == ARRAY_LEN(f_buf)) { + ast_log(LOG_ERROR, "Output buffer filled ... need to increase its size\n"); + break; + } + } + + f_buf_used = total_out_buf_used; + if (f_buf_used > ARRAY_LEN(f_buf)) + f_buf_used = ARRAY_LEN(f_buf); + } else { + /* No resampling needed */ + + for (i = 0; i < f->samples; i++) + f_buf[i] = s_buf[i] * (1.0 / SHRT_MAX); + + f_buf_used = f->samples; + } + + res = jack_ringbuffer_write(jack_data->output_rb, (const char *) f_buf, f_buf_used * sizeof(float)); + if (res != (f_buf_used * sizeof(float))) { + ast_debug(2, "Tried to write %d bytes to the ringbuffer, but only wrote %d\n", + (int) (f_buf_used * sizeof(float)), (int) res); + } + + return 0; +} + +/*! + * \brief handle jack audio + * + * \param[in] chan The Asterisk channel to write the frames to if no output frame + * is provided. + * \param[in] jack_data This is the jack_data struct that contains the input + * ringbuffer that audio will be read from. + * \param[out] out_frame If this argument is non-NULL, then assuming there is + * enough data avilable in the ringbuffer, the audio in this frame + * will get replaced with audio from the input buffer. If there is + * not enough data available to read at this time, then the frame + * data gets zeroed out. + * + * Read data from the input ringbuffer, which is the properly resampled audio + * that was read from the jack input port. Write it to the channel in 20 ms frames, + * or fill up an output frame instead if one is provided. + * + * \return Nothing. + */ +static void handle_jack_audio(struct ast_channel *chan, struct jack_data *jack_data, + struct ast_frame *out_frame) +{ + short buf[160]; + struct ast_frame f = { + .frametype = AST_FRAME_VOICE, + .subclass = AST_FORMAT_SLINEAR, + .src = "JACK", + .data = buf, + .datalen = sizeof(buf), + .samples = ARRAY_LEN(buf), + }; + + for (;;) { + size_t res, read_len; + char *read_buf; + + read_len = out_frame ? out_frame->datalen : sizeof(buf); + read_buf = out_frame ? out_frame->data : buf; + + res = jack_ringbuffer_read_space(jack_data->input_rb); + + if (res < read_len) { + /* Not enough data ready for another frame, move on ... */ + if (out_frame) { + ast_debug(1, "Sending an empty frame for the JACK_HOOK\n"); + memset(out_frame->data, 0, out_frame->datalen); + } + break; + } + + res = jack_ringbuffer_read(jack_data->input_rb, (char *) read_buf, read_len); + + if (res < read_len) { + ast_log(LOG_ERROR, "Error reading from ringbuffer, even though it said there was enough data\n"); + break; + } + + if (out_frame) { + /* If an output frame was provided, then we just want to fill up the + * buffer in that frame and return. */ + break; + } + + ast_write(chan, &f); + } +} + +enum { + OPT_SERVER_NAME = (1 << 0), + OPT_INPUT_PORT = (1 << 1), + OPT_OUTPUT_PORT = (1 << 2), + OPT_NOSTART_SERVER = (1 << 3), +}; + +enum { + OPT_ARG_SERVER_NAME, + OPT_ARG_INPUT_PORT, + OPT_ARG_OUTPUT_PORT, + /* Must be the last element */ + OPT_ARG_ARRAY_SIZE, +}; + +AST_APP_OPTIONS(jack_exec_options, BEGIN_OPTIONS + AST_APP_OPTION_ARG('s', OPT_SERVER_NAME, OPT_ARG_SERVER_NAME), + AST_APP_OPTION_ARG('i', OPT_INPUT_PORT, OPT_ARG_INPUT_PORT), + AST_APP_OPTION_ARG('o', OPT_OUTPUT_PORT, OPT_ARG_OUTPUT_PORT), + AST_APP_OPTION('n', OPT_NOSTART_SERVER), +END_OPTIONS ); + +static struct jack_data *jack_data_alloc(void) +{ + struct jack_data *jack_data; + + if (!(jack_data = ast_calloc(1, sizeof(*jack_data)))) + return NULL; + + if (ast_string_field_init(jack_data, 32)) { + ast_free(jack_data); + return NULL; + } + + return jack_data; +} + +/*! + * \note This must be done before calling init_jack_data(). + */ +static int handle_options(struct jack_data *jack_data, const char *__options_str) +{ + struct ast_flags options = { 0, }; + char *option_args[OPT_ARG_ARRAY_SIZE]; + char *options_str; + + options_str = ast_strdupa(__options_str); + + ast_app_parse_options(jack_exec_options, &options, option_args, options_str); + + if (ast_test_flag(&options, OPT_SERVER_NAME)) { + if (!ast_strlen_zero(option_args[OPT_ARG_SERVER_NAME])) + ast_string_field_set(jack_data, server_name, option_args[OPT_ARG_SERVER_NAME]); + else { + ast_log(LOG_ERROR, "A server name must be provided with the s() option\n"); + return -1; + } + } + + if (ast_test_flag(&options, OPT_INPUT_PORT)) { + if (!ast_strlen_zero(option_args[OPT_ARG_INPUT_PORT])) + ast_string_field_set(jack_data, connect_input_port, option_args[OPT_ARG_INPUT_PORT]); + else { + ast_log(LOG_ERROR, "A name must be provided with the i() option\n"); + return -1; + } + } + + if (ast_test_flag(&options, OPT_OUTPUT_PORT)) { + if (!ast_strlen_zero(option_args[OPT_ARG_OUTPUT_PORT])) + ast_string_field_set(jack_data, connect_output_port, option_args[OPT_ARG_OUTPUT_PORT]); + else { + ast_log(LOG_ERROR, "A name must be provided with the o() option\n"); + return -1; + } + } + + jack_data->no_start_server = ast_test_flag(&options, OPT_NOSTART_SERVER) ? 1 : 0; + + return 0; +} + +static int jack_exec(struct ast_channel *chan, void *data) +{ + struct jack_data *jack_data; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(options); + ); + + if (!(jack_data = jack_data_alloc())) + return -1; + + args.options = data; + + if (!ast_strlen_zero(args.options) && handle_options(jack_data, args.options)) { + destroy_jack_data(jack_data); + return -1; + } + + if (init_jack_data(chan, jack_data)) { + destroy_jack_data(jack_data); + return -1; + } + + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + destroy_jack_data(jack_data); + return -1; + } + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + destroy_jack_data(jack_data); + return -1; + } + + while (!jack_data->stop) { + struct ast_frame *f; + + ast_waitfor(chan, -1); + + f = ast_read(chan); + if (!f) { + jack_data->stop = 1; + continue; + } + + switch (f->frametype) { + case AST_FRAME_CONTROL: + if (f->subclass == AST_CONTROL_HANGUP) + jack_data->stop = 1; + break; + case AST_FRAME_VOICE: + queue_voice_frame(jack_data, f); + default: + break; + } + + ast_frfree(f); + + handle_jack_audio(chan, jack_data, NULL); + } + + jack_data = destroy_jack_data(jack_data); + + return 0; +} + +static void jack_hook_ds_destroy(void *data) +{ + struct jack_data *jack_data = data; + + destroy_jack_data(jack_data); +} + +static const struct ast_datastore_info jack_hook_ds_info = { + .type = "JACK_HOOK", + .destroy = jack_hook_ds_destroy, +}; + +static int jack_hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, + struct ast_frame *frame, enum ast_audiohook_direction direction) +{ + struct ast_datastore *datastore; + struct jack_data *jack_data; + + if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE) + return 0; + + if (direction != AST_AUDIOHOOK_DIRECTION_READ) + return 0; + + if (frame->frametype != AST_FRAME_VOICE) + return 0; + + if (frame->subclass != AST_FORMAT_SLINEAR) { + ast_log(LOG_WARNING, "Expected frame in SLINEAR for the audiohook, but got format %d\n", + frame->subclass); + return 0; + } + + ast_channel_lock(chan); + + if (!(datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) { + ast_log(LOG_ERROR, "JACK_HOOK datastore not found for '%s'\n", chan->name); + ast_channel_unlock(chan); + return -1; + } + + jack_data = datastore->data; + + queue_voice_frame(jack_data, frame); + + handle_jack_audio(chan, jack_data, frame); + + ast_channel_unlock(chan); + + return 0; +} + +static int enable_jack_hook(struct ast_channel *chan, char *data) +{ + struct ast_datastore *datastore; + struct jack_data *jack_data = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(mode); + AST_APP_ARG(options); + ); + + AST_STANDARD_APP_ARGS(args, data); + + ast_channel_lock(chan); + + if ((datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) { + ast_log(LOG_ERROR, "JACK_HOOK already enabled for '%s'\n", chan->name); + goto return_error; + } + + if (ast_strlen_zero(args.mode) || strcasecmp(args.mode, "manipulate")) { + ast_log(LOG_ERROR, "'%s' is not a supported mode. Only manipulate is supported.\n", + S_OR(args.mode, "")); + goto return_error; + } + + if (!(jack_data = jack_data_alloc())) + goto return_error; + + if (!ast_strlen_zero(args.options) && handle_options(jack_data, args.options)) + goto return_error; + + if (init_jack_data(chan, jack_data)) + goto return_error; + + if (!(datastore = ast_channel_datastore_alloc(&jack_hook_ds_info, NULL))) + goto return_error; + + jack_data->has_audiohook = 1; + ast_audiohook_init(&jack_data->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "JACK_HOOK"); + jack_data->audiohook.manipulate_callback = jack_hook_callback; + + datastore->data = jack_data; + + if (ast_audiohook_attach(chan, &jack_data->audiohook)) + goto return_error; + + if (ast_channel_datastore_add(chan, datastore)) + goto return_error; + + ast_channel_unlock(chan); + + return 0; + +return_error: + ast_channel_unlock(chan); + + if (jack_data) + destroy_jack_data(jack_data); + + return -1; +} + +static int disable_jack_hook(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + struct jack_data *jack_data; + + ast_channel_lock(chan); + + if (!(datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) { + ast_channel_unlock(chan); + ast_log(LOG_WARNING, "No JACK_HOOK found to disable\n"); + return -1; + } + + ast_channel_datastore_remove(chan, datastore); + + jack_data = datastore->data; + ast_audiohook_detach(&jack_data->audiohook); + + /* Keep the channel locked while we destroy the datastore, so that we can + * ensure that all of the jack stuff is stopped just in case another frame + * tries to come through the audiohook callback. */ + ast_channel_datastore_free(datastore); + + ast_channel_unlock(chan); + + return 0; +} + +static int jack_hook_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + int res; + + if (!strcasecmp(value, "on")) + res = enable_jack_hook(chan, data); + else if (!strcasecmp(value, "off")) + res = disable_jack_hook(chan); + else { + ast_log(LOG_ERROR, "'%s' is not a valid value for JACK_HOOK()\n", value); + res = -1; + } + + return res; +} + +static struct ast_custom_function jack_hook_function = { + .name = "JACK_HOOK", + .synopsis = "Enable a jack hook on a channel", + .syntax = "JACK_HOOK(,[options])", + .desc = + " The JACK_HOOK allows turning on or off jack connectivity to this channel.\n" + "When the JACK_HOOK is turned on, jack ports will get created that allow\n" + "access to the audio stream for this channel. The mode specifies which mode\n" + "this hook should run in. A mode must be specified when turning the JACK_HOOK.\n" + "on. However, all arguments are optional when turning it off.\n" + "\n" + " Valid modes are:\n" +#if 0 + /* XXX TODO */ + " spy - Create a read-only audio hook. Only an output jack port will\n" + " get created.\n" + " whisper - Create a write-only audio hook. Only an input jack port will\n" + " get created.\n" +#endif + " manipulate - Create a read/write audio hook. Both an input and an output\n" + " jack port will get created. Audio from the channel will be\n" + " sent out the output port and will be replaced by the audio\n" + " coming in on the input port as it gets passed on.\n" + "\n" + " Valid options are:\n" + COMMON_OPTIONS + "\n" + " Examples:\n" + " To turn on the JACK_HOOK,\n" + " Set(JACK_HOOK(manipulate,i(pure_data_0:input0)o(pure_data_0:output0))=on)\n" + " To turn off the JACK_HOOK,\n" + " Set(JACK_HOOK()=off)\n" + "", + .write = jack_hook_write, +}; + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(jack_app); + res |= ast_custom_function_unregister(&jack_hook_function); + + return res; +} + +static int load_module(void) +{ + if (ast_register_application(jack_app, jack_exec, jack_synopsis, jack_desc)) + return AST_MODULE_LOAD_DECLINE; + + if (ast_custom_function_register(&jack_hook_function)) { + ast_unregister_application(jack_app); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "JACK Interface"); diff --git a/trunk/apps/app_macro.c b/trunk/apps/app_macro.c new file mode 100644 index 0000000000..b087ad36b4 --- /dev/null +++ b/trunk/apps/app_macro.c @@ -0,0 +1,527 @@ +/* + * 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$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.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! (Note: AEL does not use macros)\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. It is advised that\n" +" if you need to deeply nest macro calls, that you use the Gosub application\n" +" (now allows arguments like a Macro) with explict Return() calls instead.\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; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Macro() requires arguments. See \"core show application macro\" for help.\n"); + return -1; + } + + /* 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"); + 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"); + 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); + return 0; + } + + /* If we are to run the macro exclusively, take the mutex */ + if (exclusive) { + ast_debug(1, "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); + 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; + int foundx; + 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_rdlock_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, &foundx,1))) { + /* 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_debug(1, "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: + ast_debug(2, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name); + ast_verb(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: + ast_debug(2, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro); + ast_verb(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_debug(1, "Executed application: %s\n", runningapp); + + if (!strcasecmp(runningapp, "GOSUB")) { + gosub_level++; + ast_debug(1, "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_debug(1, "Incrementing gosub_level\n"); + } + } else { + if (!ast_strlen_zero(app2)) { + gosub_level++; + ast_debug(1, "Incrementing gosub_level\n"); + } + } + } else if (!strcasecmp(runningapp, "RETURN")) { + gosub_level--; + ast_debug(1, "Decrementing gosub_level\n"); + } else if (!strcasecmp(runningapp, "STACKPOP")) { + gosub_level--; + ast_debug(1, "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_debug(1, "Last app: %s\n", tmp3); + + if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) { + gosub_level++; + ast_debug(1, "Incrementing gosub_level\n"); + } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) { + gosub_level--; + ast_debug(1, "Decrementing gosub_level\n"); + } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) { + gosub_level--; + ast_debug(1, "Decrementing gosub_level\n"); + } + } + + if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) { + ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro); + break; + } + + /* don't stop executing extensions when we're in "h" */ + if (ast_check_hangup(chan) && !inhangup) { + ast_debug(1, "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]); + ast_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) + ast_free(save_macro_exten); + if (save_macro_context) + ast_free(save_macro_context); + if (save_macro_priority) + ast_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 (!(ast_check_hangup(chan) & 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) + ast_free(save_macro_offset); + + /* Unlock the macro */ + if (exclusive) { + ast_debug(1, "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; + } + } + + 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; + + if (!(expr = ast_strdupa(data))) + 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"); + + 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); + + 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/trunk/apps/app_meetme.c b/trunk/apps/app_meetme.c new file mode 100644 index 0000000000..3cc5b551e0 --- /dev/null +++ b/trunk/apps/app_meetme.c @@ -0,0 +1,5592 @@ +/* + * 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 + zaptel + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/zapata.h" + +#include "asterisk/lock.h" +#include "asterisk/file.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/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/paths.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 + +/*! String format for scheduled conferences */ +#define DATE_FORMAT "%Y-%m-%d %H:%M:%S" + +enum { + ADMINFLAG_MUTED = (1 << 1), /*!< User is muted */ + ADMINFLAG_SELFMUTED = (1 << 2), /*!< User muted self */ + ADMINFLAG_KICKME = (1 << 3), /*!< User has been kicked */ + /*! User has requested to speak */ + ADMINFLAG_T_REQUEST = (1 << 4), +}; + +#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 key defined in p() option is pressed */ + CONFFLAG_KEYEXIT = (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, 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), + CONFFLAG_SLA_STATION = (1 << 26), + CONFFLAG_SLA_TRUNK = (1 << 27), + /*! If set, the user should continue in the dialplan if kicked out */ + CONFFLAG_KICK_CONTINUE = (1 << 28), + CONFFLAG_DURATION_STOP = (1 << 29), + CONFFLAG_DURATION_LIMIT = (1 << 30), +}; + +enum { + OPT_ARG_WAITMARKED = 0, + OPT_ARG_EXITKEYS = 1, + OPT_ARG_DURATION_STOP = 2, + OPT_ARG_DURATION_LIMIT = 3, + OPT_ARG_MOH_CLASS = 4, + OPT_ARG_ARRAY_SIZE = 5, +}; + +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('C', CONFFLAG_KICK_CONTINUE), + 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_ARG('M', CONFFLAG_MOH, OPT_ARG_MOH_CLASS ), + AST_APP_OPTION('m', CONFFLAG_STARTMUTED ), + AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ), + AST_APP_OPTION_ARG('p', CONFFLAG_KEYEXIT, OPT_ARG_EXITKEYS ), + 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 ), + AST_APP_OPTION_ARG('S', CONFFLAG_DURATION_STOP, OPT_ARG_DURATION_STOP), + AST_APP_OPTION_ARG('L', CONFFLAG_DURATION_LIMIT, OPT_ARG_DURATION_LIMIT), +END_OPTIONS ); + +static const char *app = "MeetMe"; +static const char *app2 = "MeetMeCount"; +static const char *app3 = "MeetMeAdmin"; +static const char *app4 = "MeetMeChannelAdmin"; +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 *synopsis4 = "MeetMe conference Administration (channel specific)"; +static const char *slastation_synopsis = "Shared Line Appearance Station"; +static const char *slatrunk_synopsis = "Shared Line Appearance Trunk"; + +/* Lookup RealTime conferences based on confno and current time */ +static int rt_schedule; +static int fuzzystart; +static int earlyalert; +static int endalert; + +/* Log participant count to the RealTime backend */ +static int rt_log_members; + +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 Zaptel kernel modules and at least one hardware driver (or ztdummy)\n" +" must be present for conferencing to operate properly. In addition, the chan_zap\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-Zap channels in the same conference)\n" +" 'c' -- announce user(s) count on joining a conference\n" +" 'C' -- continue in dialplan when kicked out of 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[()]'\n" +" -- enable music on hold when the conference has a single caller.\n" +" Optionally, specify a musiconhold class to use. If one is not\n" +" provided, it will use the channel's currently set music class,\n" +" or \"default\".\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\n" +" 'p[()]'\n" +" -- allow user to exit the conference by pressing '#' (default)\n" +" or any of the defined keys. If keys contain '*' this will override\n" +" option 's'. The key used is set to channel variable MEETME_EXIT_KEY.\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" +" 'S(x)' -- Kick the user 'x' seconds *after* he entered into the conference.\n" +" 'L(x[:y][:z])' - Limit the conference 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" +" * CONF_LIMIT_TIMEOUT_FILE File to play when time is up.\n" +" * CONF_LIMIT_WARNING_FILE File to play as warning if 'y' is defined.\n" +" The default is to say the time remaining.\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" +"A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\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 *descrip4 = +" MeetMeChannelAdmin(channel,command): Run admin command for a specific\n" +"channel in any coference.\n" +" 'k' -- Kick the specified user out of the conference he is in\n" +" 'm' -- Unmute the specified user\n" +" 'M' -- Mute the specified user\n" +""; + +static const char *slastation_desc = +" SLAStation():\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([,options]):\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" +" The available options are:\n" +" M[()] - Play back the specified MOH class instead of ringing\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 */ + int maxusers; /*!< Participant limit if scheduled */ + int endalert; /*!< When to play conf ending message */ + 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 */ + char uniqueid[32]; + long endtime; /*!< When to end the conf if scheduled */ + 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) */ +}; + +/*! \brief The MeetMe User object */ +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 */ + time_t kicktime; /*!< Time the user will be kicked from the conference */ + struct timeval start_time; /*!< Time the user entered into the conference */ + long timelimit; /*!< Time limit for the user to be in the conference L(x:y:z) */ + long play_warning; /*!< Play a warning when 'y' ms are left */ + long warning_freq; /*!< Repeat the warning every 'z' ms */ + const char *warning_sound; /*!< File to play as warning if 'y' is defined */ + const char *end_sound; /*!< File to play when time is up. */ + 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; + /*! Use count for inside sla_station_exec */ + unsigned int ref_count; +}; + +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; + /*! Use count for inside sla_trunk_exec */ + unsigned int ref_count; +}; + +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, + /*! A reload of configuration has been requested */ + SLA_EVENT_RELOAD, + /*! Poke the SLA thread so it can check if it can perform a reload */ + SLA_EVENT_CHECK_RELOAD, +}; + +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; + /*! A reload has been requested */ + unsigned int reload: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 = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT; + res = ioctl(fd, ZT_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 (!ast_check_hangup(chan)) + 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 + * \param chan The asterisk channel + * + * \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, const struct ast_channel *chan) +{ + struct ast_conference *cnf; + struct zt_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)); + ast_copy_string(cnf->uniqueid, chan->uniqueid, sizeof(cnf->uniqueid)); + + /* Setup a new zap conference */ + ztc.confno = -1; + ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON; + cnf->fd = open("/dev/zap/pseudo", O_RDWR); + if (cnf->fd < 0 || ioctl(cnf->fd, ZT_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Unable to open pseudo device\n"); + if (cnf->fd >= 0) + close(cnf->fd); + ast_free(cnf); + cnf = NULL; + goto cnfout; + } + + cnf->zapconf = ztc.confno; + + /* Setup a new channel for playback of audio files */ + cnf->chan = ast_request("zap", 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 = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON; + if (ioctl(cnf->chan->fds[0], ZT_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + if (cnf->chan) + ast_hangup(cnf->chan); + else + close(cnf->fd); + + ast_free(cnf); + cnf = NULL; + goto cnfout; + } + } + + /* Fill the conference struct */ + cnf->start = time(NULL); + cnf->maxusers = 0x7fffffff; + cnf->isdynamic = dynamic ? 1 : 0; + ast_verb(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 char *complete_meetmecmd(const char *line, const char *word, int pos, int state) +{ + static char *cmds[] = {"concise", "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 ast_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 ? ast_strdup(usrno) : NULL; + } else if (strstr(line, "list") && (state == 0)) + return ast_strdup("concise"); + } + + return NULL; +} + +static char *meetme_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + /* 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 %-6s\n"; + char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s %-6s\n"; + char cmdline[1024] = ""; + + switch (cmd) { + case CLI_INIT: + e->command = "meetme"; + e->usage = + "Usage: meetme (un)lock|(un)mute|kick|list [concise] \n" + " Executes a command for the conference or on a conferee\n"; + return NULL; + case CLI_GENERATE: + return complete_meetmecmd(a->line, a->word, a->pos, a->n); + } + + if (a->argc > 8) + ast_cli(a->fd, "Invalid Arguments.\n"); + /* Check for length so no buffer will overflow... */ + for (i = 0; i < a->argc; i++) { + if (strlen(a->argv[i]) > 100) + ast_cli(a->fd, "Invalid Arguments.\n"); + } + if (a->argc == 1 || (a->argc == 2 && !strcasecmp(a->argv[1], "concise"))) { + /* 'MeetMe': List all the conferences */ + int concise = (a->argc == 2 && !strcasecmp(a->argv[1], "concise")); + now = time(NULL); + AST_LIST_LOCK(&confs); + if (AST_LIST_EMPTY(&confs)) { + if (!concise) + ast_cli(a->fd, "No active MeetMe conferences.\n"); + AST_LIST_UNLOCK(&confs); + return CLI_SUCCESS; + } + if (!concise) + ast_cli(a->fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation", "Locked"); + 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; + if (!concise) + ast_cli(a->fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static", cnf->locked ? "Yes" : "No"); + else { + ast_cli(a->fd, "%s!%d!%d!%02d:%02d:%02d!%d!%d\n", + cnf->confno, + cnf->users, + cnf->markedusers, + hr, min, sec, + cnf->isdynamic, + cnf->locked); + } + + total += cnf->users; + } + AST_LIST_UNLOCK(&confs); + if (!concise) + ast_cli(a->fd, "* Total number of MeetMe users: %d\n", total); + return CLI_SUCCESS; + } + if (a->argc < 3) + return CLI_SHOWUSAGE; + ast_copy_string(cmdline, a->argv[2], sizeof(cmdline)); /* Argv 2: conference number */ + if (strstr(a->argv[1], "lock")) { + if (strcmp(a->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(a->argv[1], "mute")) { + if (a->argc < 4) + return CLI_SHOWUSAGE; + if (strcmp(a->argv[1], "mute") == 0) { + /* Mute */ + if (strcmp(a->argv[3], "all") == 0) { + strncat(cmdline, ",N", sizeof(cmdline) - strlen(cmdline) - 1); + } else { + strncat(cmdline, ",M,", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, a->argv[3], sizeof(cmdline) - strlen(cmdline) - 1); + } + } else { + /* Unmute */ + if (strcmp(a->argv[3], "all") == 0) { + strncat(cmdline, ",n", sizeof(cmdline) - strlen(cmdline) - 1); + } else { + strncat(cmdline, ",m,", sizeof(cmdline) - strlen(cmdline) - 1); + strncat(cmdline, a->argv[3], sizeof(cmdline) - strlen(cmdline) - 1); + } + } + } else if (strcmp(a->argv[1], "kick") == 0) { + if (a->argc < 4) + return CLI_SHOWUSAGE; + if (strcmp(a->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, a->argv[3], sizeof(cmdline) - strlen(cmdline) - 1); + } + } else if (strcmp(a->argv[1], "list") == 0) { + int concise = (a->argc == 4 && (!strcasecmp(a->argv[3], "concise"))); + /* List all the users in a conference */ + if (AST_LIST_EMPTY(&confs)) { + if (!concise) + ast_cli(a->fd, "No active conferences.\n"); + return CLI_SUCCESS; + } + /* Find the right conference */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + if (strcmp(cnf->confno, a->argv[2]) == 0) + break; + } + if (!cnf) { + if (!concise) + ast_cli(a->fd, "No such conference: %s.\n", a->argv[2]); + AST_LIST_UNLOCK(&confs); + return CLI_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(a->fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %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)" : "", + user->adminflags & ADMINFLAG_T_REQUEST ? "(Request to Talk)" : "", + istalking(user->talking), hr, min, sec); + else + ast_cli(a->fd, "%d!%s!%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->adminflags & ADMINFLAG_T_REQUEST ? "1" : "", + user->talking, hr, min, sec); + + } + if (!concise) + ast_cli(a->fd, "%d users in that conference.\n", cnf->users); + AST_LIST_UNLOCK(&confs); + return CLI_SUCCESS; + } else + return CLI_SHOWUSAGE; + + ast_debug(1, "Cmdline: %s\n", cmdline); + + admin_exec(NULL, cmdline); + + return CLI_SUCCESS; +} + +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 char *sla_show_trunks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + const struct sla_trunk *trunk; + + switch (cmd) { + case CLI_INIT: + e->command = "sla show trunks"; + e->usage = + "Usage: sla show trunks\n" + " This will list all trunks defined in sla.conf\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->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(a->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(a->fd, "=== ==> Station name: %s\n", station_ref->station->name); + AST_RWLIST_UNLOCK(&sla_stations); + ast_cli(a->fd, "=== ---------------------------------------------------------\n===\n"); + } + AST_RWLIST_UNLOCK(&sla_trunks); + ast_cli(a->fd, "=============================================================\n\n"); + + return CLI_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 char *sla_show_stations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + const struct sla_station *station; + + switch (cmd) { + case CLI_INIT: + e->command = "sla show stations"; + e->usage = + "Usage: sla show stations\n" + " This will list all stations defined in sla.conf\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->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(a->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(a->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(a->fd, "=== ---------------------------------------------------------\n" + "===\n"); + } + AST_RWLIST_UNLOCK(&sla_stations); + ast_cli(a->fd, "============================================================\n" + "\n"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_meetme[] = { + AST_CLI_DEFINE(meetme_cmd, "Execute a command on a conference or conferee"), + AST_CLI_DEFINE(sla_show_trunks, "Show SLA Trunks"), + AST_CLI_DEFINE(sla_show_stations, "Show SLA Stations"), +}; + +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 = ZT_FLUSH_ALL; + if (ioctl(fd, ZT_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); + manager_event(EVENT_FLAG_CALL, "MeetmeEnd", "Meetme: %s\r\n", conf->confno); + + 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; x < AST_FRAME_BITS; x++) { + if (conf->transframe[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); + ast_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_debug(1, "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 void conf_start_moh(struct ast_channel *chan, const char *musicclass) +{ + char *original_moh; + + ast_channel_lock(chan); + original_moh = ast_strdupa(chan->musicclass); + ast_string_field_set(chan, musicclass, musicclass); + ast_channel_unlock(chan); + + ast_moh_start(chan, original_moh, NULL); + + ast_channel_lock(chan); + ast_string_field_set(chan, musicclass, original_moh); + ast_channel_unlock(chan); +} + +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 zt_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 talkreq_manager = 0; + int using_pseudo = 0; + int duration = 20; + int hr, min, sec; + int sent_event = 0; + int checked = 0; + int announcement_played = 0; + struct timeval 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; + ZT_BUFFERINFO bi; + char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET]; + char *buf = __buf + AST_FRIENDLY_OFFSET; + char *exitkeys = NULL; + 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; + char *parse; + long time_left_ms = 0; + struct timeval nexteventts = { 0, }; + int to; + 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_DURATION_STOP) && !ast_strlen_zero(optargs[OPT_ARG_DURATION_STOP])) { + calldurationlimit = atoi(optargs[OPT_ARG_DURATION_STOP]); + ast_verb(3, "Setting call duration limit to %d seconds.\n", calldurationlimit); + } + + if ((confflags & CONFFLAG_DURATION_LIMIT) && !ast_strlen_zero(optargs[OPT_ARG_DURATION_LIMIT])) { + char *limit_str, *warning_str, *warnfreq_str; + const char *var; + + parse = optargs[OPT_ARG_DURATION_LIMIT]; + limit_str = strsep(&parse, ":"); + warning_str = strsep(&parse, ":"); + warnfreq_str = parse; + + timelimit = atol(limit_str); + if (warning_str) + play_warning = atol(warning_str); + if (warnfreq_str) + warning_freq = atol(warnfreq_str); + + if (!timelimit) { + timelimit = play_warning = warning_freq = 0; + warning_sound = NULL; + } else if (play_warning > timelimit) { + if (!warning_freq) { + play_warning = 0; + } else { + while (play_warning > timelimit) + play_warning -= warning_freq; + if (play_warning < 1) + play_warning = warning_freq = 0; + } + } + + var = pbx_builtin_getvar_helper(chan, "CONF_LIMIT_WARNING_FILE"); + warning_sound = var ? var : "timeleft"; + + var = pbx_builtin_getvar_helper(chan, "CONF_LIMIT_TIMEOUT_FILE"); + end_sound = var ? var : NULL; + + /* undo effect of S(x) in case they are both used */ + calldurationlimit = 0; + /* more efficient do it like S(x) does since no advanced opts */ + if (!play_warning && !end_sound && timelimit) { + calldurationlimit = timelimit / 1000; + timelimit = play_warning = warning_freq = 0; + } else { + ast_debug(2, "Limit Data for this call:\n"); + ast_debug(2, "- timelimit = %ld\n", timelimit); + ast_debug(2, "- play_warning = %ld\n", play_warning); + ast_debug(2, "- warning_freq = %ld\n", warning_freq); + ast_debug(2, "- warning_sound = %s\n", warning_sound ? warning_sound : "UNDEF"); + ast_debug(2, "- end_sound = %s\n", end_sound ? end_sound : "UNDEF"); + } + } + + /* Get exit keys */ + if ((confflags & CONFFLAG_KEYEXIT)) { + if (!ast_strlen_zero(optargs[OPT_ARG_EXITKEYS])) + exitkeys = ast_strdupa(optargs[OPT_ARG_EXITKEYS]); + else + exitkeys = ast_strdupa("#"); /* Default */ + } + + 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) { + ast_copy_string(recordingtmp, "wav", sizeof(recordingtmp)); + conf->recordingformat = ast_strdupa(recordingtmp); + } + ast_verb(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("zap", 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 = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON; + if (ioctl(conf->lchan->fds[0], ZT_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error starting listen channel\n"); + ast_hangup(conf->lchan); + conf->lchan = NULL; + } else { + ast_pthread_create_detached_background(&conf->recordthread, NULL, recordthread, conf); + } + } + ast_mutex_unlock(&conf->recordthreadlock); + + time(&user->jointime); + + user->timelimit = timelimit; + user->play_warning = play_warning; + user->warning_freq = warning_freq; + user->warning_sound = warning_sound; + user->end_sound = end_sound; + + if (calldurationlimit > 0) { + time(&user->kicktime); + user->kicktime = user->kicktime + calldurationlimit; + } + + if (ast_tvzero(user->start_time)) + user->start_time = ast_tvnow(); + time_left_ms = user->timelimit; + + if (user->timelimit) { + nexteventts = ast_tvadd(user->start_time, ast_samp2tv(user->timelimit, 1000)); + nexteventts = ast_tvsub(nexteventts, ast_samp2tv(user->play_warning, 1000)); + } + + if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) { + /* Sorry, but this conference 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; + + if (rt_schedule && conf->maxusers) + if (user->user_no > conf->maxusers) { + /* Sorry, but this confernce has reached the participant limit! */ + if (!ast_streamfile(chan, "conf-full", chan->language)) + ast_waitstream(chan, ""); + goto outrun; + } + + 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))) { + snprintf(user->namerecloc, sizeof(user->namerecloc), + "%s/meetme/meetme-username-%s-%d", ast_config_AST_SPOOL_DIR, + 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++; + if (rt_log_members) { + /* 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_devstate_changed(AST_DEVICE_INUSE, "meetme:%s", conf->confno); + + ast_mutex_unlock(&conf->playlock); + + /* return the unique ID of the conference */ + pbx_builtin_setvar_helper(chan, "MEETMEUNIQUEID", conf->uniqueid); + + 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, "Zap") || (chan->audiohooks || chan->monitor) ? 1 : 0); + user->zapchannel = !retryzap; + + 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; + } + 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 = ZT_POLICY_IMMEDIATE; + bi.rxbufpolicy = ZT_POLICY_IMMEDIATE; + bi.numbufs = audio_buffers; + if (ioctl(fd, ZT_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, ZT_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, ZT_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_debug(1, "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 = 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_MONITOR) + ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER; + else if (confflags & CONFFLAG_TALKER) + ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER; + else + ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER; + + if (ioctl(fd, ZT_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + ast_mutex_unlock(&conf->playlock); + goto outrun; + } + ast_debug(1, "Placed channel %s in ZAP conf %d\n", 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" + "CallerIDnum: %s\r\n" + "CallerIDname: %s\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, "") + ); + 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_MONITOR) && !(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; + now = ast_tvnow(); + + if (rt_schedule) { + if (now.tv_sec % 60 == 0) { + if (!checked) { + if (now.tv_sec > conf->endtime) { + ast_verbose("Quitting time...\n"); + goto outrun; + } + + if (!announcement_played && conf->endalert) { + if (now.tv_sec + conf->endalert > conf->endtime) { + if (!ast_streamfile(chan, "conf-will-end-in", chan->language)) + ast_waitstream(chan, ""); + ast_say_digits(chan, (now.tv_sec + conf->endalert - conf->endtime) / 60, "", chan->language); + if (!ast_streamfile(chan, "minutes", chan->language)) + ast_waitstream(chan, ""); + announcement_played = 1; + } + } + checked = 1; + + } + } else { + checked = 0; + } + } + + if (user->kicktime && (user->kicktime <= now.tv_sec)) + break; + + to = -1; + if (user->timelimit) { + int minutes = 0, seconds = 0, remain = 0; + + to = ast_tvdiff_ms(nexteventts, now); + if (to < 0) + to = 0; + time_left_ms = user->timelimit - ast_tvdiff_ms(now, user->start_time); + if (time_left_ms < to) + to = time_left_ms; + + if (time_left_ms <= 0) { + if (user->end_sound) { + res = ast_streamfile(chan, user->end_sound, chan->language); + res = ast_waitstream(chan, ""); + } + break; + } + + if (!to) { + if (time_left_ms >= 5000) { + + remain = (time_left_ms + 500) / 1000; + if (remain / 60 >= 1) { + minutes = remain / 60; + seconds = remain % 60; + } else { + seconds = remain; + } + + /* force the time left to round up if appropriate */ + if (user->warning_sound && user->play_warning) { + if (!strcmp(user->warning_sound, "timeleft")) { + + res = ast_streamfile(chan, "vm-youhave", chan->language); + res = ast_waitstream(chan, ""); + if (minutes) { + res = ast_say_number(chan, minutes, AST_DIGIT_ANY, chan->language, (char *) NULL); + res = ast_streamfile(chan, "queue-minutes", chan->language); + res = ast_waitstream(chan, ""); + } + if (seconds) { + res = ast_say_number(chan, seconds, AST_DIGIT_ANY, chan->language, (char *) NULL); + res = ast_streamfile(chan, "queue-seconds", chan->language); + res = ast_waitstream(chan, ""); + } + } else { + res = ast_streamfile(chan, user->warning_sound, chan->language); + res = ast_waitstream(chan, ""); + } + } + } + if (user->warning_freq) + nexteventts = ast_tvadd(nexteventts, ast_samp2tv(user->warning_freq, 1000)); + else + nexteventts = ast_tvadd(user->start_time, ast_samp2tv(user->timelimit, 1000)); + } + } + + now = ast_tvnow(); + if (timeout && now.tv_sec >= 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) { + if (confflags & CONFFLAG_KICK_CONTINUE) + ret = 0; + break; + } else { + ztc.confmode = ZT_CONF_CONF; + if (ioctl(fd, ZT_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + goto outrun; + } + } + } + if (!musiconhold && (confflags & CONFFLAG_MOH)) { + conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]); + musiconhold = 1; + } + } else if (currentmarked >= 1 && lastmarked == 0) { + /* Marked user entered, so cancel timeout */ + timeout = 0; + if (confflags & CONFFLAG_MONITOR) + ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER; + else if (confflags & CONFFLAG_TALKER) + ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER; + else + ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER; + if (ioctl(fd, ZT_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) { + conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]); + 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)) { + if (confflags & CONFFLAG_KICK_CONTINUE) + ret = 0; + else + 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 & ZT_CONF_TALKER)) { + ztc.confmode ^= ZT_CONF_TALKER; + if (ioctl(fd, ZT_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 & ZT_CONF_TALKER)) { + ztc.confmode |= ZT_CONF_TALKER; + if (ioctl(fd, ZT_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 ((user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && + (user->adminflags & ADMINFLAG_T_REQUEST) && !(talkreq_manager)) { + talkreq_manager = 1; + + manager_event(EVENT_FLAG_CALL, "MeetmeTalkRequest", + "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 (!(user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) && + !(user->adminflags & ADMINFLAG_T_REQUEST) && (talkreq_manager)) { + talkreq_manager = 0; + manager_event(EVENT_FLAG_CALL, "MeetmeTalkRequest", + "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) { + if (c->fds[0] != origfd || (user->zapchannel && (c->audiohooks || c->monitor))) { + if (using_pseudo) { + /* Kill old pseudo */ + close(fd); + using_pseudo = 0; + } + ast_debug(1, "Ooh, something swapped out under us, starting over\n"); + retryzap = (strcasecmp(c->tech->type, "Zap") || (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_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) { + if (user->talk.actual) + ast_frame_adjust_volume(f, user->talk.actual); + + if (!(confflags & CONFFLAG_MONITOR)) { + 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) + careful_write(fd, f->data, f->datalen, 0); + } + } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) { + char tmp[2]; + + if (confflags & CONFFLAG_PASS_DTMF) + conf_queue_dtmf(conf, user, f); + + tmp[0] = f->subclass; + tmp[1] = '\0'; + if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) { + ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext); + ret = 0; + ast_frfree(f); + break; + } else { + ast_debug(2, "Exit by single digit did not work in meetme. Extension %s does not exist in context %s\n", tmp, exitcontext); + } + } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_KEYEXIT) && (strchr(exitkeys, f->subclass))) { + char exitkey[2]; + + exitkey[0] = f->subclass; + exitkey[1] = '\0'; + + pbx_builtin_setvar_helper(chan, "MEETME_EXIT_KEY", exitkey); + + 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, ZT_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 '2': + menu_active = 0; + if (user->adminflags & (ADMINFLAG_MUTED | ADMINFLAG_SELFMUTED)) + user->adminflags |= ADMINFLAG_T_REQUEST; + + if (user->adminflags & ADMINFLAG_T_REQUEST) + if (!ast_streamfile(chan, "beep", 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) + conf_start_moh(chan, optargs[OPT_ARG_MOH_CLASS]); + + if (ioctl(fd, ZT_SETCONF, &ztc)) { + ast_log(LOG_WARNING, "Error setting conference\n"); + close(fd); + ast_frfree(f); + goto outrun; + } + + conf_flush(fd, chan); + } 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 { + ast_debug(1, + "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)) ) { + int index; + for (index = 0; index < AST_FRAME_BITS; index++) + if (chan->rawwriteformat & (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, ZT_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 = ast_tvnow(); + hr = (now.tv_sec - user->jointime) / 3600; + min = ((now.tv_sec - user->jointime) % 3600) / 60; + sec = (now.tv_sec - 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.tv_sec - user->jointime)); + } + + if (setusercount) { + conf->users--; + if (rt_log_members) { + /* 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_devstate_changed(AST_DEVICE_NOT_INUSE, "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); + } + ast_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, + char *optargs[], int *too_early) +{ + struct ast_variable *var; + struct ast_conference *cnf; + + *too_early = 0; + + /* 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 */ + int maxusers = 0; + struct timeval now; + char currenttime[19] = ""; + char eatime[19] = ""; + char useropts[32] = ""; + char adminopts[32] = ""; + struct ast_tm tm, etm; + struct timeval starttime = { .tv_sec = 0 }, endtime = { .tv_sec = 0 }; + + if (rt_schedule) { + now = ast_tvnow(); + + if (fuzzystart) + now.tv_sec += fuzzystart; + + ast_localtime(&now, &tm, NULL); + ast_strftime(currenttime, sizeof(currenttime), DATE_FORMAT, &tm); + + if (earlyalert) { + now.tv_sec += earlyalert; + ast_localtime(&now, &etm, NULL); + ast_strftime(eatime, sizeof(eatime), DATE_FORMAT, &etm); + } else { + ast_copy_string(eatime, currenttime, sizeof(eatime)); + } + + ast_debug(1, "Looking for conference %s that starts after %s\n", confno, eatime); + + var = ast_load_realtime("meetme", "confno", + confno, "starttime <= ", eatime, "endtime >= ", + currenttime, NULL); + } else + 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); + } else if (!strcasecmp(var->name, "opts")) { + ast_copy_string(useropts, var->value, sizeof(useropts)); + } else if (!strcasecmp(var->name, "maxusers")) { + maxusers = atoi(var->value); + } else if (!strcasecmp(var->name, "adminopts")) { + ast_copy_string(adminopts, var->value, sizeof(adminopts)); + } else if (!strcasecmp(var->name, "endtime")) { + union { + struct ast_tm atm; + struct tm tm; + } t = { { 0, }, }; + strptime(var->value, "%Y-%m-%d %H:%M:%S", &t.tm); + endtime = ast_mktime(&t.atm, NULL); + } else if (!strcasecmp(var->name, "starttime")) { + union { + struct ast_tm atm; + struct tm tm; + } t = { { 0, }, }; + strptime(var->value, "%Y-%m-%d %H:%M:%S", &t.tm); + starttime = ast_mktime(&t.atm, NULL); + } + + var = var->next; + } + ast_variables_destroy(var); + + if (earlyalert) { + now = ast_tvnow(); + + if (now.tv_sec + fuzzystart < starttime.tv_sec) { + /* Announce that the caller is early and exit */ + if (!ast_streamfile(chan, "conf-has-not-started", chan->language)) + ast_waitstream(chan, ""); + *too_early = 1; + return NULL; + } + } + + cnf = build_conf(confno, pin ? pin : "", pinadmin ? pinadmin : "", make, dynamic, refcount, chan); + + if (cnf) { + cnf->maxusers = maxusers; + cnf->endalert = endalert; + cnf->endtime = endtime.tv_sec; + } + } + + if (cnf) { + if (confflags && !cnf->chan && + !ast_test_flag(confflags, CONFFLAG_QUIET) && + ast_test_flag(confflags, CONFFLAG_INTROUSER)) { + ast_log(LOG_WARNING, "No Zap channel available for conference, user introduction disabled (is chan_zap loaded?)\n"); + ast_clear_flag(confflags, CONFFLAG_INTROUSER); + } + + if (confflags && !cnf->chan && + ast_test_flag(confflags, CONFFLAG_RECORDCONF)) { + ast_log(LOG_WARNING, "No Zap channel available for conference, conference recording disabled (is chan_zap loaded?)\n"); + 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_flags config_flags = { 0 }; + 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_debug(1, "The requested confno is '%s'?\n", confno); + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + ast_debug(3, "Does conf %s match %s?\n", confno, cnf->confno); + 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_debug(1, "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, chan); + } else { + cnf = build_conf(confno, "", "", make, dynamic, refcount, chan); + } + } else { + /* Check the config */ + cfg = ast_config_load(CONFIG_FILE_NAME, config_flags); + 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_STANDARD_APP_ARGS(args, parse); + ast_debug(3, "Will conf %s match %s?\n", confno, args.confno); + 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, chan); + break; + } + } + if (!var) { + ast_debug(1, "%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 Zap channel available for conference, user introduction disabled (is chan_zap loaded?)\n"); + ast_clear_flag(confflags, CONFFLAG_INTROUSER); + } + + if (confflags && !cnf->chan && + ast_test_flag(confflags, CONFFLAG_RECORDCONF)) { + ast_log(LOG_WARNING, "No Zap channel available for conference, conference recording disabled (is chan_zap loaded?)\n"); + ast_clear_flag(confflags, CONFFLAG_RECORDCONF); + } + } + + return cnf; +} + +/*! \brief The MeetmeCount application */ +static int count_exec(struct ast_channel *chan, void *data) +{ + 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; + } + + if (!(localdata = ast_strdupa(data))) + 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 */ + } + + return res; +} + +/*! \brief The meetme() application */ +static int conf_exec(struct ast_channel *chan, void *data) +{ + int res = -1; + char confno[MAX_CONFNUM] = ""; + int allowretry = 0; + int retrycnt = 0; + struct ast_conference *cnf = NULL; + struct ast_flags confflags = {0}, config_flags = { 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, }; + + 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, config_flags); + 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) { + if (!ast_test_flag(&confflags, CONFFLAG_QUIET)) { + 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) { + int too_early = 0; + cnf = find_conf_realtime(chan, confno, 1, dynamic, + the_pin, sizeof(the_pin), 1, &confflags, optargs, &too_early); + if (rt_schedule && too_early) + allowretry = 0; + } + + if (!cnf) { + if (allowretry) { + confno[0] = '\0'; + res = ast_streamfile(chan, "conf-invalid", chan->language); + if (!res) + ast_waitstream(chan, ""); + res = -1; + } + } 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); + + 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; + 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; + } + + params = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, params); + + if (!args.command) { + ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n"); + return -1; + } + + AST_LIST_LOCK(&confs); + 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); + 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 | ADMINFLAG_T_REQUEST); + } 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 | ADMINFLAG_T_REQUEST); + 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); + + return 0; +} + +/*--- channel_admin_exec: The MeetMeChannelAdmin application */ +/* MeetMeChannelAdmin(channel, command) */ +static int channel_admin_exec(struct ast_channel *chan, void *data) { + char *params; + struct ast_conference *conf = NULL; + struct ast_conf_user *user = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(channel); + AST_APP_ARG(command); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "MeetMeChannelAdmin requires two arguments!\n"); + return -1; + } + + params = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, params); + + if (!args.channel) { + ast_log(LOG_WARNING, "MeetMeChannelAdmin requires a channel name!\n"); + return -1; + } + + if (!args.command) { + ast_log(LOG_WARNING, "MeetMeChannelAdmin requires a command!\n"); + return -1; + } + + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, conf, list) { + AST_LIST_TRAVERSE(&conf->userlist, user, list) { + if (!strcmp(user->chan->name, args.channel)) + break; + } + } + + if (!user) { + ast_log(LOG_NOTICE, "Specified user (%s) not found\n", args.channel); + AST_LIST_UNLOCK(&confs); + return 0; + } + + /* perform the specified action */ + switch (*args.command) { + case 77: /* M: Mute */ + user->adminflags |= ADMINFLAG_MUTED; + break; + case 109: /* m: Unmute */ + user->adminflags &= ~ADMINFLAG_MUTED; + break; + case 107: /* k: Kick user */ + user->adminflags |= ADMINFLAG_KICKME; + break; + default: /* unknown command */ + ast_log(LOG_WARNING, "Unknown MeetMeChannelAdmin command '%s'\n", args.command); + break; + } + + AST_LIST_UNLOCK(&confs); + + 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 | ADMINFLAG_T_REQUEST); /* 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 char mandescr_meetmelist[] = +"Description: Lists all users in a particular MeetMe conference.\n" +"MeetmeList will follow as separate events, followed by a final event called\n" +"MeetmeListComplete.\n" +"Variables:\n" +" *ActionId: \n" +" *Conference: \n"; + +static int action_meetmelist(struct mansession *s, const struct message *m) +{ + const char *actionid = astman_get_header(m, "ActionID"); + const char *conference = astman_get_header(m, "Conference"); + char idText[80] = ""; + struct ast_conference *cnf; + struct ast_conf_user *user; + int total = 0; + + if (!ast_strlen_zero(actionid)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid); + + if (AST_LIST_EMPTY(&confs)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + + astman_send_listack(s, m, "Meetme user list will follow", "start"); + + /* Find the right conference */ + AST_LIST_LOCK(&confs); + AST_LIST_TRAVERSE(&confs, cnf, list) { + /* If we ask for one particular, and this isn't it, skip it */ + if (!ast_strlen_zero(conference) && strcmp(cnf->confno, conference)) + continue; + + /* Show all the users */ + AST_LIST_TRAVERSE(&cnf->userlist, user, list) { + total++; + astman_append(s, + "Event: MeetmeList\r\n" + "%s" + "Conference: %s\r\n" + "UserNumber: %d\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "Channel: %s\r\n" + "Admin: %s\r\n" + "Role: %s\r\n" + "MarkedUser: %s\r\n" + "Muted: %s\r\n" + "Talking: %s\r\n" + "\r\n", + idText, + cnf->confno, + user->user_no, + S_OR(user->chan->cid.cid_num, ""), + S_OR(user->chan->cid.cid_name, ""), + user->chan->name, + user->userflags & CONFFLAG_ADMIN ? "Yes" : "No", + user->userflags & CONFFLAG_MONITOR ? "Listen only" : user->userflags & CONFFLAG_TALKER ? "Talk only" : "Talk and listen", + user->userflags & CONFFLAG_MARKEDUSER ? "Yes" : "No", + user->adminflags & ADMINFLAG_MUTED ? "By admin" : user->adminflags & ADMINFLAG_SELFMUTED ? "By self" : "No", + user->talking > 0 ? "Yes" : user->talking == 0 ? "No" : "Not monitored"); + } + } + AST_LIST_UNLOCK(&confs); + /* Send final confirmation */ + astman_append(s, + "Event: MeetmeListComplete\r\n" + "EventList: Complete\r\n" + "ListItems: %d\r\n" + "%s" + "\r\n", total, idText); + return 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, AST_FILE_MODE); + 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; x < AST_FRAME_BITS; x++) { + /* Free any translations that have occured */ + if (cnf->transframe[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 enum ast_device_state 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; + struct ast_flags config_flags = { 0 }; + const char *val; + + if (!(cfg = ast_config_load(CONFIG_FILE_NAME, config_flags))) + return; + + audio_buffers = DEFAULT_AUDIO_BUFFERS; + + /* Scheduling support is off by default */ + rt_schedule = 0; + fuzzystart = 0; + earlyalert = 0; + endalert = 0; + + /* Logging of participants defaults to ON for compatibility reasons */ + rt_log_members = 1; + + 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 < ZT_DEFAULT_NUM_BUFS) || (audio_buffers > ZT_MAX_NUM_BUFS)) { + ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n", + ZT_DEFAULT_NUM_BUFS, ZT_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); + } + + if ((val = ast_variable_retrieve(cfg, "general", "schedule"))) + rt_schedule = ast_true(val); + if ((val = ast_variable_retrieve(cfg, "general", "logmembercount"))) + rt_log_members = ast_true(val); + if ((val = ast_variable_retrieve(cfg, "general", "fuzzystart"))) { + if ((sscanf(val, "%d", &fuzzystart) != 1)) { + ast_log(LOG_WARNING, "fuzzystart must be a number, not '%s'\n", val); + fuzzystart = 0; + } + } + if ((val = ast_variable_retrieve(cfg, "general", "earlyalert"))) { + if ((sscanf(val, "%d", &earlyalert) != 1)) { + ast_log(LOG_WARNING, "earlyalert must be a number, not '%s'\n", val); + earlyalert = 0; + } + } + if ((val = ast_variable_retrieve(cfg, "general", "endalert"))) { + if ((sscanf(val, "%d", &endalert) != 1)) { + ast_log(LOG_WARNING, "endalert must be a number, not '%s'\n", val); + endalert = 0; + } + } + + 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 enum ast_device_state sla_state_to_devstate(enum sla_trunk_state state) +{ + switch (state) { + case SLA_TRUNK_STATE_IDLE: + return AST_DEVICE_NOT_INUSE; + case SLA_TRUNK_STATE_RINGING: + return AST_DEVICE_RINGING; + case SLA_TRUNK_STATE_UP: + return AST_DEVICE_INUSE; + case SLA_TRUNK_STATE_ONHOLD: + case SLA_TRUNK_STATE_ONHOLD_BYME: + return AST_DEVICE_ONHOLD; + } + + return AST_DEVICE_UNKNOWN; +} + +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_devstate_changed(sla_state_to_devstate(state), + "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, trunk_ref->chan); + 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))) + ast_free(station_ref); + + ast_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: + ast_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(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_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(entry); + sla_stop_ringing_station(ringing_station, SLA_STATION_HANGUP_NORMAL); + break; + case AST_DIAL_RESULT_ANSWERED: + AST_LIST_REMOVE_CURRENT(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_debug(1, "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; + ast_free(ringing_trunk); + ast_free(ringing_station); + ast_mutex_init(&cond_lock); + ast_cond_init(&cond, NULL); + ast_mutex_lock(&cond_lock); + ast_pthread_create_detached_background(&dont_care, NULL, run_station, &args); + ast_cond_wait(&cond, &cond_lock); + ast_mutex_unlock(&cond_lock); + ast_mutex_destroy(&cond_lock); + ast_cond_destroy(&cond); + 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(entry); + ast_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); + ast_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); + ast_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 ringing_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(entry); + ast_dial_join(ringing_station->station->dial); + ast_dial_destroy(ringing_station->station->dial); + ringing_station->station->dial = NULL; + ast_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_devstate_changed(AST_DEVICE_ONHOLD, "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(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(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 int sla_load_config(int reload); + +/*! \brief Check if we can do a reload of SLA, and do it if we can */ +static void sla_check_reload(void) +{ + struct sla_station *station; + struct sla_trunk *trunk; + + ast_mutex_lock(&sla.lock); + + if (!AST_LIST_EMPTY(&sla.event_q) || !AST_LIST_EMPTY(&sla.ringing_trunks) + || !AST_LIST_EMPTY(&sla.ringing_stations)) { + ast_mutex_unlock(&sla.lock); + return; + } + + AST_RWLIST_RDLOCK(&sla_stations); + AST_RWLIST_TRAVERSE(&sla_stations, station, entry) { + if (station->ref_count) + break; + } + AST_RWLIST_UNLOCK(&sla_stations); + if (station) { + ast_mutex_unlock(&sla.lock); + return; + } + + AST_RWLIST_RDLOCK(&sla_trunks); + AST_RWLIST_TRAVERSE(&sla_trunks, trunk, entry) { + if (trunk->ref_count) + break; + } + AST_RWLIST_UNLOCK(&sla_trunks); + if (trunk) { + ast_mutex_unlock(&sla.lock); + return; + } + + /* yay */ + sla_load_config(1); + sla.reload = 0; + + ast_mutex_unlock(&sla.lock); +} + +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; + case SLA_EVENT_RELOAD: + sla.reload = 1; + case SLA_EVENT_CHECK_RELOAD: + break; + } + ast_free(event); + ast_mutex_lock(&sla.lock); + } + + if (sla.reload) + sla_check_reload(); + } + + ast_mutex_unlock(&sla.lock); + + while ((ringing_station = AST_LIST_REMOVE_HEAD(&sla.ringing_stations, entry))) + ast_free(ringing_station); + + while ((failed_station = AST_LIST_REMOVE_HEAD(&sla.failed_stations, entry))) + ast_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); + ast_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); + ast_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, trunk_ref->trunk->chan); + + 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); + if (station) + ast_atomic_fetchadd_int((int *) &station->ref_count, 1); + 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"); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + 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"); + ast_atomic_fetchadd_int((int *) &station->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + 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_devstate_changed(AST_DEVICE_INUSE, + "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(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; + 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); + ast_mutex_lock(&cond_lock); + ast_pthread_create_detached_background(&dont_care, NULL, dial_trunk, &args); + ast_cond_wait(&cond, &cond_lock); + ast_mutex_unlock(&cond_lock); + ast_mutex_destroy(&cond_lock); + ast_cond_destroy(&cond); + ast_autoservice_stop(chan); + if (!trunk_ref->trunk->chan) { + ast_debug(1, "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; + ast_atomic_fetchadd_int((int *) &station->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + 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, chan); + 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"); + + ast_atomic_fetchadd_int((int *) &station->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + + 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; +} + +enum { + SLA_TRUNK_OPT_MOH = (1 << 0), +}; + +enum { + SLA_TRUNK_OPT_ARG_MOH_CLASS = 0, + SLA_TRUNK_OPT_ARG_ARRAY_SIZE = 1, +}; + +AST_APP_OPTIONS(sla_trunk_opts, BEGIN_OPTIONS + AST_APP_OPTION_ARG('M', SLA_TRUNK_OPT_MOH, SLA_TRUNK_OPT_ARG_MOH_CLASS), +END_OPTIONS ); + +static int sla_trunk_exec(struct ast_channel *chan, void *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_DECLARE_APP_ARGS(args, + AST_APP_ARG(trunk_name); + AST_APP_ARG(options); + ); + char *opts[SLA_TRUNK_OPT_ARG_ARRAY_SIZE] = { NULL, }; + char *conf_opt_args[OPT_ARG_ARRAY_SIZE] = { NULL, }; + struct ast_flags opt_flags = { 0 }; + char *parse; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "The SLATrunk application requires an argument, the trunk name\n"); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + if (args.argc == 2) { + if (ast_app_parse_options(sla_trunk_opts, &opt_flags, opts, args.options)) { + ast_log(LOG_ERROR, "Error parsing options for SLATrunk\n"); + return -1; + } + } + + AST_RWLIST_RDLOCK(&sla_trunks); + trunk = sla_find_trunk(args.trunk_name); + if (trunk) + ast_atomic_fetchadd_int((int *) &trunk->ref_count, 1); + AST_RWLIST_UNLOCK(&sla_trunks); + + if (!trunk) { + ast_log(LOG_ERROR, "SLA Trunk '%s' not found!\n", args.trunk_name); + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + return 0; + } + + if (trunk->chan) { + ast_log(LOG_ERROR, "Call came in on %s, but the trunk is already in use!\n", + args.trunk_name); + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + return 0; + } + + trunk->chan = chan; + + if (!(ringing_trunk = queue_ringing_trunk(trunk))) { + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + return 0; + } + + snprintf(conf_name, sizeof(conf_name), "SLA_%s", args.trunk_name); + conf = build_conf(conf_name, "", "", 1, 1, 1, chan); + if (!conf) { + pbx_builtin_setvar_helper(chan, "SLATRUNK_STATUS", "FAILURE"); + ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + return 0; + } + ast_set_flag(&conf_flags, + CONFFLAG_QUIET | CONFFLAG_MARKEDEXIT | CONFFLAG_MARKEDUSER | CONFFLAG_PASS_DTMF); + + if (ast_test_flag(&opt_flags, SLA_TRUNK_OPT_MOH)) { + ast_indicate(chan, -1); + ast_set_flag(&conf_flags, CONFFLAG_MOH); + conf_opt_args[OPT_ARG_MOH_CLASS] = opts[SLA_TRUNK_OPT_ARG_MOH_CLASS]; + } else + ast_indicate(chan, AST_CONTROL_RINGING); + + conf_run(chan, conf, conf_flags.flags, opts); + dispose_conf(conf); + conf = NULL; + trunk->chan = NULL; + trunk->on_hold = 0; + + 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(entry); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + ast_mutex_unlock(&sla.lock); + if (ringing_trunk) { + sla_change_trunk_state(ringing_trunk->trunk, SLA_TRUNK_STATE_IDLE, ALL_TRUNK_REFS, NULL); + ast_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); + } + + ast_atomic_fetchadd_int((int *) &trunk->ref_count, -1); + sla_queue_event(SLA_EVENT_CHECK_RELOAD); + + return 0; +} + +static enum ast_device_state sla_state(const char *data) +{ + char *buf, *station_name, *trunk_name; + struct sla_station *station; + struct sla_trunk_ref *trunk_ref; + enum ast_device_state 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; + } + res = sla_state_to_devstate(trunk_ref->state); + 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))) + ast_free(station_ref); + + ast_string_field_free_memory(trunk); + ast_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))) + ast_free(trunk_ref); + + ast_string_field_free_memory(station); + ast_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)) { + ast_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_ptr, 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))) { + ast_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)) { + ast_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_ptr, 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_ptr, 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(int reload) +{ + struct ast_config *cfg; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + const char *cat = NULL; + int res = 0; + const char *val; + + if (!reload) { + ast_mutex_init(&sla.lock); + ast_cond_init(&sla.cond, NULL); + } + + if (!(cfg = ast_config_load(SLA_CONFIG_FILE, config_flags))) + return 0; /* Treat no config as normal */ + else if (cfg == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + 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 (!reload) + ast_pthread_create(&sla.thread, NULL, sla_thread, NULL); + + return res; +} + +static int load_config(int reload) +{ + load_config_meetme(); + + if (reload) { + sla_queue_event(SLA_EVENT_RELOAD); + ast_log(LOG_NOTICE, "A reload of the SLA configuration has been requested " + "and will be completed when the system is idle.\n"); + return 0; + } + + return sla_load_config(0); +} + +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_manager_unregister("MeetmeList"); + res |= ast_unregister_application(app4); + 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"); + + 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_manager_register2("MeetmeList", EVENT_FLAG_REPORTING, + action_meetmelist, "List participants in a conference", mandescr_meetmelist); + res |= ast_register_application(app4, channel_admin_exec, synopsis4, descrip4); + 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/trunk/apps/app_milliwatt.c b/trunk/apps/app_milliwatt.c new file mode 100644 index 0000000000..754faa555a --- /dev/null +++ b/trunk/apps/app_milliwatt.c @@ -0,0 +1,134 @@ +/* + * 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 + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/channel.h" + +static char *app = "Milliwatt"; + +static char *synopsis = "Generate a Constant 1000Hz tone at 0dbm (mu-law)"; + +static char *descrip = +"Milliwatt(): Generate a Constant 1000Hz tone at 0dbm (mu-law)\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) +{ + ast_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 milliwatt_exec(struct ast_channel *chan, void *data) +{ + + 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 unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_minivm.c b/trunk/apps/app_minivm.c new file mode 100644 index 0000000000..77497fb191 --- /dev/null +++ b/trunk/apps/app_minivm.c @@ -0,0 +1,3109 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * and Edvina AB, Sollentuna, Sweden + * + * Mark Spencer (Comedian Mail) + * and Olle E. Johansson, Edvina.net (Mini-Voicemail changes) + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 MiniVoiceMail - A Minimal Voicemail System for Asterisk + * + * A voicemail system in small building blocks, working together + * based on the Comedian Mail voicemail system (app_voicemail.c). + * + * \par See also + * \arg \ref Config_minivm + * \arg \ref Config_minivm_examples + * \arg \ref App_minivm + * + * \ingroup applications + * + * \page App_minivm Asterisk Mini-voicemail - A minimal voicemail system + * + * This is a minimal voicemail system, building blocks for something + * else. It is built for multi-language systems. + * The current version is focused on accounts where voicemail is + * forwarded to users in e-mail. It's work in progress, with loosed ends hanging + * around from the old voicemail system and it's configuration. + * + * Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain() + * in the future. + * + * Dialplan applications + * - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() ) + * - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() ) + * - minivmNotify - Notify user of message ( \ref minivm_notify_exec() ) + * - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() ) + * - minivmAccMess - Record personal messages (busy | unavailable | temporary) + * + * Dialplan functions + * - MINIVMACCOUNT() - A dialplan function + * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains + * + * CLI Commands + * - minivm list accounts + * - minivm list zones + * - minivm list templates + * - minivm show stats + * - minivm show settings + * + * Some notes + * - General configuration in minivm.conf + * - Users in realtime or configuration file + * - Or configured on the command line with just the e-mail address + * + * Voicemail accounts are identified by userid and domain + * + * Language codes are like setlocale - langcode_countrycode + * \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use + * language_country like setlocale(). + * + * Examples: + * - Swedish, Sweden sv_se + * - Swedish, Finland sv_fi + * - English, USA en_us + * - English, GB en_gb + * + * \par See also + * \arg \ref Config_minivm + * \arg \ref Config_minivm_examples + * \arg \ref Minivm_directories + * \arg \ref app_minivm.c + * \arg Comedian mail: app_voicemail.c + * \arg \ref descrip_minivm_accmess + * \arg \ref descrip_minivm_greet + * \arg \ref descrip_minivm_record + * \arg \ref descrip_minivm_delete + * \arg \ref descrip_minivm_notify + * + * \arg \ref App_minivm_todo + */ +/*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure + * + * The directory structure for storing voicemail + * - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf) + * - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail + * - Domain MVM_SPOOL_DIR/domain + * - Username MVM_SPOOL_DIR/domain/username + * - /greet : Recording of account owner's name + * - /busy : Busy message + * - /unavailable : Unavailable message + * - /temp : Temporary message + * + * For account anita@localdomain.xx the account directory would as a default be + * \b /var/spool/asterisk/voicemail/localdomain.xx/anita + * + * To avoid transcoding, these sound files should be converted into several formats + * They are recorded in the format closest to the incoming streams + * + * + * Back: \ref App_minivm + */ + +/*! \page Config_minivm_examples Example dialplan for Mini-Voicemail + * \section Example dialplan scripts for Mini-Voicemail + * \verbinclude extensions_minivm.conf.sample + * + * Back: \ref App_minivm + */ + +/*! \page App_minivm_todo Asterisk Mini-Voicemail - todo + * - configure accounts from AMI? + * - test, test, test, test + * - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats + * "The extension you are calling" + * - For trunk, consider using channel storage for information passing between small applications + * - Set default directory for voicemail + * - New app for creating directory for account if it does not exist + * - Re-insert code for IMAP storage at some point + * - Jabber integration for notifications + * - Figure out how to handle video in voicemail + * - Integration with the HTTP server + * - New app for moving messages between mailboxes, and optionally mark it as "new" + * + * For Asterisk 1.4/trunk + * - Use string fields for minivm_account + * + * Back: \ref App_minivm + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include +#include +#include +#include +#include +#include + + +#include "asterisk/paths.h" /* use various paths */ +#include "asterisk/astobj.h" +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/say.h" +#include "asterisk/module.h" +#include "asterisk/app.h" +#include "asterisk/manager.h" +#include "asterisk/dsp.h" +#include "asterisk/localtime.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/linkedlists.h" +#include "asterisk/callerid.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + + +#define MVM_REVIEW (1 << 0) /*!< Review message */ +#define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */ +#define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */ +#define MVM_SVMAIL (1 << 3) +#define MVM_ENVELOPE (1 << 4) +#define MVM_PBXSKIP (1 << 9) +#define MVM_ALLOCED (1 << 13) + +/*! \brief Default mail command to mail voicemail. Change it with the + mailcmd= command in voicemail.conf */ +#define SENDMAIL "/usr/sbin/sendmail -t" + +#define SOUND_INTRO "vm-intro" +#define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */ +#define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */ +#define EOL "\r\n" + +#define MAX_DATETIME_FORMAT 512 +#define MAX_NUM_CID_CONTEXTS 10 + +#define ERROR_LOCK_PATH -100 +#define VOICEMAIL_DIR_MODE 0700 + +#define VOICEMAIL_CONFIG "minivm.conf" +#define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */ + +/*! \brief Message types for notification */ +enum mvm_messagetype { + MVM_MESSAGE_EMAIL, + MVM_MESSAGE_PAGE + /* For trunk: MVM_MESSAGE_JABBER, */ +}; + +static char MVM_SPOOL_DIR[PATH_MAX]; + +/* Module declarations */ +static char *app_minivm_record = "MinivmRecord"; /* Leave a message */ +static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */ +static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */ +static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */ +static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */ + +static char *synopsis_minivm_record = "Receive Mini-Voicemail and forward via e-mail"; +static char *descrip_minivm_record = + " MinivmRecord(username@domain[,options]):\n" + "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n" + "MiniVM records audio file in configured format and forwards message to e-mail and pager.\n" + "If there's no user account for that address, a temporary account will\n" + "be used with default options.\n" + "The recorded file name and path will be stored in MINIVM_FILENAME and the \n" + "duration of the message will be stored in MINIVM_DURATION\n" + "\nNote: If the caller hangs up after the recording, the only way to send\n" + "the message and clean up is to execute in the \"h\" extension.\n" + "\nThe application will exit if any of the following DTMF digits are \n" + "received and the requested extension exist in the current context.\n" + " 0 - Jump to the 'o' extension in the current dialplan context.\n" + " * - Jump to the 'a' extension in the current dialplan context.\n" + "\n" + "Result is given in channel variable MINIVM_RECORD_STATUS\n" + " The possible values are: SUCCESS | USEREXIT | FAILED\n\n" + " Options:\n" + " g(#) - Use the specified amount of gain when recording the voicemail\n" + " message. The units are whole-number decibels (dB).\n" + "\n"; + +static char *synopsis_minivm_greet = "Play Mini-Voicemail prompts"; +static char *descrip_minivm_greet = + " MinivmGreet(username@domain[,options]):\n" + "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n" + "MinivmGreet() plays default prompts or user specific prompts for an account.\n" + "Busy and unavailable messages can be choosen, but will be overridden if a temporary\n" + "message exists for the account.\n" + "\n" + "Result is given in channel variable MINIVM_GREET_STATUS\n" + " The possible values are: SUCCESS | USEREXIT | FAILED\n\n" + " Options:\n" + " b - Play the 'busy' greeting to the calling party.\n" + " s - Skip the playback of instructions for leaving a message to the\n" + " calling party.\n" + " u - Play the 'unavailable greeting.\n" + "\n"; + +static char *synopsis_minivm_notify = "Notify voicemail owner about new messages."; +static char *descrip_minivm_notify = + " MinivmNotify(username@domain[,template]):\n" + "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n" + "MiniVMnotify forwards messages about new voicemail to e-mail and pager.\n" + "If there's no user account for that address, a temporary account will\n" + "be used with default options (set in minivm.conf).\n" + "The recorded file name and path will be read from MVM_FILENAME and the \n" + "duration of the message will be accessed from MVM_DURATION (set by MinivmRecord() )\n" + "If the channel variable MVM_COUNTER is set, this will be used in the\n" + "message file name and available in the template for the message.\n" + "If not template is given, the default email template will be used to send email and\n" + "default pager template to send paging message (if the user account is configured with\n" + "a paging address.\n" + "\n" + "Result is given in channel variable MINIVM_NOTIFY_STATUS\n" + " The possible values are: SUCCESS | FAILED\n" + "\n"; + +static char *synopsis_minivm_delete = "Delete Mini-Voicemail voicemail messages"; +static char *descrip_minivm_delete = + " MinivmDelete(filename):\n" + "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n" + "It deletes voicemail file set in MVM_FILENAME or given filename.\n" + "\n" + "Result is given in channel variable MINIVM_DELETE_STATUS\n" + " The possible values are: SUCCESS | FAILED\n" + " FAILED is set if the file does not exist or can't be deleted.\n" + "\n"; + +static char *synopsis_minivm_accmess = "Record account specific messages"; +static char *descrip_minivm_accmess = + " MinivmAccmess(username@domain,option):\n" + "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n" + "Use this application to record account specific audio/video messages for\n" + "busy, unavailable and temporary messages.\n" + "Account specific directories will be created if they do not exist.\n" + "\nThe option selects message to be recorded:\n" + " u Unavailable\n" + " b Busy\n" + " t Temporary (overrides busy and unavailable)\n" + " n Account name\n" + "\n" + "Result is given in channel variable MINIVM_ACCMESS_STATUS\n" + " The possible values are: SUCCESS | FAILED\n" + " FAILED is set if the file can't be created.\n" + "\n"; + +enum { + OPT_SILENT = (1 << 0), + OPT_BUSY_GREETING = (1 << 1), + OPT_UNAVAIL_GREETING = (1 << 2), + OPT_TEMP_GREETING = (1 << 3), + OPT_NAME_GREETING = (1 << 4), + OPT_RECORDGAIN = (1 << 5), +} minivm_option_flags; + +enum { + OPT_ARG_RECORDGAIN = 0, + OPT_ARG_ARRAY_SIZE = 1, +} minivm_option_args; + +AST_APP_OPTIONS(minivm_app_options, { + AST_APP_OPTION('s', OPT_SILENT), + AST_APP_OPTION('b', OPT_BUSY_GREETING), + AST_APP_OPTION('u', OPT_UNAVAIL_GREETING), + AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN), +}); + +AST_APP_OPTIONS(minivm_accmess_options, { + AST_APP_OPTION('b', OPT_BUSY_GREETING), + AST_APP_OPTION('u', OPT_UNAVAIL_GREETING), + AST_APP_OPTION('t', OPT_TEMP_GREETING), + AST_APP_OPTION('n', OPT_NAME_GREETING), +}); + +/*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */ +struct minivm_account { + char username[AST_MAX_CONTEXT]; /*!< Mailbox username */ + char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */ + + char pincode[10]; /*!< Secret pin code, numbers only */ + char fullname[120]; /*!< Full name, for directory app */ + char email[80]; /*!< E-mail address - override */ + char pager[80]; /*!< E-mail address to pager (no attachment) */ + char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */ + char serveremail[80]; /*!< From: Mail address */ + char externnotify[160]; /*!< Configurable notification command */ + char language[MAX_LANGUAGE]; /*!< Config: Language setting */ + char zonetag[80]; /*!< Time zone */ + char uniqueid[20]; /*!< Unique integer identifier */ + char exit[80]; /*!< Options for exiting from voicemail() */ + char attachfmt[80]; /*!< Format for voicemail audio file attachment */ + char etemplate[80]; /*!< Pager template */ + char ptemplate[80]; /*!< Voicemail format */ + unsigned int flags; /*!< MVM_ flags */ + struct ast_variable *chanvars; /*!< Variables for e-mail template */ + double volgain; /*!< Volume gain for voicemails sent via e-mail */ + AST_LIST_ENTRY(minivm_account) list; +}; + +/*! \brief The list of e-mail accounts */ +static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account); + +/*! \brief Linked list of e-mail templates in various languages + These are used as templates for e-mails, pager messages and jabber messages + \ref message_templates +*/ +struct minivm_template { + char name[80]; /*!< Template name */ + char *body; /*!< Body of this template */ + char fromaddress[100]; /*!< Who's sending the e-mail? */ + char serveremail[80]; /*!< From: Mail address */ + char subject[100]; /*!< Subject line */ + char charset[32]; /*!< Default character set for this template */ + char locale[20]; /*!< Locale for setlocale() */ + char dateformat[80]; /*!< Date format to use in this attachment */ + int attachment; /*!< Attachment of media yes/no - no for pager messages */ + AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */ +}; + +/*! \brief The list of e-mail templates */ +static AST_LIST_HEAD_STATIC(message_templates, minivm_template); + +/*! \brief Options for leaving voicemail with the voicemail() application */ +struct leave_vm_options { + unsigned int flags; + signed char record_gain; +}; + +/*! \brief Structure for base64 encoding */ +struct b64_baseio { + int iocp; + int iolen; + int linelength; + int ateof; + unsigned char iobuf[B64_BASEMAXINLINE]; +}; + +/*! \brief Voicemail time zones */ +struct minivm_zone { + char name[80]; /*!< Name of this time zone */ + char timezone[80]; /*!< Timezone definition */ + char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */ + AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */ +}; + +/*! \brief The list of e-mail time zones */ +static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone); + +/*! \brief Structure for gathering statistics */ +struct minivm_stats { + int voicemailaccounts; /*!< Number of static accounts */ + int timezones; /*!< Number of time zones */ + int templates; /*!< Number of templates */ + + struct timeval reset; /*!< Time for last reset */ + int receivedmessages; /*!< Number of received messages since reset */ + struct timeval lastreceived; /*!< Time for last voicemail sent */ +}; + +/*! \brief Statistics for voicemail */ +static struct minivm_stats global_stats; + +AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */ +AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */ + +FILE *minivmlogfile; /*!< The minivm log file */ + +static int global_vmminmessage; /*!< Minimum duration of messages */ +static int global_vmmaxmessage; /*!< Maximum duration of message */ +static int global_maxsilence; /*!< Maximum silence during recording */ +static int global_maxgreet; /*!< Maximum length of prompts */ +static int global_silencethreshold = 128; +static char global_mailcmd[160]; /*!< Configurable mail cmd */ +static char global_externnotify[160]; /*!< External notification application */ +static char global_logfile[PATH_MAX]; /*!< Global log file for messages */ +static char default_vmformat[80]; + +static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */ +static int global_saydurationminfo; +static char global_charset[32]; /*!< Global charset in messages */ + +static double global_volgain; /*!< Volume gain for voicmemail via e-mail */ + +/*! \brief Default dateformat, can be overridden in configuration file */ +#define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r" +#define DEFAULT_CHARSET "ISO-8859-1" + +/* Forward declarations */ +static char *message_template_parse_filebody(const char *filename); +static char *message_template_parse_emailbody(const char *body); +static int create_vmaccount(char *name, struct ast_variable *var, int realtime); +static struct minivm_account *find_user_realtime(const char *domain, const char *username); +static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); + +/*! \brief Create message template */ +static struct minivm_template *message_template_create(const char *name) +{ + struct minivm_template *template; + + template = ast_calloc(1, sizeof(*template)); + if (!template) + return NULL; + + /* Set some defaults for templates */ + ast_copy_string(template->name, name, sizeof(template->name)); + ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat)); + ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset)); + ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject)); + template->attachment = TRUE; + + return template; +} + +/*! \brief Release memory allocated by message template */ +static void message_template_free(struct minivm_template *template) +{ + if (template->body) + ast_free(template->body); + + ast_free (template); +} + +/*! \brief Build message template from configuration */ +static int message_template_build(const char *name, struct ast_variable *var) +{ + struct minivm_template *template; + int error = 0; + + template = message_template_create(name); + if (!template) { + ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name); + return -1; + } + + while (var) { + ast_debug(3, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name); + if (!strcasecmp(var->name, "fromaddress")) { + ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress)); + } else if (!strcasecmp(var->name, "fromemail")) { + ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail)); + } else if (!strcasecmp(var->name, "subject")) { + ast_copy_string(template->subject, var->value, sizeof(template->subject)); + } else if (!strcasecmp(var->name, "locale")) { + ast_copy_string(template->locale, var->value, sizeof(template->locale)); + } else if (!strcasecmp(var->name, "attachmedia")) { + template->attachment = ast_true(var->value); + } else if (!strcasecmp(var->name, "dateformat")) { + ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat)); + } else if (!strcasecmp(var->name, "charset")) { + ast_copy_string(template->charset, var->value, sizeof(template->charset)); + } else if (!strcasecmp(var->name, "templatefile")) { + if (template->body) + ast_free(template->body); + template->body = message_template_parse_filebody(var->value); + if (!template->body) { + ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value); + error++; + } + } else if (!strcasecmp(var->name, "messagebody")) { + if (template->body) + ast_free(template->body); + template->body = message_template_parse_emailbody(var->value); + if (!template->body) { + ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value); + error++; + } + } else { + ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value); + error++; + } + var = var->next; + } + if (error) + ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name); + + AST_LIST_LOCK(&message_templates); + AST_LIST_INSERT_TAIL(&message_templates, template, list); + AST_LIST_UNLOCK(&message_templates); + + global_stats.templates++; + + return error; +} + +/*! \brief Find named template */ +static struct minivm_template *message_template_find(const char *name) +{ + struct minivm_template *this, *res = NULL; + + if (ast_strlen_zero(name)) + return NULL; + + AST_LIST_LOCK(&message_templates); + AST_LIST_TRAVERSE(&message_templates, this, list) { + if (!strcasecmp(this->name, name)) { + res = this; + break; + } + } + AST_LIST_UNLOCK(&message_templates); + + return res; +} + + +/*! \brief Clear list of templates */ +static void message_destroy_list(void) +{ + struct minivm_template *this; + AST_LIST_LOCK(&message_templates); + while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) + message_template_free(this); + + AST_LIST_UNLOCK(&message_templates); +} + +/*! \brief read buffer from file (base64 conversion) */ +static int b64_inbuf(struct b64_baseio *bio, FILE *fi) +{ + int l; + + if (bio->ateof) + return 0; + + if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) { + if (ferror(fi)) + return -1; + + bio->ateof = 1; + return 0; + } + + bio->iolen= l; + bio->iocp= 0; + + return 1; +} + +/*! \brief read character from file to buffer (base64 conversion) */ +static int b64_inchar(struct b64_baseio *bio, FILE *fi) +{ + if (bio->iocp >= bio->iolen) { + if (!b64_inbuf(bio, fi)) + return EOF; + } + + return bio->iobuf[bio->iocp++]; +} + +/*! \brief write buffer to file (base64 conversion) */ +static int b64_ochar(struct b64_baseio *bio, int c, FILE *so) +{ + if (bio->linelength >= B64_BASELINELEN) { + if (fputs(EOL,so) == EOF) + return -1; + + bio->linelength= 0; + } + + if (putc(((unsigned char) c), so) == EOF) + return -1; + + bio->linelength++; + + return 1; +} + +/*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */ +static int base_encode(char *filename, FILE *so) +{ + unsigned char dtable[B64_BASEMAXINLINE]; + int i,hiteof= 0; + FILE *fi; + struct b64_baseio bio; + + memset(&bio, 0, sizeof(bio)); + bio.iocp = B64_BASEMAXINLINE; + + if (!(fi = fopen(filename, "rb"))) { + ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno)); + return -1; + } + + for (i= 0; i<9; i++) { + dtable[i]= 'A'+i; + dtable[i+9]= 'J'+i; + dtable[26+i]= 'a'+i; + dtable[26+i+9]= 'j'+i; + } + for (i= 0; i < 8; i++) { + dtable[i+18]= 'S'+i; + dtable[26+i+18]= 's'+i; + } + for (i= 0; i < 10; i++) { + dtable[52+i]= '0'+i; + } + dtable[62]= '+'; + dtable[63]= '/'; + + while (!hiteof){ + unsigned char igroup[3], ogroup[4]; + int c,n; + + igroup[0]= igroup[1]= igroup[2]= 0; + + for (n= 0; n < 3; n++) { + if ((c = b64_inchar(&bio, fi)) == EOF) { + hiteof= 1; + break; + } + igroup[n]= (unsigned char)c; + } + + if (n> 0) { + ogroup[0]= dtable[igroup[0]>>2]; + ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)]; + ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)]; + ogroup[3]= dtable[igroup[2]&0x3F]; + + if (n<3) { + ogroup[3]= '='; + + if (n<2) + ogroup[2]= '='; + } + + for (i= 0;i<4;i++) + b64_ochar(&bio, ogroup[i], so); + } + } + + /* Put end of line - line feed */ + if (fputs(EOL, so) == EOF) + return 0; + + fclose(fi); + + return 1; +} + +static int get_date(char *s, int len) +{ + struct ast_tm tm; + struct timeval tv = ast_tvnow(); + + ast_localtime(&tv, &tm, NULL); + return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm); +} + + +/*! \brief Free user structure - if it's allocated */ +static void free_user(struct minivm_account *vmu) +{ + if (vmu->chanvars) + ast_variables_destroy(vmu->chanvars); + ast_free(vmu); +} + + + +/*! \brief Prepare for voicemail template by adding channel variables + to the channel +*/ +static void prep_email_sub_vars(struct ast_channel *channel, const struct minivm_account *vmu, const char *cidnum, const char *cidname, const char *dur, const char *date, const char *counter) +{ + char callerid[256]; + struct ast_variable *var; + + if (!channel) { + ast_log(LOG_ERROR, "No allocated channel, giving up...\n"); + return; + } + + for (var = vmu->chanvars ; var ; var = var->next) + pbx_builtin_setvar_helper(channel, var->name, var->value); + + /* Prepare variables for substition in email body and subject */ + pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname); + pbx_builtin_setvar_helper(channel, "MVM_DUR", dur); + pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain); + pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username); + pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller")); + pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller")); + pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller")); + pbx_builtin_setvar_helper(channel, "MVM_DATE", date); + if (!ast_strlen_zero(counter)) + pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter); +} + +/*! \brief Set default values for Mini-Voicemail users */ +static void populate_defaults(struct minivm_account *vmu) +{ + ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL); + ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt)); + vmu->volgain = global_volgain; +} + +/*! \brief Fix quote of mail headers for non-ascii characters */ +static char *mailheader_quote(const char *from, char *to, size_t len) +{ + char *ptr = to; + *ptr++ = '"'; + for (; ptr < to + len - 1; from++) { + if (*from == '"') + *ptr++ = '\\'; + else if (*from == '\0') + break; + *ptr++ = *from; + } + if (ptr < to + len - 1) + *ptr++ = '"'; + *ptr = '\0'; + return to; +} + + +/*! \brief Allocate new vm user and set default values */ +static struct minivm_account *mvm_user_alloc(void) +{ + struct minivm_account *new; + + new = ast_calloc(1, sizeof(*new)); + if (!new) + return NULL; + populate_defaults(new); + + return new; +} + + +/*! \brief Clear list of users */ +static void vmaccounts_destroy_list(void) +{ + struct minivm_account *this; + AST_LIST_LOCK(&minivm_accounts); + while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list))) + ast_free(this); + AST_LIST_UNLOCK(&minivm_accounts); +} + + +/*! \brief Find user from static memory object list */ +static struct minivm_account *find_account(const char *domain, const char *username, int createtemp) +{ + struct minivm_account *vmu = NULL, *cur; + + + if (ast_strlen_zero(domain) || ast_strlen_zero(username)) { + ast_log(LOG_NOTICE, "No username or domain? \n"); + return NULL; + } + ast_debug(3, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain); + + AST_LIST_LOCK(&minivm_accounts); + AST_LIST_TRAVERSE(&minivm_accounts, cur, list) { + /* Is this the voicemail account we're looking for? */ + if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username)) + break; + } + AST_LIST_UNLOCK(&minivm_accounts); + + if (cur) { + ast_debug(3, "-_-_- Found account for %s@%s\n", username, domain); + vmu = cur; + + } else + vmu = find_user_realtime(domain, username); + + if (createtemp && !vmu) { + /* Create a temporary user, send e-mail and be gone */ + vmu = mvm_user_alloc(); + ast_set2_flag(vmu, TRUE, MVM_ALLOCED); + if (vmu) { + ast_copy_string(vmu->username, username, sizeof(vmu->username)); + ast_copy_string(vmu->domain, domain, sizeof(vmu->domain)); + ast_debug(1, "--- Created temporary account\n"); + } + + } + return vmu; +} + +/*! \brief Find user in realtime storage + Returns pointer to minivm_account structure +*/ +static struct minivm_account *find_user_realtime(const char *domain, const char *username) +{ + struct ast_variable *var; + struct minivm_account *retval; + char name[MAXHOSTNAMELEN]; + + retval = mvm_user_alloc(); + if (!retval) + return NULL; + + if (username) + ast_copy_string(retval->username, username, sizeof(retval->username)); + + populate_defaults(retval); + var = ast_load_realtime("minivm", "username", username, "domain", domain, NULL); + + if (!var) { + ast_free(retval); + return NULL; + } + + snprintf(name, sizeof(name), "%s@%s", username, domain); + create_vmaccount(name, var, TRUE); + + ast_variables_destroy(var); + return retval; +} + +/*! \brief Send voicemail with audio file as an attachment */ +static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter) +{ + FILE *p = NULL; + int pfd; + char email[256] = ""; + char who[256] = ""; + char date[256]; + char bound[256]; + char fname[PATH_MAX]; + char dur[PATH_MAX]; + char tmp[80] = "/tmp/astmail-XXXXXX"; + char tmp2[PATH_MAX]; + struct timeval now; + struct ast_tm tm; + struct minivm_zone *the_zone = NULL; + int len_passdata; + struct ast_channel *ast; + char *finalfilename; + char *passdata = NULL; + char *passdata2 = NULL; + char *fromaddress; + char *fromemail; + + if (type == MVM_MESSAGE_EMAIL) { + if (vmu && !ast_strlen_zero(vmu->email)) { + ast_copy_string(email, vmu->email, sizeof(email)); + } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain)) + snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain); + } else if (type == MVM_MESSAGE_PAGE) { + ast_copy_string(email, vmu->pager, sizeof(email)); + } + + if (ast_strlen_zero(email)) { + ast_log(LOG_WARNING, "No address to send message to.\n"); + return -1; + } + + ast_debug(3, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name); + + if (!strcmp(format, "wav49")) + format = "WAV"; + + + /* If we have a gain option, process it now with sox */ + if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) { + char newtmp[PATH_MAX]; + char tmpcmd[PATH_MAX]; + int tmpfd; + + ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp)); + ast_debug(3, "newtmp: %s\n", newtmp); + tmpfd = mkstemp(newtmp); + snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format); + ast_safe_system(tmpcmd); + finalfilename = newtmp; + ast_debug(3, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username); + } else { + finalfilename = ast_strdupa(filename); + } + + /* Create file name */ + snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format); + + if (template->attachment) + ast_debug(1, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail); + + /* Make a temporary file instead of piping directly to sendmail, in case the mail + command hangs */ + pfd = mkstemp(tmp); + if (pfd > -1) { + p = fdopen(pfd, "w"); + if (!p) { + close(pfd); + pfd = -1; + } + ast_debug(1, "-_-_- Opening temp file for e-mail: %s\n", tmp); + } + if (!p) { + ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp); + return -1; + } + /* Allocate channel used for chanvar substitution */ + ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0); + + + snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60); + + /* Does this user have a timezone specified? */ + if (!ast_strlen_zero(vmu->zonetag)) { + /* Find the zone in the list */ + struct minivm_zone *z; + AST_LIST_LOCK(&minivm_zones); + AST_LIST_TRAVERSE(&minivm_zones, z, list) { + if (strcmp(z->name, vmu->zonetag)) + continue; + the_zone = z; + } + AST_LIST_UNLOCK(&minivm_zones); + } + + now = ast_tvnow(); + ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL); + ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm); + + /* Start printing the email to the temporary file */ + fprintf(p, "Date: %s\n", date); + + /* Set date format for voicemail mail */ + ast_strftime(date, sizeof(date), template->dateformat, &tm); + + + /* Populate channel with channel variables for substitution */ + prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter); + + /* Find email address to use */ + /* If there's a server e-mail adress in the account, user that, othterwise template */ + fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail; + + /* Find name to user for server e-mail */ + fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress; + + /* If needed, add hostname as domain */ + if (ast_strlen_zero(fromemail)) + fromemail = "asterisk"; + + if (strchr(fromemail, '@')) + ast_copy_string(who, fromemail, sizeof(who)); + else { + char host[MAXHOSTNAMELEN]; + gethostname(host, sizeof(host)-1); + snprintf(who, sizeof(who), "%s@%s", fromemail, host); + } + + if (ast_strlen_zero(fromaddress)) { + fprintf(p, "From: Asterisk PBX <%s>\n", who); + } else { + /* Allocate a buffer big enough for variable substitution */ + int vmlen = strlen(fromaddress) * 3 + 200; + + ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress); + if ((passdata = alloca(vmlen))) { + pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen); + len_passdata = strlen(passdata) * 2 + 3; + passdata2 = alloca(len_passdata); + fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who); + } else { + ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n"); + fclose(p); + return -1; + } + } + ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata); + + fprintf(p, "Message-ID: \n", (unsigned int)rand(), vmu->username, (int)getpid(), who); + len_passdata = strlen(vmu->fullname) * 2 + 3; + passdata2 = alloca(len_passdata); + if (!ast_strlen_zero(vmu->email)) + fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email); + else + fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain); + + if (!ast_strlen_zero(template->subject)) { + char *passdata; + int vmlen = strlen(template->subject) * 3 + 200; + if ((passdata = alloca(vmlen))) { + pbx_substitute_variables_helper(ast, template->subject, passdata, vmlen); + fprintf(p, "Subject: %s\n", passdata); + } else { + ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n"); + fclose(p); + return -1; + } + + ast_debug(4, "-_-_- Subject now: %s\n", passdata); + + } else { + fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain); + ast_debug(1, "-_-_- Using default subject for this email \n"); + } + + + if (option_debug > 2) + fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain); + fprintf(p, "MIME-Version: 1.0\n"); + + /* Something unique. */ + snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)rand()); + + fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound); + + fprintf(p, "--%s\n", bound); + fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset); + if (!ast_strlen_zero(template->body)) { + char *passdata; + int vmlen = strlen(template->body)*3 + 200; + if ((passdata = alloca(vmlen))) { + pbx_substitute_variables_helper(ast, template->body, passdata, vmlen); + ast_debug(3, "Message now: %s\n-----\n", passdata); + fprintf(p, "%s\n", passdata); + } else + ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n"); + } else { + fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n" + + "in mailbox %s from %s, on %s so you might\n" + "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, + dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date); + ast_debug(3, "Using default message body (no template)\n-----\n"); + } + /* Eww. We want formats to tell us their own MIME type */ + if (template->attachment) { + char *ctype = "audio/x-"; + ast_debug(3, "-_-_- Attaching file to message: %s\n", fname); + if (!strcasecmp(format, "ogg")) + ctype = "application/"; + + fprintf(p, "--%s\n", bound); + fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format); + fprintf(p, "Content-Transfer-Encoding: base64\n"); + fprintf(p, "Content-Description: Voicemail sound attachment.\n"); + fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format); + + base_encode(fname, p); + fprintf(p, "\n\n--%s--\n.\n", bound); + } + fclose(p); + snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp); + ast_safe_system(tmp2); + ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : ""); + ast_debug(3, "-_-_- Actual command used: %s\n", tmp2); + if (ast) + ast_channel_free(ast); + return 0; +} + +/*! \brief Create directory based on components */ +static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder) +{ + return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : ""); +} + +/*! \brief Checks if directory exists. Does not create directory, but builds string in dest + * \param dest String. base directory. + * \param len Int. Length base directory string. + * \param domain String. Ignored if is null or empty string. + * \param username String. Ignored if is null or empty string. + * \param folder String. Ignored if is null or empty string. + * \return 0 on failure, 1 on success. + */ +static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder) +{ + struct stat filestat; + make_dir(dest, len, domain, username, folder ? folder : ""); + if (stat(dest, &filestat)== -1) + return FALSE; + else + return TRUE; +} + +/*! \brief basically mkdir -p $dest/$domain/$username/$folder + * \param dest String. base directory. + * \param len Length of directory string + * \param domain String. Ignored if is null or empty string. + * \param folder String. Ignored if is null or empty string. + * \param username String. Ignored if is null or empty string. + * \return -1 on failure, 0 on success. + */ +static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder) +{ + int res; + make_dir(dest, len, domain, username, folder); + if ((res = ast_mkdir(dest, 0777))) { + ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res)); + return -1; + } + ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest); + return 0; +} + + +/*! \brief Play intro message before recording voicemail +*/ +static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes) +{ + int res; + char fn[PATH_MAX]; + + ast_debug(2, "-_-_- Still preparing to play message ...\n"); + + snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username); + + if (ast_fileexists(fn, NULL, NULL) > 0) { + res = ast_streamfile(chan, fn, chan->language); + if (res) + return -1; + res = ast_waitstream(chan, ecodes); + if (res) + return res; + } else { + int numericusername = 1; + char *i = username; + + ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n"); + + while (*i) { + ast_debug(2, "-_-_- Numeric? Checking %c\n", *i); + if (!isdigit(*i)) { + numericusername = FALSE; + break; + } + i++; + } + + if (numericusername) { + if(ast_streamfile(chan, "vm-theperson", chan->language)) + return -1; + if ((res = ast_waitstream(chan, ecodes))) + return res; + + res = ast_say_digit_str(chan, username, ecodes, chan->language); + if (res) + return res; + } else { + if(ast_streamfile(chan, "vm-theextensionis", chan->language)) + return -1; + if ((res = ast_waitstream(chan, ecodes))) + return res; + } + } + + res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language); + if (res) + return -1; + res = ast_waitstream(chan, ecodes); + return res; +} + +/*! \brief Delete media files and attribute file */ +static int vm_delete(char *file) +{ + int res; + + ast_debug(1, "-_-_- Deleting voicemail file %s\n", file); + + res = unlink(file); /* Remove the meta data file */ + res |= ast_filedelete(file, NULL); /* remove the media file */ + return res; +} + + +/*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */ +static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, + int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir, + signed char record_gain) +{ + int cmd = 0; + int max_attempts = 3; + int attempts = 0; + int recorded = 0; + int message_exists = 0; + signed char zero_gain = 0; + char *acceptdtmf = "#"; + char *canceldtmf = ""; + + /* Note that urgent and private are for flagging messages as such in the future */ + + /* barf if no pointer passed to store duration in */ + if (duration == NULL) { + ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n"); + return -1; + } + + cmd = '3'; /* Want to start by recording */ + + while ((cmd >= 0) && (cmd != 't')) { + switch (cmd) { + case '2': + /* Review */ + ast_verb(3, "Reviewing the message\n"); + ast_streamfile(chan, recordfile, chan->language); + cmd = ast_waitstream(chan, AST_DIGIT_ANY); + break; + case '3': + message_exists = 0; + /* Record */ + if (option_verbose > 2) { + if (recorded == 1) + ast_verb(3, "Re-recording the message\n"); + else + ast_verb(3, "Recording the message\n"); + } + if (recorded && outsidecaller) + cmd = ast_play_and_wait(chan, "beep"); + recorded = 1; + /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */ + if (record_gain) + ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0); + if (ast_test_flag(vmu, MVM_OPERATOR)) + canceldtmf = "0"; + cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf); + if (record_gain) + ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0); + if (cmd == -1) /* User has hung up, no options to give */ + return cmd; + if (cmd == '0') + break; + else if (cmd == '*') + break; + else { + /* If all is well, a message exists */ + message_exists = 1; + cmd = 0; + } + break; + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '*': + case '#': + cmd = ast_play_and_wait(chan, "vm-sorry"); + break; + case '0': + if(!ast_test_flag(vmu, MVM_OPERATOR)) { + cmd = ast_play_and_wait(chan, "vm-sorry"); + break; + } + if (message_exists || recorded) { + cmd = ast_play_and_wait(chan, "vm-saveoper"); + if (!cmd) + cmd = ast_waitfordigit(chan, 3000); + if (cmd == '1') { + ast_play_and_wait(chan, "vm-msgsaved"); + cmd = '0'; + } else { + ast_play_and_wait(chan, "vm-deleted"); + vm_delete(recordfile); + cmd = '0'; + } + } + return cmd; + default: + /* If the caller is an ouside caller, and the review option is enabled, + allow them to review the message, but let the owner of the box review + their OGM's */ + if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW)) + return cmd; + if (message_exists) { + cmd = ast_play_and_wait(chan, "vm-review"); + } else { + cmd = ast_play_and_wait(chan, "vm-torerecord"); + if (!cmd) + cmd = ast_waitfordigit(chan, 600); + } + + if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) { + cmd = ast_play_and_wait(chan, "vm-reachoper"); + if (!cmd) + cmd = ast_waitfordigit(chan, 600); + } + if (!cmd) + cmd = ast_waitfordigit(chan, 6000); + if (!cmd) { + attempts++; + } + if (attempts > max_attempts) { + cmd = 't'; + } + } + } + if (outsidecaller) + ast_play_and_wait(chan, "vm-goodbye"); + if (cmd == 't') + cmd = 0; + return cmd; +} + +/*! \brief Run external notification for voicemail message */ +static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu) +{ + char arguments[BUFSIZ]; + + if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify)) + return; + + snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", + ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, + vmu->username, vmu->domain, + chan->cid.cid_name, chan->cid.cid_num); + + ast_debug(1, "Executing: %s\n", arguments); + ast_safe_system(arguments); +} + +/*! \brief Send message to voicemail account owner */ +static int notify_new_message(struct ast_channel *chan, const char *templatename, struct minivm_account *vmu, const char *filename, long duration, const char *format, char *cidnum, char *cidname) +{ + char *stringp; + struct minivm_template *etemplate; + char *messageformat; + int res = 0; + char oldlocale[100]; + const char *counter; + + if (!ast_strlen_zero(vmu->attachfmt)) { + if (strstr(format, vmu->attachfmt)) { + format = vmu->attachfmt; + } else + ast_log(LOG_WARNING, "Attachment format '%s' is not one of the recorded formats '%s'. Falling back to default format for '%s@%s'.\n", vmu->attachfmt, format, vmu->username, vmu->domain); + } + + etemplate = message_template_find(vmu->etemplate); + if (!etemplate) + etemplate = message_template_find(templatename); + if (!etemplate) + etemplate = message_template_find("email-default"); + + /* Attach only the first format */ + stringp = messageformat = ast_strdupa(format); + strsep(&stringp, "|"); + + if (!ast_strlen_zero(etemplate->locale)) { + char *newlocale; + ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale)); + ast_debug(2, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale); + newlocale = setlocale(LC_TIME, etemplate->locale); + if (newlocale == NULL) { + ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale); + } + } + + + + /* Read counter if available */ + counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"); + if (ast_strlen_zero(counter)) { + ast_debug(2, "-_-_- MVM_COUNTER not found\n"); + } else { + ast_debug(2, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter); + } + + res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter); + + if (res == 0 && !ast_strlen_zero(vmu->pager)) { + /* Find template for paging */ + etemplate = message_template_find(vmu->ptemplate); + if (!etemplate) + etemplate = message_template_find("pager-default"); + if (etemplate->locale) { + ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale)); + setlocale(LC_TIME, etemplate->locale); + } + + res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter); + } + + manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter); + + run_externnotify(chan, vmu); /* Run external notification */ + + if (etemplate->locale) + setlocale(LC_TIME, oldlocale); /* Rest to old locale */ + return res; +} + + +/*! \brief Record voicemail message, store into file prepared for sending e-mail */ +static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options) +{ + char tmptxtfile[PATH_MAX]; + char callerid[256]; + FILE *txt; + int res = 0, txtdes; + int msgnum; + int duration = 0; + char date[256]; + char tmpdir[PATH_MAX]; + char ext_context[256] = ""; + char fmt[80]; + char *domain; + char tmp[256] = ""; + struct minivm_account *vmu; + int userdir; + + ast_copy_string(tmp, username, sizeof(tmp)); + username = tmp; + domain = strchr(tmp, '@'); + if (domain) { + *domain = '\0'; + domain++; + } + + if (!(vmu = find_account(domain, username, TRUE))) { + /* We could not find user, let's exit */ + ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain); + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED"); + return 0; + } + + /* Setup pre-file if appropriate */ + if (strcmp(vmu->domain, "localhost")) + snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain); + else + ast_copy_string(ext_context, vmu->domain, sizeof(ext_context)); + + /* The meat of recording the message... All the announcements and beeps have been played*/ + if (ast_strlen_zero(vmu->attachfmt)) + ast_copy_string(fmt, default_vmformat, sizeof(fmt)); + else + ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt)); + + if (ast_strlen_zero(fmt)) { + ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat); + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED"); + return res; + } + msgnum = 0; + + userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp"); + + /* If we have no user directory, use generic temporary directory */ + if (!userdir) { + create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", ""); + ast_debug(3, "Creating temporary directory %s\n", tmpdir); + } + + + snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir); + + + /* XXX This file needs to be in temp directory */ + txtdes = mkstemp(tmptxtfile); + if (txtdes < 0) { + ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno)); + res = ast_streamfile(chan, "vm-mailboxfull", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED"); + return res; + } + + if (res >= 0) { + /* Unless we're *really* silent, try to send the beep */ + res = ast_streamfile(chan, "beep", chan->language); + if (!res) + res = ast_waitstream(chan, ""); + } + + /* OEJ XXX Maybe this can be turned into a log file? Hmm. */ + /* Store information */ + ast_debug(2, "Open file for metadata: %s\n", tmptxtfile); + + res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain); + + txt = fdopen(txtdes, "w+"); + if (!txt) { + ast_log(LOG_WARNING, "Error opening text file for output\n"); + } else { + struct ast_tm tm; + struct timeval now = ast_tvnow(); + char timebuf[30]; + char logbuf[BUFSIZ]; + get_date(date, sizeof(date)); + ast_localtime(&now, &tm, NULL); + ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm); + + snprintf(logbuf, sizeof(logbuf), + /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */ + "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n", + username, + chan->context, + chan->macrocontext, + chan->exten, + chan->priority, + chan->name, + ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"), + date, + timebuf, + duration, + duration < global_vmminmessage ? "IGNORED" : "OK", + vmu->accountcode + ); + fprintf(txt, logbuf); + if (minivmlogfile) { + ast_mutex_lock(&minivmloglock); + fprintf(minivmlogfile, logbuf); + ast_mutex_unlock(&minivmloglock); + } + + if (duration < global_vmminmessage) { + ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage); + fclose(txt); + ast_filedelete(tmptxtfile, NULL); + unlink(tmptxtfile); + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED"); + return 0; + } + fclose(txt); /* Close log file */ + if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) { + ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n"); + unlink(tmptxtfile); + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED"); + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + return 0; + } + + /* Set channel variables for the notify application */ + pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile); + snprintf(timebuf, sizeof(timebuf), "%d", duration); + pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf); + pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt); + + } + global_stats.lastreceived = ast_tvnow(); + global_stats.receivedmessages++; +// /* Go ahead and delete audio files from system, they're not needed any more */ +// if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) { +// ast_filedelete(tmptxtfile, NULL); +// /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */ +// ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile); +// } + + if (res > 0) + res = 0; + + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS"); + return res; +} + +/*! \brief Notify voicemail account owners - either generic template or user specific */ +static int minivm_notify_exec(struct ast_channel *chan, void *data) +{ + int argc; + char *argv[2]; + int res = 0; + char tmp[PATH_MAX]; + char *domain; + char *tmpptr; + struct minivm_account *vmu; + char *username = argv[0]; + const char *template = ""; + const char *filename; + const char *format; + const char *duration_string; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Minivm needs at least an account argument \n"); + return -1; + } + tmpptr = ast_strdupa((char *)data); + if (!tmpptr) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + argc = ast_app_separate_args(tmpptr, ',', argv, sizeof(argv) / sizeof(argv[0])); + + if (argc == 2 && !ast_strlen_zero(argv[1])) + template = argv[1]; + + ast_copy_string(tmp, argv[0], sizeof(tmp)); + username = tmp; + domain = strchr(tmp, '@'); + if (domain) { + *domain = '\0'; + domain++; + } + if (ast_strlen_zero(domain) || ast_strlen_zero(username)) { + ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]); + return -1; + } + + if(!(vmu = find_account(domain, username, TRUE))) { + /* We could not find user, let's exit */ + ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain); + pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED"); + return -1; + } + + filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"); + format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"); + duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"); + /* Notify of new message to e-mail and pager */ + if (!ast_strlen_zero(filename)) { + res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name); + }; + + pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED"); + + + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + + /* Ok, we're ready to rock and roll. Return to dialplan */ + + return res; + +} + +/*! \brief Dialplan function to record voicemail */ +static int minivm_record_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char *tmp; + struct leave_vm_options leave_options; + int argc; + char *argv[2]; + struct ast_flags flags = { 0 }; + char *opts[OPT_ARG_ARRAY_SIZE]; + + memset(&leave_options, 0, sizeof(leave_options)); + + /* Answer channel if it's not already answered */ + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Minivm needs at least an account argument \n"); + return -1; + } + tmp = ast_strdupa((char *)data); + if (!tmp) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + argc = ast_app_separate_args(tmp, ',', argv, sizeof(argv) / sizeof(argv[0])); + if (argc == 2) { + if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) { + return -1; + } + ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING ); + if (ast_test_flag(&flags, OPT_RECORDGAIN)) { + int gain; + + if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) { + ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]); + return -1; + } else + leave_options.record_gain = (signed char) gain; + } + } + + /* Now run the appliation and good luck to you! */ + res = leave_voicemail(chan, argv[0], &leave_options); + + if (res == ERROR_LOCK_PATH) { + ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n"); + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED"); + res = 0; + } + pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS"); + + return res; +} + +/*! \brief Play voicemail prompts - either generic or user specific */ +static int minivm_greet_exec(struct ast_channel *chan, void *data) +{ + struct leave_vm_options leave_options = { 0, '\0'}; + int argc; + char *argv[2]; + struct ast_flags flags = { 0 }; + char *opts[OPT_ARG_ARRAY_SIZE]; + int res = 0; + int ausemacro = 0; + int ousemacro = 0; + int ouseexten = 0; + char tmp[PATH_MAX]; + char dest[PATH_MAX]; + char prefile[PATH_MAX]; + char tempfile[PATH_MAX] = ""; + char ext_context[256] = ""; + char *domain; + char ecodes[16] = "#"; + char *tmpptr; + struct minivm_account *vmu; + char *username = argv[0]; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Minivm needs at least an account argument \n"); + return -1; + } + tmpptr = ast_strdupa((char *)data); + if (!tmpptr) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + argc = ast_app_separate_args(tmpptr, ',', argv, sizeof(argv) / sizeof(argv[0])); + + if (argc == 2) { + if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) + return -1; + ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING ); + } + + ast_copy_string(tmp, argv[0], sizeof(tmp)); + username = tmp; + domain = strchr(tmp, '@'); + if (domain) { + *domain = '\0'; + domain++; + } + if (ast_strlen_zero(domain) || ast_strlen_zero(username)) { + ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]); + return -1; + } + ast_debug(1, "-_-_- Trying to find configuration for user %s in domain %s\n", username, domain); + + if (!(vmu = find_account(domain, username, TRUE))) { + ast_log(LOG_ERROR, "Could not allocate memory. \n"); + return -1; + } + + /* Answer channel if it's not already answered */ + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + /* Setup pre-file if appropriate */ + if (strcmp(vmu->domain, "localhost")) + snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain); + else + ast_copy_string(ext_context, vmu->domain, sizeof(ext_context)); + + if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) { + res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy"); + if (res) + snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username); + } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) { + res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail"); + if (res) + snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username); + } + /* Check for temporary greeting - it overrides busy and unavail */ + snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username); + if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) { + ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile); + ast_copy_string(prefile, tempfile, sizeof(prefile)); + } + ast_debug(2, "-_-_- Preparing to play message ...\n"); + + /* Check current or macro-calling context for special extensions */ + if (ast_test_flag(vmu, MVM_OPERATOR)) { + if (!ast_strlen_zero(vmu->exit)) { + if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) { + strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1); + ouseexten = 1; + } + } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) { + strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1); + ouseexten = 1; + } + else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) { + strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1); + ousemacro = 1; + } + } + + if (!ast_strlen_zero(vmu->exit)) { + if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num)) + strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1); + } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num)) + strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1); + else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) { + strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1); + ausemacro = 1; + } + + res = 0; /* Reset */ + /* Play the beginning intro if desired */ + if (!ast_strlen_zero(prefile)) { + if (ast_streamfile(chan, prefile, chan->language) > -1) + res = ast_waitstream(chan, ecodes); + } else { + ast_debug(2, "%s doesn't exist, doing what we can\n", prefile); + res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes); + } + if (res < 0) { + ast_debug(2, "Hang up during prefile playback\n"); + pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED"); + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + return -1; + } + if (res == '#') { + /* On a '#' we skip the instructions */ + ast_set_flag(&leave_options, OPT_SILENT); + res = 0; + } + if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) { + res = ast_streamfile(chan, SOUND_INTRO, chan->language); + if (!res) + res = ast_waitstream(chan, ecodes); + if (res == '#') { + ast_set_flag(&leave_options, OPT_SILENT); + res = 0; + } + } + if (res > 0) + ast_stopstream(chan); + /* Check for a '*' here in case the caller wants to escape from voicemail to something + other than the operator -- an automated attendant or mailbox login for example */ + if (res == '*') { + chan->exten[0] = 'a'; + chan->exten[1] = '\0'; + if (!ast_strlen_zero(vmu->exit)) { + ast_copy_string(chan->context, vmu->exit, sizeof(chan->context)); + } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) { + ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context)); + } + chan->priority = 0; + pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT"); + res = 0; + } else if (res == '0') { /* Check for a '0' here */ + if(ouseexten || ousemacro) { + chan->exten[0] = 'o'; + chan->exten[1] = '\0'; + if (!ast_strlen_zero(vmu->exit)) { + ast_copy_string(chan->context, vmu->exit, sizeof(chan->context)); + } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) { + ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context)); + } + ast_play_and_wait(chan, "transfer"); + chan->priority = 0; + pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT"); + } + res = 0; + } else if (res < 0) { + pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED"); + res = -1; + } else + pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "SUCCESS"); + + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + + + /* Ok, we're ready to rock and roll. Return to dialplan */ + return res; + +} + +/*! \brief Dialplan application to delete voicemail */ +static int minivm_delete_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + char filename[BUFSIZ]; + + if (!ast_strlen_zero(data)) + ast_copy_string(filename, (char *) data, sizeof(filename)); + else + ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename)); + + if (ast_strlen_zero(filename)) { + ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n"); + return res; + } + + /* Go ahead and delete audio files from system, they're not needed any more */ + /* We should look for both audio and text files here */ + if (ast_fileexists(filename, NULL, NULL) > 0) { + res = vm_delete(filename); + if (res) { + ast_debug(2, "-_-_- Can't delete file: %s\n", filename); + pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED"); + } else { + ast_debug(2, "-_-_- Deleted voicemail file :: %s \n", filename); + pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "SUCCESS"); + } + } else { + ast_debug(2, "-_-_- Filename does not exist: %s\n", filename); + pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED"); + } + + return res; +} + +/*! \brief Record specific messages for voicemail account */ +static int minivm_accmess_exec(struct ast_channel *chan, void *data) +{ + int argc = 0; + char *argv[2]; + int res = 0; + char filename[PATH_MAX]; + char tmp[PATH_MAX]; + char *domain; + char *tmpptr = NULL; + struct minivm_account *vmu; + char *username = argv[0]; + struct ast_flags flags = { 0 }; + char *opts[OPT_ARG_ARRAY_SIZE]; + int error = FALSE; + char *message = NULL; + char *prompt = NULL; + int duration; + int cmd; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n"); + error = TRUE; + } else + tmpptr = ast_strdupa((char *)data); + if (!error) { + if (!tmpptr) { + ast_log(LOG_ERROR, "Out of memory\n"); + error = TRUE; + } else + argc = ast_app_separate_args(tmpptr, ',', argv, sizeof(argv) / sizeof(argv[0])); + } + + if (argc <=1) { + ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n"); + error = TRUE; + } + if (!error && strlen(argv[1]) > 1) { + ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]); + error = TRUE; + } + + if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) { + ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]); + error = TRUE; + } + + if (error) + return -1; + + ast_copy_string(tmp, argv[0], sizeof(tmp)); + username = tmp; + domain = strchr(tmp, '@'); + if (domain) { + *domain = '\0'; + domain++; + } + if (ast_strlen_zero(domain) || ast_strlen_zero(username)) { + ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]); + return -1; + } + + if(!(vmu = find_account(domain, username, TRUE))) { + /* We could not find user, let's exit */ + ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain); + pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED"); + return -1; + } + + /* Answer channel if it's not already answered */ + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + /* Here's where the action is */ + if (ast_test_flag(&flags, OPT_BUSY_GREETING)) { + message = "busy"; + prompt = "vm-rec-busy"; + } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) { + message = "unavailable"; + prompt = "vm-rec-unavail"; + } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) { + message = "temp"; + prompt = "vm-temp-greeting"; + } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) { + message = "greet"; + prompt = "vm-rec-name"; + } + snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message); + /* Maybe we should check the result of play_record_review ? */ + cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE); + + ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration); + + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + + + /* Ok, we're ready to rock and roll. Return to dialplan */ + return res; + +} + +/*! \brief Append new mailbox to mailbox list from configuration file */ +static int create_vmaccount(char *name, struct ast_variable *var, int realtime) +{ + struct minivm_account *vmu; + char *domain; + char *username; + char accbuf[BUFSIZ]; + + ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name); + + ast_copy_string(accbuf, name, sizeof(accbuf)); + username = accbuf; + domain = strchr(accbuf, '@'); + if (domain) { + *domain = '\0'; + domain++; + } + if (ast_strlen_zero(domain)) { + ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name); + return 0; + } + + ast_debug(3, "Creating static account for user %s domain %s\n", username, domain); + + /* Allocate user account */ + vmu = ast_calloc(1, sizeof(*vmu)); + if (!vmu) + return 0; + + ast_copy_string(vmu->domain, domain, sizeof(vmu->domain)); + ast_copy_string(vmu->username, username, sizeof(vmu->username)); + + populate_defaults(vmu); + + ast_debug(3, "...Configuring account %s\n", name); + + while (var) { + ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name); + if (!strcasecmp(var->name, "serveremail")) { + ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail)); + } else if (!strcasecmp(var->name, "email")) { + ast_copy_string(vmu->email, var->value, sizeof(vmu->email)); + } else if (!strcasecmp(var->name, "accountcode")) { + ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode)); + } else if (!strcasecmp(var->name, "pincode")) { + ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode)); + } else if (!strcasecmp(var->name, "domain")) { + ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain)); + } else if (!strcasecmp(var->name, "language")) { + ast_copy_string(vmu->language, var->value, sizeof(vmu->language)); + } else if (!strcasecmp(var->name, "timezone")) { + ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag)); + } else if (!strcasecmp(var->name, "externnotify")) { + ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify)); + } else if (!strcasecmp(var->name, "etemplate")) { + ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate)); + } else if (!strcasecmp(var->name, "ptemplate")) { + ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate)); + } else if (!strcasecmp(var->name, "fullname")) { + ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname)); + } else if (!strcasecmp(var->name, "setvar")) { + char *varval; + char *varname = ast_strdupa(var->value); + struct ast_variable *tmpvar; + + if (varname && (varval = strchr(varname, '='))) { + *varval = '\0'; + varval++; + if ((tmpvar = ast_variable_new(varname, varval, ""))) { + tmpvar->next = vmu->chanvars; + vmu->chanvars = tmpvar; + } + } + } else if (!strcasecmp(var->name, "pager")) { + ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager)); + } else if (!strcasecmp(var->name, "volgain")) { + sscanf(var->value, "%lf", &vmu->volgain); + } else { + ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name); + } + var = var->next; + } + ast_debug(3, "...Linking account %s\n", name); + + AST_LIST_LOCK(&minivm_accounts); + AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list); + AST_LIST_UNLOCK(&minivm_accounts); + + global_stats.voicemailaccounts++; + + ast_debug(2, "MINIVM :: Created account %s@%s - tz %s etemplate %s %s\n", username, domain, ast_strlen_zero(vmu->zonetag) ? "" : vmu->zonetag, ast_strlen_zero(vmu->etemplate) ? "" : vmu->etemplate, realtime ? "(realtime)" : ""); + return 0; +} + +/*! \brief Free Mini Voicemail timezone */ +static void free_zone(struct minivm_zone *z) +{ + ast_free(z); +} + +/*! \brief Clear list of timezones */ +static void timezone_destroy_list(void) +{ + struct minivm_zone *this; + + AST_LIST_LOCK(&minivm_zones); + while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) + free_zone(this); + + AST_LIST_UNLOCK(&minivm_zones); +} + +/*! \brief Add time zone to memory list */ +static int timezone_add(const char *zonename, const char *config) +{ + + struct minivm_zone *newzone; + char *msg_format, *timezone; + + newzone = ast_calloc(1, sizeof(*newzone)); + if (newzone == NULL) + return 0; + + msg_format = ast_strdupa(config); + if (msg_format == NULL) { + ast_log(LOG_WARNING, "Out of memory.\n"); + ast_free(newzone); + return 0; + } + + timezone = strsep(&msg_format, "|"); + if (!msg_format) { + ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename); + ast_free(newzone); + return 0; + } + + ast_copy_string(newzone->name, zonename, sizeof(newzone->name)); + ast_copy_string(newzone->timezone, timezone, sizeof(newzone->timezone)); + ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format)); + + AST_LIST_LOCK(&minivm_zones); + AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list); + AST_LIST_UNLOCK(&minivm_zones); + + global_stats.timezones++; + + return 0; +} + +/*! \brief Read message template from file */ +static char *message_template_parse_filebody(const char *filename) { + char buf[BUFSIZ * 6]; + char readbuf[BUFSIZ]; + char filenamebuf[BUFSIZ]; + char *writepos; + char *messagebody; + FILE *fi; + int lines = 0; + + if (ast_strlen_zero(filename)) + return NULL; + if (*filename == '/') + ast_copy_string(filenamebuf, filename, sizeof(filenamebuf)); + else + snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename); + + if (!(fi = fopen(filenamebuf, "r"))) { + ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf); + return NULL; + } + writepos = buf; + while (fgets(readbuf, sizeof(readbuf), fi)) { + lines ++; + if (writepos != buf) { + *writepos = '\n'; /* Replace EOL with new line */ + writepos++; + } + ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf)); + writepos += strlen(readbuf) - 1; + } + fclose(fi); + messagebody = ast_calloc(1, strlen(buf + 1)); + ast_copy_string(messagebody, buf, strlen(buf) + 1); + ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) ); + ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody); + + return messagebody; +} + +/*! \brief Parse emailbody template from configuration file */ +static char *message_template_parse_emailbody(const char *configuration) +{ + char *tmpread, *tmpwrite; + char *emailbody = ast_strdup(configuration); + + /* substitute strings \t and \n into the apropriate characters */ + tmpread = tmpwrite = emailbody; + while ((tmpwrite = strchr(tmpread,'\\'))) { + int len = strlen("\n"); + switch (tmpwrite[1]) { + case 'n': + memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1); + strncpy(tmpwrite, "\n", len); + break; + case 't': + memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1); + strncpy(tmpwrite, "\t", len); + break; + default: + ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]); + } + tmpread = tmpwrite + len; + } + return emailbody; +} + +/*! \brief Apply general configuration options */ +static int apply_general_options(struct ast_variable *var) +{ + int error = 0; + + while (var) { + /* Mail command */ + if (!strcmp(var->name, "mailcmd")) { + ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */ + } else if (!strcmp(var->name, "maxgreet")) { + global_maxgreet = atoi(var->value); + } else if (!strcmp(var->name, "maxsilence")) { + global_maxsilence = atoi(var->value); + if (global_maxsilence > 0) + global_maxsilence *= 1000; + } else if (!strcmp(var->name, "logfile")) { + if (!ast_strlen_zero(var->value) ) { + if(*(var->value) == '/') + ast_copy_string(global_logfile, var->value, sizeof(global_logfile)); + else + snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value); + } + } else if (!strcmp(var->name, "externnotify")) { + /* External voicemail notify application */ + ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify)); + } else if (!strcmp(var->name, "silencetreshold")) { + /* Silence treshold */ + global_silencethreshold = atoi(var->value); + } else if (!strcmp(var->name, "maxmessage")) { + int x; + if (sscanf(var->value, "%d", &x) == 1) { + global_vmmaxmessage = x; + } else { + error ++; + ast_log(LOG_WARNING, "Invalid max message time length\n"); + } + } else if (!strcmp(var->name, "minmessage")) { + int x; + if (sscanf(var->value, "%d", &x) == 1) { + global_vmminmessage = x; + if (global_maxsilence <= global_vmminmessage) + ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n"); + } else { + error ++; + ast_log(LOG_WARNING, "Invalid min message time length\n"); + } + } else if (!strcmp(var->name, "format")) { + ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat)); + } else if (!strcmp(var->name, "review")) { + ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW); + } else if (!strcmp(var->name, "operator")) { + ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR); + } + var = var->next; + } + return error; +} + +/*! \brief Load minivoicemail configuration */ +static int load_config(int reload) +{ + struct ast_config *cfg; + struct ast_variable *var; + char *cat; + const char *chanvar; + int error = 0; + struct minivm_template *template; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags); + if (cfg == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + ast_mutex_lock(&minivmlock); + + /* Destroy lists to reconfigure */ + message_destroy_list(); /* Destroy list of voicemail message templates */ + timezone_destroy_list(); /* Destroy list of timezones */ + vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */ + ast_debug(2, "Destroyed memory objects...\n"); + + /* First, set some default settings */ + global_externnotify[0] = '\0'; + global_logfile[0] = '\0'; + global_silencethreshold = 256; + global_vmmaxmessage = 2000; + global_maxgreet = 2000; + global_vmminmessage = 0; + strcpy(global_mailcmd, SENDMAIL); + global_maxsilence = 0; + global_saydurationminfo = 2; + ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat)); + ast_set2_flag((&globalflags), FALSE, MVM_REVIEW); + ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR); + strcpy(global_charset, "ISO-8859-1"); + /* Reset statistics */ + memset(&global_stats, 0, sizeof(global_stats)); + global_stats.reset = ast_tvnow(); + + /* Make sure we could load configuration file */ + if (!cfg) { + ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n"); + ast_mutex_unlock(&minivmlock); + return 0; + } + + ast_debug(2, "-_-_- Loaded configuration file, now parsing\n"); + + /* General settings */ + + cat = ast_category_browse(cfg, NULL); + while (cat) { + ast_debug(3, "-_-_- Found configuration section [%s]\n", cat); + if (!strcasecmp(cat, "general")) { + /* Nothing right now */ + error += apply_general_options(ast_variable_browse(cfg, cat)); + } else if (!strncasecmp(cat, "template-", 9)) { + /* Template */ + char *name = cat + 9; + + /* Now build and link template to list */ + error += message_template_build(name, ast_variable_browse(cfg, cat)); + } else { + var = ast_variable_browse(cfg, cat); + if (!strcasecmp(cat, "zonemessages")) { + /* Timezones in this context */ + while (var) { + timezone_add(var->name, var->value); + var = var->next; + } + } else { + /* Create mailbox from this */ + error += create_vmaccount(cat, var, FALSE); + } + } + /* Find next section in configuration file */ + cat = ast_category_browse(cfg, cat); + } + + /* Configure the default email template */ + message_template_build("email-default", NULL); + template = message_template_find("email-default"); + + /* Load date format config for voicemail mail */ + if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) + ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring"))) + ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress"))) + ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset"))) + ast_copy_string(template->charset, chanvar, sizeof(template->charset)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) + ast_copy_string(template->subject, chanvar, sizeof(template->subject)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) + template->body = message_template_parse_emailbody(chanvar); + template->attachment = TRUE; + + message_template_build("pager-default", NULL); + template = message_template_find("pager-default"); + if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring"))) + ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress"))) + ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset"))) + ast_copy_string(template->charset, chanvar, sizeof(template->charset)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject"))) + ast_copy_string(template->subject, chanvar,sizeof(template->subject)); + if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) + template->body = message_template_parse_emailbody(chanvar); + template->attachment = FALSE; + + if (error) + ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error); + + ast_mutex_unlock(&minivmlock); + ast_config_destroy(cfg); + + /* Close log file if it's open and disabled */ + if(minivmlogfile) + fclose(minivmlogfile); + + /* Open log file if it's enabled */ + if(!ast_strlen_zero(global_logfile)) { + minivmlogfile = fopen(global_logfile, "a"); + if(!minivmlogfile) + ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno)); + if (minivmlogfile) + ast_debug(3, "-_-_- Opened log file %s \n", global_logfile); + } + + return 0; +} + +/*! \brief CLI routine for listing templates */ +static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct minivm_template *this; + char *output_format = "%-15s %-10s %-10s %-15.15s %-50s\n"; + int count = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "minivm list templates"; + e->usage = + "Usage: minivm list templates\n" + " Lists message templates for e-mail, paging and IM\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc > 3) + return CLI_SHOWUSAGE; + + AST_LIST_LOCK(&message_templates); + if (AST_LIST_EMPTY(&message_templates)) { + ast_cli(a->fd, "There are no message templates defined\n"); + AST_LIST_UNLOCK(&message_templates); + return CLI_FAILURE; + } + ast_cli(a->fd, output_format, "Template name", "Charset", "Locale", "Attach media", "Subject"); + ast_cli(a->fd, output_format, "-------------", "-------", "------", "------------", "-------"); + AST_LIST_TRAVERSE(&message_templates, this, list) { + ast_cli(a->fd, output_format, this->name, + this->charset ? this->charset : "-", + this->locale ? this->locale : "-", + this->attachment ? "Yes" : "No", + this->subject ? this->subject : "-"); + count++; + } + AST_LIST_UNLOCK(&message_templates); + ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count); + return CLI_SUCCESS; +} + +static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state) +{ + int which = 0; + int wordlen; + struct minivm_account *vmu; + const char *domain = ""; + + /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - */ + if (pos > 4) + return NULL; + if (pos == 3) + return (state == 0) ? ast_strdup("for") : NULL; + wordlen = strlen(word); + AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) { + if (!strncasecmp(word, vmu->domain, wordlen)) { + if (domain && strcmp(domain, vmu->domain) && ++which > state) + return ast_strdup(vmu->domain); + /* ignore repeated domains ? */ + domain = vmu->domain; + } + } + return NULL; +} + +/*! \brief CLI command to list voicemail accounts */ +static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct minivm_account *vmu; + char *output_format = "%-23s %-15s %-15s %-10s %-10s %-50s\n"; + int count = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "minivm list accounts"; + e->usage = + "Usage: minivm list accounts\n" + " Lists all mailboxes currently set up\n"; + return NULL; + case CLI_GENERATE: + return complete_minivm_show_users(a->line, a->word, a->pos, a->n); + } + + if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4)) + return CLI_SHOWUSAGE; + if ((a->argc == 5) && strcmp(a->argv[3],"for")) + return CLI_SHOWUSAGE; + + AST_LIST_LOCK(&minivm_accounts); + if (AST_LIST_EMPTY(&minivm_accounts)) { + ast_cli(a->fd, "There are no voicemail users currently defined\n"); + AST_LIST_UNLOCK(&minivm_accounts); + return CLI_FAILURE; + } + ast_cli(a->fd, output_format, "User", "E-Template", "P-template", "Zone", "Format", "Full name"); + ast_cli(a->fd, output_format, "----", "----------", "----------", "----", "------", "---------"); + AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) { + char tmp[256] = ""; + if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) { + count++; + snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain); + ast_cli(a->fd, output_format, tmp, vmu->etemplate ? vmu->etemplate : "-", + vmu->ptemplate ? vmu->ptemplate : "-", + vmu->zonetag ? vmu->zonetag : "-", + vmu->attachfmt ? vmu->attachfmt : "-", + vmu->fullname); + } + } + AST_LIST_UNLOCK(&minivm_accounts); + ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count); + return CLI_SUCCESS; +} + +/*! \brief Show a list of voicemail zones in the CLI */ +static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct minivm_zone *zone; + char *output_format = "%-15s %-20s %-45s\n"; + char *res = CLI_SUCCESS; + + switch (cmd) { + case CLI_INIT: + e->command = "minivm list zones"; + e->usage = + "Usage: minivm list zones\n" + " Lists zone message formats\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) + return CLI_SHOWUSAGE; + + AST_LIST_LOCK(&minivm_zones); + if (!AST_LIST_EMPTY(&minivm_zones)) { + ast_cli(a->fd, output_format, "Zone", "Timezone", "Message Format"); + ast_cli(a->fd, output_format, "----", "--------", "--------------"); + AST_LIST_TRAVERSE(&minivm_zones, zone, list) { + ast_cli(a->fd, output_format, zone->name, zone->timezone, zone->msg_format); + } + } else { + ast_cli(a->fd, "There are no voicemail zones currently defined\n"); + res = CLI_FAILURE; + } + AST_LIST_UNLOCK(&minivm_zones); + + return res; +} + +/*! \brief CLI Show settings */ +static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "minivm show settings"; + e->usage = + "Usage: minivm show settings\n" + " Display Mini-Voicemail general settings\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, "* Mini-Voicemail general settings\n"); + ast_cli(a->fd, " -------------------------------\n"); + ast_cli(a->fd, "\n"); + ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd); + ast_cli(a->fd, " Max silence: %d\n", global_maxsilence); + ast_cli(a->fd, " Silence treshold: %d\n", global_silencethreshold); + ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage); + ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage); + ast_cli(a->fd, " Default format: %s\n", default_vmformat); + ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify); + ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : ""); + ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No"); + ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No"); + + ast_cli(a->fd, "\n"); + return CLI_SUCCESS; +} + +/*! \brief Show stats */ +static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_tm time; + char buf[BUFSIZ]; + + switch (cmd) { + + case CLI_INIT: + e->command = "minivm show stats"; + e->usage = + "Usage: minivm show stats\n" + " Display Mini-Voicemail counters\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, "* Mini-Voicemail statistics\n"); + ast_cli(a->fd, " -------------------------\n"); + ast_cli(a->fd, "\n"); + ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts); + ast_cli(a->fd, " Templates: %5d\n", global_stats.templates); + ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones); + if (global_stats.receivedmessages == 0) { + ast_cli(a->fd, " Received messages since last reset: \n"); + } else { + ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages); + ast_localtime(&global_stats.lastreceived, &time, NULL); + ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time); + ast_cli(a->fd, " Last received voicemail: %s\n", buf); + } + ast_localtime(&global_stats.reset, &time, NULL); + ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time); + ast_cli(a->fd, " Last reset: %s\n", buf); + + ast_cli(a->fd, "\n"); + return CLI_SUCCESS; +} + +/*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */ +static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct minivm_account *vmu; + char *username, *domain, *colname; + + if (!(username = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Memory Error!\n"); + return -1; + } + + if ((colname = strchr(username, ':'))) { + *colname = '\0'; + colname++; + } else { + colname = "path"; + } + if ((domain = strchr(username, '@'))) { + *domain = '\0'; + domain++; + } + if (ast_strlen_zero(username) || ast_strlen_zero(domain)) { + ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n"); + return 0; + } + + if (!(vmu = find_account(domain, username, TRUE))) + return 0; + + if (!strcasecmp(colname, "hasaccount")) { + ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len); + } else if (!strcasecmp(colname, "fullname")) { + ast_copy_string(buf, vmu->fullname, len); + } else if (!strcasecmp(colname, "email")) { + if (!ast_strlen_zero(vmu->email)) + ast_copy_string(buf, vmu->email, len); + else + snprintf(buf, len, "%s@%s", vmu->username, vmu->domain); + } else if (!strcasecmp(colname, "pager")) { + ast_copy_string(buf, vmu->pager, len); + } else if (!strcasecmp(colname, "etemplate")) { + if (!ast_strlen_zero(vmu->etemplate)) + ast_copy_string(buf, vmu->etemplate, len); + else + ast_copy_string(buf, "email-default", len); + } else if (!strcasecmp(colname, "language")) { + ast_copy_string(buf, vmu->language, len); + } else if (!strcasecmp(colname, "timezone")) { + ast_copy_string(buf, vmu->zonetag, len); + } else if (!strcasecmp(colname, "ptemplate")) { + if (!ast_strlen_zero(vmu->ptemplate)) + ast_copy_string(buf, vmu->ptemplate, len); + else + ast_copy_string(buf, "email-default", len); + } else if (!strcasecmp(colname, "accountcode")) { + ast_copy_string(buf, vmu->accountcode, len); + } else if (!strcasecmp(colname, "pincode")) { + ast_copy_string(buf, vmu->pincode, len); + } else if (!strcasecmp(colname, "path")) { + check_dirpath(buf, len, vmu->domain, vmu->username, NULL); + } else { /* Look in channel variables */ + struct ast_variable *var; + int found = 0; + + for (var = vmu->chanvars ; var ; var = var->next) + if (!strcmp(var->name, colname)) { + ast_copy_string(buf, var->value, len); + found = 1; + break; + } + } + + if(ast_test_flag(vmu, MVM_ALLOCED)) + free_user(vmu); + + return 0; +} + +/*! \brief lock directory + + only return failure if ast_lock_path returns 'timeout', + not if the path does not exist or any other reason +*/ +static int vm_lock_path(const char *path) +{ + switch (ast_lock_path(path)) { + case AST_LOCK_TIMEOUT: + return -1; + default: + return 0; + } +} + +/*! \brief Access counter file, lock directory, read and possibly write it again changed + \param directory Directory to crate file in + \param countername filename + \param value If set to zero, we only read the variable + \param operand 0 to read, 1 to set new value, 2 to change + \return -1 on error, otherwise counter value +*/ +static int access_counter_file(char *directory, char *countername, int value, int operand) +{ + char filename[BUFSIZ]; + char readbuf[BUFSIZ]; + FILE *counterfile; + int old = 0, counter = 0; + + /* Lock directory */ + if (vm_lock_path(directory)) { + return -1; /* Could not lock directory */ + } + snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername); + if (operand != 1) { + counterfile = fopen(filename, "r"); + if (counterfile) { + if(fgets(readbuf, sizeof(readbuf), counterfile)) { + ast_debug(3, "Read this string from counter file: %s\n", readbuf); + old = counter = atoi(readbuf); + } + fclose(counterfile); + } + } + switch (operand) { + case 0: /* Read only */ + ast_unlock_path(directory); + ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter); + return counter; + break; + case 1: /* Set new value */ + counter = value; + break; + case 2: /* Change value */ + counter += value; + if (counter < 0) /* Don't allow counters to fall below zero */ + counter = 0; + break; + } + + /* Now, write the new value to the file */ + counterfile = fopen(filename, "w"); + if (!counterfile) { + ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno)); + ast_unlock_path(directory); + return -1; /* Could not open file for writing */ + } + fprintf(counterfile, "%d\n\n", counter); + fclose(counterfile); + ast_unlock_path(directory); + ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter); + return counter; +} + +/*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */ +static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + char *username, *domain, *countername; + struct minivm_account *vmu = NULL; + char userpath[BUFSIZ]; + int res; + + *buf = '\0'; + + if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */ + ast_log(LOG_WARNING, "Memory error!\n"); + return -1; + } + if ((countername = strchr(username, ':'))) { + *countername = '\0'; + countername++; + } + + if ((domain = strchr(username, '@'))) { + *domain = '\0'; + domain++; + } + + /* If we have neither username nor domain now, let's give up */ + if (ast_strlen_zero(username) && ast_strlen_zero(domain)) { + ast_log(LOG_ERROR, "No account given\n"); + return -1; + } + + if (ast_strlen_zero(countername)) { + ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n"); + return -1; + } + + /* We only have a domain, no username */ + if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) { + domain = username; + username = NULL; + } + + /* If we can't find account or if the account is temporary, return. */ + if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) { + ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain); + return 0; + } + + create_dirpath(userpath, sizeof(userpath), domain, username, NULL); + + /* We have the path, now read the counter file */ + res = access_counter_file(userpath, countername, 0, 0); + if (res >= 0) + snprintf(buf, len, "%d", res); + return 0; +} + +/*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */ +static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + char *username, *domain, *countername, *operand; + char userpath[BUFSIZ]; + struct minivm_account *vmu; + int change = 0; + int operation = 0; + + if(!value) + return -1; + change = atoi(value); + + if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */ + ast_log(LOG_WARNING, "Memory error!\n"); + return -1; + } + + if ((countername = strchr(username, ':'))) { + *countername = '\0'; + countername++; + } + if ((operand = strchr(countername, ':'))) { + *operand = '\0'; + operand++; + } + + if ((domain = strchr(username, '@'))) { + *domain = '\0'; + domain++; + } + + /* If we have neither username nor domain now, let's give up */ + if (ast_strlen_zero(username) && ast_strlen_zero(domain)) { + ast_log(LOG_ERROR, "No account given\n"); + return -1; + } + + /* We only have a domain, no username */ + if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) { + domain = username; + username = NULL; + } + + if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) { + ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n"); + return -1; + } + + /* If we can't find account or if the account is temporary, return. */ + if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) { + ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain); + return 0; + } + + create_dirpath(userpath, sizeof(userpath), domain, username, NULL); + /* Now, find out our operator */ + if (*operand == 'i') /* Increment */ + operation = 2; + else if (*operand == 'd') { + change = change * -1; + operation = 2; + } else if (*operand == 's') + operation = 1; + else { + ast_log(LOG_ERROR, "Unknown operator: %s\n", operand); + return -1; + } + + /* We have the path, now read the counter file */ + access_counter_file(userpath, countername, change, operation); + return 0; +} + + +/*! \brief CLI commands for Mini-voicemail */ +static struct ast_cli_entry cli_minivm[] = { + AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"), + AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"), + AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"), + AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"), + AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"), + AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"), +}; + +static struct ast_custom_function minivm_counter_function = { + .name = "MINIVMCOUNTER", + .synopsis = "Reads or sets counters for MiniVoicemail message", + .syntax = "MINIVMCOUNTER(:name[:operand])", + .read = minivm_counter_func_read, + .write = minivm_counter_func_write, + .desc = "Valid operands for changing the value of a counter when assigning a value are:\n" + "- i Increment by value\n" + "- d Decrement by value\n" + "- s Set to value\n" + "\nThe counters never goes below zero.\n" + "- The name of the counter is a string, up to 10 characters\n" + "- If account is given and it exists, the counter is specific for the account\n" + "- If account is a domain and the domain directory exists, counters are specific for a domain\n" + "The operation is atomic and the counter is locked while changing the value\n" + "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n" + "realtime functions if you are using a database to operate your Asterisk\n", +}; + +static struct ast_custom_function minivm_account_function = { + .name = "MINIVMACCOUNT", + .synopsis = "Gets MiniVoicemail account information", + .syntax = "MINIVMACCOUNT(:item)", + .read = minivm_account_func_read, + .desc = "Valid items are:\n" + "- path Path to account mailbox (if account exists, otherwise temporary mailbox)\n" + "- hasaccount 1 if static Minivm account exists, 0 otherwise\n" + "- fullname Full name of account owner\n" + "- email Email address used for account\n" + "- etemplate E-mail template for account (default template if none is configured)\n" + "- ptemplate Pager template for account (default template if none is configured)\n" + "- accountcode Account code for voicemail account\n" + "- pincode Pin code for voicemail account\n" + "- timezone Time zone for voicemail account\n" + "- language Language for voicemail account\n" + "- Channel variable value (set in configuration for account)\n" + "\n", +}; + +/*! \brief Load mini voicemail module */ +static int load_module(void) +{ + int res; + + res = ast_register_application(app_minivm_record, minivm_record_exec, synopsis_minivm_record, descrip_minivm_record); + res = ast_register_application(app_minivm_greet, minivm_greet_exec, synopsis_minivm_greet, descrip_minivm_greet); + res = ast_register_application(app_minivm_notify, minivm_notify_exec, synopsis_minivm_notify, descrip_minivm_notify); + res = ast_register_application(app_minivm_delete, minivm_delete_exec, synopsis_minivm_delete, descrip_minivm_delete); + res = ast_register_application(app_minivm_accmess, minivm_accmess_exec, synopsis_minivm_accmess, descrip_minivm_accmess); + + ast_custom_function_register(&minivm_account_function); + ast_custom_function_register(&minivm_counter_function); + if (res) + return(res); + + if ((res = load_config(0))) + return(res); + + ast_cli_register_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0])); + + /* compute the location of the voicemail spool directory */ + snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR); + + return res; +} + +/*! \brief Reload mini voicemail module */ +static int reload(void) +{ + return(load_config(1)); +} + +/*! \brief Reload cofiguration */ +static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + + switch (cmd) { + case CLI_INIT: + e->command = "minivm reload"; + e->usage = + "Usage: minivm reload\n" + " Reload mini-voicemail configuration and reset statistics\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + reload(); + ast_cli(a->fd, "\n-- Mini voicemail re-configured \n"); + return CLI_SUCCESS; +} + +/*! \brief Unload mini voicemail module */ +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(app_minivm_record); + res |= ast_unregister_application(app_minivm_greet); + res |= ast_unregister_application(app_minivm_notify); + res |= ast_unregister_application(app_minivm_delete); + res |= ast_unregister_application(app_minivm_accmess); + ast_cli_unregister_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0])); + ast_custom_function_unregister(&minivm_account_function); + ast_custom_function_unregister(&minivm_counter_function); + + message_destroy_list(); /* Destroy list of voicemail message templates */ + timezone_destroy_list(); /* Destroy list of timezones */ + vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */ + + return res; +} + + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/apps/app_mixmonitor.c b/trunk/apps/app_mixmonitor.c new file mode 100644 index 0000000000..012af01c5e --- /dev/null +++ b/trunk/apps/app_mixmonitor.c @@ -0,0 +1,424 @@ +/* + * 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$") + +#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */ +#include "asterisk/file.h" +#include "asterisk/audiohook.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/channel.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" +"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" +"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 = NULL; + int res = 0; + + if (!chan) + return -1; + + 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; + + ast_verb(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 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); + + ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name); + + if (fs) + ast_closestream(fs); + + if (mixmonitor->post_process) { + ast_verb(2, "Executing [%s]\n", mixmonitor->post_process); + ast_safe_system(mixmonitor->post_process); + } + + ast_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_t thread; + struct mixmonitor *mixmonitor; + char postprocess2[1024] = ""; + size_t len; + + len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2; + + postprocess2[0] = 0; + /* 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 = ast_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)) { + ast_free(mixmonitor); + return; + } + + ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_WRITE); + + 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); + ast_audiohook_destroy(&mixmonitor->audiohook); + ast_free(mixmonitor); + return; + } + + ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor); + +} + +static int mixmonitor_exec(struct ast_channel *chan, void *data) +{ + int x, readvol = 0, writevol = 0; + struct ast_flags flags = {0}; + char *parse, *tmp, *slash; + 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; + } + + 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"); + 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; + } + + tmp = ast_strdupa(args.filename); + if ((slash = strrchr(tmp, '/'))) + *slash = '\0'; + ast_mkdir(tmp, 0777); + + pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename); + launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process); + + return 0; +} + +static int stop_mixmonitor_exec(struct ast_channel *chan, void *data) +{ + ast_audiohook_detach_source(chan, mixmonitor_spy_type); + return 0; +} + +static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_channel *chan; + + switch (cmd) { + case CLI_INIT: + e->command = "mixmonitor [start|stop]"; + e->usage = + "Usage: mixmonitor [args]\n" + " The optional arguments are passed to the MixMonitor\n" + " application when the 'start' command is used.\n"; + return NULL; + case CLI_GENERATE: + return ast_complete_channels(a->line, a->word, a->pos, a->n, 2); + } + + if (a->argc < 3) + return CLI_SHOWUSAGE; + + if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) { + ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]); + /* Technically this is a failure, but we don't want 2 errors printing out */ + return CLI_SUCCESS; + } + + if (!strcasecmp(a->argv[1], "start")) { + mixmonitor_exec(chan, a->argv[3]); + ast_channel_unlock(chan); + } else { + ast_channel_unlock(chan); + ast_audiohook_detach_source(chan, mixmonitor_spy_type); + } + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_mixmonitor[] = { + AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command") +}; + +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); + + 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/trunk/apps/app_morsecode.c b/trunk/apps/app_morsecode.c new file mode 100644 index 0000000000..6c88ed32ca --- /dev/null +++ b/trunk/apps/app_morsecode.c @@ -0,0 +1,161 @@ +/* + * 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$") + +#include "asterisk/file.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 = +" 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; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Syntax: Morsecode() - no argument found\n"); + 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); + } + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app_morsecode); +} + +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/trunk/apps/app_mp3.c b/trunk/apps/app_mp3.c new file mode 100644 index 0000000000..d2f2f5c0e2 --- /dev/null +++ b/trunk/apps/app_mp3.c @@ -0,0 +1,235 @@ +/* + * 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$") + +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/frame.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.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; + 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; + } + + if (pipe(fds)) { + ast_log(LOG_WARNING, "Unable to create pipe\n"); + 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"); + 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_debug(1, "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_debug(1, "Hangup detected\n"); + res = -1; + break; + } + if (ms) { + f = ast_read(chan); + if (!f) { + ast_debug(1, "Null frame == hangup() detected\n"); + res = -1; + break; + } + if (f->frametype == AST_FRAME_DTMF) { + ast_debug(1, "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); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_nbscat.c b/trunk/apps/app_nbscat.c new file mode 100644 index 0000000000..4e7203319c --- /dev/null +++ b/trunk/apps/app_nbscat.c @@ -0,0 +1,218 @@ +/* + * 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$") + +#include +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/frame.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.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; + 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; + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) { + ast_log(LOG_WARNING, "Unable to create socketpair\n"); + 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"); + 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_debug(1, "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_debug(1, "Hangup detected\n"); + res = -1; + break; + } + if (ms) { + f = ast_read(chan); + if (!f) { + ast_debug(1, "Null frame == hangup() detected\n"); + res = -1; + break; + } + if (f->frametype == AST_FRAME_DTMF) { + ast_debug(1, "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); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_osplookup.c b/trunk/apps/app_osplookup.c new file mode 100644 index 0000000000..bee5dae1de --- /dev/null +++ b/trunk/apps/app_osplookup.c @@ -0,0 +1,2041 @@ +/* + * 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 + * + * \extref The OSP Toolkit: http://www.transnexus.com + * \extref OpenSSL http://www.openssl.org + * + * \ingroup applications + */ + +/*** MODULEINFO + osptk + ssl + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include + +#include "asterisk/paths.h" +#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/cli.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 */ +#define OSP_TECHSTR_SIZE ((unsigned int)32) /* OSP signed/unsigned int string buffer size */ +#define OSP_UUID_SIZE ((unsigned int)16) /* UUID size */ +#define OSP_UUIDSTR_SIZE ((unsigned int)36) /* UUID string size */ + +/* 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 */ +}; + +/* Call ID type*/ +#define OSP_CALLID_UNDEFINED ((unsigned int)0) /* UNDEFINED */ +#define OSP_CALLID_H323 ((unsigned int)(1 << 0)) /* H.323 */ +#define OSP_CALLID_SIP ((unsigned int)(1 << 1)) /* SIP */ +#define OSP_CALLID_IAX ((unsigned int)(1 << 2)) /* IAX2 */ +#define OSP_CALLID_MAXNUM ((unsigned int)3) /* Max number of call ID type */ + +/* OSP Supported Destination Protocols */ +#define OSP_PROT_H323 ((char*)"H323") /* H323 Q931 protocol name*/ +#define OSP_PROT_SIP ((char*)"SIP") /* SIP protocol name */ +#define OSP_PROT_IAX ((char*)"IAX") /* IAX protocol name */ +#define OSP_PROT_OTHER ((char*)"OTHER") /* Other protocol name */ + +/* OSP supported Destination Tech */ +#if 0 +#define OSP_TECH_H323 ((char*)"OOH323") /* OOH323 tech name */ +#endif +#define OSP_TECH_H323 ((char*)"H323") /* OH323 tech name */ +#define OSP_TECH_SIP ((char*)"SIP") /* SIP tech name */ +#define OSP_TECH_IAX ((char*)"IAX2") /* IAX2 tech name */ + +/* SIP OSP header field name */ +#define OSP_SIP_HEADER ((char*)"P-OSP-Auth-Token: ") + +/* 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 */ +#define OSP_DEF_PROTOCOL OSP_PROT_SIP /* OSP default destination protocol, SIP */ + +/* 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 */ + char* defaultprotocol; /* OSP default destination protocol */ + OSPTPROVHANDLE handle; /* OSP provider handle */ + struct osp_provider* next; /* Pointer to next OSP provider */ +}; + +/* Call ID */ +struct osp_callid { + unsigned char buf[OSPC_CALLID_MAXSIZE]; /* Call ID string */ + unsigned int len; /* Call ID length */ +}; + +/* 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[OSP_TECHSTR_SIZE]; /* Outbound Asterisk TECH string */ + char dest[OSP_NORSTR_SIZE]; /* Outbound destination IP address */ + char called[OSP_NORSTR_SIZE]; /* Outbound called number, may be translated */ + char calling[OSP_NORSTR_SIZE]; /* Outbound calling number, may be translated */ + char token[OSP_TOKSTR_SIZE]; /* Outbound OSP token */ + char networkid[OSP_NORSTR_SIZE]; /* Outbound network ID */ + unsigned int numresults; /* Number of remain outbound destinations */ + struct osp_callid outcallid; /* Outbound call ID */ +}; + +/* 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->defaultprotocol = OSP_DEF_PROTOCOL; + 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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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_debug(1, "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); + } + } else if (!strcasecmp(v->name, "defaultprotocol")) { + if (!strcasecmp(v->value, OSP_PROT_SIP)) { + p->defaultprotocol = OSP_PROT_SIP; + ast_debug(1, "OSP: default protocol '%s'\n", p->defaultprotocol); + } else if (!strcasecmp(v->value, OSP_PROT_H323)) { + p->defaultprotocol = OSP_PROT_H323; + ast_debug(1, "OSP: default protocol '%s'\n", p->defaultprotocol); + } else if (!strcasecmp(v->value, OSP_PROT_IAX)) { + p->defaultprotocol = OSP_PROT_IAX; + ast_debug(1, "OSP: default protocol '%s'\n", p->defaultprotocol); + } else { + ast_log(LOG_WARNING, "OSP: default protocol should be %s, %s, %s, or %s not '%s' at line %d\n", + OSP_PROT_SIP, OSP_PROT_H323, OSP_PROT_IAX, OSP_PROT_OTHER, 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); + ast_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) { + ast_free(privatekey.PrivateKeyData); + } + ast_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_debug(1, "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) { + ast_free(cacerts[j].CertData); + } + } + if (localcert.CertData) { + ast_free(localcert.CertData); + } + if (privatekey.PrivateKeyData) { + ast_free(privatekey.PrivateKeyData); + } + ast_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); + ast_free(p); + res = -1; + } else { + ast_debug(1, "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) { + ast_free(cacerts[i].CertData); + } + } + if (localcert.CertData) { + ast_free(localcert.CertData); + } + if (privatekey.PrivateKeyData) { + ast_free(privatekey.PrivateKeyData); + } + + return res; +} + +/*! + * \brief Get OSP provider by name + * \param name OSP provider context name + * \param provider OSP provider structure + * \return 1 Success, 0 Failed, -1 Error + */ +static int osp_get_provider( + const char* name, + struct osp_provider** provider) +{ + int res = 0; + struct osp_provider* p; + + ast_mutex_lock(&osplock); + p = ospproviders; + while(p) { + if (!strcasecmp(p->name, name)) { + *provider = p; + ast_debug(1, "OSP: find provider '%s'\n", name); + 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_debug(1, "OSP: transaction '%d'\n", *transaction); + ast_copy_string(source, p->source, sourcesize); + ast_debug(1, "OSP: source '%s'\n", source); + res = 1; + } else { + *transaction = OSP_INVALID_HANDLE; + ast_debug(1, "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 destination 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* destination, + 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(destination, 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_debug(1, "OSP: Unable to validate inbound token\n"); + res = -1; + } else if (authorised) { + ast_debug(1, "OSP: Authorised\n"); + res = 1; + } else { + ast_debug(1, "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 provider OSP provider + * \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( + struct osp_provider* provider, + 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_debug(1, "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_debug(1, "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 = OSPPTransactionGetDestNetworkId(result->outhandle, result->networkid)) != OSPC_ERR_NO_ERROR) { + ast_debug(1, "OSP: Unable to get destination network ID, error '%d'\n", error); + result->networkid[0] = '\0'; + } + + if ((error = OSPPTransactionGetDestProtocol(result->outhandle, &protocol)) != OSPC_ERR_NO_ERROR) { + ast_debug(1, "OSP: Unable to get destination protocol, error '%d'\n", error); + *reason = OSPC_FAIL_NORMAL_UNSPECIFIED; + result->token[0] = '\0'; + result->networkid[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_debug(1, "OSP: protocol '%s'\n", OSP_PROT_H323); + ast_copy_string(result->tech, OSP_TECH_H323, sizeof(result->tech)); + ast_copy_string(result->dest, destination + 1, sizeof(result->dest)); + ast_copy_string(result->called, called, sizeof(result->called)); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + case OSPE_DEST_PROT_SIP: + ast_debug(1, "OSP: protocol '%s'\n", OSP_PROT_SIP); + ast_copy_string(result->tech, OSP_TECH_SIP, sizeof(result->tech)); + ast_copy_string(result->dest, destination + 1, sizeof(result->dest)); + ast_copy_string(result->called, called, sizeof(result->called)); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + case OSPE_DEST_PROT_IAX: + ast_debug(1, "OSP: protocol '%s'\n", OSP_PROT_IAX); + ast_copy_string(result->tech, OSP_TECH_IAX, sizeof(result->tech)); + ast_copy_string(result->dest, destination + 1, sizeof(result->dest)); + ast_copy_string(result->called, called, sizeof(result->called)); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + case OSPE_DEST_PROT_UNDEFINED: + case OSPE_DEST_PROT_UNKNOWN: + ast_debug(1, "OSP: unknown/undefined protocol '%d'\n", protocol); + ast_debug(1, "OSP: use default protocol '%s'\n", provider->defaultprotocol); + + ast_copy_string(result->tech, provider->defaultprotocol, sizeof(result->tech)); + ast_copy_string(result->dest, destination + 1, sizeof(result->dest)); + ast_copy_string(result->called, called, sizeof(result->called)); + ast_copy_string(result->calling, calling, sizeof(result->calling)); + break; + case OSPE_DEST_PROT_H323_LRQ: + default: + ast_log(LOG_WARNING, "OSP: unsupported protocol '%d'\n", protocol); + *reason = OSPC_FAIL_PROTOCOL_ERROR; + result->token[0] = '\0'; + result->networkid[0] = '\0'; + res = 0; + break; + } + + 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; + struct osp_provider* p; + char dest[OSP_NORSTR_SIZE]; + + *transaction = OSP_INVALID_HANDLE; + *timelimit = OSP_DEF_TIMELIMIT; + + if ((res = osp_get_provider(provider, &p)) <= 0) { + ast_debug(1, "OSP: Unabe to find OSP provider '%s'\n", provider); + return res; + } + + switch (p->authpolicy) { + 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_debug(1, "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_debug(1, "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 Create a UUID + * \param uuid UUID buffer + * \param buffersize UUID buffer size + * \return 1 Created, -1 Error + */ +static int osp_create_uuid( + unsigned char* uuid, + unsigned int* buffersize) +{ + int i, res; + long int* tmp; + + if (*buffersize >= OSP_UUID_SIZE) { + tmp = (long int*)uuid; + for (i = 0; i < OSP_UUID_SIZE / sizeof(long int); i++) { + tmp[i] = ast_random(); + } + *buffersize = OSP_UUID_SIZE; + res = 1; + } else { + res = -1; + } + + return res; +} + +/*! + * \brief UUID to string + * \param uuid UUID + * \param buffer String buffer + * \param buffersize String buffer size + * \return 1 Successed, -1 Error + */ +static int osp_uuid2str( + unsigned char* uuid, + char* buffer, + unsigned int buffersize) +{ + int res; + + if (buffersize > OSP_UUIDSTR_SIZE) { + snprintf(buffer, buffersize, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); + res = 1; + } else { + res = -1; + } + + return res; +} + +/*! + * \brief Create a call ID according to the type + * \param type Call ID type + * \param callid Call ID buffer + * \return 1 Created, 0 Not create, -1 Error + */ +static int osp_create_callid( + unsigned int type, + struct osp_callid* callid) +{ + int res; + + callid->len = sizeof(callid->buf); + switch (type) { + case OSP_CALLID_H323: + res = osp_create_uuid(callid->buf, &callid->len); + break; + case OSP_CALLID_SIP: + case OSP_CALLID_IAX: + res = 0; + default: + res = -1; + break; + } + + if ((res != 1) && (callid->len != 0)) { + callid->buf[0] = '\0'; + callid->len = 0; + } + + 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 callidtypes Call ID types + * \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, + unsigned int callidtypes, + struct osp_result* result) +{ + int res; + struct osp_provider* p; + char source[OSP_NORSTR_SIZE]; + 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 i, type; + struct osp_callid callid; + unsigned int callidnum; + OSPTCALLID* callids[OSP_CALLID_MAXNUM]; + unsigned int dummy = 0; + enum OSPEFAILREASON reason; + int error; + + result->outhandle = OSP_INVALID_HANDLE; + result->tech[0] = '\0'; + result->dest[0] = '\0'; + result->called[0] = '\0'; + result->calling[0] = '\0'; + result->token[0] = '\0'; + result->networkid[0] = '\0'; + result->numresults = 0; + result->outtimelimit = OSP_DEF_TIMELIMIT; + + if ((res = osp_get_provider(provider, &p)) <= 0) { + ast_debug(1, "OSP: Unabe to find OSP provider '%s'\n", provider); + return res; + } + + if ((res = osp_create_transaction(provider, &result->outhandle, sizeof(source), source)) <= 0) { + ast_debug(1, "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; + } + + callidnum = 0; + callids[0] = NULL; + for (i = 0; i < OSP_CALLID_MAXNUM; i++) { + type = 1 << i; + if (callidtypes & type) { + error = osp_create_callid(type, &callid); + if (error == 1) { + callids[callidnum] = OSPPCallIdNew(callid.len, callid.buf); + callidnum++; + } + } + } + + 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, + callidnum, + callids, + NULL, + &result->numresults, + &dummy, + NULL); + + for (i = 0; i < callidnum; i++) { + OSPPCallIdDelete(&callids[i]); + } + + if (error != OSPC_ERR_NO_ERROR) { + ast_debug(1, "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_debug(1, "OSP: No more destination\n"); + if (result->inhandle != OSP_INVALID_HANDLE) { + OSPPTransactionRecordFailure(result->inhandle, OSPC_FAIL_NO_ROUTE_TO_DEST); + } + return 0; + } + + result->outcallid.len = sizeof(result->outcallid.buf); + tokenlen = sizeof(token); + error = OSPPTransactionGetFirstDestination( + result->outhandle, + 0, + NULL, + NULL, + &result->outtimelimit, + &result->outcallid.len, + result->outcallid.buf, + sizeof(callednum), + callednum, + sizeof(callingnum), + callingnum, + sizeof(destination), + destination, + 0, + NULL, + &tokenlen, + token); + if (error != OSPC_ERR_NO_ERROR) { + ast_debug(1, "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_debug(1, "OSP: outtimelimit '%d'\n", result->outtimelimit); + ast_debug(1, "OSP: called '%s'\n", callednum); + ast_debug(1, "OSP: calling '%s'\n", callingnum); + ast_debug(1, "OSP: destination '%s'\n", destination); + ast_debug(1, "OSP: token size '%d'\n", tokenlen); + + if ((res = osp_check_destination(p, callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) { + return 1; + } + + if (!result->numresults) { + ast_debug(1, "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) { + result->outcallid.len = sizeof(result->outcallid.buf); + tokenlen = sizeof(token); + error = OSPPTransactionGetNextDestination( + result->outhandle, + reason, + 0, + NULL, + NULL, + &result->outtimelimit, + &result->outcallid.len, + result->outcallid.buf, + 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_debug(1, "OSP: outtimelimit '%d'\n", result->outtimelimit); + ast_debug(1, "OSP: called '%s'\n", callednum); + ast_debug(1, "OSP: calling '%s'\n", callingnum); + ast_debug(1, "OSP: destination '%s'\n", destination); + ast_debug(1, "OSP: token size '%d'\n", tokenlen); + + if ((res = osp_check_destination(p, callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) { + break; + } else if (!result->numresults) { + ast_debug(1, "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_debug(1, "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 provider OSP provider name + * \param cause Asterisk hangup cuase + * \param result Lookup results, in/output + * \return 1 Found , 0 No route, -1 Error + */ +static int osp_next( + const char* provider, + int cause, + struct osp_result* result) +{ + int res; + struct osp_provider* p; + 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->called[0] = '\0'; + result->calling[0] = '\0'; + result->token[0] = '\0'; + result->networkid[0] = '\0'; + result->outtimelimit = OSP_DEF_TIMELIMIT; + + if ((res = osp_get_provider(provider, &p)) <= 0) { + ast_debug(1, "OSP: Unabe to find OSP provider '%s'\n", provider); + return res; + } + + if (result->outhandle == OSP_INVALID_HANDLE) { + ast_debug(1, "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_debug(1, "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) { + result->outcallid.len = sizeof(result->outcallid.buf); + tokenlen = sizeof(token); + error = OSPPTransactionGetNextDestination( + result->outhandle, + reason, + 0, + NULL, + NULL, + &result->outtimelimit, + &result->outcallid.len, + result->outcallid.buf, + 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_debug(1, "OSP: outtimelimit '%d'\n", result->outtimelimit); + ast_debug(1, "OSP: called '%s'\n", callednum); + ast_debug(1, "OSP: calling '%s'\n", callingnum); + ast_debug(1, "OSP: destination '%s'\n", destination); + ast_debug(1, "OSP: token size '%d'\n", tokenlen); + + if ((res = osp_check_destination(p, callednum, callingnum, destination, tokenlen, token, &reason, result)) > 0) { + res = 1; + break; + } else if (!result->numresults) { + ast_debug(1, "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_debug(1, "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_debug(1, "OSP: Usage reported\n"); + res = 1; + } else { + ast_debug(1, "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; + const char* provider = OSP_DEF_PROVIDER; + 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); + ); + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + if (!ast_strlen_zero(args.provider)) { + provider = args.provider; + } + ast_debug(1, "OSPAuth: provider '%s'\n", provider); + + 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_debug(1, "OSPAuth: source '%s'\n", source); + ast_debug(1, "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_debug(1, "OSPAuth: OSPINHANDLE '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", timelimit); + pbx_builtin_setvar_helper(chan, "OSPINTIMELIMIT", buffer); + ast_debug(1, "OSPAuth: OSPINTIMELIMIT '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPAUTHSTATUS", status); + ast_debug(1, "OSPAuth: %s\n", status); + + if(res <= 0) { + res = -1; + } else { + res = 0; + } + + 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; + const char* provider = OSP_DEF_PROVIDER; + struct varshead* headp; + struct ast_var_t* current; + const char* srcdev = ""; + const char* netid = ""; + char buffer[OSP_TOKSTR_SIZE]; + unsigned int callidtypes = OSP_CALLID_UNDEFINED; + 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; + } + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + ast_debug(1, "OSPLookup: exten '%s'\n", args.exten); + + if (!ast_strlen_zero(args.provider)) { + provider = args.provider; + } + ast_debug(1, "OSPlookup: provider '%s'\n", provider); + + if (args.options) { + if (strchr(args.options, 'h')) { + callidtypes |= OSP_CALLID_H323; + } + if (strchr(args.options, 's')) { + callidtypes |= OSP_CALLID_SIP; + } + if (strchr(args.options, 'i')) { + callidtypes |= OSP_CALLID_IAX; + } + } + ast_debug(1, "OSPLookup: call id types '%d'\n", callidtypes); + + 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), "OSPINNETWORKID")) { + netid = ast_var_value(current); + } else if (!strcasecmp(ast_var_name(current), "OSPPEERIP")) { + srcdev = ast_var_value(current); + } + } + ast_debug(1, "OSPLookup: OSPINHANDLE '%d'\n", result.inhandle); + ast_debug(1, "OSPLookup: OSPINTIMELIMIT '%d'\n", result.intimelimit); + ast_debug(1, "OSPLookup: OSPINNETWORKID '%s'\n", netid); + ast_debug(1, "OSPLookup: source device '%s'\n", srcdev); + + if ((cres = ast_autoservice_start(chan)) < 0) { + return -1; + } + + if ((res = osp_lookup(provider, srcdev, chan->cid.cid_num, args.exten, callidtypes, &result)) > 0) { + status = AST_OSP_SUCCESS; + } else { + result.tech[0] = '\0'; + result.dest[0] = '\0'; + result.called[0] = '\0'; + result.calling[0] = '\0'; + result.token[0] = '\0'; + result.networkid[0] = '\0'; + result.numresults = 0; + result.outtimelimit = OSP_DEF_TIMELIMIT; + result.outcallid.buf[0] = '\0'; + result.outcallid.len = 0; + 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_debug(1, "OSPLookup: OSPOUTHANDLE '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPTECH", result.tech); + ast_debug(1, "OSPLookup: OSPTECH '%s'\n", result.tech); + pbx_builtin_setvar_helper(chan, "OSPDEST", result.dest); + ast_debug(1, "OSPLookup: OSPDEST '%s'\n", result.dest); + pbx_builtin_setvar_helper(chan, "OSPCALLED", result.called); + ast_debug(1, "OSPLookup: OSPCALLED '%s'\n", result.called); + pbx_builtin_setvar_helper(chan, "OSPCALLING", result.calling); + ast_debug(1, "OSPLookup: OSPCALLING '%s'\n", result.calling); + pbx_builtin_setvar_helper(chan, "OSPOUTTOKEN", result.token); + ast_debug(1, "OSPLookup: OSPOUTTOKEN size '%zd'\n", strlen(result.token)); + snprintf(buffer, sizeof(buffer), "%d", result.numresults); + pbx_builtin_setvar_helper(chan, "OSPRESULTS", buffer); + ast_debug(1, "OSPLookup: OSPRESULTS '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", result.outtimelimit); + pbx_builtin_setvar_helper(chan, "OSPOUTTIMELIMIT", buffer); + ast_debug(1, "OSPLookup: OSPOUTTIMELIMIT '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", callidtypes); + pbx_builtin_setvar_helper(chan, "OSPOUTCALLIDTYPES", buffer); + ast_debug(1, "OSPLookup: OSPOUTCALLIDTYPES '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPLOOKUPSTATUS", status); + ast_debug(1, "OSPLookup: %s\n", status); + + if (!strcasecmp(result.tech, OSP_TECH_H323)) { + if ((callidtypes & OSP_CALLID_H323) && (result.outcallid.len != 0)) { + osp_uuid2str(result.outcallid.buf, buffer, sizeof(buffer)); + } else { + buffer[0] = '\0'; + } + pbx_builtin_setvar_helper(chan, "OSPOUTCALLID", buffer); + snprintf(buffer, sizeof(buffer), "%s/%s@%s", result.tech, result.called, result.dest); + pbx_builtin_setvar_helper(chan, "OSPDIALSTR", buffer); + } else if (!strcasecmp(result.tech, OSP_TECH_SIP)) { + snprintf(buffer, sizeof(buffer), "%s/%s@%s", result.tech, result.called, result.dest); + pbx_builtin_setvar_helper(chan, "OSPDIALSTR", buffer); + if (!ast_strlen_zero(result.token)) { + snprintf(buffer, sizeof(buffer), "%s%s", OSP_SIP_HEADER, result.token); + pbx_builtin_setvar_helper(chan, "_SIPADDHEADER", buffer); + ast_debug(1, "OSPLookup: SIPADDHEADER size '%zd'\n", strlen(buffer)); + } + } else if (!strcasecmp(result.tech, OSP_TECH_IAX)) { + snprintf(buffer, sizeof(buffer), "%s/%s/%s", result.tech, result.dest, result.called); + pbx_builtin_setvar_helper(chan, "OSPDIALSTR", buffer); + } + + if ((cres = ast_autoservice_stop(chan)) < 0) { + return -1; + } + + if(res <= 0) { + res = -1; + } else { + res = 0; + } + + 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; + const char* provider = OSP_DEF_PROVIDER; + int cause = 0; + struct varshead* headp; + struct ast_var_t* current; + struct osp_result result; + char buffer[OSP_TOKSTR_SIZE]; + unsigned int callidtypes = OSP_CALLID_UNDEFINED; + const char* status; + char* tmp; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(cause); + AST_APP_ARG(provider); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "OSPNext: Arg required, OSPNext(cause[|provider[|options]])\n"); + return -1; + } + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + if (!ast_strlen_zero(args.cause) && sscanf(args.cause, "%d", &cause) != 1) { + cause = 0; + } + ast_debug(1, "OSPNext: cause '%d'\n", cause); + + if (!ast_strlen_zero(args.provider)) { + provider = args.provider; + } + ast_debug(1, "OSPlookup: provider '%s'\n", provider); + + 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), "OSPOUTCALLIDTYPES")) { + if (sscanf(ast_var_value(current), "%d", &callidtypes) != 1) { + callidtypes = OSP_CALLID_UNDEFINED; + } + } else if (!strcasecmp(ast_var_name(current), "OSPRESULTS")) { + if (sscanf(ast_var_value(current), "%d", &result.numresults) != 1) { + result.numresults = 0; + } + } + } + ast_debug(1, "OSPNext: OSPINHANDLE '%d'\n", result.inhandle); + ast_debug(1, "OSPNext: OSPOUTHANDLE '%d'\n", result.outhandle); + ast_debug(1, "OSPNext: OSPINTIMELIMIT '%d'\n", result.intimelimit); + ast_debug(1, "OSPNext: OSPOUTCALLIDTYPES '%d'\n", callidtypes); + ast_debug(1, "OSPNext: OSPRESULTS '%d'\n", result.numresults); + + if ((res = osp_next(provider, cause, &result)) > 0) { + status = AST_OSP_SUCCESS; + } else { + result.tech[0] = '\0'; + result.dest[0] = '\0'; + result.called[0] = '\0'; + result.calling[0] = '\0'; + result.token[0] = '\0'; + result.networkid[0] = '\0'; + result.numresults = 0; + result.outtimelimit = OSP_DEF_TIMELIMIT; + result.outcallid.buf[0] = '\0'; + result.outcallid.len = 0; + if (!res) { + status = AST_OSP_FAILED; + } else { + status = AST_OSP_ERROR; + } + } + + pbx_builtin_setvar_helper(chan, "OSPTECH", result.tech); + ast_debug(1, "OSPNext: OSPTECH '%s'\n", result.tech); + pbx_builtin_setvar_helper(chan, "OSPDEST", result.dest); + ast_debug(1, "OSPNext: OSPDEST '%s'\n", result.dest); + pbx_builtin_setvar_helper(chan, "OSPCALLED", result.called); + ast_debug(1, "OSPNext: OSPCALLED'%s'\n", result.called); + pbx_builtin_setvar_helper(chan, "OSPCALLING", result.calling); + ast_debug(1, "OSPNext: OSPCALLING '%s'\n", result.calling); + pbx_builtin_setvar_helper(chan, "OSPOUTTOKEN", result.token); + ast_debug(1, "OSPNext: OSPOUTTOKEN size '%zd'\n", strlen(result.token)); + snprintf(buffer, sizeof(buffer), "%d", result.numresults); + pbx_builtin_setvar_helper(chan, "OSPRESULTS", buffer); + ast_debug(1, "OSPNext: OSPRESULTS '%s'\n", buffer); + snprintf(buffer, sizeof(buffer), "%d", result.outtimelimit); + pbx_builtin_setvar_helper(chan, "OSPOUTTIMELIMIT", buffer); + ast_debug(1, "OSPNext: OSPOUTTIMELIMIT '%s'\n", buffer); + pbx_builtin_setvar_helper(chan, "OSPNEXTSTATUS", status); + ast_debug(1, "OSPNext: %s\n", status); + + if (!strcasecmp(result.tech, OSP_TECH_H323)) { + if ((callidtypes & OSP_CALLID_H323) && (result.outcallid.len != 0)) { + osp_uuid2str(result.outcallid.buf, buffer, sizeof(buffer)); + } else { + buffer[0] = '\0'; + } + pbx_builtin_setvar_helper(chan, "OSPOUTCALLID", buffer); + snprintf(buffer, sizeof(buffer), "%s/%s@%s", result.tech, result.called, result.dest); + pbx_builtin_setvar_helper(chan, "OSPDIALSTR", buffer); + } else if (!strcasecmp(result.tech, OSP_TECH_SIP)) { + snprintf(buffer, sizeof(buffer), "%s/%s@%s", result.tech, result.called, result.dest); + pbx_builtin_setvar_helper(chan, "OSPDIALSTR", buffer); + if (!ast_strlen_zero(result.token)) { + snprintf(buffer, sizeof(buffer), "%s%s", OSP_SIP_HEADER, result.token); + pbx_builtin_setvar_helper(chan, "_SIPADDHEADER", buffer); + ast_debug(1, "OSPLookup: SIPADDHEADER size '%zd'\n", strlen(buffer)); + } + } else if (!strcasecmp(result.tech, OSP_TECH_IAX)) { + snprintf(buffer, sizeof(buffer), "%s/%s/%s", result.tech, result.dest, result.called); + pbx_builtin_setvar_helper(chan, "OSPDIALSTR", buffer); + } + + if(res <= 0) { + res = -1; + } else { + res = 0; + } + + 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; + 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); + ); + + if (!(tmp = ast_strdupa(data))) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, tmp); + + 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_debug(1, "OSPFinish: OSPINHANDLE '%d'\n", inhandle); + ast_debug(1, "OSPFinish: OSPOUTHANDLE '%d'\n", outhandle); + ast_debug(1, "OSPFinish: recorded '%d'\n", recorded); + + if (!ast_strlen_zero(args.cause) && sscanf(args.cause, "%d", &cause) != 1) { + cause = 0; + } + ast_debug(1, "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_debug(1, "OSPFinish: start '%ld'\n", start); + ast_debug(1, "OSPFinish: connect '%ld'\n", connect); + ast_debug(1, "OSPFinish: end '%ld'\n", end); + + release = ast_check_hangup(chan) ? 0 : 1; + + if (osp_finish(outhandle, recorded, cause, start, connect, end, release) <= 0) { + ast_debug(1, "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_debug(1, "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) { + res = -1; + } else { + res = 0; + } + + return res; +} + +/* OSP Module APIs */ + +static int osp_unload(void); +static int osp_load(int reload) +{ + const char* t; + unsigned int v; + struct ast_config* cfg; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + int error = OSPC_ERR_NO_ERROR; + + if ((cfg = ast_config_load(OSP_CONFIG_FILE, config_flags)) == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + if (cfg) { + if (reload) + osp_unload(); + + 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_debug(1, "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_debug(1, "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_debug(1, "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); + ast_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 char *handle_cli_osp_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + int i; + int found = 0; + struct osp_provider* p; + const char* provider = NULL; + const char* tokenalgo; + + switch (cmd) { + case CLI_INIT: + e->command = "osp show"; + e->usage = + "Usage: osp show\n" + " Displays information on Open Settlement Protocol support\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if ((a->argc < 2) || (a->argc > 3)) + return CLI_SHOWUSAGE; + if (a->argc > 2) + provider = a->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(a->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(a->fd, "\n"); + } + ast_cli(a->fd, " == OSP Provider '%s' == \n", p->name); + ast_cli(a->fd, "Local Private Key: %s\n", p->privatekey); + ast_cli(a->fd, "Local Certificate: %s\n", p->localcert); + for (i = 0; i < p->cacount; i++) { + ast_cli(a->fd, "CA Certificate %d: %s\n", i + 1, p->cacerts[i]); + } + for (i = 0; i < p->spcount; i++) { + ast_cli(a->fd, "Service Point %d: %s\n", i + 1, p->srvpoints[i]); + } + ast_cli(a->fd, "Max Connections: %d\n", p->maxconnections); + ast_cli(a->fd, "Retry Delay: %d seconds\n", p->retrydelay); + ast_cli(a->fd, "Retry Limit: %d\n", p->retrylimit); + ast_cli(a->fd, "Timeout: %d milliseconds\n", p->timeout); + ast_cli(a->fd, "Source: %s\n", strlen(p->source) ? p->source : ""); + ast_cli(a->fd, "Auth Policy %d\n", p->authpolicy); + ast_cli(a->fd, "Default protocol %s\n", p->defaultprotocol); + ast_cli(a->fd, "OSP Handle: %d\n", p->handle); + found++; + } + p = p->next; + } + ast_mutex_unlock(&osplock); + + if (!found) { + if (provider) { + ast_cli(a->fd, "Unable to find OSP provider '%s'\n", provider); + } else { + ast_cli(a->fd, "No OSP providers configured\n"); + } + } + return CLI_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" +"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" +" ${OSPCALLED}: The called number to use for the call\n" +" ${OSPCALLING}: The calling number to use for the call\n" +" ${OSPDIALSTR}: The dial command string\n" +" ${OSPOUTTOKEN}: The actual OSP token as a string\n" +" ${OSPOUTTIMELIMIT}: The outbound call duration limit in seconds\n" +" ${OSPOUTCALLIDTYPES}: The outbound call id types\n" +" ${OSPOUTCALLID}: The outbound call id\n" +" ${OSPRESULTS}: The number of OSP results total remaining\n" +"\n" +"The option string may contain the following character:\n" +" 'h' -- generate H323 call id for the outbound call\n" +" 's' -- generate SIP call id for the outbound call. Have not been implemented\n" +" 'i' -- generate IAX call id for the outbound call. Have not been implemented\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[,provider[,options]]): Looks up the next OSP Destination for ${OSPOUTHANDLE}\n" +"See OSPLookup for more information\n" +"\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" +"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 struct ast_cli_entry cli_osp[] = { + AST_CLI_DEFINE(handle_cli_osp_show, "Displays OSF information") +}; + +static int load_module(void) +{ + int res; + + if (!osp_load(0)) + 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(); + + return res; +} + +static int reload(void) +{ + osp_load(1); + + 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/trunk/apps/app_page.c b/trunk/apps/app_page.c new file mode 100644 index 0000000000..2ea367edde --- /dev/null +++ b/trunk/apps/app_page.c @@ -0,0 +1,193 @@ +/* + * 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 + zaptel + app_meetme + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#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/devicestate.h" +#include "asterisk/dial.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" +" s - only dial channel if devicestate says it is not in use\n"; + +enum { + PAGE_DUPLEX = (1 << 0), + PAGE_QUIET = (1 << 1), + PAGE_RECORD = (1 << 2), + PAGE_SKIP = (1 << 3), +} 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), + AST_APP_OPTION('s', PAGE_SKIP), +}); + +#define MAX_DIALS 128 + +static int page_exec(struct ast_channel *chan, void *data) +{ + char *options, *tech, *resource, *tmp; + char meetmeopts[88], originator[AST_CHANNEL_NAME], *opts[0]; + 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; + } + + if (!(app = pbx_findapp("MeetMe"))) { + ast_log(LOG_WARNING, "There is no MeetMe application available!\n"); + 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, opts, 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, "&"))) { + int state = 0; + 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; + } + + /* Ensure device is not in use if skip option is enabled */ + if (ast_test_flag(&flags, PAGE_SKIP) && (state = ast_device_state(tech)) != AST_DEVICE_NOT_INUSE) { + ast_log(LOG_WARNING, "Destination '%s' has device state '%s'.\n", tech, devstate2str(state)); + 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); + } + + return -1; +} + +static int unload_module(void) +{ + return ast_unregister_application(app_page); +} + +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/trunk/apps/app_parkandannounce.c b/trunk/apps/app_parkandannounce.c new file mode 100644 index 0000000000..7ad7a6416b --- /dev/null +++ b/trunk/apps/app_parkandannounce.c @@ -0,0 +1,183 @@ +/* + * 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$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/features.h" +#include "asterisk/say.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/app.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) +{ + int res = -1; + int lot, timeout = 0, dres; + char *dialtech, *tmp[100], buf[13]; + int looptemp, i; + char *s; + + struct ast_channel *dchan; + struct outgoing_helper oh = { 0, }; + int outstate; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(template); + AST_APP_ARG(timeout); + AST_APP_ARG(dial); + AST_APP_ARG(return_context); + ); + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "ParkAndAnnounce requires arguments: (announce:template|timeout|dial|[return_context])\n"); + return -1; + } + + s = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, s); + + if (args.timeout) + timeout = atoi(args.timeout) * 1000; + + if (ast_strlen_zero(args.dial)) { + ast_log(LOG_WARNING, "PARK: A dial resource must be specified i.e: Console/dsp or Zap/g1/5551212\n"); + return -1; + } + + dialtech = strsep(&args.dial, "/"); + ast_verb(3, "Dial Tech,String: (%s,%s)\n", dialtech, args.dial); + + if (!ast_strlen_zero(args.return_context)) + ast_parseable_goto(chan, args.return_context); + + ast_verb(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_verb(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. */ + + ast_masq_park_call(chan, NULL, timeout, &lot); + + ast_verb(3, "Call Parking Called, lot: %d, timeout: %d, context: %s\n", lot, timeout, args.return_context); + + /* Now place the call to the extension */ + + snprintf(buf, sizeof(buf), "%d", lot); + oh.parent_channel = chan; + oh.vars = ast_variable_new("_PARKEDAT", buf, ""); + dchan = __ast_request_and_dial(dialtech, AST_FORMAT_SLINEAR, args.dial, 30000, &outstate, chan->cid.cid_num, chan->cid.cid_name, &oh); + + if (dchan) { + if (dchan->_state == AST_STATE_UP) { + ast_verb(4, "Channel %s was answered.\n", dchan->name); + } else { + ast_verb(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); + return -1; + } + } else { + ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n"); + return -1; + } + + ast_stopstream(dchan); + + /* now we have the call placed and are ready to play stuff to it */ + + ast_verb(4, "Announce Template:%s\n", args.template); + + for (looptemp = 0, tmp[looptemp++] = strsep(&args.template, ":"); + looptemp < sizeof(tmp) / sizeof(tmp[0]); + tmp[looptemp++] = strsep(&args.template, ":")); + + for (i = 0; i < looptemp; i++) { + ast_verb(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); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +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/trunk/apps/app_pickupchan.c b/trunk/apps/app_pickupchan.c new file mode 100644 index 0000000000..6c3c4e3eff --- /dev/null +++ b/trunk/apps/app_pickupchan.c @@ -0,0 +1,174 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2008, Gary Cook + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free 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 Pickup a ringing channel + * + * \author Gary Cook + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#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" + +static const char *app = "PickupChan"; +static const char *synopsis = "Pickup a ringing channel"; +static const char *descrip = +" PickupChan(channel[&channel...]): This application can pickup any ringing channel\n"; + +/*! \todo This application should return a result code, like PICKUPRESULT */ + +/*! \brief Helper function that determines whether a channel is capable of being picked up */ +static int can_pickup(struct ast_channel *chan) +{ + ast_debug(3, "Checking Pickup '%s' state '%s ( %d )'\n", chan->name, ast_state2str(chan->_state), chan->_state); + + if (!chan->pbx && (chan->_state == AST_STATE_RINGING || chan->_state == AST_STATE_RING)) { + return 1; + } else { + return 0; + } +} + +/*! \brief Helper Function to walk through ALL channels checking NAME and STATE */ +static struct ast_channel *my_ast_get_channel_by_name_locked(char *channame) +{ + struct ast_channel *chan; + char *chkchan = alloca(strlen(channame) + 2); + + /* need to append a '-' for the comparison so we check full channel name, + * i.e SIP/hgc- , use a temporary variable so original stays the same for + * debugging. + */ + strcpy(chkchan, channame); + strcat(chkchan, "-"); + + for (chan = ast_walk_channel_by_name_prefix_locked(NULL, channame, strlen(channame)); + chan; + chan = ast_walk_channel_by_name_prefix_locked(chan, channame, strlen(channame))) { + if (!strncasecmp(chan->name, chkchan, strlen(chkchan)) && can_pickup(chan)) + return chan; + ast_channel_unlock(chan); + } + return NULL; +} + +/*! \brief Perform actual pickup between two channels */ +static int pickup_do(struct ast_channel *chan, struct ast_channel *target) +{ + int res = 0; + + ast_debug(3, "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; +} + +/*! \brief Attempt to pick up specified channel named , does not use context */ +static int pickup_by_channel(struct ast_channel *chan, char *pickup) +{ + int res = 0; + struct ast_channel *target; + + if (!(target = my_ast_get_channel_by_name_locked(pickup))) + return -1; + + /* Just check that we are not picking up the SAME as target */ + if (chan->name != target->name && chan != target) { + res = pickup_do(chan, target); + ast_channel_unlock(target); + } + + return res; +} + +/*! \brief Main application entry point */ +static int pickupchan_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_module_user *u = NULL; + char *tmp = ast_strdupa(data); + char *pickup = NULL, *context = NULL; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Pickup requires an argument (channel)!\n"); + return -1; + } + + u = ast_module_user_add(chan); + + /* Parse channel (and ignore context if there) */ + while (!ast_strlen_zero(tmp) && (pickup = strsep(&tmp, "&"))) { + if ((context = strchr(pickup , '@'))) { + *context++ = '\0'; + } + if (!strncasecmp(chan->name, pickup , strlen(pickup))) { + ast_log(LOG_NOTICE, "Cannot pickup your own channel %s.\n", pickup); + } else { + if (!pickup_by_channel(chan, pickup)) { + break; + } + ast_log(LOG_NOTICE, "No target channel found for %s.\n", pickup); + } + } + + ast_module_user_remove(u); + + return res; +} + +static int unload_module(void) +{ + return ast_unregister_application(app); +} + +static int load_module(void) +{ + return ast_register_application(app, pickupchan_exec, synopsis, descrip); +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Channel Pickup Application"); diff --git a/trunk/apps/app_playback.c b/trunk/apps/app_playback.c new file mode 100644 index 0000000000..21b3ab3eba --- /dev/null +++ b/trunk/apps/app_playback.c @@ -0,0 +1,524 @@ +/* + * 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$") + +#include "asterisk/file.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/app.h" +/* This file provides config-file based 'say' functions, and implenents + * some CLI commands. + */ +#include "asterisk/say.h" /* provides config-file based 'say' functions */ +#include "asterisk/cli.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 comma.\n" +"The 'skip' 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.\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" +; + + +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; + + ast_debug(2, "string <%s> depth <%d>\n", s, depth); + 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 */ + ast_debug(2, "try <%s> in <%s>\n", s, a->language); + 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; + ast_debug(2, "value is <%s>\n", s); + 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 */ + pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn)); + ast_debug(2, "doing [%s]\n", 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 ast_tm tm; + struct timeval tv = { t, 0 }; + say_args_t a = { chan, ints, lang, -1, -1 }; + if (format == NULL) + format = ""; + + ast_localtime(&tv, &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_mode(const char *mode) { + if (!strcmp(mode, say_new)) { + if (say_cfg == NULL) { + ast_log(LOG_ERROR, "There is no say.conf file to use new mode\n"); + return -1; + } + 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 if (strcmp(mode, say_old)) { + ast_log(LOG_WARNING, "unrecognized mode %s\n", mode); + return -1; + } + + return 0; +} + +static char *__say_cli_init(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + const char *old_mode = say_api_buf[0] ? say_new : say_old; + char *mode; + switch (cmd) { + case CLI_INIT: + e->command = "say load [new|old]"; + e->usage = + "Usage: say load [new|old]\n" + " say load\n" + " Report status of current say mode\n" + " say load new\n" + " Set say method, configured in say.conf\n" + " say load old\n" + " Set old say method, coded in asterisk core\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + if (a->argc == 2) { + ast_cli(a->fd, "say mode is [%s]\n", old_mode); + return CLI_SUCCESS; + } else if (a->argc != 3) + return CLI_SHOWUSAGE; + mode = a->argv[2]; + if (!strcmp(mode, old_mode)) + ast_cli(a->fd, "say mode is %s already\n", mode); + else + if (say_init_mode(mode) == 0) + ast_cli(a->fd, "setting say mode from %s to %s\n", old_mode, mode); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_playback[] = { + AST_CLI_DEFINE(__say_cli_init, "Set or show the say mode"), +}; + +static int playback_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + int mres = 0; + char *tmp; + int option_skip=0; + int option_say=0; + int option_noanswer = 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); + 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 (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); + res = 0; + mres = 1; + } + } + } +done: + pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS"); + return res; +} + +static int reload(void) +{ + struct ast_variable *v; + struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED }; + struct ast_config *newcfg; + + if ((newcfg = ast_config_load("say.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + if (say_cfg) { + ast_config_destroy(say_cfg); + ast_log(LOG_NOTICE, "Reloading say.conf\n"); + say_cfg = newcfg; + } + + if (say_cfg) { + for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) { + if (ast_extension_match(v->name, "mode")) { + say_init_mode(v->value); + break; + } + } + } + + /* + * 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)); + + if (say_cfg) + ast_config_destroy(say_cfg); + + return res; +} + +static int load_module(void) +{ + struct ast_variable *v; + struct ast_flags config_flags = { 0 }; + + say_cfg = ast_config_load("say.conf", config_flags); + if (say_cfg) { + for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) { + if (ast_extension_match(v->name, "mode")) { + say_init_mode(v->value); + break; + } + } + } + + 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/trunk/apps/app_privacy.c b/trunk/apps/app_privacy.c new file mode 100644 index 0000000000..dacfd704c6 --- /dev/null +++ b/trunk/apps/app_privacy.c @@ -0,0 +1,173 @@ +/* + * 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$") + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/utils.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" + +static char *app = "PrivacyManager"; + +static char *synopsis = "Require phone number to be entered, if no CallerID sent"; + +static char *descrip = + " PrivacyManager([maxretries][,minlength]): 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 'maxretries' attempts to do so.\n" + "The application does nothing if Caller*ID was received on the channel.\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" + "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; + char phone[30]; + char *parse = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(maxretries); + AST_APP_ARG(minlength); + AST_APP_ARG(options); + ); + + 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) { + if ((res = ast_answer(chan))) + 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"); + } + + } + + /* 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 { + pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "FAILED"); + } + } + + return 0; +} + +static int unload_module(void) +{ + return ast_unregister_application (app); +} + +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/trunk/apps/app_queue.c b/trunk/apps/app_queue.c new file mode 100644 index 0000000000..a2db787a7e --- /dev/null +++ b/trunk/apps/app_queue.c @@ -0,0 +1,6153 @@ +/* + * 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$") + +#include +#include +#include + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.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/event.h" +#include "asterisk/astobj2.h" +#include "asterisk/strings.h" +#include "asterisk/global_datastores.h" + +enum { + QUEUE_STRATEGY_RINGALL = 0, + QUEUE_STRATEGY_LEASTRECENT, + QUEUE_STRATEGY_FEWESTCALLS, + QUEUE_STRATEGY_RANDOM, + QUEUE_STRATEGY_RRMEMORY, + QUEUE_STRATEGY_LINEAR, + QUEUE_STRATEGY_WRANDOM +}; + +static struct strategy { + int strategy; + char *name; +} strategies[] = { + { QUEUE_STRATEGY_RINGALL, "ringall" }, + { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" }, + { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, + { QUEUE_STRATEGY_RANDOM, "random" }, + { QUEUE_STRATEGY_RRMEMORY, "rrmemory" }, + { QUEUE_STRATEGY_LINEAR, "linear" }, + { QUEUE_STRATEGY_WRANDOM, "wrandom"}, +}; + +#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 DEFAULT_MIN_ANNOUNCE_FREQUENCY 15 /* The minimum number of seconds between position announcements + The default value of 15 provides backwards compatibility */ +#define MAX_QUEUE_BUCKETS 53 + +#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][,macro][,gosub][,rule]):\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" +" 'c' -- continue in the dialplan if the callee hangs up.\n" +" 'd' -- data-quality (modem) call (minimum delay).\n" +" 'h' -- allow callee to hang up by pressing *.\n" +" 'H' -- allow caller to hang up by pressing *.\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. Periodic Announcements are still made, if applicable.\n" +" 't' -- allow the called user to transfer the calling user.\n" +" 'T' -- 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" +" '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" +" 'x' -- allow the called user to write the conversation to disk via MixMonitor\n" +" 'X' -- allow the calling user to write the conversation to disk via MixMonitor\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 optional macro parameter will run a macro on the \n" +"calling party's channel once they are connected to a queue member.\n" +" The optional gosub parameter will run a gosub on the \n" +"calling party's channel once they are connected to a queue member.\n" +" The optional rule parameter will cause the queue's defaultrule to be\n" +"overridden by the rule specified.\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 | CONTINUE\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 it will return an error.\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 it will return an error.\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[,reason]]):\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. The application will fail if the interface is not found.\n" +"The reason string is entirely optional and is used to add extra information\n" +"to the appropriate queue_log entries and manager events.\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[,reason]]):\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 reason string is entirely optional and is used to add extra information\n" +"to the appropriate queue_log entries and manager events.\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_keep_stats = 0; + +/*! \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; + +/*! \brief queues.conf [general] option */ +static int shared_lastcall = 0; + +/*! \brief Subscription to device state change events */ +static struct ast_event_sub *device_state_sub; + +/*! \brief queues.conf [general] option */ +static int update_cdr = 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, + QUEUE_CONTINUE = 7, +}; + +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" }, + { QUEUE_CONTINUE, "CONTINUE" }, +}; + +/*! \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 call_queue *lastqueue; + 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 */ + int min_penalty; /*!< Limit the members that can take this call to this penalty or higher */ + int linpos; /*!< If using linear strategy, what position are we at? */ + int linwrapped; /*!< Is the linpos wrapped? */ + 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 */ + AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */ + struct penalty_rule *pr; /*!< Pointer to the next penalty rule to implement */ + struct queue_ent *next; /*!< The next queue entry */ +}; + +struct member { + char interface[80]; /*!< Technology/Location to dial to reach this member*/ + char state_interface[80]; /*!< Technology/Location from which to read devicestate changes */ + 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 */ + struct call_queue *lastqueue; /*!< Last queue we received a call */ + 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 QUEUE_EMPTY_LOOSE 3 +#define ANNOUNCEHOLDTIME_ALWAYS 1 +#define ANNOUNCEHOLDTIME_ONCE 2 +#define QUEUE_EVENT_VARIABLES 3 + +struct penalty_rule { + int time; /*!< Number of seconds that need to pass before applying this rule */ + int max_value; /*!< The amount specified in the penalty rule for max penalty */ + int min_value; /*!< The amount specified in the penalty rule for min penalty */ + int max_relative; /*!< Is the max adjustment relative? 1 for relative, 0 for absolute */ + int min_relative; /*!< Is the min adjustment relative? 1 for relative, 0 for absolute */ + AST_LIST_ENTRY(penalty_rule) list; /*!< Next penalty_rule */ +}; + +struct call_queue { + AST_DECLARE_STRING_FIELDS( + /*! Queue name */ + AST_STRING_FIELD(name); + /*! Music on Hold class */ + AST_STRING_FIELD(moh); + /*! Announcement to play when call is answered */ + AST_STRING_FIELD(announce); + /*! Exit context */ + AST_STRING_FIELD(context); + /*! Macro to run upon member connection */ + AST_STRING_FIELD(membermacro); + /*! Gosub to run upon member connection */ + AST_STRING_FIELD(membergosub); + /*! Default rule to use if none specified in call to Queue() */ + AST_STRING_FIELD(defaultrule); + /*! Sound file: "Your call is now first in line" (def. queue-youarenext) */ + AST_STRING_FIELD(sound_next); + /*! Sound file: "There are currently" (def. queue-thereare) */ + AST_STRING_FIELD(sound_thereare); + /*! Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting) */ + AST_STRING_FIELD(sound_calls); + /*! Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */ + AST_STRING_FIELD(sound_holdtime); + /*! Sound file: "minutes." (def. queue-minutes) */ + AST_STRING_FIELD(sound_minutes); + /*! Sound file: "less-than" (def. queue-lessthan) */ + AST_STRING_FIELD(sound_lessthan); + /*! Sound file: "seconds." (def. queue-seconds) */ + AST_STRING_FIELD(sound_seconds); + /*! Sound file: "Thank you for your patience." (def. queue-thankyou) */ + AST_STRING_FIELD(sound_thanks); + /*! Sound file: Custom announce for caller, no default */ + AST_STRING_FIELD(sound_callerannounce); + /*! Sound file: "Hold time" (def. queue-reporthold) */ + AST_STRING_FIELD(sound_reporthold); + ); + /*! Sound files: Custom announce, no default */ + struct ast_str *sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS]; + 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 setqueuevar:1; + unsigned int setqueueentryvar:1; + unsigned int reportholdtime:1; + unsigned int wrapped:1; + unsigned int timeoutrestart:1; + unsigned int announceholdtime:2; + unsigned int announceposition:1; + int strategy:4; + unsigned int maskmemberstatus:1; + unsigned int realtime:1; + unsigned int found:1; + int announcefrequency; /*!< How often to announce their position */ + int minannouncefrequency; /*!< The minimum number of seconds between position announcements (def. 15) */ + int periodicannouncefrequency; /*!< How often to play periodic announcement */ + int roundingseconds; /*!< How many seconds do we round to? */ + int holdtime; /*!< Current avg holdtime, based on recursive boxcar filter */ + 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 */ + 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 */ + AST_LIST_HEAD_NOLOCK(, penalty_rule) rules; /*!< The list of penalty rules to invoke */ +}; + +struct rule_list { + char name[80]; + AST_LIST_HEAD_NOLOCK(,penalty_rule) rules; + AST_LIST_ENTRY(rule_list) list; +}; + +AST_LIST_HEAD_STATIC(rule_lists, rule_list); + +static struct ao2_container *queues; + +static void update_realtime_members(struct call_queue *q); +static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused); + +/*! \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; +} + +static int queue_hash_cb(const void *obj, const int flags) +{ + const struct call_queue *q = obj; + return ast_str_hash(q->name); +} + +static int queue_cmp_cb(void *obj, void *arg, int flags) +{ + struct call_queue *q = obj, *q2 = arg; + return !strcasecmp(q->name, q2->name) ? CMP_MATCH : 0; +} + +static inline struct call_queue *queue_ref(struct call_queue *q) +{ + ao2_ref(q, 1); + return q; +} + +static inline struct call_queue *queue_unref(struct call_queue *q) +{ + ao2_ref(q, -1); + return q; +} + +static void set_queue_variables(struct queue_ent *qe) +{ + + char interfacevar[256]=""; + float sl = 0; + + if (qe->parent->setqueuevar) { + sl = 0; + if (qe->parent->callscompleted > 0) + sl = 100 * ((float) qe->parent->callscompletedinsl / (float) qe->parent->callscompleted); + + snprintf(interfacevar,sizeof(interfacevar), + "QUEUEMAX=%d|QUEUESTRATEGY=%s|QUEUECALLS=%d|QUEUEHOLDTIME=%d|QUEUECOMPLETED=%d|QUEUEABANDONED=%d|QUEUESRVLEVEL=%d|QUEUESRVLEVELPERF=%2.1f", + qe->parent->maxlen, int2strat(qe->parent->strategy), qe->parent->count, qe->parent->holdtime, qe->parent->callscompleted, + qe->parent->callsabandoned, qe->parent->servicelevel, sl); + + pbx_builtin_setvar(qe->chan, interfacevar); + } +} + +/*! \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_NO_UNPAUSED_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, int min_penalty) +{ + struct member *member; + struct ao2_iterator mem_iter; + enum queue_member_status result = QUEUE_NO_MEMBERS; + + ao2_lock(q); + mem_iter = ao2_iterator_init(q->members, 0); + for (; (member = ao2_iterator_next(&mem_iter)); ao2_ref(member, -1)) { + if ((max_penalty && (member->penalty > max_penalty)) || (min_penalty && (member->penalty < min_penalty))) + continue; + + switch (member->status) { + case AST_DEVICE_INVALID: + /* nothing to do */ + break; + case AST_DEVICE_UNAVAILABLE: + if (result != QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS) + result = QUEUE_NO_REACHABLE_MEMBERS; + break; + default: + if (member->paused) { + result = QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS; + } else { + ao2_unlock(q); + ao2_ref(member, -1); + return QUEUE_NORMAL; + } + break; + } + } + + ao2_unlock(q); + return result; +} + +struct statechange { + AST_LIST_ENTRY(statechange) entry; + int state; + char dev[0]; +}; +/*! \brief set a member's status based on device state of that member's state_interface*/ +static void *handle_statechange(struct statechange *sc) +{ + struct call_queue *q; + struct member *cur; + struct ao2_iterator mem_iter; + struct member_interface *curint; + struct ao2_iterator queue_iter; + 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) { + ast_debug(3, "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; + } + + ast_debug(1, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state)); + queue_iter = ao2_iterator_init(queues, 0); + while ((q = ao2_iterator_next(&queue_iter))) { + ao2_lock(q); + mem_iter = ao2_iterator_init(q->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + char *interface; + char *slash_pos; + interface = ast_strdupa(cur->state_interface); + if ((slash_pos = strchr(interface, '/'))) + if ((slash_pos = strchr(slash_pos + 1, '/'))) + *slash_pos = '\0'; + + if (strcasecmp(sc->dev, interface)) { + ao2_ref(cur, -1); + continue; + } + + if (cur->status != sc->state) { + cur->status = sc->state; + 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); + } + queue_unref(q); + ao2_unlock(q); + } + + 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); + + ast_free(sc); + sc = NULL; + } + + if (sc) + ast_free(sc); + + while ((sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) + ast_free(sc); + + return NULL; +} +/*! \brief Producer of the statechange queue */ +static int statechange_queue(const char *dev, enum ast_device_state state) +{ + 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; +} +static void device_state_cb(const struct ast_event *event, void *unused) +{ + enum ast_device_state state; + const char *device; + + state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE); + device = ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE); + + if (ast_strlen_zero(device)) { + ast_log(LOG_ERROR, "Received invalid event that had no device IE\n"); + return; + } + + statechange_queue(device, state); +} + +/*! \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, const char *state_interface) +{ + 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(state_interface)) + ast_copy_string(cur->state_interface, state_interface, sizeof(cur->state_interface)); + else + ast_copy_string(cur->state_interface, interface, sizeof(cur->state_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(cur->state_interface); + } + + return cur; +} + + +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 strcasecmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH; +} + +static void init_queue(struct call_queue *q) +{ + int i; + struct penalty_rule *pr_iter; + + q->dead = 0; + q->retry = DEFAULT_RETRY; + q->timeout = -1; + q->maxlen = 0; + q->announcefrequency = 0; + q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY; + q->announceholdtime = 0; + q->announceholdtime = 1; + q->roundingseconds = 0; /* Default - don't announce seconds */ + q->servicelevel = 0; + q->ringinuse = 1; + q->setinterfacevar = 0; + q->setqueuevar = 0; + q->setqueueentryvar = 0; + q->autofill = autofill_default; + q->montype = montype_default; + q->monfmt[0] = '\0'; + q->reportholdtime = 0; + q->wrapuptime = 0; + q->autofill = 0; + q->joinempty = 0; + q->leavewhenempty = 0; + q->memberdelay = 0; + q->maskmemberstatus = 0; + q->eventwhencalled = 0; + q->weight = 0; + q->timeoutrestart = 0; + q->periodicannouncefrequency = 0; + if (!q->members) { + if (q->strategy == QUEUE_STRATEGY_LINEAR) + /* linear strategy depends on order, so we have to place all members in a single bucket */ + q->members = ao2_container_alloc(1, member_hash_fn, member_cmp_fn); + else + q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn); + } + q->membercount = 0; + q->found = 1; + + ast_string_field_set(q, sound_next, "queue-youarenext"); + ast_string_field_set(q, sound_thereare, "queue-thereare"); + ast_string_field_set(q, sound_calls, "queue-callswaiting"); + ast_string_field_set(q, sound_holdtime, "queue-holdtime"); + ast_string_field_set(q, sound_minutes, "queue-minutes"); + ast_string_field_set(q, sound_seconds, "queue-seconds"); + ast_string_field_set(q, sound_thanks, "queue-thankyou"); + ast_string_field_set(q, sound_lessthan, "queue-less-than"); + ast_string_field_set(q, sound_reporthold, "queue-reporthold"); + + if ((q->sound_periodicannounce[0] = ast_str_create(32))) + ast_str_set(&q->sound_periodicannounce[0], 0, "queue-periodic-announce"); + + for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) { + if (q->sound_periodicannounce[i]) + ast_str_set(&q->sound_periodicannounce[i], 0, "%s", ""); + } + + while ((pr_iter = AST_LIST_REMOVE_HEAD(&q->rules,list))) + ast_free(pr_iter); +} + +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; + } + + ast_debug(1, "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; + struct ao2_iterator queue_iter, mem_iter; + int ret = 0; + + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + queue_iter = ao2_iterator_init(queues, 0); + while ((q = ao2_iterator_next(&queue_iter))) { + ao2_lock(q); + mem_iter = ao2_iterator_init(q->members, 0); + while ((mem = ao2_iterator_next(&mem_iter))) { + if (!strcasecmp(mem->state_interface, interface)) { + ao2_ref(mem, -1); + ret = 1; + break; + } + } + ao2_unlock(q); + queue_unref(q); + } + + return ret; +} + +static int remove_from_interfaces(const char *interface) +{ + struct member_interface *curint; + + AST_LIST_LOCK(&interfaces); + AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) { + if (!strcasecmp(curint->interface, interface)) { + if (!interface_exists_global(interface)) { + ast_debug(1, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface); + AST_LIST_REMOVE_CURRENT(list); + ast_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))) + ast_free(curint); + AST_LIST_UNLOCK(&interfaces); +} + +/*Note: call this with the rule_lists locked */ +static int insert_penaltychange (const char *list_name, const char *content, const int linenum) +{ + char *timestr, *maxstr, *minstr, *contentdup; + struct penalty_rule *rule = NULL, *rule_iter; + struct rule_list *rl_iter; + int time, inserted = 0; + + if (!(rule = ast_calloc(1, sizeof(*rule)))) { + ast_log(LOG_ERROR, "Cannot allocate memory for penaltychange rule at line %d!\n", linenum); + return -1; + } + + contentdup = ast_strdupa(content); + + if (!(maxstr = strchr(contentdup, ','))) { + ast_log(LOG_WARNING, "Improperly formatted penaltychange rule at line %d. Ignoring.\n", linenum); + ast_free(rule); + return -1; + } + + *maxstr++ = '\0'; + timestr = contentdup; + + if ((time = atoi(timestr)) < 0) { + ast_log(LOG_WARNING, "Improper time parameter specified for penaltychange rule at line %d. Ignoring.\n", linenum); + ast_free(rule); + return -1; + } + + rule->time = time; + + if ((minstr = strchr(maxstr,','))) + *minstr++ = '\0'; + + /* The last check will evaluate true if either no penalty change is indicated for a given rule + * OR if a min penalty change is indicated but no max penalty change is */ + if (*maxstr == '+' || *maxstr == '-' || *maxstr == '\0') { + rule->max_relative = 1; + } + + rule->max_value = atoi(maxstr); + + if (!ast_strlen_zero(minstr)) { + if (*minstr == '+' || *minstr == '-') + rule->min_relative = 1; + rule->min_value = atoi(minstr); + } else /*there was no minimum specified, so assume this means no change*/ + rule->min_relative = 1; + + /*We have the rule made, now we need to insert it where it belongs*/ + AST_LIST_TRAVERSE(&rule_lists, rl_iter, list){ + if (strcasecmp(rl_iter->name, list_name)) + continue; + + AST_LIST_TRAVERSE_SAFE_BEGIN(&rl_iter->rules, rule_iter, list) { + if (rule->time < rule_iter->time) { + AST_LIST_INSERT_BEFORE_CURRENT(rule, list); + inserted = 1; + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + if (!inserted) { + AST_LIST_INSERT_TAIL(&rl_iter->rules, rule, list); + } + } + + return 0; +} + +/*! \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_string_field_set(q, moh, val); + } else if (!strcasecmp(param, "announce")) { + ast_string_field_set(q, announce, val); + } else if (!strcasecmp(param, "context")) { + ast_string_field_set(q, context, val); + } 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, "setqueuevar")) { + q->setqueuevar = ast_true(val); + } else if (!strcasecmp(param, "setqueueentryvar")) { + q->setqueueentryvar = ast_true(val); + } else if (!strcasecmp(param, "monitor-format")) { + ast_copy_string(q->monfmt, val, sizeof(q->monfmt)); + } else if (!strcasecmp(param, "membermacro")) { + ast_string_field_set(q, membermacro, val); + } else if (!strcasecmp(param, "membergosub")) { + ast_string_field_set(q, membergosub, val); + } else if (!strcasecmp(param, "queue-youarenext")) { + ast_string_field_set(q, sound_next, val); + } else if (!strcasecmp(param, "queue-thereare")) { + ast_string_field_set(q, sound_thereare, val); + } else if (!strcasecmp(param, "queue-callswaiting")) { + ast_string_field_set(q, sound_calls, val); + } else if (!strcasecmp(param, "queue-holdtime")) { + ast_string_field_set(q, sound_holdtime, val); + } else if (!strcasecmp(param, "queue-minutes")) { + ast_string_field_set(q, sound_minutes, val); + } else if (!strcasecmp(param, "queue-seconds")) { + ast_string_field_set(q, sound_seconds, val); + } else if (!strcasecmp(param, "queue-lessthan")) { + ast_string_field_set(q, sound_lessthan, val); + } else if (!strcasecmp(param, "queue-thankyou")) { + ast_string_field_set(q, sound_thanks, val); + } else if (!strcasecmp(param, "queue-callerannounce")) { + ast_string_field_set(q, sound_callerannounce, val); + } else if (!strcasecmp(param, "queue-reporthold")) { + ast_string_field_set(q, sound_reporthold, val); + } else if (!strcasecmp(param, "announce-frequency")) { + q->announcefrequency = atoi(val); + } else if (!strcasecmp(param, "min-announce-frequency")) { + q->minannouncefrequency = atoi(val); + ast_debug(1, "%s=%s for queue '%s'\n", param, val, q->name); + } else if (!strcasecmp(param, "announce-round-seconds")) { + q->roundingseconds = atoi(val); + /* Rounding to any other values just doesn't make sense... */ + if (!(q->roundingseconds == 0 || q->roundingseconds == 1 || q->roundingseconds == 5 || q->roundingseconds == 10 + || q->roundingseconds == 15 || q->roundingseconds == 20 || q->roundingseconds == 30)) { + 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, "announce-position")) { + q->announceposition = ast_true(val); + } else if (!strcasecmp(param, "periodic-announce")) { + if (strchr(val, ',')) { + char *s, *buf = ast_strdupa(val); + unsigned int i = 0; + + while ((s = strsep(&buf, ",|"))) { + if (!q->sound_periodicannounce[i]) + q->sound_periodicannounce[i] = ast_str_create(16); + ast_str_set(&q->sound_periodicannounce[i], 0, s); + i++; + if (i == MAX_PERIODIC_ANNOUNCEMENTS) + break; + } + } else { + ast_str_set(&q->sound_periodicannounce[0], 0, val); + } + } 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")) { + /* We already have set this, no need to do it again */ + return; + } else if (!strcasecmp(param, "joinempty")) { + if (!strcasecmp(val, "loose")) + q->joinempty = QUEUE_EMPTY_LOOSE; + else 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, "loose")) + q->leavewhenempty = QUEUE_EMPTY_LOOSE; + else 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 (!strcasecmp(param, "defaultrule")) { + ast_string_field_set(q, defaultrule, 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, const char* state_interface) +{ + 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, state_interface))) { + m->dead = 0; + m->realtime = 1; + add_to_interfaces(state_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; + if (strcasecmp(state_interface, m->state_interface)) { + remove_from_interfaces(m->state_interface); + ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface)); + add_to_interfaces(m->state_interface); + } + 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->state_interface); + q->membercount--; + } + ao2_ref(cur, -1); + } +} + +static void destroy_queue(void *obj) +{ + struct call_queue *q = obj; + int i; + + ast_debug(0, "Queue destructor called for queue '%s'!\n", q->name); + + free_members(q, 1); + ast_string_field_free_memory(q); + for (i = 0; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) { + if (q->sound_periodicannounce[i]) + free(q->sound_periodicannounce[i]); + } + ao2_ref(q->members, -1); +} + +static struct call_queue *alloc_queue(const char *queuename) +{ + struct call_queue *q; + + if ((q = ao2_alloc(sizeof(*q), destroy_queue))) { + if (ast_string_field_init(q, 64)) { + free(q); + return NULL; + } + ast_string_field_set(q, name, queuename); + } + return 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, tmpq = { + .name = queuename, + }; + struct member *m; + struct ao2_iterator mem_iter; + char *interface = NULL; + const char *tmp_name; + char *tmp; + char tmpbuf[64]; /* Must be longer than the longest queue param name. */ + + /* Static queues override realtime. */ + if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) { + ao2_lock(q); + if (!q->realtime) { + if (q->dead) { + ao2_unlock(q); + queue_unref(q); + return NULL; + } else { + ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name); + ao2_unlock(q); + return q; + } + } + queue_unref(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_debug(1, "Queue %s not found in realtime.\n", queuename); + + q->dead = 1; + /* Delete if unused (else will be deleted when last caller leaves). */ + ao2_unlink(queues, q); + ao2_unlock(q); + queue_unref(q); + } + return NULL; + } + + /* Create a new queue if an in-core entry does not exist yet. */ + if (!q) { + if (!(q = alloc_queue(queuename))) + return NULL; + ao2_lock(q); + clear_queue(q); + q->realtime = 1; + init_queue(q); /* Ensure defaults for all parameters not set explicitly. */ + ao2_link(queues, q); + } + + 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 = tmpbuf; + while ((tmp = strchr(tmp, '_'))) + *tmp++ = '-'; + } else + tmp_name = v->name; + queue_set_param(q, tmp_name, v->value, -1, 0); + } + + /* 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"), + ast_variable_retrieve(member_config, interface, "state_interface")); + } + + /* 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); + remove_from_interfaces(m->state_interface); + q->membercount--; + } + ao2_ref(m, -1); + } + + ao2_unlock(q); + + return q; +} + +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 = NULL, tmpq = { + .name = queuename, + }; + + /* Find the queue in the in-core list first. */ + q = ao2_find(queues, &tmpq, OBJ_POINTER); + + 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; + } + } + + ao2_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); + ao2_unlock(queues); + + } else { + update_realtime_members(q); + } + 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; + + member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL); + if (!member_config) { + /*This queue doesn't have realtime members*/ + ast_debug(3, "Queue %s has no realtime members defined. No need for update\n", q->name); + return; + } + + ao2_lock(q); + + /* 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"), + ast_variable_retrieve(member_config, interface, "state_interface")); + } + + /* 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); + remove_from_interfaces(m->state_interface); + q->membercount--; + } + ao2_ref(m, -1); + } + ao2_unlock(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; + + ao2_lock(queues); + ao2_lock(q); + + /* This is our one */ + stat = get_member_status(q, qe->max_penalty, qe->min_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_UNPAUSED_REACHABLE_MEMBERS)) + *reason = QUEUE_JOINUNAVAIL; + else if ((q->joinempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_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 ); + ast_debug(1, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos ); + } + ao2_unlock(q); + ao2_unlock(queues); + + return res; +} + +static int play_file(struct ast_channel *chan, const 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 ringing) +{ + int res = 0, avgholdmins, avgholdsecs; + time_t now; + + /* Let minannouncefrequency seconds pass between the start of each position announcement */ + time(&now); + if ((now - qe->last_pos) < qe->parent->minannouncefrequency) + 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; + + if (ringing) { + ast_indicate(qe->chan,-1); + } else { + ast_moh_stop(qe->chan); + } + if (qe->parent->announceposition) { + /* 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, 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; + } + + ast_verb(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 (qe->parent->announceposition) { + ast_verb(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) { + if (ringing) + ast_indicate(qe->chan, AST_CONTROL_RINGING); + else + 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 a recursive boxcar filter */ + /* Thanks to SRT for this contribution */ + /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */ + + ao2_lock(qe->parent); + oldvalue = qe->parent->holdtime; + qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2; + ao2_unlock(qe->parent); +} + + +static void leave_queue(struct queue_ent *qe) +{ + struct call_queue *q; + struct queue_ent *cur, *prev = NULL; + struct penalty_rule *pr_iter; + int pos = 0; + + if (!(q = qe->parent)) + return; + queue_ref(q); + ao2_lock(q); + + 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); + ast_debug(1, "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; + /* Free penalty rules */ + while ((pr_iter = AST_LIST_REMOVE_HEAD(&qe->qe_rules, list))) + ast_free(pr_iter); + } else { + /* Renumber the people after us in the queue based on a new count */ + cur->pos = ++pos; + prev = cur; + } + } + ao2_unlock(q); + + /*If the queue is a realtime queue, check to see if it's still defined in real time*/ + if (q->realtime) { + if (!ast_load_realtime("queues", "name", q->name, NULL)) + q->dead = 1; + } + + if (q->dead) { + /* It's dead and nobody is in it, so kill it */ + ao2_unlink(queues, q); + queue_unref(q); + } + queue_unref(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); + ast_free(oo); + } +} + +static int update_status(struct call_queue *q, struct member *member, int status) +{ + struct member *cur; + struct ao2_iterator mem_iter; + + /* Since a reload could have taken place, we have to traverse the list to + be sure it's still valid */ + ao2_lock(q); + mem_iter = ao2_iterator_init(q->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + if (member != cur) { + ao2_ref(cur, -1); + continue; + } + + cur->status = status; + if (!q->maskmemberstatus) { + 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); + } + ao2_unlock(q); + return 0; +} + +static int update_dial_status(struct call_queue *q, struct member *member, int status) +{ + if (status == AST_CAUSE_BUSY) + status = AST_DEVICE_BUSY; + else if (status == AST_CAUSE_UNREGISTERED) + status = AST_DEVICE_UNAVAILABLE; + else if (status == AST_CAUSE_NOSUCHDRIVER) + status = AST_DEVICE_INVALID; + else + status = AST_DEVICE_UNKNOWN; + return update_status(q, member, status); +} + +/* 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; + struct ao2_iterator queue_iter; + + /* &qlock and &rq->lock already set by try_calling() + * to solve deadlock */ + queue_iter = ao2_iterator_init(queues, 0); + while ((q = ao2_iterator_next(&queue_iter))) { + if (q == rq) { /* don't check myself, could deadlock */ + queue_unref(q); + continue; + } + ao2_lock(q); + if (q->count && q->members) { + if ((mem = ao2_find(q->members, member, OBJ_POINTER))) { + ast_debug(1, "Found matching member %s in queue '%s'\n", mem->interface, q->name); + if (q->weight > rq->weight) { + ast_debug(1, "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); + } + } + ao2_unlock(q); + if (found) { + queue_unref(q); + break; + } + queue_unref(q); + } + 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) +{ + struct ast_str *buf = ast_str_alloca(len + 1); + char *tmp; + + if (pbx_builtin_serialize_variables(chan, &buf)) { + int i, j; + + /* convert "\n" to "\nVariable: " */ + strcpy(vars, "Variable: "); + tmp = buf->str; + + 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 - 1) + j = len - 1; + vars[j - 2] = '\r'; + vars[j - 1] = '\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; + + /* on entry here, we know that tmp->chan == NULL */ + if ((tmp->lastqueue && tmp->lastqueue->wrapuptime && (time(NULL) - tmp->lastcall < tmp->lastqueue->wrapuptime)) || + (!tmp->lastqueue && qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime))) { + ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n", + (tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), 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)) { + ast_debug(1, "%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) { + ast_debug(1, "%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_debug(1, "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_dial_status(qe->parent, tmp->member, status); + + ao2_lock(qe->parent); + qe->parent->rrpos++; + qe->linpos++; + ao2_unlock(qe->parent); + + + (*busies)++; + return 0; + } else if (status != tmp->oldstatus) + update_dial_status(qe->parent, tmp->member, status); + + tmp->chan->appl = "AppQueue"; + tmp->chan->data = "(Outgoing Line)"; + tmp->chan->whentohangup = 0; + if (tmp->chan->cid.cid_num) + ast_free(tmp->chan->cid.cid_num); + tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num); + if (tmp->chan->cid.cid_name) + ast_free(tmp->chan->cid.cid_name); + tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name); + if (tmp->chan->cid.cid_ani) + ast_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 */ + if (!ast_strlen_zero(qe->chan->macrocontext)) + ast_copy_string(tmp->chan->dialcontext, qe->chan->macrocontext, sizeof(tmp->chan->dialcontext)); + else + ast_copy_string(tmp->chan->dialcontext, qe->chan->context, sizeof(tmp->chan->dialcontext)); + if (!ast_strlen_zero(qe->chan->macroexten)) + ast_copy_string(tmp->chan->exten, qe->chan->macroexten, sizeof(tmp->chan->exten)); + else + ast_copy_string(tmp->chan->exten, qe->chan->exten, sizeof(tmp->chan->exten)); + + /* 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 */ + ast_debug(1, "ast call on peer returned %d\n", res); + ast_verb(3, "Couldn't call %s\n", tmp->interface); + do_hang(tmp); + (*busies)++; + return 0; + } else if (qe->parent->eventwhencalled) { + char vars[2048]; + + manager_event(EVENT_FLAG_AGENT, "AgentCalled", + "Queue: %s\r\n" + "AgentCalled: %s\r\n" + "AgentName: %s\r\n" + "ChannelCalling: %s\r\n" + "DestinationChannel: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n" + "Priority: %d\r\n" + "%s", + qe->parent->name, tmp->interface, tmp->member->membername, qe->chan->name, tmp->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)) : ""); + ast_verb(3, "Called %s\n", tmp->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) { + ast_debug(1, "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) { + ast_debug(1, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric); + ret |= ring_entry(qe, cur, busies); + } + } + } else { + /* Ring just the best channel */ + ast_debug(1, "Trying '%s' with metric %d\n", best->interface, best->metric); + ret = ring_entry(qe, best, busies); + } + } + + return ret; +} + +static int store_next_rr(struct queue_ent *qe, struct callattempt *outgoing) +{ + struct callattempt *best = find_best(outgoing); + + if (best) { + /* Ring just the best channel */ + ast_debug(1, "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 store_next_lin(struct queue_ent *qe, struct callattempt *outgoing) +{ + struct callattempt *best = find_best(outgoing); + + if (best) { + /* Ring just the best channel */ + ast_debug(1, "Next is '%s' with metric %d\n", best->interface, best->metric); + qe->linpos = best->metric % 1000; + } else { + /* Just increment rrpos */ + if (qe->linwrapped) { + /* No more channels, start over */ + qe->linpos = 0; + } else { + /* Prioritize next entry */ + qe->linpos++; + } + } + qe->linwrapped = 0; + + return 0; +} + +static int say_periodic_announcement(struct queue_ent *qe, int ringing) +{ + 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 */ + if (ringing) + ast_indicate(qe->chan,-1); + else + ast_moh_stop(qe->chan); + + ast_verb(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 || + !qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound] || + ast_strlen_zero(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]->str)) { + qe->last_periodic_announce_sound = 0; + } + + /* play the announcement */ + res = play_file(qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]->str); + + 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) { + if (ringing) + ast_indicate(qe->chan, AST_CONTROL_RINGING); + else + 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) +{ + ao2_lock(qe->parent); + set_queue_variables(qe); + 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++; + ao2_unlock(qe->parent); +} + +/*! \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) +{ + ast_verb(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, "Auto-Pause", 1)) { + ast_verb(3, "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", interface, qe->parent->name); + } else { + ast_verb(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) +{ + const 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; +#ifdef HAVE_EPOLL + struct callattempt *epollo; +#endif + + starttime = (long) time(NULL); +#ifdef HAVE_EPOLL + for (epollo = outgoing; epollo; epollo = epollo->q_next) { + if (epollo->chan) + ast_poll_channel_add(in, epollo->chan); + } +#endif + + 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_debug(1, "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) { + ast_verb(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) { + ast_verb(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 */ + ast_verb(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 (status != o->oldstatus) + update_dial_status(qe->parent, o->member, 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) + ast_free(o->chan->cid.cid_num); + o->chan->cid.cid_num = ast_strdup(in->cid.cid_num); + + if (o->chan->cid.cid_name) + ast_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) + ast_free(o->chan->cid.cid_ani); + o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani); + } + if (o->chan->cid.cid_rdnis) + ast_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) { + ast_verb(3, "%s answered %s\n", o->chan->name, in->name); + peer = o; + } + break; + case AST_CONTROL_BUSY: + ast_verb(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: + ast_verb(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: + ast_verb(3, "%s is ringing\n", o->chan->name); + break; + case AST_CONTROL_OFFHOOK: + /* Ignore going off hook */ + break; + default: + ast_debug(1, "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 == '*')) { + ast_verb(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)) { + ast_verb(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); + } + } + +#ifdef HAVE_EPOLL + for (epollo = outgoing; epollo; epollo = epollo->q_next) { + if (epollo->chan) + ast_poll_channel_del(in, epollo->chan); + } +#endif + + 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) { + ast_debug(1, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + ast_debug(1, "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? */ + ao2_lock(qe->parent); + + ch = qe->parent->head; + + if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { + ast_debug(1, "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_NOT_INUSE: + case AST_DEVICE_UNKNOWN: + if (!cur->paused) + avl++; + break; + } + ao2_ref(cur, -1); + } + } + + ast_debug(1, "There are %d available members.\n", avl); + + while ((idx < avl) && (ch) && !ch->pending && (ch != qe)) { + idx++; + ch = ch->next; + } + + /* If the queue entry is within avl [the number of available members] calls from the top ... */ + if (ch && idx < avl) { + ast_debug(1, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + ast_debug(1, "It's not our turn (%s).\n", qe->chan->name); + res = 0; + } + + ao2_unlock(qe->parent); + } + + return res; +} +static void update_qe_rule(struct queue_ent *qe) +{ + int max_penalty = qe->pr->max_relative ? qe->max_penalty + qe->pr->max_value : qe->pr->max_value; + int min_penalty = qe->pr->min_relative ? qe->min_penalty + qe->pr->min_value : qe->pr->min_value; + char max_penalty_str[20], min_penalty_str[20]; + /* a relative change to the penalty could put it below 0 */ + if (max_penalty < 0) + max_penalty = 0; + if (min_penalty < 0) + min_penalty = 0; + if (min_penalty > max_penalty) + min_penalty = max_penalty; + snprintf(max_penalty_str, sizeof(max_penalty_str) - 1, "%d", max_penalty); + snprintf(min_penalty_str, sizeof(min_penalty_str) - 1, "%d", min_penalty); + pbx_builtin_setvar_helper(qe->chan, "QUEUE_MAX_PENALTY", max_penalty_str); + pbx_builtin_setvar_helper(qe->chan, "QUEUE_MIN_PENALTY", min_penalty_str); + qe->max_penalty = max_penalty; + qe->min_penalty = min_penalty; + ast_debug(3, "Setting max penalty to %d and min penalty to %d for caller %s since %d seconds have elapsed\n", qe->max_penalty, qe->min_penalty, qe->chan->name, qe->pr->time); + qe->pr = AST_LIST_NEXT(qe->pr, list); +} + +/*! \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, qe->min_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 || stat == QUEUE_NO_UNPAUSED_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; + } + if ((qe->parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (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 && + (res = say_position(qe,ringing))) + break; + + /* Make a periodic announcement, if enabled */ + if (qe->parent->periodicannouncefrequency && + (res = say_periodic_announcement(qe,ringing))) + break; + + /* see if we need to move to the next penalty level for this queue */ + while (qe->pr && ((time(NULL) - qe->start) > qe->pr->time)) { + update_qe_rule(qe); + } + + /* 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; + } + } + + return res; +} + +static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl) +{ + struct member *mem; + struct call_queue *qtmp; + struct ao2_iterator queue_iter; + + if (shared_lastcall) { + queue_iter = ao2_iterator_init(queues, 0); + while ((qtmp = ao2_iterator_next(&queue_iter))) { + ao2_lock(qtmp); + if ((mem = ao2_find(qtmp->members, member, OBJ_POINTER))) { + time(&mem->lastcall); + mem->calls++; + mem->lastqueue = q; + ao2_ref(mem, -1); + } + ao2_unlock(qtmp); + ao2_ref(qtmp, -1); + } + } else { + ao2_lock(q); + time(&member->lastcall); + member->calls++; + member->lastqueue = q; + ao2_unlock(q); + } + ao2_lock(q); + q->callscompleted++; + if (callcompletedinsl) + q->callscompletedinsl++; + ao2_unlock(q); + 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)) || (qe->min_penalty && (mem->penalty < qe->min_penalty))) + return -1; + + switch (q->strategy) { + case QUEUE_STRATEGY_RINGALL: + /* Everyone equal, except for penalty */ + tmp->metric = mem->penalty * 1000000; + break; + case QUEUE_STRATEGY_LINEAR: + if (pos < qe->linpos) { + tmp->metric = 1000 + pos; + } else { + if (pos > qe->linpos) + /* Indicate there is another priority */ + qe->linwrapped = 1; + tmp->metric = pos; + } + tmp->metric += mem->penalty * 1000000; + break; + 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_WRANDOM: + tmp->metric = ast_random() % ((1 + mem->penalty) * 1000); + 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; +} + +enum agent_complete_reason { + CALLER, + AGENT, + TRANSFER +}; + +static void send_agent_complete(const struct queue_ent *qe, const char *queuename, + const struct ast_channel *peer, const struct member *member, time_t callstart, + char *vars, size_t vars_len, enum agent_complete_reason rsn) +{ + const char *reason = NULL; /* silence dumb compilers */ + + if (!qe->parent->eventwhencalled) + return; + + switch (rsn) { + case CALLER: + reason = "caller"; + break; + case AGENT: + reason = "agent"; + break; + case TRANSFER: + reason = "transfer"; + break; + } + + 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: %s\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, + (long)(callstart - qe->start), (long)(time(NULL) - callstart), reason, + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, vars_len) : ""); +} +/*! \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 + * \param[in] macro the macro passed as the sixth parameter to the Queue() application + * \param[in] gosub the gosub passed as the seventh parameter to the Queue() application + * \param[in] ringing 1 if the 'r' option is set, otherwise 0 + */ +static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) +{ + struct member *cur; + struct callattempt *outgoing = NULL; /* the list of calls we are building */ + int to, orig; + char oldexten[AST_MAX_EXTENSION]=""; + char oldcontext[AST_MAX_CONTEXT]=""; + char queuename[256]=""; + char interfacevar[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; + char *macroexec = NULL; + char *gosubexec = 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)); + tmpid[0] = 0; + meid[0] = 0; + time(&now); + + 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 'k': + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_PARKCALL); + break; + case 'K': + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_PARKCALL); + break; + case 'n': + if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_LINEAR) + (*tries)++; + else + *tries = qe->parent->membercount; + *noption = 1; + break; + case 'i': + forwardsallowed = 0; + break; + case 'x': + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMIXMON); + break; + case 'X': + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMIXMON); + break; + + } + + /* Hold the lock while we setup the outgoing calls */ + if (use_weight) + ao2_lock(queues); + ao2_lock(qe->parent); + ast_debug(1, "%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); + ao2_unlock(qe->parent); + if (use_weight) + ao2_unlock(queues); + goto out; + } + if (!datastore) { + if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) { + ao2_ref(cur, -1); + ao2_unlock(qe->parent); + if (use_weight) + ao2_unlock(queues); + free(tmp); + goto out; + } + datastore->inheritance = DATASTORE_INHERIT_FOREVER; + if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) { + ao2_ref(cur, -1); + ao2_unlock(&qe->parent); + if (use_weight) + ao2_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); + ao2_unlock(qe->parent); + if (use_weight) + ao2_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; + tmp->lastqueue = cur->lastqueue; + 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); + ast_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; + orig = to; + ++qe->pending; + ring_one(qe, outgoing, &numbusies); + ao2_unlock(qe->parent); + if (use_weight) + ao2_unlock(queues); + lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed); + if (datastore) { + ast_channel_datastore_remove(qe->chan, datastore); + ast_channel_datastore_free(datastore); + } + ao2_lock(qe->parent); + if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) { + store_next_rr(qe, outgoing); + } + if (qe->parent->strategy == QUEUE_STRATEGY_LINEAR) { + store_next_lin(qe, outgoing); + } + ao2_unlock(qe->parent); + 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 (res == -1) + ast_debug(1, "%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. */ + qe->handled++; + 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)); + ao2_lock(qe->parent); + callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel); + ao2_unlock(qe->parent); + 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 (ast_check_hangup(peer)) { + /* 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", ""); + record_abandoned(qe); + 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 */ + if (ringing) + ast_indicate(qe->chan,-1); + else + 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; + } + + /* Play announcement to the caller telling it's his turn if defined */ + if (!ast_strlen_zero(qe->parent->sound_callerannounce)) { + if (play_file(qe->chan, qe->parent->sound_callerannounce)) + ast_log(LOG_WARNING, "Announcement file '%s' is unavailable, continuing anyway...\n", qe->parent->sound_callerannounce); + } + + ao2_lock(qe->parent); + /* if setinterfacevar is defined, make member variables available to the channel */ + /* use pbx_builtin_setvar to set a load of variables with one call */ + if (qe->parent->setinterfacevar) { + snprintf(interfacevar,sizeof(interfacevar), "MEMBERINTERFACE=%s|MEMBERNAME=%s|MEMBERCALLS=%d|MEMBERLASTCALL=%ld|MEMBERPENALTY=%d|MEMBERDYNAMIC=%d|MEMBERREALTIME=%d", + member->interface, member->membername, member->calls, (long)member->lastcall, member->penalty, member->dynamic, member->realtime); + pbx_builtin_setvar(qe->chan, interfacevar); + } + + /* if setqueueentryvar is defined, make queue entry (i.e. the caller) variables available to the channel */ + /* use pbx_builtin_setvar to set a load of variables with one call */ + if (qe->parent->setqueueentryvar) { + snprintf(interfacevar,sizeof(interfacevar), "QEHOLDTIME=%ld|QEORIGINALPOS=%d", + (long) time(NULL) - qe->start, qe->opos); + pbx_builtin_setvar(qe->chan, interfacevar); + } + + /* try to set queue variables if configured to do so*/ + set_queue_variables(qe); + ao2_unlock(qe->parent); + + /* Begin Monitoring */ + if (qe->parent->monfmt && *qe->parent->monfmt) { + if (!qe->parent->montype) { + ast_debug(1, "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, X_REC_IN | X_REC_OUT); + else if (qe->chan->cdr) + ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1, X_REC_IN | X_REC_OUT); + 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, X_REC_IN | X_REC_OUT); + } + } else { + ast_debug(1, "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)); + else + snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); + } else { + const char *m = monitorfilename; + for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) { + switch (*m) { + case '^': + if (*(m + 1) == '{') + *p = '$'; + break; + case ',': + *p++ = '\\'; + /* Fall through */ + default: + *p = *m; + } + if (*m == '\0') + break; + } + if (p == tmpid2 + sizeof(tmpid2)) + tmpid2[sizeof(tmpid2) - 1] = '\0'; + + 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) { + const char *m = monitor_exec; + for (p = meid2; p < meid2 + sizeof(meid2) - 1; p++, m++) { + switch (*m) { + case '^': + if (*(m + 1) == '{') + *p = '$'; + break; + case ',': + *p++ = '\\'; + /* Fall through */ + default: + *p = *m; + } + if (*m == '\0') + break; + } + if (p == meid2 + sizeof(meid2)) + meid2[sizeof(meid2) - 1] = '\0'; + + pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1); + } + + snprintf(tmpid2, sizeof(tmpid2), "%s.%s", tmpid, qe->parent->monfmt); + + mixmonapp = pbx_findapp("MixMonitor"); + + if (!monitor_options) + monitor_options = ""; + + if (mixmonapp) { + if (!ast_strlen_zero(monitor_exec)) + snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s,%s", tmpid2, monitor_options, monitor_exec); + else + snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s", tmpid2, monitor_options); + + ast_debug(1, "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)) { + ast_debug(1, "app_queue: sendurl=%s.\n", url); + ast_channel_sendurl(peer, url); + } + + /* run a macro for this connection if defined. The macro simply returns, no action is taken on the result */ + /* use macro from dialplan if passed as a option, otherwise use the default queue macro */ + if (!ast_strlen_zero(macro)) { + macroexec = ast_strdupa(macro); + } else { + if (qe->parent->membermacro) + macroexec = ast_strdupa(qe->parent->membermacro); + } + + if (!ast_strlen_zero(macroexec)) { + ast_debug(1, "app_queue: macro=%s.\n", macroexec); + + res = ast_autoservice_start(qe->chan); + if (res) { + ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n"); + res = -1; + } + + app = pbx_findapp("Macro"); + + if (app) { + res = pbx_exec(qe->chan, app, macroexec); + ast_debug(1, "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(qe->chan) < 0) { + ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n"); + res = -1; + } + } + + /* run a gosub for this connection if defined. The gosub simply returns, no action is taken on the result */ + /* use gosub from dialplan if passed as a option, otherwise use the default queue gosub */ + if (!ast_strlen_zero(gosub)) { + gosubexec = ast_strdupa(gosub); + } else { + if (qe->parent->membergosub) + gosubexec = ast_strdupa(qe->parent->membergosub); + } + + if (!ast_strlen_zero(gosubexec)) { + if (option_debug) + ast_log(LOG_DEBUG, "app_queue: gosub=%s.\n", gosubexec); + + res = ast_autoservice_start(qe->chan); + if (res) { + ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n"); + res = -1; + } + + app = pbx_findapp("Gosub"); + + if (app) { + char *gosub_args, *gosub_argstart; + + /* Set where we came from */ + ast_copy_string(qe->chan->context, "app_dial_gosub_virtual_context", sizeof(qe->chan->context)); + ast_copy_string(qe->chan->exten, "s", sizeof(qe->chan->exten)); + qe->chan->priority = 0; + + gosub_argstart = strchr(gosubexec, ','); + if (gosub_argstart) { + *gosub_argstart = 0; + asprintf(&gosub_args, "%s,s,1(%s)", gosubexec, gosub_argstart + 1); + *gosub_argstart = '|'; + } else { + asprintf(&gosub_args, "%s,s,1", gosubexec); + } + if (gosub_args) { + res = pbx_exec(qe->chan, app, gosub_args); + ast_pbx_run(qe->chan); + free(gosub_args); + if (option_debug) + ast_log(LOG_DEBUG, "Gosub exited with status %d\n", res); + } else + ast_log(LOG_ERROR, "Could not Allocate string for Gosub arguments -- Gosub Call Aborted!\n"); + + res = 0; + } else { + ast_log(LOG_ERROR, "Could not find application Gosub\n"); + res = -1; + } + + if (ast_autoservice_stop(qe->chan) < 0) { + ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n"); + res = -1; + } + } + + if (!ast_strlen_zero(agi)) { + ast_debug(1, "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"); + } + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s|%ld", (long) time(NULL) - qe->start, peer->uniqueid, + (long)(orig - to > 0 ? (orig - to) / 1000 : 0)); + if (update_cdr && qe->chan->cdr) + ast_copy_string(qe->chan->cdr->dstchannel, member->membername, sizeof(qe->chan->cdr->dstchannel)); + 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" + "Ringtime: %ld\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, + (long) time(NULL) - qe->start, peer->uniqueid, (long)(orig - to > 0 ? (orig - to) / 1000 : 0), + 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); + + bridge = ast_bridge_call(qe->chan,peer, &bridge_config); + + 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)); + send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER); + } else if (ast_check_hangup(qe->chan)) { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d", + (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); + send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), CALLER); + } else { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d", + (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); + send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), AGENT); + } + + 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, tmpq = { + .name = queuename, + }; + struct member *mem, tmpmem; + int res = RES_NOSUCHQUEUE; + + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) { + ao2_lock(q); + if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { + /* XXX future changes should beware of this assumption!! */ + if (!mem->dynamic) { + ao2_ref(mem, -1); + ao2_unlock(q); + return RES_NOT_DYNAMIC; + } + 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); + remove_from_interfaces(mem->state_interface); + ao2_ref(mem, -1); + + if (queue_persistent_members) + dump_queue_members(q); + + res = RES_OKAY; + } else { + res = RES_EXISTS; + } + ao2_unlock(q); + queue_unref(q); + } + + return res; +} + + +static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump, const char *state_interface) +{ + 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; + + ao2_lock(queues); + + ao2_lock(q); + if ((old_member = interface_exists(q, interface)) == NULL) { + if ((new_member = create_queue_member(interface, membername, penalty, paused, state_interface))) { + add_to_interfaces(state_interface); + 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; + } + ao2_unlock(q); + ao2_unlock(queues); + + return res; +} + +static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused) +{ + int found = 0; + struct call_queue *q; + struct member *mem; + struct ao2_iterator queue_iter; + + /* 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", ""); + + queue_iter = ao2_iterator_init(queues, 0); + while ((q = ao2_iterator_next(&queue_iter))) { + ao2_lock(q); + if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) { + if ((mem = interface_exists(q, interface))) { + found++; + if (mem->paused == paused) { + ast_debug(1, "%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", S_OR(reason, "")); + + if (!ast_strlen_zero(reason)) { + manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused", + "Queue: %s\r\n" + "Location: %s\r\n" + "MemberName: %s\r\n" + "Paused: %d\r\n" + "Reason: %s\r\n", + q->name, mem->interface, mem->membername, paused, reason); + } else { + 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); + } + } + ao2_unlock(q); + queue_unref(q); + } + + return found ? RESULT_SUCCESS : RESULT_FAILURE; +} + +/* \brief Sets members penalty, if queuename=NULL we set member penalty in all the queues. */ +static int set_member_penalty(char *queuename, char *interface, int penalty) +{ + int foundinterface = 0, foundqueue = 0; + struct call_queue *q; + struct member *mem; + struct ao2_iterator queue_iter; + + if (penalty < 0) { + ast_log(LOG_ERROR, "Invalid penalty (%d)\n", penalty); + return RESULT_FAILURE; + } + + queue_iter = ao2_iterator_init(queues, 0); + while ((q = ao2_iterator_next(&queue_iter))) { + ao2_lock(q); + if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) { + foundqueue++; + if ((mem = interface_exists(q, interface))) { + foundinterface++; + mem->penalty = penalty; + + ast_queue_log(q->name, "NONE", interface, "PENALTY", "%d", penalty); + manager_event(EVENT_FLAG_AGENT, "QueueMemberPenalty", + "Queue: %s\r\n" + "Location: %s\r\n" + "Penalty: %d\r\n", + q->name, mem->interface, penalty); + + } + } + ao2_unlock(q); + queue_unref(q); + } + + if (foundinterface) { + return RESULT_SUCCESS; + } else if (!foundqueue) { + ast_log (LOG_ERROR, "Invalid queuename\n"); + } else { + ast_log (LOG_ERROR, "Invalid interface\n"); + } + + return RESULT_FAILURE; +} + +/* \brief Gets members penalty. + * + * \return Return the members penalty or RESULT_FAILURE on error. */ +static int get_member_penalty(char *queuename, char *interface) +{ + int foundqueue = 0, penalty; + struct call_queue *q, tmpq = { + .name = queuename, + }; + struct member *mem; + + if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) { + foundqueue = 1; + ao2_lock(q); + if ((mem = interface_exists(q, interface))) { + penalty = mem->penalty; + ao2_unlock(q); + queue_unref(q); + return penalty; + } + ao2_unlock(q); + queue_unref(q); + } + + /* some useful debuging */ + if (foundqueue) + ast_log (LOG_ERROR, "Invalid queuename\n"); + else + ast_log (LOG_ERROR, "Invalid interface\n"); + + return RESULT_FAILURE; +} + +/* Reload dynamic queue members persisted into the astdb */ +static void reload_queue_members(void) +{ + char *cur_ptr; + const char *queue_name; + char *member; + char *interface; + char *membername = NULL; + char *state_interface; + 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]; + + ao2_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; + + { + struct call_queue tmpq = { + .name = queue_name, + }; + cur_queue = ao2_find(queues, &tmpq, OBJ_POINTER); + } + + 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; + } + + if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN)) { + queue_unref(cur_queue); + 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, ";"); + state_interface = 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; + } + + ast_debug(1, "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, state_interface) == RES_OUTOFMEMORY) { + ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n"); + break; + } + } + queue_unref(cur_queue); + } + + ao2_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) +{ + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + AST_APP_ARG(options); + AST_APP_ARG(reason); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface[|options][|reason])\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.interface)) { + ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options[|reason]])\n"); + return -1; + } + + if (set_member_paused(args.queuename, args.interface, args.reason, 1)) { + ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface); + pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND"); + return -1; + } + + pbx_builtin_setvar_helper(chan, "PQMSTATUS", "PAUSED"); + + return 0; +} + +static int upqm_exec(struct ast_channel *chan, void *data) +{ + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + AST_APP_ARG(options); + AST_APP_ARG(reason); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options[|reason]])\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.interface)) { + ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options[|reason]])\n"); + return -1; + } + + if (set_member_paused(args.queuename, args.interface, args.reason, 0)) { + ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface); + pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND"); + return -1; + } + + pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED"); + + return 0; +} + +static int rqm_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + char *parse, *temppos = NULL; + 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); + + if (ast_strlen_zero(args.interface)) { + args.interface = ast_strdupa(chan->name); + temppos = strrchr(args.interface, '-'); + if (temppos) + *temppos = '\0'; + } + + 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_debug(1, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename); + 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; + } + + return res; +} + +static int aqm_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + char *parse, *temppos = NULL; + 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); + AST_APP_ARG(state_interface); + ); + 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); + + 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; + } + } + + switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, 0, queue_persistent_members, args.state_interface)) { + 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); + 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; + } + + return res; +} + +static int ql_exec(struct ast_channel *chan, void *data) +{ + 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; + } + + 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"); + return -1; + } + + ast_queue_log(args.queuename, args.uniqueid, args.membername, args.event, + "%s", args.params ? args.params : ""); + + return 0; +} + +static void copy_rules(struct queue_ent *qe, const char *rulename) +{ + struct penalty_rule *pr_iter; + struct rule_list *rl_iter; + const char *tmp = ast_strlen_zero(rulename) ? qe->parent->defaultrule : rulename; + AST_LIST_LOCK(&rule_lists); + AST_LIST_TRAVERSE(&rule_lists, rl_iter, list) { + if (!strcasecmp(rl_iter->name, tmp)) + break; + } + if (rl_iter) { + AST_LIST_TRAVERSE(&rl_iter->rules, pr_iter, list) { + struct penalty_rule *new_pr = ast_calloc(1, sizeof(*new_pr)); + if (!new_pr) { + ast_log(LOG_ERROR, "Memory allocation error when copying penalty rules! Aborting!\n"); + AST_LIST_UNLOCK(&rule_lists); + break; + } + new_pr->time = pr_iter->time; + new_pr->max_value = pr_iter->max_value; + new_pr->min_value = pr_iter->min_value; + new_pr->max_relative = pr_iter->max_relative; + new_pr->min_relative = pr_iter->min_relative; + AST_LIST_INSERT_TAIL(&qe->qe_rules, new_pr, list); + } + } + AST_LIST_UNLOCK(&rule_lists); +} + +/*!\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; + const char *user_priority; + const char *max_penalty_str; + const char *min_penalty_str; + int prio; + int qcontinue = 0; + int max_penalty, min_penalty; + enum queue_result reason = QUEUE_UNKNOWN; + /* whether to exit Queue application after the timeout hits */ + int tries = 0; + int noption = 0; + char *parse; + int makeannouncement = 0; + 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); + AST_APP_ARG(macro); + AST_APP_ARG(gosub); + AST_APP_ARG(rule); + ); + /* 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); + + /* Setup our queue entry */ + memset(&qe, 0, sizeof(qe)); + qe.start = time(NULL); + + /* set the expire time based on the supplied timeout; */ + if (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) { + ast_debug(1, "%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 { + ast_debug(3, "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) { + ast_debug(1, "%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 ((min_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MIN_PENALTY"))) { + if (sscanf(min_penalty_str, "%d", &min_penalty) == 1) { + ast_debug(1, "%s: Got min penalty %d from ${QUEUE_MIN_PENALTY}.\n", chan->name, min_penalty); + } else { + ast_log(LOG_WARNING, "${QUEUE_MIN_PENALTY}: Invalid value (%s), channel %s.\n", + min_penalty_str, chan->name); + min_penalty = 0; + } + } else { + min_penalty = 0; + } + + if (args.options && (strchr(args.options, 'r'))) + ringing = 1; + + if (args.options && (strchr(args.options, 'c'))) + qcontinue = 1; + + ast_debug(1, "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.min_penalty = min_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)) { + ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename); + set_queue_result(chan, reason); + return 0; + } + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""), + S_OR(chan->cid.cid_num, "")); + copy_rules(&qe, args.rule); + qe.pr = AST_LIST_FIRST(&qe.qe_rules); +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; + } + + makeannouncement = 0; + + 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|%d|%ld", + qe.pos, qe.opos, (long) time(NULL) - qe.start); + break; + } + + if (makeannouncement) { + /* Make a position announcement, if enabled */ + if (qe.parent->announcefrequency) + if ((res = say_position(&qe,ringing))) + goto stop; + } + makeannouncement = 1; + + /* Make a periodic announcement, if enabled */ + if (qe.parent->periodicannouncefrequency) + if ((res = say_periodic_announcement(&qe,ringing))) + goto stop; + + /* see if we need to move to the next penalty level for this queue */ + while (qe.pr && ((time(NULL) - qe.start) > qe.pr->time)) { + update_qe_rule(&qe); + } + + /* Try calling all queue members for 'timeout' seconds */ + res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi, args.macro, args.gosub, ringing); + if (res) { + goto stop; + } + + stat = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty); + + /* exit after 'timeout' cycle if 'n' option enabled */ + if (noption && tries >= qe.parent->membercount) { + ast_verb(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 || stat == QUEUE_NO_UNPAUSED_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; + } + if ((qe.parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + record_abandoned(&qe); + reason = QUEUE_LEAVEUNAVAIL; + 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(qe.parent->name, qe.chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d|%d|%ld", qe.pos, qe.opos, (long) time(NULL) - qe.start); + 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)) { + ast_debug(1, "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); + } + + set_queue_variables(&qe); + + leave_queue(&qe); + if (reason != QUEUE_UNKNOWN) + set_queue_result(chan, reason); + + return res; +} + +static int queue_function_var(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + int res = -1; + struct call_queue *q, tmpq = { + .name = data, + }; + + char interfacevar[256]=""; + float sl = 0; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); + return -1; + } + + if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) { + ao2_lock(q); + if (q->setqueuevar) { + sl = 0; + res = 0; + + if (q->callscompleted > 0) + sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted); + + snprintf(interfacevar,sizeof(interfacevar), + "QUEUEMAX=%d|QUEUESTRATEGY=%s|QUEUECALLS=%d|QUEUEHOLDTIME=%d|QUEUECOMPLETED=%d|QUEUEABANDONED=%d|QUEUESRVLEVEL=%d|QUEUESRVLEVELPERF=%2.1f", + q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->callscompleted, q->callsabandoned, q->servicelevel, sl); + + pbx_builtin_setvar(chan, interfacevar); + } + + ao2_unlock(q); + queue_unref(q); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + snprintf(buf, len, "%d", res); + + return 0; +} + +static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + int count = 0; + struct member *m; + struct ao2_iterator mem_iter; + struct call_queue *q; + char *option; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); + return -1; + } + + if ((option = strchr(data, ','))) + *option++ = '\0'; + else + option = "logged"; + if ((q = load_realtime_queue(data))) { + ao2_lock(q); + if (!strcasecmp(option, "logged")) { + 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); + } + } else if (!strcasecmp(option, "free")) { + 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_NOT_INUSE) && (!m->paused)) { + count++; + } + ao2_ref(m, -1); + } + } else /* must be "count" */ + count = q->membercount; + ao2_unlock(q); + queue_unref(q); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + snprintf(buf, len, "%d", count); + + return 0; +} + +static int queue_function_qac_dep(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + int count = 0; + struct member *m; + struct call_queue *q; + struct ao2_iterator mem_iter; + static int depflag = 1; + + if (depflag) { + depflag = 0; + ast_log(LOG_NOTICE, "The function QUEUE_MEMBER_COUNT has been deprecated in favor of the QUEUE_MEMBER function and will not be in further releases.\n"); + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); + return -1; + } + + if ((q = load_realtime_queue(data))) { + ao2_lock(q); + 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); + } + ao2_unlock(q); + queue_unref(q); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + snprintf(buf, len, "%d", count); + + return 0; +} + + +static int queue_function_queuewaitingcount(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + int count = 0; + struct call_queue *q, tmpq = { + .name = data, + }; + + buf[0] = '\0'; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); + return -1; + } + + if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) { + ao2_lock(q); + count = q->count; + ao2_unlock(q); + queue_unref(q); + } else + ast_log(LOG_WARNING, "queue %s was not found\n", data); + + snprintf(buf, len, "%d", count); + + return 0; +} + +static int queue_function_queuememberlist(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + struct call_queue *q, tmpq = { + .name = data, + }; + 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; + } + + if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) { + int buflen = 0, count = 0; + struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0); + + ao2_lock(q); + 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); + } + ao2_unlock(q); + queue_unref(q); + } 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'; + + return 0; +} + +/*! \brief Dialplan function QUEUE_MEMBER_PENALTY() + * Gets the members penalty. */ +static int queue_function_memberpenalty_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + int penalty; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + ); + /* Make sure the returned value on error is NULL. */ + buf[0] = '\0'; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(,)\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, data); + + if (args.argc < 2) { + ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(,)\n"); + return -1; + } + + penalty = get_member_penalty (args.queuename, args.interface); + + if (penalty >= 0) /* remember that buf is already '\0' */ + snprintf (buf, len, "%d", penalty); + + return 0; +} + +/*! Dialplan function QUEUE_MEMBER_PENALTY() + * Sets the members penalty. */ +static int queue_function_memberpenalty_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + int penalty; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(interface); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(,)\n"); + return -1; + } + + AST_STANDARD_APP_ARGS(args, data); + + if (args.argc < 2) { + ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(,)\n"); + return -1; + } + + penalty = atoi(value); + + if (ast_strlen_zero(args.interface)) { + ast_log (LOG_ERROR, " parameter can't be null\n"); + return -1; + } + + /* if queuename = NULL then penalty will be set for interface in all the queues. */ + if (set_member_penalty(args.queuename, args.interface, penalty)) { + ast_log(LOG_ERROR, "Invalid interface, queue or penalty\n"); + return -1; + } + + return 0; +} + +static struct ast_custom_function queuevar_function = { + .name = "QUEUE_VARIABLES", + .synopsis = "Return Queue information in variables", + .syntax = "QUEUE_VARIABLES()", + .desc = +"Makes the following queue variables available.\n" +"QUEUEMAX maxmimum number of calls allowed\n" +"QUEUESTRATEGY the strategy of the queue\n" +"QUEUECALLS number of calls currently in the queue\n" +"QUEUEHOLDTIME current average hold time\n" +"QUEUECOMPLETED number of completed calls for the queue\n" +"QUEUEABANDONED number of abandoned calls\n" +"QUEUESRVLEVEL queue service level\n" +"QUEUESRVLEVELPERF current service level performance\n" +"Returns 0 if queue is found and setqueuevar is defined, -1 otherwise", + .read = queue_function_var, +}; + +static struct ast_custom_function queuemembercount_function = { + .name = "QUEUE_MEMBER", + .synopsis = "Count number of members answering a queue", + .syntax = "QUEUE_MEMBER(,