41 #include <curl/curl.h>
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))))
210 #define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
212 #define CURLOPT_SPECIAL_FAILURE_CODE 999
214 static void curlds_free(
void *data);
218 .destroy = curlds_free,
229 static void curlds_free(
void *data)
257 static int parse_curlopt_key(
const char *name, CURLoption *key,
enum optiontype *ot)
259 if (!strcasecmp(name,
"header")) {
260 *key = CURLOPT_HEADER;
262 }
else if (!strcasecmp(name,
"httpheader")) {
263 *key = CURLOPT_HTTPHEADER;
265 }
else if (!strcasecmp(name,
"proxy")) {
266 *key = CURLOPT_PROXY;
268 }
else if (!strcasecmp(name,
"proxyport")) {
269 *key = CURLOPT_PROXYPORT;
271 }
else if (!strcasecmp(name,
"proxytype")) {
272 *key = CURLOPT_PROXYTYPE;
274 }
else if (!strcasecmp(name,
"dnstimeout")) {
275 *key = CURLOPT_DNS_CACHE_TIMEOUT;
277 }
else if (!strcasecmp(name,
"userpwd")) {
278 *key = CURLOPT_USERPWD;
280 }
else if (!strcasecmp(name,
"proxyuserpwd")) {
281 *key = CURLOPT_PROXYUSERPWD;
283 }
else if (!strcasecmp(name,
"followlocation")) {
284 *key = CURLOPT_FOLLOWLOCATION;
286 }
else if (!strcasecmp(name,
"maxredirs")) {
287 *key = CURLOPT_MAXREDIRS;
289 }
else if (!strcasecmp(name,
"referer")) {
290 *key = CURLOPT_REFERER;
292 }
else if (!strcasecmp(name,
"useragent")) {
293 *key = CURLOPT_USERAGENT;
295 }
else if (!strcasecmp(name,
"cookie")) {
296 *key = CURLOPT_COOKIE;
298 }
else if (!strcasecmp(name,
"ftptimeout")) {
299 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
301 }
else if (!strcasecmp(name,
"httptimeout")) {
302 #if CURLVERSION_ATLEAST(7,16,2)
303 *key = CURLOPT_TIMEOUT_MS;
306 *key = CURLOPT_TIMEOUT;
309 }
else if (!strcasecmp(name,
"conntimeout")) {
310 #if CURLVERSION_ATLEAST(7,16,2)
311 *key = CURLOPT_CONNECTTIMEOUT_MS;
314 *key = CURLOPT_CONNECTTIMEOUT;
317 }
else if (!strcasecmp(name,
"ftptext")) {
318 *key = CURLOPT_TRANSFERTEXT;
320 }
else if (!strcasecmp(name,
"ssl_verifypeer")) {
321 *key = CURLOPT_SSL_VERIFYPEER;
323 }
else if (!strcasecmp(name,
"hashcompat")) {
324 *key = CURLOPT_SPECIAL_HASHCOMPAT;
326 }
else if (!strcasecmp(name,
"failurecodes")) {
327 *key = CURLOPT_SPECIAL_FAILURE_CODE;
335 static int acf_curlopt_write(
struct ast_channel *chan,
const char *cmd,
char *name,
const char *value)
344 ast_channel_lock(chan);
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);
354 ast_log(LOG_ERROR,
"Unable to allocate list head. Cannot set any CURL options\n");
356 ast_channel_unlock(chan);
366 ast_channel_unlock(chan);
372 if (!parse_curlopt_key(name, &key, &ot)) {
373 if (ot == OT_BOOLEAN) {
375 new->value = (
void *)((
long)
ast_true(value));
377 }
else if (ot == OT_INTEGER) {
378 long tmp = atol(value);
380 new->value = (
void *)tmp;
382 }
else if (ot == OT_INTEGER_MS) {
383 long tmp = atof(value) * 1000.0;
385 new->value = (
void *)tmp;
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);
392 }
else if (ot == OT_ENUM) {
393 if (key == CURLOPT_PROXYTYPE) {
395 #if CURLVERSION_ATLEAST(7,10,0)
401 #if CURLVERSION_ATLEAST(7,15,2)
402 }
else if (!strcasecmp(value,
"socks4")) {
403 ptype = CURLPROXY_SOCKS4;
405 #if CURLVERSION_ATLEAST(7,18,0)
406 }
else if (!strcasecmp(value,
"socks4a")) {
407 ptype = CURLPROXY_SOCKS4A;
409 #if CURLVERSION_ATLEAST(7,18,0)
410 }
else if (!strcasecmp(value,
"socks5")) {
411 ptype = CURLPROXY_SOCKS5;
413 #if CURLVERSION_ATLEAST(7,18,0)
414 }
else if (!strncasecmp(value,
"socks5", 6)) {
415 ptype = CURLPROXY_SOCKS5_HOSTNAME;
420 new->value = (
void *)ptype;
422 }
else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
424 new->value = (
void *) (
long) (!strcasecmp(value,
"legacy") ? HASHCOMPAT_LEGACY :
ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
440 ast_log(LOG_ERROR,
"Unrecognized option: %s\n", name);
446 if (new->key != CURLOPT_HTTPHEADER) {
448 if (cur->key == new->key) {
458 ast_debug(1,
"Inserting entry %p with key %d and value %p\n",
new, new->key, new->value);
465 static int acf_curlopt_helper(
struct ast_channel *chan,
const char *cmd,
char *data,
char *buf,
struct ast_str **bufstr, ssize_t len)
474 if (parse_curlopt_key(data, &key, &ot)) {
475 ast_log(LOG_ERROR,
"Unrecognized option: '%s'\n", data);
482 ast_channel_lock(chan);
484 ast_channel_unlock(chan);
487 list[0] = store->
data;
492 for (i = 0; i < 2; i++) {
498 if (cur->key == key) {
499 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
501 snprintf(buf, len,
"%ld", (
long) cur->value);
503 ast_str_set(bufstr, len,
"%ld", (
long) cur->value);
505 }
else if (ot == OT_INTEGER_MS) {
506 if ((
long) cur->value % 1000 == 0) {
508 snprintf(buf, len,
"%ld", (
long)cur->value / 1000);
510 ast_str_set(bufstr, len,
"%ld", (
long) cur->value / 1000);
514 snprintf(buf, len,
"%.3f", (
double) ((
long) cur->value) / 1000.0);
516 ast_str_set(bufstr, len,
"%.3f", (
double) ((
long) cur->value) / 1000.0);
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);
526 }
else if (key == CURLOPT_PROXYTYPE) {
527 const char *strval =
"unknown";
529 #if CURLVERSION_ATLEAST(7,15,2)
530 }
else if ((
long)cur->value == CURLPROXY_SOCKS4) {
533 #if CURLVERSION_ATLEAST(7,18,0)
534 }
else if ((
long)cur->value == CURLPROXY_SOCKS4A) {
537 }
else if ((
long)cur->value == CURLPROXY_SOCKS5) {
539 #if CURLVERSION_ATLEAST(7,18,0)
540 }
else if ((
long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
541 strval =
"socks5hostname";
543 #if CURLVERSION_ATLEAST(7,10,0)
544 }
else if ((
long)cur->value == CURLPROXY_HTTP) {
553 }
else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
554 const char *strval =
"unknown";
555 if ((
long) cur->value == HASHCOMPAT_LEGACY) {
557 }
else if ((
long) cur->value == HASHCOMPAT_YES) {
559 }
else if ((
long) cur->value == HASHCOMPAT_NO) {
580 static int acf_curlopt_read(
struct ast_channel *chan,
const char *cmd,
char *data,
char *buf,
size_t len)
582 return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
585 static int acf_curlopt_read2(
struct ast_channel *chan,
const char *cmd,
char *data,
struct ast_str **buf, ssize_t len)
587 return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
600 static size_t WriteMemoryCallback(
void *ptr,
size_t size,
size_t nmemb,
void *data)
602 register int realsize = 0;
606 realsize = size * nmemb;
609 realsize = fwrite(ptr, size, nmemb, cb_data->
out_file);
615 static int curl_instance_init(
void *data)
619 if (!(*curl = curl_easy_init()))
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);
630 static void curl_instance_cleanup(
void *data)
634 curl_easy_cleanup(*curl);
660 if (strpbrk(url,
"\r\n")) {
669 const char *postdata;
680 char *failurecodestrings,*found;
683 struct curl_slist *headers = NULL;
687 char curl_errbuf[CURL_ERROR_SIZE + 1];
694 ast_log(LOG_ERROR,
"Cannot allocate curl structure\n");
699 ast_log(LOG_ERROR,
"URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args->url);
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) {
720 curl_easy_setopt(*curl, cur->key, cur->value);
726 ast_channel_lock(chan);
728 ast_channel_unlock(chan);
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) {
743 curl_easy_setopt(*curl, cur->key, cur->value);
749 curl_easy_setopt(*curl, CURLOPT_URL, args->url);
750 curl_easy_setopt(*curl, CURLOPT_FILE, (
void *) &args->cb_data);
752 if (args->postdata) {
753 curl_easy_setopt(*curl, CURLOPT_POST, 1);
754 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
760 curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers);
763 curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] =
'\0';
764 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
766 if (curl_easy_perform(*curl) != 0) {
767 ast_log(LOG_WARNING,
"%s ('%s')\n", curl_errbuf, args->url);
774 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (
char*)NULL);
775 curl_easy_getinfo (*curl, CURLINFO_RESPONSE_CODE, &http_code);
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) :
": ",
793 curl_slist_free_all(headers);
795 if (args->postdata) {
796 curl_easy_setopt(*curl, CURLOPT_POST, 0);
809 while (fields && values && (piece = strsep(&remainder,
"&"))) {
810 char *name = strsep(&piece,
"=");
811 struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
834 static int acf_curl_exec(
struct ast_channel *chan,
const char *cmd,
char *info,
struct ast_str **buf, ssize_t len)
846 if (ast_strlen_zero(info)) {
847 ast_log(LOG_WARNING,
"CURL requires an argument (URL)\n");
851 curl_params.url = args.url;
852 curl_params.postdata = args.postdata;
854 if (!curl_params.cb_data.
str) {
858 res = acf_curl_helper(chan, &curl_params);
860 ast_free(curl_params.cb_data.
str);
865 static int acf_curl_write(
struct ast_channel *chan,
const char *cmd,
char *name,
const char *value)
876 if (ast_strlen_zero(name)) {
877 ast_log(LOG_WARNING,
"CURL requires an argument (URL)\n");
881 if (ast_strlen_zero(args.file_path)) {
882 ast_log(LOG_WARNING,
"CURL requires a file to write\n");
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",
896 res = acf_curl_helper(chan, &curl_params);
898 fclose(curl_params.cb_data.
out_file);
905 .read2 = acf_curl_exec,
906 .write = acf_curl_write,
911 .read = acf_curlopt_read,
912 .read2 = acf_curlopt_read2,
913 .write = acf_curlopt_write,
916 #ifdef TEST_FRAMEWORK
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",
930 const char *good_urls [] = {
931 "http://example.com",
932 "http://example.com/%5Cr%5Cn",
935 enum ast_test_result_state res = AST_TEST_PASS;
939 info->name =
"vulnerable_url";
940 info->category =
"/funcs/func_curl/";
941 info->summary =
"cURL vulnerable URL test";
943 "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
948 for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
950 ast_test_status_update(
test,
"String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
955 for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
957 ast_test_status_update(
test,
"String '%s' detected as invalid when it should be valid\n", good_urls[i]);
966 static int unload_module(
void)
973 AST_TEST_UNREGISTER(vulnerable_url);
978 static int load_module(
void)
985 AST_TEST_REGISTER(vulnerable_url);
990 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER,
"Load external URL",
991 .support_level = AST_MODULE_SUPPORT_CORE,
993 .unload = unload_module,
995 .requires =
"res_curl",
void ast_uri_decode(char *s, struct ast_flags spec)
Decode URI, URN, URL (overwrite string)
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Main Channel structure associated with a channel.
#define AST_LIST_LOCK(head)
Locks a list.
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Callback data passed to WriteMemoryCallback.
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Structure for a data store type.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Structure for a data store object.
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.
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
Definitions to aid in the use of thread local storage.
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
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.
Data structure associated with a custom dialplan function.
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
FILE * out_file
If a file is being retrieved, the file to write to.
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Core PBX routines and definitions.
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
ssize_t len
The max size of str.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Support for dynamic strings.
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
#define ast_calloc(num, len)
A wrapper for calloc()
Structure used to handle boolean flags.
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_THREADSTORAGE_CUSTOM(a, b, c)
Define a thread storage variable, with custom initialization and cleanup.
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. ...
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Standard Command Line Interface.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
#define AST_TEST_DEFINE(hdr)
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
struct ast_str * str
If a string is being built, the string buffer.
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
Integer vector definition.
#define ast_custom_function_register(acf)
Register a custom function.
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
#define AST_APP_ARG(name)
Define an application argument.