]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
funcs/func_curl: Add the ability for CURL to download and store files 88/1888/4
authorMatthew Jordan <mjordan@digium.com>
Sun, 26 Oct 2014 01:21:18 +0000 (01:21 +0000)
committerJoshua Colp <jcolp@digium.com>
Wed, 23 Mar 2016 14:46:32 +0000 (11:46 -0300)
This patch adds a write option to the CURL dialplan function, allowing it to
CURL files and store them locally. The value 'written' to the CURL URL
specifies the location on disk to store the file. As an example:

same => n,Set(CURL(http://1.1.1.1/foo.wav)=/tmp/foo.wav)

Would retrieve the file foo.wav from the remote server and store it in the
/tmp directory.

Due to the potentially dangerous nature of this function call, APIs are
forbidden from using the write functionality unless live_dangerously is set
to True in asterisk.conf.

ASTERISK-25652 #close

Change-Id: I44f4ad823d7d20f04ceaad3698c5c7f653c41b0d

CHANGES
funcs/func_curl.c

diff --git a/CHANGES b/CHANGES
index 057542f1821a789e5f0ab91b2ec4f6f6ddc47a76..1cb8a9f3d4bdddf7e3a04c0905cf11ab87419313 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -140,6 +140,13 @@ CHANNEL
  * Added CHANNEL(onhold) item that returns 1 (onhold) and 0 (not-onhold) for
    the hold status of a channel.
 
+CURL
+------------------
+ * The CURL function now supports a write option, which will save the retrieved
+   file to a location on disk. As an example:
+     same => n,Set(CURL(https://1.1.1.1/foo.wav)=/tmp/foo.wav)
+   will save 'foo.wav' to /tmp.
+
 DTMF Features
 ------------------
  * The transferdialattempts default value has been changed from 1 to 3. The
index fd03fc375f7e118222e611b5cb1d8f15e985bef0..6a8c367672e021224085eb24113dc643b4dfcdc9 100644 (file)
@@ -58,15 +58,39 @@ ASTERISK_REGISTER_FILE()
                        Retrieve content from a remote web or ftp server
                </synopsis>
                <syntax>
-                       <parameter name="url" required="true" />
+                       <parameter name="url" required="true">
+                               <para>The full URL for the resource to retrieve.</para>
+                       </parameter>
                        <parameter name="post-data">
+                               <para><emphasis>Read Only</emphasis></para>
                                <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 />
+               <description>
+                       <para>When this function is read, a <literal>HTTP GET</literal>
+                       (by default) will be used to retrieve the contents of the provided
+                       <replaceable>url</replaceable>. The contents are returned as the
+                       result of the function.</para>
+                       <example title="Displaying contents of a page" language="text">
+                       exten => s,1,Verbose(0, ${CURL(http://localhost:8088/static/astman.css)})
+                       </example>
+                       <para>When this function is written to, a <literal>HTTP GET</literal>
+                       will be used to retrieve the contents of the provided
+                       <replaceable>url</replaceable>. The value written to the function
+                       specifies the destination file of the cURL'd resource.</para>
+                       <example title="Retrieving a file" language="text">
+                       exten => s,1,Set(CURL(http://localhost:8088/static/astman.css)=/var/spool/asterisk/tmp/astman.css))
+                       </example>
+                       <note>
+                               <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
+                               is set to <literal>no</literal>, this function can only be written to from the
+                               dialplan, and not directly from external protocols. Read operations are
+                               unaffected.</para>
+                       </note>
+               </description>
                <see-also>
                        <ref type="function">CURLOPT</ref>
                </see-also>
@@ -526,16 +550,27 @@ static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *da
        return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
 }
 
+/*! \brief Callback data passed to \ref WriteMemoryCallback */
+struct curl_write_callback_data {
+       /*! \brief If a string is being built, the string buffer */
+       struct ast_str *str;
+       /*! \brief The max size of \ref str */
+       ssize_t len;
+       /*! \brief If a file is being retrieved, the file to write to */
+       FILE *out_file;
+};
+
 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));
+       register int realsize = 0;
+       struct curl_write_callback_data *cb_data = data;
+
+       if (cb_data->str) {
+               realsize = size * nmemb;
+               ast_str_append_substr(&cb_data->str, 0, ptr, realsize);
+       } else if (cb_data->out_file) {
+               realsize = fwrite(ptr, size, nmemb, cb_data->out_file);
+       }
 
        return realsize;
 }
@@ -594,15 +629,16 @@ static int url_is_vulnerable(const char *url)
        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 curl_args {
+       const char *url;
+       const char *postdata;
+       struct curl_write_callback_data cb_data;
+};
+
+static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
 {
        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;
@@ -610,29 +646,17 @@ static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info
        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);
+       if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
+               ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
                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);
+       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;
        }
 
@@ -640,12 +664,6 @@ static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info
                ast_autoservice_start(chan);
        }
 
-       if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
-               ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
-               ast_free(str);
-               return -1;
-       }
-
        AST_LIST_LOCK(&global_curl_info);
        AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
                if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
@@ -668,12 +686,12 @@ static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info
                }
        }
 
