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