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: 211569 $")
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_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 ast_channel_lock(chan);
00210 if ((s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"))) {
00211 sscanf(s, "%30d", &maxdepth);
00212 }
00213
00214
00215 if ((s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"))) {
00216 sscanf(s, "%30d", &depth);
00217 }
00218
00219
00220 if (strcmp(chan->exten, "h") == 0)
00221 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
00222
00223 if ((inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP"))) {
00224 sscanf(inhangupc, "%30d", &inhangup);
00225 }
00226 ast_channel_unlock(chan);
00227
00228 if (depth >= maxdepth) {
00229 ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
00230 return 0;
00231 }
00232 snprintf(depthc, sizeof(depthc), "%d", depth + 1);
00233 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00234
00235 tmp = ast_strdupa(data);
00236 rest = tmp;
00237 macro = strsep(&rest, ",");
00238 if (ast_strlen_zero(macro)) {
00239 ast_log(LOG_WARNING, "Invalid macro name specified\n");
00240 return 0;
00241 }
00242
00243 snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
00244 if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
00245 if (!ast_context_find(fullmacro))
00246 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
00247 else
00248 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
00249 return 0;
00250 }
00251
00252
00253 if (exclusive) {
00254 ast_debug(1, "Locking macrolock for '%s'\n", fullmacro);
00255 ast_autoservice_start(chan);
00256 if (ast_context_lockmacro(fullmacro)) {
00257 ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
00258 ast_autoservice_stop(chan);
00259 return 0;
00260 }
00261 ast_autoservice_stop(chan);
00262 }
00263
00264
00265 oldpriority = chan->priority;
00266 ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
00267 ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
00268 if (ast_strlen_zero(chan->macrocontext)) {
00269 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
00270 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
00271 chan->macropriority = chan->priority;
00272 setmacrocontext=1;
00273 }
00274 argc = 1;
00275
00276 save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
00277 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
00278
00279 save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
00280 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
00281
00282 save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
00283 snprintf(pc, sizeof(pc), "%d", oldpriority);
00284 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
00285
00286 save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
00287 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
00288
00289
00290 chan->exten[0] = 's';
00291 chan->exten[1] = '\0';
00292 ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
00293 chan->priority = 1;
00294
00295 ast_channel_lock(chan);
00296 while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
00297 const char *argp;
00298
00299
00300 snprintf(varname, sizeof(varname), "ARG%d", argc);
00301 if ((argp = pbx_builtin_getvar_helper(chan, varname))) {
00302 oldargs[argc] = ast_strdup(argp);
00303 }
00304 pbx_builtin_setvar_helper(chan, varname, cur);
00305 argc++;
00306 }
00307 ast_channel_unlock(chan);
00308 autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
00309 ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
00310 while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
00311 struct ast_context *c;
00312 struct ast_exten *e;
00313 int foundx;
00314 runningapp[0] = '\0';
00315 runningdata[0] = '\0';
00316
00317
00318 if (ast_rdlock_contexts()) {
00319 ast_log(LOG_WARNING, "Failed to lock contexts list\n");
00320 } else {
00321 for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
00322 if (!strcmp(ast_get_context_name(c), chan->context)) {
00323 if (ast_rdlock_context(c)) {
00324 ast_log(LOG_WARNING, "Unable to lock context?\n");
00325 } else {
00326 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
00327 if (e) {
00328 ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
00329 ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
00330 }
00331 ast_unlock_context(c);
00332 }
00333 break;
00334 }
00335 }
00336 }
00337 ast_unlock_contexts();
00338
00339
00340 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00341
00342 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num, &foundx,1))) {
00343
00344 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
00345 (res == '*') || (res == '#')) {
00346
00347 ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
00348 break;
00349 }
00350 switch(res) {
00351 case MACRO_EXIT_RESULT:
00352 res = 0;
00353 goto out;
00354 default:
00355 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);
00356 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);
00357 goto out;
00358 }
00359 }
00360
00361 ast_debug(1, "Executed application: %s\n", runningapp);
00362
00363 if (!strcasecmp(runningapp, "GOSUB")) {
00364 gosub_level++;
00365 ast_debug(1, "Incrementing gosub_level\n");
00366 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
00367 char tmp2[1024], *cond, *app_arg, *app2 = tmp2;
00368 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00369 cond = strsep(&app2, "?");
00370 app_arg = strsep(&app2, ":");
00371 if (pbx_checkcondition(cond)) {
00372 if (!ast_strlen_zero(app_arg)) {
00373 gosub_level++;
00374 ast_debug(1, "Incrementing gosub_level\n");
00375 }
00376 } else {
00377 if (!ast_strlen_zero(app2)) {
00378 gosub_level++;
00379 ast_debug(1, "Incrementing gosub_level\n");
00380 }
00381 }
00382 } else if (!strcasecmp(runningapp, "RETURN")) {
00383 gosub_level--;
00384 ast_debug(1, "Decrementing gosub_level\n");
00385 } else if (!strcasecmp(runningapp, "STACKPOP")) {
00386 gosub_level--;
00387 ast_debug(1, "Decrementing gosub_level\n");
00388 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
00389
00390 char tmp2[1024], *tmp3 = NULL;
00391 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00392 if (!strcasecmp(runningapp, "EXECIF")) {
00393 tmp3 = strchr(tmp2, '|');
00394 if (tmp3)
00395 *tmp3++ = '\0';
00396 if (!pbx_checkcondition(tmp2))
00397 tmp3 = NULL;
00398 } else
00399 tmp3 = tmp2;
00400
00401 if (tmp3)
00402 ast_debug(1, "Last app: %s\n", tmp3);
00403
00404 if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
00405 gosub_level++;
00406 ast_debug(1, "Incrementing gosub_level\n");
00407 } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
00408 gosub_level--;
00409 ast_debug(1, "Decrementing gosub_level\n");
00410 } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
00411 gosub_level--;
00412 ast_debug(1, "Decrementing gosub_level\n");
00413 }
00414 }
00415
00416 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
00417 ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
00418 break;
00419 }
00420
00421
00422 if (ast_check_hangup(chan) && !inhangup) {
00423 ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan->exten, chan->macroexten, chan->priority);
00424 goto out;
00425 }
00426 chan->priority++;
00427 }
00428 out:
00429
00430
00431 ast_channel_lock(chan);
00432
00433
00434 snprintf(depthc, sizeof(depthc), "%d", depth);
00435 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00436 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
00437
00438 for (x = 1; x < argc; x++) {
00439
00440 snprintf(varname, sizeof(varname), "ARG%d", x);
00441 if (oldargs[x]) {
00442 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
00443 ast_free(oldargs[x]);
00444 } else {
00445 pbx_builtin_setvar_helper(chan, varname, NULL);
00446 }
00447 }
00448
00449
00450 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
00451 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
00452 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
00453 if (save_macro_exten)
00454 ast_free(save_macro_exten);
00455 if (save_macro_context)
00456 ast_free(save_macro_context);
00457 if (save_macro_priority)
00458 ast_free(save_macro_priority);
00459
00460 if (setmacrocontext) {
00461 chan->macrocontext[0] = '\0';
00462 chan->macroexten[0] = '\0';
00463 chan->macropriority = 0;
00464 }
00465
00466 if (!strcasecmp(chan->context, fullmacro)) {
00467
00468 chan->priority = oldpriority;
00469 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
00470 if (!(ast_check_hangup(chan) & AST_SOFTHANGUP_ASYNCGOTO)) {
00471
00472 const char *offsets;
00473 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
00474 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
00475
00476
00477 if (sscanf(offsets, "%30d", &offset) == 1) {
00478 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
00479 chan->priority += offset;
00480 }
00481 }
00482 }
00483 }
00484 }
00485
00486 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
00487 if (save_macro_offset)
00488 ast_free(save_macro_offset);
00489
00490
00491 if (exclusive) {
00492 ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro);
00493 if (ast_context_unlockmacro(fullmacro)) {
00494 ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
00495 res = 0;
00496 }
00497 }
00498 ast_channel_unlock(chan);
00499
00500 return res;
00501 }
00502
00503 static int macro_exec(struct ast_channel *chan, void *data)
00504 {
00505 return _macro_exec(chan, data, 0);
00506 }
00507
00508 static int macroexclusive_exec(struct ast_channel *chan, void *data)
00509 {
00510 return _macro_exec(chan, data, 1);
00511 }
00512
00513 static int macroif_exec(struct ast_channel *chan, void *data)
00514 {
00515 char *expr = NULL, *label_a = NULL, *label_b = NULL;
00516 int res = 0;
00517
00518 if (!(expr = ast_strdupa(data)))
00519 return -1;
00520
00521 if ((label_a = strchr(expr, '?'))) {
00522 *label_a = '\0';
00523 label_a++;
00524 if ((label_b = strchr(label_a, ':'))) {
00525 *label_b = '\0';
00526 label_b++;
00527 }
00528 if (pbx_checkcondition(expr))
00529 res = macro_exec(chan, label_a);
00530 else if (label_b)
00531 res = macro_exec(chan, label_b);
00532 } else
00533 ast_log(LOG_WARNING, "Invalid Syntax.\n");
00534
00535 return res;
00536 }
00537
00538 static int macro_exit_exec(struct ast_channel *chan, void *data)
00539 {
00540 return MACRO_EXIT_RESULT;
00541 }
00542
00543 static int unload_module(void)
00544 {
00545 int res;
00546
00547 res = ast_unregister_application(if_app);
00548 res |= ast_unregister_application(exit_app);
00549 res |= ast_unregister_application(app);
00550 res |= ast_unregister_application(exclusive_app);
00551
00552 return res;
00553 }
00554
00555 static int load_module(void)
00556 {
00557 int res;
00558
00559 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
00560 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
00561 res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
00562 res |= ast_register_application(app, macro_exec, synopsis, descrip);
00563
00564 return res;
00565 }
00566
00567 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");