remove lots of useless checks of the result of ast_strdupa
[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;
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                                 goto out;
227                         }
228                 }
229                 if (strcasecmp(chan->context, fullmacro)) {
230                         if (option_verbose > 1)
231                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
232                         break;
233                 }
234                 /* don't stop executing extensions when we're in "h" */
235                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
236                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
237                                 chan->exten, chan->priority);
238                         goto out;
239                 }
240                 chan->priority++;
241         }
242         out:
243         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
244         snprintf(depthc, sizeof(depthc), "%d", depth);
245         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
246
247         ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
248         for (x=1; x<argc; x++) {
249                 /* Restore old arguments and delete ours */
250                 snprintf(varname, sizeof(varname), "ARG%d", x);
251                 if (oldargs[x]) {
252                         pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
253                         free(oldargs[x]);
254                 } else {
255                         pbx_builtin_setvar_helper(chan, varname, NULL);
256                 }
257         }
258
259         /* Restore macro variables */
260         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
261         if (save_macro_exten)
262                 free(save_macro_exten);
263         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
264         if (save_macro_context)
265                 free(save_macro_context);
266         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
267         if (save_macro_priority)
268                 free(save_macro_priority);
269         if (setmacrocontext) {
270                 chan->macrocontext[0] = '\0';
271                 chan->macroexten[0] = '\0';
272                 chan->macropriority = 0;
273         }
274
275         if (!strcasecmp(chan->context, fullmacro)) {
276                 /* If we're leaving the macro normally, restore original information */
277                 chan->priority = oldpriority;
278                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
279                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
280                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
281                         const char *offsets;
282                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
283                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
284                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
285                                 normally if there is any problem */
286                                 if (sscanf(offsets, "%d", &offset) == 1) {
287                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
288                                                 chan->priority += offset;
289                                         }
290                                 }
291                         }
292                 }
293         }
294
295         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
296         if (save_macro_offset)
297                 free(save_macro_offset);
298         LOCAL_USER_REMOVE(u);
299         return res;
300 }
301
302 static int macroif_exec(struct ast_channel *chan, void *data) 
303 {
304         char *expr = NULL, *label_a = NULL, *label_b = NULL;
305         int res = 0;
306         struct localuser *u;
307
308         LOCAL_USER_ADD(u);
309
310         expr = ast_strdupa(data);
311
312         if ((label_a = strchr(expr, '?'))) {
313                 *label_a = '\0';
314                 label_a++;
315                 if ((label_b = strchr(label_a, ':'))) {
316                         *label_b = '\0';
317                         label_b++;
318                 }
319                 if (ast_true(expr))
320                         macro_exec(chan, label_a);
321                 else if (label_b) 
322                         macro_exec(chan, label_b);
323         } else
324                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
325
326         LOCAL_USER_REMOVE(u);
327
328         return res;
329 }
330                         
331 static int macro_exit_exec(struct ast_channel *chan, void *data)
332 {
333         return MACRO_EXIT_RESULT;
334 }
335
336 int unload_module(void)
337 {
338         int res;
339
340         res = ast_unregister_application(if_app);
341         res |= ast_unregister_application(exit_app);
342         res |= ast_unregister_application(app);
343
344         STANDARD_HANGUP_LOCALUSERS;
345
346         return res;
347 }
348
349 int load_module(void)
350 {
351         int res;
352
353         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
354         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
355         res |= ast_register_application(app, macro_exec, synopsis, descrip);
356
357         return res;
358 }
359
360 char *description(void)
361 {
362         return tdesc;
363 }
364
365 int usecount(void)
366 {
367         int res;
368         STANDARD_USECOUNT(res);
369         return res;
370 }
371
372 char *key()
373 {
374         return ASTERISK_GPL_KEY;
375 }