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