Asterisk - The Open Source Telephony Project GIT-master-7e7a603
func_curl.c
Go to the documentation of this file.
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>res_curl</depend>
35 <depend>curl</depend>
36 <support_level>core</support_level>
37 ***/
38
39#include "asterisk.h"
40
41#include <curl/curl.h>
42
43#include "asterisk/lock.h"
44#include "asterisk/file.h"
45#include "asterisk/channel.h"
46#include "asterisk/pbx.h"
47#include "asterisk/cli.h"
48#include "asterisk/module.h"
49#include "asterisk/app.h"
50#include "asterisk/utils.h"
52#include "asterisk/test.h"
53
54/*** DOCUMENTATION
55 <function name="CURL" language="en_US">
56 <synopsis>
57 Retrieve content from a remote web or ftp server
58 </synopsis>
59 <syntax>
60 <parameter name="url" required="true">
61 <para>The full URL for the resource to retrieve.</para>
62 </parameter>
63 <parameter name="post-data">
64 <para><emphasis>Read Only</emphasis></para>
65 <para>If specified, an <literal>HTTP POST</literal> will be
66 performed with the content of
67 <replaceable>post-data</replaceable>, instead of an
68 <literal>HTTP GET</literal> (default).</para>
69 </parameter>
70 </syntax>
71 <description>
72 <para>When this function is read, a <literal>HTTP GET</literal>
73 (by default) will be used to retrieve the contents of the provided
74 <replaceable>url</replaceable>. The contents are returned as the
75 result of the function.</para>
76 <example title="Displaying contents of a page" language="text">
77 exten => s,1,Verbose(0, ${CURL(http://localhost:8088/static/astman.css)})
78 </example>
79 <para>When this function is written to, a <literal>HTTP GET</literal>
80 will be used to retrieve the contents of the provided
81 <replaceable>url</replaceable>. The value written to the function
82 specifies the destination file of the cURL'd resource.</para>
83 <example title="Retrieving a file" language="text">
84 exten => s,1,Set(CURL(http://localhost:8088/static/astman.css)=/var/spool/asterisk/tmp/astman.css))
85 </example>
86 <note>
87 <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
88 is set to <literal>no</literal>, this function can only be written to from the
89 dialplan, and not directly from external protocols. Read operations are
90 unaffected.</para>
91 </note>
92 </description>
93 <see-also>
94 <ref type="function">CURLOPT</ref>
95 </see-also>
96 </function>
97 <function name="CURLOPT" language="en_US">
98 <synopsis>
99 Sets various options for future invocations of CURL.
100 </synopsis>
101 <syntax>
102 <parameter name="key" required="yes">
103 <enumlist>
104 <enum name="cookie">
105 <para>A cookie to send with the request. Multiple
106 cookies are supported.</para>
107 </enum>
108 <enum name="conntimeout">
109 <para>Number of seconds to wait for a connection to succeed</para>
110 </enum>
111 <enum name="dnstimeout">
112 <para>Number of seconds to wait for DNS to be resolved</para>
113 </enum>
114 <enum name="followlocation">
115 <para>Whether or not to follow HTTP 3xx redirects (boolean)</para>
116 </enum>
117 <enum name="ftptext">
118 <para>For FTP URIs, force a text transfer (boolean)</para>
119 </enum>
120 <enum name="ftptimeout">
121 <para>For FTP URIs, number of seconds to wait for a
122 server response</para>
123 </enum>
124 <enum name="header">
125 <para>Include header information in the result
126 (boolean)</para>
127 </enum>
128 <enum name="httpheader">
129 <para>Add HTTP header. Multiple calls add multiple headers.
130 Setting of any header will remove the default
131 "Content-Type application/x-www-form-urlencoded"</para>
132 </enum>
133 <enum name="httptimeout">
134 <para>For HTTP(S) URIs, number of seconds to wait for a
135 server response</para>
136 </enum>
137 <enum name="maxredirs">
138 <para>Maximum number of redirects to follow. The default is -1,
139 which allows for unlimited redirects. This only makes sense when
140 followlocation is also set.</para>
141 </enum>
142 <enum name="proxy">
143 <para>Hostname or IP address to use as a proxy server</para>
144 </enum>
145 <enum name="proxytype">
146 <para>Type of <literal>proxy</literal></para>
147 <enumlist>
148 <enum name="http" />
149 <enum name="socks4" />
150 <enum name="socks5" />
151 </enumlist>
152 </enum>
153 <enum name="proxyport">
154 <para>Port number of the <literal>proxy</literal></para>
155 </enum>
156 <enum name="proxyuserpwd">
157 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
158 combination to use for authenticating requests through a
159 <literal>proxy</literal></para>
160 </enum>
161 <enum name="referer">
162 <para>Referer URL to use for the request</para>
163 </enum>
164 <enum name="useragent">
165 <para>UserAgent string to use for the request</para>
166 </enum>
167 <enum name="userpwd">
168 <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
169 to use for authentication when the server response to
170 an initial request indicates a 401 status code.</para>
171 </enum>
172 <enum name="ssl_verifypeer">
173 <para>Whether to verify the server certificate against
174 a list of known root certificate authorities (boolean).</para>
175 </enum>
176 <enum name="hashcompat">
177 <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
178 format, reformat the response such that it can be used
179 by the <literal>HASH</literal> function.</para>
180 <enumlist>
181 <enum name="yes" />
182 <enum name="no" />
183 <enum name="legacy">
184 <para>Also translate <literal>+</literal> to the
185 space character, in violation of current RFC
186 standards.</para>
187 </enum>
188 </enumlist>
189 </enum>
190 <enum name="failurecodes">
191 <para>A comma separated list of HTTP response codes to be treated as errors</para>
192 </enum>
193 </enumlist>
194 </parameter>
195 </syntax>
196 <description>
197 <para>Options may be set globally or per channel. Per-channel
198 settings will override global settings. Only HTTP headers are added instead of overriding</para>
199 </description>
200 <see-also>
201 <ref type="function">CURL</ref>
202 <ref type="function">HASH</ref>
203 </see-also>
204 </function>
205 ***/
206
207#define CURLVERSION_ATLEAST(a,b,c) \
208 ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
209
210#define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
211
212#define CURLOPT_SPECIAL_FAILURE_CODE 999
213
214static void curlds_free(void *data);
215
216static const struct ast_datastore_info curl_info = {
217 .type = "CURL",
218 .destroy = curlds_free,
219};
220
223 CURLoption key;
224 void *value;
225};
226
228
229static void curlds_free(void *data)
230{
232 struct curl_settings *setting;
233 if (!list) {
234 return;
235 }
236 while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
237 ast_free(setting);
238 }
240 ast_free(list);
241}
242
249};
250
255};
256
257static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
258{
259 if (!strcasecmp(name, "header")) {
260 *key = CURLOPT_HEADER;
261 *ot = OT_BOOLEAN;
262 } else if (!strcasecmp(name, "httpheader")) {
263 *key = CURLOPT_HTTPHEADER;
264 *ot = OT_STRING;
265 } else if (!strcasecmp(name, "proxy")) {
266 *key = CURLOPT_PROXY;
267 *ot = OT_STRING;
268 } else if (!strcasecmp(name, "proxyport")) {
269 *key = CURLOPT_PROXYPORT;
270 *ot = OT_INTEGER;
271 } else if (!strcasecmp(name, "proxytype")) {
272 *key = CURLOPT_PROXYTYPE;
273 *ot = OT_ENUM;
274 } else if (!strcasecmp(name, "dnstimeout")) {
275 *key = CURLOPT_DNS_CACHE_TIMEOUT;
276 *ot = OT_INTEGER;
277 } else if (!strcasecmp(name, "userpwd")) {
278 *key = CURLOPT_USERPWD;
279 *ot = OT_STRING;
280 } else if (!strcasecmp(name, "proxyuserpwd")) {
281 *key = CURLOPT_PROXYUSERPWD;
282 *ot = OT_STRING;
283 } else if (!strcasecmp(name, "followlocation")) {
284 *key = CURLOPT_FOLLOWLOCATION;
285 *ot = OT_BOOLEAN;
286 } else if (!strcasecmp(name, "maxredirs")) {
287 *key = CURLOPT_MAXREDIRS;
288 *ot = OT_INTEGER;
289 } else if (!strcasecmp(name, "referer")) {
290 *key = CURLOPT_REFERER;
291 *ot = OT_STRING;
292 } else if (!strcasecmp(name, "useragent")) {
293 *key = CURLOPT_USERAGENT;
294 *ot = OT_STRING;
295 } else if (!strcasecmp(name, "cookie")) {
296 *key = CURLOPT_COOKIE;
297 *ot = OT_STRING;
298 } else if (!strcasecmp(name, "ftptimeout")) {
299 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
300 *ot = OT_INTEGER;
301 } else if (!strcasecmp(name, "httptimeout")) {
302#if CURLVERSION_ATLEAST(7,16,2)
303 *key = CURLOPT_TIMEOUT_MS;
304 *ot = OT_INTEGER_MS;
305#else
306 *key = CURLOPT_TIMEOUT;
307 *ot = OT_INTEGER;
308#endif
309 } else if (!strcasecmp(name, "conntimeout")) {
310#if CURLVERSION_ATLEAST(7,16,2)
311 *key = CURLOPT_CONNECTTIMEOUT_MS;
312 *ot = OT_INTEGER_MS;
313#else
314 *key = CURLOPT_CONNECTTIMEOUT;
315 *ot = OT_INTEGER;
316#endif
317 } else if (!strcasecmp(name, "ftptext")) {
318 *key = CURLOPT_TRANSFERTEXT;
319 *ot = OT_BOOLEAN;
320 } else if (!strcasecmp(name, "ssl_verifypeer")) {
321 *key = CURLOPT_SSL_VERIFYPEER;
322 *ot = OT_BOOLEAN;
323 } else if (!strcasecmp(name, "hashcompat")) {
325 *ot = OT_ENUM;
326 } else if (!strcasecmp(name, "failurecodes")) {
328 *ot = OT_STRING;
329 } else {
330 return -1;
331 }
332 return 0;
333}
334
335static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
336{
337 struct ast_datastore *store;
338 struct global_curl_info *list;
339 struct curl_settings *cur, *new = NULL;
340 CURLoption key;
341 enum optiontype ot;
342
343 if (chan) {
344 ast_channel_lock(chan);
345 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
346 /* Create a new datastore */
347 if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
348 ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
349 ast_channel_unlock(chan);
350 return -1;
351 }
352
353 if (!(list = ast_calloc(1, sizeof(*list)))) {
354 ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
355 ast_datastore_free(store);
356 ast_channel_unlock(chan);
357 return -1;
358 }
359
360 store->data = list;
362 ast_channel_datastore_add(chan, store);
363 } else {
364 list = store->data;
365 }
366 ast_channel_unlock(chan);
367 } else {
368 /* Populate the global structure */
370 }
371
372 if (!parse_curlopt_key(name, &key, &ot)) {
373 if (ot == OT_BOOLEAN) {
374 if ((new = ast_calloc(1, sizeof(*new)))) {
375 new->value = (void *)((long) ast_true(value));
376 }
377 } else if (ot == OT_INTEGER) {
378 long tmp = atol(value);
379 if ((new = ast_calloc(1, sizeof(*new)))) {
380 new->value = (void *)tmp;
381 }
382 } else if (ot == OT_INTEGER_MS) {
383 long tmp = atof(value) * 1000.0;
384 if ((new = ast_calloc(1, sizeof(*new)))) {
385 new->value = (void *)tmp;
386 }
387 } else if (ot == OT_STRING) {
388 if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
389 new->value = (char *)new + sizeof(*new);
390 strcpy(new->value, value);
391 }
392 } else if (ot == OT_ENUM) {
393 if (key == CURLOPT_PROXYTYPE) {
394 long ptype =
395#if CURLVERSION_ATLEAST(7,10,0)
396 CURLPROXY_HTTP;
397#else
398 CURLPROXY_SOCKS5;
399#endif
400 if (0) {
401#if CURLVERSION_ATLEAST(7,15,2)
402 } else if (!strcasecmp(value, "socks4")) {
403 ptype = CURLPROXY_SOCKS4;
404#endif
405#if CURLVERSION_ATLEAST(7,18,0)
406 } else if (!strcasecmp(value, "socks4a")) {
407 ptype = CURLPROXY_SOCKS4A;
408#endif
409#if CURLVERSION_ATLEAST(7,18,0)
410 } else if (!strcasecmp(value, "socks5")) {
411 ptype = CURLPROXY_SOCKS5;
412#endif
413#if CURLVERSION_ATLEAST(7,18,0)
414 } else if (!strncasecmp(value, "socks5", 6)) {
415 ptype = CURLPROXY_SOCKS5_HOSTNAME;
416#endif
417 }
418
419 if ((new = ast_calloc(1, sizeof(*new)))) {
420 new->value = (void *)ptype;
421 }
422 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
423 if ((new = ast_calloc(1, sizeof(*new)))) {
424 new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
425 }
426 } else {
427 /* Highly unlikely */
428 goto yuck;
429 }
430 }
431
432 /* Memory allocation error */
433 if (!new) {
434 return -1;
435 }
436
437 new->key = key;
438 } else {
439yuck:
440 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
441 return -1;
442 }
443
444 /* Remove any existing entry, only http headers are left */
446 if (new->key != CURLOPT_HTTPHEADER) {
448 if (cur->key == new->key) {
450 ast_free(cur);
451 break;
452 }
453 }
455 }
456
457 /* Insert new entry */
458 ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
461
462 return 0;
463}
464
465static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
466{
467 struct ast_datastore *store;
468 struct global_curl_info *list[2] = { &global_curl_info, NULL };
469 struct curl_settings *cur = NULL;
470 CURLoption key;
471 enum optiontype ot;
472 int i;
473
474 if (parse_curlopt_key(data, &key, &ot)) {
475 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
476 return -1;
477 }
478
479 if (chan) {
480 /* If we have a channel, we want to read the options set there before
481 falling back to the global settings */
482 ast_channel_lock(chan);
484 ast_channel_unlock(chan);
485
486 if (store) {
487 list[0] = store->data;
489 }
490 }
491
492 for (i = 0; i < 2; i++) {
493 if (!list[i]) {
494 break;
495 }
497 AST_LIST_TRAVERSE(list[i], cur, list) {
498 if (cur->key == key) {
499 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
500 if (buf) {
501 snprintf(buf, len, "%ld", (long) cur->value);
502 } else {
503 ast_str_set(bufstr, len, "%ld", (long) cur->value);
504 }
505 } else if (ot == OT_INTEGER_MS) {
506 if ((long) cur->value % 1000 == 0) {
507 if (buf) {
508 snprintf(buf, len, "%ld", (long)cur->value / 1000);
509 } else {
510 ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
511 }
512 } else {
513 if (buf) {
514 snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
515 } else {
516 ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
517 }
518 }
519 } else if (ot == OT_STRING) {
520 ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
521 if (buf) {
523 } else {
524 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
525 }
526 } else if (key == CURLOPT_PROXYTYPE) {
527 const char *strval = "unknown";
528 if (0) {
529#if CURLVERSION_ATLEAST(7,15,2)
530 } else if ((long)cur->value == CURLPROXY_SOCKS4) {
531 strval = "socks4";
532#endif
533#if CURLVERSION_ATLEAST(7,18,0)
534 } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
535 strval = "socks4a";
536#endif
537 } else if ((long)cur->value == CURLPROXY_SOCKS5) {
538 strval = "socks5";
539#if CURLVERSION_ATLEAST(7,18,0)
540 } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
541 strval = "socks5hostname";
542#endif
543#if CURLVERSION_ATLEAST(7,10,0)
544 } else if ((long)cur->value == CURLPROXY_HTTP) {
545 strval = "http";
546#endif
547 }
548 if (buf) {
549 ast_copy_string(buf, strval, len);
550 } else {
551 ast_str_set(bufstr, 0, "%s", strval);
552 }
553 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
554 const char *strval = "unknown";
555 if ((long) cur->value == HASHCOMPAT_LEGACY) {
556 strval = "legacy";
557 } else if ((long) cur->value == HASHCOMPAT_YES) {
558 strval = "yes";
559 } else if ((long) cur->value == HASHCOMPAT_NO) {
560 strval = "no";
561 }
562 if (buf) {
563 ast_copy_string(buf, strval, len);
564 } else {
565 ast_str_set(bufstr, 0, "%s", strval);
566 }
567 }
568 break;
569 }
570 }
572 if (cur) {
573 break;
574 }
575 }
576
577 return cur ? 0 : -1;
578}
579
580static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
581{
582 return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
583}
584
585static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
586{
587 return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
588}
589
590/*! \brief Callback data passed to \ref WriteMemoryCallback */
592 /*! \brief If a string is being built, the string buffer */
593 struct ast_str *str;
594 /*! \brief The max size of \ref str */
595 ssize_t len;
596 /*! \brief If a file is being retrieved, the file to write to */
597 FILE *out_file;
598};
599
600static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
601{
602 register int realsize = 0;
603 struct curl_write_callback_data *cb_data = data;
604
605 if (cb_data->str) {
606 realsize = size * nmemb;
607 ast_str_append_substr(&cb_data->str, 0, ptr, realsize);
608 } else if (cb_data->out_file) {
609 realsize = fwrite(ptr, size, nmemb, cb_data->out_file);
610 }
611
612 return realsize;
613}
614
615static int curl_instance_init(void *data)
616{
617 CURL **curl = data;
618
619 if (!(*curl = curl_easy_init()))
620 return -1;
621
622 curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
623 curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
624 curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
625 curl_easy_setopt(*curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
626
627 return 0;
628}
629
630static void curl_instance_cleanup(void *data)
631{
632 CURL **curl = data;
633
634 curl_easy_cleanup(*curl);
635
636 ast_free(data);
637}
638
641
642/*!
643 * \brief Check for potential HTTP injection risk.
644 *
645 * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
646 * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
647 * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
648 * requests rather than as a malformed URL.
649 *
650 * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
651 * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
652 * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
653 *
654 * \param url The URL to check for vulnerability
655 * \retval 0 The URL is not vulnerable
656 * \retval 1 The URL is vulnerable.
657 */
658static int url_is_vulnerable(const char *url)
659{
660 if (strpbrk(url, "\r\n")) {
661 return 1;
662 }
663
664 return 0;
665}
666
667struct curl_args {
668 const char *url;
669 const char *postdata;
671};
672
673static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
674{
675 struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
676 int ret = 0;
677 long http_code = 0; /* read curl response */
678 size_t i;
679 struct ast_vector_int hasfailurecode = { NULL };
680 char *failurecodestrings,*found;
681 CURL **curl;
682 struct curl_settings *cur;
683 struct curl_slist *headers = NULL;
684 struct ast_datastore *store = NULL;
685 int hashcompat = 0;
687 char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
688
689 if (!escapebuf) {
690 return -1;
691 }
692
693 if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
694 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
695 return -1;
696 }
697
698 if (url_is_vulnerable(args->url)) {
699 ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args->url);
700 return -1;
701 }
702
703 if (chan) {
705 }
706
707 AST_VECTOR_INIT(&hasfailurecode, 0); /*Initialize vector*/
710 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
711 hashcompat = (long) cur->value;
712 } else if (cur->key == CURLOPT_HTTPHEADER) {
713 headers = curl_slist_append(headers, (char*) cur->value);
714 } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) {
715 failurecodestrings = (char*) cur->value;
716 while( (found = strsep(&failurecodestrings, ",")) != NULL) {
717 AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
718 }
719 } else {
720 curl_easy_setopt(*curl, cur->key, cur->value);
721 }
722 }
724
725 if (chan) {
726 ast_channel_lock(chan);
728 ast_channel_unlock(chan);
729 if (store) {
730 list = store->data;
731 AST_LIST_LOCK(list);
732 AST_LIST_TRAVERSE(list, cur, list) {
733 if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
734 hashcompat = (long) cur->value;
735 } else if (cur->key == CURLOPT_HTTPHEADER) {
736 headers = curl_slist_append(headers, (char*) cur->value);
737 } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) {
738 failurecodestrings = (char*) cur->value;
739 while( (found = strsep(&failurecodestrings, ",")) != NULL) {
740 AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
741 }
742 } else {
743 curl_easy_setopt(*curl, cur->key, cur->value);
744 }
745 }
746 }
747 }
748
749 curl_easy_setopt(*curl, CURLOPT_URL, args->url);
750 curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
751
752 if (args->postdata) {
753 curl_easy_setopt(*curl, CURLOPT_POST, 1);
754 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
755 }
756
757 /* Always assign the headers - even when NULL - in case we had
758 * custom headers the last time we used this shared cURL
759 * instance */
760 curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers);
761
762 /* Temporarily assign a buffer for curl to write errors to. */
763 curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
764 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
765
766 if (curl_easy_perform(*curl) != 0) {
767 ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args->url);
768 }
769
770 /* Reset buffer to NULL so curl doesn't try to write to it when the
771 * buffer is deallocated. Documentation is vague about allowing NULL
772 * here, but the source allows it. See: "typecheck: allow NULL to unset
773 * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
774 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
775 curl_easy_getinfo (*curl, CURLINFO_RESPONSE_CODE, &http_code);
776
777 for (i = 0; i < AST_VECTOR_SIZE(&hasfailurecode); ++i) {
778 if (http_code == AST_VECTOR_GET(&hasfailurecode,i)){
779 ast_log(LOG_NOTICE, "%s%sCURL '%s' returned response code (%ld).\n",
780 chan ? ast_channel_name(chan) : "",
781 chan ? ast_channel_name(chan) : ": ",
782 args->url,
783 http_code);
784 ret=-1;
785 break;
786 }
787 }
788 AST_VECTOR_FREE(&hasfailurecode); /* Release the vector*/
789
790 if (store) {
791 AST_LIST_UNLOCK(list);
792 }
793 curl_slist_free_all(headers);
794
795 if (args->postdata) {
796 curl_easy_setopt(*curl, CURLOPT_POST, 0);
797 }
798
799 if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
800 ast_str_trim_blanks(args->cb_data.str);
801
802 ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
803 if (hashcompat) {
804 char *remainder = ast_str_buffer(args->cb_data.str);
805 char *piece;
806 struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
807 struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
808 int rowcount = 0;
809 while (fields && values && (piece = strsep(&remainder, "&"))) {
810 char *name = strsep(&piece, "=");
812 if (piece) {
813 ast_uri_decode(piece, mode);
814 }
815 ast_uri_decode(name, mode);
816 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
817 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
818 rowcount++;
819 }
820 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
821 ast_str_set(&args->cb_data.str, 0, "%s", ast_str_buffer(values));
822 ast_free(fields);
824 }
825 }
826
827 if (chan) {
829 }
830
831 return ret;
832}
833
834static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
835{
836 struct curl_args curl_params = { 0, };
837 int res;
838
842 );
843
845
846 if (ast_strlen_zero(info)) {
847 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
848 return -1;
849 }
850
851 curl_params.url = args.url;
852 curl_params.postdata = args.postdata;
853 curl_params.cb_data.str = ast_str_create(16);
854 if (!curl_params.cb_data.str) {
855 return -1;
856 }
857
858 res = acf_curl_helper(chan, &curl_params);
859 ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
860 ast_free(curl_params.cb_data.str);
861
862 return res;
863}
864
865static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
866{
867 struct curl_args curl_params = { 0, };
868 int res;
869 char *args_value = ast_strdupa(value);
871 AST_APP_ARG(file_path);
872 );
873
874 AST_STANDARD_APP_ARGS(args, args_value);
875
876 if (ast_strlen_zero(name)) {
877 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
878 return -1;
879 }
880
881 if (ast_strlen_zero(args.file_path)) {
882 ast_log(LOG_WARNING, "CURL requires a file to write\n");
883 return -1;
884 }
885
886 curl_params.url = name;
887 curl_params.cb_data.out_file = fopen(args.file_path, "w");
888 if (!curl_params.cb_data.out_file) {
889 ast_log(LOG_WARNING, "Failed to open file %s: %s (%d)\n",
890 args.file_path,
891 strerror(errno),
892 errno);
893 return -1;
894 }
895
896 res = acf_curl_helper(chan, &curl_params);
897
898 fclose(curl_params.cb_data.out_file);
899
900 return res;
901}
902
904 .name = "CURL",
905 .read2 = acf_curl_exec,
906 .write = acf_curl_write,
907};
908
910 .name = "CURLOPT",
911 .read = acf_curlopt_read,
912 .read2 = acf_curlopt_read2,
913 .write = acf_curlopt_write,
914};
915
916#ifdef TEST_FRAMEWORK
917AST_TEST_DEFINE(vulnerable_url)
918{
919 const char *bad_urls [] = {
920 "http://example.com\r\nDELETE http://example.com/everything",
921 "http://example.com\rDELETE http://example.com/everything",
922 "http://example.com\nDELETE http://example.com/everything",
923 "\r\nhttp://example.com",
924 "\rhttp://example.com",
925 "\nhttp://example.com",
926 "http://example.com\r\n",
927 "http://example.com\r",
928 "http://example.com\n",
929 };
930 const char *good_urls [] = {
931 "http://example.com",
932 "http://example.com/%5Cr%5Cn",
933 };
934 int i;
936
937 switch (cmd) {
938 case TEST_INIT:
939 info->name = "vulnerable_url";
940 info->category = "/funcs/func_curl/";
941 info->summary = "cURL vulnerable URL test";
942 info->description =
943 "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
944 case TEST_EXECUTE:
945 break;
946 }
947
948 for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
949 if (!url_is_vulnerable(bad_urls[i])) {
950 ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
951 res = AST_TEST_FAIL;
952 }
953 }
954
955 for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
956 if (url_is_vulnerable(good_urls[i])) {
957 ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
958 res = AST_TEST_FAIL;
959 }
960 }
961
962 return res;
963}
964#endif
965
966static int unload_module(void)
967{
968 int res;
969
972
973 AST_TEST_UNREGISTER(vulnerable_url);
974
975 return res;
976}
977
978static int load_module(void)
979{
980 int res;
981
984
985 AST_TEST_REGISTER(vulnerable_url);
986
987 return res;
988}
989
991 .support_level = AST_MODULE_SUPPORT_CORE,
992 .load = load_module,
993 .unload = unload_module,
994 .load_pri = AST_MODPRI_REALTIME_DEPEND2,
995 .requires = "res_curl",
Asterisk main include file. File version handling, generic pbx functions.
#define AST_CURL_USER_AGENT
Definition: asterisk.h:44
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
static int tmp()
Definition: bt_open.c:389
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:266
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
#define ast_channel_lock(chan)
Definition: channel.h:2922
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:200
#define ast_channel_unlock(chan)
Definition: channel.h:2923
struct ast_datastore * ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid)
Find a datastore on a channel.
Definition: channel.c:2399
Standard Command Line Interface.
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:85
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
static const char name[]
Definition: format_mp3.c:68
#define CURLOPT_SPECIAL_HASHCOMPAT
Definition: func_curl.c:210
static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
Definition: func_curl.c:673
static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:585
static void curl_instance_cleanup(void *data)
Definition: func_curl.c:630
static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:834
static const struct ast_datastore_info curl_info
Definition: func_curl.c:216
static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:335
static int curl_instance_init(void *data)
Definition: func_curl.c:615
static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_curl.c:580
#define CURLOPT_SPECIAL_FAILURE_CODE
Definition: func_curl.c:212
static struct ast_threadstorage thread_escapebuf
Definition: func_curl.c:640
static void curlds_free(void *data)
Definition: func_curl.c:229
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
Definition: func_curl.c:658
optiontype
Definition: func_curl.c:243
@ OT_BOOLEAN
Definition: func_curl.c:244
@ OT_ENUM
Definition: func_curl.c:248
@ OT_STRING
Definition: func_curl.c:247
@ OT_INTEGER
Definition: func_curl.c:245
@ OT_INTEGER_MS
Definition: func_curl.c:246
static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
Definition: func_curl.c:465
static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:865
static struct ast_custom_function acf_curlopt
Definition: func_curl.c:909
static struct ast_threadstorage curl_instance
Definition: func_curl.c:639
struct global_curl_info global_curl_info
static int load_module(void)
Definition: func_curl.c:978
static int unload_module(void)
Definition: func_curl.c:966
static struct ast_custom_function acf_curl
Definition: func_curl.c:903
static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
Definition: func_curl.c:257
hashcompat
Definition: func_curl.c:251
@ HASHCOMPAT_NO
Definition: func_curl.c:252
@ HASHCOMPAT_LEGACY
Definition: func_curl.c:254
@ HASHCOMPAT_YES
Definition: func_curl.c:253
static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
Definition: func_curl.c:600
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
char * strsep(char **str, const char *delims)
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
Definition: linkedlists.h:291
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:653
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:626
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Definition: linkedlists.h:173
Asterisk locking-related definitions:
int errno
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:317
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODPRI_REALTIME_DEPEND2
Definition: module.h:322
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
def info(msg)
Core PBX routines and definitions.
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name.
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1567
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
@ AST_CFE_WRITE
Definition: pbx.h:1551
static char url[512]
#define NULL
Definition: resample.c:96
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199
char * ast_str_set_escapecommas(struct ast_str **buf, ssize_t maxlen, const char *src, size_t maxsrc)
Set a dynamic string to a non-NULL terminated substring, with escaping of commas.
Definition: strings.h:1069
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
char * ast_str_append_substr(struct ast_str **buf, ssize_t maxlen, const char *src, size_t maxsrc)
Append a non-NULL terminated substring to the end of a dynamic string.
Definition: strings.h:1062
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
Definition: strings.h:719
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
Main Channel structure associated with a channel.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Structure for a data store type.
Definition: datastore.h:31
const char * type
Definition: datastore.h:32
Structure for a data store object.
Definition: datastore.h:64
void * data
Definition: datastore.h:66
Structure used to handle boolean flags.
Definition: utils.h:199
Support for dynamic strings.
Definition: strings.h:623
Integer vector definition.
Definition: vector.h:52
const char * postdata
Definition: func_curl.c:669
struct curl_write_callback_data cb_data
Definition: func_curl.c:670
const char * url
Definition: func_curl.c:668
void * value
Definition: func_curl.c:224
struct curl_settings::@168 list
CURLoption key
Definition: func_curl.c:223
Callback data passed to WriteMemoryCallback.
Definition: func_curl.c:591
struct ast_str * str
If a string is being built, the string buffer.
Definition: func_curl.c:593
ssize_t len
The max size of str.
Definition: func_curl.c:595
FILE * out_file
If a file is being retrieved, the file to write to.
Definition: func_curl.c:597
int value
Definition: syslog.c:37
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
ast_test_result_state
Definition: test.h:193
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
const char * args
Definitions to aid in the use of thread local storage.
#define AST_THREADSTORAGE_CUSTOM(a, b, c)
Define a thread storage variable, with custom initialization and cleanup.
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
Utility functions.
const struct ast_flags ast_uri_http
Definition: utils.c:719
#define ARRAY_LEN(a)
Definition: utils.h:666
void ast_uri_decode(char *s, struct ast_flags spec)
Decode URI, URN, URL (overwrite string)
Definition: utils.c:762
const struct ast_flags ast_uri_http_legacy
Definition: utils.c:720
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680