In app_macro, changed the previously changed upper recursion depth limit to a variabl...
[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 *tdesc = "Extension Macros";
54
55 static char *descrip =
56 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
57 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
58 "executing each step, then returning when the steps end. \n"
59 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
60 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
61 "${ARG1}, ${ARG2}, etc in the macro context.\n"
62 "If you Goto out of the Macro context, the Macro will terminate and control\n"
63 "will be returned at the location of the Goto.\n"
64 "If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
65 "at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
66
67 static char *if_descrip =
68 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
69 "Executes macro defined in <macroname_a> if <expr> is true\n"
70 "(otherwise <macroname_b> if provided)\n"
71 "Arguments and return values as in application macro()\n";
72
73 static char *exclusive_descrip =
74 "  MacroExclusive(macroname|arg1|arg2...):\n"
75 "Executes macro defined in the context 'macro-macroname'\n"
76 "Only one call at a time may run the macro.\n"
77 "(we'll wait if another call is busy executing in the Macro)\n"
78 "Arguments and return values as in application Macro()\n";
79
80 static char *exit_descrip =
81 "  MacroExit():\n"
82 "Causes the currently running macro to exit as if it had\n"
83 "ended normally by running out of priorities to execute.\n"
84 "If used outside a macro, will likely cause unexpected\n"
85 "behavior.\n";
86
87 static char *app = "Macro";
88 static char *if_app = "MacroIf";
89 static char *exclusive_app = "MacroExclusive";
90 static char *exit_app = "MacroExit";
91
92 static char *synopsis = "Macro Implementation";
93 static char *if_synopsis = "Conditional Macro Implementation";
94 static char *exclusive_synopsis = "Exclusive Macro Implementation";
95 static char *exit_synopsis = "Exit From Macro";
96
97 LOCAL_USER_DECL;
98
99 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
100 {
101         const char *s;
102
103         char *tmp;
104         char *cur, *rest;
105         char *macro;
106         char fullmacro[80];
107         char varname[80];
108         char *oldargs[MAX_ARGS + 1] = { NULL, };
109         int argc, x;
110         int res=0;
111         char oldexten[256]="";
112         int oldpriority;
113         char pc[80], depthc[12];
114         char oldcontext[AST_MAX_CONTEXT] = "";
115         int offset, depth = 0, maxdepth = 7;
116         int setmacrocontext=0;
117         int autoloopflag, dead = 0;
118   
119         char *save_macro_exten;
120         char *save_macro_context;
121         char *save_macro_priority;
122         char *save_macro_offset;
123         struct localuser *u;
124  
125         if (ast_strlen_zero(data)) {
126                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
127                 return -1;
128         }
129
130         LOCAL_USER_ADD(u);
131
132         /* does the user want a deeper rabbit hole? */
133         s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
134         if (s)
135                 sscanf(s, "%d", &maxdepth);
136
137         /* Count how many levels deep the rabbit hole goes */
138         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
139         if (s)
140                 sscanf(s, "%d", &depth);
141         if (depth >= maxdepth) {
142                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
143                 LOCAL_USER_REMOVE(u);
144                 return 0;
145         }
146         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
147         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
148
149         tmp = ast_strdupa(data);
150         rest = tmp;
151         macro = strsep(&rest, "|");
152         if (ast_strlen_zero(macro)) {
153                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
154                 LOCAL_USER_REMOVE(u);
155                 return 0;
156         }
157
158         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
159         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
160                 if (!ast_context_find(fullmacro)) 
161                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
162                 else
163                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
164                 LOCAL_USER_REMOVE(u);
165                 return 0;
166         }
167
168         /* If we are to run the macro exclusively, take the mutex */
169         if (exclusive) {
170                 ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
171                 ast_autoservice_start(chan);
172                 if (ast_context_lockmacro(fullmacro)) {
173                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
174                         ast_autoservice_stop(chan);
175                         LOCAL_USER_REMOVE(u);
176                         return 0;
177                 }
178                 ast_autoservice_stop(chan);
179         }
180         
181         /* Save old info */
182         oldpriority = chan->priority;
183         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
184         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
185         if (ast_strlen_zero(chan->macrocontext)) {
186                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
187                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
188                 chan->macropriority = chan->priority;
189                 setmacrocontext=1;
190         }
191         argc = 1;
192         /* Save old macro variables */
193         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
194         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
195
196         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
197         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
198
199         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
200         snprintf(pc, sizeof(pc), "%d", oldpriority);
201         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
202   
203         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
204         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
205
206         /* Setup environment for new run */
207         chan->exten[0] = 's';
208         chan->exten[1] = '\0';
209         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
210         chan->priority = 1;
211
212         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
213                 const char *s;
214                 /* Save copy of old arguments if we're overwriting some, otherwise
215                 let them pass through to the other macro */
216                 snprintf(varname, sizeof(varname), "ARG%d", argc);
217                 s = pbx_builtin_getvar_helper(chan, varname);
218                 if (s)
219                         oldargs[argc] = ast_strdup(s);
220                 pbx_builtin_setvar_helper(chan, varname, cur);
221                 argc++;
222         }
223         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
224         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
225         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
226                 /* Reset the macro depth, if it was changed in the last iteration */
227                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
228                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
229                         /* Something bad happened, or a hangup has been requested. */
230                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
231                         (res == '*') || (res == '#')) {
232                                 /* Just return result as to the previous application as if it had been dialed */
233                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
234                                 break;
235                         }
236                         switch(res) {
237                         case MACRO_EXIT_RESULT:
238                                 res = 0;
239                                 goto out;
240                         case AST_PBX_KEEPALIVE:
241                                 if (option_debug)
242                                         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);
243                                 else if (option_verbose > 1)
244                                         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);
245                                 goto out;
246                                 break;
247                         default:
248                                 if (option_debug)
249                                         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);
250                                 else if (option_verbose > 1)
251                                         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);
252                                 dead = 1;
253                                 goto out;
254                         }
255                 }
256                 if (strcasecmp(chan->context, fullmacro)) {
257                         if (option_verbose > 1)
258                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
259                         break;
260                 }
261                 /* don't stop executing extensions when we're in "h" */
262                 if (chan->_softhangup && strcasecmp(chan->macroexten,"h")) {
263                         ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
264                                 chan->exten, chan->macroexten, chan->priority);
265                         goto out;
266                 }
267                 chan->priority++;
268         }
269         out:
270         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
271         snprintf(depthc, sizeof(depthc), "%d", depth);
272         if (!dead) {
273                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
274                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
275         }
276
277         for (x = 1; x < argc; x++) {
278                 /* Restore old arguments and delete ours */
279                 snprintf(varname, sizeof(varname), "ARG%d", x);
280                 if (oldargs[x]) {
281                         if (!dead)
282                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
283                         free(oldargs[x]);
284                 } else if (!dead) {
285                         pbx_builtin_setvar_helper(chan, varname, NULL);
286                 }
287         }
288
289         /* Restore macro variables */
290         if (!dead) {
291                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
292                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
293                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
294         }
295         if (save_macro_exten)
296                 free(save_macro_exten);
297         if (save_macro_context)
298                 free(save_macro_context);
299         if (save_macro_priority)
300                 free(save_macro_priority);
301
302         if (!dead && setmacrocontext) {
303                 chan->macrocontext[0] = '\0';
304                 chan->macroexten[0] = '\0';
305                 chan->macropriority = 0;
306         }
307
308         if (!dead && !strcasecmp(chan->context, fullmacro)) {
309                 /* If we're leaving the macro normally, restore original information */
310                 chan->priority = oldpriority;
311                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
312                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
313                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
314                         const char *offsets;
315                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
316                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
317                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
318                                 normally if there is any problem */
319                                 if (sscanf(offsets, "%d", &offset) == 1) {
320                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
321                                                 chan->priority += offset;
322                                         }
323                                 }
324                         }
325                 }
326         }
327
328         if (!dead)
329                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
330         if (save_macro_offset)
331                 free(save_macro_offset);
332
333         /* Unlock the macro */
334         if (exclusive) {
335                 ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
336                 if (ast_context_unlockmacro(fullmacro)) {
337                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
338                         res = 0;
339                 }
340         }
341         
342         LOCAL_USER_REMOVE(u);
343         return res;
344 }
345
346 static int macro_exec(struct ast_channel *chan, void *data)
347 {
348         return _macro_exec(chan, data, 0);
349 }
350
351 static int macroexclusive_exec(struct ast_channel *chan, void *data)
352 {
353         return _macro_exec(chan, data, 1);
354 }
355
356 static int macroif_exec(struct ast_channel *chan, void *data) 
357 {
358         char *expr = NULL, *label_a = NULL, *label_b = NULL;
359         int res = 0;
360         struct localuser *u;
361
362         LOCAL_USER_ADD(u);
363
364         if (!(expr = ast_strdupa(data))) {
365                 LOCAL_USER_REMOVE(u);
366                 return -1;
367         }
368
369         if ((label_a = strchr(expr, '?'))) {
370                 *label_a = '\0';
371                 label_a++;
372                 if ((label_b = strchr(label_a, ':'))) {
373                         *label_b = '\0';
374                         label_b++;
375                 }
376                 if (pbx_checkcondition(expr))
377                         macro_exec(chan, label_a);
378                 else if (label_b) 
379                         macro_exec(chan, label_b);
380         } else
381                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
382
383         LOCAL_USER_REMOVE(u);
384
385         return res;
386 }
387                         
388 static int macro_exit_exec(struct ast_channel *chan, void *data)
389 {
390         return MACRO_EXIT_RESULT;
391 }
392
393 static int unload_module(void *mod)
394 {
395         int res;
396
397         res = ast_unregister_application(if_app);
398         res |= ast_unregister_application(exit_app);
399         res |= ast_unregister_application(app);
400         res |= ast_unregister_application(exclusive_app);
401
402         STANDARD_HANGUP_LOCALUSERS;
403
404         return res;
405 }
406
407 static int load_module(void *mod)
408 {
409         int res;
410
411         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
412         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
413         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
414         res |= ast_register_application(app, macro_exec, synopsis, descrip);
415
416         return res;
417 }
418
419 static const char *description(void)
420 {
421         return tdesc;
422 }
423
424 static const char *key(void)
425 {
426         return ASTERISK_GPL_KEY;
427 }
428
429 STD_MOD1;