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 runningapp[0] = '\0';
00283 runningdata[0] = '\0';
00284
00285
00286 if (ast_rdlock_contexts()) {
00287 ast_log(LOG_WARNING, "Failed to lock contexts list\n");
00288 } else {
00289 for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
00290 if (!strcmp(ast_get_context_name(c), chan->context)) {
00291 if (ast_lock_context(c)) {
00292 ast_log(LOG_WARNING, "Unable to lock context?\n");
00293 } else {
00294 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
00295 if (e) {
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 }
00299 ast_unlock_context(c);
00300 }
00301 break;
00302 }
00303 }
00304 }
00305 ast_unlock_contexts();
00306
00307
00308 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00309
00310 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
00311
00312 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
00313 (res == '*') || (res == '#')) {
00314
00315 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
00316 break;
00317 }
00318 switch(res) {
00319 case MACRO_EXIT_RESULT:
00320 res = 0;
00321 goto out;
00322 case AST_PBX_KEEPALIVE:
00323 if (option_debug)
00324 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);
00325 else if (option_verbose > 1)
00326 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);
00327 goto out;
00328 break;
00329 default:
00330 if (option_debug)
00331 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);
00332 else if (option_verbose > 1)
00333 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);
00334 dead = 1;
00335 goto out;
00336 }
00337 }
00338
00339 ast_log(LOG_DEBUG, "Executed application: %s\n", runningapp);
00340
00341 if (!strcasecmp(runningapp, "GOSUB")) {
00342 gosub_level++;
00343 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00344 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
00345 char tmp2[1024] = "", *cond, *app, *app2 = tmp2;
00346 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00347 cond = strsep(&app2, "?");
00348 app = strsep(&app2, ":");
00349 if (pbx_checkcondition(cond)) {
00350 if (!ast_strlen_zero(app)) {
00351 gosub_level++;
00352 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00353 }
00354 } else {
00355 if (!ast_strlen_zero(app2)) {
00356 gosub_level++;
00357 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00358 }
00359 }
00360 } else if (!strcasecmp(runningapp, "RETURN")) {
00361 gosub_level--;
00362 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00363 } else if (!strcasecmp(runningapp, "STACKPOP")) {
00364 gosub_level--;
00365 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00366 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
00367
00368 char tmp2[1024] = "", *tmp3 = NULL;
00369 pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
00370 if (!strcasecmp(runningapp, "EXECIF")) {
00371 tmp3 = strchr(tmp2, '|');
00372 if (tmp3)
00373 *tmp3++ = '\0';
00374 if (!pbx_checkcondition(tmp2))
00375 tmp3 = NULL;
00376 } else
00377 tmp3 = tmp2;
00378
00379 if (tmp3)
00380 ast_log(LOG_DEBUG, "Last app: %s\n", tmp3);
00381
00382 if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
00383 gosub_level++;
00384 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
00385 } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
00386 gosub_level--;
00387 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00388 } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
00389 gosub_level--;
00390 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
00391 }
00392 }
00393
00394 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
00395 if (option_verbose > 1)
00396 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
00397 break;
00398 }
00399
00400
00401 if (chan->_softhangup && !inhangup) {
00402 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
00403 chan->exten, chan->macroexten, chan->priority);
00404 goto out;
00405 }
00406 chan->priority++;
00407 }
00408 out:
00409
00410 snprintf(depthc, sizeof(depthc), "%d", depth);
00411 if (!dead) {
00412 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
00413 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
00414 }
00415
00416 for (x = 1; x < argc; x++) {
00417
00418 snprintf(varname, sizeof(varname), "ARG%d", x);
00419 if (oldargs[x]) {
00420 if (!dead)
00421 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
00422 free(oldargs[x]);
00423 } else if (!dead) {
00424 pbx_builtin_setvar_helper(chan, varname, NULL);
00425 }
00426 }
00427
00428
00429 if (!dead) {
00430 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
00431 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
00432 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
00433 }
00434 if (save_macro_exten)
00435 free(save_macro_exten);
00436 if (save_macro_context)
00437 free(save_macro_context);
00438 if (save_macro_priority)
00439 free(save_macro_priority);
00440
00441 if (!dead && setmacrocontext) {
00442 chan->macrocontext[0] = '\0';
00443 chan->macroexten[0] = '\0';
00444 chan->macropriority = 0;
00445 }
00446
00447 if (!dead && !strcasecmp(chan->context, fullmacro)) {
00448
00449 chan->priority = oldpriority;
00450 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
00451 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
00452
00453 const char *offsets;
00454 ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
00455 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
00456
00457
00458 if (sscanf(offsets, "%d", &offset) == 1) {
00459 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
00460 chan->priority += offset;
00461 }
00462 }
00463 }
00464 }
00465 }
00466
00467 if (!dead)
00468 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
00469 if (save_macro_offset)
00470 free(save_macro_offset);
00471
00472
00473 if (exclusive) {
00474 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
00475 if (ast_context_unlockmacro(fullmacro)) {
00476 ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
00477 res = 0;
00478 }
00479 }
00480
00481 ast_module_user_remove(u);
00482
00483 return res;
00484 }
00485
00486 static int macro_exec(struct ast_channel *chan, void *data)
00487 {
00488 return _macro_exec(chan, data, 0);
00489 }
00490
00491 static int macroexclusive_exec(struct ast_channel *chan, void *data)
00492 {
00493 return _macro_exec(chan, data, 1);
00494 }
00495
00496 static int macroif_exec(struct ast_channel *chan, void *data)
00497 {
00498 char *expr = NULL, *label_a = NULL, *label_b = NULL;
00499 int res = 0;
00500 struct ast_module_user *u;
00501
00502 u = ast_module_user_add(chan);
00503
00504 if (!(expr = ast_strdupa(data))) {
00505 ast_module_user_remove(u);
00506 return -1;
00507 }
00508
00509 if ((label_a = strchr(expr, '?'))) {
00510 *label_a = '\0';
00511 label_a++;
00512 if ((label_b = strchr(label_a, ':'))) {
00513 *label_b = '\0';
00514 label_b++;
00515 }
00516 if (pbx_checkcondition(expr))
00517 res = macro_exec(chan, label_a);
00518 else if (label_b)
00519 res = macro_exec(chan, label_b);
00520 } else
00521 ast_log(LOG_WARNING, "Invalid Syntax.\n");
00522
00523 ast_module_user_remove(u);
00524
00525 return res;
00526 }
00527
00528 static int macro_exit_exec(struct ast_channel *chan, void *data)
00529 {
00530 return MACRO_EXIT_RESULT;
00531 }
00532
00533 static int unload_module(void)
00534 {
00535 int res;
00536
00537 res = ast_unregister_application(if_app);
00538 res |= ast_unregister_application(exit_app);
00539 res |= ast_unregister_application(app);
00540 res |= ast_unregister_application(exclusive_app);
00541
00542 ast_module_user_hangup_all();
00543
00544 return res;
00545 }
00546
00547 static int load_module(void)
00548 {
00549 int res;
00550
00551 res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
00552 res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
00553 res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
00554 res |= ast_register_application(app, macro_exec, synopsis, descrip);
00555
00556 return res;
00557 }
00558
00559 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");