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