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