CHANNEL(callid): Give dialplan access to the callid.
[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 && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
676                 list = store->data;
677                 AST_LIST_LOCK(list);
678                 AST_LIST_TRAVERSE(list, cur, list) {
679                         if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
680                                 hashcompat = (long) cur->value;
681                         } else {
682                                 curl_easy_setopt(*curl, cur->key, cur->value);
683                         }
684                 }
685         }
686
687         curl_easy_setopt(*curl, CURLOPT_URL, args->url);
688         curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
689
690         if (args->postdata) {
691                 curl_easy_setopt(*curl, CURLOPT_POST, 1);
692                 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
693         }
694
695         /* Temporarily assign a buffer for curl to write errors to. */
696         curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
697         curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
698
699         if (curl_easy_perform(*curl) != 0) {
700                 ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args->url);
701         }
702
703         /* Reset buffer to NULL so curl doesn't try to write to it when the
704          * buffer is deallocated. Documentation is vague about allowing NULL
705          * here, but the source allows it. See: "typecheck: allow NULL to unset
706          * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
707         curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
708
709         if (store) {
710                 AST_LIST_UNLOCK(list);
711         }
712
713         if (args->postdata) {
714                 curl_easy_setopt(*curl, CURLOPT_POST, 0);
715         }
716
717         if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
718                 ast_str_trim_blanks(args->cb_data.str);
719
720                 ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
721                 if (hashcompat) {
722                         char *remainder = ast_str_buffer(args->cb_data.str);
723                         char *piece;
724                         struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
725                         struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
726                         int rowcount = 0;
727                         while (fields && values && (piece = strsep(&remainder, "&"))) {
728                                 char *name = strsep(&piece, "=");
729                                 struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
730                                 if (piece) {
731                                         ast_uri_decode(piece, mode);
732                                 }
733                                 ast_uri_decode(name, mode);
734                                 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
735                                 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
736                                 rowcount++;
737                         }
738                         pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
739                         ast_str_set(&args->cb_data.str, 0, "%s", ast_str_buffer(values));
740                         ast_free(fields);
741                         ast_free(values);
742                 }
743                 ret = 0;
744         }
745
746         if (chan) {
747                 ast_autoservice_stop(chan);
748         }
749
750         return ret;
751 }
752
753 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
754 {
755         struct curl_args curl_params = { 0, };
756         int res;
757
758         AST_DECLARE_APP_ARGS(args,
759                 AST_APP_ARG(url);
760                 AST_APP_ARG(postdata);
761         );
762
763         AST_STANDARD_APP_ARGS(args, info);
764
765         if (ast_strlen_zero(info)) {
766                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
767                 return -1;
768         }
769
770         curl_params.url = args.url;
771         curl_params.postdata = args.postdata;
772         curl_params.cb_data.str = ast_str_create(16);
773         if (!curl_params.cb_data.str) {
774                 return -1;
775         }
776
777         res = acf_curl_helper(chan, &curl_params);
778         ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
779         ast_free(curl_params.cb_data.str);
780
781         return res;
782 }
783
784 static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
785 {
786         struct curl_args curl_params = { 0, };
787         int res;
788         char *args_value = ast_strdupa(value);
789         AST_DECLARE_APP_ARGS(args,
790                 AST_APP_ARG(file_path);
791         );
792
793         AST_STANDARD_APP_ARGS(args, args_value);
794
795         if (ast_strlen_zero(name)) {
796                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
797                 return -1;
798         }
799
800         if (ast_strlen_zero(args.file_path)) {
801                 ast_log(LOG_WARNING, "CURL requires a file to write\n");
802                 return -1;
803         }
804
805         curl_params.url = name;
806         curl_params.cb_data.out_file = fopen(args.file_path, "w");
807         if (!curl_params.cb_data.out_file) {
808                 ast_log(LOG_WARNING, "Failed to open file %s: %s (%d)\n",
809                         args.file_path,
810                         strerror(errno),
811                         errno);
812                 return -1;
813         }
814
815         res = acf_curl_helper(chan, &curl_params);
816
817         fclose(curl_params.cb_data.out_file);
818
819         return res;
820 }
821
822 static struct ast_custom_function acf_curl = {
823         .name = "CURL",
824         .read2 = acf_curl_exec,
825         .write = acf_curl_write,
826 };
827
828 static struct ast_custom_function acf_curlopt = {
829         .name = "CURLOPT",
830         .synopsis = "Set options for use with the CURL() function",
831         .syntax = "CURLOPT(<option>)",
832         .desc =
833 "  cookie         - Send cookie with request [none]\n"
834 "  conntimeout    - Number of seconds to wait for connection\n"
835 "  dnstimeout     - Number of seconds to wait for DNS response\n"
836 "  ftptext        - For FTP, force a text transfer (boolean)\n"
837 "  ftptimeout     - For FTP, the server response timeout\n"
838 "  header         - Retrieve header information (boolean)\n"
839 "  httptimeout    - Number of seconds to wait for HTTP response\n"
840 "  maxredirs      - Maximum number of redirects to follow\n"
841 "  proxy          - Hostname or IP to use as a proxy\n"
842 "  proxytype      - http, socks4, or socks5\n"
843 "  proxyport      - port number of the proxy\n"
844 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
845 "  referer        - Referer URL to use for the request\n"
846 "  useragent      - UserAgent string to use\n"
847 "  userpwd        - A <user>:<pass> to use for authentication\n"
848 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
849 "  hashcompat     - Result data will be compatible for use with HASH()\n"
850 "                 - if value is \"legacy\", will translate '+' to ' '\n"
851 "",
852         .read = acf_curlopt_read,
853         .read2 = acf_curlopt_read2,
854         .write = acf_curlopt_write,
855 };
856
857 #ifdef TEST_FRAMEWORK
858 AST_TEST_DEFINE(vulnerable_url)
859 {
860         const char *bad_urls [] = {
861                 "http://example.com\r\nDELETE http://example.com/everything",
862                 "http://example.com\rDELETE http://example.com/everything",
863                 "http://example.com\nDELETE http://example.com/everything",
864                 "\r\nhttp://example.com",
865                 "\rhttp://example.com",
866                 "\nhttp://example.com",
867                 "http://example.com\r\n",
868                 "http://example.com\r",
869                 "http://example.com\n",
870         };
871         const char *good_urls [] = {
872                 "http://example.com",
873                 "http://example.com/%5Cr%5Cn",
874         };
875         int i;
876         enum ast_test_result_state res = AST_TEST_PASS;
877
878         switch (cmd) {
879         case TEST_INIT:
880                 info->name = "vulnerable_url";
881                 info->category = "/funcs/func_curl/";
882                 info->summary = "cURL vulnerable URL test";
883                 info->description =
884                         "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
885         case TEST_EXECUTE:
886                 break;
887         }
888
889         for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
890                 if (!url_is_vulnerable(bad_urls[i])) {
891                         ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
892                         res = AST_TEST_FAIL;
893                 }
894         }
895
896         for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
897                 if (url_is_vulnerable(good_urls[i])) {
898                         ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
899                         res = AST_TEST_FAIL;
900                 }
901         }
902
903         return res;
904 }
905 #endif
906
907 static int unload_module(void)
908 {
909         int res;
910
911         res = ast_custom_function_unregister(&acf_curl);
912         res |= ast_custom_function_unregister(&acf_curlopt);
913
914         AST_TEST_UNREGISTER(vulnerable_url);
915
916         return res;
917 }
918
919 static int load_module(void)
920 {
921         int res;
922
923         if (!ast_module_check("res_curl.so")) {
924                 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
925                         ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
926                         return AST_MODULE_LOAD_DECLINE;
927                 }
928         }
929
930         res = ast_custom_function_register_escalating(&acf_curl, AST_CFE_WRITE);
931         res |= ast_custom_function_register(&acf_curlopt);
932
933         AST_TEST_REGISTER(vulnerable_url);
934
935         return res;
936 }
937
938 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
939         .support_level = AST_MODULE_SUPPORT_CORE,
940         .load = load_module,
941         .unload = unload_module,
942         .load_pri = AST_MODPRI_REALTIME_DEPEND2,
943 );
944