Bug 5858 - Make the chanvars.c functions return a 'const char *'
[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         const char *s;
93
94         char *tmp;
95         char *cur, *rest;
96         char *macro;
97         char fullmacro[80];
98         char varname[80];
99         char *oldargs[MAX_ARGS + 1] = { NULL, };
100         int argc, x;
101         int res=0;
102         char oldexten[256]="";
103         int oldpriority;
104         char pc[80], depthc[12];
105         char oldcontext[AST_MAX_CONTEXT] = "";
106         int offset, depth = 0;
107         int setmacrocontext=0;
108         int autoloopflag;
109   
110         char *save_macro_exten;
111         char *save_macro_context;
112         char *save_macro_priority;
113         char *save_macro_offset;
114         struct localuser *u;
115  
116         if (ast_strlen_zero(data)) {
117                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
118                 return -1;
119         }
120
121         LOCAL_USER_ADD(u);
122
123         /* Count how many levels deep the rabbit hole goes */
124         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
125         if (s)
126                 sscanf(s, "%d", &depth);
127         if (depth >= 7) {
128                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
129                 LOCAL_USER_REMOVE(u);
130                 return 0;
131         }
132         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
133         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
134
135         tmp = ast_strdupa(data);
136         rest = tmp;
137         macro = strsep(&rest, "|");
138         if (ast_strlen_zero(macro)) {
139                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
140                 LOCAL_USER_REMOVE(u);
141                 return 0;
142         }
143         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
144         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
145                 if (!ast_context_find(fullmacro)) 
146                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
147                 else
148                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
149                 LOCAL_USER_REMOVE(u);
150                 return 0;
151         }
152         
153         /* Save old info */
154         oldpriority = chan->priority;
155         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
156         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
157         if (ast_strlen_zero(chan->macrocontext)) {
158                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
159                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
160                 chan->macropriority = chan->priority;
161                 setmacrocontext=1;
162         }
163         argc = 1;
164         /* Save old macro variables */
165         save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
166         if (save_macro_exten) 
167                 save_macro_exten = strdup(save_macro_exten);
168         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
169
170         save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
171         if (save_macro_context)
172                 save_macro_context = strdup(save_macro_context);
173         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
174
175         save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
176         if (save_macro_priority) 
177                 save_macro_priority = strdup(save_macro_priority);
178         snprintf(pc, sizeof(pc), "%d", oldpriority);
179         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
180   
181         save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
182         if (save_macro_offset) 
183                 save_macro_offset = strdup(save_macro_offset);
184         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
185
186         /* Setup environment for new run */
187         chan->exten[0] = 's';
188         chan->exten[1] = '\0';
189         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
190         chan->priority = 1;
191
192         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
193                 const char *s;
194                 /* Save copy of old arguments if we're overwriting some, otherwise
195                 let them pass through to the other macro */
196                 snprintf(varname, sizeof(varname), "ARG%d", argc);
197                 s = pbx_builtin_getvar_helper(chan, varname);
198                 if (s)
199                         oldargs[argc] = strdup(s);
200                 pbx_builtin_setvar_helper(chan, varname, cur);
201                 argc++;
202         }
203         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
204         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
205         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
206                 /* Reset the macro depth, if it was changed in the last iteration */
207                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
208                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
209                         /* Something bad happened, or a hangup has been requested. */
210                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
211                         (res == '*') || (res == '#')) {
212                                 /* Just return result as to the previous application as if it had been dialed */
213                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
214                                 break;
215                         }
216                         switch(res) {
217                         case MACRO_EXIT_RESULT:
218                                 res = 0;
219                                 goto out;
220                         case AST_PBX_KEEPALIVE:
221                                 if (option_debug)
222                                         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);
223                                 else if (option_verbose > 1)
224                                         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);
225                                 goto out;
226                                 break;
227                         default:
228                                 if (option_debug)
229                                         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);
230                                 else if (option_verbose > 1)
231                                         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);
232                                 goto out;
233                         }
234                 }
235                 if (strcasecmp(chan->context, fullmacro)) {
236                         if (option_verbose > 1)
237                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
238                         break;
239                 }
240                 /* don't stop executing extensions when we're in "h" */
241                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
242                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
243                                 chan->exten, chan->priority);
244                         goto out;
245                 }
246                 chan->priority++;
247         }
248         out:
249         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
250         snprintf(depthc, sizeof(depthc), "%d", depth);
251         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
252
253         ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
254         for (x=1; x<argc; x++) {
255                 /* Restore old arguments and delete ours */
256                 snprintf(varname, sizeof(varname), "ARG%d", x);
257                 if (oldargs[x]) {
258                         pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
259                         free(oldargs[x]);
260                 } else {
261                         pbx_builtin_setvar_helper(chan, varname, NULL);
262                 }
263         }
264
265         /* Restore macro variables */
266         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
267         if (save_macro_exten)
268                 free(save_macro_exten);
269         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
270         if (save_macro_context)
271                 free(save_macro_context);
272         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
273         if (save_macro_priority)
274                 free(save_macro_priority);
275         if (setmacrocontext) {
276                 chan->macrocontext[0] = '\0';
277                 chan->macroexten[0] = '\0';
278                 chan->macropriority = 0;
279         }
280
281         if (!strcasecmp(chan->context, fullmacro)) {
282                 /* If we're leaving the macro normally, restore original information */
283                 chan->priority = oldpriority;
284                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
285                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
286                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
287                         const char *offsets;
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 }