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