Merge "jansson-bundled: Patch for off-nominal crash."
[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 #include <curl/curl.h>
41
42 #include "asterisk/lock.h"
43 #include "asterisk/file.h"
44 #include "asterisk/channel.h"
45 #include "asterisk/pbx.h"
46 #include "asterisk/cli.h"
47 #include "asterisk/module.h"
48 #include "asterisk/app.h"
49 #include "asterisk/utils.h"
50 #include "asterisk/threadstorage.h"
51 #include "asterisk/test.h"
52
53 /*** DOCUMENTATION
54         <function name="CURL" language="en_US">
55                 <synopsis>
56                         Retrieve content from a remote web or ftp server
57                 </synopsis>
58                 <syntax>
59                         <parameter name="url" required="true">
60                                 <para>The full URL for the resource to retrieve.</para>
61                         </parameter>
62                         <parameter name="post-data">
63                                 <para><emphasis>Read Only</emphasis></para>
64                                 <para>If specified, an <literal>HTTP POST</literal> will be
65                                 performed with the content of
66                                 <replaceable>post-data</replaceable>, instead of an
67                                 <literal>HTTP GET</literal> (default).</para>
68                         </parameter>
69                 </syntax>
70                 <description>
71                         <para>When this function is read, a <literal>HTTP GET</literal>
72                         (by default) will be used to retrieve the contents of the provided
73                         <replaceable>url</replaceable>. The contents are returned as the
74                         result of the function.</para>
75                         <example title="Displaying contents of a page" language="text">
76                         exten => s,1,Verbose(0, ${CURL(http://localhost:8088/static/astman.css)})
77                         </example>
78                         <para>When this function is written to, a <literal>HTTP GET</literal>
79                         will be used to retrieve the contents of the provided
80                         <replaceable>url</replaceable>. The value written to the function
81                         specifies the destination file of the cURL'd resource.</para>
82                         <example title="Retrieving a file" language="text">
83                         exten => s,1,Set(CURL(http://localhost:8088/static/astman.css)=/var/spool/asterisk/tmp/astman.css))
84                         </example>
85                         <note>
86                                 <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
87                                 is set to <literal>no</literal>, this function can only be written to from the
88                                 dialplan, and not directly from external protocols. Read operations are
89                                 unaffected.</para>
90                         </note>
91                 </description>
92                 <see-also>
93                         <ref type="function">CURLOPT</ref>
94                 </see-also>
95         </function>
96         <function name="CURLOPT" language="en_US">
97                 <synopsis>
98                         Sets various options for future invocations of CURL.
99                 </synopsis>
100                 <syntax>
101                         <parameter name="key" required="yes">
102                                 <enumlist>
103                                         <enum name="cookie">
104                                                 <para>A cookie to send with the request.  Multiple
105                                                 cookies are supported.</para>
106                                         </enum>
107                                         <enum name="conntimeout">
108                                                 <para>Number of seconds to wait for a connection to succeed</para>
109                                         </enum>
110                                         <enum name="dnstimeout">
111                                                 <para>Number of seconds to wait for DNS to be resolved</para>
112                                         </enum>
113                                         <enum name="ftptext">
114                                                 <para>For FTP URIs, force a text transfer (boolean)</para>
115                                         </enum>
116                                         <enum name="ftptimeout">
117                                                 <para>For FTP URIs, number of seconds to wait for a
118                                                 server response</para>
119                                         </enum>
120                                         <enum name="header">
121                                                 <para>Include header information in the result
122                                                 (boolean)</para>
123                                         </enum>
124                                         <enum name="httptimeout">
125                                                 <para>For HTTP(S) URIs, number of seconds to wait for a
126                                                 server response</para>
127                                         </enum>
128                                         <enum name="maxredirs">
129                                                 <para>Maximum number of redirects to follow</para>
130                                         </enum>
131                                         <enum name="proxy">
132                                                 <para>Hostname or IP address to use as a proxy server</para>
133                                         </enum>
134                                         <enum name="proxytype">
135                                                 <para>Type of <literal>proxy</literal></para>
136                                                 <enumlist>
137                                                         <enum name="http" />
138                                                         <enum name="socks4" />
139                                                         <enum name="socks5" />
140                                                 </enumlist>
141                                         </enum>
142                                         <enum name="proxyport">
143                                                 <para>Port number of the <literal>proxy</literal></para>
144                                         </enum>
145                                         <enum name="proxyuserpwd">
146                                                 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
147                                                 combination to use for authenticating requests through a
148                                                 <literal>proxy</literal></para>
149                                         </enum>
150                                         <enum name="referer">
151                                                 <para>Referer URL to use for the request</para>
152                                         </enum>
153                                         <enum name="useragent">
154                                                 <para>UserAgent string to use for the request</para>
155                                         </enum>
156                                         <enum name="userpwd">
157                                                 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
158                                                 to use for authentication when the server response to
159                                                 an initial request indicates a 401 status code.</para>
160                                         </enum>
161                                         <enum name="ssl_verifypeer">
162                                                 <para>Whether to verify the server certificate against
163                                                 a list of known root certificate authorities (boolean).</para>
164                                         </enum>
165                                         <enum name="hashcompat">
166                                                 <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
167                                                 format, reformat the response such that it can be used
168                                                 by the <literal>HASH</literal> function.</para>
169                                                 <enumlist>
170                                                         <enum name="yes" />
171                                                         <enum name="no" />
172                                                         <enum name="legacy">
173                                                                 <para>Also translate <literal>+</literal> to the
174                                                                 space character, in violation of current RFC
175                                                                 standards.</para>
176                                                         </enum>
177                                                 </enumlist>
178                                         </enum>
179                                 </enumlist>
180                         </parameter>
181                 </syntax>
182                 <description>
183                         <para>Options may be set globally or per channel.  Per-channel
184                         settings will override global settings.</para>
185                 </description>
186                 <see-also>
187                         <ref type="function">CURL</ref>
188                         <ref type="function">HASH</ref>
189                 </see-also>
190         </function>
191  ***/
192
193 #define CURLVERSION_ATLEAST(a,b,c) \
194         ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
195
196 #define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
197
198 static void curlds_free(void *data);
199
200 static const struct ast_datastore_info curl_info = {
201         .type = "CURL",
202         .destroy = curlds_free,
203 };
204
205 struct curl_settings {
206         AST_LIST_ENTRY(curl_settings) list;
207         CURLoption key;
208         void *value;
209 };
210
211 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
212
213 static void curlds_free(void *data)
214 {
215         AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
216         struct curl_settings *setting;
217         if (!list) {
218                 return;
219         }
220         while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
221                 ast_free(setting);
222         }
223         AST_LIST_HEAD_DESTROY(list);
224         ast_free(list);
225 }
226
227 enum optiontype {
228         OT_BOOLEAN,
229         OT_INTEGER,
230         OT_INTEGER_MS,
231         OT_STRING,
232         OT_ENUM,
233 };
234
235 enum hashcompat {
236         HASHCOMPAT_NO = 0,
237         HASHCOMPAT_YES,
238         HASHCOMPAT_LEGACY,
239 };
240
241 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
242 {
243         if (!strcasecmp(name, "header")) {
244                 *key = CURLOPT_HEADER;
245                 *ot = OT_BOOLEAN;
246         } else if (!strcasecmp(name, "proxy")) {
247                 *key = CURLOPT_PROXY;
248                 *ot = OT_STRING;
249         } else if (!strcasecmp(name, "proxyport")) {
250                 *key = CURLOPT_PROXYPORT;
251                 *ot = OT_INTEGER;
252         } else if (!strcasecmp(name, "proxytype")) {
253                 *key = CURLOPT_PROXYTYPE;
254                 *ot = OT_ENUM;
255         } else if (!strcasecmp(name, "dnstimeout")) {
256                 *key = CURLOPT_DNS_CACHE_TIMEOUT;
257                 *ot = OT_INTEGER;
258         } else if (!strcasecmp(name, "userpwd")) {
259                 *key = CURLOPT_USERPWD;
260                 *ot = OT_STRING;
261         } else if (!strcasecmp(name, "proxyuserpwd")) {
262                 *key = CURLOPT_PROXYUSERPWD;
263                 *ot = OT_STRING;
264         } else if (!strcasecmp(name, "maxredirs")) {
265                 *key = CURLOPT_MAXREDIRS;
266                 *ot = OT_INTEGER;
267         } else if (!strcasecmp(name, "referer")) {
268                 *key = CURLOPT_REFERER;
269                 *ot = OT_STRING;
270         } else if (!strcasecmp(name, "useragent")) {
271                 *key = CURLOPT_USERAGENT;
272                 *ot = OT_STRING;
273         } else if (!strcasecmp(name, "cookie")) {
274                 *key = CURLOPT_COOKIE;
275                 *ot = OT_STRING;
276         } else if (!strcasecmp(name, "ftptimeout")) {
277                 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
278                 *ot = OT_INTEGER;
279         } else if (!strcasecmp(name, "httptimeout")) {
280 #if CURLVERSION_ATLEAST(7,16,2)
281                 *key = CURLOPT_TIMEOUT_MS;
282                 *ot = OT_INTEGER_MS;
283 #else
284                 *key = CURLOPT_TIMEOUT;
285                 *ot = OT_INTEGER;
286 #endif
287         } else if (!strcasecmp(name, "conntimeout")) {
288 #if CURLVERSION_ATLEAST(7,16,2)
289                 *key = CURLOPT_CONNECTTIMEOUT_MS;
290                 *ot = OT_INTEGER_MS;
291 #else
292                 *key = CURLOPT_CONNECTTIMEOUT;
293                 *ot = OT_INTEGER;
294 #endif
295         } else if (!strcasecmp(name, "ftptext")) {
296                 *key = CURLOPT_TRANSFERTEXT;
297                 *ot = OT_BOOLEAN;
298         } else if (!strcasecmp(name, "ssl_verifypeer")) {
299                 *key = CURLOPT_SSL_VERIFYPEER;
300                 *ot = OT_BOOLEAN;
301         } else if (!strcasecmp(name, "hashcompat")) {
302                 *key = CURLOPT_SPECIAL_HASHCOMPAT;
303                 *ot = OT_ENUM;
304         } else {
305                 return -1;
306         }
307         return 0;
308 }
309
310 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
311 {
312         struct ast_datastore *store;
313         struct global_curl_info *list;
314         struct curl_settings *cur, *new = NULL;
315         CURLoption key;
316         enum optiontype ot;
317
318         if (chan) {
319                 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
320                         /* Create a new datastore */
321                         if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
322                                 ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
323                                 return -1;
324                         }
325
326                         if (!(list = ast_calloc(1, sizeof(*list)))) {
327                                 ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
328                                 ast_datastore_free(store);
329                                 return -1;
330                         }
331
332                         store->data = list;
333                         AST_LIST_HEAD_INIT(list);
334                         ast_channel_datastore_add(chan, store);
335                 } else {
336                         list = store->data;
337                 }
338         } else {
339                 /* Populate the global structure */
340                 list = &global_curl_info;
341         }
342
343         if (!parse_curlopt_key(name, &key, &ot)) {
344                 if (ot == OT_BOOLEAN) {
345                         if ((new = ast_calloc(1, sizeof(*new)))) {
346                                 new->value = (void *)((long) ast_true(value));
347                         }
348                 } else if (ot == OT_INTEGER) {
349                         long tmp = atol(value);
350                         if ((new = ast_calloc(1, sizeof(*new)))) {
351                                 new->value = (void *)tmp;
352                         }
353                 } else if (ot == OT_INTEGER_MS) {
354                         long tmp = atof(value) * 1000.0;
355                         if ((new = ast_calloc(1, sizeof(*new)))) {
356                                 new->value = (void *)tmp;
357                         }
358                 } else if (ot == OT_STRING) {
359                         if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
360                                 new->value = (char *)new + sizeof(*new);
361                                 strcpy(new->value, value);
362                         }
363                 } else if (ot == OT_ENUM) {
364                         if (key == CURLOPT_PROXYTYPE) {
365                                 long ptype =
366 #if CURLVERSION_ATLEAST(7,10,0)
367                                         CURLPROXY_HTTP;
368 #else
369                                         CURLPROXY_SOCKS5;
370 #endif
371                                 if (0) {
372 #if CURLVERSION_ATLEAST(7,15,2)
373                                 } else if (!strcasecmp(value, "socks4")) {
374                                         ptype = CURLPROXY_SOCKS4;
375 #endif
376 #if CURLVERSION_ATLEAST(7,18,0)
377                                 } else if (!strcasecmp(value, "socks4a")) {
378                                         ptype = CURLPROXY_SOCKS4A;
379 #endif
380 #if CURLVERSION_ATLEAST(7,18,0)
381                                 } else if (!strcasecmp(value, "socks5")) {
382                                         ptype = CURLPROXY_SOCKS5;
383 #endif
384 #if CURLVERSION_ATLEAST(7,18,0)
385                                 } else if (!strncasecmp(value, "socks5", 6)) {
386                                         ptype = CURLPROXY_SOCKS5_HOSTNAME;
387 #endif
388                                 }
389
390                                 if ((new = ast_calloc(1, sizeof(*new)))) {
391                                         new->value = (void *)ptype;
392                                 }
393                         } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
394                                 if ((new = ast_calloc(1, sizeof(*new)))) {
395                                         new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
396                                 }
397                         } else {
398                                 /* Highly unlikely */
399                                 goto yuck;
400                         }
401                 }
402
403                 /* Memory allocation error */
404                 if (!new) {
405                         return -1;
406                 }
407
408                 new->key = key;
409         } else {
410 yuck:
411                 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
412                 return -1;
413         }
414
415         /* Remove any existing entry */
416         AST_LIST_LOCK(list);
417         AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
418                 if (cur->key == new->key) {
419                         AST_LIST_REMOVE_CURRENT(list);
420                         ast_free(cur);
421                         break;
422                 }
423         }
424         AST_LIST_TRAVERSE_SAFE_END
425
426         /* Insert new entry */
427         ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
428         AST_LIST_INSERT_TAIL(list, new, list);
429         AST_LIST_UNLOCK(list);
430
431         return 0;
432 }
433
434 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
435 {
436         struct ast_datastore *store;
437         struct global_curl_info *list[2] = { &global_curl_info, NULL };
438         struct curl_settings *cur = NULL;
439         CURLoption key;
440         enum optiontype ot;
441         int i;
442
443         if (parse_curlopt_key(data, &key, &ot)) {
444                 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
445                 return -1;
446         }
447
448         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
449                 list[0] = store->data;
450                 list[1] = &global_curl_info;
451         }
452
453         for (i = 0; i < 2; i++) {
454                 if (!list[i]) {
455                         break;
456                 }
457                 AST_LIST_LOCK(list[i]);
458                 AST_LIST_TRAVERSE(list[i], cur, list) {
459                         if (cur->key == key) {
460                                 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
461                                         if (buf) {
462                                                 snprintf(buf, len, "%ld", (long) cur->value);
463                                         } else {
464                                                 ast_str_set(bufstr, len, "%ld", (long) cur->value);
465                                         }
466                                 } else if (ot == OT_INTEGER_MS) {
467                                         if ((long) cur->value % 1000 == 0) {
468                                                 if (buf) {
469                                                         snprintf(buf, len, "%ld", (long)cur->value / 1000);
470                                                 } else {
471                                                         ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
472                                                 }
473                                         } else {
474                                                 if (buf) {
475                                                         snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
476                                                 } else {
477                                                         ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
478                                                 }
479                                         }
480                                 } else if (ot == OT_STRING) {
481                                         ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
482                                         if (buf) {
483                                                 ast_copy_string(buf, cur->value, len);
484                                         } else {
485                                                 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
486                                         }
487                                 } else if (key == CURLOPT_PROXYTYPE) {
488                                         const char *strval = "unknown";
489                                         if (0) {
490 #if CURLVERSION_ATLEAST(7,15,2)
491                                         } else if ((long)cur->value == CURLPROXY_SOCKS4) {
492                                                 strval = "socks4";
493 #endif
494 #if CURLVERSION_ATLEAST(7,18,0)
495                                         } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
496                                                 strval = "socks4a";
497 #endif
498                                         } else if ((long)cur->value == CURLPROXY_SOCKS5) {
499                                                 strval = "socks5";
500 #if CURLVERSION_ATLEAST(7,18,0)
501                                         } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
502                                                 strval = "socks5hostname";
503 #endif
504 #if CURLVERSION_ATLEAST(7,10,0)
505                                         } else if ((long)cur->value == CURLPROXY_HTTP) {
506                                                 strval = "http";
507 #endif
508                                         }
509                                         if (buf) {
510                                                 ast_copy_string(buf, strval, len);
511                                         } else {
512                                                 ast_str_set(bufstr, 0, "%s", strval);
513                                         }
514                                 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
515                                         const char *strval = "unknown";
516                                         if ((long) cur->value == HASHCOMPAT_LEGACY) {
517                                                 strval = "legacy";
518                                         } else if ((long) cur->value == HASHCOMPAT_YES) {
519                                                 strval = "yes";
520                                         } else if ((long) cur->value == HASHCOMPAT_NO) {
521                                                 strval = "no";
522                                         }
523                                         if (buf) {
524                                                 ast_copy_string(buf, strval, len);
525                                         } else {
526                                                 ast_str_set(bufstr, 0, "%s", strval);
527                                         }
528                                 }
529                                 break;
530                         }
531                 }
532                 AST_LIST_UNLOCK(list[i]);
533                 if (cur) {
534                         break;
535                 }
536         }
537
538         return cur ? 0 : -1;
539 }
540
541 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
542 {
543         return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
544 }
545
546 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
547 {
548         return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
549 }
550
551 /*! \brief Callback data passed to \ref WriteMemoryCallback */
552 struct curl_write_callback_data {
553         /*! \brief If a string is being built, the string buffer */
554         struct ast_str *str;
555         /*! \brief The max size of \ref str */
556         ssize_t len;
557         /*! \brief If a file is being retrieved, the file to write to */
558         FILE *out_file;
559 };
560
561 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
562 {
563         register int realsize = 0;
564         struct curl_write_callback_data *cb_data = data;
565
566         if (cb_data->str) {
567                 realsize = size * nmemb;
568                 ast_str_append_substr(&cb_data->str, 0, ptr, realsize);
569         } else if (cb_data->out_file) {
570                 realsize = fwrite(ptr, size, nmemb, cb_data->out_file);
571         }
572
573         return realsize;
574 }
575
576 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
577
578 static int curl_instance_init(void *data)
579 {
580         CURL **curl = data;
581
582         if (!(*curl = curl_easy_init()))
583                 return -1;
584
585         curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
586         curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
587         curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
588         curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
589
590         return 0;
591 }
592
593 static void curl_instance_cleanup(void *data)
594 {
595         CURL **curl = data;
596
597         curl_easy_cleanup(*curl);
598
599         ast_free(data);
600 }
601
602 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
603 AST_THREADSTORAGE(thread_escapebuf);
604
605 /*!
606  * \brief Check for potential HTTP injection risk.
607  *
608  * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
609  * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
610  * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
611  * requests rather than as a malformed URL.
612  *
613  * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
614  * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
615  * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
616  *
617  * \param url The URL to check for vulnerability
618  * \retval 0 The URL is not vulnerable
619  * \retval 1 The URL is vulnerable.
620  */
621 static int url_is_vulnerable(const char *url)
622 {
623         if (strpbrk(url, "\r\n")) {
624                 return 1;
625         }
626
627         return 0;
628 }
629
630 struct curl_args {
631         const char *url;
632         const char *postdata;
633         struct curl_write_callback_data cb_data;
634 };
635
636 static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
637 {
638         struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
639         int ret = -1;
640         CURL **curl;
641         struct curl_settings *cur;
642         struct ast_datastore *store = NULL;
643         int hashcompat = 0;
644         AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
645         char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
646
647         if (!escapebuf) {
648                 return -1;
649         }
650
651         if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
652                 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
653                 return -1;
654         }
655
656         if (url_is_vulnerable(args->url)) {
657                 ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args->url);
658                 return -1;
659         }
660
661         if (chan) {
662                 ast_autoservice_start(chan);
663         }
664
665         AST_LIST_LOCK(&global_curl_info);
666         AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
667                 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
668                         hashcompat = (long) cur->value;
669                 } else {
670                         curl_easy_setopt(*curl, cur->key, cur->value);
671                 }
672         }
673         AST_LIST_UNLOCK(&global_curl_info);
674
675         if (chan) {
676                 ast_channel_lock(chan);
677                 store = ast_channel_datastore_find(chan, &curl_info, NULL);
678                 ast_channel_unlock(chan);
679                 if (store) {
680                         list = store->data;
681                         AST_LIST_LOCK(list);
682                         AST_LIST_TRAVERSE(list, cur, list) {
683                                 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
684                                         hashcompat = (long) cur->value;
685                                 } else {
686                                         curl_easy_setopt(*curl, cur->key, cur->value);
687                                 }
688                         }
689                 }
690         }
691
692         curl_easy_setopt(*curl, CURLOPT_URL, args->url);
693         curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
694
695         if (args->postdata) {
696                 curl_easy_setopt(*curl, CURLOPT_POST, 1);
697                 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
698         }
699
700         /* Temporarily assign a buffer for curl to write errors to. */
701         curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
702         curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
703
704         if (curl_easy_perform(*curl) != 0) {
705                 ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args->url);
706         }
707
708         /* Reset buffer to NULL so curl doesn't try to write to it when the
709          * buffer is deallocated. Documentation is vague about allowing NULL
710          * here, but the source allows it. See: "typecheck: allow NULL to unset
711          * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
712         curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
713
714         if (store) {
715                 AST_LIST_UNLOCK(list);
716         }
717
718         if (args->postdata) {
719                 curl_easy_setopt(*curl, CURLOPT_POST, 0);
720         }
721
722         if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
723                 ast_str_trim_blanks(args->cb_data.str);
724
725                 ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
726                 if (hashcompat) {
727                         char *remainder = ast_str_buffer(args->cb_data.str);
728                         char *piece;
729                         struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
730                         struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
731                         int rowcount = 0;
732                         while (fields && values && (piece = strsep(&remainder, "&"))) {
733                                 char *name = strsep(&piece, "=");
734                                 struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
735                                 if (piece) {
736                                         ast_uri_decode(piece, mode);
737                                 }
738                                 ast_uri_decode(name, mode);
739                                 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
740                                 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
741                                 rowcount++;
742                         }
743                         pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
744                         ast_str_set(&args->cb_data.str, 0, "%s", ast_str_buffer(values));
745                         ast_free(fields);
746                         ast_free(values);
747                 }
748                 ret = 0;
749         }
750
751         if (chan) {
752                 ast_autoservice_stop(chan);
753         }
754
755         return ret;
756 }
757
758 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
759 {
760         struct curl_args curl_params = { 0, };
761         int res;
762
763         AST_DECLARE_APP_ARGS(args,
764                 AST_APP_ARG(url);
765                 AST_APP_ARG(postdata);
766         );
767
768         AST_STANDARD_APP_ARGS(args, info);
769
770         if (ast_strlen_zero(info)) {
771                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
772                 return -1;
773         }
774
775         curl_params.url = args.url;
776         curl_params.postdata = args.postdata;
777         curl_params.cb_data.str = ast_str_create(16);
778         if (!curl_params.cb_data.str) {
779                 return -1;
780         }
781
782         res = acf_curl_helper(chan, &curl_params);
783         ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
784         ast_free(curl_params.cb_data.str);
785
786         return res;
787 }
788
789 static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
790 {
791         struct curl_args curl_params = { 0, };
792         int res;
793         char *args_value = ast_strdupa(value);
794         AST_DECLARE_APP_ARGS(args,
795                 AST_APP_ARG(file_path);
796         );
797
798         AST_STANDARD_APP_ARGS(args, args_value);
799
800         if (ast_strlen_zero(name)) {
801                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
802                 return -1;
803         }
804
805         if (ast_strlen_zero(args.file_path)) {
806                 ast_log(LOG_WARNING, "CURL requires a file to write\n");
807                 return -1;
808         }
809
810         curl_params.url = name;
811         curl_params.cb_data.out_file = fopen(args.file_path, "w");
812         if (!curl_params.cb_data.out_file) {
813                 ast_log(LOG_WARNING, "Failed to open file %s: %s (%d)\n",
814                         args.file_path,
815                         strerror(errno),
816                         errno);
817                 return -1;
818         }
819
820         res = acf_curl_helper(chan, &curl_params);
821
822         fclose(curl_params.cb_data.out_file);
823
824         return res;
825 }
826
827 static struct ast_custom_function acf_curl = {
828         .name = "CURL",
829         .read2 = acf_curl_exec,
830         .write = acf_curl_write,
831 };
832
833 static struct ast_custom_function acf_curlopt = {
834         .name = "CURLOPT",
835         .synopsis = "Set options for use with the CURL() function",
836         .syntax = "CURLOPT(<option>)",
837         .desc =
838 "  cookie         - Send cookie with request [none]\n"
839 "  conntimeout    - Number of seconds to wait for connection\n"
840 "  dnstimeout     - Number of seconds to wait for DNS response\n"
841 "  ftptext        - For FTP, force a text transfer (boolean)\n"
842 "  ftptimeout     - For FTP, the server response timeout\n"
843 "  header         - Retrieve header information (boolean)\n"
844 "  httptimeout    - Number of seconds to wait for HTTP response\n"
845 "  maxredirs      - Maximum number of redirects to follow\n"
846 "  proxy          - Hostname or IP to use as a proxy\n"
847 "  proxytype      - http, socks4, or socks5\n"
848 "  proxyport      - port number of the proxy\n"
849 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
850 "  referer        - Referer URL to use for the request\n"
851 "  useragent      - UserAgent string to use\n"
852 "  userpwd        - A <user>:<pass> to use for authentication\n"
853 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
854 "  hashcompat     - Result data will be compatible for use with HASH()\n"
855 "                 - if value is \"legacy\", will translate '+' to ' '\n"
856 "",
857         .read = acf_curlopt_read,
858         .read2 = acf_curlopt_read2,
859         .write = acf_curlopt_write,
860 };
861
862 #ifdef TEST_FRAMEWORK
863 AST_TEST_DEFINE(vulnerable_url)
864 {
865         const char *bad_urls [] = {
866                 "http://example.com\r\nDELETE http://example.com/everything",
867                 "http://example.com\rDELETE http://example.com/everything",
868                 "http://example.com\nDELETE http://example.com/everything",
869                 "\r\nhttp://example.com",
870                 "\rhttp://example.com",
871                 "\nhttp://example.com",
872                 "http://example.com\r\n",
873                 "http://example.com\r",
874                 "http://example.com\n",
875         };
876         const char *good_urls [] = {
877                 "http://example.com",
878                 "http://example.com/%5Cr%5Cn",
879         };
880         int i;
881         enum ast_test_result_state res = AST_TEST_PASS;
882
883         switch (cmd) {
884         case TEST_INIT:
885                 info->name = "vulnerable_url";
886                 info->category = "/funcs/func_curl/";
887                 info->summary = "cURL vulnerable URL test";
888                 info->description =
889                         "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
890         case TEST_EXECUTE:
891                 break;
892         }
893
894         for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
895                 if (!url_is_vulnerable(bad_urls[i])) {
896                         ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
897                         res = AST_TEST_FAIL;
898                 }
899         }
900
901         for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
902                 if (url_is_vulnerable(good_urls[i])) {
903                         ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
904                         res = AST_TEST_FAIL;
905                 }
906         }
907
908         return res;
909 }
910 #endif
911
912 static int unload_module(void)
913 {
914         int res;
915
916         res = ast_custom_function_unregister(&acf_curl);
917         res |= ast_custom_function_unregister(&acf_curlopt);
918
919         AST_TEST_UNREGISTER(vulnerable_url);
920
921         return res;
922 }
923
924 static int load_module(void)
925 {
926         int res;
927
928         res = ast_custom_function_register_escalating(&acf_curl, AST_CFE_WRITE);
929         res |= ast_custom_function_register(&acf_curlopt);
930
931         AST_TEST_REGISTER(vulnerable_url);
932
933         return res;
934 }
935
936 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
937         .support_level = AST_MODULE_SUPPORT_CORE,
938         .load = load_module,
939         .unload = unload_module,
940         .load_pri = AST_MODPRI_REALTIME_DEPEND2,
941         .requires = "res_curl",
942 );