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: 294989 $")
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_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_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 if (buf) {
00310 snprintf(buf, len, "%ld", (long) cur->value);
00311 } else {
00312 ast_str_set(bufstr, len, "%ld", (long) cur->value);
00313 }
00314 } else if (ot == OT_INTEGER_MS) {
00315 if ((long) cur->value % 1000 == 0) {
00316 if (buf) {
00317 snprintf(buf, len, "%ld", (long)cur->value / 1000);
00318 } else {
00319 ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
00320 }
00321 } else {
00322 if (buf) {
00323 snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00324 } else {
00325 ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00326 }
00327 }
00328 } else if (ot == OT_STRING) {
00329 ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
00330 if (buf) {
00331 ast_copy_string(buf, cur->value, len);
00332 } else {
00333 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
00334 }
00335 } else if (key == CURLOPT_PROXYTYPE) {
00336 if (0) {
00337 #if CURLVERSION_ATLEAST(7,15,2)
00338 } else if ((long)cur->value == CURLPROXY_SOCKS4) {
00339 if (buf) {
00340 ast_copy_string(buf, "socks4", len);
00341 } else {
00342 ast_str_set(bufstr, 0, "socks4");
00343 }
00344 #endif
00345 #if CURLVERSION_ATLEAST(7,18,0)
00346 } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
00347 if (buf) {
00348 ast_copy_string(buf, "socks4a", len);
00349 } else {
00350 ast_str_set(bufstr, 0, "socks4a");
00351 }
00352 #endif
00353 } else if ((long)cur->value == CURLPROXY_SOCKS5) {
00354 if (buf) {
00355 ast_copy_string(buf, "socks5", len);
00356 } else {
00357 ast_str_set(bufstr, 0, "socks5");
00358 }
00359 #if CURLVERSION_ATLEAST(7,18,0)
00360 } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
00361 if (buf) {
00362 ast_copy_string(buf, "socks5hostname", len);
00363 } else {
00364 ast_str_set(bufstr, 0, "socks5hostname");
00365 }
00366 #endif
00367 #if CURLVERSION_ATLEAST(7,10,0)
00368 } else if ((long)cur->value == CURLPROXY_HTTP) {
00369 if (buf) {
00370 ast_copy_string(buf, "http", len);
00371 } else {
00372 ast_str_set(bufstr, 0, "http");
00373 }
00374 #endif
00375 } else {
00376 if (buf) {
00377 ast_copy_string(buf, "unknown", len);
00378 } else {
00379 ast_str_set(bufstr, 0, "unknown");
00380 }
00381 }
00382 }
00383 break;
00384 }
00385 }
00386 AST_LIST_UNLOCK(list[i]);
00387 if (cur) {
00388 break;
00389 }
00390 }
00391
00392 return cur ? 0 : -1;
00393 }
00394
00395 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00396 {
00397 return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
00398 }
00399
00400 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
00401 {
00402 return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
00403 }
00404
00405 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
00406 {
00407 register int realsize = size * nmemb;
00408 struct ast_str **pstr = (struct ast_str **)data;
00409
00410 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));
00411
00412 ast_str_append_substr(pstr, 0, ptr, realsize);
00413
00414 ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
00415
00416 return realsize;
00417 }
00418
00419 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
00420
00421 static int curl_instance_init(void *data)
00422 {
00423 CURL **curl = data;
00424
00425 if (!(*curl = curl_easy_init()))
00426 return -1;
00427
00428 curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
00429 curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
00430 curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
00431 curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
00432
00433 return 0;
00434 }
00435
00436 static void curl_instance_cleanup(void *data)
00437 {
00438 CURL **curl = data;
00439
00440 curl_easy_cleanup(*curl);
00441
00442 ast_free(data);
00443 }
00444
00445 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
00446
00447 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
00448 {
00449 struct ast_str *str = ast_str_create(16);
00450 int ret = -1;
00451 AST_DECLARE_APP_ARGS(args,
00452 AST_APP_ARG(url);
00453 AST_APP_ARG(postdata);
00454 );
00455 CURL **curl;
00456 struct curl_settings *cur;
00457 struct ast_datastore *store = NULL;
00458 int hashcompat = 0;
00459 AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
00460
00461 if (buf) {
00462 *buf = '\0';
00463 }
00464
00465 if (ast_strlen_zero(info)) {
00466 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
00467 ast_free(str);
00468 return -1;
00469 }
00470
00471 AST_STANDARD_APP_ARGS(args, info);
00472
00473 if (chan) {
00474 ast_autoservice_start(chan);
00475 }
00476
00477 if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
00478 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
00479 return -1;
00480 }
00481
00482 AST_LIST_LOCK(&global_curl_info);
00483 AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
00484 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00485 hashcompat = (cur->value != NULL) ? 1 : 0;
00486 } else {
00487 curl_easy_setopt(*curl, cur->key, cur->value);
00488 }
00489 }
00490
00491 if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00492 list = store->data;
00493 AST_LIST_LOCK(list);
00494 AST_LIST_TRAVERSE(list, cur, list) {
00495 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00496 hashcompat = (cur->value != NULL) ? 1 : 0;
00497 } else {
00498 curl_easy_setopt(*curl, cur->key, cur->value);
00499 }
00500 }
00501 }
00502
00503 curl_easy_setopt(*curl, CURLOPT_URL, args.url);
00504 curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
00505
00506 if (args.postdata) {
00507 curl_easy_setopt(*curl, CURLOPT_POST, 1);
00508 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
00509 }
00510
00511 curl_easy_perform(*curl);
00512
00513 if (store) {
00514 AST_LIST_UNLOCK(list);
00515 }
00516 AST_LIST_UNLOCK(&global_curl_info);
00517
00518 if (args.postdata) {
00519 curl_easy_setopt(*curl, CURLOPT_POST, 0);
00520 }
00521
00522 if (ast_str_strlen(str)) {
00523 ast_str_trim_blanks(str);
00524
00525 ast_debug(3, "str='%s'\n", ast_str_buffer(str));
00526 if (hashcompat) {
00527 char *remainder = ast_str_buffer(str);
00528 char *piece;
00529 struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
00530 struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
00531 int rowcount = 0;
00532 while (fields && values && (piece = strsep(&remainder, "&"))) {
00533 char *name = strsep(&piece, "=");
00534 if (!piece) {
00535 piece = "";
00536 }
00537 ast_uri_decode(piece);
00538 ast_uri_decode(name);
00539 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", name);
00540 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", piece);
00541 rowcount++;
00542 }
00543 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
00544 if (buf) {
00545 ast_copy_string(buf, ast_str_buffer(values), len);
00546 } else {
00547 ast_str_set(input_str, len, "%s", ast_str_buffer(values));
00548 }
00549 ast_free(fields);
00550 ast_free(values);
00551 } else {
00552 if (buf) {
00553 ast_copy_string(buf, ast_str_buffer(str), len);
00554 } else {
00555 ast_str_set(input_str, len, "%s", ast_str_buffer(str));
00556 }
00557 }
00558 ret = 0;
00559 }
00560 ast_free(str);
00561
00562 if (chan)
00563 ast_autoservice_stop(chan);
00564
00565 return ret;
00566 }
00567
00568 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
00569 {
00570 return acf_curl_helper(chan, cmd, info, buf, NULL, len);
00571 }
00572
00573 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
00574 {
00575 return acf_curl_helper(chan, cmd, info, NULL, buf, len);
00576 }
00577
00578 static struct ast_custom_function acf_curl = {
00579 .name = "CURL",
00580 .synopsis = "Retrieves the contents of a URL",
00581 .syntax = "CURL(url[,post-data])",
00582 .desc =
00583 " url - URL to retrieve\n"
00584 " post-data - Optional data to send as a POST (GET is default action)\n",
00585 .read = acf_curl_exec,
00586 .read2 = acf_curl2_exec,
00587 };
00588
00589 static struct ast_custom_function acf_curlopt = {
00590 .name = "CURLOPT",
00591 .synopsis = "Set options for use with the CURL() function",
00592 .syntax = "CURLOPT(<option>)",
00593 .desc =
00594 " cookie - Send cookie with request [none]\n"
00595 " conntimeout - Number of seconds to wait for connection\n"
00596 " dnstimeout - Number of seconds to wait for DNS response\n"
00597 " ftptext - For FTP, force a text transfer (boolean)\n"
00598 " ftptimeout - For FTP, the server response timeout\n"
00599 " header - Retrieve header information (boolean)\n"
00600 " httptimeout - Number of seconds to wait for HTTP response\n"
00601 " maxredirs - Maximum number of redirects to follow\n"
00602 " proxy - Hostname or IP to use as a proxy\n"
00603 " proxytype - http, socks4, or socks5\n"
00604 " proxyport - port number of the proxy\n"
00605 " proxyuserpwd - A <user>:<pass> to use for authentication\n"
00606 " referer - Referer URL to use for the request\n"
00607 " useragent - UserAgent string to use\n"
00608 " userpwd - A <user>:<pass> to use for authentication\n"
00609 " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
00610 " hashcompat - Result data will be compatible for use with HASH()\n"
00611 "",
00612 .read = acf_curlopt_read,
00613 .read2 = acf_curlopt_read2,
00614 .write = acf_curlopt_write,
00615 };
00616
00617 static int unload_module(void)
00618 {
00619 int res;
00620
00621 res = ast_custom_function_unregister(&acf_curl);
00622 res |= ast_custom_function_unregister(&acf_curlopt);
00623
00624 return res;
00625 }
00626
00627 static int load_module(void)
00628 {
00629 int res;
00630
00631 if (!ast_module_check("res_curl.so")) {
00632 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
00633 ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
00634 return AST_MODULE_LOAD_DECLINE;
00635 }
00636 }
00637
00638 res = ast_custom_function_register(&acf_curl);
00639 res |= ast_custom_function_register(&acf_curlopt);
00640
00641 return res;
00642 }
00643
00644 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
00645 .load = load_module,
00646 .unload = unload_module,
00647 .load_pri = AST_MODPRI_REALTIME_DEPEND2,
00648 );
00649