Merged revisions 9156 via svnmerge from
[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 <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33
34 #include "asterisk.h"
35
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
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 *exit_descrip =
74 "  MacroExit():\n"
75 "Causes the currently running macro to exit as if it had\n"
76 "ended normally by running out of priorities to execute.\n"
77 "If used outside a macro, will likely cause unexpected\n"
78 "behavior.\n";
79
80 static char *app = "Macro";
81 static char *if_app = "MacroIf";
82 static char *exit_app = "MacroExit";
83
84 static char *synopsis = "Macro Implementation";
85 static char *if_synopsis = "Conditional Macro Implementation";
86 static char *exit_synopsis = "Exit From Macro";
87
88 STANDARD_LOCAL_USER;
89
90 LOCAL_USER_DECL;
91
92 static int macro_exec(struct ast_channel *chan, void *data)
93 {
94         const char *s;
95
96         char *tmp;
97         char *cur, *rest;
98         char *macro;
99         char fullmacro[80];
100         char varname[80];
101         char *oldargs[MAX_ARGS + 1] = { NULL, };
102         int argc, x;
103         int res=0;
104         char oldexten[256]="";
105         int oldpriority;
106         char pc[80], depthc[12];
107         char oldcontext[AST_MAX_CONTEXT] = "";
108         int offset, depth = 0;
109         int setmacrocontext=0;
110         int autoloopflag, dead = 0;
111   
112         char *save_macro_exten;
113         char *save_macro_context;
114         char *save_macro_priority;
115         char *save_macro_offset;
116         struct localuser *u;
117  
118         if (ast_strlen_zero(data)) {
119                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
120                 return -1;
121         }
122
123         LOCAL_USER_ADD(u);
124
125         /* Count how many levels deep the rabbit hole goes */
126         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
127         if (s)
128                 sscanf(s, "%d", &depth);
129         if (depth >= 7) {
130                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
131                 LOCAL_USER_REMOVE(u);
132                 return 0;
133         }
134         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
135         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
136
137         tmp = ast_strdupa(data);
138         rest = tmp;
139         macro = strsep(&rest, "|");
140         if (ast_strlen_zero(macro)) {
141                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
142                 LOCAL_USER_REMOVE(u);
143                 return 0;
144         }
145         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
146         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
147                 if (!ast_context_find(fullmacro)) 
148                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
149                 else
150                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
151                 LOCAL_USER_REMOVE(u);
152                 return 0;
153         }
154         
155         /* Save old info */
156         oldpriority = chan->priority;
157         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
158         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
159         if (ast_strlen_zero(chan->macrocontext)) {
160                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
161                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
162                 chan->macropriority = chan->priority;
163                 setmacrocontext=1;
164         }
165         argc = 1;
166         /* Save old macro variables */
167         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
168         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
169
170         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
171         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
172
173         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
174         snprintf(pc, sizeof(pc), "%d", oldpriority);
175         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
176   
177         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
178         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
179
180         /* Setup environment for new run */
181         chan->exten[0] = 's';
182         chan->exten[1] = '\0';
183         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
184         chan->priority = 1;
185
186         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
187                 const char *s;
188                 /* Save copy of old arguments if we're overwriting some, otherwise
189                 let them pass through to the other macro */
190                 snprintf(varname, sizeof(varname), "ARG%d", argc);
191                 s = pbx_builtin_getvar_helper(chan, varname);
192                 if (s)
193                         oldargs[argc] = strdup(s);
194                 pbx_builtin_setvar_helper(chan, varname, cur);
195                 argc++;
196         }
197         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
198         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
199         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
200                 /* Reset the macro depth, if it was changed in the last iteration */
201                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
202                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
203                         /* Something bad happened, or a hangup has been requested. */
204                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
205                         (res == '*') || (res == '#')) {
206                                 /* Just return result as to the previous application as if it had been dialed */
207                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
208                                 break;
209                         }
210                         switch(res) {
211                         case MACRO_EXIT_RESULT:
212                                 res = 0;
213                                 goto out;
214                         case AST_PBX_KEEPALIVE:
215                                 if (option_debug)
216                                         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);
217                                 else if (option_verbose > 1)
218                                         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);
219                                 goto out;
220                                 break;
221                         default:
222                                 if (option_debug)
223                                         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);
224                                 else if (option_verbose > 1)
225                                         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);
226                                 dead = 1;
227                                 goto out;
228                         }
229                 }
230                 if (strcasecmp(chan->context, fullmacro)) {
231                         if (option_verbose > 1)
232                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
233                         break;
234                 }
235                 /* don't stop executing extensions when we're in "h" */
236                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
237                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
238                                 chan->exten, chan->priority);
239                         goto out;
240                 }
241                 chan->priority++;
242         }
243         out:
244         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
245         snprintf(depthc, sizeof(depthc), "%d", depth);
246         if (!dead) {
247                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
248
249                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
250         }
251
252         for (x = 1; x < argc; x++) {
253                 /* Restore old arguments and delete ours */
254                 snprintf(varname, sizeof(varname), "ARG%d", x);
255                 if (oldargs[x]) {
256                         if (!dead)
257                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
258                         free(oldargs[x]);
259                 } else if (!dead) {
260                         pbx_builtin_setvar_helper(chan, varname, NULL);
261                 }
262         }
263
264         /* Restore macro variables */
265         if (!dead) {
266                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
267                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
268                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
269         }
270         if (save_macro_exten)
271                 free(save_macro_exten);
272         if (save_macro_context)
273                 free(save_macro_context);
274         if (save_macro_priority)
275                 free(save_macro_priority);
276
277         if (!dead && setmacrocontext) {
278                 chan->macrocontext[0] = '\0';
279                 chan->macroexten[0] = '\0';
280                 chan->macropriority = 0;
281         }
282
283         if (!dead && !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                         const char *offsets;
290                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
291                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
292                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
293                                 normally if there is any problem */
294                                 if (sscanf(offsets, "%d", &offset) == 1) {
295                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
296                                                 chan->priority += offset;
297                                         }
298                                 }
299                         }
300                 }
301         }
302
303         if (!dead)
304                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
305         if (save_macro_offset)
306                 free(save_macro_offset);
307         LOCAL_USER_REMOVE(u);
308         return res;
309 }
310
311 static int macroif_exec(struct ast_channel *chan, void *data) 
312 {
313         char *expr = NULL, *label_a = NULL, *label_b = NULL;
314         int res = 0;
315         struct localuser *u;
316
317         LOCAL_USER_ADD(u);
318
319         if (!(expr = ast_strdupa(data))) {
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 }