put a limit on Macro depth (to combat recursion) (issue #5114)
[asterisk/asterisk.git] / apps / app_macro.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Macro Implementation
5  * 
6  * Copyright (C) 2003 - 2005, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <sys/types.h>
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include <string.h>
18
19 #include "asterisk.h"
20
21 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
22
23 #include "asterisk/file.h"
24 #include "asterisk/logger.h"
25 #include "asterisk/channel.h"
26 #include "asterisk/pbx.h"
27 #include "asterisk/module.h"
28 #include "asterisk/options.h"
29 #include "asterisk/config.h"
30 #include "asterisk/utils.h"
31 #include "asterisk/lock.h"
32
33 #define MAX_ARGS 80
34
35 /* special result value used to force macro exit */
36 #define MACRO_EXIT_RESULT 1024
37
38 static char *tdesc = "Extension Macros";
39
40 static char *descrip =
41 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
42 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
43 "executing each step, then returning when the steps end. \n"
44 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
45 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
46 "${ARG1}, ${ARG2}, etc in the macro context.\n"
47 "If you Goto out of the Macro context, the Macro will terminate and control\n"
48 "will be returned at the location of the Goto.\n"
49 "Macro returns -1 if any step in the macro returns -1, and 0 otherwise.\n" 
50 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
51 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
52
53 static char *if_descrip =
54 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
55 "Executes macro defined in <macroname_a> if <expr> is true\n"
56 "(otherwise <macroname_b> if provided)\n"
57 "Arguments and return values as in application macro()\n";
58
59 static char *exit_descrip =
60 "  MacroExit():\n"
61 "Causes the currently running macro to exit as if it had\n"
62 "ended normally by running out of priorities to execute.\n"
63 "If used outside a macro, will likely cause unexpected\n"
64 "behavior.\n";
65
66 static char *app = "Macro";
67 static char *if_app = "MacroIf";
68 static char *exit_app = "MacroExit";
69
70 static char *synopsis = "Macro Implementation";
71 static char *if_synopsis = "Conditional Macro Implementation";
72 static char *exit_synopsis = "Exit From Macro";
73
74 STANDARD_LOCAL_USER;
75
76 LOCAL_USER_DECL;
77
78 static int macro_exec(struct ast_channel *chan, void *data)
79 {
80         char *tmp;
81         char *cur, *rest;
82         char *macro;
83         char fullmacro[80];
84         char varname[80];
85         char *oldargs[MAX_ARGS + 1] = { NULL, };
86         int argc, x;
87         int res=0;
88         char oldexten[256]="";
89         int oldpriority;
90         char pc[80], depthc[12];
91         char oldcontext[AST_MAX_CONTEXT] = "";
92         char *offsets;
93         int offset, depth;
94         int setmacrocontext=0;
95         int autoloopflag;
96   
97         char *save_macro_exten;
98         char *save_macro_context;
99         char *save_macro_priority;
100         char *save_macro_offset;
101         struct localuser *u;
102   
103         if (!data || ast_strlen_zero(data)) {
104                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
105                 return 0;
106         }
107
108         /* Count how many levels deep the rabbit hole goes */
109         tmp = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
110         if (tmp) {
111                 sscanf(tmp, "%d", &depth);
112         } else {
113                 depth = 0;
114         }
115
116         if (depth >= 7) {
117                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
118                 return 0;
119         }
120         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
121         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
122
123         tmp = ast_strdupa((char *) data);
124         rest = tmp;
125         macro = strsep(&rest, "|");
126         if (!macro || ast_strlen_zero(macro)) {
127                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
128                 return 0;
129         }
130         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
131         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
132                 if (!ast_context_find(fullmacro)) 
133                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
134                 else
135                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
136                 return 0;
137         }
138
139         LOCAL_USER_ADD(u);
140         /* Save old info */
141         oldpriority = chan->priority;
142         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
143         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
144         if (ast_strlen_zero(chan->macrocontext)) {
145                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
146                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
147                 chan->macropriority = chan->priority;
148                 setmacrocontext=1;
149         }
150         argc = 1;
151         /* Save old macro variables */
152         save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
153         if (save_macro_exten) 
154                 save_macro_exten = strdup(save_macro_exten);
155         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
156
157         save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
158         if (save_macro_context)
159                 save_macro_context = strdup(save_macro_context);
160         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
161
162         save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
163         if (save_macro_priority) 
164                 save_macro_priority = strdup(save_macro_priority);
165         snprintf(pc, sizeof(pc), "%d", oldpriority);
166         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
167   
168         save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
169         if (save_macro_offset) 
170                 save_macro_offset = strdup(save_macro_offset);
171         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
172
173         /* Setup environment for new run */
174         chan->exten[0] = 's';
175         chan->exten[1] = '\0';
176         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
177         chan->priority = 1;
178
179         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
180                 /* Save copy of old arguments if we're overwriting some, otherwise
181                 let them pass through to the other macro */
182                 snprintf(varname, sizeof(varname), "ARG%d", argc);
183                 oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
184                 if (oldargs[argc])
185                         oldargs[argc] = strdup(oldargs[argc]);
186                 pbx_builtin_setvar_helper(chan, varname, cur);
187                 argc++;
188         }
189         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
190         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
191         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
192                 /* Reset the macro depth, if it was changed in the last iteration */
193                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
194                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
195                         /* Something bad happened, or a hangup has been requested. */
196                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
197                         (res == '*') || (res == '#')) {
198                                 /* Just return result as to the previous application as if it had been dialed */
199                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
200                                 break;
201                         }
202                         switch(res) {
203                         case MACRO_EXIT_RESULT:
204                                 res = 0;
205                                 goto out;
206                         case AST_PBX_KEEPALIVE:
207                                 if (option_debug)
208                                         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);
209                                 else if (option_verbose > 1)
210                                         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);
211                                 goto out;
212                                 break;
213                         default:
214                                 if (option_debug)
215                                         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);
216                                 else if (option_verbose > 1)
217                                         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);
218                                 goto out;
219                         }
220                 }
221                 if (strcasecmp(chan->context, fullmacro)) {
222                         if (option_verbose > 1)
223                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
224                         break;
225                 }
226                 /* don't stop executing extensions when we're in "h" */
227                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
228                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
229                                 chan->exten, chan->priority);
230                         goto out;
231                 }
232                 chan->priority++;
233         }
234         out:
235         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
236         snprintf(depthc, sizeof(depthc), "%d", depth);
237         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
238
239         ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
240         for (x=1; x<argc; x++) {
241                 /* Restore old arguments and delete ours */
242                 snprintf(varname, sizeof(varname), "ARG%d", x);
243                 if (oldargs[x]) {
244                         pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
245                         free(oldargs[x]);
246                 } else {
247                         pbx_builtin_setvar_helper(chan, varname, NULL);
248                 }
249         }
250
251         /* Restore macro variables */
252         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
253         if (save_macro_exten)
254                 free(save_macro_exten);
255         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
256         if (save_macro_context)
257                 free(save_macro_context);
258         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
259         if (save_macro_priority)
260                 free(save_macro_priority);
261         if (setmacrocontext) {
262                 chan->macrocontext[0] = '\0';
263                 chan->macroexten[0] = '\0';
264                 chan->macropriority = 0;
265         }
266
267         if (!strcasecmp(chan->context, fullmacro)) {
268                 /* If we're leaving the macro normally, restore original information */
269                 chan->priority = oldpriority;
270                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
271                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
272                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
273                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
274                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
275                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
276                                 normally if there is any problem */
277                                 if (sscanf(offsets, "%d", &offset) == 1) {
278                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
279                                                 chan->priority += offset;
280                                         }
281                                 }
282                         }
283                 }
284         }
285
286         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
287         if (save_macro_offset)
288                 free(save_macro_offset);
289         LOCAL_USER_REMOVE(u);
290         return res;
291 }
292
293 static int macroif_exec(struct ast_channel *chan, void *data) 
294 {
295         char *expr = NULL, *label_a = NULL, *label_b = NULL;
296         int res = 0;
297
298         if((expr = ast_strdupa((char *) data))) {
299                 if ((label_a = strchr(expr, '?'))) {
300                         *label_a = '\0';
301                         label_a++;
302                         if ((label_b = strchr(label_a, ':'))) {
303                                 *label_b = '\0';
304                                 label_b++;
305                         }
306                         if (ast_true(expr))
307                                 macro_exec(chan, label_a);
308                         else if (label_b) 
309                                 macro_exec(chan, label_b);
310                         
311                 } else
312                         ast_log(LOG_WARNING, "Invalid Syntax.\n");
313         } else 
314                 ast_log(LOG_ERROR, "Out of Memory!\n");
315         return res;
316 }
317                         
318 static int macro_exit_exec(struct ast_channel *chan, void *data)
319 {
320         return MACRO_EXIT_RESULT;
321 }
322
323 int unload_module(void)
324 {
325         STANDARD_HANGUP_LOCALUSERS;
326         ast_unregister_application(if_app);
327         ast_unregister_application(exit_app);
328         return ast_unregister_application(app);
329 }
330
331 int load_module(void)
332 {
333         ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
334         ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
335         return ast_register_application(app, macro_exec, synopsis, descrip);
336 }
337
338 char *description(void)
339 {
340         return tdesc;
341 }
342
343 int usecount(void)
344 {
345         int res;
346         STANDARD_USECOUNT(res);
347         return res;
348 }
349
350 char *key()
351 {
352         return ASTERISK_GPL_KEY;
353 }