[Asterisk-code-review] funcs/func curl: Add the ability for CURL to download and st... (asterisk[master])

Anonymous Coward asteriskteam at digium.com
Wed Mar 23 13:45:49 CDT 2016


Anonymous Coward #1000019 has submitted this change and it was merged.

Change subject: funcs/func_curl: Add the ability for CURL to download and store files
......................................................................


funcs/func_curl: Add the ability for CURL to download and store files

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
---
M CHANGES
M funcs/func_curl.c
2 files changed, 143 insertions(+), 74 deletions(-)

Approvals:
  Anonymous Coward #1000019: Verified
  Joshua Colp: Looks good to me, approved
  George Joseph: Looks good to me, but someone else must approve



diff --git a/CHANGES b/CHANGES
index 057542f..1cb8a9f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -140,6 +140,13 @@
  * 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
diff --git a/funcs/func_curl.c b/funcs/func_curl.c
index fd03fc3..6a8c367 100644
--- a/funcs/func_curl.c
+++ b/funcs/func_curl.c
@@ -58,15 +58,39 @@
 			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 @@
 	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;
+	register int realsize = 0;
+	struct curl_write_callback_data *cb_data = 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));
+	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 @@
 	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,40 +646,22 @@
 	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;
 	}
 
 	if (chan) {
 		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);
@@ -668,12 +686,12 @@
 		}
 	}
 
-	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 @@
 	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 @@
 		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 @@
 				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 @@
 		}
 	}
 
-	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);

-- 
To view, visit https://gerrit.asterisk.org/1888
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I44f4ad823d7d20f04ceaad3698c5c7f653c41b0d
Gerrit-PatchSet: 4
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: George Joseph <george.joseph at fairview5.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>



More information about the asterisk-code-review mailing list