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