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