diff options
author | russell <russell@f38db490-d61c-443f-a65b-d21fe96a405b> | 2008-01-19 00:19:29 +0000 |
---|---|---|
committer | russell <russell@f38db490-d61c-443f-a65b-d21fe96a405b> | 2008-01-19 00:19:29 +0000 |
commit | f8247040e6231c4b3b5099ea3a526348b7941566 (patch) | |
tree | 0cc92ad6ebf6ae49a62f6e7ef8ec819121d63630 | |
parent | d88e56c61ce2042544c1a8a71c93b69ab2e6ffba (diff) |
Creating tag for the release of asterisk-1.6.0-beta1v1.6.0-beta1
git-svn-id: http://svn.digium.com/svn/asterisk/tags/1.6.0-beta1@99163 f38db490-d61c-443f-a65b-d21fe96a405b
1092 files changed, 554646 insertions, 0 deletions
diff --git a/trunk/.cleancount b/trunk/.cleancount new file mode 100644 index 000000000..bb95160cb --- /dev/null +++ b/trunk/.cleancount @@ -0,0 +1 @@ +33 diff --git a/trunk/BUGS b/trunk/BUGS new file mode 100644 index 000000000..96b55e655 --- /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 000000000..00757c4b1 --- /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 <exten>) + * 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 000000000..aa2ebac66 --- /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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + 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. + + <signature of Ty Coon>, 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 000000000..a7a432d69 --- /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 <bmc(AT)clapper.org> + +=== 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 000000000..39e0bb8ae --- /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 000000000..5b8951809 --- /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 <markster@digium.com> +# +# 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 "<?xml version=\"1.0\"?>" > $@ + @echo >> $@ + @echo "<menu name=\"Asterisk Module and Build Option Selection\">" >> $@ + @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 "</menu>" >> $@ + +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 000000000..9258e8d63 --- /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 <kpfleming@digium.com> +# +# 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 "<member name=\"$*\" displayname=\"$(shell $(GREP) -e AST_MODULE_INFO $< | head -n 1 | cut -d '"' -f 2)\" remove_on_change=\"$(SUBDIR)/$*.o $(SUBDIR)/$*.so\">" > $@ + $(AWK) -f $(ASTTOPDIR)/build_tools/get_moduleinfo $< >> $@ + echo "</member>" >> $@ + +.%.moduleinfo: %.cc + @echo "<member name=\"$*\" displayname=\"$(shell $(GREP) -e AST_MODULE_INFO $< | head -n 1 | cut -d '"' -f 2)\" remove_on_change=\"$(SUBDIR)/$*.oo $(SUBDIR)/$*.so\">" > $@ + $(AWK) -f $(ASTTOPDIR)/build_tools/get_moduleinfo $< >> $@ + echo "</member>" >> $@ + +.moduleinfo:: $(addsuffix .moduleinfo,$(addprefix .,$(ALL_C_MODS) $(ALL_CC_MODS))) + @echo "<category name=\"MENUSELECT_$(MENUSELECT_CATEGORY)\" displayname=\"$(MENUSELECT_DESCRIPTION)\" remove_on_change=\"$(SUBDIR)/modules.link\">" > $@ + @cat $^ >> $@ + @echo "</category>" >> $@ + +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 000000000..ca21dcc04 --- /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 <kpfleming@digium.com> +# +# 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 000000000..176bfca44 --- /dev/null +++ b/trunk/README @@ -0,0 +1,262 @@ +The Asterisk(R) Open Source PBX +by Mark Spencer <markster@digium.com> +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 <command>". 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 000000000..dcde18981 --- /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 000000000..9b84af614 --- /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 <ptlib.h>]) + 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 <ptlib.h>]) + 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 </dev/null` in + *GNU* | *'with BFD'*) + test "$with_gnu_ld" != no && break + ;; + *) + test "$with_gnu_ld" != yes && break + ;; + esac + fi + done + IFS="$lt_save_ifs" +else + lt_cv_path_LD="$LD" # Let the user override the test with a path. +fi]) +LD="$lt_cv_path_LD" +if test -n "$LD"; then + AC_MSG_RESULT($LD) +else + AC_MSG_RESULT(no) +fi +test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH]) +AST_PROG_LD_GNU +])# AST_PROG_LD + + +# AST_PROG_LD_GNU +# -------------- +AC_DEFUN([AST_PROG_LD_GNU], +[AC_REQUIRE([AST_PROG_EGREP])dnl +AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld, +[# I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 </dev/null` in +*GNU* | *'with BFD'*) + lt_cv_prog_gnu_ld=yes + ;; +*) + lt_cv_prog_gnu_ld=no + ;; +esac]) +with_gnu_ld=$lt_cv_prog_gnu_ld +])# AST_PROG_LD_GNU + +# AST_PROG_EGREP +# ------------- +m4_ifndef([AST_PROG_EGREP], [AC_DEFUN([AST_PROG_EGREP], +[AC_CACHE_CHECK([for egrep], [ac_cv_prog_egrep], + [if echo a | (grep -E '(a|b)') >/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 <stevenj@alum.mit.edu> +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.h>], + [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 <pthread.h>], [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 000000000..59a54265e --- /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(<STDIN>) { + 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 = <STDIN>; +#checkresult($result); + +print STDERR "2. Getting song name...\n"; +print "GET DATA demo-enterkeywords\n"; +$result = <STDIN>; +$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 = <STDIN>; + if (&checkresult($result) < 0) { + exit 0; + } + print "EXEC MP3Player \"$key\"\n"; +# print "WAIT FOR DIGIT 60000\n"; + $result = <STDIN>; + 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 = <STDIN>; +checkresult($result); + diff --git a/trunk/agi/Makefile b/trunk/agi/Makefile new file mode 100644 index 000000000..0cb6f3f02 --- /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 <markster@digium.com> +# +# 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 000000000..4fc36eda8 --- /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(<STDIN>) { + 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 = <STDIN>; +&checkresult($result); + +print STDERR "2. Testing 'sendtext'..."; +print "SEND TEXT \"hello world\"\n"; +my $result = <STDIN>; +&checkresult($result); + +print STDERR "3. Testing 'sendimage'..."; +print "SEND IMAGE asterisk-image\n"; +my $result = <STDIN>; +&checkresult($result); + +print STDERR "4. Testing 'saynumber'..."; +print "SAY NUMBER 192837465 \"\"\n"; +my $result = <STDIN>; +&checkresult($result); + +print STDERR "5. Testing 'waitdtmf'..."; +print "WAIT FOR DIGIT 1000\n"; +my $result = <STDIN>; +&checkresult($result); + +print STDERR "6. Testing 'record'..."; +print "RECORD FILE testagi gsm 1234 3000\n"; +my $result = <STDIN>; +&checkresult($result); + +print STDERR "6a. Testing 'record' playback..."; +print "STREAM FILE testagi \"\"\n"; +my $result = <STDIN>; +&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 000000000..d2898763c --- /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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#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 000000000..704fd7b22 --- /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 000000000..d3f13cf6b --- /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(<CLIENT>) { + 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 = <CLIENT>; + &checkresult($result); + + print STDERR "2. Testing 'sendtext'..."; + print CLIENT "SEND TEXT \"hello world\"\n"; + my $result = <CLIENT>; + &checkresult($result); + + print STDERR "3. Testing 'sendimage'..."; + print CLIENT "SEND IMAGE asterisk-image\n"; + my $result = <CLIENT>; + &checkresult($result); + + print STDERR "4. Testing 'saynumber'..."; + print CLIENT "SAY NUMBER 192837465 \"\"\n"; + my $result = <CLIENT>; + &checkresult($result); + + print STDERR "5. Testing 'waitdtmf'..."; + print CLIENT "WAIT FOR DIGIT 1000\n"; + my $result = <CLIENT>; + &checkresult($result); + + print STDERR "6. Testing 'record'..."; + print CLIENT "RECORD FILE testagi gsm 1234 3000\n"; + my $result = <CLIENT>; + &checkresult($result); + + print STDERR "6a. Testing 'record' playback..."; + print CLIENT "STREAM FILE testagi \"\"\n"; + my $result = <CLIENT>; + &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 000000000..7bd9c10f9 --- /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 <jesuscyborg@gmail.com> +# +# 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 (<STDIN>) { + 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 = <STDIN>; &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 = <STDIN>; &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 = <STDIN>; + $digit = &check_result($result); + if ($digit > 0) { + $digitstr .= chr($digit); + last; + } + } + for (;;) { + print "WAIT FOR DIGIT 3000\n"; + my $result = <STDIN>; + $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<length($digitstr); $n++) { + my $d = substr($digitstr, $n, 1); + if ($d == 2) { + $regexp .= '[abc]'; + } elsif ($d == 3) { + $regexp .= '[def]'; + } elsif ($d == 4) { + $regexp .= '[ghi]'; + } elsif ($d == 5) { + $regexp .= '[jkl]'; + } elsif ($d == 6) { + $regexp .= '[mno]'; + } elsif ($d == 7) { + $regexp .= '[pqrs]'; + } elsif ($d == 8) { + $regexp .= '[tuv]'; + } elsif ($d == 9) { + $regexp .= '[wxyz]'; + } + } + @filterList = (); + for (my $n=1; $n<@masterBgList; $n++) { + push(@filterList, $masterBgList[$n]) if $masterBgList[$n][3] =~ /^$regexp/i; + } + goto MYCONTINUE; + } + + for (my $n=0; $n<@masterBgList; $n++) { + if ($digitstr == $masterBgList[$n][0]) { + if ($masterBgList[$n][1] == 1) { # a folder + &music_dir_menu(rmts($dir).'/'.$masterBgList[$n][4]); + @filterList = @masterBgList; + goto MYCONTINUE; + } elsif ($masterBgList[$n][1] == 2) { # a file +# because *'s scripting language is crunk and won't allow us to escape +# funny filenames, we need to create a temporary symlink to the mp3 +# file + my $mp3 = &escape_file($masterBgList[$n][4]); + my $link = `mktemp`; + chomp($link); + $link .= '.mp3'; + print STDERR "ln -s $mp3 $link\n"; + my $cmdr = `ln -s $mp3 $link`; + chomp($cmdr); + print "Failed to create symlink to mp3: $cmdr\n" if $cmdr ne ''; + + print "EXEC MP3Player \"$link\"\n"; + my $result = <STDIN>; &check_result($result); + + `rm $link`; + + if (!$MENUAFTERSONG) { + print "EXEC Playback \"$FESTIVALCACHE/jukebox_thankyou\"\n"; + my $result = <STDIN>; &check_result($result); + exit 0; + } else { + goto MYCONTINUE; + } + } + } + } + print "EXEC Playback \"$FESTIVALCACHE/jukebox_invalid\"\n"; + my $result = <STDIN>; &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 = <STDIN>; &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 = <STDIN>; &check_result($result); + print "EXEC Playback \"$FESTIVALCACHE/jukebox_percent\"\n"; + my $result = <STDIN>; &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 000000000..5ca51913d --- /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 000000000..22b8f6d7a --- /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 000000000..1b09ce7b5 --- /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 <markster@digium.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. + */ + +/*! \file + * + * \brief Program Asterisk ADSI Scripts into phone + * + * \author Mark Spencer <markster@digium.com> + * + * \ingroup applications + */ + +/*** MODULEINFO + <depend>res_adsi</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <netinet/in.h> +#include <ctype.h> + +#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 <sizeof(justify) / sizeof(justify[0]); x++) { + if (!strcasecmp(justify[x].name, name)) + return justify[x].id; + } + + return -1; +} + +static struct adsi_soft_key *getkeybyname(struct adsi_script *state, char *name, char *script, int lineno) +{ + int x; + + for (x = 0; x < state->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 <state->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 : "<nothing>", 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 : "<nothing>", 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 = "<nothing>"; + 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 : "<nothing>", 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 : "<nothing>", 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 000000000..c4fa81109 --- /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 <hwstar@rodgers.sdcoxmail.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. + */ + +/*! \file + * \brief Central Station Alarm receiver for Ademco Contact ID + * \author Steve Rodgers <hwstar@rodgers.sdcoxmail.com> + * + * *** 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 <math.h> +#include <sys/wait.h> +#include <sys/time.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/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) ? "<unknown>" : cl); + + if(res >- 0) + res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "<unknown>" : 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 000000000..4e440d388 --- /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 000000000..3a3e7582e --- /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 <markster@digium.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. + */ + +/*! \file + * + * \brief Execute arbitrary authenticate commands + * + * \author Mark Spencer <markster@digium.com> + * + * \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 000000000..7804a806b --- /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 <martinp@digium.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. + */ + +/*! \file + * + * \brief Applications connected with CDR engine + * + * \author Martin Pycko <martinp@digium.com> + * + * \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 000000000..67d29e6ee --- /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 <markster@digium.com> +* James Golovich <james@gnuinter.net> +* +* See http://www.asterisk.org for more information about +* the Asterisk project. Please do not directly 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 <markster@digium.com> + * \author James Golovich <james@gnuinter.net> + + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/ioctl.h> + +#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 000000000..325c681ac --- /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 <sergey_basmanov@mail.ru> + * + * \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 000000000..a29973ff4 --- /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 <anthmct@yahoo.com> + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <ctype.h> + +#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 000000000..b6dfd898e --- /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 <markster@digium.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. + */ + +/*! \file + * + * \brief Trivial application to control playback of a sound file + * + * \author Mark Spencer <markster@digium.com> + * + * \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 000000000..0cc5043ce --- /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 <markster@digium.com> + * Jefferson Noxon <jeff@debian.org> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly 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 <markster@digium.com> + * \author Jefferson Noxon <jeff@debian.org> + * + * \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 000000000..db1f76c8d --- /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 <markster@digium.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. + */ + +/*! \file + * + * \brief dial() & retrydial() - Trivial application to dial a channel and send an URL on answer + * + * \author Mark Spencer <markster@digium.com> + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/time.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <netinet/in.h> + +#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:<context>^<exten>^<priority> - 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 |