diff options
Diffstat (limited to '1.2-netsec/pbx.c')
-rw-r--r-- | 1.2-netsec/pbx.c | 6487 |
1 files changed, 6487 insertions, 0 deletions
diff --git a/1.2-netsec/pbx.c b/1.2-netsec/pbx.c new file mode 100644 index 000000000..f7d26c92a --- /dev/null +++ b/1.2-netsec/pbx.c @@ -0,0 +1,6487 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Core PBX routines. + * + */ + +#include <sys/types.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <time.h> +#include <sys/time.h> + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/lock.h" +#include "asterisk/cli.h" +#include "asterisk/pbx.h" +#include "asterisk/channel.h" +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/file.h" +#include "asterisk/callerid.h" +#include "asterisk/cdr.h" +#include "asterisk/config.h" +#include "asterisk/term.h" +#include "asterisk/manager.h" +#include "asterisk/ast_expr.h" +#include "asterisk/linkedlists.h" +#include "asterisk/say.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/musiconhold.h" +#include "asterisk/app.h" +#include "asterisk/devicestate.h" +#include "asterisk/compat.h" + +/*! + * \note I M P O R T A N T : + * + * The speed of extension handling will likely be among the most important + * aspects of this PBX. The switching scheme as it exists right now isn't + * terribly bad (it's O(N+M), where N is the # of extensions and M is the avg # + * of priorities, but a constant search time here would be great ;-) + * + */ + +#ifdef LOW_MEMORY +#define EXT_DATA_SIZE 256 +#else +#define EXT_DATA_SIZE 8192 +#endif + +#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) + +AST_APP_OPTIONS(waitexten_opts, { + AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 1), +}); + +struct ast_context; + +/*!\brief ast_exten: An extension + The dialplan is saved as a linked list with each context + having it's own linked list of extensions - one item per + priority. +*/ +struct ast_exten { + char *exten; /* Extension name */ + int matchcid; /* Match caller id ? */ + char *cidmatch; /* Caller id to match for this extension */ + int priority; /* Priority */ + char *label; /* Label */ + struct ast_context *parent; /* The context this extension belongs to */ + char *app; /* Application to execute */ + void *data; /* Data to use (arguments) */ + void (*datad)(void *); /* Data destructor */ + struct ast_exten *peer; /* Next higher priority with our extension */ + const char *registrar; /* Registrar */ + struct ast_exten *next; /* Extension with a greater ID */ + char stuff[0]; +}; + +/*! \brief ast_include: include= support in extensions.conf */ +struct ast_include { + char *name; + char *rname; /* Context to include */ + const char *registrar; /* Registrar */ + int hastime; /* If time construct exists */ + struct ast_timing timing; /* time construct */ + struct ast_include *next; /* Link them together */ + char stuff[0]; +}; + +/*! \brief ast_sw: Switch statement in extensions.conf */ +struct ast_sw { + char *name; + const char *registrar; /* Registrar */ + char *data; /* Data load */ + int eval; + struct ast_sw *next; /* Link them together */ + char *tmpdata; + char stuff[0]; +}; + +/*! \brief ast_ignorepat: Ignore patterns in dial plan */ +struct ast_ignorepat { + const char *registrar; + struct ast_ignorepat *next; + char pattern[0]; +}; + +/*! \brief ast_context: An extension context */ +struct ast_context { + ast_mutex_t lock; /*!< A lock to prevent multiple threads from clobbering the context */ + struct ast_exten *root; /*!< The root of the list of extensions */ + struct ast_context *next; /*!< Link them together */ + struct ast_include *includes; /*!< Include other contexts */ + struct ast_ignorepat *ignorepats; /*!< Patterns for which to continue playing dialtone */ + const char *registrar; /*!< Registrar */ + struct ast_sw *alts; /*!< Alternative switches */ + char name[0]; /*!< Name of the context */ +}; + + +/*! \brief ast_app: A registered application */ +struct ast_app { + int (*execute)(struct ast_channel *chan, void *data); + const char *synopsis; /* Synopsis text for 'show applications' */ + const char *description; /* Description (help text) for 'show application <name>' */ + struct ast_app *next; /* Next app in list */ + char name[0]; /* Name of the application */ +}; + +/*! \brief ast_state_cb: An extension state notify register item */ +struct ast_state_cb { + int id; + void *data; + ast_state_cb_type callback; + struct ast_state_cb *next; +}; + +/*! \brief Structure for dial plan hints + + Hints are pointers from an extension in the dialplan to one or + more devices (tech/name) */ +struct ast_hint { + struct ast_exten *exten; /*!< Extension */ + int laststate; /*!< Last known state */ + struct ast_state_cb *callbacks; /*!< Callback list for this extension */ + struct ast_hint *next; /*!< Pointer to next hint in list */ +}; + +int ast_pbx_outgoing_cdr_failed(void); + +static int pbx_builtin_answer(struct ast_channel *, void *); +static int pbx_builtin_goto(struct ast_channel *, void *); +static int pbx_builtin_hangup(struct ast_channel *, void *); +static int pbx_builtin_background(struct ast_channel *, void *); +static int pbx_builtin_dtimeout(struct ast_channel *, void *); +static int pbx_builtin_rtimeout(struct ast_channel *, void *); +static int pbx_builtin_atimeout(struct ast_channel *, void *); +static int pbx_builtin_wait(struct ast_channel *, void *); +static int pbx_builtin_waitexten(struct ast_channel *, void *); +static int pbx_builtin_setlanguage(struct ast_channel *, void *); +static int pbx_builtin_resetcdr(struct ast_channel *, void *); +static int pbx_builtin_setaccount(struct ast_channel *, void *); +static int pbx_builtin_setamaflags(struct ast_channel *, void *); +static int pbx_builtin_ringing(struct ast_channel *, void *); +static int pbx_builtin_progress(struct ast_channel *, void *); +static int pbx_builtin_congestion(struct ast_channel *, void *); +static int pbx_builtin_busy(struct ast_channel *, void *); +static int pbx_builtin_setglobalvar(struct ast_channel *, void *); +static int pbx_builtin_noop(struct ast_channel *, void *); +static int pbx_builtin_gotoif(struct ast_channel *, void *); +static int pbx_builtin_gotoiftime(struct ast_channel *, void *); +static int pbx_builtin_execiftime(struct ast_channel *, void *); +static int pbx_builtin_saynumber(struct ast_channel *, void *); +static int pbx_builtin_saydigits(struct ast_channel *, void *); +static int pbx_builtin_saycharacters(struct ast_channel *, void *); +static int pbx_builtin_sayphonetic(struct ast_channel *, void *); +static int pbx_builtin_setvar_old(struct ast_channel *, void *); +int pbx_builtin_setvar(struct ast_channel *, void *); +static int pbx_builtin_importvar(struct ast_channel *, void *); + +static struct varshead globals; + +static int autofallthrough = 0; + +AST_MUTEX_DEFINE_STATIC(maxcalllock); +static int countcalls = 0; + +AST_MUTEX_DEFINE_STATIC(acflock); /*!< Lock for the custom function list */ +static struct ast_custom_function *acf_root = NULL; + +/*! \brief Declaration of builtin applications */ +static struct pbx_builtin { + char name[AST_MAX_APP]; + int (*execute)(struct ast_channel *chan, void *data); + char *synopsis; + char *description; +} builtins[] = +{ + /* These applications are built into the PBX core and do not + need separate modules */ + + { "AbsoluteTimeout", pbx_builtin_atimeout, + "Set absolute maximum time of call", + " AbsoluteTimeout(seconds): This application will set the absolute maximum\n" + "amount of time permitted for a call. A setting of 0 disables the timeout.\n" + " AbsoluteTimeout has been deprecated in favor of Set(TIMEOUT(absolute)=timeout)\n" + }, + + { "Answer", pbx_builtin_answer, + "Answer a channel if ringing", + " Answer([delay]): If the call has not been answered, this application will\n" + "answer it. Otherwise, it has no effect on the call. If a delay is specified,\n" + "Asterisk will wait this number of milliseconds before answering the call.\n" + }, + + { "BackGround", pbx_builtin_background, + "Play a file while awaiting extension", + " Background(filename1[&filename2...][|options[|langoverride][|context]]):\n" + "This application will play the given list of files while waiting for an\n" + "extension to be dialed by the calling channel. To continue waiting for digits\n" + "after this application has finished playing files, the WaitExten application\n" + "should be used. The 'langoverride' option explicity specifies which language\n" + "to attempt to use for the requested sound files. If a 'context' is specified,\n" + "this is the dialplan context that this application will use when exiting to a\n" + "dialed extension." + " If one of the requested sound files does not exist, call processing will be\n" + "terminated.\n" + " Options:\n" + " s - causes the playback of the message to be skipped\n" + " if the channel is not in the 'up' state (i.e. it\n" + " hasn't been answered yet.) If this happens, the\n" + " application will return immediately.\n" + " n - don't answer the channel before playing the files\n" + " m - only break if a digit hit matches a one digit\n" + " extension in the destination context\n" + }, + + { "Busy", pbx_builtin_busy, + "Indicate the Busy condition", + " Busy([timeout]): This application will indicate the busy condition to\n" + "the calling channel. If the optional timeout is specified, the calling channel\n" + "will be hung up after the specified number of seconds. Otherwise, this\n" + "application will wait until the calling channel hangs up.\n" + }, + + { "Congestion", pbx_builtin_congestion, + "Indicate the Congestion condition", + " Congestion([timeout]): This application will indicate the congenstion\n" + "condition to the calling channel. If the optional timeout is specified, the\n" + "calling channel will be hung up after the specified number of seconds.\n" + "Otherwise, this application will wait until the calling channel hangs up.\n" + }, + + { "DigitTimeout", pbx_builtin_dtimeout, + "Set maximum timeout between digits", + " DigitTimeout(seconds): Set the maximum amount of time permitted between\n" + "digits when the user is typing in an extension. When this timeout expires,\n" + "after the user has started to type in an extension, the extension will be\n" + "considered complete, and will be interpreted. Note that if an extension\n" + "typed in is valid, it will not have to timeout to be tested, so typically\n" + "at the expiry of this timeout, the extension will be considered invalid\n" + "(and thus control would be passed to the 'i' extension, or if it doesn't\n" + "exist the call would be terminated). The default timeout is 5 seconds.\n" + " DigitTimeout has been deprecated in favor of Set(TIMEOUT(digit)=timeout)\n" + }, + + { "Goto", pbx_builtin_goto, + "Jump to a particular priority, extension, or context", + " Goto([[context|]extension|]priority): This application will cause the\n" + "calling channel to continue dialplan execution at the specified priority.\n" + "If no specific extension, or extension and context, are specified, then this\n" + "application will jump to the specified priority of the current extension.\n" + " If the attempt to jump to another location in the dialplan is not successful,\n" + "then the channel will continue at the next priority of the current extension.\n" + }, + + { "GotoIf", pbx_builtin_gotoif, + "Conditional goto", + " GotoIf(Condition?[label1]:[label2]): This application will cause the calling\n" + "channel to jump to the speicifed location in the dialplan based on the\n" + "evaluation of the given condition. The channel will continue at 'label1' if the\n" + "condition is true, or 'label2' if the condition is false. The labels are\n" + "specified in the same syntax that is used with the Goto application.\n" + }, + + { "GotoIfTime", pbx_builtin_gotoiftime, + "Conditional Goto based on the current time", + " GotoIfTime(<times>|<weekdays>|<mdays>|<months>?[[context|]exten|]priority):\n" + "This application will have the calling channel jump to the speicified location\n" + "int the dialplan if the current time matches the given time specification.\n" + "Further information on the time specification can be found in examples\n" + "illustrating how to do time-based context includes in the dialplan.\n" + }, + + { "ExecIfTime", pbx_builtin_execiftime, + "Conditional application execution based on the current time", + " ExecIfTime(<times>|<weekdays>|<mdays>|<months>?appname[|appargs]):\n" + "This application will execute the specified dialplan application, with optional\n" + "arguments, if the current time matches the given time specification. Further\n" + "information on the time speicification can be found in examples illustrating\n" + "how to do time-based context includes in the dialplan.\n" + }, + + { "Hangup", pbx_builtin_hangup, + "Hang up the calling channel", + " Hangup(): This application will hang up the calling channel.\n" + }, + + { "NoOp", pbx_builtin_noop, + "Do Nothing", + " NoOp(): This applicatiion does nothing. However, it is useful for debugging\n" + "purposes. Any text that is provided as arguments to this application can be\n" + "viewed at the Asterisk CLI. This method can be used to see the evaluations of\n" + "variables or functions without having any effect." + }, + + { "Progress", pbx_builtin_progress, + "Indicate progress", + " Progress(): This application will request that in-band progress information\n" + "be provided to the calling channel.\n" + }, + + { "ResetCDR", pbx_builtin_resetcdr, + "Resets the Call Data Record", + " ResetCDR([options]): This application causes the Call Data Record to be\n" + "reset.\n" + " Options:\n" + " w -- Store the current CDR record before resetting it.\n" + " a -- Store any stacked records.\n" + " v -- Save CDR variables.\n" + }, + + { "ResponseTimeout", pbx_builtin_rtimeout, + "Set maximum timeout awaiting response", + " ResponseTimeout(seconds): This will set the maximum amount of time permitted\n" + "to wait for an extension to dialed (see the WaitExten application), before the\n" + "timeout occurs. If this timeout is reached, dialplan execution will continue at\n" + "the 't' extension, if it exists.\n" + " ResponseTimeout has been deprecated in favor of Set(TIMEOUT(response)=timeout)\n" + }, + + { "Ringing", pbx_builtin_ringing, + "Indicate ringing tone", + " Ringing(): This application will request that the channel indicate a ringing\n" + "tone to the user.\n" + }, + + { "SayNumber", pbx_builtin_saynumber, + "Say Number", + " SayNumber(digits[,gender]): This application will play the sounds that\n" + "correspond to the given number. Optionally, a gender may be specified.\n" + "This will use the language that is currently set for the channel. See the\n" + "LANGUAGE function for more information on setting the language for the channel.\n" + }, + + { "SayDigits", pbx_builtin_saydigits, + "Say Digits", + " SayDigits(digits): This application will play the sounds that correspond\n" + "to the digits of the given number. This will use the language that is currently\n" + "set for the channel. See the LANGUAGE function for more information on setting\n" + "the language for the channel.\n" + }, + + { "SayAlpha", pbx_builtin_saycharacters, + "Say Alpha", + " SayAlpha(string): This application will play the sounds that correspond to\n" + "the letters of the given string.\n" + }, + + { "SayPhonetic", pbx_builtin_sayphonetic, + "Say Phonetic", + " SayPhonetic(string): This application will play the sounds from the phonetic\n" + "alphabet that correspond to the letters in the given string.\n" + }, + + { "SetAccount", pbx_builtin_setaccount, + "Set the CDR Account Code", + " SetAccount([account]): This application will set the channel account code for\n" + "billing purposes.\n" + " SetAccount has been deprecated in favor of the Set(CDR(accountcode)=account).\n" + }, + + { "SetAMAFlags", pbx_builtin_setamaflags, + "Set the AMA Flags", + " SetAMAFlags([flag]): This channel will set the channel's AMA Flags for billing\n" + "purposes.\n" + }, + + { "SetGlobalVar", pbx_builtin_setglobalvar, + "Set a global variable to a given value", + " SetGlobalVar(variable=value): This application sets a given global variable to\n" + "the specified value.\n" + }, + + { "SetLanguage", pbx_builtin_setlanguage, + "Set the channel's preferred language", + " SetLanguage(language): This will set the channel language to the given value.\n" + "This information is used for the syntax in generation of numbers, and to choose\n" + "a sound file in the given language, when it is available.\n" + " For example, if language is set to 'fr' and the file 'demo-congrats' is \n" + "requested to be played, if the file 'fr/demo-congrats' exists, then\n" + "it will play that file. If not, it will play the normal 'demo-congrats'.\n" + "For some language codes, SetLanguage also changes the syntax of some\n" + "Asterisk functions, like SayNumber.\n" + " SetLanguage has been deprecated in favor of Set(LANGUAGE()=language)\n" + }, + + { "Set", pbx_builtin_setvar, + "Set channel variable(s) or function value(s)", + " Set(name1=value1|name2=value2|..[|options])\n" + "This function can be used to set the value of channel variables or dialplan\n" + "functions. It will accept up to 24 name/value pairs. When setting variables,\n" + "if the variable name is prefixed with _, the variable will be inherited into\n" + "channels created from the current channel. If the variable name is prefixed\n" + "with __, the variable will be inherited into channels created from the current\n" + "channel and all children channels.\n" + " Options:\n" + " g - Set variable globally instead of on the channel\n" + " (applies only to variables, not functions)\n" + }, + + { "SetVar", pbx_builtin_setvar_old, + "Set channel variable(s)", + " SetVar(name1=value1|name2=value2|..[|options]): This application has been\n" + "deprecated in favor of using the Set application.\n" + }, + + { "ImportVar", pbx_builtin_importvar, + "Import a variable from a channel into a new variable", + " ImportVar(newvar=channelname|variable): This application imports a variable\n" + "from the specified channel (as opposed to the current one) and stores it as\n" + "a variable in the current channel (the channel that is calling this\n" + "application). Variables created by this application have the same inheritance\n" + "properties as those created with the Set application. See the documentation for\n" + "Set for more information.\n" + }, + + { "Wait", pbx_builtin_wait, + "Waits for some time", + " Wait(seconds): This application waits for a specified number of seconds.\n" + "Then, dialplan execution will continue at the next priority.\n" + " Note that the seconds can be passed with fractions of a second. For example,\n" + "'1.5' will ask the application to wait for 1.5 seconds.\n" + }, + + { "WaitExten", pbx_builtin_waitexten, + "Waits for an extension to be entered", + " WaitExten([seconds][|options]): This application waits for the user to enter\n" + "a new extension for a specified number of seconds.\n" + " Note that the seconds can be passed with fractions of a second. For example,\n" + "'1.5' will ask the application to wait for 1.5 seconds.\n" + " Options:\n" + " m[(x)] - Provide music on hold to the caller while waiting for an extension.\n" + " Optionally, specify the class for music on hold within parenthesis.\n" + }, + +}; + +static struct ast_context *contexts = NULL; +AST_MUTEX_DEFINE_STATIC(conlock); /* Lock for the ast_context list */ +static struct ast_app *apps = NULL; +AST_MUTEX_DEFINE_STATIC(applock); /* Lock for the application list */ + +struct ast_switch *switches = NULL; +AST_MUTEX_DEFINE_STATIC(switchlock); /* Lock for switches */ + +AST_MUTEX_DEFINE_STATIC(hintlock); /* Lock for extension state notifys */ +static int stateid = 1; +struct ast_hint *hints = NULL; +struct ast_state_cb *statecbs = NULL; + +/* + \note This function is special. It saves the stack so that no matter + how many times it is called, it returns to the same place */ +int pbx_exec(struct ast_channel *c, /*!< Channel */ + struct ast_app *app, /*!< Application */ + void *data, /*!< Data for execution */ + int newstack) /*!< Force stack increment */ +{ + int res; + + char *saved_c_appl; + char *saved_c_data; + + int (*execute)(struct ast_channel *chan, void *data) = app->execute; + + if (newstack) { + if (c->cdr) + ast_cdr_setapp(c->cdr, app->name, data); + + /* save channel values */ + saved_c_appl= c->appl; + saved_c_data= c->data; + + c->appl = app->name; + c->data = data; + res = execute(c, data); + /* restore channel values */ + c->appl= saved_c_appl; + c->data= saved_c_data; + return res; + } else + ast_log(LOG_WARNING, "You really didn't want to call this function with newstack set to 0\n"); + return -1; +} + + +/*! Go no deeper than this through includes (not counting loops) */ +#define AST_PBX_MAX_STACK 128 + +#define HELPER_EXISTS 0 +#define HELPER_SPAWN 1 +#define HELPER_EXEC 2 +#define HELPER_CANMATCH 3 +#define HELPER_MATCHMORE 4 +#define HELPER_FINDLABEL 5 + +/*! \brief Find application handle in linked list + */ +struct ast_app *pbx_findapp(const char *app) +{ + struct ast_app *tmp; + + if (ast_mutex_lock(&applock)) { + ast_log(LOG_WARNING, "Unable to obtain application lock\n"); + return NULL; + } + tmp = apps; + while(tmp) { + if (!strcasecmp(tmp->name, app)) + break; + tmp = tmp->next; + } + ast_mutex_unlock(&applock); + return tmp; +} + +static struct ast_switch *pbx_findswitch(const char *sw) +{ + struct ast_switch *asw; + + if (ast_mutex_lock(&switchlock)) { + ast_log(LOG_WARNING, "Unable to obtain application lock\n"); + return NULL; + } + asw = switches; + while(asw) { + if (!strcasecmp(asw->name, sw)) + break; + asw = asw->next; + } + ast_mutex_unlock(&switchlock); + return asw; +} + +static inline int include_valid(struct ast_include *i) +{ + if (!i->hastime) + return 1; + + return ast_check_timing(&(i->timing)); +} + +static void pbx_destroy(struct ast_pbx *p) +{ + free(p); +} + +#define EXTENSION_MATCH_CORE(data,pattern,match) {\ + /* All patterns begin with _ */\ + if (pattern[0] != '_') \ + return 0;\ + /* Start optimistic */\ + match=1;\ + pattern++;\ + while(match && *data && *pattern && (*pattern != '/')) {\ + while (*data == '-' && (*(data+1) != '\0')) data++;\ + switch(toupper(*pattern)) {\ + case '[': \ + {\ + int i,border=0;\ + char *where;\ + match=0;\ + pattern++;\ + where=strchr(pattern,']');\ + if (where)\ + border=(int)(where-pattern);\ + if (!where || border > strlen(pattern)) {\ + ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n");\ + return match;\ + }\ + for (i=0; i<border; i++) {\ + int res=0;\ + if (i+2<border)\ + if (pattern[i+1]=='-') {\ + if (*data >= pattern[i] && *data <= pattern[i+2]) {\ + res=1;\ + } else {\ + i+=2;\ + continue;\ + }\ + }\ + if (res==1 || *data==pattern[i]) {\ + match = 1;\ + break;\ + }\ + }\ + pattern+=border;\ + break;\ + }\ + case 'N':\ + if ((*data < '2') || (*data > '9'))\ + match=0;\ + break;\ + case 'X':\ + if ((*data < '0') || (*data > '9'))\ + match = 0;\ + break;\ + case 'Z':\ + if ((*data < '1') || (*data > '9'))\ + match = 0;\ + break;\ + case '.':\ + /* Must match */\ + return 1;\ + case '!':\ + /* Early match */\ + return 2;\ + case ' ':\ + case '-':\ + /* Ignore these characters */\ + data--;\ + break;\ + default:\ + if (*data != *pattern)\ + match =0;\ + }\ + data++;\ + pattern++;\ + }\ + /* If we ran off the end of the data and the pattern ends in '!', match */\ + if (match && !*data && (*pattern == '!'))\ + return 2;\ +} + +int ast_extension_match(const char *pattern, const char *data) +{ + int match; + /* If they're the same return */ + if (!strcmp(pattern, data)) + return 1; + EXTENSION_MATCH_CORE(data,pattern,match); + /* Must be at the end of both */ + if (*data || (*pattern && (*pattern != '/'))) + match = 0; + return match; +} + +int ast_extension_close(const char *pattern, const char *data, int needmore) +{ + int match; + /* If "data" is longer, it can'be a subset of pattern unless + pattern is a pattern match */ + if ((strlen(pattern) < strlen(data)) && (pattern[0] != '_')) + return 0; + + if ((ast_strlen_zero((char *)data) || !strncasecmp(pattern, data, strlen(data))) && + (!needmore || (strlen(pattern) > strlen(data)))) { + return 1; + } + EXTENSION_MATCH_CORE(data,pattern,match); + /* If there's more or we don't care about more, or if it's a possible early match, + return non-zero; otherwise it's a miss */ + if (!needmore || *pattern || match == 2) { + return match; + } else + return 0; +} + +struct ast_context *ast_context_find(const char *name) +{ + struct ast_context *tmp; + ast_mutex_lock(&conlock); + if (name) { + tmp = contexts; + while(tmp) { + if (!strcasecmp(name, tmp->name)) + break; + tmp = tmp->next; + } + } else + tmp = contexts; + ast_mutex_unlock(&conlock); + return tmp; +} + +#define STATUS_NO_CONTEXT 1 +#define STATUS_NO_EXTENSION 2 +#define STATUS_NO_PRIORITY 3 +#define STATUS_NO_LABEL 4 +#define STATUS_SUCCESS 5 + +static int matchcid(const char *cidpattern, const char *callerid) +{ + int failresult; + + /* If the Caller*ID pattern is empty, then we're matching NO Caller*ID, so + failing to get a number should count as a match, otherwise not */ + + if (!ast_strlen_zero(cidpattern)) + failresult = 0; + else + failresult = 1; + + if (!callerid) + return failresult; + + return ast_extension_match(cidpattern, callerid); +} + +static struct ast_exten *pbx_find_extension(struct ast_channel *chan, struct ast_context *bypass, const char *context, const char *exten, int priority, const char *label, const char *callerid, int action, char *incstack[], int *stacklen, int *status, struct ast_switch **swo, char **data, const char **foundcontext) +{ + int x, res; + struct ast_context *tmp; + struct ast_exten *e, *eroot; + struct ast_include *i; + struct ast_sw *sw; + struct ast_switch *asw; + + /* Initialize status if appropriate */ + if (!*stacklen) { + *status = STATUS_NO_CONTEXT; + *swo = NULL; + *data = NULL; + } + /* Check for stack overflow */ + if (*stacklen >= AST_PBX_MAX_STACK) { + ast_log(LOG_WARNING, "Maximum PBX stack exceeded\n"); + return NULL; + } + /* Check first to see if we've already been checked */ + for (x=0; x<*stacklen; x++) { + if (!strcasecmp(incstack[x], context)) + return NULL; + } + if (bypass) + tmp = bypass; + else + tmp = contexts; + while(tmp) { + /* Match context */ + if (bypass || !strcmp(tmp->name, context)) { + struct ast_exten *earlymatch = NULL; + + if (*status < STATUS_NO_EXTENSION) + *status = STATUS_NO_EXTENSION; + for (eroot = tmp->root; eroot; eroot=eroot->next) { + int match = 0; + /* Match extension */ + if ((((action != HELPER_MATCHMORE) && ast_extension_match(eroot->exten, exten)) || + ((action == HELPER_CANMATCH) && (ast_extension_close(eroot->exten, exten, 0))) || + ((action == HELPER_MATCHMORE) && (match = ast_extension_close(eroot->exten, exten, 1)))) && + (!eroot->matchcid || matchcid(eroot->cidmatch, callerid))) { + + if (action == HELPER_MATCHMORE && match == 2 && !earlymatch) { + /* It matched an extension ending in a '!' wildcard + So ignore it for now, unless there's a better match */ + earlymatch = eroot; + } else { + e = eroot; + if (*status < STATUS_NO_PRIORITY) + *status = STATUS_NO_PRIORITY; + while(e) { + /* Match priority */ + if (action == HELPER_FINDLABEL) { + if (*status < STATUS_NO_LABEL) + *status = STATUS_NO_LABEL; + if (label && e->label && !strcmp(label, e->label)) { + *status = STATUS_SUCCESS; + *foundcontext = context; + return e; + } + } else if (e->priority == priority) { + *status = STATUS_SUCCESS; + *foundcontext = context; + return e; + } + e = e->peer; + } + } + } + } + if (earlymatch) { + /* Bizarre logic for HELPER_MATCHMORE. We return zero to break out + of the loop waiting for more digits, and _then_ match (normally) + the extension we ended up with. We got an early-matching wildcard + pattern, so return NULL to break out of the loop. */ + return NULL; + } + /* Check alternative switches */ + sw = tmp->alts; + while(sw) { + if ((asw = pbx_findswitch(sw->name))) { + /* Substitute variables now */ + if (sw->eval) + pbx_substitute_variables_helper(chan, sw->data, sw->tmpdata, SWITCH_DATA_LENGTH - 1); + if (action == HELPER_CANMATCH) + res = asw->canmatch ? asw->canmatch(chan, context, exten, priority, callerid, sw->eval ? sw->tmpdata : sw->data) : 0; + else if (action == HELPER_MATCHMORE) + res = asw->matchmore ? asw->matchmore(chan, context, exten, priority, callerid, sw->eval ? sw->tmpdata : sw->data) : 0; + else + res = asw->exists ? asw->exists(chan, context, exten, priority, callerid, sw->eval ? sw->tmpdata : sw->data) : 0; + if (res) { + /* Got a match */ + *swo = asw; + *data = sw->eval ? sw->tmpdata : sw->data; + *foundcontext = context; + return NULL; + } + } else { + ast_log(LOG_WARNING, "No such switch '%s'\n", sw->name); + } + sw = sw->next; + } + /* Setup the stack */ + incstack[*stacklen] = tmp->name; + (*stacklen)++; + /* Now try any includes we have in this context */ + i = tmp->includes; + while(i) { + if (include_valid(i)) { + if ((e = pbx_find_extension(chan, bypass, i->rname, exten, priority, label, callerid, action, incstack, stacklen, status, swo, data, foundcontext))) + return e; + if (*swo) + return NULL; + } + i = i->next; + } + break; + } + tmp = tmp->next; + } + return NULL; +} + +/* Note that it's negative -- that's important later. */ +#define DONT_HAVE_LENGTH 0x80000000 + +static int parse_variable_name(char *var, int *offset, int *length, int *isfunc) +{ + char *varchar, *offsetchar = NULL; + int parens=0; + + *offset = 0; + *length = DONT_HAVE_LENGTH; + *isfunc = 0; + for (varchar=var; *varchar; varchar++) { + switch (*varchar) { + case '(': + (*isfunc)++; + parens++; + break; + case ')': + parens--; + break; + case ':': + if (parens == 0) { + offsetchar = varchar + 1; + *varchar = '\0'; + goto pvn_endfor; + } + } + } +pvn_endfor: + if (offsetchar) { + sscanf(offsetchar, "%d:%d", offset, length); + return 1; + } else { + return 0; + } +} + +/*! \brief takes a substring. It is ok to call with value == workspace. + * + * offset < 0 means start from the end of the string and set the beginning + * to be that many characters back. + * length is the length of the substring, -1 means unlimited + * (we take any negative value). + * Always return a copy in workspace. + */ +static char *substring(const char *value, int offset, int length, char *workspace, size_t workspace_len) +{ + char *ret = workspace; + int lr; /* length of the input string after the copy */ + + ast_copy_string(workspace, value, workspace_len); /* always make a copy */ + + if (offset == 0 && length < 0) /* take the whole string */ + return ret; + + lr = strlen(ret); /* compute length after copy, so we never go out of the workspace */ + + if (offset < 0) { /* translate negative offset into positive ones */ + offset = lr + offset; + if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */ + offset = 0; + } + + /* too large offset result in empty string so we know what to return */ + if (offset >= lr) + return ret + lr; /* the final '\0' */ + + ret += offset; /* move to the start position */ + if (length >= 0 && length < lr - offset) /* truncate if necessary */ + ret[length] = '\0'; + + return ret; +} + +/*! \brief pbx_retrieve_variable: Support for Asterisk built-in variables and + functions in the dialplan + ---*/ +void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp) +{ + char tmpvar[80]; + time_t thistime; + struct tm brokentime; + int offset, offset2, isfunc; + struct ast_var_t *variables; + + if (c) + headp=&c->varshead; + *ret=NULL; + ast_copy_string(tmpvar, var, sizeof(tmpvar)); + if (parse_variable_name(tmpvar, &offset, &offset2, &isfunc)) { + pbx_retrieve_variable(c, tmpvar, ret, workspace, workspacelen, headp); + if (!(*ret)) + return; + *ret = substring(*ret, offset, offset2, workspace, workspacelen); + } else if (c && !strncmp(var, "CALL", 4)) { + if (!strncmp(var + 4, "ER", 2)) { + if (!strncmp(var + 6, "ID", 2)) { + if (!var[8]) { /* CALLERID */ + if (c->cid.cid_num) { + if (c->cid.cid_name) { + snprintf(workspace, workspacelen, "\"%s\" <%s>", c->cid.cid_name, c->cid.cid_num); + } else { + ast_copy_string(workspace, c->cid.cid_num, workspacelen); + } + *ret = workspace; + } else if (c->cid.cid_name) { + ast_copy_string(workspace, c->cid.cid_name, workspacelen); + *ret = workspace; + } else + *ret = NULL; + } else if (!strcmp(var + 8, "NUM")) { + /* CALLERIDNUM */ + if (c->cid.cid_num) { + ast_copy_string(workspace, c->cid.cid_num, workspacelen); + *ret = workspace; + } else + *ret = NULL; + } else if (!strcmp(var + 8, "NAME")) { + /* CALLERIDNAME */ + if (c->cid.cid_name) { + ast_copy_string(workspace, c->cid.cid_name, workspacelen); + *ret = workspace; + } else + *ret = NULL; + } + } else if (!strcmp(var + 6, "ANI")) { + /* CALLERANI */ + if (c->cid.cid_ani) { + ast_copy_string(workspace, c->cid.cid_ani, workspacelen); + *ret = workspace; + } else + *ret = NULL; + } else + goto icky; + } else if (!strncmp(var + 4, "ING", 3)) { + if (!strcmp(var + 7, "PRES")) { + /* CALLINGPRES */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_pres); + *ret = workspace; + } else if (!strcmp(var + 7, "ANI2")) { + /* CALLINGANI2 */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_ani2); + *ret = workspace; + } else if (!strcmp(var + 7, "TON")) { + /* CALLINGTON */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_ton); + *ret = workspace; + } else if (!strcmp(var + 7, "TNS")) { + /* CALLINGTNS */ + snprintf(workspace, workspacelen, "%d", c->cid.cid_tns); + *ret = workspace; + } else + goto icky; + } else + goto icky; + } else if (c && !strcmp(var, "DNID")) { + if (c->cid.cid_dnid) { + ast_copy_string(workspace, c->cid.cid_dnid, workspacelen); + *ret = workspace; + } else + *ret = NULL; + } else if (c && !strcmp(var, "HINT")) { + if (!ast_get_hint(workspace, workspacelen, NULL, 0, c, c->context, c->exten)) + *ret = NULL; + else + *ret = workspace; + } else if (c && !strcmp(var, "HINTNAME")) { + if (!ast_get_hint(NULL, 0, workspace, workspacelen, c, c->context, c->exten)) + *ret = NULL; + else + *ret = workspace; + } else if (c && !strcmp(var, "EXTEN")) { + ast_copy_string(workspace, c->exten, workspacelen); + *ret = workspace; + } else if (c && !strcmp(var, "RDNIS")) { + if (c->cid.cid_rdnis) { + ast_copy_string(workspace, c->cid.cid_rdnis, workspacelen); + *ret = workspace; + } else + *ret = NULL; + } else if (c && !strcmp(var, "CONTEXT")) { + ast_copy_string(workspace, c->context, workspacelen); + *ret = workspace; + } else if (c && !strcmp(var, "PRIORITY")) { + snprintf(workspace, workspacelen, "%d", c->priority); + *ret = workspace; + } else if (c && !strcmp(var, "CHANNEL")) { + ast_copy_string(workspace, c->name, workspacelen); + *ret = workspace; + } else if (!strcmp(var, "EPOCH")) { + snprintf(workspace, workspacelen, "%u",(int)time(NULL)); + *ret = workspace; + } else if (!strcmp(var, "DATETIME")) { + thistime=time(NULL); + localtime_r(&thistime, &brokentime); + snprintf(workspace, workspacelen, "%02d%02d%04d-%02d:%02d:%02d", + brokentime.tm_mday, + brokentime.tm_mon+1, + brokentime.tm_year+1900, + brokentime.tm_hour, + brokentime.tm_min, + brokentime.tm_sec + ); + *ret = workspace; + } else if (!strcmp(var, "TIMESTAMP")) { + thistime=time(NULL); + localtime_r(&thistime, &brokentime); + /* 20031130-150612 */ + snprintf(workspace, workspacelen, "%04d%02d%02d-%02d%02d%02d", + brokentime.tm_year+1900, + brokentime.tm_mon+1, + brokentime.tm_mday, + brokentime.tm_hour, + brokentime.tm_min, + brokentime.tm_sec + ); + *ret = workspace; + } else if (c && !strcmp(var, "UNIQUEID")) { + snprintf(workspace, workspacelen, "%s", c->uniqueid); + *ret = workspace; + } else if (c && !strcmp(var, "HANGUPCAUSE")) { + snprintf(workspace, workspacelen, "%d", c->hangupcause); + *ret = workspace; + } else if (c && !strcmp(var, "ACCOUNTCODE")) { + ast_copy_string(workspace, c->accountcode, workspacelen); + *ret = workspace; + } else if (c && !strcmp(var, "LANGUAGE")) { + ast_copy_string(workspace, c->language, workspacelen); + *ret = workspace; + } else { +icky: + if (headp) { + AST_LIST_TRAVERSE(headp,variables,entries) { +#if 0 + ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",var,ast_var_name(variables)); +#endif + if (strcasecmp(ast_var_name(variables),var)==0) { + *ret=ast_var_value(variables); + if (*ret) { + ast_copy_string(workspace, *ret, workspacelen); + *ret = workspace; + } + break; + } + } + } + if (!(*ret)) { + /* Try globals */ + AST_LIST_TRAVERSE(&globals,variables,entries) { +#if 0 + ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",var,ast_var_name(variables)); +#endif + if (strcasecmp(ast_var_name(variables),var)==0) { + *ret = ast_var_value(variables); + if (*ret) { + ast_copy_string(workspace, *ret, workspacelen); + *ret = workspace; + } + } + } + } + } +} + +/*! \brief CLI function to show installed custom functions + \addtogroup CLI_functions + */ +static int handle_show_functions(int fd, int argc, char *argv[]) +{ + struct ast_custom_function *acf; + int count_acf = 0; + + ast_cli(fd, "Installed Custom Functions:\n--------------------------------------------------------------------------------\n"); + for (acf = acf_root ; acf; acf = acf->next) { + ast_cli(fd, "%-20.20s %-35.35s %s\n", acf->name, acf->syntax, acf->synopsis); + count_acf++; + } + ast_cli(fd, "%d custom functions installed.\n", count_acf); + return 0; +} + +static int handle_show_function(int fd, int argc, char *argv[]) +{ + 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]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL; + char stxtitle[40], *syntax = NULL; + int synopsis_size, description_size, syntax_size; + + if (argc < 3) return RESULT_SHOWUSAGE; + + if (!(acf = ast_custom_function_find(argv[2]))) { + ast_cli(fd, "No function by that name registered.\n"); + return RESULT_FAILURE; + + } + + if (acf->synopsis) + synopsis_size = strlen(acf->synopsis) + 23; + else + synopsis_size = strlen("Not available") + 23; + synopsis = alloca(synopsis_size); + + if (acf->desc) + description_size = strlen(acf->desc) + 23; + else + description_size = strlen("Not available") + 23; + description = alloca(description_size); + + if (acf->syntax) + syntax_size = strlen(acf->syntax) + 23; + else + syntax_size = strlen("Not available") + 23; + syntax = alloca(syntax_size); + + snprintf(info, 64 + AST_MAX_APP, "\n -= Info about function '%s' =- \n\n", acf->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, 64 + AST_MAX_APP + 22); + term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(syntax, + acf->syntax ? acf->syntax : "Not available", + COLOR_CYAN, 0, syntax_size); + term_color(synopsis, + acf->synopsis ? acf->synopsis : "Not available", + COLOR_CYAN, 0, synopsis_size); + term_color(description, + acf->desc ? acf->desc : "Not available", + COLOR_CYAN, 0, description_size); + + ast_cli(fd,"%s%s%s\n\n%s%s\n\n%s%s\n", infotitle, stxtitle, syntax, syntitle, synopsis, destitle, description); + + return RESULT_SUCCESS; +} + +static char *complete_show_function(char *line, char *word, int pos, int state) +{ + struct ast_custom_function *acf; + int which = 0; + + /* try to lock functions list ... */ + if (ast_mutex_lock(&acflock)) { + ast_log(LOG_ERROR, "Unable to lock function list\n"); + return NULL; + } + + acf = acf_root; + while (acf) { + if (!strncasecmp(word, acf->name, strlen(word))) { + if (++which > state) { + char *ret = strdup(acf->name); + ast_mutex_unlock(&acflock); + return ret; + } + } + acf = acf->next; + } + + ast_mutex_unlock(&acflock); + return NULL; +} + +struct ast_custom_function* ast_custom_function_find(char *name) +{ + struct ast_custom_function *acfptr; + + /* try to lock functions list ... */ + if (ast_mutex_lock(&acflock)) { + ast_log(LOG_ERROR, "Unable to lock function list\n"); + return NULL; + } + + for (acfptr = acf_root; acfptr; acfptr = acfptr->next) { + if (!strcmp(name, acfptr->name)) { + break; + } + } + + ast_mutex_unlock(&acflock); + + return acfptr; +} + +int ast_custom_function_unregister(struct ast_custom_function *acf) +{ + struct ast_custom_function *acfptr, *lastacf = NULL; + int res = -1; + + if (!acf) + return -1; + + /* try to lock functions list ... */ + if (ast_mutex_lock(&acflock)) { + ast_log(LOG_ERROR, "Unable to lock function list\n"); + return -1; + } + + for (acfptr = acf_root; acfptr; acfptr = acfptr->next) { + if (acfptr == acf) { + if (lastacf) { + lastacf->next = acf->next; + } else { + acf_root = acf->next; + } + res = 0; + break; + } + lastacf = acfptr; + } + + ast_mutex_unlock(&acflock); + + if (!res && (option_verbose > 1)) + ast_verbose(VERBOSE_PREFIX_2 "Unregistered custom function %s\n", acf->name); + + return res; +} + +int ast_custom_function_register(struct ast_custom_function *acf) +{ + if (!acf) + return -1; + + /* try to lock functions list ... */ + if (ast_mutex_lock(&acflock)) { + ast_log(LOG_ERROR, "Unable to lock function list. Failed registering function %s\n", acf->name); + return -1; + } + + if (ast_custom_function_find(acf->name)) { + ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name); + ast_mutex_unlock(&acflock); + return -1; + } + + acf->next = acf_root; + acf_root = acf; + + ast_mutex_unlock(&acflock); + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Registered custom function %s\n", acf->name); + + return 0; +} + +char *ast_func_read(struct ast_channel *chan, const char *in, char *workspace, size_t len) +{ + char *args = NULL, *function, *p; + char *ret = "0"; + struct ast_custom_function *acfptr; + + function = ast_strdupa(in); + if (!function) { + ast_log(LOG_ERROR, "Out of memory\n"); + return ret; + } + if ((args = strchr(function, '('))) { + *args = '\0'; + args++; + if ((p = strrchr(args, ')'))) { + *p = '\0'; + } else { + ast_log(LOG_WARNING, "Can't find trailing parenthesis?\n"); + } + } else { + ast_log(LOG_WARNING, "Function doesn't contain parentheses. Assuming null argument.\n"); + } + + if ((acfptr = ast_custom_function_find(function))) { + /* run the custom function */ + if (acfptr->read) { + return acfptr->read(chan, function, args, workspace, len); + } else { + ast_log(LOG_ERROR, "Function %s cannot be read\n", function); + } + } else { + ast_log(LOG_ERROR, "Function %s not registered\n", function); + } + return ret; +} + +void ast_func_write(struct ast_channel *chan, const char *in, const char *value) +{ + char *args = NULL, *function, *p; + struct ast_custom_function *acfptr; + + function = ast_strdupa(in); + if (!function) { + ast_log(LOG_ERROR, "Out of memory\n"); + return; + } + if ((args = strchr(function, '('))) { + *args = '\0'; + args++; + if ((p = strrchr(args, ')'))) { + *p = '\0'; + } else { + ast_log(LOG_WARNING, "Can't find trailing parenthesis?\n"); + } + } else { + ast_log(LOG_WARNING, "Function doesn't contain parentheses. Assuming null argument.\n"); + } + + if ((acfptr = ast_custom_function_find(function))) { + /* run the custom function */ + if (acfptr->write) { + acfptr->write(chan, function, args, value); + } else { + ast_log(LOG_ERROR, "Function %s is read-only, it cannot be written to\n", function); + } + } else { + ast_log(LOG_ERROR, "Function %s not registered\n", function); + } +} + +static void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count) +{ + char *cp4; + const char *tmp, *whereweare; + int length, offset, offset2, isfunction; + char *workspace = NULL; + char *ltmp = NULL, *var = NULL; + char *nextvar, *nextexp, *nextthing; + char *vars, *vare; + int pos, brackets, needsub, len; + + /* Substitutes variables into cp2, based on string cp1, and assuming cp2 to be + zero-filled */ + whereweare=tmp=cp1; + while(!ast_strlen_zero(whereweare) && count) { + /* Assume we're copying the whole remaining string */ + pos = strlen(whereweare); + nextvar = NULL; + nextexp = NULL; + nextthing = strchr(whereweare, '$'); + if (nextthing) { + switch(nextthing[1]) { + case '{': + nextvar = nextthing; + pos = nextvar - whereweare; + break; + case '[': + nextexp = nextthing; + pos = nextexp - whereweare; + break; + } + } + + if (pos) { + /* Can't copy more than 'count' bytes */ + if (pos > count) + pos = count; + + /* Copy that many bytes */ + memcpy(cp2, whereweare, pos); + + count -= pos; + cp2 += pos; + whereweare += pos; + } + + if (nextvar) { + /* We have a variable. Find the start and end, and determine + if we are going to have to recursively call ourselves on the + contents */ + vars = vare = nextvar + 2; + brackets = 1; + needsub = 0; + + /* Find the end of it */ + while(brackets && *vare) { + if ((vare[0] == '$') && (vare[1] == '{')) { + needsub++; + brackets++; + } else if (vare[0] == '}') { + brackets--; + } else if ((vare[0] == '$') && (vare[1] == '[')) + needsub++; + vare++; + } + if (brackets) + ast_log(LOG_NOTICE, "Error in extension logic (missing '}')\n"); + len = vare - vars - 1; + + /* Skip totally over variable string */ + whereweare += (len + 3); + + if (!var) + var = alloca(VAR_BUF_SIZE); + + /* Store variable name (and truncate) */ + ast_copy_string(var, vars, len + 1); + + /* Substitute if necessary */ + if (needsub) { + if (!ltmp) + ltmp = alloca(VAR_BUF_SIZE); + + memset(ltmp, 0, VAR_BUF_SIZE); + pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1); + vars = ltmp; + } else { + vars = var; + } + + if (!workspace) + workspace = alloca(VAR_BUF_SIZE); + + workspace[0] = '\0'; + + parse_variable_name(vars, &offset, &offset2, &isfunction); + if (isfunction) { + /* Evaluate function */ + cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE); + + ast_log(LOG_DEBUG, "Function result is '%s'\n", cp4 ? cp4 : "(null)"); + } else { + /* Retrieve variable value */ + pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp); + } + if (cp4) { + cp4 = substring(cp4, offset, offset2, workspace, VAR_BUF_SIZE); + + length = strlen(cp4); + if (length > count) + length = count; + memcpy(cp2, cp4, length); + count -= length; + cp2 += length; + } + } else if (nextexp) { + /* We have an expression. Find the start and end, and determine + if we are going to have to recursively call ourselves on the + contents */ + vars = vare = nextexp + 2; + brackets = 1; + needsub = 0; + + /* Find the end of it */ + while(brackets && *vare) { + if ((vare[0] == '$') && (vare[1] == '[')) { + needsub++; + brackets++; + vare++; + } else if (vare[0] == '[') { + brackets++; + } else if (vare[0] == ']') { + brackets--; + } else if ((vare[0] == '$') && (vare[1] == '{')) { + needsub++; + vare++; + } + vare++; + } + if (brackets) + ast_log(LOG_NOTICE, "Error in extension logic (missing ']')\n"); + len = vare - vars - 1; + + /* Skip totally over expression */ + whereweare += (len + 3); + + if (!var) + var = alloca(VAR_BUF_SIZE); + + /* Store variable name (and truncate) */ + ast_copy_string(var, vars, len + 1); + + /* Substitute if necessary */ + if (needsub) { + if (!ltmp) + ltmp = alloca(VAR_BUF_SIZE); + + memset(ltmp, 0, VAR_BUF_SIZE); + pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1); + vars = ltmp; + } else { + vars = var; + } + + length = ast_expr(vars, cp2, count); + + if (length) { + ast_log(LOG_DEBUG, "Expression result is '%s'\n", cp2); + count -= length; + cp2 += length; + } + } else + break; + } +} + +void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count) +{ + pbx_substitute_variables_helper_full(c, (c) ? &c->varshead : NULL, cp1, cp2, count); +} + +void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count) +{ + pbx_substitute_variables_helper_full(NULL, headp, cp1, cp2, count); +} + +static void pbx_substitute_variables(char *passdata, int datalen, struct ast_channel *c, struct ast_exten *e) +{ + memset(passdata, 0, datalen); + + /* No variables or expressions in e->data, so why scan it? */ + if (!strchr(e->data, '$') && !strstr(e->data,"${") && !strstr(e->data,"$[") && !strstr(e->data,"$(")) { + ast_copy_string(passdata, e->data, datalen); + return; + } + + pbx_substitute_variables_helper(c, e->data, passdata, datalen - 1); +} + +static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con, const char *context, const char *exten, int priority, const char *label, const char *callerid, int action) +{ + struct ast_exten *e; + struct ast_app *app; + struct ast_switch *sw; + char *data; + const char *foundcontext=NULL; + int newstack = 0; + int res; + int status = 0; + char *incstack[AST_PBX_MAX_STACK]; + char passdata[EXT_DATA_SIZE]; + int stacklen = 0; + char tmp[80]; + char tmp2[80]; + char tmp3[EXT_DATA_SIZE]; + char atmp[80]; + char atmp2[EXT_DATA_SIZE+100]; + + if (ast_mutex_lock(&conlock)) { + ast_log(LOG_WARNING, "Unable to obtain lock\n"); + if ((action == HELPER_EXISTS) || (action == HELPER_CANMATCH) || (action == HELPER_MATCHMORE)) + return 0; + else + return -1; + } + e = pbx_find_extension(c, con, context, exten, priority, label, callerid, action, incstack, &stacklen, &status, &sw, &data, &foundcontext); + if (e) { + switch(action) { + case HELPER_CANMATCH: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_EXISTS: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_FINDLABEL: + res = e->priority; + ast_mutex_unlock(&conlock); + return res; + case HELPER_MATCHMORE: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_SPAWN: + newstack++; + /* Fall through */ + case HELPER_EXEC: + app = pbx_findapp(e->app); + ast_mutex_unlock(&conlock); + if (app) { + if (c->context != context) + ast_copy_string(c->context, context, sizeof(c->context)); + if (c->exten != exten) + ast_copy_string(c->exten, exten, sizeof(c->exten)); + c->priority = priority; + pbx_substitute_variables(passdata, sizeof(passdata), c, e); + if (option_debug) { + ast_log(LOG_DEBUG, "Launching '%s'\n", app->name); + snprintf(atmp, 80, "STACK-%s-%s-%d", context, exten, priority); + snprintf(atmp2, EXT_DATA_SIZE+100, "%s(\"%s\", \"%s\") %s", app->name, c->name, (!ast_strlen_zero(passdata) ? (char *)passdata : ""), (newstack ? "in new stack" : "in same stack")); + pbx_builtin_setvar_helper(c, atmp, atmp2); + } + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Executing %s(\"%s\", \"%s\") %s\n", + term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)), + term_color(tmp2, c->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)), + term_color(tmp3, (!ast_strlen_zero(passdata) ? (char *)passdata : ""), COLOR_BRMAGENTA, 0, sizeof(tmp3)), + (newstack ? "in new stack" : "in same stack")); + manager_event(EVENT_FLAG_CALL, "Newexten", + "Channel: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n" + "Priority: %d\r\n" + "Application: %s\r\n" + "AppData: %s\r\n" + "Uniqueid: %s\r\n", + c->name, c->context, c->exten, c->priority, app->name, passdata ? passdata : "(NULL)", c->uniqueid); + res = pbx_exec(c, app, passdata, newstack); + return res; + } else { + ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority); + return -1; + } + default: + ast_log(LOG_WARNING, "Huh (%d)?\n", action); return -1; + } + } else if (sw) { + switch(action) { + case HELPER_CANMATCH: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_EXISTS: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_MATCHMORE: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_FINDLABEL: + ast_mutex_unlock(&conlock); + return -1; + case HELPER_SPAWN: + newstack++; + /* Fall through */ + case HELPER_EXEC: + ast_mutex_unlock(&conlock); + if (sw->exec) + res = sw->exec(c, foundcontext ? foundcontext : context, exten, priority, callerid, newstack, data); + else { + ast_log(LOG_WARNING, "No execution engine for switch %s\n", sw->name); + res = -1; + } + return res; + default: + ast_log(LOG_WARNING, "Huh (%d)?\n", action); + return -1; + } + } else { + ast_mutex_unlock(&conlock); + switch(status) { + case STATUS_NO_CONTEXT: + if ((action != HELPER_EXISTS) && (action != HELPER_MATCHMORE)) + ast_log(LOG_NOTICE, "Cannot find extension context '%s'\n", context); + break; + case STATUS_NO_EXTENSION: + if ((action != HELPER_EXISTS) && (action != HELPER_CANMATCH) && (action != HELPER_MATCHMORE)) + ast_log(LOG_NOTICE, "Cannot find extension '%s' in context '%s'\n", exten, context); + break; + case STATUS_NO_PRIORITY: + if ((action != HELPER_EXISTS) && (action != HELPER_CANMATCH) && (action != HELPER_MATCHMORE)) + ast_log(LOG_NOTICE, "No such priority %d in extension '%s' in context '%s'\n", priority, exten, context); + break; + case STATUS_NO_LABEL: + if (context) + ast_log(LOG_NOTICE, "No such label '%s' in extension '%s' in context '%s'\n", label, exten, context); + break; + default: + ast_log(LOG_DEBUG, "Shouldn't happen!\n"); + } + + if ((action != HELPER_EXISTS) && (action != HELPER_CANMATCH) && (action != HELPER_MATCHMORE)) + return -1; + else + return 0; + } + +} + +/*! \brief ast_hint_extension: Find hint for given extension in context */ +static struct ast_exten *ast_hint_extension(struct ast_channel *c, const char *context, const char *exten) +{ + struct ast_exten *e; + struct ast_switch *sw; + char *data; + const char *foundcontext = NULL; + int status = 0; + char *incstack[AST_PBX_MAX_STACK]; + int stacklen = 0; + + if (ast_mutex_lock(&conlock)) { + ast_log(LOG_WARNING, "Unable to obtain lock\n"); + return NULL; + } + e = pbx_find_extension(c, NULL, context, exten, PRIORITY_HINT, NULL, "", HELPER_EXISTS, incstack, &stacklen, &status, &sw, &data, &foundcontext); + ast_mutex_unlock(&conlock); + return e; +} + +/*! \brief ast_extensions_state2: Check state of extension by using hints */ +static int ast_extension_state2(struct ast_exten *e) +{ + char hint[AST_MAX_EXTENSION] = ""; + char *cur, *rest; + int res = -1; + int allunavailable = 1, allbusy = 1, allfree = 1; + int busy = 0, inuse = 0, ring = 0; + + if (!e) + return -1; + + ast_copy_string(hint, ast_get_extension_app(e), sizeof(hint)); + + cur = hint; /* On or more devices separated with a & character */ + do { + rest = strchr(cur, '&'); + if (rest) { + *rest = 0; + rest++; + } + + res = ast_device_state(cur); + switch (res) { + case AST_DEVICE_NOT_INUSE: + allunavailable = 0; + allbusy = 0; + break; + case AST_DEVICE_INUSE: + inuse = 1; + allunavailable = 0; + allfree = 0; + break; + case AST_DEVICE_RINGING: + ring = 1; + allunavailable = 0; + allfree = 0; + break; + case AST_DEVICE_BUSY: + allunavailable = 0; + allfree = 0; + busy = 1; + break; + case AST_DEVICE_UNAVAILABLE: + case AST_DEVICE_INVALID: + allbusy = 0; + allfree = 0; + break; + default: + allunavailable = 0; + allbusy = 0; + allfree = 0; + } + cur = rest; + } while (cur); + + if (!inuse && ring) + return AST_EXTENSION_RINGING; + if (inuse && ring) + return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING); + if (inuse) + return AST_EXTENSION_INUSE; + if (allfree) + return AST_EXTENSION_NOT_INUSE; + if (allbusy) + return AST_EXTENSION_BUSY; + if (allunavailable) + return AST_EXTENSION_UNAVAILABLE; + if (busy) + return AST_EXTENSION_INUSE; + + return AST_EXTENSION_NOT_INUSE; +} + +/*! \brief ast_extension_state2str: Return extension_state as string */ +const char *ast_extension_state2str(int extension_state) +{ + int i; + + for (i = 0; (i < (sizeof(extension_states) / sizeof(extension_states[0]))); i++) { + if (extension_states[i].extension_state == extension_state) { + return extension_states[i].text; + } + } + return "Unknown"; +} + +/*! \brief ast_extension_state: Check extension state for an extension by using hint */ +int ast_extension_state(struct ast_channel *c, char *context, char *exten) +{ + struct ast_exten *e; + + e = ast_hint_extension(c, context, exten); /* Do we have a hint for this extension ? */ + if (!e) + return -1; /* No hint, return -1 */ + + return ast_extension_state2(e); /* Check all devices in the hint */ +} + +void ast_hint_state_changed(const char *device) +{ + struct ast_hint *hint; + struct ast_state_cb *cblist; + char buf[AST_MAX_EXTENSION]; + char *parse; + char *cur; + int state; + + ast_mutex_lock(&hintlock); + + for (hint = hints; hint; hint = hint->next) { + ast_copy_string(buf, ast_get_extension_app(hint->exten), sizeof(buf)); + parse = buf; + for (cur = strsep(&parse, "&"); cur; cur = strsep(&parse, "&")) { + if (strcasecmp(cur, device)) + continue; + + /* Get device state for this hint */ + state = ast_extension_state2(hint->exten); + + if ((state == -1) || (state == hint->laststate)) + continue; + + /* Device state changed since last check - notify the watchers */ + + /* For general callbacks */ + for (cblist = statecbs; cblist; cblist = cblist->next) + cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); + + /* For extension callbacks */ + for (cblist = hint->callbacks; cblist; cblist = cblist->next) + cblist->callback(hint->exten->parent->name, hint->exten->exten, state, cblist->data); + + hint->laststate = state; + break; + } + } + + ast_mutex_unlock(&hintlock); +} + +/*! \brief ast_extension_state_add: Add watcher for extension states */ +int ast_extension_state_add(const char *context, const char *exten, + ast_state_cb_type callback, void *data) +{ + struct ast_hint *list; + struct ast_state_cb *cblist; + struct ast_exten *e; + + /* If there's no context and extension: add callback to statecbs list */ + if (!context && !exten) { + ast_mutex_lock(&hintlock); + + cblist = statecbs; + while (cblist) { + if (cblist->callback == callback) { + cblist->data = data; + ast_mutex_unlock(&hintlock); + return 0; + } + cblist = cblist->next; + } + + /* Now insert the callback */ + cblist = malloc(sizeof(struct ast_state_cb)); + if (!cblist) { + ast_mutex_unlock(&hintlock); + return -1; + } + memset(cblist, 0, sizeof(struct ast_state_cb)); + cblist->id = 0; + cblist->callback = callback; + cblist->data = data; + + cblist->next = statecbs; + statecbs = cblist; + + ast_mutex_unlock(&hintlock); + return 0; + } + + if (!context || !exten) + return -1; + + /* This callback type is for only one hint, so get the hint */ + e = ast_hint_extension(NULL, context, exten); + if (!e) { + return -1; + } + + /* Find the hint in the list of hints */ + ast_mutex_lock(&hintlock); + list = hints; + + while (list) { + if (list->exten == e) + break; + list = list->next; + } + + if (!list) { + /* We have no hint, sorry */ + ast_mutex_unlock(&hintlock); + return -1; + } + + /* Now insert the callback in the callback list */ + cblist = malloc(sizeof(struct ast_state_cb)); + if (!cblist) { + ast_mutex_unlock(&hintlock); + return -1; + } + memset(cblist, 0, sizeof(struct ast_state_cb)); + cblist->id = stateid++; /* Unique ID for this callback */ + cblist->callback = callback; /* Pointer to callback routine */ + cblist->data = data; /* Data for the callback */ + + cblist->next = list->callbacks; + list->callbacks = cblist; + + ast_mutex_unlock(&hintlock); + return cblist->id; +} + +/*! \brief ast_extension_state_del: Remove a watcher from the callback list */ +int ast_extension_state_del(int id, ast_state_cb_type callback) +{ + struct ast_hint *list; + struct ast_state_cb *cblist, *cbprev; + + if (!id && !callback) + return -1; + + ast_mutex_lock(&hintlock); + + /* id is zero is a callback without extension */ + if (!id) { + cbprev = NULL; + cblist = statecbs; + while (cblist) { + if (cblist->callback == callback) { + if (!cbprev) + statecbs = cblist->next; + else + cbprev->next = cblist->next; + + free(cblist); + + ast_mutex_unlock(&hintlock); + return 0; + } + cbprev = cblist; + cblist = cblist->next; + } + + ast_mutex_unlock(&hintlock); + return -1; + } + + /* id greater than zero is a callback with extension */ + /* Find the callback based on ID */ + list = hints; + while (list) { + cblist = list->callbacks; + cbprev = NULL; + while (cblist) { + if (cblist->id==id) { + if (!cbprev) + list->callbacks = cblist->next; + else + cbprev->next = cblist->next; + + free(cblist); + + ast_mutex_unlock(&hintlock); + return 0; + } + cbprev = cblist; + cblist = cblist->next; + } + list = list->next; + } + + ast_mutex_unlock(&hintlock); + return -1; +} + +/*! \brief ast_add_hint: Add hint to hint list, check initial extension state */ +static int ast_add_hint(struct ast_exten *e) +{ + struct ast_hint *list; + + if (!e) + return -1; + + ast_mutex_lock(&hintlock); + list = hints; + + /* Search if hint exists, do nothing */ + while (list) { + if (list->exten == e) { + ast_mutex_unlock(&hintlock); + if (option_debug > 1) + ast_log(LOG_DEBUG, "HINTS: Not re-adding existing hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e)); + return -1; + } + list = list->next; + } + + if (option_debug > 1) + ast_log(LOG_DEBUG, "HINTS: Adding hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e)); + + list = malloc(sizeof(struct ast_hint)); + if (!list) { + ast_mutex_unlock(&hintlock); + if (option_debug > 1) + ast_log(LOG_DEBUG, "HINTS: Out of memory...\n"); + return -1; + } + /* Initialize and insert new item at the top */ + memset(list, 0, sizeof(struct ast_hint)); + list->exten = e; + list->laststate = ast_extension_state2(e); + list->next = hints; + hints = list; + + ast_mutex_unlock(&hintlock); + return 0; +} + +/*! \brief ast_change_hint: Change hint for an extension */ +static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne) +{ + struct ast_hint *list; + + ast_mutex_lock(&hintlock); + list = hints; + + while(list) { + if (list->exten == oe) { + list->exten = ne; + ast_mutex_unlock(&hintlock); + return 0; + } + list = list->next; + } + ast_mutex_unlock(&hintlock); + + return -1; +} + +/*! \brief ast_remove_hint: Remove hint from extension */ +static int ast_remove_hint(struct ast_exten *e) +{ + /* Cleanup the Notifys if hint is removed */ + struct ast_hint *list, *prev = NULL; + struct ast_state_cb *cblist, *cbprev; + + if (!e) + return -1; + + ast_mutex_lock(&hintlock); + + list = hints; + while(list) { + if (list->exten==e) { + cbprev = NULL; + cblist = list->callbacks; + while (cblist) { + /* Notify with -1 and remove all callbacks */ + cbprev = cblist; + cblist = cblist->next; + cbprev->callback(list->exten->parent->name, list->exten->exten, AST_EXTENSION_DEACTIVATED, cbprev->data); + free(cbprev); + } + list->callbacks = NULL; + + if (!prev) + hints = list->next; + else + prev->next = list->next; + free(list); + + ast_mutex_unlock(&hintlock); + return 0; + } else { + prev = list; + list = list->next; + } + } + + ast_mutex_unlock(&hintlock); + return -1; +} + + +/*! \brief ast_get_hint: Get hint for channel */ +int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten) +{ + struct ast_exten *e; + void *tmp; + + e = ast_hint_extension(c, context, exten); + if (e) { + if (hint) + ast_copy_string(hint, ast_get_extension_app(e), hintsize); + if (name) { + tmp = ast_get_extension_app_data(e); + if (tmp) + ast_copy_string(name, (char *) tmp, namesize); + } + return -1; + } + return 0; +} + +int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, HELPER_EXISTS); +} + +int ast_findlabel_extension(struct ast_channel *c, const char *context, const char *exten, const char *label, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, 0, label, callerid, HELPER_FINDLABEL); +} + +int ast_findlabel_extension2(struct ast_channel *c, struct ast_context *con, const char *exten, const char *label, const char *callerid) +{ + return pbx_extension_helper(c, con, NULL, exten, 0, label, callerid, HELPER_FINDLABEL); +} + +int ast_canmatch_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, HELPER_CANMATCH); +} + +int ast_matchmore_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, HELPER_MATCHMORE); +} + +int ast_spawn_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, HELPER_SPAWN); +} + +int ast_exec_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) +{ + return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, HELPER_EXEC); +} + +static int __ast_pbx_run(struct ast_channel *c) +{ + int firstpass = 1; + int digit; + char exten[256]; + int pos; + int waittime; + int res=0; + int autoloopflag; + + /* A little initial setup here */ + if (c->pbx) + ast_log(LOG_WARNING, "%s already has PBX structure??\n", c->name); + c->pbx = malloc(sizeof(struct ast_pbx)); + if (!c->pbx) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + if (c->amaflags) { + if (!c->cdr) { + c->cdr = ast_cdr_alloc(); + if (!c->cdr) { + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + free(c->pbx); + return -1; + } + ast_cdr_init(c->cdr, c); + } + } + memset(c->pbx, 0, sizeof(struct ast_pbx)); + /* Set reasonable defaults */ + c->pbx->rtimeout = 10; + c->pbx->dtimeout = 5; + + autoloopflag = ast_test_flag(c, AST_FLAG_IN_AUTOLOOP); + ast_set_flag(c, AST_FLAG_IN_AUTOLOOP); + + /* Start by trying whatever the channel is set to */ + if (!ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + /* If not successful fall back to 's' */ + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Starting %s at %s,%s,%d failed so falling back to exten 's'\n", c->name, c->context, c->exten, c->priority); + ast_copy_string(c->exten, "s", sizeof(c->exten)); + if (!ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + /* JK02: And finally back to default if everything else failed */ + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Starting %s at %s,%s,%d still failed so falling back to context 'default'\n", c->name, c->context, c->exten, c->priority); + ast_copy_string(c->context, "default", sizeof(c->context)); + } + c->priority = 1; + } + if (c->cdr && !c->cdr->start.tv_sec && !c->cdr->start.tv_usec) + ast_cdr_start(c->cdr); + for(;;) { + pos = 0; + digit = 0; + while(ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + memset(exten, 0, sizeof(exten)); + if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->cid.cid_num))) { + /* Something bad happened, or a hangup has been requested. */ + if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) || + (res == '*') || (res == '#')) { + ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res); + memset(exten, 0, sizeof(exten)); + pos = 0; + exten[pos++] = digit = res; + break; + } + switch(res) { + case AST_PBX_KEEPALIVE: + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE on '%s'\n", c->context, c->exten, c->priority, c->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE on '%s'\n", c->context, c->exten, c->priority, c->name); + goto out; + break; + default: + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) { + c->_softhangup =0; + break; + } + /* atimeout */ + if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT) { + break; + } + + if (c->cdr) { + ast_cdr_update(c); + } + goto out; + } + } + if ((c->_softhangup == AST_SOFTHANGUP_TIMEOUT) && (ast_exists_extension(c,c->context,"T",1,c->cid.cid_num))) { + ast_copy_string(c->exten, "T", sizeof(c->exten)); + /* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */ + c->whentohangup = 0; + c->priority = 0; + c->_softhangup &= ~AST_SOFTHANGUP_TIMEOUT; + } else if (c->_softhangup) { + ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n", + c->exten, c->priority); + goto out; + } + firstpass = 0; + c->priority++; + } + if (!ast_exists_extension(c, c->context, c->exten, 1, c->cid.cid_num)) { + /* It's not a valid extension anymore */ + if (ast_exists_extension(c, c->context, "i", 1, c->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Sent into invalid extension '%s' in context '%s' on %s\n", c->exten, c->context, c->name); + pbx_builtin_setvar_helper(c, "INVALID_EXTEN", c->exten); + ast_copy_string(c->exten, "i", sizeof(c->exten)); + c->priority = 1; + } else { + ast_log(LOG_WARNING, "Channel '%s' sent into invalid extension '%s' in context '%s', but no invalid handler\n", + c->name, c->exten, c->context); + goto out; + } + } else if (c->_softhangup == AST_SOFTHANGUP_TIMEOUT) { + /* If we get this far with AST_SOFTHANGUP_TIMEOUT, then we know that the "T" extension is next. */ + c->_softhangup = 0; + } else { + /* Done, wait for an extension */ + waittime = 0; + if (digit) + waittime = c->pbx->dtimeout; + else if (!autofallthrough) + waittime = c->pbx->rtimeout; + if (waittime) { + while (ast_matchmore_extension(c, c->context, exten, 1, c->cid.cid_num)) { + /* As long as we're willing to wait, and as long as it's not defined, + keep reading digits until we can't possibly get a right answer anymore. */ + digit = ast_waitfordigit(c, waittime * 1000); + if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) { + c->_softhangup = 0; + } else { + if (!digit) + /* No entry */ + break; + if (digit < 0) + /* Error, maybe a hangup */ + goto out; + exten[pos++] = digit; + waittime = c->pbx->dtimeout; + } + } + if (ast_exists_extension(c, c->context, exten, 1, c->cid.cid_num)) { + /* Prepare the next cycle */ + ast_copy_string(c->exten, exten, sizeof(c->exten)); + c->priority = 1; + } else { + /* No such extension */ + if (!ast_strlen_zero(exten)) { + /* An invalid extension */ + if (ast_exists_extension(c, c->context, "i", 1, c->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Invalid extension '%s' in context '%s' on %s\n", exten, c->context, c->name); + pbx_builtin_setvar_helper(c, "INVALID_EXTEN", exten); + ast_copy_string(c->exten, "i", sizeof(c->exten)); + c->priority = 1; + } else { + ast_log(LOG_WARNING, "Invalid extension '%s', but no rule 'i' in context '%s'\n", exten, c->context); + goto out; + } + } else { + /* A simple timeout */ + if (ast_exists_extension(c, c->context, "t", 1, c->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Timeout on %s\n", c->name); + ast_copy_string(c->exten, "t", sizeof(c->exten)); + c->priority = 1; + } else { + ast_log(LOG_WARNING, "Timeout, but no rule 't' in context '%s'\n", c->context); + goto out; + } + } + } + if (c->cdr) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "CDR updated on %s\n",c->name); + ast_cdr_update(c); + } + } else { + char *status; + + status = pbx_builtin_getvar_helper(c, "DIALSTATUS"); + if (!status) + status = "UNKNOWN"; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_2 "Auto fallthrough, channel '%s' status is '%s'\n", c->name, status); + if (!strcasecmp(status, "CONGESTION")) + res = pbx_builtin_congestion(c, "10"); + else if (!strcasecmp(status, "CHANUNAVAIL")) + res = pbx_builtin_congestion(c, "10"); + else if (!strcasecmp(status, "BUSY")) + res = pbx_builtin_busy(c, "10"); + goto out; + } + } + } + if (firstpass) + ast_log(LOG_WARNING, "Don't know what to do with '%s'\n", c->name); +out: + if ((res != AST_PBX_KEEPALIVE) && ast_exists_extension(c, c->context, "h", 1, c->cid.cid_num)) { + c->exten[0] = 'h'; + c->exten[1] = '\0'; + c->priority = 1; + while(ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { + if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->cid.cid_num))) { + /* Something bad happened, or a hangup has been requested. */ + if (option_debug) + ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + else if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name); + break; + } + c->priority++; + } + } + ast_set2_flag(c, autoloopflag, AST_FLAG_IN_AUTOLOOP); + + pbx_destroy(c->pbx); + c->pbx = NULL; + if (res != AST_PBX_KEEPALIVE) + ast_hangup(c); + return 0; +} + +/* Returns 0 on success, non-zero if call limit was reached */ +static int increase_call_count(const struct ast_channel *c) +{ + int failed = 0; + double curloadavg; + ast_mutex_lock(&maxcalllock); + if (option_maxcalls) { + if (countcalls >= option_maxcalls) { + ast_log(LOG_NOTICE, "Maximum call limit of %d calls exceeded by '%s'!\n", option_maxcalls, c->name); + failed = -1; + } + } + if (option_maxload) { + getloadavg(&curloadavg, 1); + if (curloadavg >= option_maxload) { + ast_log(LOG_NOTICE, "Maximum loadavg limit of %lf load exceeded by '%s' (currently %f)!\n", option_maxload, c->name, curloadavg); + failed = -1; + } + } + if (!failed) + countcalls++; + ast_mutex_unlock(&maxcalllock); + + return failed; +} + +static void decrease_call_count(void) +{ + ast_mutex_lock(&maxcalllock); + if (countcalls > 0) + countcalls--; + ast_mutex_unlock(&maxcalllock); +} + +static void *pbx_thread(void *data) +{ + /* Oh joyeous kernel, we're a new thread, with nothing to do but + answer this channel and get it going. + */ + /* NOTE: + The launcher of this function _MUST_ increment 'countcalls' + before invoking the function; it will be decremented when the + PBX has finished running on the channel + */ + struct ast_channel *c = data; + + __ast_pbx_run(c); + decrease_call_count(); + + pthread_exit(NULL); + + return NULL; +} + +enum ast_pbx_result ast_pbx_start(struct ast_channel *c) +{ + pthread_t t; + pthread_attr_t attr; + + if (!c) { + ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n"); + return AST_PBX_FAILED; + } + + if (increase_call_count(c)) + return AST_PBX_CALL_LIMIT; + + /* Start a new thread, and get something handling this channel. */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ast_pthread_create(&t, &attr, pbx_thread, c)) { + ast_log(LOG_WARNING, "Failed to create new channel thread\n"); + return AST_PBX_FAILED; + } + + return AST_PBX_SUCCESS; +} + +enum ast_pbx_result ast_pbx_run(struct ast_channel *c) +{ + enum ast_pbx_result res = AST_PBX_SUCCESS; + + if (increase_call_count(c)) + return AST_PBX_CALL_LIMIT; + + res = __ast_pbx_run(c); + decrease_call_count(); + + return res; +} + +int ast_active_calls(void) +{ + return countcalls; +} + +int pbx_set_autofallthrough(int newval) +{ + int oldval; + oldval = autofallthrough; + if (oldval != newval) + autofallthrough = newval; + return oldval; +} + +/* + * This function locks contexts list by &conlist, search for the right context + * structure, leave context list locked and call ast_context_remove_include2 + * which removes include, unlock contexts list and return ... + */ +int ast_context_remove_include(const char *context, const char *include, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) return -1; + + /* walk contexts and search for the right one ...*/ + c = ast_walk_contexts(NULL); + while (c) { + /* we found one ... */ + if (!strcmp(ast_get_context_name(c), context)) { + int ret; + /* remove include from this context ... */ + ret = ast_context_remove_include2(c, include, registrar); + + ast_unlock_contexts(); + + /* ... return results */ + return ret; + } + c = ast_walk_contexts(c); + } + + /* we can't find the right one context */ + ast_unlock_contexts(); + return -1; +} + +/* + * When we call this function, &conlock lock must be locked, because when + * we giving *con argument, some process can remove/change this context + * and after that there can be segfault. + * + * This function locks given context, removes include, unlock context and + * return. + */ +int ast_context_remove_include2(struct ast_context *con, const char *include, const char *registrar) +{ + struct ast_include *i, *pi = NULL; + + if (ast_mutex_lock(&con->lock)) return -1; + + /* walk includes */ + i = con->includes; + while (i) { + /* find our include */ + if (!strcmp(i->name, include) && + (!registrar || !strcmp(i->registrar, registrar))) { + /* remove from list */ + if (pi) + pi->next = i->next; + else + con->includes = i->next; + /* free include and return */ + free(i); + ast_mutex_unlock(&con->lock); + return 0; + } + pi = i; + i = i->next; + } + + /* we can't find the right include */ + ast_mutex_unlock(&con->lock); + return -1; +} + +/*! + * \note This function locks contexts list by &conlist, search for the rigt context + * structure, leave context list locked and call ast_context_remove_switch2 + * which removes switch, unlock contexts list and return ... + */ +int ast_context_remove_switch(const char *context, const char *sw, const char *data, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) return -1; + + /* walk contexts and search for the right one ...*/ + c = ast_walk_contexts(NULL); + while (c) { + /* we found one ... */ + if (!strcmp(ast_get_context_name(c), context)) { + int ret; + /* remove switch from this context ... */ + ret = ast_context_remove_switch2(c, sw, data, registrar); + + ast_unlock_contexts(); + + /* ... return results */ + return ret; + } + c = ast_walk_contexts(c); + } + + /* we can't find the right one context */ + ast_unlock_contexts(); + return -1; +} + +/*! + * \brief This function locks given context, removes switch, unlock context and + * return. + * \note When we call this function, &conlock lock must be locked, because when + * we giving *con argument, some process can remove/change this context + * and after that there can be segfault. + * + */ +int ast_context_remove_switch2(struct ast_context *con, const char *sw, const char *data, const char *registrar) +{ + struct ast_sw *i, *pi = NULL; + + if (ast_mutex_lock(&con->lock)) return -1; + + /* walk switchs */ + i = con->alts; + while (i) { + /* find our switch */ + if (!strcmp(i->name, sw) && !strcmp(i->data, data) && + (!registrar || !strcmp(i->registrar, registrar))) { + /* remove from list */ + if (pi) + pi->next = i->next; + else + con->alts = i->next; + /* free switch and return */ + free(i); + ast_mutex_unlock(&con->lock); + return 0; + } + pi = i; + i = i->next; + } + + /* we can't find the right switch */ + ast_mutex_unlock(&con->lock); + return -1; +} + +/* + * \note This functions lock contexts list, search for the right context, + * call ast_context_remove_extension2, unlock contexts list and return. + * In this function we are using + */ +int ast_context_remove_extension(const char *context, const char *extension, int priority, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) return -1; + + /* walk contexts ... */ + c = ast_walk_contexts(NULL); + while (c) { + /* ... search for the right one ... */ + if (!strcmp(ast_get_context_name(c), context)) { + /* ... remove extension ... */ + int ret = ast_context_remove_extension2(c, extension, priority, + registrar); + /* ... unlock contexts list and return */ + ast_unlock_contexts(); + return ret; + } + c = ast_walk_contexts(c); + } + + /* we can't find the right context */ + ast_unlock_contexts(); + return -1; +} + +/*! + * \brief This functionc locks given context, search for the right extension and + * fires out all peer in this extensions with given priority. If priority + * is set to 0, all peers are removed. After that, unlock context and + * return. + * \note When do you want to call this function, make sure that &conlock is locked, + * because some process can handle with your *con context before you lock + * it. + * + */ +int ast_context_remove_extension2(struct ast_context *con, const char *extension, int priority, const char *registrar) +{ + struct ast_exten *exten, *prev_exten = NULL; + + if (ast_mutex_lock(&con->lock)) return -1; + + /* go through all extensions in context and search the right one ... */ + exten = con->root; + while (exten) { + + /* look for right extension */ + if (!strcmp(exten->exten, extension) && + (!registrar || !strcmp(exten->registrar, registrar))) { + struct ast_exten *peer; + + /* should we free all peers in this extension? (priority == 0)? */ + if (priority == 0) { + /* remove this extension from context list */ + if (prev_exten) + prev_exten->next = exten->next; + else + con->root = exten->next; + + /* fire out all peers */ + peer = exten; + while (peer) { + exten = peer->peer; + + if (!peer->priority==PRIORITY_HINT) + ast_remove_hint(peer); + + peer->datad(peer->data); + free(peer); + + peer = exten; + } + + ast_mutex_unlock(&con->lock); + return 0; + } else { + /* remove only extension with exten->priority == priority */ + struct ast_exten *previous_peer = NULL; + + peer = exten; + while (peer) { + /* is this our extension? */ + if (peer->priority == priority && + (!registrar || !strcmp(peer->registrar, registrar) )) { + /* we are first priority extension? */ + if (!previous_peer) { + /* exists previous extension here? */ + if (prev_exten) { + /* yes, so we must change next pointer in + * previous connection to next peer + */ + if (peer->peer) { + prev_exten->next = peer->peer; + peer->peer->next = exten->next; + } else + prev_exten->next = exten->next; + } else { + /* no previous extension, we are first + * extension, so change con->root ... + */ + if (peer->peer) + con->root = peer->peer; + else + con->root = exten->next; + } + } else { + /* we are not first priority in extension */ + previous_peer->peer = peer->peer; + } + + /* now, free whole priority extension */ + if (peer->priority==PRIORITY_HINT) + ast_remove_hint(peer); + peer->datad(peer->data); + free(peer); + + ast_mutex_unlock(&con->lock); + return 0; + } else { + /* this is not right extension, skip to next peer */ + previous_peer = peer; + peer = peer->peer; + } + } + + ast_mutex_unlock(&con->lock); + return -1; + } + } + + prev_exten = exten; + exten = exten->next; + } + + /* we can't find right extension */ + ast_mutex_unlock(&con->lock); + return -1; +} + + +/*! \brief Dynamically register a new dial plan application */ +int ast_register_application(const char *app, int (*execute)(struct ast_channel *, void *), const char *synopsis, const char *description) +{ + struct ast_app *tmp, *prev, *cur; + char tmps[80]; + int length; + length = sizeof(struct ast_app); + length += strlen(app) + 1; + if (ast_mutex_lock(&applock)) { + ast_log(LOG_ERROR, "Unable to lock application list\n"); + return -1; + } + tmp = apps; + while(tmp) { + if (!strcasecmp(app, tmp->name)) { + ast_log(LOG_WARNING, "Already have an application '%s'\n", app); + ast_mutex_unlock(&applock); + return -1; + } + tmp = tmp->next; + } + tmp = malloc(length); + if (tmp) { + memset(tmp, 0, length); + strcpy(tmp->name, app); + tmp->execute = execute; + tmp->synopsis = synopsis; + tmp->description = description; + /* Store in alphabetical order */ + cur = apps; + prev = NULL; + while(cur) { + if (strcasecmp(tmp->name, cur->name) < 0) + break; + prev = cur; + cur = cur->next; + } + if (prev) { + tmp->next = prev->next; + prev->next = tmp; + } else { + tmp->next = apps; + apps = tmp; + } + } else { + ast_log(LOG_ERROR, "Out of memory\n"); + ast_mutex_unlock(&applock); + return -1; + } + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Registered application '%s'\n", term_color(tmps, tmp->name, COLOR_BRCYAN, 0, sizeof(tmps))); + ast_mutex_unlock(&applock); + return 0; +} + +int ast_register_switch(struct ast_switch *sw) +{ + struct ast_switch *tmp, *prev=NULL; + if (ast_mutex_lock(&switchlock)) { + ast_log(LOG_ERROR, "Unable to lock switch lock\n"); + return -1; + } + tmp = switches; + while(tmp) { + if (!strcasecmp(tmp->name, sw->name)) + break; + prev = tmp; + tmp = tmp->next; + } + if (tmp) { + ast_mutex_unlock(&switchlock); + ast_log(LOG_WARNING, "Switch '%s' already found\n", sw->name); + return -1; + } + sw->next = NULL; + if (prev) + prev->next = sw; + else + switches = sw; + ast_mutex_unlock(&switchlock); + return 0; +} + +void ast_unregister_switch(struct ast_switch *sw) +{ + struct ast_switch *tmp, *prev=NULL; + if (ast_mutex_lock(&switchlock)) { + ast_log(LOG_ERROR, "Unable to lock switch lock\n"); + return; + } + tmp = switches; + while(tmp) { + if (tmp == sw) { + if (prev) + prev->next = tmp->next; + else + switches = tmp->next; + tmp->next = NULL; + break; + } + prev = tmp; + tmp = tmp->next; + } + ast_mutex_unlock(&switchlock); +} + +/* + * Help for CLI commands ... + */ +static char show_application_help[] = +"Usage: show application <application> [<application> [<application> [...]]]\n" +" Describes a particular application.\n"; + +static char show_functions_help[] = +"Usage: show functions\n" +" List builtin functions accessable as $(function args)\n"; + +static char show_function_help[] = +"Usage: show function <function>\n" +" Describe a particular dialplan function.\n"; + +static char show_applications_help[] = +"Usage: show applications [{like|describing} <text>]\n" +" List applications which are currently available.\n" +" If 'like', <text> will be a substring of the app name\n" +" If 'describing', <text> will be a substring of the description\n"; + +static char show_dialplan_help[] = +"Usage: show dialplan [exten@][context]\n" +" Show dialplan\n"; + +static char show_switches_help[] = +"Usage: show switches\n" +" Show registered switches\n"; + +static char show_hints_help[] = +"Usage: show hints\n" +" Show registered hints\n"; + + +/* + * IMPLEMENTATION OF CLI FUNCTIONS IS IN THE SAME ORDER AS COMMANDS HELPS + * + */ + +/* + * \brief 'show application' CLI command implementation functions ... + */ + +/* + * There is a possibility to show informations about more than one + * application at one time. You can type 'show application Dial Echo' and + * you will see informations about these two applications ... + */ +static char *complete_show_application(char *line, char *word, + int pos, int state) +{ + struct ast_app *a; + int which = 0; + + /* try to lock applications list ... */ + if (ast_mutex_lock(&applock)) { + ast_log(LOG_ERROR, "Unable to lock application list\n"); + return NULL; + } + + /* ... walk all applications ... */ + a = apps; + while (a) { + /* ... check if word matches this application ... */ + if (!strncasecmp(word, a->name, strlen(word))) { + /* ... if this is right app serve it ... */ + if (++which > state) { + char *ret = strdup(a->name); + ast_mutex_unlock(&applock); + return ret; + } + } + a = a->next; + } + + /* no application match */ + ast_mutex_unlock(&applock); + return NULL; +} + +static int handle_show_application(int fd, int argc, char *argv[]) +{ + struct ast_app *a; + int app, no_registered_app = 1; + + if (argc < 3) return RESULT_SHOWUSAGE; + + /* try to lock applications list ... */ + if (ast_mutex_lock(&applock)) { + ast_log(LOG_ERROR, "Unable to lock application list\n"); + return -1; + } + + /* ... go through all applications ... */ + a = apps; + while (a) { + /* ... compare this application name with all arguments given + * to 'show application' command ... */ + for (app = 2; app < argc; app++) { + if (!strcasecmp(a->name, argv[app])) { + /* Maximum number of characters added by terminal coloring is 22 */ + char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40]; + char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL; + int synopsis_size, description_size; + + no_registered_app = 0; + + if (a->synopsis) + synopsis_size = strlen(a->synopsis) + 23; + else + synopsis_size = strlen("Not available") + 23; + synopsis = alloca(synopsis_size); + + if (a->description) + description_size = strlen(a->description) + 23; + else + description_size = strlen("Not available") + 23; + description = alloca(description_size); + + if (synopsis && description) { + snprintf(info, 64 + AST_MAX_APP, "\n -= Info about application '%s' =- \n\n", a->name); + term_color(infotitle, info, COLOR_MAGENTA, 0, 64 + AST_MAX_APP + 22); + term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); + term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); + term_color(synopsis, + a->synopsis ? a->synopsis : "Not available", + COLOR_CYAN, 0, synopsis_size); + term_color(description, + a->description ? a->description : "Not available", + COLOR_CYAN, 0, description_size); + + ast_cli(fd,"%s%s%s\n\n%s%s\n", infotitle, syntitle, synopsis, destitle, description); + } else { + /* ... one of our applications, show info ...*/ + ast_cli(fd,"\n -= Info about application '%s' =- \n\n" + "[Synopsis]\n %s\n\n" + "[Description]\n%s\n", + a->name, + a->synopsis ? a->synopsis : "Not available", + a->description ? a->description : "Not available"); + } + } + } + a = a->next; + } + + ast_mutex_unlock(&applock); + + /* we found at least one app? no? */ + if (no_registered_app) { + ast_cli(fd, "Your application(s) is (are) not registered\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +/*! \brief handle_show_hints: CLI support for listing registred dial plan hints */ +static int handle_show_hints(int fd, int argc, char *argv[]) +{ + struct ast_hint *hint; + int num = 0; + int watchers; + struct ast_state_cb *watcher; + + if (!hints) { + ast_cli(fd, "There are no registered dialplan hints\n"); + return RESULT_SUCCESS; + } + /* ... we have hints ... */ + ast_cli(fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); + if (ast_mutex_lock(&hintlock)) { + ast_log(LOG_ERROR, "Unable to lock hints\n"); + return -1; + } + hint = hints; + while (hint) { + watchers = 0; + for (watcher = hint->callbacks; watcher; watcher = watcher->next) + watchers++; + ast_cli(fd, " %-20.20s: %-20.20s State:%-15.15s Watchers %2d\n", + ast_get_extension_name(hint->exten), ast_get_extension_app(hint->exten), + ast_extension_state2str(hint->laststate), watchers); + num++; + hint = hint->next; + } + ast_cli(fd, "----------------\n"); + ast_cli(fd, "- %d hints registered\n", num); + ast_mutex_unlock(&hintlock); + return RESULT_SUCCESS; +} + +/*! \brief handle_show_switches: CLI support for listing registred dial plan switches */ +static int handle_show_switches(int fd, int argc, char *argv[]) +{ + struct ast_switch *sw; + if (!switches) { + ast_cli(fd, "There are no registered alternative switches\n"); + return RESULT_SUCCESS; + } + /* ... we have applications ... */ + ast_cli(fd, "\n -= Registered Asterisk Alternative Switches =-\n"); + if (ast_mutex_lock(&switchlock)) { + ast_log(LOG_ERROR, "Unable to lock switches\n"); + return -1; + } + sw = switches; + while (sw) { + ast_cli(fd, "%s: %s\n", sw->name, sw->description); + sw = sw->next; + } + ast_mutex_unlock(&switchlock); + return RESULT_SUCCESS; +} + +/* + * 'show applications' CLI command implementation functions ... + */ +static int handle_show_applications(int fd, int argc, char *argv[]) +{ + struct ast_app *a; + int like=0, describing=0; + int total_match = 0; /* Number of matches in like clause */ + int total_apps = 0; /* Number of apps registered */ + + /* try to lock applications list ... */ + if (ast_mutex_lock(&applock)) { + ast_log(LOG_ERROR, "Unable to lock application list\n"); + return -1; + } + + /* ... have we got at least one application (first)? no? */ + if (!apps) { + ast_cli(fd, "There are no registered applications\n"); + ast_mutex_unlock(&applock); + return -1; + } + + /* show applications like <keyword> */ + if ((argc == 4) && (!strcmp(argv[2], "like"))) { + like = 1; + } else if ((argc > 3) && (!strcmp(argv[2], "describing"))) { + describing = 1; + } + + /* show applications describing <keyword1> [<keyword2>] [...] */ + if ((!like) && (!describing)) { + ast_cli(fd, " -= Registered Asterisk Applications =-\n"); + } else { + ast_cli(fd, " -= Matching Asterisk Applications =-\n"); + } + + /* ... go through all applications ... */ + for (a = apps; a; a = a->next) { + /* ... show informations about applications ... */ + int printapp=0; + total_apps++; + if (like) { + if (strcasestr(a->name, argv[3])) { + printapp = 1; + total_match++; + } + } else if (describing) { + if (a->description) { + /* Match all words on command line */ + int i; + printapp = 1; + for (i=3; i<argc; i++) { + if (!strcasestr(a->description, argv[i])) { + printapp = 0; + } else { + total_match++; + } + } + } + } else { + printapp = 1; + } + + if (printapp) { + ast_cli(fd," %20s: %s\n", a->name, a->synopsis ? a->synopsis : "<Synopsis not available>"); + } + } + if ((!like) && (!describing)) { + ast_cli(fd, " -= %d Applications Registered =-\n",total_apps); + } else { + ast_cli(fd, " -= %d Applications Matching =-\n",total_match); + } + + /* ... unlock and return */ + ast_mutex_unlock(&applock); + + return RESULT_SUCCESS; +} + +static char *complete_show_applications(char *line, char *word, int pos, int state) +{ + if (pos == 2) { + if (ast_strlen_zero(word)) { + switch (state) { + case 0: + return strdup("like"); + case 1: + return strdup("describing"); + default: + return NULL; + } + } else if (! strncasecmp(word, "like", strlen(word))) { + if (state == 0) { + return strdup("like"); + } else { + return NULL; + } + } else if (! strncasecmp(word, "describing", strlen(word))) { + if (state == 0) { + return strdup("describing"); + } else { + return NULL; + } + } + } + return NULL; +} + +/* + * 'show dialplan' CLI command implementation functions ... + */ +static char *complete_show_dialplan_context(char *line, char *word, int pos, + int state) +{ + struct ast_context *c; + int which = 0; + + /* we are do completion of [exten@]context on second position only */ + if (pos != 2) return NULL; + + /* try to lock contexts list ... */ + if (ast_lock_contexts()) { + ast_log(LOG_ERROR, "Unable to lock context list\n"); + return NULL; + } + + /* ... walk through all contexts ... */ + c = ast_walk_contexts(NULL); + while(c) { + /* ... word matches context name? yes? ... */ + if (!strncasecmp(word, ast_get_context_name(c), strlen(word))) { + /* ... for serve? ... */ + if (++which > state) { + /* ... yes, serve this context name ... */ + char *ret = strdup(ast_get_context_name(c)); + ast_unlock_contexts(); + return ret; + } + } + c = ast_walk_contexts(c); + } + + /* ... unlock and return */ + ast_unlock_contexts(); + return NULL; +} + +struct dialplan_counters { + int total_context; + int total_exten; + int total_prio; + int context_existence; + int extension_existence; +}; + +static int show_dialplan_helper(int fd, char *context, char *exten, struct dialplan_counters *dpc, struct ast_include *rinclude, int includecount, char *includes[]) +{ + struct ast_context *c; + int res=0, old_total_exten = dpc->total_exten; + + /* try to lock contexts */ + if (ast_lock_contexts()) { + ast_log(LOG_WARNING, "Failed to lock contexts list\n"); + return -1; + } + + /* walk all contexts ... */ + for (c = ast_walk_contexts(NULL); c ; c = ast_walk_contexts(c)) { + /* show this context? */ + if (!context || + !strcmp(ast_get_context_name(c), context)) { + dpc->context_existence = 1; + + /* try to lock context before walking in ... */ + if (!ast_lock_context(c)) { + struct ast_exten *e; + struct ast_include *i; + struct ast_ignorepat *ip; + struct ast_sw *sw; + char buf[256], buf2[256]; + int context_info_printed = 0; + + /* are we looking for exten too? if yes, we print context + * if we our extension only + */ + if (!exten) { + dpc->total_context++; + ast_cli(fd, "[ Context '%s' created by '%s' ]\n", + ast_get_context_name(c), ast_get_context_registrar(c)); + context_info_printed = 1; + } + + /* walk extensions ... */ + for (e = ast_walk_context_extensions(c, NULL); e; e = ast_walk_context_extensions(c, e)) { + struct ast_exten *p; + int prio; + + /* looking for extension? is this our extension? */ + if (exten && + !ast_extension_match(ast_get_extension_name(e), exten)) + { + /* we are looking for extension and it's not our + * extension, so skip to next extension */ + continue; + } + + dpc->extension_existence = 1; + + /* may we print context info? */ + if (!context_info_printed) { + dpc->total_context++; + if (rinclude) { + /* TODO Print more info about rinclude */ + ast_cli(fd, "[ Included context '%s' created by '%s' ]\n", + ast_get_context_name(c), + ast_get_context_registrar(c)); + } else { + ast_cli(fd, "[ Context '%s' created by '%s' ]\n", + ast_get_context_name(c), + ast_get_context_registrar(c)); + } + context_info_printed = 1; + } + dpc->total_prio++; + + /* write extension name and first peer */ + bzero(buf, sizeof(buf)); + snprintf(buf, sizeof(buf), "'%s' =>", + ast_get_extension_name(e)); + + prio = ast_get_extension_priority(e); + if (prio == PRIORITY_HINT) { + snprintf(buf2, sizeof(buf2), + "hint: %s", + ast_get_extension_app(e)); + } else { + snprintf(buf2, sizeof(buf2), + "%d. %s(%s)", + prio, + ast_get_extension_app(e), + (char *)ast_get_extension_app_data(e)); + } + + ast_cli(fd, " %-17s %-45s [%s]\n", buf, buf2, + ast_get_extension_registrar(e)); + + dpc->total_exten++; + /* walk next extension peers */ + for (p=ast_walk_extension_priorities(e, e); p; p=ast_walk_extension_priorities(e, p)) { + dpc->total_prio++; + bzero((void *)buf2, sizeof(buf2)); + bzero((void *)buf, sizeof(buf)); + if (ast_get_extension_label(p)) + snprintf(buf, sizeof(buf), " [%s]", ast_get_extension_label(p)); + prio = ast_get_extension_priority(p); + if (prio == PRIORITY_HINT) { + snprintf(buf2, sizeof(buf2), + "hint: %s", + ast_get_extension_app(p)); + } else { + snprintf(buf2, sizeof(buf2), + "%d. %s(%s)", + prio, + ast_get_extension_app(p), + (char *)ast_get_extension_app_data(p)); + } + + ast_cli(fd," %-17s %-45s [%s]\n", + buf, buf2, + ast_get_extension_registrar(p)); + } + } + + /* walk included and write info ... */ + for (i = ast_walk_context_includes(c, NULL); i; i = ast_walk_context_includes(c, i)) { + bzero(buf, sizeof(buf)); + snprintf(buf, sizeof(buf), "'%s'", + ast_get_include_name(i)); + if (exten) { + /* Check all includes for the requested extension */ + if (includecount >= AST_PBX_MAX_STACK) { + ast_log(LOG_NOTICE, "Maximum include depth exceeded!\n"); + } else { + int dupe=0; + int x; + for (x=0;x<includecount;x++) { + if (!strcasecmp(includes[x], ast_get_include_name(i))) { + dupe++; + break; + } + } + if (!dupe) { + includes[includecount] = (char *)ast_get_include_name(i); + show_dialplan_helper(fd, (char *)ast_get_include_name(i), exten, dpc, i, includecount + 1, includes); + } else { + ast_log(LOG_WARNING, "Avoiding circular include of %s within %s\n", ast_get_include_name(i), context); + } + } + } else { + ast_cli(fd, " Include => %-45s [%s]\n", + buf, ast_get_include_registrar(i)); + } + } + + /* walk ignore patterns and write info ... */ + for (ip=ast_walk_context_ignorepats(c, NULL); ip; ip=ast_walk_context_ignorepats(c, ip)) { + const char *ipname = ast_get_ignorepat_name(ip); + char ignorepat[AST_MAX_EXTENSION]; + snprintf(buf, sizeof(buf), "'%s'", ipname); + snprintf(ignorepat, sizeof(ignorepat), "_%s.", ipname); + if ((!exten) || ast_extension_match(ignorepat, exten)) { + ast_cli(fd, " Ignore pattern => %-45s [%s]\n", + buf, ast_get_ignorepat_registrar(ip)); + } + } + if (!rinclude) { + for (sw = ast_walk_context_switches(c, NULL); sw; sw = ast_walk_context_switches(c, sw)) { + snprintf(buf, sizeof(buf), "'%s/%s'", + ast_get_switch_name(sw), + ast_get_switch_data(sw)); + ast_cli(fd, " Alt. Switch => %-45s [%s]\n", + buf, ast_get_switch_registrar(sw)); + } + } + + ast_unlock_context(c); + + /* if we print something in context, make an empty line */ + if (context_info_printed) ast_cli(fd, "\r\n"); + } + } + } + ast_unlock_contexts(); + + if (dpc->total_exten == old_total_exten) { + /* Nothing new under the sun */ + return -1; + } else { + return res; + } +} + +static int handle_show_dialplan(int fd, int argc, char *argv[]) +{ + char *exten = NULL, *context = NULL; + /* Variables used for different counters */ + struct dialplan_counters counters; + char *incstack[AST_PBX_MAX_STACK]; + memset(&counters, 0, sizeof(counters)); + + if (argc != 2 && argc != 3) + return RESULT_SHOWUSAGE; + + /* we obtain [exten@]context? if yes, split them ... */ + if (argc == 3) { + char *splitter = ast_strdupa(argv[2]); + /* is there a '@' character? */ + if (splitter && strchr(argv[2], '@')) { + /* yes, split into exten & context ... */ + exten = strsep(&splitter, "@"); + context = splitter; + + /* check for length and change to NULL if ast_strlen_zero() */ + if (ast_strlen_zero(exten)) + exten = NULL; + if (ast_strlen_zero(context)) + context = NULL; + show_dialplan_helper(fd, context, exten, &counters, NULL, 0, incstack); + } else { + /* no '@' char, only context given */ + context = argv[2]; + if (ast_strlen_zero(context)) + context = NULL; + show_dialplan_helper(fd, context, exten, &counters, NULL, 0, incstack); + } + } else { + /* Show complete dial plan */ + show_dialplan_helper(fd, NULL, NULL, &counters, NULL, 0, incstack); + } + + /* check for input failure and throw some error messages */ + if (context && !counters.context_existence) { + ast_cli(fd, "There is no existence of '%s' context\n", context); + return RESULT_FAILURE; + } + + if (exten && !counters.extension_existence) { + if (context) + ast_cli(fd, "There is no existence of %s@%s extension\n", + exten, context); + else + ast_cli(fd, + "There is no existence of '%s' extension in all contexts\n", + exten); + return RESULT_FAILURE; + } + + ast_cli(fd,"-= %d %s (%d %s) in %d %s. =-\n", + counters.total_exten, counters.total_exten == 1 ? "extension" : "extensions", + counters.total_prio, counters.total_prio == 1 ? "priority" : "priorities", + counters.total_context, counters.total_context == 1 ? "context" : "contexts"); + + /* everything ok */ + return RESULT_SUCCESS; +} + +/* + * CLI entries for upper commands ... + */ +static struct ast_cli_entry pbx_cli[] = { + { { "show", "applications", NULL }, handle_show_applications, + "Shows registered dialplan applications", show_applications_help, complete_show_applications }, + { { "show", "functions", NULL }, handle_show_functions, + "Shows registered dialplan functions", show_functions_help }, + { { "show" , "function", NULL }, handle_show_function, + "Describe a specific dialplan function", show_function_help, complete_show_function }, + { { "show", "application", NULL }, handle_show_application, + "Describe a specific dialplan application", show_application_help, complete_show_application }, + { { "show", "dialplan", NULL }, handle_show_dialplan, + "Show dialplan", show_dialplan_help, complete_show_dialplan_context }, + { { "show", "switches", NULL }, handle_show_switches, + "Show alternative switches", show_switches_help }, + { { "show", "hints", NULL }, handle_show_hints, + "Show dialplan hints", show_hints_help }, +}; + +int ast_unregister_application(const char *app) +{ + struct ast_app *tmp, *tmpl = NULL; + if (ast_mutex_lock(&applock)) { + ast_log(LOG_ERROR, "Unable to lock application list\n"); + return -1; + } + tmp = apps; + while(tmp) { + if (!strcasecmp(app, tmp->name)) { + if (tmpl) + tmpl->next = tmp->next; + else + apps = tmp->next; + if (option_verbose > 1) + ast_verbose( VERBOSE_PREFIX_2 "Unregistered application '%s'\n", tmp->name); + free(tmp); + ast_mutex_unlock(&applock); + return 0; + } + tmpl = tmp; + tmp = tmp->next; + } + ast_mutex_unlock(&applock); + return -1; +} + +struct ast_context *ast_context_create(struct ast_context **extcontexts, const char *name, const char *registrar) +{ + struct ast_context *tmp, **local_contexts; + int length; + length = sizeof(struct ast_context); + length += strlen(name) + 1; + if (!extcontexts) { + local_contexts = &contexts; + ast_mutex_lock(&conlock); + } else + local_contexts = extcontexts; + + tmp = *local_contexts; + while(tmp) { + if (!strcasecmp(tmp->name, name)) { + ast_mutex_unlock(&conlock); + ast_log(LOG_WARNING, "Tried to register context '%s', already in use\n", name); + if (!extcontexts) + ast_mutex_unlock(&conlock); + return NULL; + } + tmp = tmp->next; + } + tmp = malloc(length); + if (tmp) { + memset(tmp, 0, length); + ast_mutex_init(&tmp->lock); + strcpy(tmp->name, name); + tmp->root = NULL; + tmp->registrar = registrar; + tmp->next = *local_contexts; + tmp->includes = NULL; + tmp->ignorepats = NULL; + *local_contexts = tmp; + if (option_debug) + ast_log(LOG_DEBUG, "Registered context '%s'\n", tmp->name); + else if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Registered extension context '%s'\n", tmp->name); + } else + ast_log(LOG_ERROR, "Out of memory\n"); + + if (!extcontexts) + ast_mutex_unlock(&conlock); + return tmp; +} + +void __ast_context_destroy(struct ast_context *con, const char *registrar); + +struct store_hint { + char *context; + char *exten; + struct ast_state_cb *callbacks; + int laststate; + AST_LIST_ENTRY(store_hint) list; + char data[1]; +}; + +AST_LIST_HEAD(store_hints, store_hint); + +void ast_merge_contexts_and_delete(struct ast_context **extcontexts, const char *registrar) +{ + struct ast_context *tmp, *lasttmp = NULL; + struct store_hints store; + struct store_hint *this; + struct ast_hint *hint; + struct ast_exten *exten; + int length; + struct ast_state_cb *thiscb, *prevcb; + + /* preserve all watchers for hints associated with this registrar */ + AST_LIST_HEAD_INIT(&store); + ast_mutex_lock(&hintlock); + for (hint = hints; hint; hint = hint->next) { + if (hint->callbacks && !strcmp(registrar, hint->exten->parent->registrar)) { + length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2 + sizeof(*this); + this = calloc(1, length); + if (!this) { + ast_log(LOG_WARNING, "Could not allocate memory to preserve hint\n"); + continue; + } + this->callbacks = hint->callbacks; + hint->callbacks = NULL; + this->laststate = hint->laststate; + this->context = this->data; + strcpy(this->data, hint->exten->parent->name); + this->exten = this->data + strlen(this->context) + 1; + strcpy(this->exten, hint->exten->exten); + AST_LIST_INSERT_HEAD(&store, this, list); + } + } + ast_mutex_unlock(&hintlock); + + tmp = *extcontexts; + ast_mutex_lock(&conlock); + if (registrar) { + __ast_context_destroy(NULL,registrar); + while (tmp) { + lasttmp = tmp; + tmp = tmp->next; + } + } else { + while (tmp) { + __ast_context_destroy(tmp,tmp->registrar); + lasttmp = tmp; + tmp = tmp->next; + } + } + if (lasttmp) { + lasttmp->next = contexts; + contexts = *extcontexts; + *extcontexts = NULL; + } else + ast_log(LOG_WARNING, "Requested contexts didn't get merged\n"); + ast_mutex_unlock(&conlock); + + /* restore the watchers for hints that can be found; notify those that + cannot be restored + */ + while ((this = AST_LIST_REMOVE_HEAD(&store, list))) { + exten = ast_hint_extension(NULL, this->context, this->exten); + /* Find the hint in the list of hints */ + ast_mutex_lock(&hintlock); + for (hint = hints; hint; hint = hint->next) { + if (hint->exten == exten) + break; + } + if (!exten || !hint) { + /* this hint has been removed, notify the watchers */ + prevcb = NULL; + thiscb = this->callbacks; + while (thiscb) { + prevcb = thiscb; + thiscb = thiscb->next; + prevcb->callback(this->context, this->exten, AST_EXTENSION_REMOVED, prevcb->data); + free(prevcb); + } + } else { + thiscb = this->callbacks; + while (thiscb->next) + thiscb = thiscb->next; + thiscb->next = hint->callbacks; + hint->callbacks = this->callbacks; + hint->laststate = this->laststate; + } + ast_mutex_unlock(&hintlock); + free(this); + } + + return; +} + +/* + * errno values + * EBUSY - can't lock + * ENOENT - no existence of context + */ +int ast_context_add_include(const char *context, const char *include, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) { + errno = EBUSY; + return -1; + } + + /* walk contexts ... */ + c = ast_walk_contexts(NULL); + while (c) { + /* ... search for the right one ... */ + if (!strcmp(ast_get_context_name(c), context)) { + int ret = ast_context_add_include2(c, include, registrar); + /* ... unlock contexts list and return */ + ast_unlock_contexts(); + return ret; + } + c = ast_walk_contexts(c); + } + + /* we can't find the right context */ + ast_unlock_contexts(); + errno = ENOENT; + return -1; +} + +#define FIND_NEXT \ +do { \ + c = info; \ + while(*c && (*c != '|')) c++; \ + if (*c) { *c = '\0'; c++; } else c = NULL; \ +} while(0) + +static void get_timerange(struct ast_timing *i, char *times) +{ + char *e; + int x; + int s1, s2; + int e1, e2; + /* int cth, ctm; */ + + /* start disabling all times, fill the fields with 0's, as they may contain garbage */ + memset(i->minmask, 0, sizeof(i->minmask)); + + /* Star is all times */ + if (ast_strlen_zero(times) || !strcmp(times, "*")) { + for (x=0; x<24; x++) + i->minmask[x] = (1 << 30) - 1; + return; + } + /* Otherwise expect a range */ + e = strchr(times, '-'); + if (!e) { + ast_log(LOG_WARNING, "Time range is not valid. Assuming no restrictions based on time.\n"); + return; + } + *e = '\0'; + e++; + while(*e && !isdigit(*e)) + e++; + if (!*e) { + ast_log(LOG_WARNING, "Invalid time range. Assuming no restrictions based on time.\n"); + return; + } + if (sscanf(times, "%d:%d", &s1, &s2) != 2) { + ast_log(LOG_WARNING, "%s isn't a time. Assuming no restrictions based on time.\n", times); + return; + } + if (sscanf(e, "%d:%d", &e1, &e2) != 2) { + ast_log(LOG_WARNING, "%s isn't a time. Assuming no restrictions based on time.\n", e); + return; + } + +#if 1 + s1 = s1 * 30 + s2/2; + if ((s1 < 0) || (s1 >= 24*30)) { + ast_log(LOG_WARNING, "%s isn't a valid start time. Assuming no time.\n", times); + return; + } + e1 = e1 * 30 + e2/2; + if ((e1 < 0) || (e1 >= 24*30)) { + ast_log(LOG_WARNING, "%s isn't a valid end time. Assuming no time.\n", e); + return; + } + /* Go through the time and enable each appropriate bit */ + for (x=s1;x != e1;x = (x + 1) % (24 * 30)) { + i->minmask[x/30] |= (1 << (x % 30)); + } + /* Do the last one */ + i->minmask[x/30] |= (1 << (x % 30)); +#else + for (cth=0; cth<24; cth++) { + /* Initialize masks to blank */ + i->minmask[cth] = 0; + for (ctm=0; ctm<30; ctm++) { + if ( + /* First hour with more than one hour */ + (((cth == s1) && (ctm >= s2)) && + ((cth < e1))) + /* Only one hour */ + || (((cth == s1) && (ctm >= s2)) && + ((cth == e1) && (ctm <= e2))) + /* In between first and last hours (more than 2 hours) */ + || ((cth > s1) && + (cth < e1)) + /* Last hour with more than one hour */ + || ((cth > s1) && + ((cth == e1) && (ctm <= e2))) + ) + i->minmask[cth] |= (1 << (ctm / 2)); + } + } +#endif + /* All done */ + return; +} + +static char *days[] = +{ + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", +}; + +/*! \brief get_dow: Get day of week */ +static unsigned int get_dow(char *dow) +{ + char *c; + /* The following line is coincidence, really! */ + int s, e, x; + unsigned int mask; + + /* Check for all days */ + if (ast_strlen_zero(dow) || !strcmp(dow, "*")) + return (1 << 7) - 1; + /* Get start and ending days */ + c = strchr(dow, '-'); + if (c) { + *c = '\0'; + c++; + } else + c = NULL; + /* Find the start */ + s = 0; + while((s < 7) && strcasecmp(dow, days[s])) s++; + if (s >= 7) { + ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", dow); + return 0; + } + if (c) { + e = 0; + while((e < 7) && strcasecmp(c, days[e])) e++; + if (e >= 7) { + ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", c); + return 0; + } + } else + e = s; + mask = 0; + for (x=s; x != e; x = (x + 1) % 7) { + mask |= (1 << x); + } + /* One last one */ + mask |= (1 << x); + return mask; +} + +static unsigned int get_day(char *day) +{ + char *c; + /* The following line is coincidence, really! */ + int s, e, x; + unsigned int mask; + + /* Check for all days */ + if (ast_strlen_zero(day) || !strcmp(day, "*")) { + mask = (1 << 30) + ((1 << 30) - 1); + return mask; + } + /* Get start and ending days */ + c = strchr(day, '-'); + if (c) { + *c = '\0'; + c++; + } + /* Find the start */ + if (sscanf(day, "%d", &s) != 1) { + ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", day); + return 0; + } + if ((s < 1) || (s > 31)) { + ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", day); + return 0; + } + s--; + if (c) { + if (sscanf(c, "%d", &e) != 1) { + ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", c); + return 0; + } + if ((e < 1) || (e > 31)) { + ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", c); + return 0; + } + e--; + } else + e = s; + mask = 0; + for (x=s; x!=e; x = (x + 1) % 31) { + mask |= (1 << x); + } + mask |= (1 << x); + return mask; +} + +static char *months[] = +{ + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", +}; + +static unsigned int get_month(char *mon) +{ + char *c; + /* The following line is coincidence, really! */ + int s, e, x; + unsigned int mask; + + /* Check for all days */ + if (ast_strlen_zero(mon) || !strcmp(mon, "*")) + return (1 << 12) - 1; + /* Get start and ending days */ + c = strchr(mon, '-'); + if (c) { + *c = '\0'; + c++; + } + /* Find the start */ + s = 0; + while((s < 12) && strcasecmp(mon, months[s])) s++; + if (s >= 12) { + ast_log(LOG_WARNING, "Invalid month '%s', assuming none\n", mon); + return 0; + } + if (c) { + e = 0; + while((e < 12) && strcasecmp(mon, months[e])) e++; + if (e >= 12) { + ast_log(LOG_WARNING, "Invalid month '%s', assuming none\n", c); + return 0; + } + } else + e = s; + mask = 0; + for (x=s; x!=e; x = (x + 1) % 12) { + mask |= (1 << x); + } + /* One last one */ + mask |= (1 << x); + return mask; +} + +int ast_build_timing(struct ast_timing *i, char *info_in) +{ + char info_save[256]; + char *info; + char *c; + + /* Check for empty just in case */ + if (ast_strlen_zero(info_in)) + return 0; + /* make a copy just in case we were passed a static string */ + ast_copy_string(info_save, info_in, sizeof(info_save)); + info = info_save; + /* Assume everything except time */ + i->monthmask = (1 << 12) - 1; + i->daymask = (1 << 30) - 1 + (1 << 30); + i->dowmask = (1 << 7) - 1; + /* Avoid using str tok */ + FIND_NEXT; + /* Info has the time range, start with that */ + get_timerange(i, info); + info = c; + if (!info) + return 1; + FIND_NEXT; + /* Now check for day of week */ + i->dowmask = get_dow(info); + + info = c; + if (!info) + return 1; + FIND_NEXT; + /* Now check for the day of the month */ + i->daymask = get_day(info); + info = c; + if (!info) + return 1; + FIND_NEXT; + /* And finally go for the month */ + i->monthmask = get_month(info); + + return 1; +} + +int ast_check_timing(struct ast_timing *i) +{ + struct tm tm; + time_t t; + + time(&t); + localtime_r(&t,&tm); + + /* If it's not the right month, return */ + if (!(i->monthmask & (1 << tm.tm_mon))) { + return 0; + } + + /* If it's not that time of the month.... */ + /* Warning, tm_mday has range 1..31! */ + if (!(i->daymask & (1 << (tm.tm_mday-1)))) + return 0; + + /* If it's not the right day of the week */ + if (!(i->dowmask & (1 << tm.tm_wday))) + return 0; + + /* Sanity check the hour just to be safe */ + if ((tm.tm_hour < 0) || (tm.tm_hour > 23)) { + ast_log(LOG_WARNING, "Insane time...\n"); + return 0; + } + + /* Now the tough part, we calculate if it fits + in the right time based on min/hour */ + if (!(i->minmask[tm.tm_hour] & (1 << (tm.tm_min / 2)))) + return 0; + + /* If we got this far, then we're good */ + return 1; +} + +/* + * errno values + * ENOMEM - out of memory + * EBUSY - can't lock + * EEXIST - already included + * EINVAL - there is no existence of context for inclusion + */ +int ast_context_add_include2(struct ast_context *con, const char *value, + const char *registrar) +{ + struct ast_include *new_include; + char *c; + struct ast_include *i, *il = NULL; /* include, include_last */ + int length; + char *p; + + length = sizeof(struct ast_include); + length += 2 * (strlen(value) + 1); + + /* allocate new include structure ... */ + if (!(new_include = malloc(length))) { + ast_log(LOG_ERROR, "Out of memory\n"); + errno = ENOMEM; + return -1; + } + + /* ... fill in this structure ... */ + memset(new_include, 0, length); + p = new_include->stuff; + new_include->name = p; + strcpy(new_include->name, value); + p += strlen(value) + 1; + new_include->rname = p; + strcpy(new_include->rname, value); + c = new_include->rname; + /* Strip off timing info */ + while(*c && (*c != '|')) + c++; + /* Process if it's there */ + if (*c) { + new_include->hastime = ast_build_timing(&(new_include->timing), c+1); + *c = '\0'; + } + new_include->next = NULL; + new_include->registrar = registrar; + + /* ... try to lock this context ... */ + if (ast_mutex_lock(&con->lock)) { + free(new_include); + errno = EBUSY; + return -1; + } + + /* ... go to last include and check if context is already included too... */ + i = con->includes; + while (i) { + if (!strcasecmp(i->name, new_include->name)) { + free(new_include); + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + il = i; + i = i->next; + } + + /* ... include new context into context list, unlock, return */ + if (il) + il->next = new_include; + else + con->includes = new_include; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Including context '%s' in context '%s'\n", new_include->name, ast_get_context_name(con)); + ast_mutex_unlock(&con->lock); + + return 0; +} + +/* + * errno values + * EBUSY - can't lock + * ENOENT - no existence of context + */ +int ast_context_add_switch(const char *context, const char *sw, const char *data, int eval, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) { + errno = EBUSY; + return -1; + } + + /* walk contexts ... */ + c = ast_walk_contexts(NULL); + while (c) { + /* ... search for the right one ... */ + if (!strcmp(ast_get_context_name(c), context)) { + int ret = ast_context_add_switch2(c, sw, data, eval, registrar); + /* ... unlock contexts list and return */ + ast_unlock_contexts(); + return ret; + } + c = ast_walk_contexts(c); + } + + /* we can't find the right context */ + ast_unlock_contexts(); + errno = ENOENT; + return -1; +} + +/* + * errno values + * ENOMEM - out of memory + * EBUSY - can't lock + * EEXIST - already included + * EINVAL - there is no existence of context for inclusion + */ +int ast_context_add_switch2(struct ast_context *con, const char *value, + const char *data, int eval, const char *registrar) +{ + struct ast_sw *new_sw; + struct ast_sw *i, *il = NULL; /* sw, sw_last */ + int length; + char *p; + + length = sizeof(struct ast_sw); + length += strlen(value) + 1; + if (data) + length += strlen(data); + length++; + if (eval) { + /* Create buffer for evaluation of variables */ + length += SWITCH_DATA_LENGTH; + length++; + } + + /* allocate new sw structure ... */ + if (!(new_sw = malloc(length))) { + ast_log(LOG_ERROR, "Out of memory\n"); + errno = ENOMEM; + return -1; + } + + /* ... fill in this structure ... */ + memset(new_sw, 0, length); + p = new_sw->stuff; + new_sw->name = p; + strcpy(new_sw->name, value); + p += strlen(value) + 1; + new_sw->data = p; + if (data) { + strcpy(new_sw->data, data); + p += strlen(data) + 1; + } else { + strcpy(new_sw->data, ""); + p++; + } + if (eval) + new_sw->tmpdata = p; + new_sw->next = NULL; + new_sw->eval = eval; + new_sw->registrar = registrar; + + /* ... try to lock this context ... */ + if (ast_mutex_lock(&con->lock)) { + free(new_sw); + errno = EBUSY; + return -1; + } + + /* ... go to last sw and check if context is already swd too... */ + i = con->alts; + while (i) { + if (!strcasecmp(i->name, new_sw->name) && !strcasecmp(i->data, new_sw->data)) { + free(new_sw); + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + il = i; + i = i->next; + } + + /* ... sw new context into context list, unlock, return */ + if (il) + il->next = new_sw; + else + con->alts = new_sw; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Including switch '%s/%s' in context '%s'\n", new_sw->name, new_sw->data, ast_get_context_name(con)); + ast_mutex_unlock(&con->lock); + + return 0; +} + +/* + * EBUSY - can't lock + * ENOENT - there is not context existence + */ +int ast_context_remove_ignorepat(const char *context, const char *ignorepat, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) { + errno = EBUSY; + return -1; + } + + c = ast_walk_contexts(NULL); + while (c) { + if (!strcmp(ast_get_context_name(c), context)) { + int ret = ast_context_remove_ignorepat2(c, ignorepat, registrar); + ast_unlock_contexts(); + return ret; + } + c = ast_walk_contexts(c); + } + + ast_unlock_contexts(); + errno = ENOENT; + return -1; +} + +int ast_context_remove_ignorepat2(struct ast_context *con, const char *ignorepat, const char *registrar) +{ + struct ast_ignorepat *ip, *ipl = NULL; + + if (ast_mutex_lock(&con->lock)) { + errno = EBUSY; + return -1; + } + + ip = con->ignorepats; + while (ip) { + if (!strcmp(ip->pattern, ignorepat) && + (!registrar || (registrar == ip->registrar))) { + if (ipl) { + ipl->next = ip->next; + free(ip); + } else { + con->ignorepats = ip->next; + free(ip); + } + ast_mutex_unlock(&con->lock); + return 0; + } + ipl = ip; ip = ip->next; + } + + ast_mutex_unlock(&con->lock); + errno = EINVAL; + return -1; +} + +/* + * EBUSY - can't lock + * ENOENT - there is no existence of context + */ +int ast_context_add_ignorepat(const char *con, const char *value, const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) { + errno = EBUSY; + return -1; + } + + c = ast_walk_contexts(NULL); + while (c) { + if (!strcmp(ast_get_context_name(c), con)) { + int ret = ast_context_add_ignorepat2(c, value, registrar); + ast_unlock_contexts(); + return ret; + } + c = ast_walk_contexts(c); + } + + ast_unlock_contexts(); + errno = ENOENT; + return -1; +} + +int ast_context_add_ignorepat2(struct ast_context *con, const char *value, const char *registrar) +{ + struct ast_ignorepat *ignorepat, *ignorepatc, *ignorepatl = NULL; + int length; + length = sizeof(struct ast_ignorepat); + length += strlen(value) + 1; + ignorepat = malloc(length); + if (!ignorepat) { + ast_log(LOG_ERROR, "Out of memory\n"); + errno = ENOMEM; + return -1; + } + memset(ignorepat, 0, length); + strcpy(ignorepat->pattern, value); + ignorepat->next = NULL; + ignorepat->registrar = registrar; + ast_mutex_lock(&con->lock); + ignorepatc = con->ignorepats; + while(ignorepatc) { + ignorepatl = ignorepatc; + if (!strcasecmp(ignorepatc->pattern, value)) { + /* Already there */ + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + ignorepatc = ignorepatc->next; + } + if (ignorepatl) + ignorepatl->next = ignorepat; + else + con->ignorepats = ignorepat; + ast_mutex_unlock(&con->lock); + return 0; + +} + +int ast_ignore_pattern(const char *context, const char *pattern) +{ + struct ast_context *con; + struct ast_ignorepat *pat; + + con = ast_context_find(context); + if (con) { + pat = con->ignorepats; + while (pat) { + if (ast_extension_match(pat->pattern, pattern)) + return 1; + pat = pat->next; + } + } + return 0; +} + +/* + * EBUSY - can't lock + * ENOENT - no existence of context + * + */ +int ast_add_extension(const char *context, int replace, const char *extension, int priority, const char *label, const char *callerid, + const char *application, void *data, void (*datad)(void *), const char *registrar) +{ + struct ast_context *c; + + if (ast_lock_contexts()) { + errno = EBUSY; + return -1; + } + + c = ast_walk_contexts(NULL); + while (c) { + if (!strcmp(context, ast_get_context_name(c))) { + int ret = ast_add_extension2(c, replace, extension, priority, label, callerid, + application, data, datad, registrar); + ast_unlock_contexts(); + return ret; + } + c = ast_walk_contexts(c); + } + + ast_unlock_contexts(); + errno = ENOENT; + return -1; +} + +int ast_explicit_goto(struct ast_channel *chan, const char *context, const char *exten, int priority) +{ + if (!chan) + return -1; + + if (!ast_strlen_zero(context)) + ast_copy_string(chan->context, context, sizeof(chan->context)); + if (!ast_strlen_zero(exten)) + ast_copy_string(chan->exten, exten, sizeof(chan->exten)); + if (priority > -1) { + chan->priority = priority; + /* see flag description in channel.h for explanation */ + if (ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP)) + chan->priority--; + } + + return 0; +} + +int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority) +{ + int res = 0; + + ast_mutex_lock(&chan->lock); + + if (chan->pbx) { + /* This channel is currently in the PBX */ + ast_explicit_goto(chan, context, exten, priority); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO); + } else { + /* In order to do it when the channel doesn't really exist within + the PBX, we have to make a new channel, masquerade, and start the PBX + at the new location */ + struct ast_channel *tmpchan; + tmpchan = ast_channel_alloc(0); + if (tmpchan) { + snprintf(tmpchan->name, sizeof(tmpchan->name), "AsyncGoto/%s", chan->name); + ast_setstate(tmpchan, chan->_state); + /* Make formats okay */ + tmpchan->readformat = chan->readformat; + tmpchan->writeformat = chan->writeformat; + /* Setup proper location */ + ast_explicit_goto(tmpchan, + (!ast_strlen_zero(context)) ? context : chan->context, + (!ast_strlen_zero(exten)) ? exten : chan->exten, + priority); + + /* Masquerade into temp channel */ + ast_channel_masquerade(tmpchan, chan); + + /* Grab the locks and get going */ + ast_mutex_lock(&tmpchan->lock); + ast_do_masquerade(tmpchan); + ast_mutex_unlock(&tmpchan->lock); + /* Start the PBX going on our stolen channel */ + if (ast_pbx_start(tmpchan)) { + ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmpchan->name); + ast_hangup(tmpchan); + res = -1; + } + } else { + res = -1; + } + } + ast_mutex_unlock(&chan->lock); + return res; +} + +int ast_async_goto_by_name(const char *channame, const char *context, const char *exten, int priority) +{ + struct ast_channel *chan; + int res = -1; + + chan = ast_get_channel_by_name_locked(channame); + if (chan) { + res = ast_async_goto(chan, context, exten, priority); + ast_mutex_unlock(&chan->lock); + } + return res; +} + +static int ext_strncpy(char *dst, const char *src, int len) +{ + int count=0; + + while(*src && (count < len - 1)) { + switch(*src) { + case ' ': + /* otherwise exten => [a-b],1,... doesn't work */ + /* case '-': */ + /* Ignore */ + break; + default: + *dst = *src; + dst++; + } + src++; + count++; + } + *dst = '\0'; + + return count; +} + +static void null_datad(void *foo) +{ +} + +/* + * EBUSY - can't lock + * EEXIST - extension with the same priority exist and no replace is set + * + */ +int ast_add_extension2(struct ast_context *con, + int replace, const char *extension, int priority, const char *label, const char *callerid, + const char *application, void *data, void (*datad)(void *), + const char *registrar) +{ + +#define LOG do { if (option_debug) {\ + if (tmp->matchcid) { \ + ast_log(LOG_DEBUG, "Added extension '%s' priority %d (CID match '%s') to %s\n", tmp->exten, tmp->priority, tmp->cidmatch, con->name); \ + } else { \ + ast_log(LOG_DEBUG, "Added extension '%s' priority %d to %s\n", tmp->exten, tmp->priority, con->name); \ + } \ + } else if (option_verbose > 2) { \ + if (tmp->matchcid) { \ + ast_verbose( VERBOSE_PREFIX_3 "Added extension '%s' priority %d (CID match '%s')to %s\n", tmp->exten, tmp->priority, tmp->cidmatch, con->name); \ + } else { \ + ast_verbose( VERBOSE_PREFIX_3 "Added extension '%s' priority %d to %s\n", tmp->exten, tmp->priority, con->name); \ + } \ + } } while(0) + + /* + * This is a fairly complex routine. Different extensions are kept + * in order by the extension number. Then, extensions of different + * priorities (same extension) are kept in a list, according to the + * peer pointer. + */ + struct ast_exten *tmp, *e, *el = NULL, *ep = NULL; + int res; + int length; + char *p; + char expand_buf[VAR_BUF_SIZE] = { 0, }; + + /* if we are adding a hint, and there are global variables, and the hint + contains variable references, then expand them + */ + if ((priority == PRIORITY_HINT) && AST_LIST_FIRST(&globals) && strstr(application, "${")) { + pbx_substitute_variables_varshead(&globals, application, expand_buf, sizeof(expand_buf)); + application = expand_buf; + } + + length = sizeof(struct ast_exten); + length += strlen(extension) + 1; + length += strlen(application) + 1; + if (label) + length += strlen(label) + 1; + if (callerid) + length += strlen(callerid) + 1; + else + length ++; + + /* Be optimistic: Build the extension structure first */ + if (datad == NULL) + datad = null_datad; + tmp = malloc(length); + if (tmp) { + memset(tmp, 0, length); + p = tmp->stuff; + if (label) { + tmp->label = p; + strcpy(tmp->label, label); + p += strlen(label) + 1; + } + tmp->exten = p; + p += ext_strncpy(tmp->exten, extension, strlen(extension) + 1) + 1; + tmp->priority = priority; + tmp->cidmatch = p; + if (callerid) { + p += ext_strncpy(tmp->cidmatch, callerid, strlen(callerid) + 1) + 1; + tmp->matchcid = 1; + } else { + tmp->cidmatch[0] = '\0'; + tmp->matchcid = 0; + p++; + } + tmp->app = p; + strcpy(tmp->app, application); + tmp->parent = con; + tmp->data = data; + tmp->datad = datad; + tmp->registrar = registrar; + tmp->peer = NULL; + tmp->next = NULL; + } else { + ast_log(LOG_ERROR, "Out of memory\n"); + errno = ENOMEM; + return -1; + } + if (ast_mutex_lock(&con->lock)) { + free(tmp); + /* And properly destroy the data */ + datad(data); + ast_log(LOG_WARNING, "Failed to lock context '%s'\n", con->name); + errno = EBUSY; + return -1; + } + e = con->root; + while(e) { + /* Make sure patterns are always last! */ + if ((e->exten[0] != '_') && (extension[0] == '_')) + res = -1; + else if ((e->exten[0] == '_') && (extension[0] != '_')) + res = 1; + else + res= strcmp(e->exten, extension); + if (!res) { + if (!e->matchcid && !tmp->matchcid) + res = 0; + else if (tmp->matchcid && !e->matchcid) + res = 1; + else if (e->matchcid && !tmp->matchcid) + res = -1; + else + res = strcasecmp(e->cidmatch, tmp->cidmatch); + } + if (res == 0) { + /* We have an exact match, now we find where we are + and be sure there's no duplicates */ + while(e) { + if (e->priority == tmp->priority) { + /* Can't have something exactly the same. Is this a + replacement? If so, replace, otherwise, bonk. */ + if (replace) { + if (ep) { + /* We're in the peer list, insert ourselves */ + ep->peer = tmp; + tmp->peer = e->peer; + } else if (el) { + /* We're the first extension. Take over e's functions */ + el->next = tmp; + tmp->next = e->next; + tmp->peer = e->peer; + } else { + /* We're the very first extension. */ + con->root = tmp; + tmp->next = e->next; + tmp->peer = e->peer; + } + if (tmp->priority == PRIORITY_HINT) + ast_change_hint(e,tmp); + /* Destroy the old one */ + e->datad(e->data); + free(e); + ast_mutex_unlock(&con->lock); + if (tmp->priority == PRIORITY_HINT) + ast_change_hint(e, tmp); + /* And immediately return success. */ + LOG; + return 0; + } else { + ast_log(LOG_WARNING, "Unable to register extension '%s', priority %d in '%s', already in use\n", tmp->exten, tmp->priority, con->name); + tmp->datad(tmp->data); + free(tmp); + ast_mutex_unlock(&con->lock); + errno = EEXIST; + return -1; + } + } else if (e->priority > tmp->priority) { + /* Slip ourselves in just before e */ + if (ep) { + /* Easy enough, we're just in the peer list */ + ep->peer = tmp; + tmp->peer = e; + } else if (el) { + /* We're the first extension in this peer list */ + el->next = tmp; + tmp->next = e->next; + e->next = NULL; + tmp->peer = e; + } else { + /* We're the very first extension altogether */ + tmp->next = con->root->next; + /* Con->root must always exist or we couldn't get here */ + tmp->peer = con->root; + con->root = tmp; + } + ast_mutex_unlock(&con->lock); + + /* And immediately return success. */ + if (tmp->priority == PRIORITY_HINT) + ast_add_hint(tmp); + + LOG; + return 0; + } + ep = e; + e = e->peer; + } + /* If we make it here, then it's time for us to go at the very end. + ep *must* be defined or we couldn't have gotten here. */ + ep->peer = tmp; + ast_mutex_unlock(&con->lock); + if (tmp->priority == PRIORITY_HINT) + ast_add_hint(tmp); + + /* And immediately return success. */ + LOG; + return 0; + + } else if (res > 0) { + /* Insert ourselves just before 'e'. We're the first extension of + this kind */ + tmp->next = e; + if (el) { + /* We're in the list somewhere */ + el->next = tmp; + } else { + /* We're at the top of the list */ + con->root = tmp; + } + ast_mutex_unlock(&con->lock); + if (tmp->priority == PRIORITY_HINT) + ast_add_hint(tmp); + + /* And immediately return success. */ + LOG; + return 0; + } + + el = e; + e = e->next; + } + /* If we fall all the way through to here, then we need to be on the end. */ + if (el) + el->next = tmp; + else + con->root = tmp; + ast_mutex_unlock(&con->lock); + if (tmp->priority == PRIORITY_HINT) + ast_add_hint(tmp); + LOG; + return 0; +} + +struct async_stat { + pthread_t p; + struct ast_channel *chan; + char context[AST_MAX_CONTEXT]; + char exten[AST_MAX_EXTENSION]; + int priority; + int timeout; + char app[AST_MAX_EXTENSION]; + char appdata[1024]; +}; + +static void *async_wait(void *data) +{ + struct async_stat *as = data; + struct ast_channel *chan = as->chan; + int timeout = as->timeout; + int res; + struct ast_frame *f; + struct ast_app *app; + + while(timeout && (chan->_state != AST_STATE_UP)) { + res = ast_waitfor(chan, timeout); + if (res < 1) + break; + if (timeout > -1) + timeout = res; + f = ast_read(chan); + if (!f) + break; + if (f->frametype == AST_FRAME_CONTROL) { + if ((f->subclass == AST_CONTROL_BUSY) || + (f->subclass == AST_CONTROL_CONGESTION) ) + break; + } + ast_frfree(f); + } + if (chan->_state == AST_STATE_UP) { + if (!ast_strlen_zero(as->app)) { + app = pbx_findapp(as->app); + if (app) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Launching %s(%s) on %s\n", as->app, as->appdata, chan->name); + pbx_exec(chan, app, as->appdata, 1); + } else + ast_log(LOG_WARNING, "No such application '%s'\n", as->app); + } else { + if (!ast_strlen_zero(as->context)) + ast_copy_string(chan->context, as->context, sizeof(chan->context)); + if (!ast_strlen_zero(as->exten)) + ast_copy_string(chan->exten, as->exten, sizeof(chan->exten)); + if (as->priority > 0) + chan->priority = as->priority; + /* Run the PBX */ + if (ast_pbx_run(chan)) { + ast_log(LOG_ERROR, "Failed to start PBX on %s\n", chan->name); + } else { + /* PBX will have taken care of this */ + chan = NULL; + } + } + + } + free(as); + if (chan) + ast_hangup(chan); + return NULL; +} + +/*! Function to update the cdr after a spool call fails. + * + * This function updates the cdr for a failed spool call + * and takes the channel of the failed call as an argument. + * + */ +int ast_pbx_outgoing_cdr_failed(void) +{ + /* allocate a channel */ + struct ast_channel *chan = ast_channel_alloc(0); + if(!chan) { + /* allocation of the channel failed, let some peeps know */ + ast_log(LOG_WARNING, "Unable to allocate channel structure for CDR record\n"); + return -1; /* failure */ + } + + chan->cdr = ast_cdr_alloc(); /* allocate a cdr for the channel */ + + if(!chan->cdr) { + /* allocation of the cdr failed */ + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + ast_channel_free(chan); /* free the channel */ + return -1; /* return failure */ + } + + /* allocation of the cdr was successful */ + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); /* record the start and stop time */ + ast_cdr_end(chan->cdr); + ast_cdr_failed(chan->cdr); /* set the status to failed */ + ast_cdr_detach(chan->cdr); /* post and free the record */ + ast_channel_free(chan); /* free the channel */ + + return 0; /* success */ +} + +int ast_pbx_outgoing_exten(const char *type, int format, void *data, int timeout, const char *context, const char *exten, int priority, int *reason, int sync, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **channel) +{ + struct ast_channel *chan; + struct async_stat *as; + int res = -1, cdr_res = -1; + struct outgoing_helper oh; + pthread_attr_t attr; + + if (sync) { + LOAD_OH(oh); + chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh); + if (channel) { + *channel = chan; + if (chan) + ast_mutex_lock(&chan->lock); + } + if (chan) { + if (chan->cdr) { /* check if the channel already has a cdr record, if not give it one */ + ast_log(LOG_WARNING, "%s already has a call record??\n", chan->name); + } else { + chan->cdr = ast_cdr_alloc(); /* allocate a cdr for the channel */ + if (!chan->cdr) { + /* allocation of the cdr failed */ + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + free(chan->pbx); + res = -1; + goto outgoing_exten_cleanup; + } + /* allocation of the cdr was successful */ + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); + } + if (chan->_state == AST_STATE_UP) { + res = 0; + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", chan->name); + + if (sync > 1) { + if (channel) + ast_mutex_unlock(&chan->lock); + if (ast_pbx_run(chan)) { + ast_log(LOG_ERROR, "Unable to run PBX on %s\n", chan->name); + if (channel) + *channel = NULL; + ast_hangup(chan); + res = -1; + } + } else { + if (ast_pbx_start(chan)) { + ast_log(LOG_ERROR, "Unable to start PBX on %s\n", chan->name); + if (channel) + *channel = NULL; + ast_hangup(chan); + res = -1; + } + } + } else { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", chan->name); + + if(chan->cdr) { /* update the cdr */ + /* here we update the status of the call, which sould be busy. + * if that fails then we set the status to failed */ + if (ast_cdr_disposition(chan->cdr, chan->hangupcause)) + ast_cdr_failed(chan->cdr); + } + + if (channel) + *channel = NULL; + ast_hangup(chan); + } + } + + if(res < 0) { /* the call failed for some reason */ + if (*reason == 0) { /* if the call failed (not busy or no answer) + * update the cdr with the failed message */ + cdr_res = ast_pbx_outgoing_cdr_failed(); + if (cdr_res != 0) { + res = cdr_res; + goto outgoing_exten_cleanup; + } + } + + /* create a fake channel and execute the "failed" extension (if it exists) within the requested context */ + /* check if "failed" exists */ + if (ast_exists_extension(chan, context, "failed", 1, NULL)) { + chan = ast_channel_alloc(0); + if (chan) { + ast_copy_string(chan->name, "OutgoingSpoolFailed", sizeof(chan->name)); + if (!ast_strlen_zero(context)) + ast_copy_string(chan->context, context, sizeof(chan->context)); + ast_copy_string(chan->exten, "failed", sizeof(chan->exten)); + chan->priority = 1; + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + ast_pbx_run(chan); + } else + ast_log(LOG_WARNING, "Can't allocate the channel structure, skipping execution of extension 'failed'\n"); + } + } + } else { + as = malloc(sizeof(struct async_stat)); + if (!as) { + res = -1; + goto outgoing_exten_cleanup; + } + memset(as, 0, sizeof(struct async_stat)); + chan = ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name); + if (channel) { + *channel = chan; + if (chan) + ast_mutex_lock(&chan->lock); + } + if (!chan) { + free(as); + res = -1; + goto outgoing_exten_cleanup; + } + as->chan = chan; + ast_copy_string(as->context, context, sizeof(as->context)); + ast_copy_string(as->exten, exten, sizeof(as->exten)); + as->priority = priority; + as->timeout = timeout; + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (ast_pthread_create(&as->p, &attr, async_wait, as)) { + ast_log(LOG_WARNING, "Failed to start async wait\n"); + free(as); + if (channel) + *channel = NULL; + ast_hangup(chan); + res = -1; + goto outgoing_exten_cleanup; + } + res = 0; + } +outgoing_exten_cleanup: + ast_variables_destroy(vars); + return res; +} + +struct app_tmp { + char app[256]; + char data[256]; + struct ast_channel *chan; + pthread_t t; +}; + +static void *ast_pbx_run_app(void *data) +{ + struct app_tmp *tmp = data; + struct ast_app *app; + app = pbx_findapp(tmp->app); + if (app) { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Launching %s(%s) on %s\n", tmp->app, tmp->data, tmp->chan->name); + pbx_exec(tmp->chan, app, tmp->data, 1); + } else + ast_log(LOG_WARNING, "No such application '%s'\n", tmp->app); + ast_hangup(tmp->chan); + free(tmp); + return NULL; +} + +int ast_pbx_outgoing_app(const char *type, int format, void *data, int timeout, const char *app, const char *appdata, int *reason, int sync, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel) +{ + struct ast_channel *chan; + struct async_stat *as; + struct app_tmp *tmp; + int res = -1, cdr_res = -1; + struct outgoing_helper oh; + pthread_attr_t attr; + + memset(&oh, 0, sizeof(oh)); + oh.vars = vars; + oh.account = account; + + if (locked_channel) + *locked_channel = NULL; + if (ast_strlen_zero(app)) { + res = -1; + goto outgoing_app_cleanup; + } + if (sync) { + chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh); + if (chan) { + if (chan->cdr) { /* check if the channel already has a cdr record, if not give it one */ + ast_log(LOG_WARNING, "%s already has a call record??\n", chan->name); + } else { + chan->cdr = ast_cdr_alloc(); /* allocate a cdr for the channel */ + if(!chan->cdr) { + /* allocation of the cdr failed */ + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + free(chan->pbx); + res = -1; + goto outgoing_app_cleanup; + } + /* allocation of the cdr was successful */ + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); + } + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + if (chan->_state == AST_STATE_UP) { + res = 0; + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", chan->name); + tmp = malloc(sizeof(struct app_tmp)); + if (tmp) { + memset(tmp, 0, sizeof(struct app_tmp)); + ast_copy_string(tmp->app, app, sizeof(tmp->app)); + if (appdata) + ast_copy_string(tmp->data, appdata, sizeof(tmp->data)); + tmp->chan = chan; + if (sync > 1) { + if (locked_channel) + ast_mutex_unlock(&chan->lock); + ast_pbx_run_app(tmp); + } else { + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (locked_channel) + ast_mutex_lock(&chan->lock); + if (ast_pthread_create(&tmp->t, &attr, ast_pbx_run_app, tmp)) { + ast_log(LOG_WARNING, "Unable to spawn execute thread on %s: %s\n", chan->name, strerror(errno)); + free(tmp); + if (locked_channel) + ast_mutex_unlock(&chan->lock); + ast_hangup(chan); + res = -1; + } else { + if (locked_channel) + *locked_channel = chan; + } + } + } else { + ast_log(LOG_ERROR, "Out of memory :(\n"); + res = -1; + } + } else { + if (option_verbose > 3) + ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", chan->name); + if (chan->cdr) { /* update the cdr */ + /* here we update the status of the call, which sould be busy. + * if that fails then we set the status to failed */ + if (ast_cdr_disposition(chan->cdr, chan->hangupcause)) + ast_cdr_failed(chan->cdr); + } + ast_hangup(chan); + } + } + + if (res < 0) { /* the call failed for some reason */ + if (*reason == 0) { /* if the call failed (not busy or no answer) + * update the cdr with the failed message */ + cdr_res = ast_pbx_outgoing_cdr_failed(); + if (cdr_res != 0) { + res = cdr_res; + goto outgoing_app_cleanup; + } + } + } + + } else { + as = malloc(sizeof(struct async_stat)); + if (!as) { + res = -1; + goto outgoing_app_cleanup; + } + memset(as, 0, sizeof(struct async_stat)); + chan = __ast_request_and_dial(type, format, data, timeout, reason, cid_num, cid_name, &oh); + if (!chan) { + free(as); + res = -1; + goto outgoing_app_cleanup; + } + as->chan = chan; + ast_copy_string(as->app, app, sizeof(as->app)); + if (appdata) + ast_copy_string(as->appdata, appdata, sizeof(as->appdata)); + as->timeout = timeout; + ast_set_variables(chan, vars); + if (account) + ast_cdr_setaccount(chan, account); + /* Start a new thread, and get something handling this channel. */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (locked_channel) + ast_mutex_lock(&chan->lock); + if (ast_pthread_create(&as->p, &attr, async_wait, as)) { + ast_log(LOG_WARNING, "Failed to start async wait\n"); + free(as); + if (locked_channel) + ast_mutex_unlock(&chan->lock); + ast_hangup(chan); + res = -1; + goto outgoing_app_cleanup; + } else { + if (locked_channel) + *locked_channel = chan; + } + res = 0; + } +outgoing_app_cleanup: + ast_variables_destroy(vars); + return res; +} + +static void destroy_exten(struct ast_exten *e) +{ + if (e->priority == PRIORITY_HINT) + ast_remove_hint(e); + + if (e->datad) + e->datad(e->data); + free(e); +} + +void __ast_context_destroy(struct ast_context *con, const char *registrar) +{ + struct ast_context *tmp, *tmpl=NULL; + struct ast_include *tmpi, *tmpil= NULL; + struct ast_sw *sw, *swl= NULL; + struct ast_exten *e, *el, *en; + struct ast_ignorepat *ipi, *ipl = NULL; + + ast_mutex_lock(&conlock); + tmp = contexts; + while(tmp) { + if (((tmp->name && con && con->name && !strcasecmp(tmp->name, con->name)) || !con) && + (!registrar || !strcasecmp(registrar, tmp->registrar))) { + /* Okay, let's lock the structure to be sure nobody else + is searching through it. */ + if (ast_mutex_lock(&tmp->lock)) { + ast_log(LOG_WARNING, "Unable to lock context lock\n"); + return; + } + if (tmpl) + tmpl->next = tmp->next; + else + contexts = tmp->next; + /* Okay, now we're safe to let it go -- in a sense, we were + ready to let it go as soon as we locked it. */ + ast_mutex_unlock(&tmp->lock); + for (tmpi = tmp->includes; tmpi; ) { + /* Free includes */ + tmpil = tmpi; + tmpi = tmpi->next; + free(tmpil); + } + for (ipi = tmp->ignorepats; ipi; ) { + /* Free ignorepats */ + ipl = ipi; + ipi = ipi->next; + free(ipl); + } + for (sw = tmp->alts; sw; ) { + /* Free switches */ + swl = sw; + sw = sw->next; + free(swl); + swl = sw; + } + for (e = tmp->root; e;) { + for (en = e->peer; en;) { + el = en; + en = en->peer; + destroy_exten(el); + } + el = e; + e = e->next; + destroy_exten(el); + } + ast_mutex_destroy(&tmp->lock); + free(tmp); + if (!con) { + /* Might need to get another one -- restart */ + tmp = contexts; + tmpl = NULL; + tmpil = NULL; + continue; + } + ast_mutex_unlock(&conlock); + return; + } + tmpl = tmp; + tmp = tmp->next; + } + ast_mutex_unlock(&conlock); +} + +void ast_context_destroy(struct ast_context *con, const char *registrar) +{ + __ast_context_destroy(con,registrar); +} + +static void wait_for_hangup(struct ast_channel *chan, void *data) +{ + int res; + struct ast_frame *f; + int waittime; + + if (ast_strlen_zero(data) || (sscanf(data, "%d", &waittime) != 1) || (waittime < 0)) + waittime = -1; + if (waittime > -1) { + ast_safe_sleep(chan, waittime * 1000); + } else do { + res = ast_waitfor(chan, -1); + if (res < 0) + return; + f = ast_read(chan); + if (f) + ast_frfree(f); + } while(f); +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_progress(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_PROGRESS); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_ringing(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_RINGING); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_busy(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_BUSY); + ast_setstate(chan, AST_STATE_BUSY); + wait_for_hangup(chan, data); + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_congestion(struct ast_channel *chan, void *data) +{ + ast_indicate(chan, AST_CONTROL_CONGESTION); + ast_setstate(chan, AST_STATE_BUSY); + wait_for_hangup(chan, data); + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_answer(struct ast_channel *chan, void *data) +{ + int delay = 0; + int res; + + if (chan->_state == AST_STATE_UP) + delay = 0; + else if (!ast_strlen_zero(data)) + delay = atoi(data); + + res = ast_answer(chan); + if (res) + return res; + + if (delay) + res = ast_safe_sleep(chan, delay); + + return res; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_setlanguage(struct ast_channel *chan, void *data) +{ + static int deprecation_warning = 0; + + if (!deprecation_warning) { + ast_log(LOG_WARNING, "SetLanguage is deprecated, please use Set(LANGUAGE()=language) instead.\n"); + deprecation_warning = 1; + } + + /* Copy the language as specified */ + if (!ast_strlen_zero(data)) + ast_copy_string(chan->language, data, sizeof(chan->language)); + + return 0; +} + +AST_APP_OPTIONS(resetcdr_opts, { + AST_APP_OPTION('w', AST_CDR_FLAG_POSTED), + AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED), + AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), +}); + +/*! + * \ingroup applications + */ +static int pbx_builtin_resetcdr(struct ast_channel *chan, void *data) +{ + char *args; + struct ast_flags flags = { 0 }; + + if (!ast_strlen_zero(data)) { + args = ast_strdupa(data); + if (!args) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return -1; + } + ast_app_parse_options(resetcdr_opts, &flags, NULL, args); + } + + ast_cdr_reset(chan->cdr, &flags); + + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_setaccount(struct ast_channel *chan, void *data) +{ + /* Copy the account code as specified */ + if (data) + ast_cdr_setaccount(chan, (char *)data); + else + ast_cdr_setaccount(chan, ""); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_setamaflags(struct ast_channel *chan, void *data) +{ + /* Copy the AMA Flags as specified */ + if (data) + ast_cdr_setamaflags(chan, (char *)data); + else + ast_cdr_setamaflags(chan, ""); + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_hangup(struct ast_channel *chan, void *data) +{ + /* Just return non-zero and it will hang up */ + if (!chan->hangupcause) + chan->hangupcause = AST_CAUSE_NORMAL_CLEARING; + return -1; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_gotoiftime(struct ast_channel *chan, void *data) +{ + int res=0; + char *s, *ts; + struct ast_timing timing; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n <time range>|<days of week>|<days of month>|<months>?[[context|]extension|]priority\n"); + return -1; + } + + if ((s = ast_strdupa((char *) data))) { + ts = s; + + /* Separate the Goto path */ + strsep(&ts,"?"); + + /* struct ast_include include contained garbage here, fixed by zeroing it on get_timerange */ + if (ast_build_timing(&timing, s) && ast_check_timing(&timing)) + res = pbx_builtin_goto(chan, (void *)ts); + } else { + ast_log(LOG_ERROR, "Memory Error!\n"); + } + return res; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_execiftime(struct ast_channel *chan, void *data) +{ + int res = 0; + char *ptr1, *ptr2; + struct ast_timing timing; + struct ast_app *app; + const char *usage = "ExecIfTime requires an argument:\n <time range>|<days of week>|<days of month>|<months>?<appname>[|<appargs>]"; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s\n", usage); + return -1; + } + + ptr1 = ast_strdupa(data); + + if (!ptr1) { + ast_log(LOG_ERROR, "Out of Memory!\n"); + return -1; + } + + ptr2 = ptr1; + /* Separate the Application data ptr1 is the time spec ptr2 is the app|data */ + strsep(&ptr2,"?"); + if(!ast_build_timing(&timing, ptr1)) { + ast_log(LOG_WARNING, "Invalid Time Spec: %s\nCorrect usage: %s\n", ptr1, usage); + res = -1; + } + + if (!res && ast_check_timing(&timing)) { + if (!ptr2) { + ast_log(LOG_WARNING, "%s\n", usage); + } + + /* ptr2 is now the app name + we're done with ptr1 now so recycle it and use it to point to the app args */ + if((ptr1 = strchr(ptr2, '|'))) { + *ptr1 = '\0'; + ptr1++; + } + + if ((app = pbx_findapp(ptr2))) { + res = pbx_exec(chan, app, ptr1 ? ptr1 : "", 1); + } else { + ast_log(LOG_WARNING, "Cannot locate application %s\n", ptr2); + res = -1; + } + } + + return res; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_wait(struct ast_channel *chan, void *data) +{ + int ms; + + /* Wait for "n" seconds */ + if (data && atof((char *)data)) { + ms = atof((char *)data) * 1000; + return ast_safe_sleep(chan, ms); + } + return 0; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_waitexten(struct ast_channel *chan, void *data) +{ + int ms, res, argc; + char *args; + char *argv[2]; + char *options = NULL; + char *timeout = NULL; + struct ast_flags flags = {0}; + char *opts[1] = { NULL }; + + args = ast_strdupa(data); + + if ((argc = ast_app_separate_args(args, '|', argv, sizeof(argv) / sizeof(argv[0])))) { + if (argc > 0) { + timeout = argv[0]; + if (argc > 1) + options = argv[1]; + } + } + + if (options) + ast_app_parse_options(waitexten_opts, &flags, opts, options); + + if (ast_test_flag(&flags, WAITEXTEN_MOH)) + ast_moh_start(chan, opts[0]); + + /* Wait for "n" seconds */ + if (timeout && atof((char *)timeout)) + ms = atof((char *)timeout) * 1000; + else if (chan->pbx) + ms = chan->pbx->rtimeout * 1000; + else + ms = 10000; + res = ast_waitfordigit(chan, ms); + if (!res) { + if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 1, chan->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Timeout on %s, continuing...\n", chan->name); + } else if (ast_exists_extension(chan, chan->context, "t", 1, chan->cid.cid_num)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Timeout on %s, going to 't'\n", chan->name); + ast_copy_string(chan->exten, "t", sizeof(chan->exten)); + chan->priority = 0; + } else { + ast_log(LOG_WARNING, "Timeout but no rule 't' in context '%s'\n", chan->context); + res = -1; + } + } + + if (ast_test_flag(&flags, WAITEXTEN_MOH)) + ast_moh_stop(chan); + + return res; +} + +/*! + * \ingroup applications + */ +static int pbx_builtin_background(struct ast_channel *chan, void *data) +{ + int res = 0; + int argc; + char *parse; + char *argv[4]; + char *options = NULL; + char *filename = NULL; + char *front = NULL, *back = NULL; + char *lang = NULL; + char *context = NULL; + struct ast_flags flags = {0}; + + parse = ast_strdupa(data); + + if ((argc = ast_app_separate_args(parse, '|', argv, sizeof(argv) / sizeof(argv[0])))) { + switch (argc) { + case 4: + context = argv[3]; + case 3: + lang = argv[2]; + case 2: + options = argv[1]; + case 1: + filename = argv[0]; + break; + default: + ast_log(LOG_WARNING, "Background requires an argument (filename)\n"); + break; + } + } + + if (!lang) + lang = chan->language; + + if (!context) + context = chan->context; + + if (options) { + if (!strcasecmp(options, "skip")) + flags.flags = BACKGROUND_SKIP; + else if (!strcasecmp(options, "noanswer")) + flags.flags = BACKGROUND_NOANSWER; + else + ast_app_parse_options(background_opts, &flags, NULL, options); + } + + /* Answer if need be */ + if (chan->_state != AST_STATE_UP) { + if (ast_test_flag(&flags, BACKGROUND_SKIP)) { + return 0; + } else if (!ast_test_flag(&flags, BACKGROUND_NOANSWER)) { + res = ast_answer(chan); + } + } + + if (!res) { + /* Stop anything playing */ + ast_stopstream(chan); + /* Stream a file */ + front = filename; + while(!res && front) { + if((back = strchr(front, '&'))) { + *back = '\0'; + back++; + } + res = ast_streamfile(chan, front, lang); + if (!res) { + if (ast_test_flag(&flags, BACKGROUND_PLAYBACK)) { + res = ast_waitstream(chan, ""); + } else { + if (ast_test_flag(&flags, BACKGROUND_MATCHEXTEN)) { + res = ast_waitstream_exten(chan, context); + } else { + res = ast_waitstream(chan, AST_DIGIT_ANY); + } + } + ast_stopstream(chan); + } else { + ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char*)data); + res = 0; + break; + } + front = back; + } + } + if (context != chan->context && res) { + snprintf(chan->exten, sizeof(chan->exten), "%c", res); + ast_copy_string(chan->context, context, sizeof(chan->context)); + chan->priority = 0; + return 0; + } else { + return res; + } +} + +/*! AbsoluteTimeout + * \ingroup applications + * \todo Remove in 1.3 dev + */ +static int pbx_builtin_atimeout(struct ast_channel *chan, void *data) +{ + static int deprecation_warning = 0; + int x = atoi((char *) data); + + if (!deprecation_warning) { + ast_log(LOG_WARNING, "AbsoluteTimeout is deprecated, please use Set(TIMEOUT(absolute)=timeout) instead.\n"); + deprecation_warning = 1; + } + + /* Set the absolute maximum time how long a call can be connected */ + ast_channel_setwhentohangup(chan,x); + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Set Absolute Timeout to %d\n", x); + return 0; +} + +/*! ResponseTimeout + * \ingroup applications + * \todo Remove in 1.3 dev + */ +static int pbx_builtin_rtimeout(struct ast_channel *chan, void *data) +{ + static int deprecation_warning = 0; + + if (!deprecation_warning) { + ast_log(LOG_WARNING, "ResponseTimeout is deprecated, please use Set(TIMEOUT(response)=timeout) instead.\n"); + deprecation_warning = 1; + } + + /* If the channel is not in a PBX, return now */ + if (!chan->pbx) + return 0; + + /* Set the timeout for how long to wait between digits */ + chan->pbx->rtimeout = atoi((char *)data); + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Set Response Timeout to %d\n", chan->pbx->rtimeout); + return 0; +} + +/*! DigitTimeout + * \ingroup applications + * \todo Remove in 1.3 dev + */ +static int pbx_builtin_dtimeout(struct ast_channel *chan, void *data) +{ + static int deprecation_warning = 0; + + if (!deprecation_warning) { + ast_log(LOG_WARNING, "DigitTimeout is deprecated, please use Set(TIMEOUT(digit)=timeout) instead.\n"); + deprecation_warning = 1; + } + + /* If the channel is not in a PBX, return now */ + if (!chan->pbx) + return 0; + + /* Set the timeout for how long to wait between digits */ + chan->pbx->dtimeout = atoi((char *)data); + if (option_verbose > 2) + ast_verbose( VERBOSE_PREFIX_3 "Set Digit Timeout to %d\n", chan->pbx->dtimeout); + return 0; +} + +/*! Goto + * \ingroup applications + */ +static int pbx_builtin_goto(struct ast_channel *chan, void *data) +{ + int res; + res = ast_parseable_goto(chan, (const char *) data); + if (!res && (option_verbose > 2)) + ast_verbose( VERBOSE_PREFIX_3 "Goto (%s,%s,%d)\n", chan->context,chan->exten, chan->priority+1); + return res; +} + + +int pbx_builtin_serialize_variables(struct ast_channel *chan, char *buf, size_t size) +{ + struct ast_var_t *variables; + char *var, *val; + int total = 0; + + if (!chan) + return 0; + + memset(buf, 0, size); + + AST_LIST_TRAVERSE(&chan->varshead, variables, entries) { + if(variables && + (var=ast_var_name(variables)) && (val=ast_var_value(variables)) && + !ast_strlen_zero(var) && !ast_strlen_zero(val)) { + if (ast_build_string(&buf, &size, "%s=%s\n", var, val)) { + ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); + break; + } else + total++; + } else + break; + } + + return total; +} + +char *pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name) +{ + struct ast_var_t *variables; + struct varshead *headp; + + if (chan) + headp=&chan->varshead; + else + headp=&globals; + + if (name) { + AST_LIST_TRAVERSE(headp,variables,entries) { + if (!strcmp(name, ast_var_name(variables))) + return ast_var_value(variables); + } + if (headp != &globals) { + /* Check global variables if we haven't already */ + headp = &globals; + AST_LIST_TRAVERSE(headp,variables,entries) { + if (!strcmp(name, ast_var_name(variables))) + return ast_var_value(variables); + } + } + } + return NULL; +} + +void pbx_builtin_pushvar_helper(struct ast_channel *chan, const char *name, const char *value) +{ + struct ast_var_t *newvariable; + struct varshead *headp; + + if (name[strlen(name)-1] == ')') { + ast_log(LOG_WARNING, "Cannot push a value onto a function\n"); + return ast_func_write(chan, name, value); + } + + headp = (chan) ? &chan->varshead : &globals; + + if (value) { + if ((option_verbose > 1) && (headp == &globals)) + ast_verbose(VERBOSE_PREFIX_2 "Setting global variable '%s' to '%s'\n", name, value); + newvariable = ast_var_assign(name, value); + AST_LIST_INSERT_HEAD(headp, newvariable, entries); + } +} + +void pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value) +{ + struct ast_var_t *newvariable; + struct varshead *headp; + const char *nametail = name; + + if (name[strlen(name)-1] == ')') + return ast_func_write(chan, name, value); + + headp = (chan) ? &chan->varshead : &globals; + + /* For comparison purposes, we have to strip leading underscores */ + if (*nametail == '_') { + nametail++; + if (*nametail == '_') + nametail++; + } + + AST_LIST_TRAVERSE (headp, newvariable, entries) { + if (strcasecmp(ast_var_name(newvariable), nametail) == 0) { + /* there is already such a variable, delete it */ + AST_LIST_REMOVE(headp, newvariable, entries); + ast_var_delete(newvariable); + break; + } + } + + if (value) { + if ((option_verbose > 1) && (headp == &globals)) + ast_verbose(VERBOSE_PREFIX_2 "Setting global variable '%s' to '%s'\n", name, value); + newvariable = ast_var_assign(name, value); + AST_LIST_INSERT_HEAD(headp, newvariable, entries); + } +} + +int pbx_builtin_setvar_old(struct ast_channel *chan, void *data) +{ + static int deprecation_warning = 0; + + if (!deprecation_warning) { + ast_log(LOG_WARNING, "SetVar is deprecated, please use Set instead.\n"); + deprecation_warning = 1; + } + + return pbx_builtin_setvar(chan, data); +} + +int pbx_builtin_setvar(struct ast_channel *chan, void *data) +{ + char *name, *value, *mydata; + int argc; + char *argv[24]; /* this will only support a maximum of 24 variables being set in a single operation */ + int global = 0; + int x; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Set requires at least one variable name/value pair.\n"); + return 0; + } + + mydata = ast_strdupa(data); + argc = ast_app_separate_args(mydata, '|', argv, sizeof(argv) / sizeof(argv[0])); + + /* check for a trailing flags argument */ + if ((argc > 1) && !strchr(argv[argc-1], '=')) { + argc--; + if (strchr(argv[argc], 'g')) + global = 1; + } + + for (x = 0; x < argc; x++) { + name = argv[x]; + if ((value = strchr(name, '='))) { + *value = '\0'; + value++; + pbx_builtin_setvar_helper((global) ? NULL : chan, name, value); + } else + ast_log(LOG_WARNING, "Ignoring entry '%s' with no = (and not last 'options' entry)\n", name); + } + + return(0); +} + +int pbx_builtin_importvar(struct ast_channel *chan, void *data) +{ + char *name; + char *value; + char *stringp=NULL; + char *channel; + struct ast_channel *chan2; + char tmp[VAR_BUF_SIZE]=""; + char *s; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n"); + return 0; + } + + stringp = ast_strdupa(data); + name = strsep(&stringp,"="); + channel = strsep(&stringp,"|"); + value = strsep(&stringp,"\0"); + if (channel && value && name) { + chan2 = ast_get_channel_by_name_locked(channel); + if (chan2) { + s = alloca(strlen(value) + 4); + if (s) { + sprintf(s, "${%s}", value); + pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1); + } + ast_mutex_unlock(&chan2->lock); + } + pbx_builtin_setvar_helper(chan, name, tmp); + } + + return(0); +} + +static int pbx_builtin_setglobalvar(struct ast_channel *chan, void *data) +{ + char *name; + char *value; + char *stringp = NULL; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n"); + return 0; + } + + stringp = data; + name = strsep(&stringp, "="); + value = strsep(&stringp, "\0"); + + pbx_builtin_setvar_helper(NULL, name, value); + + return(0); +} + +static int pbx_builtin_noop(struct ast_channel *chan, void *data) +{ + return 0; +} + + +void pbx_builtin_clear_globals(void) +{ + struct ast_var_t *vardata; + while (!AST_LIST_EMPTY(&globals)) { + vardata = AST_LIST_REMOVE_HEAD(&globals, entries); + ast_var_delete(vardata); + } +} + +static int pbx_checkcondition(char *condition) +{ + if (condition) { + if (*condition == '\0') { + /* Empty strings are false */ + return 0; + } else if (*condition >= '0' && *condition <= '9') { + /* Numbers are evaluated for truth */ + return atoi(condition); + } else { + /* Strings are true */ + return 1; + } + } else { + /* NULL is also false */ + return 0; + } +} + +static int pbx_builtin_gotoif(struct ast_channel *chan, void *data) +{ + char *condition, *branch1, *branch2, *branch; + char *s; + int rc; + char *stringp=NULL; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Ignoring, since there is no variable to check\n"); + return 0; + } + + s = ast_strdupa(data); + stringp = s; + condition = strsep(&stringp,"?"); + branch1 = strsep(&stringp,":"); + branch2 = strsep(&stringp,""); + branch = pbx_checkcondition(condition) ? branch1 : branch2; + + if (ast_strlen_zero(branch)) { + ast_log(LOG_DEBUG, "Not taking any branch\n"); + return 0; + } + + rc = pbx_builtin_goto(chan, branch); + + return rc; +} + +static int pbx_builtin_saynumber(struct ast_channel *chan, void *data) +{ + int res = 0; + char tmp[256]; + char *number = (char *) NULL; + char *options = (char *) NULL; + + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SayNumber requires an argument (number)\n"); + return -1; + } + ast_copy_string(tmp, (char *) data, sizeof(tmp)); + number=tmp; + strsep(&number, "|"); + 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; + } + } + return res = ast_say_number(chan, atoi((char *) tmp), "", chan->language, options); +} + +static int pbx_builtin_saydigits(struct ast_channel *chan, void *data) +{ + int res = 0; + + if (data) + res = ast_say_digit_str(chan, (char *)data, "", chan->language); + return res; +} + +static int pbx_builtin_saycharacters(struct ast_channel *chan, void *data) +{ + int res = 0; + + if (data) + res = ast_say_character_str(chan, (char *)data, "", chan->language); + return res; +} + +static int pbx_builtin_sayphonetic(struct ast_channel *chan, void *data) +{ + int res = 0; + + if (data) + res = ast_say_phonetic_str(chan, (char *)data, "", chan->language); + return res; +} + +int load_pbx(void) +{ + int x; + + /* Initialize the PBX */ + if (option_verbose) { + ast_verbose( "Asterisk PBX Core Initializing\n"); + ast_verbose( "Registering builtin applications:\n"); + } + AST_LIST_HEAD_INIT_NOLOCK(&globals); + ast_cli_register_multiple(pbx_cli, sizeof(pbx_cli) / sizeof(pbx_cli[0])); + + /* Register builtin applications */ + for (x=0; x<sizeof(builtins) / sizeof(struct pbx_builtin); x++) { + if (option_verbose) + ast_verbose( VERBOSE_PREFIX_1 "[%s]\n", builtins[x].name); + if (ast_register_application(builtins[x].name, builtins[x].execute, builtins[x].synopsis, builtins[x].description)) { + ast_log(LOG_ERROR, "Unable to register builtin application '%s'\n", builtins[x].name); + return -1; + } + } + return 0; +} + +/* + * Lock context list functions ... + */ +int ast_lock_contexts() +{ + return ast_mutex_lock(&conlock); +} + +int ast_unlock_contexts() +{ + return ast_mutex_unlock(&conlock); +} + +/* + * Lock context ... + */ +int ast_lock_context(struct ast_context *con) +{ + return ast_mutex_lock(&con->lock); +} + +int ast_unlock_context(struct ast_context *con) +{ + return ast_mutex_unlock(&con->lock); +} + +/* + * Name functions ... + */ +const char *ast_get_context_name(struct ast_context *con) +{ + return con ? con->name : NULL; +} + +const char *ast_get_extension_name(struct ast_exten *exten) +{ + return exten ? exten->exten : NULL; +} + +const char *ast_get_extension_label(struct ast_exten *exten) +{ + return exten ? exten->label : NULL; +} + +const char *ast_get_include_name(struct ast_include *inc) +{ + return inc ? inc->name : NULL; +} + +const char *ast_get_ignorepat_name(struct ast_ignorepat *ip) +{ + return ip ? ip->pattern : NULL; +} + +int ast_get_extension_priority(struct ast_exten *exten) +{ + return exten ? exten->priority : -1; +} + +/* + * Registrar info functions ... + */ +const char *ast_get_context_registrar(struct ast_context *c) +{ + return c ? c->registrar : NULL; +} + +const char *ast_get_extension_registrar(struct ast_exten *e) +{ + return e ? e->registrar : NULL; +} + +const char *ast_get_include_registrar(struct ast_include *i) +{ + return i ? i->registrar : NULL; +} + +const char *ast_get_ignorepat_registrar(struct ast_ignorepat *ip) +{ + return ip ? ip->registrar : NULL; +} + +int ast_get_extension_matchcid(struct ast_exten *e) +{ + return e ? e->matchcid : 0; +} + +const char *ast_get_extension_cidmatch(struct ast_exten *e) +{ + return e ? e->cidmatch : NULL; +} + +const char *ast_get_extension_app(struct ast_exten *e) +{ + return e ? e->app : NULL; +} + +void *ast_get_extension_app_data(struct ast_exten *e) +{ + return e ? e->data : NULL; +} + +const char *ast_get_switch_name(struct ast_sw *sw) +{ + return sw ? sw->name : NULL; +} + +const char *ast_get_switch_data(struct ast_sw *sw) +{ + return sw ? sw->data : NULL; +} + +const char *ast_get_switch_registrar(struct ast_sw *sw) +{ + return sw ? sw->registrar : NULL; +} + +/* + * Walking functions ... + */ +struct ast_context *ast_walk_contexts(struct ast_context *con) +{ + if (!con) + return contexts; + else + return con->next; +} + +struct ast_exten *ast_walk_context_extensions(struct ast_context *con, + struct ast_exten *exten) +{ + if (!exten) + return con ? con->root : NULL; + else + return exten->next; +} + +struct ast_sw *ast_walk_context_switches(struct ast_context *con, + struct ast_sw *sw) +{ + if (!sw) + return con ? con->alts : NULL; + else + return sw->next; +} + +struct ast_exten *ast_walk_extension_priorities(struct ast_exten *exten, + struct ast_exten *priority) +{ + if (!priority) + return exten; + else + return priority->peer; +} + +struct ast_include *ast_walk_context_includes(struct ast_context *con, + struct ast_include *inc) +{ + if (!inc) + return con ? con->includes : NULL; + else + return inc->next; +} + +struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, + struct ast_ignorepat *ip) +{ + if (!ip) + return con ? con->ignorepats : NULL; + else + return ip->next; +} + +int ast_context_verify_includes(struct ast_context *con) +{ + struct ast_include *inc; + int res = 0; + + for (inc = ast_walk_context_includes(con, NULL); inc; inc = ast_walk_context_includes(con, inc)) + if (!ast_context_find(inc->rname)) { + res = -1; + ast_log(LOG_WARNING, "Context '%s' tries includes nonexistent context '%s'\n", + ast_get_context_name(con), inc->rname); + } + return res; +} + + +static int __ast_goto_if_exists(struct ast_channel *chan, char *context, char *exten, int priority, int async) +{ + int (*goto_func)(struct ast_channel *chan, const char *context, const char *exten, int priority); + + if (!chan) + return -2; + + goto_func = (async) ? ast_async_goto : ast_explicit_goto; + if (ast_exists_extension(chan, context ? context : chan->context, + exten ? exten : chan->exten, priority, + chan->cid.cid_num)) + return goto_func(chan, context ? context : chan->context, + exten ? exten : chan->exten, priority); + else + return -3; +} + +int ast_goto_if_exists(struct ast_channel *chan, char* context, char *exten, int priority) { + return __ast_goto_if_exists(chan, context, exten, priority, 0); +} + +int ast_async_goto_if_exists(struct ast_channel *chan, char* context, char *exten, int priority) { + return __ast_goto_if_exists(chan, context, exten, priority, 1); +} + +int ast_parseable_goto(struct ast_channel *chan, const char *goto_string) +{ + char *s; + char *exten, *pri, *context; + char *stringp=NULL; + int ipri; + int mode = 0; + + if (ast_strlen_zero(goto_string)) { + ast_log(LOG_WARNING, "Goto requires an argument (optional context|optional extension|priority)\n"); + return -1; + } + s = ast_strdupa(goto_string); + stringp=s; + context = strsep(&stringp, "|"); + exten = strsep(&stringp, "|"); + if (!exten) { + /* Only a priority in this one */ + pri = context; + exten = NULL; + context = NULL; + } else { + pri = strsep(&stringp, "|"); + if (!pri) { + /* Only an extension and priority in this one */ + pri = exten; + exten = context; + context = NULL; + } + } + if (*pri == '+') { + mode = 1; + pri++; + } else if (*pri == '-') { + mode = -1; + pri++; + } + if (sscanf(pri, "%d", &ipri) != 1) { + if ((ipri = ast_findlabel_extension(chan, context ? context : chan->context, (exten && strcasecmp(exten, "BYEXTENSION")) ? exten : chan->exten, + pri, chan->cid.cid_num)) < 1) { + ast_log(LOG_WARNING, "Priority '%s' must be a number > 0, or valid label\n", pri); + return -1; + } else + mode = 0; + } + /* At this point we have a priority and maybe an extension and a context */ + + if (exten && !strcasecmp(exten, "BYEXTENSION")) + exten = NULL; + + if (mode) + ipri = chan->priority + (ipri * mode); + + ast_explicit_goto(chan, context, exten, ipri); + ast_cdr_update(chan); + return 0; + +} |