21f65f1ab42b5cea49afe154742a17b18ceea4e2
[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 "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <sys/types.h>
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 *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 "Extensions: While a macro is being executed, it becomes the current context.\n"
65 "            This means that if a hangup occurs, for instance, that the macro\n"
66 "            will be searched for an 'h' extension, NOT the context from which\n"
67 "            the macro was called. So, make sure to define all appropriate\n"
68 "            extensions in your macro! (Note: AEL does not use macros)\n"
69 "WARNING: Because of the way Macro is implemented (it executes the priorities\n"
70 "         contained within it via sub-engine), and a fixed per-thread\n"
71 "         memory stack allowance, macros are limited to 7 levels\n"
72 "         of nesting (macro calling macro calling macro, etc.); It\n"
73 "         may be possible that stack-intensive applications in deeply nested macros\n"
74 "         could cause asterisk to crash earlier than this limit. It is advised that\n"
75 "         if you need to deeply nest macro calls, that you use the Gosub application\n"
76 "         (now allows arguments like a Macro) with explict Return() calls instead.\n";
77
78 static char *if_descrip =
79 "  MacroIf(<expr>?macroname_a[|arg1][:macroname_b[|arg1]])\n"
80 "Executes macro defined in <macroname_a> if <expr> is true\n"
81 "(otherwise <macroname_b> if provided)\n"
82 "Arguments and return values as in application macro()\n";
83
84 static char *exclusive_descrip =
85 "  MacroExclusive(macroname|arg1|arg2...):\n"
86 "Executes macro defined in the context 'macro-macroname'\n"
87 "Only one call at a time may run the macro.\n"
88 "(we'll wait if another call is busy executing in the Macro)\n"
89 "Arguments and return values as in application Macro()\n";
90
91 static char *exit_descrip =
92 "  MacroExit():\n"
93 "Causes the currently running macro to exit as if it had\n"
94 "ended normally by running out of priorities to execute.\n"
95 "If used outside a macro, will likely cause unexpected\n"
96 "behavior.\n";
97
98 static char *app = "Macro";
99 static char *if_app = "MacroIf";
100 static char *exclusive_app = "MacroExclusive";
101 static char *exit_app = "MacroExit";
102
103 static char *synopsis = "Macro Implementation";
104 static char *if_synopsis = "Conditional Macro Implementation";
105 static char *exclusive_synopsis = "Exclusive Macro Implementation";
106 static char *exit_synopsis = "Exit From Macro";
107
108
109 static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
110 {
111         struct ast_exten *e;
112         struct ast_include *i;
113         struct ast_context *c2;
114
115         for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
116                 if (ast_extension_match(ast_get_extension_name(e), exten)) {
117                         int needmatch = ast_get_extension_matchcid(e);
118                         if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
119                                 (!needmatch)) {
120                                 /* This is the matching extension we want */
121                                 struct ast_exten *p;
122                                 for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
123                                         if (priority != ast_get_extension_priority(p))
124                                                 continue;
125                                         return p;
126                                 }
127                         }
128                 }
129         }
130
131         /* No match; run through includes */
132         for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
133                 for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
134                         if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
135                                 e = find_matching_priority(c2, exten, priority, callerid);
136                                 if (e)
137                                         return e;
138                         }
139                 }
140         }
141         return NULL;
142 }
143
144 static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
145 {
146         const char *s;
147         char *tmp;
148         char *cur, *rest;
149         char *macro;
150         char fullmacro[80];
151         char varname[80];
152         char runningapp[80], runningdata[1024];
153         char *oldargs[MAX_ARGS + 1] = { NULL, };
154         int argc, x;
155         int res=0;
156         char oldexten[256]="";
157         int oldpriority, gosub_level = 0;
158         char pc[80], depthc[12];
159         char oldcontext[AST_MAX_CONTEXT] = "";
160         const char *inhangupc;
161         int offset, depth = 0, maxdepth = 7;
162         int setmacrocontext=0;
163         int autoloopflag, dead = 0, inhangup = 0;
164   
165         char *save_macro_exten;
166         char *save_macro_context;
167         char *save_macro_priority;
168         char *save_macro_offset;
169         struct ast_module_user *u;
170  
171         if (ast_strlen_zero(data)) {
172                 ast_log(LOG_WARNING, "Macro() requires arguments. See \"show application macro\" for help.\n");
173                 return -1;
174         }
175
176         u = ast_module_user_add(chan);
177
178         /* does the user want a deeper rabbit hole? */
179         s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
180         if (s)
181                 sscanf(s, "%d", &maxdepth);
182
183         /* Count how many levels deep the rabbit hole goes */
184         s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
185         if (s)
186                 sscanf(s, "%d", &depth);
187         /* Used for detecting whether to return when a Macro is called from another Macro after hangup */
188         if (strcmp(chan->exten, "h") == 0)
189                 pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
190         inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
191         if (!ast_strlen_zero(inhangupc))
192                 sscanf(inhangupc, "%d", &inhangup);
193
194         if (depth >= maxdepth) {
195                 ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
196                 ast_module_user_remove(u);
197                 return 0;
198         }
199         snprintf(depthc, sizeof(depthc), "%d", depth + 1);
200         pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
201
202         tmp = ast_strdupa(data);
203         rest = tmp;
204         macro = strsep(&rest, "|");
205         if (ast_strlen_zero(macro)) {
206                 ast_log(LOG_WARNING, "Invalid macro name specified\n");
207                 ast_module_user_remove(u);
208                 return 0;
209         }
210
211         snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
212         if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
213                 if (!ast_context_find(fullmacro)) 
214                         ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
215                 else
216                         ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
217                 ast_module_user_remove(u);
218                 return 0;
219         }
220
221         /* If we are to run the macro exclusively, take the mutex */
222         if (exclusive) {
223                 if (option_debug)
224                         ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro);
225                 ast_autoservice_start(chan);
226                 if (ast_context_lockmacro(fullmacro)) {
227                         ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
228                         ast_autoservice_stop(chan);
229                         ast_module_user_remove(u);
230
231                         return 0;
232                 }
233                 ast_autoservice_stop(chan);
234         }
235         
236         /* Save old info */
237         oldpriority = chan->priority;
238         ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
239         ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
240         if (ast_strlen_zero(chan->macrocontext)) {
241                 ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
242                 ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
243                 chan->macropriority = chan->priority;
244                 setmacrocontext=1;
245         }
246         argc = 1;
247         /* Save old macro variables */
248         save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
249         pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
250
251         save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
252         pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
253
254         save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
255         snprintf(pc, sizeof(pc), "%d", oldpriority);
256         pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
257   
258         save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
259         pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
260
261         /* Setup environment for new run */
262         chan->exten[0] = 's';
263         chan->exten[1] = '\0';
264         ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
265         chan->priority = 1;
266
267         while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
268                 const char *s;
269                 /* Save copy of old arguments if we're overwriting some, otherwise
270                 let them pass through to the other macro */
271                 snprintf(varname, sizeof(varname), "ARG%d", argc);
272                 s = pbx_builtin_getvar_helper(chan, varname);
273                 if (s)
274                         oldargs[argc] = ast_strdup(s);
275                 pbx_builtin_setvar_helper(chan, varname, cur);
276                 argc++;
277         }
278         autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
279         ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
280         while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
281                 struct ast_context *c;
282                 struct ast_exten *e;
283                 runningapp[0] = '\0';
284                 runningdata[0] = '\0';
285
286                 /* What application will execute? */
287                 if (ast_rdlock_contexts()) {
288                         ast_log(LOG_WARNING, "Failed to lock contexts list\n");
289                 } else {
290                         for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
291                                 if (!strcmp(ast_get_context_name(c), chan->context)) {
292                                         if (ast_rdlock_context(c)) {
293                                                 ast_log(LOG_WARNING, "Unable to lock context?\n");
294                                         } else {
295                                                 e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
296                                                 if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
297                                                         ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
298                                                         ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
299                                                 }
300                                                 ast_unlock_context(c);
301                                         }
302                                         break;
303                                 }
304                         }
305                 }
306                 ast_unlock_contexts();
307
308                 /* Reset the macro depth, if it was changed in the last iteration */
309                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
310
311                 if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num))) {
312                         /* Something bad happened, or a hangup has been requested. */
313                         if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
314                         (res == '*') || (res == '#')) {
315                                 /* Just return result as to the previous application as if it had been dialed */
316                                 if (option_debug)
317                                         ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
318                                 break;
319                         }
320                         switch(res) {
321                         case MACRO_EXIT_RESULT:
322                                 res = 0;
323                                 goto out;
324                         case AST_PBX_KEEPALIVE:
325                                 if (option_debug)
326                                         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);
327                                 else if (option_verbose > 1)
328                                         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);
329                                 goto out;
330                                 break;
331                         default:
332                                 if (option_debug)
333                                         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);
334                                 else if (option_verbose > 1)
335                                         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);
336                                 dead = 1;
337                                 goto out;
338                         }
339                 }
340
341                 ast_log(LOG_DEBUG, "Executed application: %s\n", runningapp);
342
343                 if (!strcasecmp(runningapp, "GOSUB")) {
344                         gosub_level++;
345                         ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
346                 } else if (!strcasecmp(runningapp, "GOSUBIF")) {
347                         char tmp2[1024] = "", *cond, *app, *app2 = tmp2;
348                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
349                         cond = strsep(&app2, "?");
350                         app = strsep(&app2, ":");
351                         if (pbx_checkcondition(cond)) {
352                                 if (!ast_strlen_zero(app)) {
353                                         gosub_level++;
354                                         ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
355                                 }
356                         } else {
357                                 if (!ast_strlen_zero(app2)) {
358                                         gosub_level++;
359                                         ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
360                                 }
361                         }
362                 } else if (!strcasecmp(runningapp, "RETURN")) {
363                         gosub_level--;
364                         ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
365                 } else if (!strcasecmp(runningapp, "STACKPOP")) {
366                         gosub_level--;
367                         ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
368                 } else if (!strncasecmp(runningapp, "EXEC", 4)) {
369                         /* Must evaluate args to find actual app */
370                         char tmp2[1024] = "", *tmp3 = NULL;
371                         pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
372                         if (!strcasecmp(runningapp, "EXECIF")) {
373                                 tmp3 = strchr(tmp2, '|');
374                                 if (tmp3)
375                                         *tmp3++ = '\0';
376                                 if (!pbx_checkcondition(tmp2))
377                                         tmp3 = NULL;
378                         } else
379                                 tmp3 = tmp2;
380
381                         if (tmp3)
382                                 ast_log(LOG_DEBUG, "Last app: %s\n", tmp3);
383
384                         if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
385                                 gosub_level++;
386                                 ast_log(LOG_DEBUG, "Incrementing gosub_level\n");
387                         } else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
388                                 gosub_level--;
389                                 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
390                         } else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
391                                 gosub_level--;
392                                 ast_log(LOG_DEBUG, "Decrementing gosub_level\n");
393                         }
394                 }
395
396                 if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
397                         if (option_verbose > 1)
398                                 ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
399                         break;
400                 }
401
402                 /* don't stop executing extensions when we're in "h" */
403                 if (chan->_softhangup && !inhangup) {
404                         if (option_debug)
405                                 ast_log(LOG_DEBUG, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
406                                         chan->exten, chan->macroexten, chan->priority);
407                         goto out;
408                 }
409                 chan->priority++;
410         }
411         out:
412         /* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
413         snprintf(depthc, sizeof(depthc), "%d", depth);
414         if (!dead) {
415                 pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
416                 ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
417         }
418
419         for (x = 1; x < argc; x++) {
420                 /* Restore old arguments and delete ours */
421                 snprintf(varname, sizeof(varname), "ARG%d", x);
422                 if (oldargs[x]) {
423                         if (!dead)
424                                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
425                         ast_free(oldargs[x]);
426                 } else if (!dead) {
427                         pbx_builtin_setvar_helper(chan, varname, NULL);
428                 }
429         }
430
431         /* Restore macro variables */
432         if (!dead) {
433                 pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
434                 pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
435                 pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
436         }
437         if (save_macro_exten)
438                 ast_free(save_macro_exten);
439         if (save_macro_context)
440                 ast_free(save_macro_context);
441         if (save_macro_priority)
442                 ast_free(save_macro_priority);
443
444         if (!dead && setmacrocontext) {
445                 chan->macrocontext[0] = '\0';
446                 chan->macroexten[0] = '\0';
447                 chan->macropriority = 0;
448         }
449
450         if (!dead && !strcasecmp(chan->context, fullmacro)) {
451                 /* If we're leaving the macro normally, restore original information */
452                 chan->priority = oldpriority;
453                 ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
454                 if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
455                         /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
456                         const char *offsets;
457                         ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
458                         if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
459                                 /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
460                                 normally if there is any problem */
461                                 if (sscanf(offsets, "%d", &offset) == 1) {
462                                         if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
463                                                 chan->priority += offset;
464                                         }
465                                 }
466                         }
467                 }
468         }
469
470         if (!dead)
471                 pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
472         if (save_macro_offset)
473                 ast_free(save_macro_offset);
474
475         /* Unlock the macro */
476         if (exclusive) {
477                 if (option_debug)
478                         ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro);
479                 if (ast_context_unlockmacro(fullmacro)) {
480                         ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
481                         res = 0;
482                 }
483         }
484         
485         ast_module_user_remove(u);
486
487         return res;
488 }
489
490 static int macro_exec(struct ast_channel *chan, void *data)
491 {
492         return _macro_exec(chan, data, 0);
493 }
494
495 static int macroexclusive_exec(struct ast_channel *chan, void *data)
496 {
497         return _macro_exec(chan, data, 1);
498 }
499
500 static int macroif_exec(struct ast_channel *chan, void *data) 
501 {
502         char *expr = NULL, *label_a = NULL, *label_b = NULL;
503         int res = 0;
504         struct ast_module_user *u;
505
506         u = ast_module_user_add(chan);
507
508         if (!(expr = ast_strdupa(data))) {
509                 ast_module_user_remove(u);
510                 return -1;
511         }
512
513         if ((label_a = strchr(expr, '?'))) {
514                 *label_a = '\0';
515                 label_a++;
516                 if ((label_b = strchr(label_a, ':'))) {
517                         *label_b = '\0';
518                         label_b++;
519                 }
520                 if (pbx_checkcondition(expr))
521                         macro_exec(chan, label_a);
522                 else if (label_b) 
523                         macro_exec(chan, label_b);
524         } else
525                 ast_log(LOG_WARNING, "Invalid Syntax.\n");
526
527         ast_module_user_remove(u);
528
529         return res;
530 }
531                         
532 static int macro_exit_exec(struct ast_channel *chan, void *data)
533 {
534         return MACRO_EXIT_RESULT;
535 }
536
537 static int unload_module(void)
538 {
539         int res;
540
541         res = ast_unregister_application(if_app);
542         res |= ast_unregister_application(exit_app);
543         res |= ast_unregister_application(app);
544         res |= ast_unregister_application(exclusive_app);
545
546         ast_module_user_hangup_all();
547
548         return res;
549 }
550
551 static int load_module(void)
552 {
553         int res;
554
555         res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
556         res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
557         res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
558         res |= ast_register_application(app, macro_exec, synopsis, descrip);
559
560         return res;
561 }
562
563 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");