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