reduce some duplicated code when doing a strdup (issue #5986)
[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 char *safe_strdup(const char *s)
91 {
92         return s ? strdup(s) : NULL;
93 }
94
95 static int macro_exec(struct ast_channel *chan, void *data)
96 {
97         const char *s;
98
99         char *tmp;
100         char *cur, *rest;
101         char *macro;
102         char fullmacro[80];
103         char varname[80];
104         char *oldargs[MAX_ARGS + 1] = { NULL, };
105         int argc, x;
106         int res=0;
107         char oldexten[256]="";
108         int oldpriority;
109         char pc[80], depthc[12];
110         char oldcontext[AST_MAX_CONTEXT] = "";
111         int offset, depth = 0;
112         int setmacrocontext=0;
113         int autoloopflag;
114   
115         char *save_macro_exten;
116         char *save_macro_context;
117         char *save_macro_priority;
118         char *save_macro_offset;
119         struct localuser *u;
120  
121         if (ast_strlen_zero(data)) {
122                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
123                 return -1;
124         }
125
126         LOCAL_USER_ADD(u);
127
128         /* Count how many levels deep the rabbit hole goes */
129         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
130         if (s)
131                 sscanf(s, "%d", &depth);
132         if (depth >= 7) {
133                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
134                 LOCAL_USER_REMOVE(u);
135                 return 0;
136         }
137         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
138         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
139
140         tmp = ast_strdupa(data);
141         rest = tmp;
142         macro = strsep(&rest, "|");
143         if (ast_strlen_zero(macro)) {
144                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
145                 LOCAL_USER_REMOVE(u);
146                 return 0;
147         }
148         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
149         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
150                 if (!ast_context_find(fullmacro)) 
151                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
152                 else
153                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
154                 LOCAL_USER_REMOVE(u);
155                 return 0;
156         }
157         
158         /* Save old info */
159         oldpriority = chan->priority;
160         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
161         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
162         if (ast_strlen_zero(chan->macrocontext)) {
163                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
164                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
165                 chan->macropriority = chan->priority;
166                 setmacrocontext=1;
167         }
168         argc = 1;
169         /* Save old macro variables */
170         save_macro_exten = safe_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
171         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
172
173         save_macro_context = safe_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
174         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
175
176         save_macro_priority = safe_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
177         snprintf(pc, sizeof(pc), "%d", oldpriority);
178         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
179   
180         save_macro_offset = safe_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
181         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
182
183         /* Setup environment for new run */
184         chan->exten[0] = 's';
185         chan->exten[1] = '\0';
186         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
187         chan->priority = 1;
188
189         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
190                 const char *s;
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                 s = pbx_builtin_getvar_helper(chan, varname);
195                 if (s)
196                         oldargs[argc] = strdup(s);
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                         const char *offsets;
285                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
286                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
287                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
288                                 normally if there is any problem */
289                                 if (sscanf(offsets, "%d", &offset) == 1) {
290                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
291                                                 chan->priority += offset;
292                                         }
293                                 }
294                         }
295                 }
296         }
297
298         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
299         if (save_macro_offset)
300                 free(save_macro_offset);
301         LOCAL_USER_REMOVE(u);
302         return res;
303 }
304
305 static int macroif_exec(struct ast_channel *chan, void *data) 
306 {
307         char *expr = NULL, *label_a = NULL, *label_b = NULL;
308         int res = 0;
309         struct localuser *u;
310
311         LOCAL_USER_ADD(u);
312
313         expr = ast_strdupa(data);
314         if (!expr) {
315                 ast_log(LOG_ERROR, "Out of Memory!\n");
316                 LOCAL_USER_REMOVE(u);
317                 return -1;
318         }
319
320         if ((label_a = strchr(expr, '?'))) {
321                 *label_a = '\0';
322                 label_a++;
323                 if ((label_b = strchr(label_a, ':'))) {
324                         *label_b = '\0';
325                         label_b++;
326                 }
327                 if (ast_true(expr))
328                         macro_exec(chan, label_a);
329                 else if (label_b) 
330                         macro_exec(chan, label_b);
331         } else
332                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
333
334         LOCAL_USER_REMOVE(u);
335
336         return res;
337 }
338                         
339 static int macro_exit_exec(struct ast_channel *chan, void *data)
340 {
341         return MACRO_EXIT_RESULT;
342 }
343
344 int unload_module(void)
345 {
346         int res;
347
348         res = ast_unregister_application(if_app);
349         res |= ast_unregister_application(exit_app);
350         res |= ast_unregister_application(app);
351
352         STANDARD_HANGUP_LOCALUSERS;
353
354         return res;
355 }
356
357 int load_module(void)
358 {
359         int res;
360
361         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
362         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
363         res |= ast_register_application(app, macro_exec, synopsis, descrip);
364
365         return res;
366 }
367
368 char *description(void)
369 {
370         return tdesc;
371 }
372
373 int usecount(void)
374 {
375         int res;
376         STANDARD_USECOUNT(res);
377         return res;
378 }
379
380 char *key()
381 {
382         return ASTERISK_GPL_KEY;
383 }