only process args that exist
[asterisk/asterisk.git] / pbx / pbx_realtime.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 Realtime PBX Module
22  *
23  * \arg See also: \ref AstARA
24  */
25
26 /*** MODULEINFO
27         <support_level>extended</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include <signal.h>
35
36 #include "asterisk/file.h"
37 #include "asterisk/logger.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/config.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/frame.h"
43 #include "asterisk/term.h"
44 #include "asterisk/manager.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/lock.h"
47 #include "asterisk/linkedlists.h"
48 #include "asterisk/chanvars.h"
49 #include "asterisk/sched.h"
50 #include "asterisk/io.h"
51 #include "asterisk/utils.h"
52 #include "asterisk/astdb.h"
53 #include "asterisk/app.h"
54 #include "asterisk/astobj2.h"
55
56 #define MODE_MATCH              0
57 #define MODE_MATCHMORE  1
58 #define MODE_CANMATCH   2
59
60 #define EXT_DATA_SIZE 256
61
62 enum option_flags {
63         OPTION_PATTERNS_DISABLED = (1 << 0),
64 };
65
66 AST_APP_OPTIONS(switch_opts, {
67         AST_APP_OPTION('p', OPTION_PATTERNS_DISABLED),
68 });
69
70 struct cache_entry {
71         struct timeval when;
72         struct ast_variable *var;
73         int priority;
74         char *context;
75         char exten[2];
76 };
77
78 struct ao2_container *cache;
79 pthread_t cleanup_thread = 0;
80
81 static int cache_hash(const void *obj, const int flags)
82 {
83         const struct cache_entry *e = obj;
84         return ast_str_case_hash(e->exten) + e->priority;
85 }
86
87 static int cache_cmp(void *obj, void *arg, int flags)
88 {
89         struct cache_entry *e = obj, *f = arg;
90         return e->priority != f->priority ? 0 :
91                 strcmp(e->exten, f->exten) ? 0 :
92                 strcmp(e->context, f->context) ? 0 :
93                 CMP_MATCH;
94 }
95
96 static struct ast_variable *dup_vars(struct ast_variable *v)
97 {
98         struct ast_variable *new, *list = NULL;
99         for (; v; v = v->next) {
100                 if (!(new = ast_variable_new(v->name, v->value, v->file))) {
101                         ast_variables_destroy(list);
102                         return NULL;
103                 }
104                 /* Reversed list in cache, but when we duplicate out of the cache,
105                  * it's back to correct order. */
106                 new->next = list;
107                 list = new;
108         }
109         return list;
110 }
111
112 static void free_entry(void *obj)
113 {
114         struct cache_entry *e = obj;
115         ast_variables_destroy(e->var);
116 }
117
118 static int purge_old_fn(void *obj, void *arg, int flags)
119 {
120         struct cache_entry *e = obj;
121         struct timeval *now = arg;
122         return ast_tvdiff_ms(*now, e->when) >= 1000 ? CMP_MATCH : 0;
123 }
124
125 static void *cleanup(void *unused)
126 {
127         struct timespec forever = { 999999999, 0 }, one_second = { 1, 0 };
128         struct timeval now;
129
130         for (;;) {
131                 pthread_testcancel();
132                 if (ao2_container_count(cache) == 0) {
133                         nanosleep(&forever, NULL);
134                 }
135                 pthread_testcancel();
136                 now = ast_tvnow();
137                 ao2_callback(cache, OBJ_MULTIPLE | OBJ_UNLINK | OBJ_NODATA, purge_old_fn, &now);
138                 pthread_testcancel();
139                 nanosleep(&one_second, NULL);
140         }
141
142         return NULL;
143 }
144
145
146 /* Realtime switch looks up extensions in the supplied realtime table.
147
148         [context@][realtimetable][/options]
149
150         If the realtimetable is omitted it is assumed to be "extensions".  If no context is 
151         specified the context is assumed to be whatever is the container.
152
153         The realtime table should have entries for context,exten,priority,app,args
154         
155         The realtime table currently does not support callerid fields.
156
157 */
158
159
160 static struct ast_variable *realtime_switch_common(const char *table, const char *context, const char *exten, int priority, int mode, struct ast_flags flags)
161 {
162         struct ast_variable *var;
163         struct ast_config *cfg;
164         char pri[20];
165         char *ematch;
166         char rexten[AST_MAX_EXTENSION + 20]="";
167         int match;
168         /* Optimization: since we don't support hints in realtime, it's silly to
169          * query for a hint here, since we won't actually do anything with it.
170          * This just wastes CPU time and resources. */
171         if (priority < 0) {
172                 return NULL;
173         }
174         snprintf(pri, sizeof(pri), "%d", priority);
175         switch(mode) {
176         case MODE_MATCHMORE:
177                 ematch = "exten LIKE";
178                 snprintf(rexten, sizeof(rexten), "%s_%%", exten);
179                 break;
180         case MODE_CANMATCH:
181                 ematch = "exten LIKE";
182                 snprintf(rexten, sizeof(rexten), "%s%%", exten);
183                 break;
184         case MODE_MATCH:
185         default:
186                 ematch = "exten";
187                 ast_copy_string(rexten, exten, sizeof(rexten));
188         }
189         var = ast_load_realtime(table, ematch, rexten, "context", context, "priority", pri, SENTINEL);
190         if (!var && !ast_test_flag(&flags, OPTION_PATTERNS_DISABLED)) {
191                 cfg = ast_load_realtime_multientry(table, "exten LIKE", "\\_%", "context", context, "priority", pri, SENTINEL); 
192                 if (cfg) {
193                         char *cat = ast_category_browse(cfg, NULL);
194
195                         while(cat) {
196                                 switch(mode) {
197                                 case MODE_MATCHMORE:
198                                         match = ast_extension_close(cat, exten, 1);
199                                         break;
200                                 case MODE_CANMATCH:
201                                         match = ast_extension_close(cat, exten, 0);
202                                         break;
203                                 case MODE_MATCH:
204                                 default:
205                                         match = ast_extension_match(cat, exten);
206                                 }
207                                 if (match) {
208                                         var = ast_category_detach_variables(ast_category_get(cfg, cat));
209                                         break;
210                                 }
211                                 cat = ast_category_browse(cfg, cat);
212                         }
213                         ast_config_destroy(cfg);
214                 }
215         }
216         return var;
217 }
218
219 static struct ast_variable *realtime_common(const char *context, const char *exten, int priority, const char *data, int mode)
220 {
221         const char *ctx = NULL;
222         char *table;
223         struct ast_variable *var=NULL;
224         struct ast_flags flags = { 0, };
225         struct cache_entry *ce;
226         struct {
227                 struct cache_entry ce;
228                 char exten[AST_MAX_EXTENSION];
229         } cache_search = { { .priority = priority, .context = (char *) context }, };
230         char *buf = ast_strdupa(data);
231         if (buf) {
232                 /* "Realtime" prefix is stripped off in the parent engine.  The
233                  * remaining string is: [[context@]table][/opts] */
234                 char *opts = strchr(buf, '/');
235                 if (opts)
236                         *opts++ = '\0';
237                 table = strchr(buf, '@');
238                 if (table) {
239                         *table++ = '\0';
240                         ctx = buf;
241                 }
242                 ctx = S_OR(ctx, context);
243                 table = S_OR(table, "extensions");
244                 if (!ast_strlen_zero(opts)) {
245                         ast_app_parse_options(switch_opts, &flags, NULL, opts);
246                 }
247                 ast_copy_string(cache_search.exten, exten, sizeof(cache_search.exten));
248                 if (mode == MODE_MATCH && (ce = ao2_find(cache, &cache_search, OBJ_POINTER))) {
249                         var = dup_vars(ce->var);
250                         ao2_ref(ce, -1);
251                 } else {
252                         var = realtime_switch_common(table, ctx, exten, priority, mode, flags);
253                         do {
254                                 struct ast_variable *new;
255                                 /* Only cache matches */
256                                 if (mode != MODE_MATCH) {
257                                         break;
258                                 }
259                                 if (!(new = dup_vars(var))) {
260                                         break;
261                                 }
262                                 if (!(ce = ao2_alloc(sizeof(*ce) + strlen(exten) + strlen(context), free_entry))) {
263                                         ast_variables_destroy(new);
264                                         break;
265                                 }
266                                 ce->context = ce->exten + strlen(exten) + 1;
267                                 strcpy(ce->exten, exten); /* SAFE */
268                                 strcpy(ce->context, context); /* SAFE */
269                                 ce->priority = priority;
270                                 ce->var = new;
271                                 ce->when = ast_tvnow();
272                                 ao2_link(cache, ce);
273                                 pthread_kill(cleanup_thread, SIGURG);
274                                 ao2_ref(ce, -1);
275                         } while (0);
276                 }
277         }
278         return var;
279 }
280
281 static int realtime_exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
282 {
283         struct ast_variable *var = realtime_common(context, exten, priority, data, MODE_MATCH);
284         if (var) {
285                 ast_variables_destroy(var);
286                 return 1;
287         }
288         return 0;
289 }
290
291 static int realtime_canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
292 {
293         struct ast_variable *var = realtime_common(context, exten, priority, data, MODE_CANMATCH);
294         if (var) {
295                 ast_variables_destroy(var);
296                 return 1;
297         }
298         return 0;
299 }
300
301 static int realtime_exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
302 {
303         int res = -1;
304         struct ast_variable *var = realtime_common(context, exten, priority, data, MODE_MATCH);
305
306         if (var) {
307                 char *tmp="";
308                 char *app = NULL;
309                 struct ast_variable *v;
310
311                 for (v = var; v ; v = v->next) {
312                         if (!strcasecmp(v->name, "app"))
313                                 app = ast_strdupa(v->value);
314                         else if (!strcasecmp(v->name, "appdata")) {
315                                 if (ast_compat_pbx_realtime) {
316                                         char *ptr;
317                                         int in = 0;
318                                         tmp = alloca(strlen(v->value) * 2 + 1);
319                                         for (ptr = tmp; *v->value; v->value++) {
320                                                 if (*v->value == ',') {
321                                                         *ptr++ = '\\';
322                                                         *ptr++ = ',';
323                                                 } else if (*v->value == '|' && !in) {
324                                                         *ptr++ = ',';
325                                                 } else {
326                                                         *ptr++ = *v->value;
327                                                 }
328
329                                                 /* Don't escape '|', meaning 'or', inside expressions ($[ ]) */
330                                                 if (v->value[0] == '[' && v->value[-1] == '$') {
331                                                         in++;
332                                                 } else if (v->value[0] == ']' && in) {
333                                                         in--;
334                                                 }
335                                         }
336                                         *ptr = '\0';
337                                 } else {
338                                         tmp = ast_strdupa(v->value);
339                                 }
340                         }
341                 }
342                 ast_variables_destroy(var);
343                 if (!ast_strlen_zero(app)) {
344                         struct ast_app *a = pbx_findapp(app);
345                         if (a) {
346                                 char appdata[512];
347                                 char tmp1[80];
348                                 char tmp2[80];
349                                 char tmp3[EXT_DATA_SIZE];
350
351                                 appdata[0] = 0; /* just in case the substitute var func isn't called */
352                                 if(!ast_strlen_zero(tmp))
353                                         pbx_substitute_variables_helper(chan, tmp, appdata, sizeof(appdata) - 1);
354                                 ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\")\n",
355                                                 chan->exten, chan->context, chan->priority,
356                                                  term_color(tmp1, app, COLOR_BRCYAN, 0, sizeof(tmp1)),
357                                                  term_color(tmp2, chan->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)),
358                                                  term_color(tmp3, S_OR(appdata, ""), COLOR_BRMAGENTA, 0, sizeof(tmp3)));
359                                 manager_event(EVENT_FLAG_DIALPLAN, "Newexten",
360                                                           "Channel: %s\r\n"
361                                                           "Context: %s\r\n"
362                                                           "Extension: %s\r\n"
363                                                           "Priority: %d\r\n"
364                                                           "Application: %s\r\n"
365                                                           "AppData: %s\r\n"
366                                                           "Uniqueid: %s\r\n",
367                                                           chan->name, chan->context, chan->exten, chan->priority, app, !ast_strlen_zero(appdata) ? appdata : "(NULL)", chan->uniqueid);
368                                 
369                                 res = pbx_exec(chan, a, appdata);
370                         } else
371                                 ast_log(LOG_NOTICE, "No such application '%s' for extension '%s' in context '%s'\n", app, exten, context);
372                 } else {
373                         ast_log(LOG_WARNING, "No application specified for realtime extension '%s' in context '%s'\n", exten, context);
374                 }
375         }
376         return res;
377 }
378
379 static int realtime_matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
380 {
381         struct ast_variable *var = realtime_common(context, exten, priority, data, MODE_MATCHMORE);
382         if (var) {
383                 ast_variables_destroy(var);
384                 return 1;
385         }
386         return 0;
387 }
388
389 static struct ast_switch realtime_switch =
390 {
391         name:                   "Realtime",
392         description:            "Realtime Dialplan Switch",
393         exists:                 realtime_exists,
394         canmatch:               realtime_canmatch,
395         exec:                   realtime_exec,
396         matchmore:              realtime_matchmore,
397 };
398
399 static int unload_module(void)
400 {
401         ast_unregister_switch(&realtime_switch);
402         pthread_cancel(cleanup_thread);
403         pthread_kill(cleanup_thread, SIGURG);
404         pthread_join(cleanup_thread, NULL);
405         /* Destroy all remaining entries */
406         ao2_ref(cache, -1);
407         return 0;
408 }
409
410 static int load_module(void)
411 {
412         if (!(cache = ao2_container_alloc(573, cache_hash, cache_cmp))) {
413                 return AST_MODULE_LOAD_FAILURE;
414         }
415
416         if (ast_pthread_create(&cleanup_thread, NULL, cleanup, NULL)) {
417                 return AST_MODULE_LOAD_FAILURE;
418         }
419
420         if (ast_register_switch(&realtime_switch))
421                 return AST_MODULE_LOAD_FAILURE;
422         return AST_MODULE_LOAD_SUCCESS;
423 }
424
425 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Realtime Switch");