-       curl_easy_setopt(*curl, CURLOPT_URL, args.url);
-       curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
+       curl_easy_setopt(*curl, CURLOPT_URL, args->url);
+       curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
 
-       if (args.postdata) {
+       if (args->postdata) {
                curl_easy_setopt(*curl, CURLOPT_POST, 1);
-               curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
+               curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
        }
 
        /* Temporarily assign a buffer for curl to write errors to. */
@@ -681,7 +699,7 @@ static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info
        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);
+               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
@@ -694,19 +712,19 @@ static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info
                AST_LIST_UNLOCK(list);
        }
 
-       if (args.postdata) {
+       if (args->postdata) {
                curl_easy_setopt(*curl, CURLOPT_POST, 0);
        }
 
-       if (ast_str_strlen(str)) {
-               ast_str_trim_blanks(str);
+       if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
+               ast_str_trim_blanks(args->cb_data.str);
 
-               ast_debug(3, "str='%s'\n", ast_str_buffer(str));
+               ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
                if (hashcompat) {
-                       char *remainder = ast_str_buffer(str);
+                       char *remainder = ast_str_buffer(args->cb_data.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);
+                       struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
+                       struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
                        int rowcount = 0;
                        while (fields && values && (piece = strsep(&remainder, "&"))) {
                                char *name = strsep(&piece, "=");
@@ -720,49 +738,93 @@ static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info
                                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_str_set(&args->cb_data.str, 0, "%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)
+       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)
+static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
 {
-       return acf_curl_helper(chan, cmd, info, buf, NULL, len);
+       struct curl_args curl_params = { 0, };
+       int res;
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(url);
+               AST_APP_ARG(postdata);
+       );
+
+       AST_STANDARD_APP_ARGS(args, info);
+
+       if (ast_strlen_zero(info)) {
+               ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
+               return -1;
+       }
+
+       curl_params.url = args.url;
+       curl_params.postdata = args.postdata;
+       curl_params.cb_data.str = ast_str_create(16);
+       if (!curl_params.cb_data.str) {
+               return -1;
+       }
+
+       res = acf_curl_helper(chan, &curl_params);
+       ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
+       ast_free(curl_params.cb_data.str);
+
+       return res;
 }
 
-static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
+static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
 {
-       return acf_curl_helper(chan, cmd, info, NULL, buf, len);
+       struct curl_args curl_params = { 0, };
+       int res;
+       char *args_value = ast_strdupa(value);
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(file_path);
+       );
+
+       AST_STANDARD_APP_ARGS(args, args_value);
+
+       if (ast_strlen_zero(name)) {
+               ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.file_path)) {
+               ast_log(LOG_WARNING, "CURL requires a file to write\n");
+               return -1;
+       }
+
+       curl_params.url = name;
+       curl_params.cb_data.out_file = fopen(args.file_path, "w");
+       if (!curl_params.cb_data.out_file) {
+               ast_log(LOG_WARNING, "Failed to open file %s: %s (%d)\n",
+                       args.file_path,
+                       strerror(errno),
+                       errno);
+               return -1;
+       }
+
+       res = acf_curl_helper(chan, &curl_params);
+
+       fclose(curl_params.cb_data.out_file);
+
+       return res;
 }
 
 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,
+       .read2 = acf_curl_exec,
+       .write = acf_curl_write,
 };
 
 static struct ast_custom_function acf_curlopt = {
@@ -865,7 +927,7 @@ static int load_module(void)
                }
        }
 
-       res = ast_custom_function_register(&acf_curl);
+       res = ast_custom_function_register_escalating(&acf_curl, AST_CFE_WRITE);
        res |= ast_custom_function_register(&acf_curlopt);
 
        AST_TEST_REGISTER(vulnerable_url);