diff options
-rw-r--r-- | include/asterisk/_private.h | 2 | ||||
-rw-r--r-- | main/asterisk.c | 10 | ||||
-rw-r--r-- | main/pbx.c | 2130 | ||||
-rw-r--r-- | main/pbx_builtins.c | 1500 | ||||
-rw-r--r-- | main/pbx_functions.c | 723 | ||||
-rw-r--r-- | main/pbx_private.h | 37 |
6 files changed, 2280 insertions, 2122 deletions
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index d49de1789..55c92fb00 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -17,6 +17,8 @@ int load_modules(unsigned int); /*!< Provided by loader.c */ int load_pbx(void); /*!< Provided by pbx.c */ +int load_pbx_builtins(void); /*!< Provided by pbx_builtins.c */ +int load_pbx_functions_cli(void); /*!< Provided by pbx_functions.c */ int init_logger(void); /*!< Provided by logger.c */ void close_logger(void); /*!< Provided by logger.c */ void logger_queue_start(void); /*!< Provided by logger.c */ diff --git a/main/asterisk.c b/main/asterisk.c index 3f16caf06..0e179a397 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -4643,6 +4643,16 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou exit(1); } + if (load_pbx_builtins()) { + printf("Failed: load_pbx_builtins\n%s", term_quit()); + exit(1); + } + + if (load_pbx_functions_cli()) { + printf("Failed: load_pbx_functions_cli\n%s", term_quit()); + exit(1); + } + if (ast_local_init()) { printf("Failed: ast_local_init\n%s", term_quit()); exit(1); diff --git a/main/pbx.c b/main/pbx.c index be003286d..7c58e613a 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -73,6 +73,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_channels.h" #include "asterisk/dial.h" #include "asterisk/vector.h" +#include "pbx_private.h" /*! * \note I M P O R T A N T : @@ -96,637 +97,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") */ /*** DOCUMENTATION - <application name="Answer" language="en_US"> - <synopsis> - Answer a channel if ringing. - </synopsis> - <syntax> - <parameter name="delay"> - <para>Asterisk will wait this number of milliseconds before returning to - the dialplan after answering the call.</para> - </parameter> - </syntax> - <description> - <para>If the call has not been answered, this application will - answer it. Otherwise, it has no effect on the call.</para> - </description> - <see-also> - <ref type="application">Hangup</ref> - </see-also> - </application> - <application name="BackGround" language="en_US"> - <synopsis> - Play an audio file while waiting for digits of an extension to go to. - </synopsis> - <syntax> - <parameter name="filenames" required="true" argsep="&"> - <argument name="filename1" required="true" /> - <argument name="filename2" multiple="true" /> - </parameter> - <parameter name="options"> - <optionlist> - <option name="s"> - <para>Causes the playback of the message to be skipped - if the channel is not in the <literal>up</literal> state (i.e. it - hasn't been answered yet). If this happens, the - application will return immediately.</para> - </option> - <option name="n"> - <para>Don't answer the channel before playing the files.</para> - </option> - <option name="m"> - <para>Only break if a digit hit matches a one digit - extension in the destination context.</para> - </option> - </optionlist> - </parameter> - <parameter name="langoverride"> - <para>Explicitly specifies which language to attempt to use for the requested sound files.</para> - </parameter> - <parameter name="context"> - <para>This is the dialplan context that this application will use when exiting - to a dialed extension.</para> - </parameter> - </syntax> - <description> - <para>This application will play the given list of files <emphasis>(do not put extension)</emphasis> - while waiting for an extension to be dialed by the calling channel. To continue waiting - for digits after this application has finished playing files, the <literal>WaitExten</literal> - application should be used.</para> - <para>If one of the requested sound files does not exist, call processing will be terminated.</para> - <para>This application sets the following channel variable upon completion:</para> - <variablelist> - <variable name="BACKGROUNDSTATUS"> - <para>The status of the background attempt as a text string.</para> - <value name="SUCCESS" /> - <value name="FAILED" /> - </variable> - </variablelist> - </description> - <see-also> - <ref type="application">ControlPlayback</ref> - <ref type="application">WaitExten</ref> - <ref type="application">BackgroundDetect</ref> - <ref type="function">TIMEOUT</ref> - </see-also> - </application> - <application name="Busy" language="en_US"> - <synopsis> - Indicate the Busy condition. - </synopsis> - <syntax> - <parameter name="timeout"> - <para>If specified, the calling channel will be hung up after the specified number of seconds. - Otherwise, this application will wait until the calling channel hangs up.</para> - </parameter> - </syntax> - <description> - <para>This application will indicate the busy condition to the calling channel.</para> - </description> - <see-also> - <ref type="application">Congestion</ref> - <ref type="application">Progress</ref> - <ref type="application">Playtones</ref> - <ref type="application">Hangup</ref> - </see-also> - </application> - <application name="Congestion" language="en_US"> - <synopsis> - Indicate the Congestion condition. - </synopsis> - <syntax> - <parameter name="timeout"> - <para>If specified, the calling channel will be hung up after the specified number of seconds. - Otherwise, this application will wait until the calling channel hangs up.</para> - </parameter> - </syntax> - <description> - <para>This application will indicate the congestion condition to the calling channel.</para> - </description> - <see-also> - <ref type="application">Busy</ref> - <ref type="application">Progress</ref> - <ref type="application">Playtones</ref> - <ref type="application">Hangup</ref> - </see-also> - </application> - <application name="ExecIfTime" language="en_US"> - <synopsis> - Conditional application execution based on the current time. - </synopsis> - <syntax argsep="?"> - <parameter name="day_condition" required="true"> - <argument name="times" required="true" /> - <argument name="weekdays" required="true" /> - <argument name="mdays" required="true" /> - <argument name="months" required="true" /> - <argument name="timezone" required="false" /> - </parameter> - <parameter name="appname" required="true" hasparams="optional"> - <argument name="appargs" required="true" /> - </parameter> - </syntax> - <description> - <para>This application will execute the specified dialplan application, with optional - arguments, if the current time matches the given time specification.</para> - </description> - <see-also> - <ref type="application">Exec</ref> - <ref type="application">ExecIf</ref> - <ref type="application">TryExec</ref> - <ref type="application">GotoIfTime</ref> - </see-also> - </application> - <application name="Goto" language="en_US"> - <synopsis> - Jump to a particular priority, extension, or context. - </synopsis> - <syntax> - <parameter name="context" /> - <parameter name="extensions" /> - <parameter name="priority" required="true" /> - </syntax> - <description> - <para>This application will set the current context, extension, and priority in the channel structure. - After it completes, the pbx engine will continue dialplan execution at the specified location. - If no specific <replaceable>extension</replaceable>, or <replaceable>extension</replaceable> and - <replaceable>context</replaceable>, are specified, then this application will - just set the specified <replaceable>priority</replaceable> of the current extension.</para> - <para>At least a <replaceable>priority</replaceable> is required as an argument, or the goto will - return a <literal>-1</literal>, and the channel and call will be terminated.</para> - <para>If the location that is put into the channel information is bogus, and asterisk cannot - find that location in the dialplan, then the execution engine will try to find and execute the code in - the <literal>i</literal> (invalid) extension in the current context. If that does not exist, it will try to execute the - <literal>h</literal> extension. If neither the <literal>h</literal> nor <literal>i</literal> extensions - have been defined, the channel is hung up, and the execution of instructions on the channel is terminated. - What this means is that, for example, you specify a context that does not exist, then - it will not be possible to find the <literal>h</literal> or <literal>i</literal> extensions, - and the call will terminate!</para> - </description> - <see-also> - <ref type="application">GotoIf</ref> - <ref type="application">GotoIfTime</ref> - <ref type="application">Gosub</ref> - <ref type="application">Macro</ref> - </see-also> - </application> - <application name="GotoIf" language="en_US"> - <synopsis> - Conditional goto. - </synopsis> - <syntax argsep="?"> - <parameter name="condition" required="true" /> - <parameter name="destination" required="true" argsep=":"> - <argument name="labeliftrue"> - <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true. - Takes the form similar to Goto() of [[context,]extension,]priority.</para> - </argument> - <argument name="labeliffalse"> - <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false. - Takes the form similar to Goto() of [[context,]extension,]priority.</para> - </argument> - </parameter> - </syntax> - <description> - <para>This application will set the current context, extension, and priority in the channel structure - based on the evaluation of the given condition. After this application completes, the - pbx engine will continue dialplan execution at the specified location in the dialplan. - The labels are specified with the same syntax as used within the Goto application. - If the label chosen by the condition is omitted, no jump is performed, and the execution passes to the - next instruction. If the target location is bogus, and does not exist, the execution engine will try - to find and execute the code in the <literal>i</literal> (invalid) extension in the current context. - If that does not exist, it will try to execute the <literal>h</literal> extension. - If neither the <literal>h</literal> nor <literal>i</literal> extensions have been defined, - the channel is hung up, and the execution of instructions on the channel is terminated. - Remember that this command can set the current context, and if the context specified - does not exist, then it will not be able to find any 'h' or 'i' extensions there, and - the channel and call will both be terminated!.</para> - </description> - <see-also> - <ref type="application">Goto</ref> - <ref type="application">GotoIfTime</ref> - <ref type="application">GosubIf</ref> - <ref type="application">MacroIf</ref> - </see-also> - </application> - <application name="GotoIfTime" language="en_US"> - <synopsis> - Conditional Goto based on the current time. - </synopsis> - <syntax argsep="?"> - <parameter name="condition" required="true"> - <argument name="times" required="true" /> - <argument name="weekdays" required="true" /> - <argument name="mdays" required="true" /> - <argument name="months" required="true" /> - <argument name="timezone" required="false" /> - </parameter> - <parameter name="destination" required="true" argsep=":"> - <argument name="labeliftrue"> - <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true. - Takes the form similar to Goto() of [[context,]extension,]priority.</para> - </argument> - <argument name="labeliffalse"> - <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false. - Takes the form similar to Goto() of [[context,]extension,]priority.</para> - </argument> - </parameter> - </syntax> - <description> - <para>This application will set the context, extension, and priority in the channel structure - based on the evaluation of the given time specification. After this application completes, - the pbx engine will continue dialplan execution at the specified location in the dialplan. - If the current time is within the given time specification, the channel will continue at - <replaceable>labeliftrue</replaceable>. Otherwise the channel will continue at <replaceable>labeliffalse</replaceable>. - If the label chosen by the condition is omitted, no jump is performed, and execution passes to the next - instruction. If the target jump location is bogus, the same actions would be taken as for <literal>Goto</literal>. - Further information on the time specification can be found in examples - illustrating how to do time-based context includes in the dialplan.</para> - </description> - <see-also> - <ref type="application">GotoIf</ref> - <ref type="application">Goto</ref> - <ref type="function">IFTIME</ref> - <ref type="function">TESTTIME</ref> - </see-also> - </application> - <application name="ImportVar" language="en_US"> - <synopsis> - Import a variable from a channel into a new variable. - </synopsis> - <syntax argsep="="> - <parameter name="newvar" required="true" /> - <parameter name="vardata" required="true"> - <argument name="channelname" required="true" /> - <argument name="variable" required="true" /> - </parameter> - </syntax> - <description> - <para>This application imports a <replaceable>variable</replaceable> from the specified - <replaceable>channel</replaceable> (as opposed to the current one) and stores it as a variable - (<replaceable>newvar</replaceable>) in the current channel (the channel that is calling this - application). Variables created by this application have the same inheritance properties as those - created with the <literal>Set</literal> application.</para> - </description> - <see-also> - <ref type="application">Set</ref> - </see-also> - </application> - <application name="Hangup" language="en_US"> - <synopsis> - Hang up the calling channel. - </synopsis> - <syntax> - <parameter name="causecode"> - <para>If a <replaceable>causecode</replaceable> is given the channel's - hangup cause will be set to the given value.</para> - </parameter> - </syntax> - <description> - <para>This application will hang up the calling channel.</para> - </description> - <see-also> - <ref type="application">Answer</ref> - <ref type="application">Busy</ref> - <ref type="application">Congestion</ref> - </see-also> - </application> - <application name="Incomplete" language="en_US"> - <synopsis> - Returns AST_PBX_INCOMPLETE value. - </synopsis> - <syntax> - <parameter name="n"> - <para>If specified, then Incomplete will not attempt to answer the channel first.</para> - <note><para>Most channel types need to be in Answer state in order to receive DTMF.</para></note> - </parameter> - </syntax> - <description> - <para>Signals the PBX routines that the previous matched extension is incomplete - and that further input should be allowed before matching can be considered - to be complete. Can be used within a pattern match when certain criteria warrants - a longer match.</para> - </description> - </application> - <application name="NoOp" language="en_US"> - <synopsis> - Do Nothing (No Operation). - </synopsis> - <syntax> - <parameter name="text"> - <para>Any text provided can be viewed at the Asterisk CLI.</para> - </parameter> - </syntax> - <description> - <para>This application does nothing. However, it is useful for debugging purposes.</para> - <para>This method can be used to see the evaluations of variables or functions without having any effect.</para> - </description> - <see-also> - <ref type="application">Verbose</ref> - <ref type="application">Log</ref> - </see-also> - </application> - <application name="Proceeding" language="en_US"> - <synopsis> - Indicate proceeding. - </synopsis> - <syntax /> - <description> - <para>This application will request that a proceeding message be provided to the calling channel.</para> - </description> - </application> - <application name="Progress" language="en_US"> - <synopsis> - Indicate progress. - </synopsis> - <syntax /> - <description> - <para>This application will request that in-band progress information be provided to the calling channel.</para> - </description> - <see-also> - <ref type="application">Busy</ref> - <ref type="application">Congestion</ref> - <ref type="application">Ringing</ref> - <ref type="application">Playtones</ref> - </see-also> - </application> - <application name="RaiseException" language="en_US"> - <synopsis> - Handle an exceptional condition. - </synopsis> - <syntax> - <parameter name="reason" required="true" /> - </syntax> - <description> - <para>This application will jump to the <literal>e</literal> extension in the current context, setting the - dialplan function EXCEPTION(). If the <literal>e</literal> extension does not exist, the call will hangup.</para> - </description> - <see-also> - <ref type="function">Exception</ref> - </see-also> - </application> - <application name="Ringing" language="en_US"> - <synopsis> - Indicate ringing tone. - </synopsis> - <syntax /> - <description> - <para>This application will request that the channel indicate a ringing tone to the user.</para> - </description> - <see-also> - <ref type="application">Busy</ref> - <ref type="application">Congestion</ref> - <ref type="application">Progress</ref> - <ref type="application">Playtones</ref> - </see-also> - </application> - <application name="SayAlpha" language="en_US"> - <synopsis> - Say Alpha. - </synopsis> - <syntax> - <parameter name="string" required="true" /> - </syntax> - <description> - <para>This application will play the sounds that correspond to the letters - of the given <replaceable>string</replaceable>. If the channel variable - <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), - then this application will react to DTMF in the same way as - <literal>Background</literal>.</para> - </description> - <see-also> - <ref type="application">SayDigits</ref> - <ref type="application">SayNumber</ref> - <ref type="application">SayPhonetic</ref> - <ref type="function">CHANNEL</ref> - </see-also> - </application> - <application name="SayAlphaCase" language="en_US"> - <synopsis> - Say Alpha. - </synopsis> - <syntax> - <parameter name="casetype" required="true" > - <enumlist> - <enum name="a"> - <para>Case sensitive (all) pronunciation. - (Ex: SayAlphaCase(a,aBc); - lowercase a uppercase b lowercase c).</para> - </enum> - <enum name="l"> - <para>Case sensitive (lower) pronunciation. - (Ex: SayAlphaCase(l,aBc); - lowercase a b lowercase c).</para> - </enum> - <enum name="n"> - <para>Case insensitive pronunciation. Equivalent to SayAlpha. - (Ex: SayAlphaCase(n,aBc) - a b c).</para> - </enum> - <enum name="u"> - <para>Case sensitive (upper) pronunciation. - (Ex: SayAlphaCase(u,aBc); - a uppercase b c).</para> - </enum> - </enumlist> - </parameter> - <parameter name="string" required="true" /> - </syntax> - <description> - <para>This application will play the sounds that correspond to the letters of the - given <replaceable>string</replaceable>. Optionally, a <replaceable>casetype</replaceable> may be - specified. This will be used for case-insensitive or case-sensitive pronunciations. If the channel - variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this - application will react to DTMF in the same way as <literal>Background</literal>.</para> - </description> - <see-also> - <ref type="application">SayDigits</ref> - <ref type="application">SayNumber</ref> - <ref type="application">SayPhonetic</ref> - <ref type="application">SayAlpha</ref> - <ref type="function">CHANNEL</ref> - </see-also> - </application> - <application name="SayDigits" language="en_US"> - <synopsis> - Say Digits. - </synopsis> - <syntax> - <parameter name="digits" required="true" /> - </syntax> - <description> - <para>This application will play the sounds that correspond to the digits of - the given number. This will use the language that is currently set for the channel. - If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' - (case insensitive), then this application will react to DTMF in the same way as - <literal>Background</literal>.</para> - </description> - <see-also> - <ref type="application">SayAlpha</ref> - <ref type="application">SayNumber</ref> - <ref type="application">SayPhonetic</ref> - <ref type="function">CHANNEL</ref> - </see-also> - </application> - <application name="SayNumber" language="en_US"> - <synopsis> - Say Number. - </synopsis> - <syntax> - <parameter name="digits" required="true" /> - <parameter name="gender" /> - </syntax> - <description> - <para>This application will play the sounds that correspond to the given - <replaceable>digits</replaceable>. Optionally, a <replaceable>gender</replaceable> may be - specified. This will use the language that is currently set for the channel. See the CHANNEL() - function for more information on setting the language for the channel. If the channel variable - <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this - application will react to DTMF in the same way as <literal>Background</literal>.</para> - </description> - <see-also> - <ref type="application">SayAlpha</ref> - <ref type="application">SayDigits</ref> - <ref type="application">SayPhonetic</ref> - <ref type="function">CHANNEL</ref> - </see-also> - </application> - <application name="SayPhonetic" language="en_US"> - <synopsis> - Say Phonetic. - </synopsis> - <syntax> - <parameter name="string" required="true" /> - </syntax> - <description> - <para>This application will play the sounds from the phonetic alphabet that correspond to the - letters in the given <replaceable>string</replaceable>. If the channel variable - <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this - application will react to DTMF in the same way as <literal>Background</literal>.</para> - </description> - <see-also> - <ref type="application">SayAlpha</ref> - <ref type="application">SayDigits</ref> - <ref type="application">SayNumber</ref> - </see-also> - </application> - <application name="Set" language="en_US"> - <synopsis> - Set channel variable or function value. - </synopsis> - <syntax argsep="="> - <parameter name="name" required="true" /> - <parameter name="value" required="true" /> - </syntax> - <description> - <para>This function can be used to set the value of channel variables or dialplan functions. - When setting variables, if the variable name is prefixed with <literal>_</literal>, - the variable will be inherited into channels created from the current channel. - If the variable name is prefixed with <literal>__</literal>, the variable will be - inherited into channels created from the current channel and all children channels.</para> - <note><para>If (and only if), in <filename>/etc/asterisk/asterisk.conf</filename>, you have - a <literal>[compat]</literal> category, and you have <literal>app_set = 1.4</literal> under that, then - the behavior of this app changes, and strips surrounding quotes from the right hand side as - it did previously in 1.4. - The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar) - were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly - protect separators and quotes in various database access strings has been greatly - reduced by these changes.</para></note> - </description> - <see-also> - <ref type="application">MSet</ref> - <ref type="function">GLOBAL</ref> - <ref type="function">SET</ref> - <ref type="function">ENV</ref> - </see-also> - </application> - <application name="MSet" language="en_US"> - <synopsis> - Set channel variable(s) or function value(s). - </synopsis> - <syntax> - <parameter name="set1" required="true" argsep="="> - <argument name="name1" required="true" /> - <argument name="value1" required="true" /> - </parameter> - <parameter name="set2" multiple="true" argsep="="> - <argument name="name2" required="true" /> - <argument name="value2" required="true" /> - </parameter> - </syntax> - <description> - <para>This function can be used to set the value of channel variables or dialplan functions. - When setting variables, if the variable name is prefixed with <literal>_</literal>, - the variable will be inherited into channels created from the current channel - If the variable name is prefixed with <literal>__</literal>, the variable will be - inherited into channels created from the current channel and all children channels. - MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus - prone to doing things that you may not expect. For example, it strips surrounding - double-quotes from the right-hand side (value). If you need to put a separator - character (comma or vert-bar), you will need to escape them by inserting a backslash - before them. Avoid its use if possible.</para> - </description> - <see-also> - <ref type="application">Set</ref> - </see-also> - </application> - <application name="SetAMAFlags" language="en_US"> - <synopsis> - Set the AMA Flags. - </synopsis> - <syntax> - <parameter name="flag" /> - </syntax> - <description> - <para>This application will set the channel's AMA Flags for billing purposes.</para> - <warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning> - </description> - <see-also> - <ref type="function">CDR</ref> - <ref type="function">CHANNEL</ref> - </see-also> - </application> - <application name="Wait" language="en_US"> - <synopsis> - Waits for some time. - </synopsis> - <syntax> - <parameter name="seconds" required="true"> - <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the - application to wait for 1.5 seconds.</para> - </parameter> - </syntax> - <description> - <para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para> - </description> - </application> - <application name="WaitExten" language="en_US"> - <synopsis> - Waits for an extension to be entered. - </synopsis> - <syntax> - <parameter name="seconds"> - <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the - application to wait for 1.5 seconds.</para> - </parameter> - <parameter name="options"> - <optionlist> - <option name="m"> - <para>Provide music on hold to the caller while waiting for an extension.</para> - <argument name="x"> - <para>Specify the class for music on hold. <emphasis>CHANNEL(musicclass) will - be used instead if set</emphasis></para> - </argument> - </option> - </optionlist> - </parameter> - </syntax> - <description> - <para>This application waits for the user to enter a new extension for a specified number - of <replaceable>seconds</replaceable>.</para> - <xi:include xpointer="xpointer(/docs/application[@name='Macro']/description/warning[2])" /> - </description> - <see-also> - <ref type="application">Background</ref> - <ref type="function">TIMEOUT</ref> - </see-also> - </application> <function name="EXCEPTION" language="en_US"> <synopsis> Retrieve the details of the current dialplan exception. @@ -850,48 +220,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define SWITCH_DATA_LENGTH 256 -#define VAR_BUF_SIZE 4096 - #define VAR_NORMAL 1 #define VAR_SOFTTRAN 2 #define VAR_HARDTRAN 3 -#define BACKGROUND_SKIP (1 << 0) -#define BACKGROUND_NOANSWER (1 << 1) -#define BACKGROUND_MATCHEXTEN (1 << 2) -#define BACKGROUND_PLAYBACK (1 << 3) - -AST_APP_OPTIONS(background_opts, { - AST_APP_OPTION('s', BACKGROUND_SKIP), - AST_APP_OPTION('n', BACKGROUND_NOANSWER), - AST_APP_OPTION('m', BACKGROUND_MATCHEXTEN), - AST_APP_OPTION('p', BACKGROUND_PLAYBACK), -}); - -#define WAITEXTEN_MOH (1 << 0) -#define WAITEXTEN_DIALTONE (1 << 1) - -AST_APP_OPTIONS(waitexten_opts, { - AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 0), - AST_APP_OPTION_ARG('d', WAITEXTEN_DIALTONE, 0), -}); - struct ast_context; struct ast_app; AST_THREADSTORAGE(switch_data); AST_THREADSTORAGE(extensionstate_buf); -/*! - * \brief A thread local indicating whether the current thread can run - * 'dangerous' dialplan functions. - */ -AST_THREADSTORAGE(thread_inhibit_escalations_tl); - -/*! - * \brief Set to true (non-zero) to globally allow all dangerous dialplan - * functions to run. - */ -static int live_dangerously; /*! \brief ast_exten: An extension @@ -1263,34 +600,10 @@ struct pbx_exception { int priority; /*!< Priority associated with this exception */ }; -static int pbx_builtin_answer(struct ast_channel *, const char *); -static int pbx_builtin_goto(struct ast_channel *, const char *); -static int pbx_builtin_hangup(struct ast_channel *, const char *); -static int pbx_builtin_background(struct ast_channel *, const char *); -static int pbx_builtin_wait(struct ast_channel *, const char *); -static int pbx_builtin_waitexten(struct ast_channel *, const char *); -static int pbx_builtin_incomplete(struct ast_channel *, const char *); -static int pbx_builtin_setamaflags(struct ast_channel *, const char *); -static int pbx_builtin_ringing(struct ast_channel *, const char *); -static int pbx_builtin_proceeding(struct ast_channel *, const char *); -static int pbx_builtin_progress(struct ast_channel *, const char *); -static int pbx_builtin_congestion(struct ast_channel *, const char *); -static int pbx_builtin_busy(struct ast_channel *, const char *); -static int pbx_builtin_noop(struct ast_channel *, const char *); -static int pbx_builtin_gotoif(struct ast_channel *, const char *); -static int pbx_builtin_gotoiftime(struct ast_channel *, const char *); -static int pbx_builtin_execiftime(struct ast_channel *, const char *); -static int pbx_builtin_saynumber(struct ast_channel *, const char *); -static int pbx_builtin_saydigits(struct ast_channel *, const char *); -static int pbx_builtin_saycharacters(struct ast_channel *, const char *); -static int pbx_builtin_saycharacters_case(struct ast_channel *, const char *); -static int pbx_builtin_sayphonetic(struct ast_channel *, const char *); static int matchcid(const char *cidpattern, const char *callerid); #ifdef NEED_DEBUG static void log_match_char_tree(struct match_char *node, char *prefix); /* for use anywhere */ #endif -static int pbx_builtin_importvar(struct ast_channel *, const char *); -static void set_ext_pri(struct ast_channel *c, const char *exten, int pri); static void new_find_extension(const char *str, struct scoreboard *score, struct match_char *tree, int length, int spec, const char *callerid, const char *label, enum ext_match_t action); @@ -1435,50 +748,6 @@ AST_MUTEX_DEFINE_STATIC(maxcalllock); static int countcalls; static int totalcalls; -/*! - * \brief Registered functions container. - * - * It is sorted by function name. - */ -static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); - -/*! \brief Declaration of builtin applications */ -static struct pbx_builtin { - char name[AST_MAX_APP]; - int (*execute)(struct ast_channel *chan, const char *data); -} builtins[] = -{ - /* These applications are built into the PBX core and do not - need separate modules */ - - { "Answer", pbx_builtin_answer }, - { "BackGround", pbx_builtin_background }, - { "Busy", pbx_builtin_busy }, - { "Congestion", pbx_builtin_congestion }, - { "ExecIfTime", pbx_builtin_execiftime }, - { "Goto", pbx_builtin_goto }, - { "GotoIf", pbx_builtin_gotoif }, - { "GotoIfTime", pbx_builtin_gotoiftime }, - { "ImportVar", pbx_builtin_importvar }, - { "Hangup", pbx_builtin_hangup }, - { "Incomplete", pbx_builtin_incomplete }, - { "NoOp", pbx_builtin_noop }, - { "Proceeding", pbx_builtin_proceeding }, - { "Progress", pbx_builtin_progress }, - { "RaiseException", pbx_builtin_raise_exception }, - { "Ringing", pbx_builtin_ringing }, - { "SayAlpha", pbx_builtin_saycharacters }, - { "SayAlphaCase", pbx_builtin_saycharacters_case }, - { "SayDigits", pbx_builtin_saydigits }, - { "SayNumber", pbx_builtin_saynumber }, - { "SayPhonetic", pbx_builtin_sayphonetic }, - { "Set", pbx_builtin_setvar }, - { "MSet", pbx_builtin_setvar_multiple }, - { "SetAMAFlags", pbx_builtin_setamaflags }, - { "Wait", pbx_builtin_wait }, - { "WaitExten", pbx_builtin_waitexten } -}; - static struct ast_context *contexts; static struct ast_hashtab *contexts_table = NULL; @@ -3822,7 +3091,7 @@ static const struct ast_datastore_info exception_store_info = { * \retval 0 on success. * \retval -1 on error. */ -static int raise_exception(struct ast_channel *chan, const char *reason, int priority) +int raise_exception(struct ast_channel *chan, const char *reason, int priority) { struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL); struct pbx_exception *exception = NULL; @@ -3848,12 +3117,6 @@ static int raise_exception(struct ast_channel *chan, const char *reason, int pri return 0; } -int pbx_builtin_raise_exception(struct ast_channel *chan, const char *reason) -{ - /* Priority will become 1, next time through the AUTOLOOP */ - return raise_exception(chan, reason, 0); -} - static int acf_exception_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen) { struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL); @@ -3879,632 +3142,6 @@ static struct ast_custom_function exception_function = { .read = acf_exception_read, }; -static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct ast_custom_function *acf; - int count_acf = 0; - int like = 0; - - switch (cmd) { - case CLI_INIT: - e->command = "core show functions [like]"; - e->usage = - "Usage: core show functions [like <text>]\n" - " List builtin functions, optionally only those matching a given string\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) { - like = 1; - } else if (a->argc != 3) { - return CLI_SHOWUSAGE; - } - - ast_cli(a->fd, "%s Custom Functions:\n--------------------------------------------------------------------------------\n", like ? "Matching" : "Installed"); - - AST_RWLIST_RDLOCK(&acf_root); - AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { - if (!like || strstr(acf->name, a->argv[4])) { - count_acf++; - ast_cli(a->fd, "%-20.20s %-35.35s %s\n", - S_OR(acf->name, ""), - S_OR(acf->syntax, ""), - S_OR(acf->synopsis, "")); - } - } - AST_RWLIST_UNLOCK(&acf_root); - - ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : ""); - - return CLI_SUCCESS; -} - -static char *complete_functions(const char *word, int pos, int state) -{ - struct ast_custom_function *cur; - char *ret = NULL; - int which = 0; - int wordlen; - int cmp; - - if (pos != 3) { - return NULL; - } - - wordlen = strlen(word); - AST_RWLIST_RDLOCK(&acf_root); - AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { - /* - * Do a case-insensitive search for convenience in this - * 'complete' function. - * - * We must search the entire container because the functions are - * sorted and normally found case sensitively. - */ - cmp = strncasecmp(word, cur->name, wordlen); - if (!cmp) { - /* Found match. */ - if (++which <= state) { - /* Not enough matches. */ - continue; - } - ret = ast_strdup(cur->name); - break; - } - } - AST_RWLIST_UNLOCK(&acf_root); - - return ret; -} - -static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct ast_custom_function *acf; - /* Maximum number of characters added by terminal coloring is 22 */ - char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40]; - char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL; - char stxtitle[40], *syntax = NULL, *arguments = NULL; - int syntax_size, description_size, synopsis_size, arguments_size, seealso_size; - - switch (cmd) { - case CLI_INIT: - e->command = "core show function"; - e->usage = - "Usage: core show function <function>\n" - " Describe a particular dialplan function.\n"; - return NULL; - case CLI_GENERATE: - return complete_functions(a->word, a->pos, a->n); - } - - if (a->argc != 4) { - return CLI_SHOWUSAGE; - } - - if (!(acf = ast_custom_function_find(a->argv[3]))) { - ast_cli(a->fd, "No function by that name registered.\n"); - return CLI_FAILURE; - } - - syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - if (!(syntax = ast_malloc(syntax_size))) { - ast_cli(a->fd, "Memory allocation failure!\n"); - return CLI_FAILURE; - } - - snprintf(info, sizeof(info), "\n -= Info about function '%s' =- \n\n", acf->name); - term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); - term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); - term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); - term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); - term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); - term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); - term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); -#ifdef AST_XML_DOCS - if (acf->docsrc == AST_XML_DOC) { - arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1); - synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1); - description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1); - seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1); - } else -#endif - { - synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - synopsis = ast_malloc(synopsis_size); - - description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - description = ast_malloc(description_size); - - arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - arguments = ast_malloc(arguments_size); - - seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; - seealso = ast_malloc(seealso_size); - - /* check allocated memory. */ - if (!synopsis || !description || !arguments || !seealso) { - ast_free(synopsis); - ast_free(description); - ast_free(arguments); - ast_free(seealso); - ast_free(syntax); - return CLI_FAILURE; - } - - term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); - term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); - term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size); - term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); - } - - ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", - infotitle, syntitle, synopsis, destitle, description, - stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); - - ast_free(arguments); - ast_free(synopsis); - ast_free(description); - ast_free(seealso); - ast_free(syntax); - - return CLI_SUCCESS; -} - -static struct ast_custom_function *ast_custom_function_find_nolock(const char *name) -{ - struct ast_custom_function *cur; - int cmp; - - AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { - cmp = strcmp(name, cur->name); - if (cmp > 0) { - continue; - } - if (!cmp) { - /* Found it. */ - break; - } - /* Not in container. */ - cur = NULL; - break; - } - - return cur; -} - -struct ast_custom_function *ast_custom_function_find(const char *name) -{ - struct ast_custom_function *acf; - - AST_RWLIST_RDLOCK(&acf_root); - acf = ast_custom_function_find_nolock(name); - AST_RWLIST_UNLOCK(&acf_root); - - return acf; -} - -int ast_custom_function_unregister(struct ast_custom_function *acf) -{ - struct ast_custom_function *cur; - - if (!acf) { - return -1; - } - - AST_RWLIST_WRLOCK(&acf_root); - if ((cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist))) { -#ifdef AST_XML_DOCS - if (cur->docsrc == AST_XML_DOC) { - ast_string_field_free_memory(acf); - } -#endif - ast_verb(2, "Unregistered custom function %s\n", cur->name); - } - AST_RWLIST_UNLOCK(&acf_root); - - return cur ? 0 : -1; -} - -/*! - * \brief Returns true if given custom function escalates privileges on read. - * - * \param acf Custom function to query. - * \return True (non-zero) if reads escalate privileges. - * \return False (zero) if reads just read. - */ -static int read_escalates(const struct ast_custom_function *acf) { - return acf->read_escalates; -} - -/*! - * \brief Returns true if given custom function escalates privileges on write. - * - * \param acf Custom function to query. - * \return True (non-zero) if writes escalate privileges. - * \return False (zero) if writes just write. - */ -static int write_escalates(const struct ast_custom_function *acf) { - return acf->write_escalates; -} - -/*! \internal - * \brief Retrieve the XML documentation of a specified ast_custom_function, - * and populate ast_custom_function string fields. - * \param acf ast_custom_function structure with empty 'desc' and 'synopsis' - * but with a function 'name'. - * \retval -1 On error. - * \retval 0 On succes. - */ -static int acf_retrieve_docs(struct ast_custom_function *acf) -{ -#ifdef AST_XML_DOCS - char *tmpxml; - - /* Let's try to find it in the Documentation XML */ - if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) { - return 0; - } - - if (ast_string_field_init(acf, 128)) { - return -1; - } - - /* load synopsis */ - tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, synopsis, tmpxml); - ast_free(tmpxml); - - /* load description */ - tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, desc, tmpxml); - ast_free(tmpxml); - - /* load syntax */ - tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, syntax, tmpxml); - ast_free(tmpxml); - - /* load arguments */ - tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, arguments, tmpxml); - ast_free(tmpxml); - - /* load seealso */ - tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod)); - ast_string_field_set(acf, seealso, tmpxml); - ast_free(tmpxml); - - acf->docsrc = AST_XML_DOC; -#endif - - return 0; -} - -int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod) -{ - struct ast_custom_function *cur; - - if (!acf) { - return -1; - } - - acf->mod = mod; -#ifdef AST_XML_DOCS - acf->docsrc = AST_STATIC_DOC; -#endif - - if (acf_retrieve_docs(acf)) { - return -1; - } - - AST_RWLIST_WRLOCK(&acf_root); - - cur = ast_custom_function_find_nolock(acf->name); - if (cur) { - ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name); - AST_RWLIST_UNLOCK(&acf_root); - return -1; - } - - /* Store in alphabetical order */ - AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) { - if (strcmp(acf->name, cur->name) < 0) { - AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist); - break; - } - } - AST_RWLIST_TRAVERSE_SAFE_END; - if (!cur) { - AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist); - } - - AST_RWLIST_UNLOCK(&acf_root); - - ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name)); - - return 0; -} - -int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod) -{ - int res; - - res = __ast_custom_function_register(acf, mod); - if (res != 0) { - return -1; - } - - switch (escalation) { - case AST_CFE_NONE: - break; - case AST_CFE_READ: - acf->read_escalates = 1; - break; - case AST_CFE_WRITE: - acf->write_escalates = 1; - break; - case AST_CFE_BOTH: - acf->read_escalates = 1; - acf->write_escalates = 1; - break; - } - - return 0; -} - -/*! \brief return a pointer to the arguments of the function, - * and terminates the function name with '\\0' - */ -static char *func_args(char *function) -{ - char *args = strchr(function, '('); - - if (!args) { - ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses. Assuming null argument.\n", function); - } else { - char *p; - *args++ = '\0'; - if ((p = strrchr(args, ')'))) { - *p = '\0'; - } else { - ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args); - } - } - return args; -} - -void pbx_live_dangerously(int new_live_dangerously) -{ - if (new_live_dangerously && !live_dangerously) { - ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n" - "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n"); - } - - if (!new_live_dangerously && live_dangerously) { - ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n"); - } - live_dangerously = new_live_dangerously; -} - -int ast_thread_inhibit_escalations(void) -{ - int *thread_inhibit_escalations; - - thread_inhibit_escalations = ast_threadstorage_get( - &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); - - if (thread_inhibit_escalations == NULL) { - ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n"); - return -1; - } - - *thread_inhibit_escalations = 1; - return 0; -} - -/*! - * \brief Indicates whether the current thread inhibits the execution of - * dangerous functions. - * - * \return True (non-zero) if dangerous function execution is inhibited. - * \return False (zero) if dangerous function execution is allowed. - */ -static int thread_inhibits_escalations(void) -{ - int *thread_inhibit_escalations; - - thread_inhibit_escalations = ast_threadstorage_get( - &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); - - if (thread_inhibit_escalations == NULL) { - ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n"); - /* On error, assume that we are inhibiting */ - return 1; - } - - return *thread_inhibit_escalations; -} - -/*! - * \brief Determines whether execution of a custom function's read function - * is allowed. - * - * \param acfptr Custom function to check - * \return True (non-zero) if reading is allowed. - * \return False (zero) if reading is not allowed. - */ -static int is_read_allowed(struct ast_custom_function *acfptr) -{ - if (!acfptr) { - return 1; - } - - if (!read_escalates(acfptr)) { - return 1; - } - - if (!thread_inhibits_escalations()) { - return 1; - } - - if (live_dangerously) { - /* Global setting overrides the thread's preference */ - ast_debug(2, "Reading %s from a dangerous context\n", - acfptr->name); - return 1; - } - - /* We have no reason to allow this function to execute */ - return 0; -} - -/*! - * \brief Determines whether execution of a custom function's write function - * is allowed. - * - * \param acfptr Custom function to check - * \return True (non-zero) if writing is allowed. - * \return False (zero) if writing is not allowed. - */ -static int is_write_allowed(struct ast_custom_function *acfptr) -{ - if (!acfptr) { - return 1; - } - - if (!write_escalates(acfptr)) { - return 1; - } - - if (!thread_inhibits_escalations()) { - return 1; - } - - if (live_dangerously) { - /* Global setting overrides the thread's preference */ - ast_debug(2, "Writing %s from a dangerous context\n", - acfptr->name); - return 1; - } - - /* We have no reason to allow this function to execute */ - return 0; -} - -int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len) -{ - char *copy = ast_strdupa(function); - char *args = func_args(copy); - struct ast_custom_function *acfptr = ast_custom_function_find(copy); - int res; - struct ast_module_user *u = NULL; - - if (acfptr == NULL) { - ast_log(LOG_ERROR, "Function %s not registered\n", copy); - } else if (!acfptr->read && !acfptr->read2) { - ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); - } else if (!is_read_allowed(acfptr)) { - ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); - } else if (acfptr->read) { - if (acfptr->mod) { - u = __ast_module_user_add(acfptr->mod, chan); - } - res = acfptr->read(chan, copy, args, workspace, len); - if (acfptr->mod && u) { - __ast_module_user_remove(acfptr->mod, u); - } - return res; - } else { - struct ast_str *str = ast_str_create(16); - if (acfptr->mod) { - u = __ast_module_user_add(acfptr->mod, chan); - } - res = acfptr->read2(chan, copy, args, &str, 0); - if (acfptr->mod && u) { - __ast_module_user_remove(acfptr->mod, u); - } - ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len); - ast_free(str); - return res; - } - return -1; -} - -int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen) -{ - char *copy = ast_strdupa(function); - char *args = func_args(copy); - struct ast_custom_function *acfptr = ast_custom_function_find(copy); - int res; - struct ast_module_user *u = NULL; - - if (acfptr == NULL) { - ast_log(LOG_ERROR, "Function %s not registered\n", copy); - } else if (!acfptr->read && !acfptr->read2) { - ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); - } else if (!is_read_allowed(acfptr)) { - ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); - } else { - if (acfptr->mod) { - u = __ast_module_user_add(acfptr->mod, chan); - } - ast_str_reset(*str); - if (acfptr->read2) { - /* ast_str enabled */ - res = acfptr->read2(chan, copy, args, str, maxlen); - } else { - /* Legacy function pointer, allocate buffer for result */ - int maxsize = ast_str_size(*str); - if (maxlen > -1) { - if (maxlen == 0) { - if (acfptr->read_max) { - maxsize = acfptr->read_max; - } else { - maxsize = VAR_BUF_SIZE; - } - } else { - maxsize = maxlen; - } - ast_str_make_space(str, maxsize); - } - res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize); - } - if (acfptr->mod && u) { - __ast_module_user_remove(acfptr->mod, u); - } - return res; - } - return -1; -} - -int ast_func_write(struct ast_channel *chan, const char *function, const char *value) -{ - char *copy = ast_strdupa(function); - char *args = func_args(copy); - struct ast_custom_function *acfptr = ast_custom_function_find(copy); - - if (acfptr == NULL) { - ast_log(LOG_ERROR, "Function %s not registered\n", copy); - } else if (!acfptr->write) { - ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy); - } else if (!is_write_allowed(acfptr)) { - ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy); - } else { - int res; - struct ast_module_user *u = NULL; - if (acfptr->mod) - u = __ast_module_user_add(acfptr->mod, chan); - res = acfptr->write(chan, copy, args, value); - if (acfptr->mod && u) - __ast_module_user_remove(acfptr->mod, u); - return res; - } - - return -1; -} - void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used) { /* Substitutes variables into buf, based on string templ */ @@ -6463,7 +5100,7 @@ static char *handle_show_hangup_all(struct ast_cli_entry *e, int cmd, struct ast } /*! helper function to set extension and priority */ -static void set_ext_pri(struct ast_channel *c, const char *exten, int pri) +void set_ext_pri(struct ast_channel *c, const char *exten, int pri) { ast_channel_lock(c); ast_channel_exten_set(c, exten); @@ -6726,11 +5363,11 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, status = "UNKNOWN"; ast_verb(3, "Auto fallthrough, channel '%s' status is '%s'\n", ast_channel_name(c), status); if (!strcasecmp(status, "CONGESTION")) - res = pbx_builtin_congestion(c, "10"); + res = indicate_congestion(c, "10"); else if (!strcasecmp(status, "CHANUNAVAIL")) - res = pbx_builtin_congestion(c, "10"); + res = indicate_congestion(c, "10"); else if (!strcasecmp(status, "BUSY")) - res = pbx_builtin_busy(c, "10"); + res = indicate_busy(c, "10"); error = 1; /* XXX disable message */ break; /* exit from the 'for' loop */ } @@ -8776,7 +7413,6 @@ static struct ast_cli_entry pbx_cli[] = { AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"), #endif AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"), - AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"), AST_CLI_DEFINE(handle_show_switches, "Show alternative switches"), AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"), AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"), @@ -8785,7 +7421,6 @@ static struct ast_cli_entry pbx_cli[] = { AST_CLI_DEFINE(handle_show_device2extenstate, "Show expected exten state from multiple device states"), #endif AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"), - AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"), AST_CLI_DEFINE(handle_show_hangup_all, "Show hangup handlers of all channels"), AST_CLI_DEFINE(handle_show_hangup_channel, "Show hangup handlers of a specified channel"), AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"), @@ -11050,7 +9685,7 @@ void ast_context_destroy(struct ast_context *con, const char *registrar) ast_unlock_contexts(); } -static void wait_for_hangup(struct ast_channel *chan, const void *data) +void wait_for_hangup(struct ast_channel *chan, const void *data) { int res; struct ast_frame *f; @@ -11073,188 +9708,6 @@ static void wait_for_hangup(struct ast_channel *chan, const void *data) } /*! - * \ingroup applications - */ -static int pbx_builtin_proceeding(struct ast_channel *chan, const char *data) -{ - ast_indicate(chan, AST_CONTROL_PROCEEDING); - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_progress(struct ast_channel *chan, const char *data) -{ - ast_indicate(chan, AST_CONTROL_PROGRESS); - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_ringing(struct ast_channel *chan, const char *data) -{ - ast_indicate(chan, AST_CONTROL_RINGING); - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_busy(struct ast_channel *chan, const char *data) -{ - ast_indicate(chan, AST_CONTROL_BUSY); - /* Don't change state of an UP channel, just indicate - busy in audio */ - ast_channel_lock(chan); - if (ast_channel_state(chan) != AST_STATE_UP) { - ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY); - ast_setstate(chan, AST_STATE_BUSY); - } - ast_channel_unlock(chan); - wait_for_hangup(chan, data); - return -1; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_congestion(struct ast_channel *chan, const char *data) -{ - ast_indicate(chan, AST_CONTROL_CONGESTION); - /* Don't change state of an UP channel, just indicate - congestion in audio */ - ast_channel_lock(chan); - if (ast_channel_state(chan) != AST_STATE_UP) { - ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION); - ast_setstate(chan, AST_STATE_BUSY); - } - ast_channel_unlock(chan); - wait_for_hangup(chan, data); - return -1; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_answer(struct ast_channel *chan, const char *data) -{ - int delay = 0; - char *parse; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(delay); - AST_APP_ARG(answer_cdr); - ); - - if (ast_strlen_zero(data)) { - return __ast_answer(chan, 0); - } - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - if (!ast_strlen_zero(args.delay) && (ast_channel_state(chan) != AST_STATE_UP)) - delay = atoi(data); - - if (delay < 0) { - delay = 0; - } - - if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) { - ast_log(AST_LOG_WARNING, "The nocdr option for the Answer application has been removed and is no longer supported.\n"); - } - - return __ast_answer(chan, delay); -} - -static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) -{ - const char *options = data; - int answer = 1; - - /* Some channels can receive DTMF in unanswered state; some cannot */ - if (!ast_strlen_zero(options) && strchr(options, 'n')) { - answer = 0; - } - - /* If the channel is hungup, stop waiting */ - if (ast_check_hangup(chan)) { - return -1; - } else if (ast_channel_state(chan) != AST_STATE_UP && answer) { - __ast_answer(chan, 0); - } - - ast_indicate(chan, AST_CONTROL_INCOMPLETE); - - return AST_PBX_INCOMPLETE; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) -{ - ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n"); - - if (ast_strlen_zero(data)) { - ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n"); - return 0; - } - /* Copy the AMA Flags as specified */ - ast_channel_lock(chan); - if (isdigit(data[0])) { - int amaflags; - if (sscanf(data, "%30d", &amaflags) != 1) { - ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan)); - ast_channel_unlock(chan); - return 0; - } - ast_channel_amaflags_set(chan, amaflags); - } else { - ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data)); - } - ast_channel_unlock(chan); - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_hangup(struct ast_channel *chan, const char *data) -{ - int cause; - - ast_set_hangupsource(chan, "dialplan/builtin", 0); - - if (!ast_strlen_zero(data)) { - cause = ast_str2cause(data); - if (cause <= 0) { - if (sscanf(data, "%30d", &cause) != 1 || cause <= 0) { - ast_log(LOG_WARNING, "Invalid cause given to Hangup(): \"%s\"\n", data); - cause = 0; - } - } - } else { - cause = 0; - } - - ast_channel_lock(chan); - if (cause <= 0) { - cause = ast_channel_hangupcause(chan); - if (cause <= 0) { - cause = AST_CAUSE_NORMAL_CLEARING; - } - } - ast_channel_hangupcause_set(chan, cause); - ast_softhangup_nolock(chan, AST_SOFTHANGUP_EXPLICIT); - ast_channel_unlock(chan); - - return -1; -} - -/*! * \ingroup functions */ static int testtime_write(struct ast_channel *chan, const char *cmd, char *var, const char *value) @@ -11286,333 +9739,6 @@ static struct ast_custom_function testtime_function = { .write = testtime_write, }; -/*! - * \ingroup applications - */ -static int pbx_builtin_gotoiftime(struct ast_channel *chan, const char *data) -{ - char *s, *ts, *branch1, *branch2, *branch; - struct ast_timing timing; - const char *ctime; - struct timeval tv = ast_tvnow(); - long timesecs; - - if (!chan) { - ast_log(LOG_WARNING, "GotoIfTime requires a channel on which to operate\n"); - return -1; - } - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>[,<timezone>]?'labeliftrue':'labeliffalse'\n"); - return -1; - } - - ts = s = ast_strdupa(data); - - ast_channel_lock(chan); - if ((ctime = pbx_builtin_getvar_helper(chan, "TESTTIME")) && sscanf(ctime, "%ld", ×ecs) == 1) { - tv.tv_sec = timesecs; - } else if (ctime) { - ast_log(LOG_WARNING, "Using current time to evaluate\n"); - /* Reset when unparseable */ - pbx_builtin_setvar_helper(chan, "TESTTIME", NULL); - } - ast_channel_unlock(chan); - - /* Separate the Goto path */ - strsep(&ts, "?"); - branch1 = strsep(&ts,":"); - branch2 = strsep(&ts,""); - - /* struct ast_include include contained garbage here, fixed by zeroing it on get_timerange */ - if (ast_build_timing(&timing, s) && ast_check_timing2(&timing, tv)) { - branch = branch1; - } else { - branch = branch2; - } - ast_destroy_timing(&timing); - - if (ast_strlen_zero(branch)) { - ast_debug(1, "Not taking any branch\n"); - return 0; - } - - return pbx_builtin_goto(chan, branch); -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data) -{ - char *s, *appname; - struct ast_timing timing; - struct ast_app *app; - static const char * const usage = "ExecIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>[,<timezone>]?<appname>[(<appargs>)]"; - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "%s\n", usage); - return -1; - } - - appname = ast_strdupa(data); - - s = strsep(&appname, "?"); /* Separate the timerange and application name/data */ - if (!appname) { /* missing application */ - ast_log(LOG_WARNING, "%s\n", usage); - return -1; - } - - if (!ast_build_timing(&timing, s)) { - ast_log(LOG_WARNING, "Invalid Time Spec: %s\nCorrect usage: %s\n", s, usage); - ast_destroy_timing(&timing); - return -1; - } - - if (!ast_check_timing(&timing)) { /* outside the valid time window, just return */ - ast_destroy_timing(&timing); - return 0; - } - ast_destroy_timing(&timing); - - /* now split appname(appargs) */ - if ((s = strchr(appname, '('))) { - char *e; - *s++ = '\0'; - if ((e = strrchr(s, ')'))) - *e = '\0'; - else - ast_log(LOG_WARNING, "Failed to find closing parenthesis\n"); - } - - - if ((app = pbx_findapp(appname))) { - return pbx_exec(chan, app, S_OR(s, "")); - } else { - ast_log(LOG_WARNING, "Cannot locate application %s\n", appname); - return -1; - } -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_wait(struct ast_channel *chan, const char *data) -{ - int ms; - - /* Wait for "n" seconds */ - if (!ast_app_parse_timelen(data, &ms, TIMELEN_SECONDS) && ms > 0) { - return ast_safe_sleep(chan, ms); - } - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data) -{ - int ms, res; - struct ast_flags flags = {0}; - char *opts[1] = { NULL }; - char *parse; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(timeout); - AST_APP_ARG(options); - ); - - if (!ast_strlen_zero(data)) { - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(args, parse); - } else - memset(&args, 0, sizeof(args)); - - if (args.options) - ast_app_parse_options(waitexten_opts, &flags, opts, args.options); - - if (ast_test_flag(&flags, WAITEXTEN_MOH) && !opts[0] ) { - ast_log(LOG_WARNING, "The 'm' option has been specified for WaitExten without a class.\n"); - } else if (ast_test_flag(&flags, WAITEXTEN_MOH)) { - ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(opts[0], NULL), - !ast_strlen_zero(opts[0]) ? strlen(opts[0]) + 1 : 0); - } else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) { - struct ast_tone_zone_sound *ts = ast_get_indication_tone(ast_channel_zone(chan), "dial"); - if (ts) { - ast_playtones_start(chan, 0, ts->data, 0); - ts = ast_tone_zone_sound_unref(ts); - } else { - ast_tonepair_start(chan, 350, 440, 0, 0); - } - } - /* Wait for "n" seconds */ - if (!ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) && ms > 0) { - /* Yay! */ - } else if (ast_channel_pbx(chan)) { - ms = ast_channel_pbx(chan)->rtimeoutms; - } else { - ms = 10000; - } - - res = ast_waitfordigit(chan, ms); - if (!res) { - if (ast_check_hangup(chan)) { - /* Call is hungup for some reason. */ - res = -1; - } else if (ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - ast_verb(3, "Timeout on %s, continuing...\n", ast_channel_name(chan)); - } else if (ast_exists_extension(chan, ast_channel_context(chan), "t", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - ast_verb(3, "Timeout on %s, going to 't'\n", ast_channel_name(chan)); - set_ext_pri(chan, "t", 0); /* 0 will become 1, next time through the loop */ - } else if (ast_exists_extension(chan, ast_channel_context(chan), "e", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - raise_exception(chan, "RESPONSETIMEOUT", 0); /* 0 will become 1, next time through the loop */ - } else { - ast_log(LOG_WARNING, "Timeout but no rule 't' or 'e' in context '%s'\n", - ast_channel_context(chan)); - res = -1; - } - } - - if (ast_test_flag(&flags, WAITEXTEN_MOH)) - ast_indicate(chan, AST_CONTROL_UNHOLD); - else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) - ast_playtones_stop(chan); - - return res; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_background(struct ast_channel *chan, const char *data) -{ - int res = 0; - int mres = 0; - struct ast_flags flags = {0}; - char *parse, exten[2] = ""; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(filename); - AST_APP_ARG(options); - AST_APP_ARG(lang); - AST_APP_ARG(context); - ); - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "Background requires an argument (filename)\n"); - return -1; - } - - parse = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(args, parse); - - if (ast_strlen_zero(args.lang)) - args.lang = (char *)ast_channel_language(chan); /* XXX this is const */ - - if (ast_strlen_zero(args.context)) { - const char *context; - ast_channel_lock(chan); - if ((context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"))) { - args.context = ast_strdupa(context); - } else { - args.context = ast_strdupa(ast_channel_context(chan)); - } - ast_channel_unlock(chan); - } - - if (args.options) { - if (!strcasecmp(args.options, "skip")) - flags.flags = BACKGROUND_SKIP; - else if (!strcasecmp(args.options, "noanswer")) - flags.flags = BACKGROUND_NOANSWER; - else - ast_app_parse_options(background_opts, &flags, NULL, args.options); - } - - /* Answer if need be */ - if (ast_channel_state(chan) != AST_STATE_UP) { - if (ast_test_flag(&flags, BACKGROUND_SKIP)) { - goto done; - } else if (!ast_test_flag(&flags, BACKGROUND_NOANSWER)) { - res = ast_answer(chan); - } - } - - if (!res) { - char *back = ast_strip(args.filename); - char *front; - - ast_stopstream(chan); /* Stop anything playing */ - /* Stream the list of files */ - while (!res && (front = strsep(&back, "&")) ) { - if ( (res = ast_streamfile(chan, front, args.lang)) ) { - ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char*)data); - res = 0; - mres = 1; - break; - } - if (ast_test_flag(&flags, BACKGROUND_PLAYBACK)) { - res = ast_waitstream(chan, ""); - } else if (ast_test_flag(&flags, BACKGROUND_MATCHEXTEN)) { - res = ast_waitstream_exten(chan, args.context); - } else { - res = ast_waitstream(chan, AST_DIGIT_ANY); - } - ast_stopstream(chan); - } - } - - /* - * If the single digit DTMF is an extension in the specified context, then - * go there and signal no DTMF. Otherwise, we should exit with that DTMF. - * If we're in Macro, we'll exit and seek that DTMF as the beginning of an - * extension in the Macro's calling context. If we're not in Macro, then - * we'll simply seek that extension in the calling context. Previously, - * someone complained about the behavior as it related to the interior of a - * Gosub routine, and the fix (#14011) inadvertently broke FreePBX - * (#14940). This change should fix both of these situations, but with the - * possible incompatibility that if a single digit extension does not exist - * (but a longer extension COULD have matched), it would have previously - * gone immediately to the "i" extension, but will now need to wait for a - * timeout. - * - * Later, we had to add a flag to disable this workaround, because AGI - * users can EXEC Background and reasonably expect that the DTMF code will - * be returned (see #16434). - */ - if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS) - && (exten[0] = res) - && ast_canmatch_extension(chan, args.context, exten, 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) - && !ast_matchmore_extension(chan, args.context, exten, 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - char buf[2] = { 0, }; - snprintf(buf, sizeof(buf), "%c", res); - ast_channel_exten_set(chan, buf); - ast_channel_context_set(chan, args.context); - ast_channel_priority_set(chan, 0); - res = 0; - } -done: - pbx_builtin_setvar_helper(chan, "BACKGROUNDSTATUS", mres ? "FAILED" : "SUCCESS"); - return res; -} - -/*! Goto - * \ingroup applications - */ -static int pbx_builtin_goto(struct ast_channel *chan, const char *data) -{ - int res = ast_parseable_goto(chan, data); - if (!res) - ast_verb(3, "Goto (%s,%s,%d)\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1); - return res; -} - - int pbx_builtin_serialize_variables(struct ast_channel *chan, struct ast_str **buf) { struct ast_var_t *variables; @@ -11835,46 +9961,6 @@ int pbx_builtin_setvar_multiple(struct ast_channel *chan, const char *vdata) return 0; } -int pbx_builtin_importvar(struct ast_channel *chan, const char *data) -{ - char *name; - char *value; - char *channel; - char tmp[VAR_BUF_SIZE]; - static int deprecation_warning = 0; - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n"); - return 0; - } - tmp[0] = 0; - if (!deprecation_warning) { - ast_log(LOG_WARNING, "ImportVar is deprecated. Please use Set(varname=${IMPORT(channel,variable)}) instead.\n"); - deprecation_warning = 1; - } - - value = ast_strdupa(data); - name = strsep(&value,"="); - channel = strsep(&value,","); - if (channel && value && name) { /*! \todo XXX should do !ast_strlen_zero(..) of the args ? */ - struct ast_channel *chan2 = ast_channel_get_by_name(channel); - if (chan2) { - char *s = ast_alloca(strlen(value) + 4); - sprintf(s, "${%s}", value); - pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1); - chan2 = ast_channel_unref(chan2); - } - pbx_builtin_setvar_helper(chan, name, tmp); - } - - return(0); -} - -static int pbx_builtin_noop(struct ast_channel *chan, const char *data) -{ - return 0; -} - void pbx_builtin_clear_globals(void) { struct ast_var_t *vardata; @@ -11897,191 +9983,6 @@ int pbx_checkcondition(const char *condition) } } -static int pbx_builtin_gotoif(struct ast_channel *chan, const char *data) -{ - char *condition, *branch1, *branch2, *branch; - char *stringp; - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "Ignoring, since there is no variable to check\n"); - return 0; - } - - stringp = ast_strdupa(data); - condition = strsep(&stringp,"?"); - branch1 = strsep(&stringp,":"); - branch2 = strsep(&stringp,""); - branch = pbx_checkcondition(condition) ? branch1 : branch2; - - if (ast_strlen_zero(branch)) { - ast_debug(1, "Not taking any branch\n"); - return 0; - } - - return pbx_builtin_goto(chan, branch); -} - -static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data) -{ - char tmp[256]; - char *number = tmp; - int number_val; - char *options; - int res; - int interrupt = 0; - const char *interrupt_string; - - ast_channel_lock(chan); - interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); - if (ast_true(interrupt_string)) { - interrupt = 1; - } - ast_channel_unlock(chan); - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "SayNumber requires an argument (number)\n"); - return -1; - } - ast_copy_string(tmp, data, sizeof(tmp)); - strsep(&number, ","); - - if (sscanf(tmp, "%d", &number_val) != 1) { - ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp); - return 0; - } - - options = strsep(&number, ","); - if (options) { - if ( strcasecmp(options, "f") && strcasecmp(options, "m") && - strcasecmp(options, "c") && strcasecmp(options, "n") ) { - ast_log(LOG_WARNING, "SayNumber gender option is either 'f', 'm', 'c' or 'n'\n"); - return -1; - } - } - - res = ast_say_number(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options); - - if (res < 0) { - ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp); - } - - return interrupt ? res : 0; -} - -static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data) -{ - int res = 0; - int interrupt = 0; - const char *interrupt_string; - - ast_channel_lock(chan); - interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); - if (ast_true(interrupt_string)) { - interrupt = 1; - } - ast_channel_unlock(chan); - - if (data) { - res = ast_say_digit_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan)); - } - - return res; -} - -static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data) -{ - int res = 0; - int sensitivity = 0; - char *parse; - int interrupt = 0; - const char *interrupt_string; - - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(options); - AST_APP_ARG(characters); - ); - - ast_channel_lock(chan); - interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); - if (ast_true(interrupt_string)) { - interrupt = 1; - } - ast_channel_unlock(chan); - - if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "SayAlphaCase requires two arguments (options, characters)\n"); - return 0; - } - - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(args, parse); - - if (!args.options || strlen(args.options) != 1) { - ast_log(LOG_WARNING, "SayAlphaCase options are mutually exclusive and required\n"); - return 0; - } - - switch (args.options[0]) { - case 'a': - sensitivity = AST_SAY_CASE_ALL; - break; - case 'l': - sensitivity = AST_SAY_CASE_LOWER; - break; - case 'n': - sensitivity = AST_SAY_CASE_NONE; - break; - case 'u': - sensitivity = AST_SAY_CASE_UPPER; - break; - default: - ast_log(LOG_WARNING, "Invalid option: '%s'\n", args.options); - return 0; - } - - res = ast_say_character_str(chan, args.characters, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), sensitivity); - - return res; -} - -static int pbx_builtin_saycharacters(struct ast_channel *chan, const char *data) -{ - int res = 0; - int interrupt = 0; - const char *interrupt_string; - - ast_channel_lock(chan); - interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); - if (ast_true(interrupt_string)) { - interrupt = 1; - } - ast_channel_unlock(chan); - - if (data) { - res = ast_say_character_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), AST_SAY_CASE_NONE); - } - - return res; -} - -static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data) -{ - int res = 0; - int interrupt = 0; - const char *interrupt_string; - - ast_channel_lock(chan); - interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); - if (ast_true(interrupt_string)) { - interrupt = 1; - } - ast_channel_unlock(chan); - - if (data) - res = ast_say_phonetic_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan)); - return res; -} - static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg) { struct ast_presence_state_message *presence_state = stasis_message_data(msg); @@ -12320,15 +10221,9 @@ static int action_extensionstatelist(struct mansession *s, const struct message */ static void unload_pbx(void) { - int x; - presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub); device_state_sub = stasis_unsubscribe_and_join(device_state_sub); - /* Unregister builtin applications */ - for (x = 0; x < ARRAY_LEN(builtins); x++) { - ast_unregister_application(builtins[x].name); - } ast_manager_unregister("ShowDialPlan"); ast_manager_unregister("ExtensionStateList"); ast_cli_unregister_multiple(pbx_cli, ARRAY_LEN(pbx_cli)); @@ -12340,27 +10235,18 @@ static void unload_pbx(void) int load_pbx(void) { int res = 0; - int x; ast_register_cleanup(unload_pbx); /* Initialize the PBX */ ast_verb(1, "Asterisk PBX Core Initializing\n"); - ast_verb(2, "Registering builtin applications and functions:\n"); + ast_verb(2, "Registering builtin functions:\n"); ast_cli_register_multiple(pbx_cli, ARRAY_LEN(pbx_cli)); ast_data_register_multiple_core(pbx_data_providers, ARRAY_LEN(pbx_data_providers)); __ast_custom_function_register(&exception_function, NULL); __ast_custom_function_register(&testtime_function, NULL); - /* Register builtin applications */ - for (x = 0; x < ARRAY_LEN(builtins); x++) { - if (ast_register_application2(builtins[x].name, builtins[x].execute, NULL, NULL, NULL)) { - ast_log(LOG_ERROR, "Unable to register builtin application '%s'\n", builtins[x].name); - return -1; - } - } - /* Register manager application */ res |= ast_manager_register_xml_core("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan); res |= ast_manager_register_xml_core("ExtensionStateList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstatelist); diff --git a/main/pbx_builtins.c b/main/pbx_builtins.c new file mode 100644 index 000000000..351e28c8d --- /dev/null +++ b/main/pbx_builtins.c @@ -0,0 +1,1500 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015 Fairview 5 Engineering, LLC + * + * George Joseph <george.joseph@fairview5.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 Core PBX builtin routines. + * + * \author George Joseph <george.joseph@fairview5.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/_private.h" +#include "asterisk/pbx.h" +#include "asterisk/causes.h" +#include "asterisk/indications.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/say.h" +#include "asterisk/app.h" +#include "asterisk/module.h" +#include "pbx_private.h" + + /*** DOCUMENTATION + <application name="Answer" language="en_US"> + <synopsis> + Answer a channel if ringing. + </synopsis> + <syntax> + <parameter name="delay"> + <para>Asterisk will wait this number of milliseconds before returning to + the dialplan after answering the call.</para> + </parameter> + </syntax> + <description> + <para>If the call has not been answered, this application will + answer it. Otherwise, it has no effect on the call.</para> + </description> + <see-also> + <ref type="application">Hangup</ref> + </see-also> + </application> + <application name="BackGround" language="en_US"> + <synopsis> + Play an audio file while waiting for digits of an extension to go to. + </synopsis> + <syntax> + <parameter name="filenames" required="true" argsep="&"> + <argument name="filename1" required="true" /> + <argument name="filename2" multiple="true" /> + </parameter> + <parameter name="options"> + <optionlist> + <option name="s"> + <para>Causes the playback of the message to be skipped + if the channel is not in the <literal>up</literal> state (i.e. it + hasn't been answered yet). If this happens, the + application will return immediately.</para> + </option> + <option name="n"> + <para>Don't answer the channel before playing the files.</para> + </option> + <option name="m"> + <para>Only break if a digit hit matches a one digit + extension in the destination context.</para> + </option> + </optionlist> + </parameter> + <parameter name="langoverride"> + <para>Explicitly specifies which language to attempt to use for the requested sound files.</para> + </parameter> + <parameter name="context"> + <para>This is the dialplan context that this application will use when exiting + to a dialed extension.</para> + </parameter> + </syntax> + <description> + <para>This application will play the given list of files <emphasis>(do not put extension)</emphasis> + while waiting for an extension to be dialed by the calling channel. To continue waiting + for digits after this application has finished playing files, the <literal>WaitExten</literal> + application should be used.</para> + <para>If one of the requested sound files does not exist, call processing will be terminated.</para> + <para>This application sets the following channel variable upon completion:</para> + <variablelist> + <variable name="BACKGROUNDSTATUS"> + <para>The status of the background attempt as a text string.</para> + <value name="SUCCESS" /> + <value name="FAILED" /> + </variable> + </variablelist> + </description> + <see-also> + <ref type="application">ControlPlayback</ref> + <ref type="application">WaitExten</ref> + <ref type="application">BackgroundDetect</ref> + <ref type="function">TIMEOUT</ref> + </see-also> + </application> + <application name="Busy" language="en_US"> + <synopsis> + Indicate the Busy condition. + </synopsis> + <syntax> + <parameter name="timeout"> + <para>If specified, the calling channel will be hung up after the specified number of seconds. + Otherwise, this application will wait until the calling channel hangs up.</para> + </parameter> + </syntax> + <description> + <para>This application will indicate the busy condition to the calling channel.</para> + </description> + <see-also> + <ref type="application">Congestion</ref> + <ref type="application">Progress</ref> + <ref type="application">Playtones</ref> + <ref type="application">Hangup</ref> + </see-also> + </application> + <application name="Congestion" language="en_US"> + <synopsis> + Indicate the Congestion condition. + </synopsis> + <syntax> + <parameter name="timeout"> + <para>If specified, the calling channel will be hung up after the specified number of seconds. + Otherwise, this application will wait until the calling channel hangs up.</para> + </parameter> + </syntax> + <description> + <para>This application will indicate the congestion condition to the calling channel.</para> + </description> + <see-also> + <ref type="application">Busy</ref> + <ref type="application">Progress</ref> + <ref type="application">Playtones</ref> + <ref type="application">Hangup</ref> + </see-also> + </application> + <application name="ExecIfTime" language="en_US"> + <synopsis> + Conditional application execution based on the current time. + </synopsis> + <syntax argsep="?"> + <parameter name="day_condition" required="true"> + <argument name="times" required="true" /> + <argument name="weekdays" required="true" /> + <argument name="mdays" required="true" /> + <argument name="months" required="true" /> + <argument name="timezone" required="false" /> + </parameter> + <parameter name="appname" required="true" hasparams="optional"> + <argument name="appargs" required="true" /> + </parameter> + </syntax> + <description> + <para>This application will execute the specified dialplan application, with optional + arguments, if the current time matches the given time specification.</para> + </description> + <see-also> + <ref type="application">Exec</ref> + <ref type="application">ExecIf</ref> + <ref type="application">TryExec</ref> + <ref type="application">GotoIfTime</ref> + </see-also> + </application> + <application name="Goto" language="en_US"> + <synopsis> + Jump to a particular priority, extension, or context. + </synopsis> + <syntax> + <parameter name="context" /> + <parameter name="extensions" /> + <parameter name="priority" required="true" /> + </syntax> + <description> + <para>This application will set the current context, extension, and priority in the channel structure. + After it completes, the pbx engine will continue dialplan execution at the specified location. + If no specific <replaceable>extension</replaceable>, or <replaceable>extension</replaceable> and + <replaceable>context</replaceable>, are specified, then this application will + just set the specified <replaceable>priority</replaceable> of the current extension.</para> + <para>At least a <replaceable>priority</replaceable> is required as an argument, or the goto will + return a <literal>-1</literal>, and the channel and call will be terminated.</para> + <para>If the location that is put into the channel information is bogus, and asterisk cannot + find that location in the dialplan, then the execution engine will try to find and execute the code in + the <literal>i</literal> (invalid) extension in the current context. If that does not exist, it will try to execute the + <literal>h</literal> extension. If neither the <literal>h</literal> nor <literal>i</literal> extensions + have been defined, the channel is hung up, and the execution of instructions on the channel is terminated. + What this means is that, for example, you specify a context that does not exist, then + it will not be possible to find the <literal>h</literal> or <literal>i</literal> extensions, + and the call will terminate!</para> + </description> + <see-also> + <ref type="application">GotoIf</ref> + <ref type="application">GotoIfTime</ref> + <ref type="application">Gosub</ref> + <ref type="application">Macro</ref> + </see-also> + </application> + <application name="GotoIf" language="en_US"> + <synopsis> + Conditional goto. + </synopsis> + <syntax argsep="?"> + <parameter name="condition" required="true" /> + <parameter name="destination" required="true" argsep=":"> + <argument name="labeliftrue"> + <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true. + Takes the form similar to Goto() of [[context,]extension,]priority.</para> + </argument> + <argument name="labeliffalse"> + <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false. + Takes the form similar to Goto() of [[context,]extension,]priority.</para> + </argument> + </parameter> + </syntax> + <description> + <para>This application will set the current context, extension, and priority in the channel structure + based on the evaluation of the given condition. After this application completes, the + pbx engine will continue dialplan execution at the specified location in the dialplan. + The labels are specified with the same syntax as used within the Goto application. + If the label chosen by the condition is omitted, no jump is performed, and the execution passes to the + next instruction. If the target location is bogus, and does not exist, the execution engine will try + to find and execute the code in the <literal>i</literal> (invalid) extension in the current context. + If that does not exist, it will try to execute the <literal>h</literal> extension. + If neither the <literal>h</literal> nor <literal>i</literal> extensions have been defined, + the channel is hung up, and the execution of instructions on the channel is terminated. + Remember that this command can set the current context, and if the context specified + does not exist, then it will not be able to find any 'h' or 'i' extensions there, and + the channel and call will both be terminated!.</para> + </description> + <see-also> + <ref type="application">Goto</ref> + <ref type="application">GotoIfTime</ref> + <ref type="application">GosubIf</ref> + <ref type="application">MacroIf</ref> + </see-also> + </application> + <application name="GotoIfTime" language="en_US"> + <synopsis> + Conditional Goto based on the current time. + </synopsis> + <syntax argsep="?"> + <parameter name="condition" required="true"> + <argument name="times" required="true" /> + <argument name="weekdays" required="true" /> + <argument name="mdays" required="true" /> + <argument name="months" required="true" /> + <argument name="timezone" required="false" /> + </parameter> + <parameter name="destination" required="true" argsep=":"> + <argument name="labeliftrue"> + <para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true. + Takes the form similar to Goto() of [[context,]extension,]priority.</para> + </argument> + <argument name="labeliffalse"> + <para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false. + Takes the form similar to Goto() of [[context,]extension,]priority.</para> + </argument> + </parameter> + </syntax> + <description> + <para>This application will set the context, extension, and priority in the channel structure + based on the evaluation of the given time specification. After this application completes, + the pbx engine will continue dialplan execution at the specified location in the dialplan. + If the current time is within the given time specification, the channel will continue at + <replaceable>labeliftrue</replaceable>. Otherwise the channel will continue at <replaceable>labeliffalse</replaceable>. + If the label chosen by the condition is omitted, no jump is performed, and execution passes to the next + instruction. If the target jump location is bogus, the same actions would be taken as for <literal>Goto</literal>. + Further information on the time specification can be found in examples + illustrating how to do time-based context includes in the dialplan.</para> + </description> + <see-also> + <ref type="application">GotoIf</ref> + <ref type="application">Goto</ref> + <ref type="function">IFTIME</ref> + <ref type="function">TESTTIME</ref> + </see-also> + </application> + <application name="ImportVar" language="en_US"> + <synopsis> + Import a variable from a channel into a new variable. + </synopsis> + <syntax argsep="="> + <parameter name="newvar" required="true" /> + <parameter name="vardata" required="true"> + <argument name="channelname" required="true" /> + <argument name="variable" required="true" /> + </parameter> + </syntax> + <description> + <para>This application imports a <replaceable>variable</replaceable> from the specified + <replaceable>channel</replaceable> (as opposed to the current one) and stores it as a variable + (<replaceable>newvar</replaceable>) in the current channel (the channel that is calling this + application). Variables created by this application have the same inheritance properties as those + created with the <literal>Set</literal> application.</para> + </description> + <see-also> + <ref type="application">Set</ref> + </see-also> + </application> + <application name="Hangup" language="en_US"> + <synopsis> + Hang up the calling channel. + </synopsis> + <syntax> + <parameter name="causecode"> + <para>If a <replaceable>causecode</replaceable> is given the channel's + hangup cause will be set to the given value.</para> + </parameter> + </syntax> + <description> + <para>This application will hang up the calling channel.</para> + </description> + <see-also> + <ref type="application">Answer</ref> + <ref type="application">Busy</ref> + <ref type="application">Congestion</ref> + </see-also> + </application> + <application name="Incomplete" language="en_US"> + <synopsis> + Returns AST_PBX_INCOMPLETE value. + </synopsis> + <syntax> + <parameter name="n"> + <para>If specified, then Incomplete will not attempt to answer the channel first.</para> + <note><para>Most channel types need to be in Answer state in order to receive DTMF.</para></note> + </parameter> + </syntax> + <description> + <para>Signals the PBX routines that the previous matched extension is incomplete + and that further input should be allowed before matching can be considered + to be complete. Can be used within a pattern match when certain criteria warrants + a longer match.</para> + </description> + </application> + <application name="NoOp" language="en_US"> + <synopsis> + Do Nothing (No Operation). + </synopsis> + <syntax> + <parameter name="text"> + <para>Any text provided can be viewed at the Asterisk CLI.</para> + </parameter> + </syntax> + <description> + <para>This application does nothing. However, it is useful for debugging purposes.</para> + <para>This method can be used to see the evaluations of variables or functions without having any effect.</para> + </description> + <see-also> + <ref type="application">Verbose</ref> + <ref type="application">Log</ref> + </see-also> + </application> + <application name="Proceeding" language="en_US"> + <synopsis> + Indicate proceeding. + </synopsis> + <syntax /> + <description> + <para>This application will request that a proceeding message be provided to the calling channel.</para> + </description> + </application> + <application name="Progress" language="en_US"> + <synopsis> + Indicate progress. + </synopsis> + <syntax /> + <description> + <para>This application will request that in-band progress information be provided to the calling channel.</para> + </description> + <see-also> + <ref type="application">Busy</ref> + <ref type="application">Congestion</ref> + <ref type="application">Ringing</ref> + <ref type="application">Playtones</ref> + </see-also> + </application> + <application name="RaiseException" language="en_US"> + <synopsis> + Handle an exceptional condition. + </synopsis> + <syntax> + <parameter name="reason" required="true" /> + </syntax> + <description> + <para>This application will jump to the <literal>e</literal> extension in the current context, setting the + dialplan function EXCEPTION(). If the <literal>e</literal> extension does not exist, the call will hangup.</para> + </description> + <see-also> + <ref type="function">Exception</ref> + </see-also> + </application> + <application name="Ringing" language="en_US"> + <synopsis> + Indicate ringing tone. + </synopsis> + <syntax /> + <description> + <para>This application will request that the channel indicate a ringing tone to the user.</para> + </description> + <see-also> + <ref type="application">Busy</ref> + <ref type="application">Congestion</ref> + <ref type="application">Progress</ref> + <ref type="application">Playtones</ref> + </see-also> + </application> + <application name="SayAlpha" language="en_US"> + <synopsis> + Say Alpha. + </synopsis> + <syntax> + <parameter name="string" required="true" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the letters + of the given <replaceable>string</replaceable>. If the channel variable + <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), + then this application will react to DTMF in the same way as + <literal>Background</literal>.</para> + </description> + <see-also> + <ref type="application">SayDigits</ref> + <ref type="application">SayNumber</ref> + <ref type="application">SayPhonetic</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </application> + <application name="SayAlphaCase" language="en_US"> + <synopsis> + Say Alpha. + </synopsis> + <syntax> + <parameter name="casetype" required="true" > + <enumlist> + <enum name="a"> + <para>Case sensitive (all) pronunciation. + (Ex: SayAlphaCase(a,aBc); - lowercase a uppercase b lowercase c).</para> + </enum> + <enum name="l"> + <para>Case sensitive (lower) pronunciation. + (Ex: SayAlphaCase(l,aBc); - lowercase a b lowercase c).</para> + </enum> + <enum name="n"> + <para>Case insensitive pronunciation. Equivalent to SayAlpha. + (Ex: SayAlphaCase(n,aBc) - a b c).</para> + </enum> + <enum name="u"> + <para>Case sensitive (upper) pronunciation. + (Ex: SayAlphaCase(u,aBc); - a uppercase b c).</para> + </enum> + </enumlist> + </parameter> + <parameter name="string" required="true" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the letters of the + given <replaceable>string</replaceable>. Optionally, a <replaceable>casetype</replaceable> may be + specified. This will be used for case-insensitive or case-sensitive pronunciations. If the channel + variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this + application will react to DTMF in the same way as <literal>Background</literal>.</para> + </description> + <see-also> + <ref type="application">SayDigits</ref> + <ref type="application">SayNumber</ref> + <ref type="application">SayPhonetic</ref> + <ref type="application">SayAlpha</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </application> + <application name="SayDigits" language="en_US"> + <synopsis> + Say Digits. + </synopsis> + <syntax> + <parameter name="digits" required="true" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the digits of + the given number. This will use the language that is currently set for the channel. + If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' + (case insensitive), then this application will react to DTMF in the same way as + <literal>Background</literal>.</para> + </description> + <see-also> + <ref type="application">SayAlpha</ref> + <ref type="application">SayNumber</ref> + <ref type="application">SayPhonetic</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </application> + <application name="SayNumber" language="en_US"> + <synopsis> + Say Number. + </synopsis> + <syntax> + <parameter name="digits" required="true" /> + <parameter name="gender" /> + </syntax> + <description> + <para>This application will play the sounds that correspond to the given + <replaceable>digits</replaceable>. Optionally, a <replaceable>gender</replaceable> may be + specified. This will use the language that is currently set for the channel. See the CHANNEL() + function for more information on setting the language for the channel. If the channel variable + <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this + application will react to DTMF in the same way as <literal>Background</literal>.</para> + </description> + <see-also> + <ref type="application">SayAlpha</ref> + <ref type="application">SayDigits</ref> + <ref type="application">SayPhonetic</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </application> + <application name="SayPhonetic" language="en_US"> + <synopsis> + Say Phonetic. + </synopsis> + <syntax> + <parameter name="string" required="true" /> + </syntax> + <description> + <para>This application will play the sounds from the phonetic alphabet that correspond to the + letters in the given <replaceable>string</replaceable>. If the channel variable + <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this + application will react to DTMF in the same way as <literal>Background</literal>.</para> + </description> + <see-also> + <ref type="application">SayAlpha</ref> + <ref type="application">SayDigits</ref> + <ref type="application">SayNumber</ref> + </see-also> + </application> + <application name="Set" language="en_US"> + <synopsis> + Set channel variable or function value. + </synopsis> + <syntax argsep="="> + <parameter name="name" required="true" /> + <parameter name="value" required="true" /> + </syntax> + <description> + <para>This function can be used to set the value of channel variables or dialplan functions. + When setting variables, if the variable name is prefixed with <literal>_</literal>, + the variable will be inherited into channels created from the current channel. + If the variable name is prefixed with <literal>__</literal>, the variable will be + inherited into channels created from the current channel and all children channels.</para> + <note><para>If (and only if), in <filename>/etc/asterisk/asterisk.conf</filename>, you have + a <literal>[compat]</literal> category, and you have <literal>app_set = 1.4</literal> under that, then + the behavior of this app changes, and strips surrounding quotes from the right hand side as + it did previously in 1.4. + The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar) + were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly + protect separators and quotes in various database access strings has been greatly + reduced by these changes.</para></note> + </description> + <see-also> + <ref type="application">MSet</ref> + <ref type="function">GLOBAL</ref> + <ref type="function">SET</ref> + <ref type="function">ENV</ref> + </see-also> + </application> + <application name="MSet" language="en_US"> + <synopsis> + Set channel variable(s) or function value(s). + </synopsis> + <syntax> + <parameter name="set1" required="true" argsep="="> + <argument name="name1" required="true" /> + <argument name="value1" required="true" /> + </parameter> + <parameter name="set2" multiple="true" argsep="="> + <argument name="name2" required="true" /> + <argument name="value2" required="true" /> + </parameter> + </syntax> + <description> + <para>This function can be used to set the value of channel variables or dialplan functions. + When setting variables, if the variable name is prefixed with <literal>_</literal>, + the variable will be inherited into channels created from the current channel + If the variable name is prefixed with <literal>__</literal>, the variable will be + inherited into channels created from the current channel and all children channels. + MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus + prone to doing things that you may not expect. For example, it strips surrounding + double-quotes from the right-hand side (value). If you need to put a separator + character (comma or vert-bar), you will need to escape them by inserting a backslash + before them. Avoid its use if possible.</para> + </description> + <see-also> + <ref type="application">Set</ref> + </see-also> + </application> + <application name="SetAMAFlags" language="en_US"> + <synopsis> + Set the AMA Flags. + </synopsis> + <syntax> + <parameter name="flag" /> + </syntax> + <description> + <para>This application will set the channel's AMA Flags for billing purposes.</para> + <warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning> + </description> + <see-also> + <ref type="function">CDR</ref> + <ref type="function">CHANNEL</ref> + </see-also> + </application> + <application name="Wait" language="en_US"> + <synopsis> + Waits for some time. + </synopsis> + <syntax> + <parameter name="seconds" required="true"> + <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the + application to wait for 1.5 seconds.</para> + </parameter> + </syntax> + <description> + <para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para> + </description> + </application> + <application name="WaitExten" language="en_US"> + <synopsis> + Waits for an extension to be entered. + </synopsis> + <syntax> + <parameter name="seconds"> + <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the + application to wait for 1.5 seconds.</para> + </parameter> + <parameter name="options"> + <optionlist> + <option name="m"> + <para>Provide music on hold to the caller while waiting for an extension.</para> + <argument name="x"> + <para>Specify the class for music on hold. <emphasis>CHANNEL(musicclass) will + be used instead if set</emphasis></para> + </argument> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>This application waits for the user to enter a new extension for a specified number + of <replaceable>seconds</replaceable>.</para> + <xi:include xpointer="xpointer(/docs/application[@name='Macro']/description/warning[2])" /> + </description> + <see-also> + <ref type="application">Background</ref> + <ref type="function">TIMEOUT</ref> + </see-also> + </application> + ***/ + +#define BACKGROUND_SKIP (1 << 0) +#define BACKGROUND_NOANSWER (1 << 1) +#define BACKGROUND_MATCHEXTEN (1 << 2) +#define BACKGROUND_PLAYBACK (1 << 3) + +AST_APP_OPTIONS(background_opts, { + AST_APP_OPTION('s', BACKGROUND_SKIP), + AST_APP_OPTION('n', BACKGROUND_NOANSWER), + AST_APP_OPTION('m', BACKGROUND_MATCHEXTEN), + AST_APP_OPTION('p', BACKGROUND_PLAYBACK), +}); + +#define WAITEXTEN_MOH (1 << 0) +#define WAITEXTEN_DIALTONE (1 << 1) + +AST_APP_OPTIONS(waitexten_opts, { + AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 0), + AST_APP_OPTION_ARG('d', WAITEXTEN_DIALTONE, 0), +}); + +int pbx_builtin_raise_exception(struct ast_channel *chan, const char *reason) +{ + /* Priority will become 1, next time through the AUTOLOOP */ + return raise_exception(chan, reason, 0); +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_proceeding(struct ast_channel *chan, const char *data) +{ + ast_indicate(chan, AST_CONTROL_PROCEEDING); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_progress(struct ast_channel *chan, const char *data) +{ + ast_indicate(chan, AST_CONTROL_PROGRESS); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_ringing(struct ast_channel *chan, const char *data) +{ + ast_indicate(chan, AST_CONTROL_RINGING); + return 0; +} + +/*! + * \ingroup applications + */ +int indicate_busy(struct ast_channel *chan, const char *data) +{ + ast_indicate(chan, AST_CONTROL_BUSY); + /* Don't change state of an UP channel, just indicate + busy in audio */ + ast_channel_lock(chan); + if (ast_channel_state(chan) != AST_STATE_UP) { + ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY); + ast_setstate(chan, AST_STATE_BUSY); + } + ast_channel_unlock(chan); + wait_for_hangup(chan, data); + return -1; +} + +/*! + * \ingroup applications + */ +int indicate_congestion(struct ast_channel *chan, const char *data) +{ + ast_indicate(chan, AST_CONTROL_CONGESTION); + /* Don't change state of an UP channel, just indicate + congestion in audio */ + ast_channel_lock(chan); + if (ast_channel_state(chan) != AST_STATE_UP) { + ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION); + ast_setstate(chan, AST_STATE_BUSY); + } + ast_channel_unlock(chan); + wait_for_hangup(chan, data); + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_answer(struct ast_channel *chan, const char *data) +{ + int delay = 0; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(delay); + AST_APP_ARG(answer_cdr); + ); + + if (ast_strlen_zero(data)) { + return __ast_answer(chan, 0); + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (!ast_strlen_zero(args.delay) && (ast_channel_state(chan) != AST_STATE_UP)) + delay = atoi(data); + + if (delay < 0) { + delay = 0; + } + + if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) { + ast_log(AST_LOG_WARNING, "The nocdr option for the Answer application has been removed and is no longer supported.\n"); + } + + return __ast_answer(chan, delay); +} + +static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) +{ + const char *options = data; + int answer = 1; + + /* Some channels can receive DTMF in unanswered state; some cannot */ + if (!ast_strlen_zero(options) && strchr(options, 'n')) { + answer = 0; + } + + /* If the channel is hungup, stop waiting */ + if (ast_check_hangup(chan)) { + return -1; + } else if (ast_channel_state(chan) != AST_STATE_UP && answer) { + __ast_answer(chan, 0); + } + + ast_indicate(chan, AST_CONTROL_INCOMPLETE); + + return AST_PBX_INCOMPLETE; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) +{ + ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n"); + + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n"); + return 0; + } + /* Copy the AMA Flags as specified */ + ast_channel_lock(chan); + if (isdigit(data[0])) { + int amaflags; + if (sscanf(data, "%30d", &amaflags) != 1) { + ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan)); + ast_channel_unlock(chan); + return 0; + } + ast_channel_amaflags_set(chan, amaflags); + } else { + ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data)); + } + ast_channel_unlock(chan); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_hangup(struct ast_channel *chan, const char *data) +{ + int cause; + + ast_set_hangupsource(chan, "dialplan/builtin", 0); + + if (!ast_strlen_zero(data)) { + cause = ast_str2cause(data); + if (cause <= 0) { + if (sscanf(data, "%30d", &cause) != 1 || cause <= 0) { + ast_log(LOG_WARNING, "Invalid cause given to Hangup(): \"%s\"\n", data); + cause = 0; + } + } + } else { + cause = 0; + } + + ast_channel_lock(chan); + if (cause <= 0) { + cause = ast_channel_hangupcause(chan); + if (cause <= 0) { + cause = AST_CAUSE_NORMAL_CLEARING; + } + } + ast_channel_hangupcause_set(chan, cause); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unlock(chan); + + return -1; +} + +/*! Goto + * \ingroup applications + */ +static int pbx_builtin_goto(struct ast_channel *chan, const char *data) +{ + int res = ast_parseable_goto(chan, data); + if (!res) + ast_verb(3, "Goto (%s,%s,%d)\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1); + return res; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_gotoiftime(struct ast_channel *chan, const char *data) +{ + char *s, *ts, *branch1, *branch2, *branch; + struct ast_timing timing; + const char *ctime; + struct timeval tv = ast_tvnow(); + long timesecs; + + if (!chan) { + ast_log(LOG_WARNING, "GotoIfTime requires a channel on which to operate\n"); + return -1; + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>[,<timezone>]?'labeliftrue':'labeliffalse'\n"); + return -1; + } + + ts = s = ast_strdupa(data); + + ast_channel_lock(chan); + if ((ctime = pbx_builtin_getvar_helper(chan, "TESTTIME")) && sscanf(ctime, "%ld", ×ecs) == 1) { + tv.tv_sec = timesecs; + } else if (ctime) { + ast_log(LOG_WARNING, "Using current time to evaluate\n"); + /* Reset when unparseable */ + pbx_builtin_setvar_helper(chan, "TESTTIME", NULL); + } + ast_channel_unlock(chan); + + /* Separate the Goto path */ + strsep(&ts, "?"); + branch1 = strsep(&ts,":"); + branch2 = strsep(&ts,""); + + /* struct ast_include include contained garbage here, fixed by zeroing it on get_timerange */ + if (ast_build_timing(&timing, s) && ast_check_timing2(&timing, tv)) { + branch = branch1; + } else { + branch = branch2; + } + ast_destroy_timing(&timing); + + if (ast_strlen_zero(branch)) { + ast_debug(1, "Not taking any branch\n"); + return 0; + } + + return pbx_builtin_goto(chan, branch); +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data) +{ + char *s, *appname; + struct ast_timing timing; + struct ast_app *app; + static const char * const usage = "ExecIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>[,<timezone>]?<appname>[(<appargs>)]"; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s\n", usage); + return -1; + } + + appname = ast_strdupa(data); + + s = strsep(&appname, "?"); /* Separate the timerange and application name/data */ + if (!appname) { /* missing application */ + ast_log(LOG_WARNING, "%s\n", usage); + return -1; + } + + if (!ast_build_timing(&timing, s)) { + ast_log(LOG_WARNING, "Invalid Time Spec: %s\nCorrect usage: %s\n", s, usage); + ast_destroy_timing(&timing); + return -1; + } + + if (!ast_check_timing(&timing)) { /* outside the valid time window, just return */ + ast_destroy_timing(&timing); + return 0; + } + ast_destroy_timing(&timing); + + /* now split appname(appargs) */ + if ((s = strchr(appname, '('))) { + char *e; + *s++ = '\0'; + if ((e = strrchr(s, ')'))) + *e = '\0'; + else + ast_log(LOG_WARNING, "Failed to find closing parenthesis\n"); + } + + + if ((app = pbx_findapp(appname))) { + return pbx_exec(chan, app, S_OR(s, "")); + } else { + ast_log(LOG_WARNING, "Cannot locate application %s\n", appname); + return -1; + } +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_wait(struct ast_channel *chan, const char *data) +{ + int ms; + + /* Wait for "n" seconds */ + if (!ast_app_parse_timelen(data, &ms, TIMELEN_SECONDS) && ms > 0) { + return ast_safe_sleep(chan, ms); + } + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data) +{ + int ms, res; + struct ast_flags flags = {0}; + char *opts[1] = { NULL }; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(timeout); + AST_APP_ARG(options); + ); + + if (!ast_strlen_zero(data)) { + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + } else + memset(&args, 0, sizeof(args)); + + if (args.options) + ast_app_parse_options(waitexten_opts, &flags, opts, args.options); + + if (ast_test_flag(&flags, WAITEXTEN_MOH) && !opts[0] ) { + ast_log(LOG_WARNING, "The 'm' option has been specified for WaitExten without a class.\n"); + } else if (ast_test_flag(&flags, WAITEXTEN_MOH)) { + ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(opts[0], NULL), + !ast_strlen_zero(opts[0]) ? strlen(opts[0]) + 1 : 0); + } else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) { + struct ast_tone_zone_sound *ts = ast_get_indication_tone(ast_channel_zone(chan), "dial"); + if (ts) { + ast_playtones_start(chan, 0, ts->data, 0); + ts = ast_tone_zone_sound_unref(ts); + } else { + ast_tonepair_start(chan, 350, 440, 0, 0); + } + } + /* Wait for "n" seconds */ + if (!ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) && ms > 0) { + /* Yay! */ + } else if (ast_channel_pbx(chan)) { + ms = ast_channel_pbx(chan)->rtimeoutms; + } else { + ms = 10000; + } + + res = ast_waitfordigit(chan, ms); + if (!res) { + if (ast_check_hangup(chan)) { + /* Call is hungup for some reason. */ + res = -1; + } else if (ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { + ast_verb(3, "Timeout on %s, continuing...\n", ast_channel_name(chan)); + } else if (ast_exists_extension(chan, ast_channel_context(chan), "t", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { + ast_verb(3, "Timeout on %s, going to 't'\n", ast_channel_name(chan)); + set_ext_pri(chan, "t", 0); /* 0 will become 1, next time through the loop */ + } else if (ast_exists_extension(chan, ast_channel_context(chan), "e", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { + raise_exception(chan, "RESPONSETIMEOUT", 0); /* 0 will become 1, next time through the loop */ + } else { + ast_log(LOG_WARNING, "Timeout but no rule 't' or 'e' in context '%s'\n", + ast_channel_context(chan)); + res = -1; + } + } + + if (ast_test_flag(&flags, WAITEXTEN_MOH)) + ast_indicate(chan, AST_CONTROL_UNHOLD); + else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) + ast_playtones_stop(chan); + + return res; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_background(struct ast_channel *chan, const char *data) +{ + int res = 0; + int mres = 0; + struct ast_flags flags = {0}; + char *parse, exten[2] = ""; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filename); + AST_APP_ARG(options); + AST_APP_ARG(lang); + AST_APP_ARG(context); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Background requires an argument (filename)\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.lang)) + args.lang = (char *)ast_channel_language(chan); /* XXX this is const */ + + if (ast_strlen_zero(args.context)) { + const char *context; + ast_channel_lock(chan); + if ((context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"))) { + args.context = ast_strdupa(context); + } else { + args.context = ast_strdupa(ast_channel_context(chan)); + } + ast_channel_unlock(chan); + } + + if (args.options) { + if (!strcasecmp(args.options, "skip")) + flags.flags = BACKGROUND_SKIP; + else if (!strcasecmp(args.options, "noanswer")) + flags.flags = BACKGROUND_NOANSWER; + else + ast_app_parse_options(background_opts, &flags, NULL, args.options); + } + + /* Answer if need be */ + if (ast_channel_state(chan) != AST_STATE_UP) { + if (ast_test_flag(&flags, BACKGROUND_SKIP)) { + goto done; + } else if (!ast_test_flag(&flags, BACKGROUND_NOANSWER)) { + res = ast_answer(chan); + } + } + + if (!res) { + char *back = ast_strip(args.filename); + char *front; + + ast_stopstream(chan); /* Stop anything playing */ + /* Stream the list of files */ + while (!res && (front = strsep(&back, "&")) ) { + if ( (res = ast_streamfile(chan, front, args.lang)) ) { + ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char*)data); + res = 0; + mres = 1; + break; + } + if (ast_test_flag(&flags, BACKGROUND_PLAYBACK)) { + res = ast_waitstream(chan, ""); + } else if (ast_test_flag(&flags, BACKGROUND_MATCHEXTEN)) { + res = ast_waitstream_exten(chan, args.context); + } else { + res = ast_waitstream(chan, AST_DIGIT_ANY); + } + ast_stopstream(chan); + } + } + + /* + * If the single digit DTMF is an extension in the specified context, then + * go there and signal no DTMF. Otherwise, we should exit with that DTMF. + * If we're in Macro, we'll exit and seek that DTMF as the beginning of an + * extension in the Macro's calling context. If we're not in Macro, then + * we'll simply seek that extension in the calling context. Previously, + * someone complained about the behavior as it related to the interior of a + * Gosub routine, and the fix (#14011) inadvertently broke FreePBX + * (#14940). This change should fix both of these situations, but with the + * possible incompatibility that if a single digit extension does not exist + * (but a longer extension COULD have matched), it would have previously + * gone immediately to the "i" extension, but will now need to wait for a + * timeout. + * + * Later, we had to add a flag to disable this workaround, because AGI + * users can EXEC Background and reasonably expect that the DTMF code will + * be returned (see #16434). + */ + if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS) + && (exten[0] = res) + && ast_canmatch_extension(chan, args.context, exten, 1, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) + && !ast_matchmore_extension(chan, args.context, exten, 1, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { + char buf[2] = { 0, }; + snprintf(buf, sizeof(buf), "%c", res); + ast_channel_exten_set(chan, buf); + ast_channel_context_set(chan, args.context); + ast_channel_priority_set(chan, 0); + res = 0; + } +done: + pbx_builtin_setvar_helper(chan, "BACKGROUNDSTATUS", mres ? "FAILED" : "SUCCESS"); + return res; +} + +static int pbx_builtin_noop(struct ast_channel *chan, const char *data) +{ + return 0; +} + +static int pbx_builtin_gotoif(struct ast_channel *chan, const char *data) +{ + char *condition, *branch1, *branch2, *branch; + char *stringp; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Ignoring, since there is no variable to check\n"); + return 0; + } + + stringp = ast_strdupa(data); + condition = strsep(&stringp,"?"); + branch1 = strsep(&stringp,":"); + branch2 = strsep(&stringp,""); + branch = pbx_checkcondition(condition) ? branch1 : branch2; + + if (ast_strlen_zero(branch)) { + ast_debug(1, "Not taking any branch\n"); + return 0; + } + + return pbx_builtin_goto(chan, branch); +} + +static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data) +{ + char tmp[256]; + char *number = tmp; + int number_val; + char *options; + int res; + int interrupt = 0; + const char *interrupt_string; + + ast_channel_lock(chan); + interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); + if (ast_true(interrupt_string)) { + interrupt = 1; + } + ast_channel_unlock(chan); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SayNumber requires an argument (number)\n"); + return -1; + } + ast_copy_string(tmp, data, sizeof(tmp)); + strsep(&number, ","); + + if (sscanf(tmp, "%d", &number_val) != 1) { + ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp); + return 0; + } + + options = strsep(&number, ","); + if (options) { + if ( strcasecmp(options, "f") && strcasecmp(options, "m") && + strcasecmp(options, "c") && strcasecmp(options, "n") ) { + ast_log(LOG_WARNING, "SayNumber gender option is either 'f', 'm', 'c' or 'n'\n"); + return -1; + } + } + + res = ast_say_number(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options); + + if (res < 0) { + ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp); + } + + return interrupt ? res : 0; +} + +static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data) +{ + int res = 0; + int interrupt = 0; + const char *interrupt_string; + + ast_channel_lock(chan); + interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); + if (ast_true(interrupt_string)) { + interrupt = 1; + } + ast_channel_unlock(chan); + + if (data) { + res = ast_say_digit_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan)); + } + + return res; +} + +static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data) +{ + int res = 0; + int sensitivity = 0; + char *parse; + int interrupt = 0; + const char *interrupt_string; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(options); + AST_APP_ARG(characters); + ); + + ast_channel_lock(chan); + interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); + if (ast_true(interrupt_string)) { + interrupt = 1; + } + ast_channel_unlock(chan); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SayAlphaCase requires two arguments (options, characters)\n"); + return 0; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!args.options || strlen(args.options) != 1) { + ast_log(LOG_WARNING, "SayAlphaCase options are mutually exclusive and required\n"); + return 0; + } + + switch (args.options[0]) { + case 'a': + sensitivity = AST_SAY_CASE_ALL; + break; + case 'l': + sensitivity = AST_SAY_CASE_LOWER; + break; + case 'n': + sensitivity = AST_SAY_CASE_NONE; + break; + case 'u': + sensitivity = AST_SAY_CASE_UPPER; + break; + default: + ast_log(LOG_WARNING, "Invalid option: '%s'\n", args.options); + return 0; + } + + res = ast_say_character_str(chan, args.characters, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), sensitivity); + + return res; +} + +static int pbx_builtin_saycharacters(struct ast_channel *chan, const char *data) +{ + int res = 0; + int interrupt = 0; + const char *interrupt_string; + + ast_channel_lock(chan); + interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); + if (ast_true(interrupt_string)) { + interrupt = 1; + } + ast_channel_unlock(chan); + + if (data) { + res = ast_say_character_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), AST_SAY_CASE_NONE); + } + + return res; +} + +static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data) +{ + int res = 0; + int interrupt = 0; + const char *interrupt_string; + + ast_channel_lock(chan); + interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT"); + if (ast_true(interrupt_string)) { + interrupt = 1; + } + ast_channel_unlock(chan); + + if (data) + res = ast_say_phonetic_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan)); + return res; +} + +static int pbx_builtin_importvar(struct ast_channel *chan, const char *data) +{ + char *name; + char *value; + char *channel; + char tmp[VAR_BUF_SIZE]; + static int deprecation_warning = 0; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n"); + return 0; + } + tmp[0] = 0; + if (!deprecation_warning) { + ast_log(LOG_WARNING, "ImportVar is deprecated. Please use Set(varname=${IMPORT(channel,variable)}) instead.\n"); + deprecation_warning = 1; + } + + value = ast_strdupa(data); + name = strsep(&value,"="); + channel = strsep(&value,","); + if (channel && value && name) { /*! \todo XXX should do !ast_strlen_zero(..) of the args ? */ + struct ast_channel *chan2 = ast_channel_get_by_name(channel); + if (chan2) { + char *s = ast_alloca(strlen(value) + 4); + sprintf(s, "${%s}", value); + pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1); + chan2 = ast_channel_unref(chan2); + } + pbx_builtin_setvar_helper(chan, name, tmp); + } + + return(0); +} + +/*! \brief Declaration of builtin applications */ +struct pbx_builtin { + char name[AST_MAX_APP]; + int (*execute)(struct ast_channel *chan, const char *data); +} builtins[] = +{ + /* These applications are built into the PBX core and do not + need separate modules */ + + { "Answer", pbx_builtin_answer }, + { "BackGround", pbx_builtin_background }, + { "Busy", indicate_busy }, + { "Congestion", indicate_congestion }, + { "ExecIfTime", pbx_builtin_execiftime }, + { "Goto", pbx_builtin_goto }, + { "GotoIf", pbx_builtin_gotoif }, + { "GotoIfTime", pbx_builtin_gotoiftime }, + { "ImportVar", pbx_builtin_importvar }, + { "Hangup", pbx_builtin_hangup }, + { "Incomplete", pbx_builtin_incomplete }, + { "NoOp", pbx_builtin_noop }, + { "Proceeding", pbx_builtin_proceeding }, + { "Progress", pbx_builtin_progress }, + { "RaiseException", pbx_builtin_raise_exception }, + { "Ringing", pbx_builtin_ringing }, + { "SayAlpha", pbx_builtin_saycharacters }, + { "SayAlphaCase", pbx_builtin_saycharacters_case }, + { "SayDigits", pbx_builtin_saydigits }, + { "SayNumber", pbx_builtin_saynumber }, + { "SayPhonetic", pbx_builtin_sayphonetic }, + { "Set", pbx_builtin_setvar }, + { "MSet", pbx_builtin_setvar_multiple }, + { "SetAMAFlags", pbx_builtin_setamaflags }, + { "Wait", pbx_builtin_wait }, + { "WaitExten", pbx_builtin_waitexten } +}; + +static void unload_pbx_builtins(void) +{ + int x; + + /* Unregister builtin applications */ + for (x = 0; x < ARRAY_LEN(builtins); x++) { + ast_unregister_application(builtins[x].name); + } +} + +int load_pbx_builtins(void) +{ + int x; + + /* Register builtin applications */ + for (x = 0; x < ARRAY_LEN(builtins); x++) { + if (ast_register_application2(builtins[x].name, builtins[x].execute, NULL, NULL, NULL)) { + ast_log(LOG_ERROR, "Unable to register builtin application '%s'\n", builtins[x].name); + unload_pbx_builtins(); + return -1; + } + } + + ast_register_cleanup(unload_pbx_builtins); + + return 0; +} diff --git a/main/pbx_functions.c b/main/pbx_functions.c new file mode 100644 index 000000000..b8be2bc40 --- /dev/null +++ b/main/pbx_functions.c @@ -0,0 +1,723 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, CFWare, LLC + * + * Corey Farrell <git@cfware.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 Custom function management routines. + * + * \author Corey Farrell <git@cfware.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/_private.h" +#include "asterisk/cli.h" +#include "asterisk/linkedlists.h" +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/term.h" +#include "asterisk/threadstorage.h" +#include "asterisk/xmldoc.h" +#include "pbx_private.h" + +/*! + * \brief A thread local indicating whether the current thread can run + * 'dangerous' dialplan functions. + */ +AST_THREADSTORAGE(thread_inhibit_escalations_tl); + +/*! + * \brief Set to true (non-zero) to globally allow all dangerous dialplan + * functions to run. + */ +static int live_dangerously; + +/*! + * \brief Registered functions container. + * + * It is sorted by function name. + */ +static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); + +static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_custom_function *acf; + int count_acf = 0; + int like = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "core show functions [like]"; + e->usage = + "Usage: core show functions [like <text>]\n" + " List builtin functions, optionally only those matching a given string\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) { + like = 1; + } else if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_cli(a->fd, "%s Custom Functions:\n" + "--------------------------------------------------------------------------------\n", + like ? "Matching" : "Installed"); + + AST_RWLIST_RDLOCK(&acf_root); + AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { + if (!like || strstr(acf->name, a->argv[4])) { + count_acf++; + ast_cli(a->fd, "%-20.20s %-35.35s %s\n", + S_OR(acf->name, ""), + S_OR(acf->syntax, ""), + S_OR(acf->synopsis, "")); + } + } + AST_RWLIST_UNLOCK(&acf_root); + + ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : ""); + + return CLI_SUCCESS; +} + +static char *complete_functions(const char *word, int pos, int state) +{ + struct ast_custom_function *cur; + char *ret = NULL; + int which = 0; + int wordlen; + int cmp; + + if (pos != 3) { + return NULL; + } + + wordlen = strlen(word); + AST_RWLIST_RDLOCK(&acf_root); + AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { + /* + * Do a case-insensitive search for convenience in this + * 'complete' function. + * + * We must search the entire container because the functions are + * sorted and normally found case sensitively. + */ + cmp = strncasecmp(word, cur->name, wordlen); + if (!cmp) { + /* Found match. */ + if (++which <= state) { + /* Not enough matches. */ + continue; + } + ret = ast_strdup(cur->name); + break; + } + } + AST_RWLIST_UNLOCK(&acf_root); + + return ret; +} + +static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_custom_function *acf; + /* Maximum number of characters added by terminal coloring is 22 */ + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL; + char stxtitle[40], *syntax = NULL, *arguments = NULL; + int syntax_size, description_size, synopsis_size, arguments_size, seealso_size; + + switch (cmd) { + case CLI_INIT: + e->command = "core show function"; + e->usage = + "Usage: core show function <function>\n" + " Describe a particular dialplan function.\n"; + return NULL; + case CLI_GENERATE: + return complete_functions(a->word, a->pos, a->n); + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (!(acf = ast_custom_function_find(a->argv[3]))) { + ast_cli(a->fd, "No function by that name registered.\n"); + + return CLI_FAILURE; + } + + syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + syntax = ast_malloc(syntax_size); + if (!syntax) { + ast_cli(a->fd, "Memory allocation failure!\n"); + + return CLI_FAILURE; + } + + snprintf(info, sizeof(info), "\n -= Info about function '%s' =- \n\n", acf->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); + term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); + term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); + term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); +#ifdef AST_XML_DOCS + if (acf->docsrc == AST_XML_DOC) { + arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1); + synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1); + description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1); + seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1); + } else +#endif + { + synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + synopsis = ast_malloc(synopsis_size); + + description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + description = ast_malloc(description_size); + + arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + arguments = ast_malloc(arguments_size); + + seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS; + seealso = ast_malloc(seealso_size); + + /* check allocated memory. */ + if (!synopsis || !description || !arguments || !seealso) { + ast_free(synopsis); + ast_free(description); + ast_free(arguments); + ast_free(seealso); + ast_free(syntax); + + return CLI_FAILURE; + } + + term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); + term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); + term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size); + term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); + } + + ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", + infotitle, syntitle, synopsis, destitle, description, + stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); + + ast_free(arguments); + ast_free(synopsis); + ast_free(description); + ast_free(seealso); + ast_free(syntax); + + return CLI_SUCCESS; +} + +static struct ast_custom_function *ast_custom_function_find_nolock(const char *name) +{ + struct ast_custom_function *cur; + int cmp; + + AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) { + cmp = strcmp(name, cur->name); + if (cmp > 0) { + continue; + } + if (!cmp) { + /* Found it. */ + break; + } + /* Not in container. */ + cur = NULL; + break; + } + + return cur; +} + +struct ast_custom_function *ast_custom_function_find(const char *name) +{ + struct ast_custom_function *acf; + + AST_RWLIST_RDLOCK(&acf_root); + acf = ast_custom_function_find_nolock(name); + AST_RWLIST_UNLOCK(&acf_root); + + return acf; +} + +int ast_custom_function_unregister(struct ast_custom_function *acf) +{ + struct ast_custom_function *cur; + + if (!acf) { + return -1; + } + + AST_RWLIST_WRLOCK(&acf_root); + cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist); + if (cur) { +#ifdef AST_XML_DOCS + if (cur->docsrc == AST_XML_DOC) { + ast_string_field_free_memory(acf); + } +#endif + ast_verb(2, "Unregistered custom function %s\n", cur->name); + } + AST_RWLIST_UNLOCK(&acf_root); + + return cur ? 0 : -1; +} + +/*! + * \brief Returns true if given custom function escalates privileges on read. + * + * \param acf Custom function to query. + * \return True (non-zero) if reads escalate privileges. + * \return False (zero) if reads just read. + */ +static int read_escalates(const struct ast_custom_function *acf) { + return acf->read_escalates; +} + +/*! + * \brief Returns true if given custom function escalates privileges on write. + * + * \param acf Custom function to query. + * \return True (non-zero) if writes escalate privileges. + * \return False (zero) if writes just write. + */ +static int write_escalates(const struct ast_custom_function *acf) { + return acf->write_escalates; +} + +/*! \internal + * \brief Retrieve the XML documentation of a specified ast_custom_function, + * and populate ast_custom_function string fields. + * \param acf ast_custom_function structure with empty 'desc' and 'synopsis' + * but with a function 'name'. + * \retval -1 On error. + * \retval 0 On succes. + */ +static int acf_retrieve_docs(struct ast_custom_function *acf) +{ +#ifdef AST_XML_DOCS + char *tmpxml; + + /* Let's try to find it in the Documentation XML */ + if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) { + return 0; + } + + if (ast_string_field_init(acf, 128)) { + return -1; + } + + /* load synopsis */ + tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, synopsis, tmpxml); + ast_free(tmpxml); + + /* load description */ + tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, desc, tmpxml); + ast_free(tmpxml); + + /* load syntax */ + tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, syntax, tmpxml); + ast_free(tmpxml); + + /* load arguments */ + tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, arguments, tmpxml); + ast_free(tmpxml); + + /* load seealso */ + tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod)); + ast_string_field_set(acf, seealso, tmpxml); + ast_free(tmpxml); + + acf->docsrc = AST_XML_DOC; +#endif + + return 0; +} + +int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod) +{ + struct ast_custom_function *cur; + + if (!acf) { + return -1; + } + + acf->mod = mod; +#ifdef AST_XML_DOCS + acf->docsrc = AST_STATIC_DOC; +#endif + + if (acf_retrieve_docs(acf)) { + return -1; + } + + AST_RWLIST_WRLOCK(&acf_root); + + cur = ast_custom_function_find_nolock(acf->name); + if (cur) { + ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name); + AST_RWLIST_UNLOCK(&acf_root); + return -1; + } + + /* Store in alphabetical order */ + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) { + if (strcmp(acf->name, cur->name) < 0) { + AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist); + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + if (!cur) { + AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist); + } + + AST_RWLIST_UNLOCK(&acf_root); + + ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name)); + + return 0; +} + +int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod) +{ + int res; + + res = __ast_custom_function_register(acf, mod); + if (res != 0) { + return -1; + } + + switch (escalation) { + case AST_CFE_NONE: + break; + case AST_CFE_READ: + acf->read_escalates = 1; + break; + case AST_CFE_WRITE: + acf->write_escalates = 1; + break; + case AST_CFE_BOTH: + acf->read_escalates = 1; + acf->write_escalates = 1; + break; + } + + return 0; +} + +/*! \brief return a pointer to the arguments of the function, + * and terminates the function name with '\\0' + */ +static char *func_args(char *function) +{ + char *args = strchr(function, '('); + + if (!args) { + ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses. Assuming null argument.\n", function); + } else { + char *p; + *args++ = '\0'; + if ((p = strrchr(args, ')'))) { + *p = '\0'; + } else { + ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args); + } + } + return args; +} + +void pbx_live_dangerously(int new_live_dangerously) +{ + if (new_live_dangerously && !live_dangerously) { + ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n" + "See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n"); + } + + if (!new_live_dangerously && live_dangerously) { + ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n"); + } + live_dangerously = new_live_dangerously; +} + +int ast_thread_inhibit_escalations(void) +{ + int *thread_inhibit_escalations; + + thread_inhibit_escalations = ast_threadstorage_get( + &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); + + if (thread_inhibit_escalations == NULL) { + ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n"); + return -1; + } + + *thread_inhibit_escalations = 1; + return 0; +} + +/*! + * \brief Indicates whether the current thread inhibits the execution of + * dangerous functions. + * + * \return True (non-zero) if dangerous function execution is inhibited. + * \return False (zero) if dangerous function execution is allowed. + */ +static int thread_inhibits_escalations(void) +{ + int *thread_inhibit_escalations; + + thread_inhibit_escalations = ast_threadstorage_get( + &thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations)); + + if (thread_inhibit_escalations == NULL) { + ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n"); + /* On error, assume that we are inhibiting */ + return 1; + } + + return *thread_inhibit_escalations; +} + +/*! + * \brief Determines whether execution of a custom function's read function + * is allowed. + * + * \param acfptr Custom function to check + * \return True (non-zero) if reading is allowed. + * \return False (zero) if reading is not allowed. + */ +static int is_read_allowed(struct ast_custom_function *acfptr) +{ + if (!acfptr) { + return 1; + } + + if (!read_escalates(acfptr)) { + return 1; + } + + if (!thread_inhibits_escalations()) { + return 1; + } + + if (live_dangerously) { + /* Global setting overrides the thread's preference */ + ast_debug(2, "Reading %s from a dangerous context\n", + acfptr->name); + return 1; + } + + /* We have no reason to allow this function to execute */ + return 0; +} + +/*! + * \brief Determines whether execution of a custom function's write function + * is allowed. + * + * \param acfptr Custom function to check + * \return True (non-zero) if writing is allowed. + * \return False (zero) if writing is not allowed. + */ +static int is_write_allowed(struct ast_custom_function *acfptr) +{ + if (!acfptr) { + return 1; + } + + if (!write_escalates(acfptr)) { + return 1; + } + + if (!thread_inhibits_escalations()) { + return 1; + } + + if (live_dangerously) { + /* Global setting overrides the thread's preference */ + ast_debug(2, "Writing %s from a dangerous context\n", + acfptr->name); + return 1; + } + + /* We have no reason to allow this function to execute */ + return 0; +} + +int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len) +{ + char *copy = ast_strdupa(function); + char *args = func_args(copy); + struct ast_custom_function *acfptr = ast_custom_function_find(copy); + int res; + struct ast_module_user *u = NULL; + + if (acfptr == NULL) { + ast_log(LOG_ERROR, "Function %s not registered\n", copy); + } else if (!acfptr->read && !acfptr->read2) { + ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); + } else if (!is_read_allowed(acfptr)) { + ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); + } else if (acfptr->read) { + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + res = acfptr->read(chan, copy, args, workspace, len); + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + + return res; + } else { + struct ast_str *str = ast_str_create(16); + + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + res = acfptr->read2(chan, copy, args, &str, 0); + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len); + ast_free(str); + + return res; + } + + return -1; +} + +int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen) +{ + char *copy = ast_strdupa(function); + char *args = func_args(copy); + struct ast_custom_function *acfptr = ast_custom_function_find(copy); + int res; + struct ast_module_user *u = NULL; + + if (acfptr == NULL) { + ast_log(LOG_ERROR, "Function %s not registered\n", copy); + } else if (!acfptr->read && !acfptr->read2) { + ast_log(LOG_ERROR, "Function %s cannot be read\n", copy); + } else if (!is_read_allowed(acfptr)) { + ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy); + } else { + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + ast_str_reset(*str); + if (acfptr->read2) { + /* ast_str enabled */ + res = acfptr->read2(chan, copy, args, str, maxlen); + } else { + /* Legacy function pointer, allocate buffer for result */ + int maxsize = ast_str_size(*str); + + if (maxlen > -1) { + if (maxlen == 0) { + if (acfptr->read_max) { + maxsize = acfptr->read_max; + } else { + maxsize = VAR_BUF_SIZE; + } + } else { + maxsize = maxlen; + } + ast_str_make_space(str, maxsize); + } + res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize); + } + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + + return res; + } + + return -1; +} + +int ast_func_write(struct ast_channel *chan, const char *function, const char *value) +{ + char *copy = ast_strdupa(function); + char *args = func_args(copy); + struct ast_custom_function *acfptr = ast_custom_function_find(copy); + + if (acfptr == NULL) { + ast_log(LOG_ERROR, "Function %s not registered\n", copy); + } else if (!acfptr->write) { + ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy); + } else if (!is_write_allowed(acfptr)) { + ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy); + } else { + int res; + struct ast_module_user *u = NULL; + + if (acfptr->mod) { + u = __ast_module_user_add(acfptr->mod, chan); + } + res = acfptr->write(chan, copy, args, value); + if (acfptr->mod && u) { + __ast_module_user_remove(acfptr->mod, u); + } + + return res; + } + + return -1; +} + +static struct ast_cli_entry acf_cli[] = { + AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"), + AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"), +}; + +static void unload_pbx_functions_cli(void) +{ + ast_cli_unregister_multiple(acf_cli, ARRAY_LEN(acf_cli)); +} + +int load_pbx_functions_cli(void) +{ + ast_cli_register_multiple(acf_cli, ARRAY_LEN(acf_cli)); + ast_register_cleanup(unload_pbx_functions_cli); + + return 0; +} diff --git a/main/pbx_private.h b/main/pbx_private.h new file mode 100644 index 000000000..e1711796c --- /dev/null +++ b/main/pbx_private.h @@ -0,0 +1,37 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015 Fairview 5 Engineering, LLC + * + * George Joseph <george.joseph@fairview5.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 Private include file for pbx + */ + +#ifndef _PBX_PRIVATE_H +#define _PBX_PRIVATE_H + +/*! pbx.c functions needed by pbx_builtins.c */ +int raise_exception(struct ast_channel *chan, const char *reason, int priority); +void wait_for_hangup(struct ast_channel *chan, const void *data); +void set_ext_pri(struct ast_channel *c, const char *exten, int pri); + +/*! pbx_builtins.c functions needed by pbx.c */ +int indicate_congestion(struct ast_channel *, const char *); +int indicate_busy(struct ast_channel *, const char *); + +#define VAR_BUF_SIZE 4096 + +#endif /* _PBX_PRIVATE_H */ |