123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888 |
- /*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2004 - 2006, Tilghman Lesher
- *
- * Tilghman Lesher <curl-20050919@the-tilghman.com>
- * and Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option)
- *
- * app_curl.c is distributed with no restrictions on usage or
- * redistribution.
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- */
- /*! \file
- *
- * \brief Curl - Load a URL
- *
- * \author Tilghman Lesher <curl-20050919@the-tilghman.com>
- *
- * \note Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option)
- *
- * \extref Depends on the CURL library - http://curl.haxx.se/
- *
- * \ingroup functions
- */
- /*** MODULEINFO
- <depend>curl</depend>
- <support_level>core</support_level>
- ***/
- #include "asterisk.h"
- ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
- #include <curl/curl.h>
- #include "asterisk/lock.h"
- #include "asterisk/file.h"
- #include "asterisk/channel.h"
- #include "asterisk/pbx.h"
- #include "asterisk/cli.h"
- #include "asterisk/module.h"
- #include "asterisk/app.h"
- #include "asterisk/utils.h"
- #include "asterisk/threadstorage.h"
- #include "asterisk/test.h"
- /*** DOCUMENTATION
- <function name="CURL" language="en_US">
- <synopsis>
- Retrieve content from a remote web or ftp server
- </synopsis>
- <syntax>
- <parameter name="url" required="true" />
- <parameter name="post-data">
- <para>If specified, an <literal>HTTP POST</literal> will be
- performed with the content of
- <replaceable>post-data</replaceable>, instead of an
- <literal>HTTP GET</literal> (default).</para>
- </parameter>
- </syntax>
- <description />
- <see-also>
- <ref type="function">CURLOPT</ref>
- </see-also>
- </function>
- <function name="CURLOPT" language="en_US">
- <synopsis>
- Sets various options for future invocations of CURL.
- </synopsis>
- <syntax>
- <parameter name="key" required="yes">
- <enumlist>
- <enum name="cookie">
- <para>A cookie to send with the request. Multiple
- cookies are supported.</para>
- </enum>
- <enum name="conntimeout">
- <para>Number of seconds to wait for a connection to succeed</para>
- </enum>
- <enum name="dnstimeout">
- <para>Number of seconds to wait for DNS to be resolved</para>
- </enum>
- <enum name="ftptext">
- <para>For FTP URIs, force a text transfer (boolean)</para>
- </enum>
- <enum name="ftptimeout">
- <para>For FTP URIs, number of seconds to wait for a
- server response</para>
- </enum>
- <enum name="header">
- <para>Include header information in the result
- (boolean)</para>
- </enum>
- <enum name="httptimeout">
- <para>For HTTP(S) URIs, number of seconds to wait for a
- server response</para>
- </enum>
- <enum name="maxredirs">
- <para>Maximum number of redirects to follow</para>
- </enum>
- <enum name="proxy">
- <para>Hostname or IP address to use as a proxy server</para>
- </enum>
- <enum name="proxytype">
- <para>Type of <literal>proxy</literal></para>
- <enumlist>
- <enum name="http" />
- <enum name="socks4" />
- <enum name="socks5" />
- </enumlist>
- </enum>
- <enum name="proxyport">
- <para>Port number of the <literal>proxy</literal></para>
- </enum>
- <enum name="proxyuserpwd">
- <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
- combination to use for authenticating requests through a
- <literal>proxy</literal></para>
- </enum>
- <enum name="referer">
- <para>Referer URL to use for the request</para>
- </enum>
- <enum name="useragent">
- <para>UserAgent string to use for the request</para>
- </enum>
- <enum name="userpwd">
- <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
- to use for authentication when the server response to
- an initial request indicates a 401 status code.</para>
- </enum>
- <enum name="ssl_verifypeer">
- <para>Whether to verify the server certificate against
- a list of known root certificate authorities (boolean).</para>
- </enum>
- <enum name="hashcompat">
- <para>Assuming the responses will be in <literal>key1=value1&key2=value2</literal>
- format, reformat the response such that it can be used
- by the <literal>HASH</literal> function.</para>
- <enumlist>
- <enum name="yes" />
- <enum name="no" />
- <enum name="legacy">
- <para>Also translate <literal>+</literal> to the
- space character, in violation of current RFC
- standards.</para>
- </enum>
- </enumlist>
- </enum>
- </enumlist>
- </parameter>
- </syntax>
- <description>
- <para>Options may be set globally or per channel. Per-channel
- settings will override global settings.</para>
- </description>
- <see-also>
- <ref type="function">CURL</ref>
- <ref type="function">HASH</ref>
- </see-also>
- </function>
- ***/
- #define CURLVERSION_ATLEAST(a,b,c) \
- ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
- #define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
- static void curlds_free(void *data);
- static const struct ast_datastore_info curl_info = {
- .type = "CURL",
- .destroy = curlds_free,
- };
- struct curl_settings {
- AST_LIST_ENTRY(curl_settings) list;
- CURLoption key;
- void *value;
- };
- AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
- static void curlds_free(void *data)
- {
- AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
- struct curl_settings *setting;
- if (!list) {
- return;
- }
- while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
- free(setting);
- }
- AST_LIST_HEAD_DESTROY(list);
- ast_free(list);
- }
- enum optiontype {
- OT_BOOLEAN,
- OT_INTEGER,
- OT_INTEGER_MS,
- OT_STRING,
- OT_ENUM,
- };
- enum hashcompat {
- HASHCOMPAT_NO = 0,
- HASHCOMPAT_YES,
- HASHCOMPAT_LEGACY,
- };
- static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
- {
- if (!strcasecmp(name, "header")) {
- *key = CURLOPT_HEADER;
- *ot = OT_BOOLEAN;
- } else if (!strcasecmp(name, "proxy")) {
- *key = CURLOPT_PROXY;
- *ot = OT_STRING;
- } else if (!strcasecmp(name, "proxyport")) {
- *key = CURLOPT_PROXYPORT;
- *ot = OT_INTEGER;
- } else if (!strcasecmp(name, "proxytype")) {
- *key = CURLOPT_PROXYTYPE;
- *ot = OT_ENUM;
- } else if (!strcasecmp(name, "dnstimeout")) {
- *key = CURLOPT_DNS_CACHE_TIMEOUT;
- *ot = OT_INTEGER;
- } else if (!strcasecmp(name, "userpwd")) {
- *key = CURLOPT_USERPWD;
- *ot = OT_STRING;
- } else if (!strcasecmp(name, "proxyuserpwd")) {
- *key = CURLOPT_PROXYUSERPWD;
- *ot = OT_STRING;
- } else if (!strcasecmp(name, "maxredirs")) {
- *key = CURLOPT_MAXREDIRS;
- *ot = OT_INTEGER;
- } else if (!strcasecmp(name, "referer")) {
- *key = CURLOPT_REFERER;
- *ot = OT_STRING;
- } else if (!strcasecmp(name, "useragent")) {
- *key = CURLOPT_USERAGENT;
- *ot = OT_STRING;
- } else if (!strcasecmp(name, "cookie")) {
- *key = CURLOPT_COOKIE;
- *ot = OT_STRING;
- } else if (!strcasecmp(name, "ftptimeout")) {
- *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
- *ot = OT_INTEGER;
- } else if (!strcasecmp(name, "httptimeout")) {
- #if CURLVERSION_ATLEAST(7,16,2)
- *key = CURLOPT_TIMEOUT_MS;
- *ot = OT_INTEGER_MS;
- #else
- *key = CURLOPT_TIMEOUT;
- *ot = OT_INTEGER;
- #endif
- } else if (!strcasecmp(name, "conntimeout")) {
- #if CURLVERSION_ATLEAST(7,16,2)
- *key = CURLOPT_CONNECTTIMEOUT_MS;
- *ot = OT_INTEGER_MS;
- #else
- *key = CURLOPT_CONNECTTIMEOUT;
- *ot = OT_INTEGER;
- #endif
- } else if (!strcasecmp(name, "ftptext")) {
- *key = CURLOPT_TRANSFERTEXT;
- *ot = OT_BOOLEAN;
- } else if (!strcasecmp(name, "ssl_verifypeer")) {
- *key = CURLOPT_SSL_VERIFYPEER;
- *ot = OT_BOOLEAN;
- } else if (!strcasecmp(name, "hashcompat")) {
- *key = CURLOPT_SPECIAL_HASHCOMPAT;
- *ot = OT_ENUM;
- } else {
- return -1;
- }
- return 0;
- }
- static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
- {
- struct ast_datastore *store;
- struct global_curl_info *list;
- struct curl_settings *cur, *new = NULL;
- CURLoption key;
- enum optiontype ot;
- if (chan) {
- if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
- /* Create a new datastore */
- if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
- ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
- return -1;
- }
- if (!(list = ast_calloc(1, sizeof(*list)))) {
- ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
- ast_datastore_free(store);
- return -1;
- }
- store->data = list;
- AST_LIST_HEAD_INIT(list);
- ast_channel_datastore_add(chan, store);
- } else {
- list = store->data;
- }
- } else {
- /* Populate the global structure */
- list = &global_curl_info;
- }
- if (!parse_curlopt_key(name, &key, &ot)) {
- if (ot == OT_BOOLEAN) {
- if ((new = ast_calloc(1, sizeof(*new)))) {
- new->value = (void *)((long) ast_true(value));
- }
- } else if (ot == OT_INTEGER) {
- long tmp = atol(value);
- if ((new = ast_calloc(1, sizeof(*new)))) {
- new->value = (void *)tmp;
- }
- } else if (ot == OT_INTEGER_MS) {
- long tmp = atof(value) * 1000.0;
- if ((new = ast_calloc(1, sizeof(*new)))) {
- new->value = (void *)tmp;
- }
- } else if (ot == OT_STRING) {
- if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
- new->value = (char *)new + sizeof(*new);
- strcpy(new->value, value);
- }
- } else if (ot == OT_ENUM) {
- if (key == CURLOPT_PROXYTYPE) {
- long ptype =
- #if CURLVERSION_ATLEAST(7,10,0)
- CURLPROXY_HTTP;
- #else
- CURLPROXY_SOCKS5;
- #endif
- if (0) {
- #if CURLVERSION_ATLEAST(7,15,2)
- } else if (!strcasecmp(value, "socks4")) {
- ptype = CURLPROXY_SOCKS4;
- #endif
- #if CURLVERSION_ATLEAST(7,18,0)
- } else if (!strcasecmp(value, "socks4a")) {
- ptype = CURLPROXY_SOCKS4A;
- #endif
- #if CURLVERSION_ATLEAST(7,18,0)
- } else if (!strcasecmp(value, "socks5")) {
- ptype = CURLPROXY_SOCKS5;
- #endif
- #if CURLVERSION_ATLEAST(7,18,0)
- } else if (!strncasecmp(value, "socks5", 6)) {
- ptype = CURLPROXY_SOCKS5_HOSTNAME;
- #endif
- }
- if ((new = ast_calloc(1, sizeof(*new)))) {
- new->value = (void *)ptype;
- }
- } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
- if ((new = ast_calloc(1, sizeof(*new)))) {
- new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
- }
- } else {
- /* Highly unlikely */
- goto yuck;
- }
- }
- /* Memory allocation error */
- if (!new) {
- return -1;
- }
- new->key = key;
- } else {
- yuck:
- ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
- return -1;
- }
- /* Remove any existing entry */
- AST_LIST_LOCK(list);
- AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
- if (cur->key == new->key) {
- AST_LIST_REMOVE_CURRENT(list);
- free(cur);
- break;
- }
- }
- AST_LIST_TRAVERSE_SAFE_END
- /* Insert new entry */
- ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
- AST_LIST_INSERT_TAIL(list, new, list);
- AST_LIST_UNLOCK(list);
- return 0;
- }
- static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
- {
- struct ast_datastore *store;
- struct global_curl_info *list[2] = { &global_curl_info, NULL };
- struct curl_settings *cur = NULL;
- CURLoption key;
- enum optiontype ot;
- int i;
- if (parse_curlopt_key(data, &key, &ot)) {
- ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
- return -1;
- }
- if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
- list[0] = store->data;
- list[1] = &global_curl_info;
- }
- for (i = 0; i < 2; i++) {
- if (!list[i]) {
- break;
- }
- AST_LIST_LOCK(list[i]);
- AST_LIST_TRAVERSE(list[i], cur, list) {
- if (cur->key == key) {
- if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
- if (buf) {
- snprintf(buf, len, "%ld", (long) cur->value);
- } else {
- ast_str_set(bufstr, len, "%ld", (long) cur->value);
- }
- } else if (ot == OT_INTEGER_MS) {
- if ((long) cur->value % 1000 == 0) {
- if (buf) {
- snprintf(buf, len, "%ld", (long)cur->value / 1000);
- } else {
- ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
- }
- } else {
- if (buf) {
- snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
- } else {
- ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
- }
- }
- } else if (ot == OT_STRING) {
- ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
- if (buf) {
- ast_copy_string(buf, cur->value, len);
- } else {
- ast_str_set(bufstr, 0, "%s", (char *) cur->value);
- }
- } else if (key == CURLOPT_PROXYTYPE) {
- const char *strval = "unknown";
- if (0) {
- #if CURLVERSION_ATLEAST(7,15,2)
- } else if ((long)cur->value == CURLPROXY_SOCKS4) {
- strval = "socks4";
- #endif
- #if CURLVERSION_ATLEAST(7,18,0)
- } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
- strval = "socks4a";
- #endif
- } else if ((long)cur->value == CURLPROXY_SOCKS5) {
- strval = "socks5";
- #if CURLVERSION_ATLEAST(7,18,0)
- } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
- strval = "socks5hostname";
- #endif
- #if CURLVERSION_ATLEAST(7,10,0)
- } else if ((long)cur->value == CURLPROXY_HTTP) {
- strval = "http";
- #endif
- }
- if (buf) {
- ast_copy_string(buf, strval, len);
- } else {
- ast_str_set(bufstr, 0, "%s", strval);
- }
- } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
- const char *strval = "unknown";
- if ((long) cur->value == HASHCOMPAT_LEGACY) {
- strval = "legacy";
- } else if ((long) cur->value == HASHCOMPAT_YES) {
- strval = "yes";
- } else if ((long) cur->value == HASHCOMPAT_NO) {
- strval = "no";
- }
- if (buf) {
- ast_copy_string(buf, strval, len);
- } else {
- ast_str_set(bufstr, 0, "%s", strval);
- }
- }
- break;
- }
- }
- AST_LIST_UNLOCK(list[i]);
- if (cur) {
- break;
- }
- }
- return cur ? 0 : -1;
- }
- static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
- {
- return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
- }
- static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
- {
- return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
- }
- static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
- {
- register int realsize = size * nmemb;
- struct ast_str **pstr = (struct ast_str **)data;
- ast_debug(3, "Called with data=%p, str=%p, realsize=%d, len=%zu, used=%zu\n", data, *pstr, realsize, ast_str_size(*pstr), ast_str_strlen(*pstr));
- ast_str_append_substr(pstr, 0, ptr, realsize);
- ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
- return realsize;
- }
- static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
- static int curl_instance_init(void *data)
- {
- CURL **curl = data;
- if (!(*curl = curl_easy_init()))
- return -1;
- curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
- curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
- curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
- curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
- return 0;
- }
- static void curl_instance_cleanup(void *data)
- {
- CURL **curl = data;
- curl_easy_cleanup(*curl);
- ast_free(data);
- }
- AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
- AST_THREADSTORAGE(thread_escapebuf);
- /*!
- * \brief Check for potential HTTP injection risk.
- *
- * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
- * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
- * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
- * requests rather than as a malformed URL.
- *
- * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
- * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
- * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
- *
- * \param url The URL to check for vulnerability
- * \retval 0 The URL is not vulnerable
- * \retval 1 The URL is vulnerable.
- */
- static int url_is_vulnerable(const char *url)
- {
- if (strpbrk(url, "\r\n")) {
- return 1;
- }
- return 0;
- }
- static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
- {
- struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
- struct ast_str *str = ast_str_create(16);
- int ret = -1;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(url);
- AST_APP_ARG(postdata);
- );
- CURL **curl;
- struct curl_settings *cur;
- struct ast_datastore *store = NULL;
- int hashcompat = 0;
- AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
- char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
- if (buf) {
- *buf = '\0';
- }
- if (!str) {
- return -1;
- }
- if (!escapebuf) {
- ast_free(str);
- return -1;
- }
- if (ast_strlen_zero(info)) {
- ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
- ast_free(str);
- return -1;
- }
- AST_STANDARD_APP_ARGS(args, info);
- if (url_is_vulnerable(args.url)) {
- ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args.url);
- return -1;
- }
- if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
- ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
- ast_free(str);
- return -1;
- }
- if (chan) {
- ast_autoservice_start(chan);
- }
- AST_LIST_LOCK(&global_curl_info);
- AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
- if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
- hashcompat = (long) cur->value;
- } else {
- curl_easy_setopt(*curl, cur->key, cur->value);
- }
- }
- AST_LIST_UNLOCK(&global_curl_info);
- if (chan) {
- ast_channel_lock(chan);
- store = ast_channel_datastore_find(chan, &curl_info, NULL);
- ast_channel_unlock(chan);
- if (store) {
- list = store->data;
- AST_LIST_LOCK(list);
- AST_LIST_TRAVERSE(list, cur, list) {
- if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
- hashcompat = (long) cur->value;
- } else {
- curl_easy_setopt(*curl, cur->key, cur->value);
- }
- }
- }
- }
- curl_easy_setopt(*curl, CURLOPT_URL, args.url);
- curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
- if (args.postdata) {
- curl_easy_setopt(*curl, CURLOPT_POST, 1);
- curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
- }
- /* Temporarily assign a buffer for curl to write errors to. */
- curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
- curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
- if (curl_easy_perform(*curl) != 0) {
- ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args.url);
- }
- /* Reset buffer to NULL so curl doesn't try to write to it when the
- * buffer is deallocated. Documentation is vague about allowing NULL
- * here, but the source allows it. See: "typecheck: allow NULL to unset
- * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
- curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
- if (store) {
- AST_LIST_UNLOCK(list);
- }
- if (args.postdata) {
- curl_easy_setopt(*curl, CURLOPT_POST, 0);
- }
- if (ast_str_strlen(str)) {
- ast_str_trim_blanks(str);
- ast_debug(3, "str='%s'\n", ast_str_buffer(str));
- if (hashcompat) {
- char *remainder = ast_str_buffer(str);
- char *piece;
- struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
- struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
- int rowcount = 0;
- while (fields && values && (piece = strsep(&remainder, "&"))) {
- char *name = strsep(&piece, "=");
- struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
- if (piece) {
- ast_uri_decode(piece, mode);
- }
- ast_uri_decode(name, mode);
- ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
- ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
- rowcount++;
- }
- pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
- if (buf) {
- ast_copy_string(buf, ast_str_buffer(values), len);
- } else {
- ast_str_set(input_str, len, "%s", ast_str_buffer(values));
- }
- ast_free(fields);
- ast_free(values);
- } else {
- if (buf) {
- ast_copy_string(buf, ast_str_buffer(str), len);
- } else {
- ast_str_set(input_str, len, "%s", ast_str_buffer(str));
- }
- }
- ret = 0;
- }
- ast_free(str);
- if (chan)
- ast_autoservice_stop(chan);
- return ret;
- }
- static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
- {
- return acf_curl_helper(chan, cmd, info, buf, NULL, len);
- }
- static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
- {
- return acf_curl_helper(chan, cmd, info, NULL, buf, len);
- }
- static struct ast_custom_function acf_curl = {
- .name = "CURL",
- .synopsis = "Retrieves the contents of a URL",
- .syntax = "CURL(url[,post-data])",
- .desc =
- " url - URL to retrieve\n"
- " post-data - Optional data to send as a POST (GET is default action)\n",
- .read = acf_curl_exec,
- .read2 = acf_curl2_exec,
- };
- static struct ast_custom_function acf_curlopt = {
- .name = "CURLOPT",
- .synopsis = "Set options for use with the CURL() function",
- .syntax = "CURLOPT(<option>)",
- .desc =
- " cookie - Send cookie with request [none]\n"
- " conntimeout - Number of seconds to wait for connection\n"
- " dnstimeout - Number of seconds to wait for DNS response\n"
- " ftptext - For FTP, force a text transfer (boolean)\n"
- " ftptimeout - For FTP, the server response timeout\n"
- " header - Retrieve header information (boolean)\n"
- " httptimeout - Number of seconds to wait for HTTP response\n"
- " maxredirs - Maximum number of redirects to follow\n"
- " proxy - Hostname or IP to use as a proxy\n"
- " proxytype - http, socks4, or socks5\n"
- " proxyport - port number of the proxy\n"
- " proxyuserpwd - A <user>:<pass> to use for authentication\n"
- " referer - Referer URL to use for the request\n"
- " useragent - UserAgent string to use\n"
- " userpwd - A <user>:<pass> to use for authentication\n"
- " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
- " hashcompat - Result data will be compatible for use with HASH()\n"
- " - if value is \"legacy\", will translate '+' to ' '\n"
- "",
- .read = acf_curlopt_read,
- .read2 = acf_curlopt_read2,
- .write = acf_curlopt_write,
- };
- #ifdef TEST_FRAMEWORK
- AST_TEST_DEFINE(vulnerable_url)
- {
- const char *bad_urls [] = {
- "http://example.com\r\nDELETE http://example.com/everything",
- "http://example.com\rDELETE http://example.com/everything",
- "http://example.com\nDELETE http://example.com/everything",
- "\r\nhttp://example.com",
- "\rhttp://example.com",
- "\nhttp://example.com",
- "http://example.com\r\n",
- "http://example.com\r",
- "http://example.com\n",
- };
- const char *good_urls [] = {
- "http://example.com",
- "http://example.com/%5Cr%5Cn",
- };
- int i;
- enum ast_test_result_state res = AST_TEST_PASS;
- switch (cmd) {
- case TEST_INIT:
- info->name = "vulnerable_url";
- info->category = "/funcs/func_curl/";
- info->summary = "cURL vulnerable URL test";
- info->description =
- "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
- case TEST_EXECUTE:
- break;
- }
- for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
- if (!url_is_vulnerable(bad_urls[i])) {
- ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
- res = AST_TEST_FAIL;
- }
- }
- for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
- if (url_is_vulnerable(good_urls[i])) {
- ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
- res = AST_TEST_FAIL;
- }
- }
- return res;
- }
- #endif
- static int unload_module(void)
- {
- int res;
- res = ast_custom_function_unregister(&acf_curl);
- res |= ast_custom_function_unregister(&acf_curlopt);
- AST_TEST_UNREGISTER(vulnerable_url);
- return res;
- }
- static int load_module(void)
- {
- int res;
- if (!ast_module_check("res_curl.so")) {
- if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
- ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
- return AST_MODULE_LOAD_DECLINE;
- }
- }
- res = ast_custom_function_register(&acf_curl);
- res |= ast_custom_function_register(&acf_curlopt);
- AST_TEST_REGISTER(vulnerable_url);
- return res;
- }
- AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
- .support_level = AST_MODULE_SUPPORT_CORE,
- .load = load_module,
- .unload = unload_module,
- .load_pri = AST_MODPRI_REALTIME_DEPEND2,
- );
|