6d99a79ca2b41e9251d17301edcc9589e751c08a
[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 <sys/types.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include "asterisk/file.h"
36 #include "asterisk/logger.h"
37 #include "asterisk/channel.h"
38 #include "asterisk/pbx.h"
39 #include "asterisk/module.h"
40 #include "asterisk/options.h"
41 #include "asterisk/config.h"
42 #include "asterisk/utils.h"
43 #include "asterisk/lock.h"
44
45 #define MAX_ARGS 80
46
47 /* special result value used to force macro exit */
48 #define MACRO_EXIT_RESULT 1024
49
50 static char *tdesc = "Extension Macros";
51
52 static char *descrip =
53 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
54 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
55 "executing each step, then returning when the steps end. \n"
56 "The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
57 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
58 "${ARG1}, ${ARG2}, etc in the macro context.\n"
59 "If you Goto out of the Macro context, the Macro will terminate and control\n"
60 "will be returned at the location of the Goto.\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 (ast_strlen_zero(data)) {
115                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
116                 return -1;
117         }
118
119         LOCAL_USER_ADD(u);
120
121         /* Count how many levels deep the rabbit hole goes */
122         tmp = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
123         if (tmp) {
124                 sscanf(tmp, "%d", &depth);
125         } else {
126                 depth = 0;
127         }
128
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 = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
168         if (save_macro_exten) 
169                 save_macro_exten = strdup(save_macro_exten);
170         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
171
172         save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
173         if (save_macro_context)
174                 save_macro_context = strdup(save_macro_context);
175         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
176
177         save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
178         if (save_macro_priority) 
179                 save_macro_priority = strdup(save_macro_priority);
180         snprintf(pc, sizeof(pc), "%d", oldpriority);
181         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
182   
183         save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
184         if (save_macro_offset) 
185                 save_macro_offset = strdup(save_macro_offset);
186         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
187
188         /* Setup environment for new run */
189         chan->exten[0] = 's';
190         chan->exten[1] = '\0';
191         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
192         chan->priority = 1;
193
194         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
195                 /* Save copy of old arguments if we're overwriting some, otherwise
196                 let them pass through to the other macro */
197                 snprintf(varname, sizeof(varname), "ARG%d", argc);
198                 oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
199                 if (oldargs[argc])
200                         oldargs[argc] = strdup(oldargs[argc]);
201                 pbx_builtin_setvar_helper(chan, varname, cur);
202                 argc++;
203         }
204         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
205         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
206         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
207                 /* Reset the macro depth, if it was changed in the last iteration */
208                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
209                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
210                         /* Something bad happened, or a hangup has been requested. */
211                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
212                         (res == '*') || (res == '#')) {
213                                 /* Just return result as to the previous application as if it had been dialed */
214                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
215                                 break;
216                         }
217                         switch(res) {
218                         case MACRO_EXIT_RESULT:
219                                 res = 0;
220                                 goto out;
221                         case AST_PBX_KEEPALIVE:
222                                 if (option_debug)
223                                         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);
224                                 else if (option_verbose > 1)
225                                         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);
226                                 goto out;
227                                 break;
228                         default:
229                                 if (option_debug)
230                                         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);
231                                 else if (option_verbose > 1)
232                                         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);
233                                 goto out;
234                         }
235                 }
236                 if (strcasecmp(chan->context, fullmacro)) {
237                         if (option_verbose > 1)
238                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
239                         break;
240                 }
241                 /* don't stop executing extensions when we're in "h" */
242                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
243                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
244                                 chan->exten, chan->priority);
245                         goto out;
246                 }
247                 chan->priority++;
248         }
249         out:
250         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
251         snprintf(depthc, sizeof(depthc), "%d", depth);
252         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
253
254         ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
255         for (x=1; x<argc; x++) {
256                 /* Restore old arguments and delete ours */
257                 snprintf(varname, sizeof(varname), "ARG%d", x);
258                 if (oldargs[x]) {
259                         pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
260                         free(oldargs[x]);
261                 } else {
262                         pbx_builtin_setvar_helper(chan, varname, NULL);
263                 }
264         }
265
266         /* Restore macro variables */
267         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
268         if (save_macro_exten)
269                 free(save_macro_exten);
270         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
271         if (save_macro_context)
272                 free(save_macro_context);
273         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
274         if (save_macro_priority)
275                 free(save_macro_priority);
276         if (setmacrocontext) {
277                 chan->macrocontext[0] = '\0';
278                 chan->macroexten[0] = '\0';
279                 chan->macropriority = 0;
280         }
281
282         if (!strcasecmp(chan->context, fullmacro)) {
283                 /* If we're leaving the macro normally, restore original information */
284                 chan->priority = oldpriority;
285                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
286                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
287                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
288                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
289                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
290                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
291                                 normally if there is any problem */
292                                 if (sscanf(offsets, "%d", &offset) == 1) {
293                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
294                                                 chan->priority += offset;
295                                         }
296                                 }
297                         }
298                 }
299         }
300
301         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
302         if (save_macro_offset)
303                 free(save_macro_offset);
304         LOCAL_USER_REMOVE(u);
305         return res;
306 }
307
308 static int macroif_exec(struct ast_channel *chan, void *data) 
309 {
310         char *expr = NULL, *label_a = NULL, *label_b = NULL;
311         int res = 0;
312         struct localuser *u;
313
314         LOCAL_USER_ADD(u);
315
316         expr = ast_strdupa(data);
317         if (!expr) {
318                 ast_log(LOG_ERROR, "Out of Memory!\n");
319                 LOCAL_USER_REMOVE(u);
320                 return -1;
321         }
322
323         if ((label_a = strchr(expr, '?'))) {
324                 *label_a = '\0';
325                 label_a++;
326                 if ((label_b = strchr(label_a, ':'))) {
327                         *label_b = '\0';
328                         label_b++;
329                 }
330                 if (ast_true(expr))
331                         macro_exec(chan, label_a);
332                 else if (label_b) 
333                         macro_exec(chan, label_b);
334         } else
335                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
336
337         LOCAL_USER_REMOVE(u);
338
339         return res;
340 }
341                         
342 static int macro_exit_exec(struct ast_channel *chan, void *data)
343 {
344         return MACRO_EXIT_RESULT;
345 }
346
347 int unload_module(void)
348 {
349         int res;
350
351         res = ast_unregister_application(if_app);
352         res |= ast_unregister_application(exit_app);
353         res |= ast_unregister_application(app);
354
355         STANDARD_HANGUP_LOCALUSERS;
356
357         return res;
358 }
359
360 int load_module(void)
361 {
362         int res;
363
364         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
365         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
366         res |= ast_register_application(app, macro_exec, synopsis, descrip);
367
368         return res;
369 }
370
371 char *description(void)
372 {
373         return tdesc;
374 }
375
376 int usecount(void)
377 {
378         int res;
379         STANDARD_USECOUNT(res);
380         return res;
381 }
382
383 char *key()
384 {
385         return ASTERISK_GPL_KEY;
386 }