remove the uses of the deprecated STANDARD_LOCAL_USER
[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 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, dead = 0;
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 = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
166         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
167
168         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
169         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
170
171         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
172         snprintf(pc, sizeof(pc), "%d", oldpriority);
173         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
174   
175         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
176         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
177
178         /* Setup environment for new run */
179         chan->exten[0] = 's';
180         chan->exten[1] = '\0';
181         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
182         chan->priority = 1;
183
184         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
185                 const char *s;
186                 /* Save copy of old arguments if we're overwriting some, otherwise
187                 let them pass through to the other macro */
188                 snprintf(varname, sizeof(varname), "ARG%d", argc);
189                 s = pbx_builtin_getvar_helper(chan, varname);
190                 if (s)
191                         oldargs[argc] = strdup(s);
192                 pbx_builtin_setvar_helper(chan, varname, cur);
193                 argc++;
194         }
195         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
196         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
197         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
198                 /* Reset the macro depth, if it was changed in the last iteration */
199                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
200                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
201                         /* Something bad happened, or a hangup has been requested. */
202                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
203                         (res == '*') || (res == '#')) {
204                                 /* Just return result as to the previous application as if it had been dialed */
205                                 ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
206                                 break;
207                         }
208                         switch(res) {
209                         case MACRO_EXIT_RESULT:
210                                 res = 0;
211                                 goto out;
212                         case AST_PBX_KEEPALIVE:
213                                 if (option_debug)
214                                         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);
215                                 else if (option_verbose > 1)
216                                         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);
217                                 goto out;
218                                 break;
219                         default:
220                                 if (option_debug)
221                                         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);
222                                 else if (option_verbose > 1)
223                                         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);
224                                 dead = 1;
225                                 goto out;
226                         }
227                 }
228                 if (strcasecmp(chan->context, fullmacro)) {
229                         if (option_verbose > 1)
230                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
231                         break;
232                 }
233                 /* don't stop executing extensions when we're in "h" */
234                 if (chan->_softhangup && strcasecmp(oldexten,"h")) {
235                         ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
236                                 chan->exten, chan->priority);
237                         goto out;
238                 }
239                 chan->priority++;
240         }
241         out:
242         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
243         snprintf(depthc, sizeof(depthc), "%d", depth);
244         if (!dead) {
245                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
246
247                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
248         }
249
250         for (x = 1; x < argc; x++) {
251                 /* Restore old arguments and delete ours */
252                 snprintf(varname, sizeof(varname), "ARG%d", x);
253                 if (oldargs[x]) {
254                         if (!dead)
255                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
256                         free(oldargs[x]);
257                 } else if (!dead) {
258                         pbx_builtin_setvar_helper(chan, varname, NULL);
259                 }
260         }
261
262         /* Restore macro variables */
263         if (!dead) {
264                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
265                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
266                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
267         }
268         if (save_macro_exten)
269                 free(save_macro_exten);
270         if (save_macro_context)
271                 free(save_macro_context);
272         if (save_macro_priority)
273                 free(save_macro_priority);
274
275         if (!dead && setmacrocontext) {
276                 chan->macrocontext[0] = '\0';
277                 chan->macroexten[0] = '\0';
278                 chan->macropriority = 0;
279         }
280
281         if (!dead && !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         if (!dead)
302                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
303         if (save_macro_offset)
304                 free(save_macro_offset);
305         LOCAL_USER_REMOVE(u);
306         return res;
307 }
308
309 static int macroif_exec(struct ast_channel *chan, void *data) 
310 {
311         char *expr = NULL, *label_a = NULL, *label_b = NULL;
312         int res = 0;
313         struct localuser *u;
314
315         LOCAL_USER_ADD(u);
316
317         if (!(expr = ast_strdupa(data))) {
318                 LOCAL_USER_REMOVE(u);
319                 return -1;
320         }
321
322         if ((label_a = strchr(expr, '?'))) {
323                 *label_a = '\0';
324                 label_a++;
325                 if ((label_b = strchr(label_a, ':'))) {
326                         *label_b = '\0';
327                         label_b++;
328                 }
329                 if (ast_true(expr))
330                         macro_exec(chan, label_a);
331                 else if (label_b) 
332                         macro_exec(chan, label_b);
333         } else
334                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
335
336         LOCAL_USER_REMOVE(u);
337
338         return res;
339 }
340                         
341 static int macro_exit_exec(struct ast_channel *chan, void *data)
342 {
343         return MACRO_EXIT_RESULT;
344 }
345
346 int unload_module(void)
347 {
348         int res;
349
350         res = ast_unregister_application(if_app);
351         res |= ast_unregister_application(exit_app);
352         res |= ast_unregister_application(app);
353
354         STANDARD_HANGUP_LOCALUSERS;
355
356         return res;
357 }
358
359 int load_module(void)
360 {
361         int res;
362
363         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
364         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
365         res |= ast_register_application(app, macro_exec, synopsis, descrip);
366
367         return res;
368 }
369
370 char *description(void)
371 {
372         return tdesc;
373 }
374
375 int usecount(void)
376 {
377         int res;
378         STANDARD_USECOUNT(res);
379         return res;
380 }
381
382 char *key()
383 {
384         return ASTERISK_GPL_KEY;
385 }