Merge rgagnon's pedantic string checks (apps a-m, bug #2035)
[asterisk/asterisk.git] / apps / app_macro.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Macro Implementation
5  * 
6  * Copyright (C) 2003, Digium
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <sys/types.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/options.h>
21 #include <asterisk/utils.h>
22 #include <asterisk/lock.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <stdlib.h>
27
28 #define MAX_ARGS 80
29
30 static char *tdesc = "Extension Macros";
31
32 static char *descrip =
33 "  Macro(macroname|arg1|arg2...): Executes a macro using the context\n"
34 "'macro-<macroname>', jumping to the 's' extension of that context and\n"
35 "executing each step, then returning when the steps end.  The calling\n"
36 "extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
37 "${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively.  Arguments become\n"
38 "${ARG1}, ${ARG2}, etc in the macro context.  Macro returns -1 if\n"
39 "any step in the macro returns -1, and 0 otherwise.  If you Goto out\n"
40 "of the Macro context, the Macro will terminate and control will be return\n"
41 "at the location of the Goto.  Otherwise if ${MACRO_OFFSET} is set at\n"
42 "termination, Macro will attempt to continue at priority\n"
43 "MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n";
44
45 static char *app = "Macro";
46
47 static char *synopsis = "Macro Implementation";
48
49 STANDARD_LOCAL_USER;
50
51 LOCAL_USER_DECL;
52
53 static int macro_exec(struct ast_channel *chan, void *data)
54 {
55   char tmp[256] = "";
56   char *cur, *rest;
57   char *macro;
58   char fullmacro[80];
59   char varname[80];
60   char *oldargs[MAX_ARGS + 1] = { NULL, };
61   int argc, x;
62   int res=0;
63   char oldexten[256]="";
64   int oldpriority;
65   char pc[80];
66   char oldcontext[256] = "";
67   char *offsets;
68   int offset;
69   int setmacrocontext=0;
70   
71   char *save_macro_exten;
72   char *save_macro_context;
73   char *save_macro_priority;
74   char *save_macro_offset;
75   
76   if (!data || ast_strlen_zero(data)) {
77     ast_log(LOG_WARNING, "Invalid Macro incantation\n");
78     return 0;
79   }
80   strncpy(tmp, data, sizeof(tmp) - 1);
81   rest = tmp;
82   macro = strsep(&rest, "|");
83   if (!macro || ast_strlen_zero(macro)) {
84         ast_log(LOG_WARNING, "Invalid macro name specified\n");
85         return 0;
86   }
87   snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
88   if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->callerid)) {
89         if (!ast_context_find(fullmacro)) 
90                 ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
91         else
92                 ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
93         return 0;
94   }
95   /* Save old info */
96   oldpriority = chan->priority;
97   strncpy(oldexten, chan->exten, sizeof(oldexten) - 1);
98   strncpy(oldcontext, chan->context, sizeof(oldcontext) - 1);
99   if (ast_strlen_zero(chan->macrocontext)) {
100         strncpy(chan->macrocontext, chan->context, sizeof(chan->macrocontext) - 1);
101         strncpy(chan->macroexten, chan->exten, sizeof(chan->macroexten) - 1);
102         chan->macropriority = chan->priority;
103         setmacrocontext=1;
104   }
105   argc = 1;
106   /* Save old macro variables */
107   save_macro_exten = pbx_builtin_getvar_helper(chan, "MACRO_EXTEN");
108   if (save_macro_exten) save_macro_exten = strdup(save_macro_exten);
109   pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
110
111   save_macro_context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT");
112   if (save_macro_context) save_macro_context = strdup(save_macro_context);
113   pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
114
115   save_macro_priority = pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY");
116   if (save_macro_priority) save_macro_priority = strdup(save_macro_priority);
117   snprintf(pc, sizeof(pc), "%d", oldpriority);
118   pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
119   
120   save_macro_offset = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET");
121   if (save_macro_offset) save_macro_offset = strdup(save_macro_offset);
122   pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
123
124   /* Setup environment for new run */
125   chan->exten[0] = 's';
126   chan->exten[1] = '\0';
127   strncpy(chan->context, fullmacro, sizeof(chan->context) - 1);
128   chan->priority = 1;
129
130   while((cur = strsep(&rest, "|")) && (argc < MAX_ARGS)) {
131         /* Save copy of old arguments if we're overwriting some, otherwise
132            let them pass through to the other macro */
133         snprintf(varname, sizeof(varname), "ARG%d", argc);
134         oldargs[argc] = pbx_builtin_getvar_helper(chan, varname);
135         if (oldargs[argc])
136                 oldargs[argc] = strdup(oldargs[argc]);
137         pbx_builtin_setvar_helper(chan, varname, cur);
138         argc++;
139   }
140   while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->callerid)) {
141         if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->callerid))) {
142                 /* Something bad happened, or a hangup has been requested. */
143                 if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F'))) {
144                         /* Just return result as to the previous application as if it had been dialed */
145                         ast_log(LOG_DEBUG, "Oooh, got something to jump out with ('%c')!\n", res);
146                         break;
147                 }
148                 switch(res) {
149                 case AST_PBX_KEEPALIVE:
150                         if (option_debug)
151                                 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);
152                         else if (option_verbose > 1)
153                                 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);
154                         goto out;
155                         break;
156                 default:
157                         if (option_debug)
158                                 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);
159                         else if (option_verbose > 1)
160                                 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);
161                         goto out;
162                 }
163         }
164         if (strcasecmp(chan->context, fullmacro)) {
165                 if (option_verbose > 1)
166                         ast_verbose(VERBOSE_PREFIX_2 "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
167                 break;
168         }
169         /* don't stop executing extensions when we're in "h" */
170         if (chan->_softhangup && strcasecmp(oldexten,"h")) {
171                 ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
172                         chan->exten, chan->priority);
173                 goto out;
174         }
175         chan->priority++;
176   }
177 out:
178   for (x=1;x<argc;x++) {
179         /* Restore old arguments and delete ours */
180         snprintf(varname, sizeof(varname), "ARG%d", x);
181         if (oldargs[x]) {
182                 pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
183                 free(oldargs[x]);
184         } else {
185                 pbx_builtin_setvar_helper(chan, varname, NULL);
186         }
187   }
188
189   /* Restore macro variables */
190   pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
191   if (save_macro_exten) free(save_macro_exten);
192   pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
193   if (save_macro_context) free(save_macro_context);
194   pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
195   if (save_macro_priority) free(save_macro_priority);
196   if (setmacrocontext) {
197         chan->macrocontext[0] = '\0';
198         chan->macroexten[0] = '\0';
199         chan->macropriority = 0;
200   }
201
202   if (!strcasecmp(chan->context, fullmacro)) {
203         /* If we're leaving the macro normally, restore original information */
204         chan->priority = oldpriority;
205         strncpy(chan->context, oldcontext, sizeof(chan->context) - 1);
206         if (!(chan->_softhangup & AST_SOFTHANGUP_ASYNCGOTO)) {
207                 /* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
208                 strncpy(chan->exten, oldexten, sizeof(chan->exten) - 1);
209                 if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
210                         /* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
211                            normally if there is any problem */
212                         if (sscanf(offsets, "%d", &offset) == 1) {
213                                 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->callerid)) {
214                                         chan->priority += offset;
215                                 }
216                         }
217                 }
218         }
219   }
220
221   pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
222   if (save_macro_offset) free(save_macro_offset);
223   return res;
224 }
225
226 int unload_module(void)
227 {
228         STANDARD_HANGUP_LOCALUSERS;
229         return ast_unregister_application(app);
230 }
231
232 int load_module(void)
233 {
234         return ast_register_application(app, macro_exec, synopsis, descrip);
235 }
236
237 char *description(void)
238 {
239         return tdesc;
240 }
241
242 int usecount(void)
243 {
244         int res;
245         STANDARD_USECOUNT(res);
246         return res;
247 }
248
249 char *key()
250 {
251         return ASTERISK_GPL_KEY;
252 }