00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037 #include "asterisk.h"
00038
00039 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 294988 $")
00040
00041 #include <curl/curl.h>
00042
00043 #include "asterisk/lock.h"
00044 #include "asterisk/file.h"
00045 #include "asterisk/channel.h"
00046 #include "asterisk/pbx.h"
00047 #include "asterisk/cli.h"
00048 #include "asterisk/module.h"
00049 #include "asterisk/app.h"
00050 #include "asterisk/utils.h"
00051 #include "asterisk/threadstorage.h"
00052
00053 #define CURLVERSION_ATLEAST(a,b,c) \
00054 ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
00055
00056 #define CURLOPT_SPECIAL_HASHCOMPAT -500
00057
00058 static void curlds_free(void *data);
00059
00060 static struct ast_datastore_info curl_info = {
00061 .type = "CURL",
00062 .destroy = curlds_free,
00063 };
00064
00065 struct curl_settings {
00066 AST_LIST_ENTRY(curl_settings) list;
00067 CURLoption key;
00068 void *value;
00069 };
00070
00071 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
00072
00073 static void curlds_free(void *data)
00074 {
00075 AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
00076 struct curl_settings *setting;
00077 if (!list) {
00078 return;
00079 }
00080 while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
00081 free(setting);
00082 }
00083 AST_LIST_HEAD_DESTROY(list);
00084 }
00085
00086 enum optiontype {
00087 OT_BOOLEAN,
00088 OT_INTEGER,
00089 OT_INTEGER_MS,
00090 OT_STRING,
00091 OT_ENUM,
00092 };
00093
00094 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
00095 {
00096 if (!strcasecmp(name, "header")) {
00097 *key = CURLOPT_HEADER;
00098 *ot = OT_BOOLEAN;
00099 } else if (!strcasecmp(name, "proxy")) {
00100 *key = CURLOPT_PROXY;
00101 *ot = OT_STRING;
00102 } else if (!strcasecmp(name, "proxyport")) {
00103 *key = CURLOPT_PROXYPORT;
00104 *ot = OT_INTEGER;
00105 } else if (!strcasecmp(name, "proxytype")) {
00106 *key = CURLOPT_PROXYTYPE;
00107 *ot = OT_ENUM;
00108 } else if (!strcasecmp(name, "dnstimeout")) {
00109 *key = CURLOPT_DNS_CACHE_TIMEOUT;
00110 *ot = OT_INTEGER;
00111 } else if (!strcasecmp(name, "userpwd")) {
00112 *key = CURLOPT_USERPWD;
00113 *ot = OT_STRING;
00114 } else if (!strcasecmp(name, "proxyuserpwd")) {
00115 *key = CURLOPT_PROXYUSERPWD;
00116 *ot = OT_STRING;
00117 } else if (!strcasecmp(name, "maxredirs")) {
00118 *key = CURLOPT_MAXREDIRS;
00119 *ot = OT_INTEGER;
00120 } else if (!strcasecmp(name, "referer")) {
00121 *key = CURLOPT_REFERER;
00122 *ot = OT_STRING;
00123 } else if (!strcasecmp(name, "useragent")) {
00124 *key = CURLOPT_USERAGENT;
00125 *ot = OT_STRING;
00126 } else if (!strcasecmp(name, "cookie")) {
00127 *key = CURLOPT_COOKIE;
00128 *ot = OT_STRING;
00129 } else if (!strcasecmp(name, "ftptimeout")) {
00130 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
00131 *ot = OT_INTEGER;
00132 } else if (!strcasecmp(name, "httptimeout")) {
00133 #if CURLVERSION_ATLEAST(7,16,2)
00134 *key = CURLOPT_TIMEOUT_MS;
00135 *ot = OT_INTEGER_MS;
00136 #else
00137 *key = CURLOPT_TIMEOUT;
00138 *ot = OT_INTEGER;
00139 #endif
00140 } else if (!strcasecmp(name, "conntimeout")) {
00141 #if CURLVERSION_ATLEAST(7,16,2)
00142 *key = CURLOPT_CONNECTTIMEOUT_MS;
00143 *ot = OT_INTEGER_MS;
00144 #else
00145 *key = CURLOPT_CONNECTTIMEOUT;
00146 *ot = OT_INTEGER;
00147 #endif
00148 } else if (!strcasecmp(name, "ftptext")) {
00149 *key = CURLOPT_TRANSFERTEXT;
00150 *ot = OT_BOOLEAN;
00151 } else if (!strcasecmp(name, "ssl_verifypeer")) {
00152 *key = CURLOPT_SSL_VERIFYPEER;
00153 *ot = OT_BOOLEAN;
00154 } else if (!strcasecmp(name, "hashcompat")) {
00155 *key = CURLOPT_SPECIAL_HASHCOMPAT;
00156 *ot = OT_BOOLEAN;
00157 } else {
00158 return -1;
00159 }
00160 return 0;
00161 }
00162
00163 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
00164 {
00165 struct ast_datastore *store;
00166 struct global_curl_info *list;
00167 struct curl_settings *cur, *new = NULL;
00168 CURLoption key;
00169 enum optiontype ot;
00170
00171 if (chan) {
00172 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00173
00174 if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
00175 ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
00176 return -1;
00177 }
00178
00179 if (!(list = ast_calloc(1, sizeof(*list)))) {
00180 ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
00181 ast_datastore_free(store);
00182 }
00183
00184 store->data = list;
00185 AST_LIST_HEAD_INIT(list);
00186 ast_channel_datastore_add(chan, store);
00187 } else {
00188 list = store->data;
00189 }
00190 } else {
00191
00192 list = &global_curl_info;
00193 }
00194
00195 if (!parse_curlopt_key(name, &key, &ot)) {
00196 if (ot == OT_BOOLEAN) {
00197 if ((new = ast_calloc(1, sizeof(*new)))) {
00198 new->value = (void *)((long) ast_true(value));
00199 }
00200 } else if (ot == OT_INTEGER) {
00201 long tmp = atol(value);
00202 if ((new = ast_calloc(1, sizeof(*new)))) {
00203 new->value = (void *)tmp;
00204 }
00205 } else if (ot == OT_INTEGER_MS) {
00206 long tmp = atof(value) * 1000.0;
00207 if ((new = ast_calloc(1, sizeof(*new)))) {
00208 new->value = (void *)tmp;
00209 }
00210 } else if (ot == OT_STRING) {
00211 if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
00212 new->value = (char *)new + sizeof(*new);
00213 strcpy(new->value, value);
00214 }
00215 } else if (ot == OT_ENUM) {
00216 if (key == CURLOPT_PROXYTYPE) {
00217 long ptype =
00218 #if CURLVERSION_ATLEAST(7,10,0)
00219 CURLPROXY_HTTP;
00220 #else
00221 CURLPROXY_SOCKS5;
00222 #endif
00223 if (0) {
00224 #if CURLVERSION_ATLEAST(7,15,2)
00225 } else if (!strcasecmp(value, "socks4")) {
00226 ptype = CURLPROXY_SOCKS4;
00227 #endif
00228 #if CURLVERSION_ATLEAST(7,18,0)
00229 } else if (!strcasecmp(value, "socks4a")) {
00230 ptype = CURLPROXY_SOCKS4A;
00231 #endif
00232 #if CURLVERSION_ATLEAST(7,18,0)
00233 } else if (!strcasecmp(value, "socks5")) {
00234 ptype = CURLPROXY_SOCKS5;
00235 #endif
00236 #if CURLVERSION_ATLEAST(7,18,0)
00237 } else if (!strncasecmp(value, "socks5", 6)) {
00238 ptype = CURLPROXY_SOCKS5_HOSTNAME;
00239 #endif
00240 }
00241
00242 if ((new = ast_calloc(1, sizeof(*new)))) {
00243 new->value = (void *)ptype;
00244 }
00245 } else {
00246
00247 goto yuck;
00248 }
00249 }
00250
00251
00252 if (!new) {
00253 return -1;
00254 }
00255
00256 new->key = key;
00257 } else {
00258 yuck:
00259 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
00260 return -1;
00261 }
00262
00263
00264 AST_LIST_LOCK(list);
00265 AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
00266 if (cur->key == new->key) {
00267 AST_LIST_REMOVE_CURRENT(list);
00268 free(cur);
00269 break;
00270 }
00271 }
00272 AST_LIST_TRAVERSE_SAFE_END
00273
00274
00275 ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
00276 AST_LIST_INSERT_TAIL(list, new, list);
00277 AST_LIST_UNLOCK(list);
00278
00279 return 0;
00280 }
00281
00282 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00283 {
00284 struct ast_datastore *store;
00285 struct global_curl_info *list[2] = { &global_curl_info, NULL };
00286 struct curl_settings *cur = NULL;
00287 CURLoption key;
00288 enum optiontype ot;
00289 int i;
00290
00291 if (parse_curlopt_key(data, &key, &ot)) {
00292 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
00293 return -1;
00294 }
00295
00296 if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00297 list[0] = store->data;
00298 list[1] = &global_curl_info;
00299 }
00300
00301 for (i = 0; i < 2; i++) {
00302 if (!list[i]) {
00303 break;
00304 }
00305 AST_LIST_LOCK(list[i]);
00306 AST_LIST_TRAVERSE(list[i], cur, list) {
00307 if (cur->key == key) {
00308 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
00309 snprintf(buf, len, "%ld", (long)cur->value);
00310 } else if (ot == OT_INTEGER_MS) {
00311 if ((long)cur->value % 1000 == 0) {
00312 snprintf(buf, len, "%ld", (long)cur->value / 1000);
00313 } else {
00314 snprintf(buf, len, "%.3f", (double)((long)cur->value) / 1000.0);
00315 }
00316 } else if (ot == OT_STRING) {
00317 ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
00318 ast_copy_string(buf, cur->value, len);
00319 } else if (key == CURLOPT_PROXYTYPE) {
00320 if (0) {
00321 #if CURLVERSION_ATLEAST(7,15,2)
00322 } else if ((long)cur->value == CURLPROXY_SOCKS4) {
00323 ast_copy_string(buf, "socks4", len);
00324 #endif
00325 #if CURLVERSION_ATLEAST(7,18,0)
00326 } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
00327 ast_copy_string(buf, "socks4a", len);
00328 #endif
00329 } else if ((long)cur->value == CURLPROXY_SOCKS5) {
00330 ast_copy_string(buf, "socks5", len);
00331 #if CURLVERSION_ATLEAST(7,18,0)
00332 } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
00333 ast_copy_string(buf, "socks5hostname", len);
00334 #endif
00335 #if CURLVERSION_ATLEAST(7,10,0)
00336 } else if ((long)cur->value == CURLPROXY_HTTP) {
00337 ast_copy_string(buf, "http", len);
00338 #endif
00339 } else {
00340 ast_copy_string(buf, "unknown", len);
00341 }
00342 }
00343 break;
00344 }
00345 }
00346 AST_LIST_UNLOCK(list[i]);
00347 if (cur) {
00348 break;
00349 }
00350 }
00351
00352 return cur ? 0 : -1;
00353 }
00354
00355 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
00356 {
00357 register int realsize = size * nmemb;
00358 struct ast_str **pstr = (struct ast_str **)data;
00359
00360 ast_debug(3, "Called with data=%p, str=%p, realsize=%d, len=%zu, used=%zu\n", data, *pstr, realsize, ast_str_size(*pstr), ast_str_strlen(*pstr));
00361
00362 ast_str_append_substr(pstr, 0, ptr, realsize);
00363
00364 ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
00365
00366 return realsize;
00367 }
00368
00369 static const char *global_useragent = "asterisk-libcurl-agent/1.0";
00370
00371 static int curl_instance_init(void *data)
00372 {
00373 CURL **curl = data;
00374
00375 if (!(*curl = curl_easy_init()))
00376 return -1;
00377
00378 curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
00379 curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
00380 curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
00381 curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
00382
00383 return 0;
00384 }
00385
00386 static void curl_instance_cleanup(void *data)
00387 {
00388 CURL **curl = data;
00389
00390 curl_easy_cleanup(*curl);
00391
00392 ast_free(data);
00393 }
00394
00395 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
00396
00397 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
00398 {
00399 struct ast_str *str = ast_str_create(16);
00400 int ret = -1;
00401 AST_DECLARE_APP_ARGS(args,
00402 AST_APP_ARG(url);
00403 AST_APP_ARG(postdata);
00404 );
00405 CURL **curl;
00406 struct curl_settings *cur;
00407 struct ast_datastore *store = NULL;
00408 int hashcompat = 0;
00409 AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
00410
00411 *buf = '\0';
00412
00413 if (ast_strlen_zero(info)) {
00414 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
00415 ast_free(str);
00416 return -1;
00417 }
00418
00419 AST_STANDARD_APP_ARGS(args, info);
00420
00421 if (chan) {
00422 ast_autoservice_start(chan);
00423 }
00424
00425 if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
00426 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
00427 return -1;
00428 }
00429
00430 AST_LIST_LOCK(&global_curl_info);
00431 AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
00432 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00433 hashcompat = (cur->value != NULL) ? 1 : 0;
00434 } else {
00435 curl_easy_setopt(*curl, cur->key, cur->value);
00436 }
00437 }
00438
00439 if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00440 list = store->data;
00441 AST_LIST_LOCK(list);
00442 AST_LIST_TRAVERSE(list, cur, list) {
00443 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00444 hashcompat = (cur->value != NULL) ? 1 : 0;
00445 } else {
00446 curl_easy_setopt(*curl, cur->key, cur->value);
00447 }
00448 }
00449 }
00450
00451 curl_easy_setopt(*curl, CURLOPT_URL, args.url);
00452 curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
00453
00454 if (args.postdata) {
00455 curl_easy_setopt(*curl, CURLOPT_POST, 1);
00456 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
00457 }
00458
00459 curl_easy_perform(*curl);
00460
00461 if (store) {
00462 AST_LIST_UNLOCK(list);
00463 }
00464 AST_LIST_UNLOCK(&global_curl_info);
00465
00466 if (args.postdata) {
00467 curl_easy_setopt(*curl, CURLOPT_POST, 0);
00468 }
00469
00470 if (ast_str_strlen(str)) {
00471 ast_str_trim_blanks(str);
00472
00473 ast_debug(3, "str='%s'\n", ast_str_buffer(str));
00474 if (hashcompat) {
00475 char *remainder = ast_str_buffer(str);
00476 char *piece;
00477 struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
00478 struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
00479 int rowcount = 0;
00480 while (fields && values && (piece = strsep(&remainder, "&"))) {
00481 char *name = strsep(&piece, "=");
00482 if (!piece) {
00483 piece = "";
00484 }
00485 ast_uri_decode(piece);
00486 ast_uri_decode(name);
00487 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", name);
00488 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", piece);
00489 rowcount++;
00490 }
00491 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
00492 ast_copy_string(buf, ast_str_buffer(values), len);
00493 ast_free(fields);
00494 ast_free(values);
00495 } else {
00496 ast_copy_string(buf, ast_str_buffer(str), len);
00497 }
00498 ret = 0;
00499 }
00500 ast_free(str);
00501
00502 if (chan)
00503 ast_autoservice_stop(chan);
00504
00505 return ret;
00506 }
00507
00508 struct ast_custom_function acf_curl = {
00509 .name = "CURL",
00510 .synopsis = "Retrieves the contents of a URL",
00511 .syntax = "CURL(url[,post-data])",
00512 .desc =
00513 " url - URL to retrieve\n"
00514 " post-data - Optional data to send as a POST (GET is default action)\n",
00515 .read = acf_curl_exec,
00516 };
00517
00518 struct ast_custom_function acf_curlopt = {
00519 .name = "CURLOPT",
00520 .synopsis = "Set options for use with the CURL() function",
00521 .syntax = "CURLOPT(<option>)",
00522 .desc =
00523 " cookie - Send cookie with request [none]\n"
00524 " conntimeout - Number of seconds to wait for connection\n"
00525 " dnstimeout - Number of seconds to wait for DNS response\n"
00526 " ftptext - For FTP, force a text transfer (boolean)\n"
00527 " ftptimeout - For FTP, the server response timeout\n"
00528 " header - Retrieve header information (boolean)\n"
00529 " httptimeout - Number of seconds to wait for HTTP response\n"
00530 " maxredirs - Maximum number of redirects to follow\n"
00531 " proxy - Hostname or IP to use as a proxy\n"
00532 " proxytype - http, socks4, or socks5\n"
00533 " proxyport - port number of the proxy\n"
00534 " proxyuserpwd - A <user>:<pass> to use for authentication\n"
00535 " referer - Referer URL to use for the request\n"
00536 " useragent - UserAgent string to use\n"
00537 " userpwd - A <user>:<pass> to use for authentication\n"
00538 " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
00539 " hashcompat - Result data will be compatible for use with HASH()\n"
00540 "",
00541 .read = acf_curlopt_read,
00542 .write = acf_curlopt_write,
00543 };
00544
00545 static int unload_module(void)
00546 {
00547 int res;
00548
00549 res = ast_custom_function_unregister(&acf_curl);
00550 res |= ast_custom_function_unregister(&acf_curlopt);
00551
00552 return res;
00553 }
00554
00555 static int load_module(void)
00556 {
00557 int res;
00558
00559 if (!ast_module_check("res_curl.so")) {
00560 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
00561 ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
00562 return AST_MODULE_LOAD_DECLINE;
00563 }
00564 }
00565
00566 res = ast_custom_function_register(&acf_curl);
00567 res |= ast_custom_function_register(&acf_curlopt);
00568
00569 return res;
00570 }
00571
00572 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Load external URL");
00573