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