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