diff --git a/CHANGES b/CHANGES index 057542f1821a789e5f0ab91b2ec4f6f6ddc47a76..1cb8a9f3d4bdddf7e3a04c0905cf11ab87419313 100644 --- 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 diff --git a/funcs/func_curl.c b/funcs/func_curl.c index fd03fc375f7e118222e611b5cb1d8f15e985bef0..6a8c367672e021224085eb24113dc643b4dfcdc9 100644 --- a/funcs/func_curl.c +++ b/funcs/func_curl.c @@ -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);