00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028 #include "asterisk.h"
00029
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00031
00032 #include <stdlib.h>
00033 #include <stdio.h>
00034 #include <string.h>
00035 #include <unistd.h>
00036 #include <sys/types.h>
00037
00038 #include "asterisk/file.h"
00039 #include "asterisk/logger.h"
00040 #include "asterisk/channel.h"
00041 #include "asterisk/pbx.h"
00042 #include "asterisk/module.h"
00043 #include "asterisk/options.h"
00044 #include "asterisk/config.h"
00045 #include "asterisk/utils.h"
00046 #include "asterisk/lock.h"
00047
00048 #define MAX_ARGS 80
00049
00050
00051 #define MACRO_EXIT_RESULT 1024
00052
00053 static char *descrip =
00054 " Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
00055 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
00056 "executing each step, then returning when the steps end. \n"
00057 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
00058 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
00059 "${ARG1}, ${ARG2}, etc in the macro context.\n"
00060 "If you Goto out of the Macro context, the Macro will terminate and control\n"
00061 "will be returned at the location of the Goto.\n"
00062 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
00063 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
00064 "Extensions: While a macro is being executed, it becomes the current context.\n"
00065 " This means that if a hangup occurs, for instance, that the macro\n"
00066 " will be searched for an 'h' extension, NOT the context from which\n"
00067 " the macro was called. So, make sure to define all appropriate\n"
00068 " extensions in your macro! (you can use 'catch' in AEL) \n"
00069 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
00070 " contained within it via sub-engine), and a fixed per-thread\n"
00071 " memory stack allowance, macros are limited to 7 levels\n"
00072 " of nesting (macro calling macro calling macro, etc.); It\n"
00073 " may be possible that stack-intensive applications in deeply nested macros\n"
00074 " could cause asterisk to crash earlier than this limit. It is advised that\n"
00075 " if you need to deeply nest macro calls, that you use the Gosub application\n"
00076 " (now allows arguments like a Macro) with explict Return() calls instead.\n";
00077
00078 static char *if_descrip =
00079 " MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
00080 "Executes macro defined in <macroname_a> if <expr> is true\n"
00081 "(otherwise <macroname_b> if provided)\n"
00082 "Arguments and return values as in application macro()\n";
00083
00084 static char *exclusive_descrip =
00085 " MacroExclusive(macroname|arg1|arg2...):\n"
00086 "Executes macro defined in the context 'macro-macroname'\n"
00087 "Only one call at a time may run the macro.\n"
00088 "(we'll wait if another call is busy executing in the Macro)\n"
00089 "Arguments and return values as in application Macro()\n";
00090
00091 static char *exit_descrip =
00092 " MacroExit():\n"
00093 "Causes the currently running macro to exit as if it had\n"
00094 "ended normally by running out of priorities to execute.\n"
00095 "If used outside a macro, will likely cause unexpected\n"
00096 "behavior.\n";
00097
00098 static char *app = "Macro";
00099 static char *if_app = "MacroIf";
00100 static char *exclusive_app = "MacroExclusive";
00101 static char *exit_app = "MacroExit";
00102
00103 static char *synopsis = "Macro Implementation";
00104 static char *if_synopsis = "Conditional Macro Implementation";
00105 static char *exclusive_synopsis = "Exclusive Macro Implementation";
00106 static char *exit_synopsis = "Exit From Macro";
00107
00108
00109 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
00110 {
00111 struct ast_exten *e;
00112 struct ast_include *i;
00113 struct ast_context *c2;
00114
00115 for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
00116 if (ast_extension_match(ast_get_extension_name(e), exten)) {
00117 int needmatch = ast_get_extension_matchcid(e);
00118 if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
00119 (!needmatch)) {
00120
00121 struct ast_exten *p;
00122 for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
00123 if (priority != ast_get_extension_priority(p))
00124 continue;
00125 return p;
00126 }
00127 }
00128 }
00129 }
00130
00131
00132 for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
00133 for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
00134 if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
00135 e = find_matching_priority(c2, exten, priority, callerid);
00136 if (e)
00137 return e;
00138 }
00139 }
00140 }
00141 return NULL;
00142 }
00143
00144 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
00145 {
00146 const char *s;
00147 char *tmp;
00148 char *cur, *rest;
00149 char *macro;
00150 char fullmacro[80];
00151 char varname[80];
00152 char runningapp[80], runningdata[1024];
00153 char *oldargs[MAX_ARGS + 1] = { NULL, };
00154 int argc, x;
00155 int res=0;
00156 char oldexten[256]="";
00157 int oldpriority, gosub_level = 0;
00158 char pc[80], depthc[12];
00159 char oldcontext[AST_MAX_CONTEXT] = "";
00160 const char *inhangupc;
00161 int offset, depth = 0, maxdepth = 7;
00162 int setmacrocontext=0;
00163 int autoloopflag, dead = 0, inhangup = 0;
00164
00165 char *save_macro_exten;
00166 char *save_macro_context;
00167 char *save_macro_priority;
00168 char *save_macro_offset;
00169 struct ast_module_user *u;
00170
00171 if (ast_strlen_zero(data)) {
00172 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
00173 return -1;
00174 }
00175
00176 u = ast_module_user_add(chan);
00177
00178
00179 s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
00180 if (s)
00181 sscanf(s, "%d", &maxdepth);
00182
00183
00184 s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
00185 if (s)
00186 sscanf(s, "%d", &depth);
00187
00188 if (strcmp(chan->exten, "h") == 0)
00189 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
00190 inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
00191 if (!ast_strlen_zero(inhangupc))
00192 sscanf(inhangupc, "%d", &inhangup);
00193
00194 if (depth >= maxdepth) {
00195 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
00196 ast_module_user_remove(u);
00197 return 0;
00198 }
00199 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
00200 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00201
00202 tmp = ast_strdupa(data);
00203 rest = tmp;
00204 macro = strsep(&rest, "|");
00205 if (ast_strlen_zero(macro)) {
00206 ast_log(LOG_WARNING, "Invalid macro name specified\n");
00207 ast_module_user_remove(u);
00208 return 0;
00209 }
00210
00211 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
00212 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
00213 if (!ast_context_find(fullmacro))
00214 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
00215 else
00216 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
00217 ast_module_user_remove(u);
00218 return 0;
00219 }
00220
00221
00222 if (exclusive) {
00223 ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
00224 ast_autoservice_start(chan);
00225 if (ast_context_lockmacro(fullmacro)) {
00226 ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
00227 ast_autoservice_stop(chan);
00228 ast_module_user_remove(u);
00229
00230 return 0;
00231 }
00232 ast_autoservice_stop(chan);
00233 }
00234
00235
00236 oldpriority = chan->priority;
00237 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
00238 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
00239 if (ast_strlen_zero(chan->macrocontext)) {
00240 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
00241 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
00242 chan->macropriority = chan->priority;
00243 setmacrocontext=1;
00244 }
00245 argc = 1;
00246
00247 save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
00248 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
00249
00250 save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
00251 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
00252
00253 save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
00254 snprintf(pc, sizeof(pc), "%d", oldpriority);
00255 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
00256
00257 save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
00258 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
00259
00260
00261 chan->exten[0] = 's';
00262 chan->exten[1] = '\0';
00263 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
00264 chan->priority = 1;
00265
00266 while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
00267 const char *s;
00268
00269
00270 snprintf(varname, sizeof(varname), "ARG%d", argc);
00271 s = pbx_builtin_getvar_helper(chan, varname);
00272 if (s)
00273 oldargs[argc] = ast_strdup(s);
00274 pbx_builtin_setvar_helper(chan, varname, cur);
00275 argc++;
00276 }
00277 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
00278 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
00279 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
00280 struct ast_context *c;
00281 struct ast_exten *e;
00282
00283
00284 if (ast_lock_contexts()) {
00285 ast_log(LOG_WARNING, "Failed to lock contexts list\n");
00286 e = NULL;
00287 } else {
00288 for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
00289 if (!strcmp(ast_get_context_name(c), chan->context)) {
00290 if (ast_lock_context(c)) {
00291 ast_log(LOG_WARNING, "Unable to lock context?\n");
00292 runningapp[0] = '\0';
00293 runningdata[0] = '\0';
00294 } else {
00295 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
00296 ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
00297 ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
00298 ast_unlock_context(c);
00299 }
00300 break;
00301 }
00302 }
00303 }
00304 ast_unlock_contexts();
00305
00306
00307 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00308
00309 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
00310
00311 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
00312 (res == '*') || (res == '#')) {
00313
00314 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
00315 break;
00316 }
00317 switch(res) {
00318 case MACRO_EXIT_RESULT:
00319 res = 0;
00320 goto out;
00321 case AST_PBX_KEEPALIVE:
00322 if (option_debug)
00323 ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
00324 else if (option_verbose > 1)
00325 ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
00326 goto out;
00327 break;
00328 default:
00329 if (option_debug)
00330 ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
00331 else if (option_verbose > 1)
00332 ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
00333 dead = 1;
00334 goto out;
00335 }
00336 }
00337
00338 ast_log(LOG_DEBUG, "Executed application: %s\n", runningapp);
00339
00340 if (!strcasecmp(runningapp, "GOSUB")) {
00341 gosub_level++;
00342 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00343 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
00344 char tmp2[1024] = "", *cond, *app, *app2 = tmp2;
00345 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00346 cond = strsep(&app2, "?");
00347 app = strsep(&app2, ":");
00348 if (pbx_checkcondition(cond)) {
00349 if (!ast_strlen_zero(app)) {
00350 gosub_level++;
00351 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00352 }
00353 } else {
00354 if (!ast_strlen_zero(app2)) {
00355 gosub_level++;
00356 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00357 }
00358 }
00359 } else if (!strcasecmp(runningapp, "RETURN")) {
00360 gosub_level--;
00361 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00362 } else if (!strcasecmp(runningapp, "STACKPOP")) {
00363 gosub_level--;
00364 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00365 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
00366
00367 char tmp2[1024] = "", *tmp3 = NULL;
00368 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00369 if (!strcasecmp(runningapp, "EXECIF")) {
00370 tmp3 = strchr(tmp2, '|');
00371 if (tmp3)
00372 *tmp3++ = '\0';
00373 if (!pbx_checkcondition(tmp2))
00374 tmp3 = NULL;
00375 } else
00376 tmp3 = tmp2;
00377
00378 if (tmp3)
00379 ast_log(LOG_DEBUG, "Last app: %s\n", tmp3);
00380
00381 if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
00382 gosub_level++;
00383 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00384 } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
00385 gosub_level--;
00386 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00387 } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
00388 gosub_level--;
00389 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00390 }
00391 }
00392
00393 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
00394 if (option_verbose > 1)
00395 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
00396 break;
00397 }
00398
00399
00400 if (chan->_softhangup && !inhangup) {
00401 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
00402 chan->exten, chan->macroexten, chan->priority);
00403 goto out;
00404 }
00405 chan->priority++;
00406 }
00407 out:
00408
00409 snprintf(depthc, sizeof(depthc), "%d", depth);
00410 if (!dead) {
00411 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00412 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
00413 }
00414
00415 for (x = 1; x < argc; x++) {
00416
00417 snprintf(varname, sizeof(varname), "ARG%d", x);
00418 if (oldargs[x]) {
00419 if (!dead)
00420 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
00421 free(oldargs[x]);
00422 } else if (!dead) {
00423 pbx_builtin_setvar_helper(chan, varname, NULL);
00424 }
00425 }
00426
00427
00428 if (!dead) {
00429 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
00430 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
00431 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
00432 }
00433 if (save_macro_exten)
00434 free(save_macro_exten);
00435 if (save_macro_context)
00436 free(save_macro_context);
00437 if (save_macro_priority)
00438 free(save_macro_priority);
00439
00440 if (!dead && setmacrocontext) {
00441 chan->macrocontext[0] = '\0';
00442 chan->macroexten[0] = '\0';
00443 chan->macropriority = 0;
00444 }
00445
00446 if (!dead && !strcasecmp(chan->context, fullmacro)) {
00447
00448 chan->priority = oldpriority;
00449 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
00450 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
00451
00452 const char *offsets;
00453 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
00454 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
00455
00456
00457 if (sscanf(offsets, "%d", &offset) == 1) {
00458 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
00459 chan->priority += offset;
00460 }
00461 }
00462 }
00463 }
00464 }
00465
00466 if (!dead)
00467 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
00468 if (save_macro_offset)
00469 free(save_macro_offset);
00470
00471
00472 if (exclusive) {
00473 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
00474 if (ast_context_unlockmacro(fullmacro)) {
00475 ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
00476 res = 0;
00477 }
00478 }
00479
00480 ast_module_user_remove(u);
00481
00482 return res;
00483 }
00484
00485 static int macro_exec(struct ast_channel *chan, void *data)
00486 {
00487 return _macro_exec(chan, data, 0);
00488 }
00489
00490 static int macroexclusive_exec(struct ast_channel *chan, void *data)
00491 {
00492 return _macro_exec(chan, data, 1);
00493 }
00494
00495 static int macroif_exec(struct ast_channel *chan, void *data)
00496 {
00497 char *expr = NULL, *label_a = NULL, *label_b = NULL;
00498 int res = 0;
00499 struct ast_module_user *u;
00500
00501 u = ast_module_user_add(chan);
00502
00503 if (!(expr = ast_strdupa(data))) {
00504 ast_module_user_remove(u);
00505 return -1;
00506 }
00507
00508 if ((label_a = strchr(expr, '?'))) {
00509 *label_a = '\0';
00510 label_a++;
00511 if ((label_b = strchr(label_a, ':'))) {
00512 *label_b = '\0';
00513 label_b++;
00514 }
00515 if (pbx_checkcondition(expr))
00516 macro_exec(chan, label_a);
00517 else if (label_b)
00518 macro_exec(chan, label_b);
00519 } else
00520 ast_log(LOG_WARNING, "Invalid Syntax.\n");
00521
00522 ast_module_user_remove(u);
00523
00524 return res;
00525 }
00526
00527 static int macro_exit_exec(struct ast_channel *chan, void *data)
00528 {
00529 return MACRO_EXIT_RESULT;
00530 }
00531
00532 static int unload_module(void)
00533 {
00534 int res;
00535
00536 res = ast_unregister_application(if_app);
00537 res |= ast_unregister_application(exit_app);
00538 res |= ast_unregister_application(app);
00539 res |= ast_unregister_application(exclusive_app);
00540
00541 ast_module_user_hangup_all();
00542
00543 return res;
00544 }
00545
00546 static int load_module(void)
00547 {
00548 int res;
00549
00550 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
00551 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
00552 res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
00553 res |= ast_register_application(app, macro_exec, synopsis, descrip);
00554
00555 return res;
00556 }
00557
00558 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");