/* * Asterisk -- A telephony toolkit for Linux. * * flyexten Application For Asterisk * * Copyright (C) 2005, Anthony Minessale II * * Anthony Minessale II * * This program is free software, distributed under the terms of * the GNU General Public License * * Load the config for a user on the fly. * * exten => _X.,1,DynaGoto(${EXTEN},${EXTEN},1,/etc/asterisk/users/${USER_ID}.conf,sa) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For where to put dynamic tables */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision: 1.10 $") #include #include #include #include #include #include #include #define GOTOFLAG_FLUSHRELOAD ( 1 << 0 ) STANDARD_LOCAL_USER; LOCAL_USER_DECL; struct dg_cache { char *file; time_t time; struct dg_cache *next; }; static char *registrar = "DynaGoto"; static char *tdesc = "DynaGoto"; static char *synopsis = "DynaGoto"; static char *app = "DynaGoto"; AST_MUTEX_DEFINE_STATIC(exten_lock); AST_MUTEX_DEFINE_STATIC(cache_lock); static struct ast_context *local_contexts = NULL; static struct ast_flags globalflags = {0}; static char *cfgfile = "dynagoto.conf"; static struct dg_cache *global_cache = NULL; #define DG_SEEK 0 #define DG_DEL 1 static void do_config(int reload) { struct ast_config *cfg; struct ast_variable *v; if ((cfg = ast_config_load(cfgfile))) { for (v = ast_variable_browse(cfg, "general") ; v ; v = v->next) { if (!strcasecmp(v->name,"flushonreload")) ast_set2_flag((&globalflags), ast_true(v->value), GOTOFLAG_FLUSHRELOAD); } ast_config_destroy(cfg); } } static void do_merge(int wipe) { ast_mutex_lock(&exten_lock); ast_merge_contexts_and_delete(&local_contexts, wipe ? registrar : NULL); ast_mutex_unlock(&exten_lock); } static char *process_quotes_and_slashes(char *start, char find, char replace_with) { char *dataPut = start; int inEscape = 0; int inQuotes = 0; for (; *start; start++) { if (inEscape) { *dataPut++ = *start; /* Always goes verbatim */ inEscape = 0; } else { if (*start == '\\') { inEscape = 1; /* Do not copy \ into the data */ } else if (*start == '\'') { inQuotes = 1-inQuotes; /* Do not copy ' into the data */ } else { /* Replace , with |, unless in quotes */ *dataPut++ = inQuotes ? *start : ((*start==find) ? replace_with : *start); } } } *dataPut = 0; return dataPut; } static struct dg_cache *dg_cache_new(char *file, time_t time) { struct dg_cache *cache; if ((cache = malloc(sizeof(struct dg_cache)))) { memset(cache, 0, sizeof(struct dg_cache)); cache->time = time; cache->file = strdup(file); ast_mutex_lock(&cache_lock); cache->next = global_cache; global_cache = cache; ast_mutex_unlock(&cache_lock); } return cache; } static void free_cache(struct dg_cache **cache) { if(*cache) { if((*cache)->file) free((*cache)->file); free(*cache); *cache = NULL; } } static void dg_cache_show(int fd) { struct dg_cache *cp1 = NULL; if(!fd) return; for(cp1 = global_cache; cp1 ; cp1 = cp1->next) { ast_cli(fd, "%s -> %ld\n", cp1->file, cp1->time); } } static void dg_cache_nuke(void) { struct dg_cache *cp1 = NULL, *tmp = NULL; ast_mutex_lock(&cache_lock); for(cp1 = global_cache; cp1 ; tmp = cp1) { cp1 = cp1->next; free_cache(&tmp); } global_cache = NULL; ast_mutex_unlock(&cache_lock); } static struct dg_cache *dg_cache_find(char *file, int unlink) { struct dg_cache *cp1 = NULL, *last = NULL; ast_mutex_lock(&cache_lock); for(cp1 = global_cache; cp1 ; cp1 = cp1->next) { if(!strcmp(cp1->file, file)) { if(unlink) { if(last) { last->next = cp1->next; } else { global_cache = cp1->next; } } break; } last = cp1; } ast_mutex_unlock(&cache_lock); return cp1; } static int dg_cache_update(struct dg_cache *cache, time_t newtime) { if(cache) { ast_mutex_lock(&cache_lock); cache-> time = newtime; ast_mutex_unlock(&cache_lock); return 0; } return -1; } static int dg_cache_file(char *file) { struct stat buf; struct dg_cache *cache = NULL; if (stat(file, &buf)) { ast_log(LOG_ERROR, "ERROR: %s %s\n", file,strerror(errno)); return -1; } if((cache = dg_cache_find(file, DG_SEEK))) { dg_cache_update(cache, buf.st_mtime); } else if (!dg_cache_new(file, buf.st_mtime)) { ast_log(LOG_ERROR, "ERROR: Out Of Memory!\n"); return -1; } return 0; } static int dg_check_file(char *file) { struct stat buf; struct dg_cache *cache; int res = -1; memset(&buf, 0, sizeof(buf)); if((cache = dg_cache_find(file, DG_SEEK))) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Found %s in cache date: %ld.\n", file, cache->time); if (stat(file, &buf)) { ast_log(LOG_ERROR, "ERROR: %s.\n", strerror(errno)); } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Stat: %s date %ld.\n", file, buf.st_mtime); if(buf.st_mtime != cache->time) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Mismatch found on %s, updating.\n", file); res = 1; } else if(buf.st_mtime) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Same time found on %s.\n", file); res = 0; } } else { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "File: %s not found.\n", file); } return res; } static int dynagoto_exec(struct ast_channel *chan, void *data) { struct localuser *u; char *file = NULL; struct ast_config *cfg = NULL; struct ast_variable *v = NULL; int res = 0, argc = 0; char *ext = NULL, *pri, *appl, *mydata = NULL, *tc, *cidmatch; struct ast_context *con = NULL; char *start, *end; char *label; char realvalue[256]; int lastpri = -2, priority=0; char *argv[5]; char *context = NULL, *cptr = NULL; char *exten = NULL; char *info = NULL; char *flags = NULL; int all = 0, modified = 0, ccount = 0, ecount = 0; LOCAL_USER_ADD(u); if ((info = ast_strdupa(data))) { if ((argc = ast_app_separate_args(info, '|', argv, sizeof(argv) / sizeof(argv[0]))) >= 4) { context = argv[0]; exten = argv[1]; priority = atoi(argv[2]); file = argv[3]; flags = argc == 5 ? argv[4] : ""; all = strchr(flags, 'a') ? 1 : 0; if(strchr(flags, 's')) modified = dg_check_file(file); cptr = context; if (chan && !modified && !strchr(flags, 'e') && ast_exists_extension(chan, cptr, exten, priority, chan->cid.cid_num)) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3"exten: %s@%s priority: %d already exists performing goto!\n",exten, cptr, priority); ast_explicit_goto(chan, cptr, exten, priority); LOCAL_USER_REMOVE(u); return 0; } if (modified && option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3"Rescanning file %s.\n", file); if ((cfg = ast_config_load(file))) { if(strchr(flags, 's')) dg_cache_file(file); if(all) { cptr = ast_category_browse(cfg, NULL); } do { if ((v = ast_variable_browse(cfg, cptr))) { if (!(con = ast_context_find(cptr))) con = ast_context_create(&local_contexts, cptr, registrar); } if (!con) { ast_log(LOG_WARNING,"Unable to obtain data for %s in %s\n", cptr, file); LOCAL_USER_REMOVE(u); return 0; } for (; v ; v = v->next) { if (!strcasecmp(v->name, "exten")) { char *stringp=NULL; int ipri = -2; char realext[256]=""; char *plus; tc = strdup(v->value); if (tc!=NULL){ stringp=tc; ext = strsep(&stringp, ","); if (!ext) ext=""; cidmatch = strchr(ext, '/'); if (cidmatch) { *cidmatch = '\0'; cidmatch++; ast_shrink_phone_number(cidmatch); } pri = strsep(&stringp, ","); if (!pri) pri=""; label = strchr(pri, '('); if (label) { *label = '\0'; label++; end = strchr(label, ')'); if (end) *end = '\0'; else ast_log(LOG_WARNING, "Label missing trailing ')' at line %d\n", v->lineno); } plus = strchr(pri, '+'); if (plus) { *plus = '\0'; plus++; } if (!strcmp(pri,"hint")) ipri=PRIORITY_HINT; else if (!strcmp(pri, "next") || !strcmp(pri, "n")) { if (lastpri > -2) ipri = lastpri + 1; else ast_log(LOG_WARNING, "Can't use 'next' priority on the first entry!\n"); } else if (!strcmp(pri, "same") || !strcmp(pri, "s")) { if (lastpri > -2) ipri = lastpri; else ast_log(LOG_WARNING, "Can't use 'same' priority on the first entry!\n"); } else { if (sscanf(pri, "%i", &ipri) != 1) { if ((ipri = ast_findlabel_extension2(NULL, con, ext, pri, cidmatch)) < 1) { ast_log(LOG_WARNING, "Invalid priority/label '%s' at line %d\n", pri, v->lineno); ipri = 0; } } } appl = stringp; if (!appl) appl=""; if (!(start = strchr(appl, '('))) { if (stringp) appl = strsep(&stringp, ","); else appl = ""; } if (start && (end = strrchr(appl, ')'))) { *start = *end = '\0'; mydata = start + 1; process_quotes_and_slashes(mydata, ',', '|'); } else if (stringp!=NULL && *stringp=='"') { stringp++; mydata = strsep(&stringp, "\""); stringp++; } else { if (stringp) mydata = strsep(&stringp, ","); else mydata = ""; } if (!mydata) mydata=""; while (*appl && (*appl < 33)) appl++; pbx_substitute_variables_helper(NULL, ext, realext, sizeof(realext) - 1); if (ipri) { if (plus) ipri += atoi(plus); lastpri = ipri; if (ast_add_extension2(con, 1, realext, ipri, label, cidmatch, appl, strdup(mydata), free, registrar)) { ast_log(LOG_WARNING, "Unable to register extension at line %d\n", v->lineno); } else { res++; ecount++; } } free(tc); } else fprintf(stderr,"Error strdup returned NULL in %s\n",__PRETTY_FUNCTION__); } else if (!strcasecmp(v->name, "include")) { memset(realvalue, 0, sizeof(realvalue)); pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1); if (ast_context_add_include2(con, realvalue, registrar)) ast_log(LOG_WARNING, "Unable to include context '%s' in context '%s'\n", v->value, cptr); } else if (!strcasecmp(v->name, "ignorepat")) { memset(realvalue, 0, sizeof(realvalue)); pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1); if (ast_context_add_ignorepat2(con, realvalue, registrar)) ast_log(LOG_WARNING, "Unable to include ignorepat '%s' in context '%s'\n", v->value, cptr); } else if (!strcasecmp(v->name, "switch") || !strcasecmp(v->name, "lswitch") || !strcasecmp(v->name, "eswitch")) { char *stringp=NULL; memset(realvalue, 0, sizeof(realvalue)); if (!strcasecmp(v->name, "switch")) pbx_substitute_variables_helper(NULL, v->value, realvalue, sizeof(realvalue) - 1); else strncpy(realvalue, v->value, sizeof(realvalue) - 1); tc = realvalue; stringp=tc; appl = strsep(&stringp, "/"); mydata = strsep(&stringp, ""); if (!mydata) mydata = ""; if (ast_context_add_switch2(con, appl, mydata, !strcasecmp(v->name, "eswitch"), registrar)) ast_log(LOG_WARNING, "Unable to include switch '%s' in context '%s'\n", v->value, cptr); } } ccount++; } while(all && (cptr = ast_category_browse(cfg, cptr))); ast_config_destroy(cfg); } else { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3"Cannot open %s giving up!\n", file); res = 0; } } } else { ast_log(LOG_ERROR,"DOH! No MEMORY?\n"); } if (res) { if (!all && option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3"Located context %s in %s and added %d extensions.\n", context, file, res); do_merge(0); if (chan && context && exten && ast_exists_extension(chan, context, exten, priority, chan->cid.cid_num)) { if (option_verbose > 2) { if(all) ast_verbose(VERBOSE_PREFIX_3"entire file %s parsed. %d extensions in %d contexts loaded.\n", file, ecount, ccount); ast_verbose(VERBOSE_PREFIX_3"exten: %s@%s priority: %d loaded and cached. Performing goto!\n", exten, context, priority); } ast_explicit_goto(chan, context, exten, priority); } res = 0; } else { res = -1; } LOCAL_USER_REMOVE(u); return res; } static int dynagoto_flush(int fd, int argc, char *argv[]) { if(argv[1]) { if(!strcmp(argv[1],"flush")) { ast_cli(fd, "OK Cache Flushed!\n"); dg_cache_nuke(); do_merge(1); } else if(!strcmp(argv[1],"show")) { dg_cache_show(fd); } else if(!strcmp(argv[1],"load")) { char data[1024]; if(argv[2]) { snprintf(data,1024, "|||%s|a%s",argv[2],argv[3] ? "s" : ""); dynagoto_exec(NULL, data); } } return 0; } return -1; } static struct ast_cli_entry cli_flush = { { "dynagoto", "flush", NULL }, dynagoto_flush, "Flush the dynamic cache", "dynagoto flush", NULL}; static struct ast_cli_entry cli_show = { { "dynagoto", "show", NULL }, dynagoto_flush, "Show the dynamic cache", "dynagoto show", NULL}; static struct ast_cli_entry cli_load = { { "dynagoto", "load", NULL }, dynagoto_flush, "Load the dynamic cache", "dynagoto load", NULL}; int reload(void) { do_config(1); if (ast_test_flag(&globalflags,GOTOFLAG_FLUSHRELOAD)) { do_merge(1); dg_cache_nuke(); ast_log(LOG_NOTICE, "Cache Flushed!\n"); } return 0; } int unload_module(void) { do_merge(1); dg_cache_nuke(); ast_cli_unregister(&cli_flush); ast_cli_unregister(&cli_show); ast_cli_unregister(&cli_load); return ast_unregister_application(app); } int load_module(void) { do_config(0); ast_cli_register(&cli_flush); ast_cli_register(&cli_show); ast_cli_register(&cli_load); return ast_register_application(app, dynagoto_exec, synopsis, tdesc); } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; }