/* * res_config_curl.c * Copyright (c) 2004-2007 Anthony Minessale II * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *tdesc = "CURL URL Based Configuration"; static const char *global_tmp_prefix = "/var/tmp/res_confg_curl/"; static int global_cache_time = 0; static int global_no_cache_neg = 1; static char *global_config_file = "curl.conf"; static char *app_1 = "URLFetch"; static char *synopsis_1 = "Fetch Data from a URL"; static char *desc_1 = " URLFetch()\n" "load a url that returns ast_config and set according chanvars\n" ; STANDARD_LOCAL_USER; LOCAL_USER_DECL; struct config_data { struct ast_variable *vars; struct ast_config *cfg; const char *table; const char *database; const char *keyfield; const char *lookup; const char *file; const char *action; char url[1024]; char cachefile[1152]; int fd; int recur; int skip_prep; va_list aq; }; static size_t realtime_callback(void *ptr, size_t size, size_t nmemb, void *data) { register int realsize = size * nmemb; struct config_data *config_data = data; size_t len; char *line = NULL, *newline = NULL, *nextline = NULL; if ((line = ast_strdupa((char *) ptr))) { while (line) { if ((nextline = strchr(line, '\n'))) { *nextline = '\0'; nextline++; } if (!strcmp(line, ";OK;")) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Open file %s\n", config_data->cachefile); if (!config_data->fd) config_data->fd = open(config_data->cachefile, O_WRONLY | O_TRUNC | O_CREAT, 0600); } else { if (config_data->fd) { len = strlen(line); newline = alloca(len + 1); strncpy(newline, line, len); newline[len] = '\n'; if (write(config_data->fd, newline, len+1) != len+1) break; } } line = nextline; } } else ast_log(LOG_ERROR, "Memory Allocation Failed.\n"); if (config_data->fd) close(config_data->fd); return realsize; } static void curl_process(struct config_data *config_data) { CURL *curl_handle = NULL; const char *param = NULL, *val = NULL; int x = 0, offset=0; struct ast_config *cfg; struct ast_variable *v, *vp; struct stat sbuf; int elapsed = 0; int perform = 1; strncpy(config_data->url, config_data->database, sizeof(config_data->url)); if(! config_data->skip_prep) { if (strchr(config_data->url, '?')) x++; if (config_data->file) { if (config_data->url[strlen(config_data->url)-1] == '/') snprintf(config_data->url + strlen(config_data->url), sizeof(config_data->url) - strlen(config_data->url), "%s", config_data->file); else snprintf(config_data->url + strlen(config_data->url), sizeof(config_data->url) - strlen(config_data->url), "%c_file=%s", x++ == 0 ? '?' : '&', config_data->file); } else { if (config_data->table) { snprintf(config_data->url + strlen(config_data->url), sizeof(config_data->url) - strlen(config_data->url), "%c_table=%s", x++ == 0 ? '?' : '&', config_data->table); } if (config_data->action) { snprintf(config_data->url + strlen(config_data->url), sizeof(config_data->url) - strlen(config_data->url), "%c_action=%s", x++ == 0 ? '?' : '&', config_data->action); } if (config_data->keyfield && config_data->lookup) { snprintf(config_data->url + strlen(config_data->url), sizeof(config_data->url) - strlen(config_data->url), "%c_keyfield=%s&_lookup=%s", x++ == 0 ? '?' : '&', config_data->keyfield, config_data->lookup); x++; } while ((param = va_arg(config_data->aq, const char *)) && (val = va_arg(config_data->aq, const char *))) { snprintf(config_data->url + strlen(config_data->url), sizeof(config_data->url) - strlen(config_data->url), "%c%s=%s", x++ == 0 ? '?' : '&', param, val); } } } snprintf(config_data->cachefile, sizeof(config_data->cachefile), "%s", global_tmp_prefix); offset = strlen(global_tmp_prefix); for (x = 0; x < strlen(config_data->url); x++) { if (config_data->url[x] == '/' || config_data->url[x] == ':' || config_data->url[x] == '$' || config_data->url[x] == '&' || config_data->url[x] == '(' || config_data->url[x] == ')' || config_data->url[x] == '[' || config_data->url[x] == ']' || config_data->url[x] == '*' || config_data->url[x] == '?' || config_data->url[x] == '=' || config_data->url[x] == '.' || config_data->url[x] == '#' ) config_data->cachefile[offset+x] = '_'; else config_data->cachefile[offset+x] = config_data->url[x]; } if (global_cache_time && !stat(config_data->cachefile, &sbuf)) { elapsed = (time(NULL) - sbuf.st_mtime); if (elapsed < global_cache_time) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "CURL config engine using cached file for %d more seconds\n", global_cache_time - elapsed); perform = 0; } } if (perform) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "CURL config engine fetching [%s]\n", config_data->url); curl_global_init(CURL_GLOBAL_ALL); curl_handle = curl_easy_init(); if (!strncasecmp(config_data->url, "https", 5)) { curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0); } curl_easy_setopt(curl_handle, CURLOPT_URL, config_data->url); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, realtime_callback); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)config_data); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "asterisk/1.0"); curl_easy_perform(curl_handle); curl_easy_cleanup(curl_handle); } if (!ast_strlen_zero(config_data->cachefile) && !strncmp(config_data->action, "realtime", 8) && (cfg = ast_config_load(config_data->cachefile))) { vp = config_data->vars; for(v = ast_variable_browse(cfg, "realtime") ; v ; v = v->next) { vp->next = ast_variable_new(v->name, v->value); vp = vp->next; } ast_config_destroy(cfg); cfg = NULL; } else if (!ast_strlen_zero(config_data->cachefile) && (cfg = ast_config_internal_load(config_data->cachefile, config_data->cfg))) config_data->cfg = cfg; if (global_no_cache_neg && !ast_strlen_zero(config_data->cachefile) && !config_data->vars && !config_data->cfg) unlink(config_data->cachefile); } static struct ast_variable *realtime_curl(const char *database, const char *table, va_list ap) { struct config_data config_data; memset(&config_data, 0, sizeof(config_data)); va_copy(config_data.aq, ap); config_data.database = database; config_data.table = table; config_data.action = "realtime_lookup"; curl_process(&config_data); va_end(config_data.aq); return config_data.vars; } static int realtime_exec(struct ast_channel *chan, void *data) { struct config_data config_data; struct ast_variable *v; memset(&config_data, 0, sizeof(config_data)); config_data.database = (char *) data; config_data.action = "realtime_lookup"; config_data.skip_prep = 1; curl_process(&config_data); if (config_data.vars) { for (v = config_data.vars; v; v = v->next) { if(strncmp(v->name, "private_", 8)) pbx_builtin_setvar_helper(chan, v->name, v->value); } ast_variables_destroy(config_data.vars); } return 0; } static struct ast_config *realtime_multi_curl(const char *database, const char *table, va_list ap) { struct config_data config_data; memset(&config_data, 0, sizeof(config_data)); va_copy(config_data.aq, ap); config_data.cfg = ast_config_new(); config_data.database = database; config_data.table = table; config_data.action = "realtime_multi_lookup"; curl_process(&config_data); va_end(config_data.aq); return config_data.cfg; } static int update_curl(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) { struct config_data config_data; struct ast_variable *v; int res = -1; memset(&config_data, 0, sizeof(config_data)); va_copy(config_data.aq, ap); config_data.database = database; config_data.keyfield = keyfield; config_data.table = table; config_data.lookup = lookup; config_data.action = "realtime_update"; curl_process(&config_data); va_end(config_data.aq); if (config_data.vars) { for (v = config_data.vars; v; v = v->next) { if (!strcmp(v->name, "result")) { res = ast_true(v->value) ? 0 : -1; } } ast_variables_destroy(config_data.vars); } return res; } static struct ast_config *config_curl (const char *database, const char *table, const char *file, struct ast_config *cfg) { struct config_data config_data; /* can't configure myself */ if (!strcmp(file, global_config_file)) return NULL; memset(&config_data, 0, sizeof(config_data)); config_data.action = "config"; config_data.cfg = cfg; config_data.database = database; config_data.file = file; if (!config_data.cfg) config_data.cfg = ast_config_new(); curl_process(&config_data); return config_data.cfg; } static void load_config(void) { struct ast_config *cfg; struct ast_variable *v; if ( (cfg = ast_config_load(global_config_file)) ) { for (v = ast_variable_browse(cfg, "config"); v ; v = v->next) if (!strcasecmp(v->name, "cache_time")) global_cache_time = atoi(v->value); else if (!strcasecmp(v->name, "no_cache_neg")) global_no_cache_neg = atoi(v->value); ast_config_destroy(cfg); cfg = NULL; } } static struct ast_config_engine curl_engine = { .name = "curl", .load_func = config_curl, .realtime_func = realtime_curl, .realtime_multi_func = realtime_multi_curl, .update_func = update_curl }; int reload(void) { load_config(); return 0; } int unload_module (void) { ast_config_engine_deregister(&curl_engine); if (option_verbose) ast_verbose(VERBOSE_PREFIX_1 "res_config_curl unloaded.\n"); ast_unregister_application(app_1); STANDARD_HANGUP_LOCALUSERS; return 0; } int load_module (void) { char cmd[128]; snprintf(cmd, 128, "/bin/rm -fr %s ; /bin/mkdir -p %s", global_tmp_prefix, global_tmp_prefix); system(cmd); load_config(); ast_config_engine_register(&curl_engine); ast_register_application(app_1, realtime_exec, synopsis_1, desc_1); if (option_verbose) ast_verbose(VERBOSE_PREFIX_1 "res_config_curl loaded.\n"); return 0; } char *description (void) { return tdesc; } int usecount (void) { /* never unload a config module */ return 1; } char *key () { return ASTERISK_GPL_KEY; }