b087ad36b43a39622317e249a3760e15f9718f3c
[asterisk/asterisk.git] / apps / app_macro.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Dial plan macro Implementation
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include "asterisk/file.h"
33 #include "asterisk/channel.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/module.h"
36 #include "asterisk/config.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/lock.h"
39
40 #define MAX_ARGS 80
41
42 /* special result value used to force macro exit */
43 #define MACRO_EXIT_RESULT 1024
44
45 static char *descrip =
46 "  Macro(macroname,arg1,arg2...): Executes a macro using the context\n"
47 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
48 "executing each step, then returning when the steps end. \n"
49 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
50 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
51 "${ARG1}, ${ARG2}, etc in the macro context.\n"
52 "If you Goto out of the Macro context, the Macro will terminate and control\n"
53 "will be returned at the location of the Goto.\n"
54 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
55 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
56 "Extensions: While a macro is being executed, it becomes the current context.\n"
57 "            This means that if a hangup occurs, for instance, that the macro\n"
58 "            will be searched for an 'h' extension, NOT the context from which\n"
59 "            the macro was called. So, make sure to define all appropriate\n"
60 "            extensions in your macro! (Note: AEL does not use macros)\n"
61 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
62 "         contained within it via sub-engine), and a fixed per-thread\n"
63 "         memory stack allowance, macros are limited to 7 levels\n"
64 "         of nesting (macro calling macro calling macro, etc.); It\n"
65 "         may be possible that stack-intensive applications in deeply nested macros\n"
66 "         could cause asterisk to crash earlier than this limit. It is advised that\n"
67 "         if you need to deeply nest macro calls, that you use the Gosub application\n"
68 "         (now allows arguments like a Macro) with explict Return() calls instead.\n";
69
70 static char *if_descrip =
71 "  MacroIf(<expr>?macroname_a[,arg1][:macroname_b[,arg1]])\n"
72 "Executes macro defined in <macroname_a> if <expr> is true\n"
73 "(otherwise <macroname_b> if provided)\n"
74 "Arguments and return values as in application Macro()\n";
75
76 static char *exclusive_descrip =
77 "  MacroExclusive(macroname,arg1,arg2...):\n"
78 "Executes macro defined in the context 'macro-macroname'\n"
79 "Only one call at a time may run the macro.\n"
80 "(we'll wait if another call is busy executing in the Macro)\n"
81 "Arguments and return values as in application Macro()\n";
82
83 static char *exit_descrip =
84 "  MacroExit():\n"
85 "Causes the currently running macro to exit as if it had\n"
86 "ended normally by running out of priorities to execute.\n"
87 "If used outside a macro, will likely cause unexpected\n"
88 "behavior.\n";
89
90 static char *app = "Macro";
91 static char *if_app = "MacroIf";
92 static char *exclusive_app = "MacroExclusive";
93 static char *exit_app = "MacroExit";
94
95 static char *synopsis = "Macro Implementation";
96 static char *if_synopsis = "Conditional Macro Implementation";
97 static char *exclusive_synopsis = "Exclusive Macro Implementation";
98 static char *exit_synopsis = "Exit From Macro";
99
100
101 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
102 {
103         struct ast_exten *e;
104         struct ast_include *i;
105         struct ast_context *c2;
106
107         for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
108                 if (ast_extension_match(ast_get_extension_name(e), exten)) {
109                         int needmatch = ast_get_extension_matchcid(e);
110                         if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
111                                 (!needmatch)) {
112                                 /* This is the matching extension we want */
113                                 struct ast_exten *p;
114                                 for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
115                                         if (priority != ast_get_extension_priority(p))
116                                                 continue;
117                                         return p;
118                                 }
119                         }
120                 }
121         }
122
123         /* No match; run through includes */
124         for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
125                 for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
126                         if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
127                                 e = find_matching_priority(c2, exten, priority, callerid);
128                                 if (e)
129                                         return e;
130                         }
131                 }
132         }
133         return NULL;
134 }
135
136 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
137 {
138         const char *s;
139         char *tmp;
140         char *cur, *rest;
141         char *macro;
142         char fullmacro[80];
143         char varname[80];
144         char runningapp[80], runningdata[1024];
145         char *oldargs[MAX_ARGS + 1] = { NULL, };
146         int argc, x;
147         int res=0;
148         char oldexten[256]="";
149         int oldpriority, gosub_level = 0;
150         char pc[80], depthc[12];
151         char oldcontext[AST_MAX_CONTEXT] = "";
152         const char *inhangupc;
153         int offset, depth = 0, maxdepth = 7;
154         int setmacrocontext=0;
155         int autoloopflag, dead = 0, inhangup = 0;
156   
157         char *save_macro_exten;
158         char *save_macro_context;
159         char *save_macro_priority;
160         char *save_macro_offset;
161  
162         if (ast_strlen_zero(data)) {
163                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"core show application macro\" for help.\n");
164                 return -1;
165         }
166
167         /* does the user want a deeper rabbit hole? */
168         s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
169         if (s)
170                 sscanf(s, "%d", &maxdepth);
171
172         /* Count how many levels deep the rabbit hole goes */
173         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
174         if (s)
175                 sscanf(s, "%d", &depth);
176         /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
177         if (strcmp(chan->exten, "h") == 0)
178                 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
179         inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
180         if (!ast_strlen_zero(inhangupc))
181                 sscanf(inhangupc, "%d", &inhangup);
182
183         if (depth >= maxdepth) {
184                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
185                 return 0;
186         }
187         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
188         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
189
190         tmp = ast_strdupa(data);
191         rest = tmp;
192         macro = strsep(&rest, ",");
193         if (ast_strlen_zero(macro)) {
194                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
195                 return 0;
196         }
197
198         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
199         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
200                 if (!ast_context_find(fullmacro)) 
201                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
202                 else
203                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
204                 return 0;
205         }
206
207         /* If we are to run the macro exclusively, take the mutex */
208         if (exclusive) {
209                 ast_debug(1, "Locking macrolock for '%s'\n", fullmacro);
210                 ast_autoservice_start(chan);
211                 if (ast_context_lockmacro(fullmacro)) {
212                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
213                         ast_autoservice_stop(chan);
214                         return 0;
215                 }
216                 ast_autoservice_stop(chan);
217         }
218         
219         /* Save old info */
220         oldpriority = chan->priority;
221         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
222         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
223         if (ast_strlen_zero(chan->macrocontext)) {
224                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
225                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
226                 chan->macropriority = chan->priority;
227                 setmacrocontext=1;
228         }
229         argc = 1;
230         /* Save old macro variables */
231         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
232         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
233
234         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
235         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
236
237         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
238         snprintf(pc, sizeof(pc), "%d", oldpriority);
239         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
240   
241         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
242         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
243
244         /* Setup environment for new run */
245         chan->exten[0] = 's';
246         chan->exten[1] = '\0';
247         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
248         chan->priority = 1;
249
250         while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
251                 const char *s;
252                 /* Save copy of old arguments if we're overwriting some, otherwise
253                 let them pass through to the other macro */
254                 snprintf(varname, sizeof(varname), "ARG%d", argc);
255                 s = pbx_builtin_getvar_helper(chan, varname);
256                 if (s)
257                         oldargs[argc] = ast_strdup(s);
258                 pbx_builtin_setvar_helper(chan, varname, cur);
259                 argc++;
260         }
261         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
262         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
263         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
264                 struct ast_context *c;
265                 struct ast_exten *e;
266                 int foundx;
267                 runningapp[0] = '\0';
268                 runningdata[0] = '\0';
269
270                 /* What application will execute? */
271                 if (ast_rdlock_contexts()) {
272                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
273                 } else {
274                         for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
275                                 if (!strcmp(ast_get_context_name(c), chan->context)) {
276                                         if (ast_rdlock_context(c)) {
277                                                 ast_log(LOG_WARNING, "Unable to lock context?\n");
278                                         } else {
279                                                 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
280                                                 if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
281                                                         ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
282                                                         ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
283                                                 }
284                                                 ast_unlock_context(c);
285                                         }
286                                         break;
287                                 }
288                         }
289                 }
290                 ast_unlock_contexts();
291
292                 /* Reset the macro depth, if it was changed in the last iteration */
293                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
294
295                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num, &foundx,1))) {
296                         /* Something bad happened, or a hangup has been requested. */
297                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
298                         (res == '*') || (res == '#')) {
299                                 /* Just return result as to the previous application as if it had been dialed */
300                                 ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
301                                 break;
302                         }
303                         switch(res) {
304                         case MACRO_EXIT_RESULT:
305                                 res = 0;
306                                 goto out;
307                         case AST_PBX_KEEPALIVE:
308                                 ast_debug(2, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
309                                 ast_verb(2, "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
310                                 goto out;
311                                 break;
312                         default:
313                                 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);
314                                 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);
315                                 dead = 1;
316                                 goto out;
317                         }
318                 }
319
320                 ast_debug(1, "Executed application: %s\n", runningapp);
321
322                 if (!strcasecmp(runningapp, "GOSUB")) {
323                         gosub_level++;
324                         ast_debug(1, "Incrementing gosub_level\n");
325                 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
326                         char tmp2[1024], *cond, *app, *app2 = tmp2;
327                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
328                         cond = strsep(&app2, "?");
329                         app = strsep(&app2, ":");
330                         if (pbx_checkcondition(cond)) {
331                                 if (!ast_strlen_zero(app)) {
332                                         gosub_level++;
333                                         ast_debug(1, "Incrementing gosub_level\n");
334                                 }
335                         } else {
336                                 if (!ast_strlen_zero(app2)) {
337                                         gosub_level++;
338                                         ast_debug(1, "Incrementing gosub_level\n");
339                                 }
340                         }
341                 } else if (!strcasecmp(runningapp, "RETURN")) {
342                         gosub_level--;
343                         ast_debug(1, "Decrementing gosub_level\n");
344                 } else if (!strcasecmp(runningapp, "STACKPOP")) {
345                         gosub_level--;
346                         ast_debug(1, "Decrementing gosub_level\n");
347                 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
348                         /* Must evaluate args to find actual app */
349                         char tmp2[1024], *tmp3 = NULL;
350                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
351                         if (!strcasecmp(runningapp, "EXECIF")) {
352                                 tmp3 = strchr(tmp2, '|');
353                                 if (tmp3)
354                                         *tmp3++ = '\0';
355                                 if (!pbx_checkcondition(tmp2))
356                                         tmp3 = NULL;
357                         } else
358                                 tmp3 = tmp2;
359
360                         if (tmp3)
361                                 ast_debug(1, "Last app: %s\n", tmp3);
362
363                         if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
364                                 gosub_level++;
365                                 ast_debug(1, "Incrementing gosub_level\n");
366                         } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
367                                 gosub_level--;
368                                 ast_debug(1, "Decrementing gosub_level\n");
369                         } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
370                                 gosub_level--;
371                                 ast_debug(1, "Decrementing gosub_level\n");
372                         }
373                 }
374
375                 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
376                         ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
377                         break;
378                 }
379
380                 /* don't stop executing extensions when we're in "h" */
381                 if (ast_check_hangup(chan) && !inhangup) {
382                         ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan->exten, chan->macroexten, chan->priority);
383                         goto out;
384                 }
385                 chan->priority++;
386         }
387         out:
388         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
389         snprintf(depthc, sizeof(depthc), "%d", depth);
390         if (!dead) {
391                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
392                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
393         }
394
395         for (x = 1; x < argc; x++) {
396                 /* Restore old arguments and delete ours */
397                 snprintf(varname, sizeof(varname), "ARG%d", x);
398                 if (oldargs[x]) {
399                         if (!dead)
400                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
401                         ast_free(oldargs[x]);
402                 } else if (!dead) {
403                         pbx_builtin_setvar_helper(chan, varname, NULL);
404                 }
405         }
406
407         /* Restore macro variables */
408         if (!dead) {
409                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
410                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
411                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
412         }
413         if (save_macro_exten)
414                 ast_free(save_macro_exten);
415         if (save_macro_context)
416                 ast_free(save_macro_context);
417         if (save_macro_priority)
418                 ast_free(save_macro_priority);
419
420         if (!dead && setmacrocontext) {
421                 chan->macrocontext[0] = '\0';
422                 chan->macroexten[0] = '\0';
423                 chan->macropriority = 0;
424         }
425
426         if (!dead && !strcasecmp(chan->context, fullmacro)) {
427                 /* If we're leaving the macro normally, restore original information */
428                 chan->priority = oldpriority;
429                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
430                 if (!(ast_check_hangup(chan) & AST_SOFTHANGUP_ASYNCGOTO)) {
431                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
432                         const char *offsets;
433                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
434                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
435                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
436                                 normally if there is any problem */
437                                 if (sscanf(offsets, "%d", &offset) == 1) {
438                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
439                                                 chan->priority += offset;
440                                         }
441                                 }
442                         }
443                 }
444         }
445
446         if (!dead)
447                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
448         if (save_macro_offset)
449                 ast_free(save_macro_offset);
450
451         /* Unlock the macro */
452         if (exclusive) {
453                 ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro);
454                 if (ast_context_unlockmacro(fullmacro)) {
455                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
456                         res = 0;
457                 }
458         }
459
460         return res;
461 }
462
463 static int macro_exec(struct ast_channel *chan, void *data)
464 {
465         return _macro_exec(chan, data, 0);
466 }
467
468 static int macroexclusive_exec(struct ast_channel *chan, void *data)
469 {
470         return _macro_exec(chan, data, 1);
471 }
472
473 static int macroif_exec(struct ast_channel *chan, void *data) 
474 {
475         char *expr = NULL, *label_a = NULL, *label_b = NULL;
476         int res = 0;
477
478         if (!(expr = ast_strdupa(data)))
479                 return -1;
480
481         if ((label_a = strchr(expr, '?'))) {
482                 *label_a = '\0';
483                 label_a++;
484                 if ((label_b = strchr(label_a, ':'))) {
485                         *label_b = '\0';
486                         label_b++;
487                 }
488                 if (pbx_checkcondition(expr))
489                         res = macro_exec(chan, label_a);
490                 else if (label_b) 
491                         res = macro_exec(chan, label_b);
492         } else
493                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
494
495         return res;
496 }
497                         
498 static int macro_exit_exec(struct ast_channel *chan, void *data)
499 {
500         return MACRO_EXIT_RESULT;
501 }
502
503 static int unload_module(void)
504 {
505         int res;
506
507         res = ast_unregister_application(if_app);
508         res |= ast_unregister_application(exit_app);
509         res |= ast_unregister_application(app);
510         res |= ast_unregister_application(exclusive_app);
511
512         return res;
513 }
514
515 static int load_module(void)
516 {
517         int res;
518
519         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
520         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
521         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
522         res |= ast_register_application(app, macro_exec, synopsis, descrip);
523
524         return res;
525 }
526
527 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");