func_pjsip_endpoint: Add PJSIP_ENDPOINT function for querying endpoint details
[asterisk/asterisk.git] / funcs / func_curl.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C)  2004 - 2006, Tilghman Lesher
5  *
6  * Tilghman Lesher <curl-20050919@the-tilghman.com>
7  * and Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option)
8  *
9  * app_curl.c is distributed with no restrictions on usage or
10  * redistribution.
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  */
19
20 /*! \file
21  * 
22  * \brief Curl - Load a URL
23  *
24  * \author Tilghman Lesher <curl-20050919@the-tilghman.com>
25  *
26  * \note Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option) 
27  *
28  * \extref Depends on the CURL library  - http://curl.haxx.se/
29  * 
30  * \ingroup functions
31  */
32  
33 /*** MODULEINFO
34         <depend>curl</depend>
35         <support_level>core</support_level>
36  ***/
37
38 #include "asterisk.h"
39
40 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
41
42 #include <curl/curl.h>
43
44 #include "asterisk/lock.h"
45 #include "asterisk/file.h"
46 #include "asterisk/channel.h"
47 #include "asterisk/pbx.h"
48 #include "asterisk/cli.h"
49 #include "asterisk/module.h"
50 #include "asterisk/app.h"
51 #include "asterisk/utils.h"
52 #include "asterisk/threadstorage.h"
53
54 /*** DOCUMENTATION
55         <function name="CURL" language="en_US">
56                 <synopsis>
57                         Retrieve content from a remote web or ftp server
58                 </synopsis>
59                 <syntax>
60                         <parameter name="url" required="true" />
61                         <parameter name="post-data">
62                                 <para>If specified, an <literal>HTTP POST</literal> will be
63                                 performed with the content of
64                                 <replaceable>post-data</replaceable>, instead of an
65                                 <literal>HTTP GET</literal> (default).</para>
66                         </parameter>
67                 </syntax>
68                 <description />
69                 <see-also>
70                         <ref type="function">CURLOPT</ref>
71                 </see-also>
72         </function>
73         <function name="CURLOPT" language="en_US">
74                 <synopsis>
75                         Sets various options for future invocations of CURL.
76                 </synopsis>
77                 <syntax>
78                         <parameter name="key" required="yes">
79                                 <enumlist>
80                                         <enum name="cookie">
81                                                 <para>A cookie to send with the request.  Multiple
82                                                 cookies are supported.</para>
83                                         </enum>
84                                         <enum name="conntimeout">
85                                                 <para>Number of seconds to wait for a connection to succeed</para>
86                                         </enum>
87                                         <enum name="dnstimeout">
88                                                 <para>Number of seconds to wait for DNS to be resolved</para>
89                                         </enum>
90                                         <enum name="ftptext">
91                                                 <para>For FTP URIs, force a text transfer (boolean)</para>
92                                         </enum>
93                                         <enum name="ftptimeout">
94                                                 <para>For FTP URIs, number of seconds to wait for a
95                                                 server response</para>
96                                         </enum>
97                                         <enum name="header">
98                                                 <para>Include header information in the result
99                                                 (boolean)</para>
100                                         </enum>
101                                         <enum name="httptimeout">
102                                                 <para>For HTTP(S) URIs, number of seconds to wait for a
103                                                 server response</para>
104                                         </enum>
105                                         <enum name="maxredirs">
106                                                 <para>Maximum number of redirects to follow</para>
107                                         </enum>
108                                         <enum name="proxy">
109                                                 <para>Hostname or IP address to use as a proxy server</para>
110                                         </enum>
111                                         <enum name="proxytype">
112                                                 <para>Type of <literal>proxy</literal></para>
113                                                 <enumlist>
114                                                         <enum name="http" />
115                                                         <enum name="socks4" />
116                                                         <enum name="socks5" />
117                                                 </enumlist>
118                                         </enum>
119                                         <enum name="proxyport">
120                                                 <para>Port number of the <literal>proxy</literal></para>
121                                         </enum>
122                                         <enum name="proxyuserpwd">
123                                                 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
124                                                 combination to use for authenticating requests through a
125                                                 <literal>proxy</literal></para>
126                                         </enum>
127                                         <enum name="referer">
128                                                 <para>Referer URL to use for the request</para>
129                                         </enum>
130                                         <enum name="useragent">
131                                                 <para>UserAgent string to use for the request</para>
132                                         </enum>
133                                         <enum name="userpwd">
134                                                 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
135                                                 to use for authentication when the server response to
136                                                 an initial request indicates a 401 status code.</para>
137                                         </enum>
138                                         <enum name="ssl_verifypeer">
139                                                 <para>Whether to verify the server certificate against
140                                                 a list of known root certificate authorities (boolean).</para>
141                                         </enum>
142                                         <enum name="hashcompat">
143                                                 <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
144                                                 format, reformat the response such that it can be used
145                                                 by the <literal>HASH</literal> function.</para>
146                                                 <enumlist>
147                                                         <enum name="yes" />
148                                                         <enum name="no" />
149                                                         <enum name="legacy">
150                                                                 <para>Also translate <literal>+</literal> to the
151                                                                 space character, in violation of current RFC
152                                                                 standards.</para>
153                                                         </enum>
154                                                 </enumlist>
155                                         </enum>
156                                 </enumlist>
157                         </parameter>
158                 </syntax>
159                 <description>
160                         <para>Options may be set globally or per channel.  Per-channel
161                         settings will override global settings.</para>
162                 </description>
163                 <see-also>
164                         <ref type="function">CURL</ref>
165                         <ref type="function">HASH</ref>
166                 </see-also>
167         </function>
168  ***/
169
170 #define CURLVERSION_ATLEAST(a,b,c) \
171         ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
172
173 #define CURLOPT_SPECIAL_HASHCOMPAT -500
174
175 static void curlds_free(void *data);
176
177 static const struct ast_datastore_info curl_info = {
178         .type = "CURL",
179         .destroy = curlds_free,
180 };
181
182 struct curl_settings {
183         AST_LIST_ENTRY(curl_settings) list;
184         CURLoption key;
185         void *value;
186 };
187
188 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
189
190 static void curlds_free(void *data)
191 {
192         AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
193         struct curl_settings *setting;
194         if (!list) {
195                 return;
196         }
197         while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
198                 free(setting);
199         }
200         AST_LIST_HEAD_DESTROY(list);
201 }
202
203 enum optiontype {
204         OT_BOOLEAN,
205         OT_INTEGER,
206         OT_INTEGER_MS,
207         OT_STRING,
208         OT_ENUM,
209 };
210
211 enum hashcompat {
212         HASHCOMPAT_NO = 0,
213         HASHCOMPAT_YES,
214         HASHCOMPAT_LEGACY,
215 };
216
217 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
218 {
219         if (!strcasecmp(name, "header")) {
220                 *key = CURLOPT_HEADER;
221                 *ot = OT_BOOLEAN;
222         } else if (!strcasecmp(name, "proxy")) {
223                 *key = CURLOPT_PROXY;
224                 *ot = OT_STRING;
225         } else if (!strcasecmp(name, "proxyport")) {
226                 *key = CURLOPT_PROXYPORT;
227                 *ot = OT_INTEGER;
228         } else if (!strcasecmp(name, "proxytype")) {
229                 *key = CURLOPT_PROXYTYPE;
230                 *ot = OT_ENUM;
231         } else if (!strcasecmp(name, "dnstimeout")) {
232                 *key = CURLOPT_DNS_CACHE_TIMEOUT;
233                 *ot = OT_INTEGER;
234         } else if (!strcasecmp(name, "userpwd")) {
235                 *key = CURLOPT_USERPWD;
236                 *ot = OT_STRING;
237         } else if (!strcasecmp(name, "proxyuserpwd")) {
238                 *key = CURLOPT_PROXYUSERPWD;
239                 *ot = OT_STRING;
240         } else if (!strcasecmp(name, "maxredirs")) {
241                 *key = CURLOPT_MAXREDIRS;
242                 *ot = OT_INTEGER;
243         } else if (!strcasecmp(name, "referer")) {
244                 *key = CURLOPT_REFERER;
245                 *ot = OT_STRING;
246         } else if (!strcasecmp(name, "useragent")) {
247                 *key = CURLOPT_USERAGENT;
248                 *ot = OT_STRING;
249         } else if (!strcasecmp(name, "cookie")) {
250                 *key = CURLOPT_COOKIE;
251                 *ot = OT_STRING;
252         } else if (!strcasecmp(name, "ftptimeout")) {
253                 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
254                 *ot = OT_INTEGER;
255         } else if (!strcasecmp(name, "httptimeout")) {
256 #if CURLVERSION_ATLEAST(7,16,2)
257                 *key = CURLOPT_TIMEOUT_MS;
258                 *ot = OT_INTEGER_MS;
259 #else
260                 *key = CURLOPT_TIMEOUT;
261                 *ot = OT_INTEGER;
262 #endif
263         } else if (!strcasecmp(name, "conntimeout")) {
264 #if CURLVERSION_ATLEAST(7,16,2)
265                 *key = CURLOPT_CONNECTTIMEOUT_MS;
266                 *ot = OT_INTEGER_MS;
267 #else
268                 *key = CURLOPT_CONNECTTIMEOUT;
269                 *ot = OT_INTEGER;
270 #endif
271         } else if (!strcasecmp(name, "ftptext")) {
272                 *key = CURLOPT_TRANSFERTEXT;
273                 *ot = OT_BOOLEAN;
274         } else if (!strcasecmp(name, "ssl_verifypeer")) {
275                 *key = CURLOPT_SSL_VERIFYPEER;
276                 *ot = OT_BOOLEAN;
277         } else if (!strcasecmp(name, "hashcompat")) {
278                 *key = CURLOPT_SPECIAL_HASHCOMPAT;
279                 *ot = OT_ENUM;
280         } else {
281                 return -1;
282         }
283         return 0;
284 }
285
286 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
287 {
288         struct ast_datastore *store;
289         struct global_curl_info *list;
290         struct curl_settings *cur, *new = NULL;
291         CURLoption key;
292         enum optiontype ot;
293
294         if (chan) {
295                 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
296                         /* Create a new datastore */
297                         if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
298                                 ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
299                                 return -1;
300                         }
301
302                         if (!(list = ast_calloc(1, sizeof(*list)))) {
303                                 ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
304                                 ast_datastore_free(store);
305                                 return -1;
306                         }
307
308                         store->data = list;
309                         AST_LIST_HEAD_INIT(list);
310                         ast_channel_datastore_add(chan, store);
311                 } else {
312                         list = store->data;
313                 }
314         } else {
315                 /* Populate the global structure */
316                 list = &global_curl_info;
317         }
318
319         if (!parse_curlopt_key(name, &key, &ot)) {
320                 if (ot == OT_BOOLEAN) {
321                         if ((new = ast_calloc(1, sizeof(*new)))) {
322                                 new->value = (void *)((long) ast_true(value));
323                         }
324                 } else if (ot == OT_INTEGER) {
325                         long tmp = atol(value);
326                         if ((new = ast_calloc(1, sizeof(*new)))) {
327                                 new->value = (void *)tmp;
328                         }
329                 } else if (ot == OT_INTEGER_MS) {
330                         long tmp = atof(value) * 1000.0;
331                         if ((new = ast_calloc(1, sizeof(*new)))) {
332                                 new->value = (void *)tmp;
333                         }
334                 } else if (ot == OT_STRING) {
335                         if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
336                                 new->value = (char *)new + sizeof(*new);
337                                 strcpy(new->value, value);
338                         }
339                 } else if (ot == OT_ENUM) {
340                         if (key == CURLOPT_PROXYTYPE) {
341                                 long ptype =
342 #if CURLVERSION_ATLEAST(7,10,0)
343                                         CURLPROXY_HTTP;
344 #else
345                                         CURLPROXY_SOCKS5;
346 #endif
347                                 if (0) {
348 #if CURLVERSION_ATLEAST(7,15,2)
349                                 } else if (!strcasecmp(value, "socks4")) {
350                                         ptype = CURLPROXY_SOCKS4;
351 #endif
352 #if CURLVERSION_ATLEAST(7,18,0)
353                                 } else if (!strcasecmp(value, "socks4a")) {
354                                         ptype = CURLPROXY_SOCKS4A;
355 #endif
356 #if CURLVERSION_ATLEAST(7,18,0)
357                                 } else if (!strcasecmp(value, "socks5")) {
358                                         ptype = CURLPROXY_SOCKS5;
359 #endif
360 #if CURLVERSION_ATLEAST(7,18,0)
361                                 } else if (!strncasecmp(value, "socks5", 6)) {
362                                         ptype = CURLPROXY_SOCKS5_HOSTNAME;
363 #endif
364                                 }
365
366                                 if ((new = ast_calloc(1, sizeof(*new)))) {
367                                         new->value = (void *)ptype;
368                                 }
369                         } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
370                                 if ((new = ast_calloc(1, sizeof(*new)))) {
371                                         new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
372                                 }
373                         } else {
374                                 /* Highly unlikely */
375                                 goto yuck;
376                         }
377                 }
378
379                 /* Memory allocation error */
380                 if (!new) {
381                         return -1;
382                 }
383
384                 new->key = key;
385         } else {
386 yuck:
387                 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
388                 return -1;
389         }
390
391         /* Remove any existing entry */
392         AST_LIST_LOCK(list);
393         AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
394                 if (cur->key == new->key) {
395                         AST_LIST_REMOVE_CURRENT(list);
396                         free(cur);
397                         break;
398                 }
399         }
400         AST_LIST_TRAVERSE_SAFE_END
401
402         /* Insert new entry */
403         ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
404         AST_LIST_INSERT_TAIL(list, new, list);
405         AST_LIST_UNLOCK(list);
406
407         return 0;
408 }
409
410 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
411 {
412         struct ast_datastore *store;
413         struct global_curl_info *list[2] = { &global_curl_info, NULL };
414         struct curl_settings *cur = NULL;
415         CURLoption key;
416         enum optiontype ot;
417         int i;
418
419         if (parse_curlopt_key(data, &key, &ot)) {
420                 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
421                 return -1;
422         }
423
424         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
425                 list[0] = store->data;
426                 list[1] = &global_curl_info;
427         }
428
429         for (i = 0; i < 2; i++) {
430                 if (!list[i]) {
431                         break;
432                 }
433                 AST_LIST_LOCK(list[i]);
434                 AST_LIST_TRAVERSE(list[i], cur, list) {
435                         if (cur->key == key) {
436                                 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
437                                         if (buf) {
438                                                 snprintf(buf, len, "%ld", (long) cur->value);
439                                         } else {
440                                                 ast_str_set(bufstr, len, "%ld", (long) cur->value);
441                                         }
442                                 } else if (ot == OT_INTEGER_MS) {
443                                         if ((long) cur->value % 1000 == 0) {
444                                                 if (buf) {
445                                                         snprintf(buf, len, "%ld", (long)cur->value / 1000);
446                                                 } else {
447                                                         ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
448                                                 }
449                                         } else {
450                                                 if (buf) {
451                                                         snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
452                                                 } else {
453                                                         ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
454                                                 }
455                                         }
456                                 } else if (ot == OT_STRING) {
457                                         ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
458                                         if (buf) {
459                                                 ast_copy_string(buf, cur->value, len);
460                                         } else {
461                                                 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
462                                         }
463                                 } else if (key == CURLOPT_PROXYTYPE) {
464                                         const char *strval = "unknown";
465                                         if (0) {
466 #if CURLVERSION_ATLEAST(7,15,2)
467                                         } else if ((long)cur->value == CURLPROXY_SOCKS4) {
468                                                 strval = "socks4";
469 #endif
470 #if CURLVERSION_ATLEAST(7,18,0)
471                                         } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
472                                                 strval = "socks4a";
473 #endif
474                                         } else if ((long)cur->value == CURLPROXY_SOCKS5) {
475                                                 strval = "socks5";
476 #if CURLVERSION_ATLEAST(7,18,0)
477                                         } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
478                                                 strval = "socks5hostname";
479 #endif
480 #if CURLVERSION_ATLEAST(7,10,0)
481                                         } else if ((long)cur->value == CURLPROXY_HTTP) {
482                                                 strval = "http";
483 #endif
484                                         }
485                                         if (buf) {
486                                                 ast_copy_string(buf, strval, len);
487                                         } else {
488                                                 ast_str_set(bufstr, 0, "%s", strval);
489                                         }
490                                 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
491                                         const char *strval = "unknown";
492                                         if ((long) cur->value == HASHCOMPAT_LEGACY) {
493                                                 strval = "legacy";
494                                         } else if ((long) cur->value == HASHCOMPAT_YES) {
495                                                 strval = "yes";
496                                         } else if ((long) cur->value == HASHCOMPAT_NO) {
497                                                 strval = "no";
498                                         }
499                                         if (buf) {
500                                                 ast_copy_string(buf, strval, len);
501                                         } else {
502                                                 ast_str_set(bufstr, 0, "%s", strval);
503                                         }
504                                 }
505                                 break;
506                         }
507                 }
508                 AST_LIST_UNLOCK(list[i]);
509                 if (cur) {
510                         break;
511                 }
512         }
513
514         return cur ? 0 : -1;
515 }
516
517 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
518 {
519         return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
520 }
521
522 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
523 {
524         return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
525 }
526
527 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
528 {
529         register int realsize = size * nmemb;
530         struct ast_str **pstr = (struct ast_str **)data;
531
532         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));
533
534         ast_str_append_substr(pstr, 0, ptr, realsize);
535
536         ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
537
538         return realsize;
539 }
540
541 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
542
543 static int curl_instance_init(void *data)
544 {
545         CURL **curl = data;
546
547         if (!(*curl = curl_easy_init()))
548                 return -1;
549
550         curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
551         curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
552         curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
553         curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
554
555         return 0;
556 }
557
558 static void curl_instance_cleanup(void *data)
559 {
560         CURL **curl = data;
561
562         curl_easy_cleanup(*curl);
563
564         ast_free(data);
565 }
566
567 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
568 AST_THREADSTORAGE(thread_escapebuf);
569
570 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
571 {
572         struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
573         struct ast_str *str = ast_str_create(16);
574         int ret = -1;
575         AST_DECLARE_APP_ARGS(args,
576                 AST_APP_ARG(url);
577                 AST_APP_ARG(postdata);
578         );
579         CURL **curl;
580         struct curl_settings *cur;
581         struct ast_datastore *store = NULL;
582         int hashcompat = 0;
583         AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
584         char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
585
586         if (buf) {
587                 *buf = '\0';
588         }
589
590         if (!str) {
591                 return -1;
592         }
593
594         if (!escapebuf) {
595                 ast_free(str);
596                 return -1;
597         }
598
599         if (ast_strlen_zero(info)) {
600                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
601                 ast_free(str);
602                 return -1;
603         }
604
605         AST_STANDARD_APP_ARGS(args, info);
606
607         if (chan) {
608                 ast_autoservice_start(chan);
609         }
610
611         if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
612                 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
613                 ast_free(str);
614                 return -1;
615         }
616
617         AST_LIST_LOCK(&global_curl_info);
618         AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
619                 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
620                         hashcompat = (long) cur->value;
621                 } else {
622                         curl_easy_setopt(*curl, cur->key, cur->value);
623                 }
624         }
625
626         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
627                 list = store->data;
628                 AST_LIST_LOCK(list);
629                 AST_LIST_TRAVERSE(list, cur, list) {
630                         if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
631                                 hashcompat = (long) cur->value;
632                         } else {
633                                 curl_easy_setopt(*curl, cur->key, cur->value);
634                         }
635                 }
636         }
637
638         curl_easy_setopt(*curl, CURLOPT_URL, args.url);
639         curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
640
641         if (args.postdata) {
642                 curl_easy_setopt(*curl, CURLOPT_POST, 1);
643                 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
644         }
645
646         /* Temporarily assign a buffer for curl to write errors to. */
647         curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
648         curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
649
650         if (curl_easy_perform(*curl) != 0) {
651                 ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args.url);
652         }
653
654         /* Reset buffer to NULL so curl doesn't try to write to it when the
655          * buffer is deallocated. Documentation is vague about allowing NULL
656          * here, but the source allows it. See: "typecheck: allow NULL to unset
657          * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
658         curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
659
660         if (store) {
661                 AST_LIST_UNLOCK(list);
662         }
663         AST_LIST_UNLOCK(&global_curl_info);
664
665         if (args.postdata) {
666                 curl_easy_setopt(*curl, CURLOPT_POST, 0);
667         }
668
669         if (ast_str_strlen(str)) {
670                 ast_str_trim_blanks(str);
671
672                 ast_debug(3, "str='%s'\n", ast_str_buffer(str));
673                 if (hashcompat) {
674                         char *remainder = ast_str_buffer(str);
675                         char *piece;
676                         struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
677                         struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
678                         int rowcount = 0;
679                         while (fields && values && (piece = strsep(&remainder, "&"))) {
680                                 char *name = strsep(&piece, "=");
681                                 struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
682                                 if (piece) {
683                                         ast_uri_decode(piece, mode);
684                                 }
685                                 ast_uri_decode(name, mode);
686                                 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
687                                 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
688                                 rowcount++;
689                         }
690                         pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
691                         if (buf) {
692                                 ast_copy_string(buf, ast_str_buffer(values), len);
693                         } else {
694                                 ast_str_set(input_str, len, "%s", ast_str_buffer(values));
695                         }
696                         ast_free(fields);
697                         ast_free(values);
698                 } else {
699                         if (buf) {
700                                 ast_copy_string(buf, ast_str_buffer(str), len);
701                         } else {
702                                 ast_str_set(input_str, len, "%s", ast_str_buffer(str));
703                         }
704                 }
705                 ret = 0;
706         }
707         ast_free(str);
708
709         if (chan)
710                 ast_autoservice_stop(chan);
711
712         return ret;
713 }
714
715 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
716 {
717         return acf_curl_helper(chan, cmd, info, buf, NULL, len);
718 }
719
720 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
721 {
722         return acf_curl_helper(chan, cmd, info, NULL, buf, len);
723 }
724
725 static struct ast_custom_function acf_curl = {
726         .name = "CURL",
727         .synopsis = "Retrieves the contents of a URL",
728         .syntax = "CURL(url[,post-data])",
729         .desc =
730         "  url       - URL to retrieve\n"
731         "  post-data - Optional data to send as a POST (GET is default action)\n",
732         .read = acf_curl_exec,
733         .read2 = acf_curl2_exec,
734 };
735
736 static struct ast_custom_function acf_curlopt = {
737         .name = "CURLOPT",
738         .synopsis = "Set options for use with the CURL() function",
739         .syntax = "CURLOPT(<option>)",
740         .desc =
741 "  cookie         - Send cookie with request [none]\n"
742 "  conntimeout    - Number of seconds to wait for connection\n"
743 "  dnstimeout     - Number of seconds to wait for DNS response\n"
744 "  ftptext        - For FTP, force a text transfer (boolean)\n"
745 "  ftptimeout     - For FTP, the server response timeout\n"
746 "  header         - Retrieve header information (boolean)\n"
747 "  httptimeout    - Number of seconds to wait for HTTP response\n"
748 "  maxredirs      - Maximum number of redirects to follow\n"
749 "  proxy          - Hostname or IP to use as a proxy\n"
750 "  proxytype      - http, socks4, or socks5\n"
751 "  proxyport      - port number of the proxy\n"
752 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
753 "  referer        - Referer URL to use for the request\n"
754 "  useragent      - UserAgent string to use\n"
755 "  userpwd        - A <user>:<pass> to use for authentication\n"
756 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
757 "  hashcompat     - Result data will be compatible for use with HASH()\n"
758 "                 - if value is \"legacy\", will translate '+' to ' '\n"
759 "",
760         .read = acf_curlopt_read,
761         .read2 = acf_curlopt_read2,
762         .write = acf_curlopt_write,
763 };
764
765 static int unload_module(void)
766 {
767         int res;
768
769         res = ast_custom_function_unregister(&acf_curl);
770         res |= ast_custom_function_unregister(&acf_curlopt);
771
772         return res;
773 }
774
775 static int load_module(void)
776 {
777         int res;
778
779         if (!ast_module_check("res_curl.so")) {
780                 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
781                         ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
782                         return AST_MODULE_LOAD_DECLINE;
783                 }
784         }
785
786         res = ast_custom_function_register(&acf_curl);
787         res |= ast_custom_function_register(&acf_curlopt);
788
789         return res;
790 }
791
792 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
793                 .load = load_module,
794                 .unload = unload_module,
795                 .load_pri = AST_MODPRI_REALTIME_DEPEND2,
796         );
797