Asterisk - The Open Source Telephony Project GIT-master-2de1a68
Data Structures | Macros | Enumerations | Functions | Variables
func_curl.c File Reference

Curl - Load a URL. More...

#include "asterisk.h"
#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"
Include dependency graph for func_curl.c:

Go to the source code of this file.

Data Structures

struct  curl_args
 
struct  curl_settings
 
struct  curl_write_callback_data
 Callback data passed to WriteMemoryCallback. More...
 
struct  global_curl_info
 

Macros

#define CURLOPT_SPECIAL_FAILURE_CODE   999
 
#define CURLOPT_SPECIAL_HASHCOMPAT   ((CURLoption) -500)
 
#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))))
 

Enumerations

enum  hashcompat { HASHCOMPAT_NO = 0 , HASHCOMPAT_YES , HASHCOMPAT_LEGACY }
 
enum  optiontype {
  OT_BOOLEAN , OT_INTEGER , OT_INTEGER_MS , OT_STRING ,
  OT_ENUM
}
 

Functions

static void __init_curl_instance (void)
 
static void __init_thread_escapebuf (void)
 
static void __reg_module (void)
 
static void __unreg_module (void)
 
static int acf_curl_exec (struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
 
static int acf_curl_helper (struct ast_channel *chan, struct curl_args *args)
 
static int acf_curl_write (struct ast_channel *chan, const char *cmd, char *name, const char *value)
 
static int acf_curlopt_helper (struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
 
static int acf_curlopt_read (struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 
static int acf_curlopt_read2 (struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
 
static int acf_curlopt_write (struct ast_channel *chan, const char *cmd, char *name, const char *value)
 
struct ast_moduleAST_MODULE_SELF_SYM (void)
 
static void curl_instance_cleanup (void *data)
 
static int curl_instance_init (void *data)
 
static void curlds_free (void *data)
 
static int load_module (void)
 
static int parse_curlopt_key (const char *name, CURLoption *key, enum optiontype *ot)
 
static int unload_module (void)
 
static int url_is_vulnerable (const char *url)
 Check for potential HTTP injection risk. More...
 
static size_t WriteMemoryCallback (void *ptr, size_t size, size_t nmemb, void *data)
 

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Load external URL" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_REALTIME_DEPEND2, .requires = "res_curl", }
 
static struct ast_custom_function acf_curl
 
static struct ast_custom_function acf_curlopt
 
static const struct ast_module_infoast_module_info = &__mod_info
 
static const struct ast_datastore_info curl_info
 
static struct ast_threadstorage curl_instance = { .once = PTHREAD_ONCE_INIT , .key_init = __init_curl_instance , .custom_init = curl_instance_init , }
 
struct global_curl_info global_curl_info = { .first = NULL, .last = NULL, .lock = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP , NULL, {1, 0} } , }
 
static struct ast_threadstorage thread_escapebuf = { .once = PTHREAD_ONCE_INIT , .key_init = __init_thread_escapebuf , .custom_init = NULL , }
 

Detailed Description

Curl - Load a URL.

Author
Tilghman Lesher curl-.nosp@m.2005.nosp@m.0919@.nosp@m.the-.nosp@m.tilgh.nosp@m.man..nosp@m.com
Note
Brian Wilkins bwilk.nosp@m.ins@.nosp@m.cfl.r.nosp@m.r.co.nosp@m.m (Added POST option)
ExtRef:
Depends on the CURL library - http://curl.haxx.se/

Definition in file func_curl.c.

Macro Definition Documentation

◆ CURLOPT_SPECIAL_FAILURE_CODE

#define CURLOPT_SPECIAL_FAILURE_CODE   999

Definition at line 212 of file func_curl.c.

◆ CURLOPT_SPECIAL_HASHCOMPAT

#define CURLOPT_SPECIAL_HASHCOMPAT   ((CURLoption) -500)

Definition at line 210 of file func_curl.c.

◆ CURLVERSION_ATLEAST

#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))))

Definition at line 207 of file func_curl.c.

Enumeration Type Documentation

◆ hashcompat

enum hashcompat
Enumerator
HASHCOMPAT_NO 
HASHCOMPAT_YES 
HASHCOMPAT_LEGACY 

Definition at line 251 of file func_curl.c.

251 {
252 HASHCOMPAT_NO = 0,
255};
@ HASHCOMPAT_NO
Definition: func_curl.c:252
@ HASHCOMPAT_LEGACY
Definition: func_curl.c:254
@ HASHCOMPAT_YES
Definition: func_curl.c:253

◆ optiontype

enum optiontype
Enumerator
OT_BOOLEAN 
OT_INTEGER 
OT_INTEGER_MS 
OT_STRING 
OT_ENUM 

Definition at line 243 of file func_curl.c.

243 {
247 OT_STRING,
248 OT_ENUM,
249};
@ OT_BOOLEAN
Definition: func_curl.c:244
@ OT_ENUM
Definition: func_curl.c:248
@ OT_STRING
Definition: func_curl.c:247
@ OT_INTEGER
Definition: func_curl.c:245
@ OT_INTEGER_MS
Definition: func_curl.c:246

Function Documentation

◆ __init_curl_instance()

static void __init_curl_instance ( void  )
static

Definition at line 639 of file func_curl.c.

659{

◆ __init_thread_escapebuf()

static void __init_thread_escapebuf ( void  )
static

Definition at line 640 of file func_curl.c.

659{

◆ __reg_module()

static void __reg_module ( void  )
static

Definition at line 996 of file func_curl.c.

◆ __unreg_module()

static void __unreg_module ( void  )
static

Definition at line 996 of file func_curl.c.

◆ acf_curl_exec()

static int acf_curl_exec ( struct ast_channel chan,
const char *  cmd,
char *  info,
struct ast_str **  buf,
ssize_t  len 
)
static

Definition at line 834 of file func_curl.c.

835{
836 struct curl_args curl_params = { 0, };
837 int res;
838
842 );
843
845
846 if (ast_strlen_zero(info)) {
847 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
848 return -1;
849 }
850
851 curl_params.url = args.url;
852 curl_params.postdata = args.postdata;
853 curl_params.cb_data.str = ast_str_create(16);
854 if (!curl_params.cb_data.str) {
855 return -1;
856 }
857
858 res = acf_curl_helper(chan, &curl_params);
859 ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
860 ast_free(curl_params.cb_data.str);
861
862 return res;
863}
#define ast_free(a)
Definition: astmm.h:180
#define ast_log
Definition: astobj2.c:42
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
Definition: func_curl.c:673
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define LOG_WARNING
def info(msg)
static char url[512]
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
const char * postdata
Definition: func_curl.c:669
struct curl_write_callback_data cb_data
Definition: func_curl.c:670
const char * url
Definition: func_curl.c:668
struct ast_str * str
If a string is being built, the string buffer.
Definition: func_curl.c:593
const char * args

References acf_curl_helper(), args, AST_APP_ARG, AST_DECLARE_APP_ARGS, ast_free, ast_log, AST_STANDARD_APP_ARGS, ast_str_buffer(), ast_str_create, ast_str_set(), ast_strlen_zero(), buf, curl_args::cb_data, sip_to_pjsip::info(), len(), LOG_WARNING, curl_args::postdata, curl_write_callback_data::str, curl_args::url, and url.

◆ acf_curl_helper()

static int acf_curl_helper ( struct ast_channel chan,
struct curl_args args 
)
static

Definition at line 673 of file func_curl.c.

674{
675 struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
676 int ret = 0;
677 long http_code = 0; /* read curl response */
678 size_t i;
679 struct ast_vector_int hasfailurecode = { NULL };
680 char *failurecodestrings,*found;
681 CURL **curl;
682 struct curl_settings *cur;
683 struct curl_slist *headers = NULL;
684 struct ast_datastore *store = NULL;
685 int hashcompat = 0;
687 char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
688
689 if (!escapebuf) {
690 return -1;
691 }
692
693 if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
694 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
695 return -1;
696 }
697
698 if (url_is_vulnerable(args->url)) {
699 ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args->url);
700 return -1;
701 }
702
703 if (chan) {
705 }
706
707 AST_VECTOR_INIT(&hasfailurecode, 0); /*Initialize vector*/
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) {
717 AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
718 }
719 } else {
720 curl_easy_setopt(*curl, cur->key, cur->value);
721 }
722 }
724
725 if (chan) {
726 ast_channel_lock(chan);
728 ast_channel_unlock(chan);
729 if (store) {
730 list = store->data;
731 AST_LIST_LOCK(list);
732 AST_LIST_TRAVERSE(list, cur, list) {
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) {
740 AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
741 }
742 } else {
743 curl_easy_setopt(*curl, cur->key, cur->value);
744 }
745 }
746 }
747 }
748
749 curl_easy_setopt(*curl, CURLOPT_URL, args->url);
750 curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
751
752 if (args->postdata) {
753 curl_easy_setopt(*curl, CURLOPT_POST, 1);
754 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
755 }
756
757 /* Always assign the headers - even when NULL - in case we had
758 * custom headers the last time we used this shared cURL
759 * instance */
760 curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers);
761
762 /* Temporarily assign a buffer for curl to write errors to. */
763 curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
764 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
765
766 if (curl_easy_perform(*curl) != 0) {
767 ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args->url);
768 }
769
770 /* Reset buffer to NULL so curl doesn't try to write to it when the
771 * buffer is deallocated. Documentation is vague about allowing NULL
772 * here, but the source allows it. See: "typecheck: allow NULL to unset
773 * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
774 curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
775 curl_easy_getinfo (*curl, CURLINFO_RESPONSE_CODE, &http_code);
776
777 for (i = 0; i < AST_VECTOR_SIZE(&hasfailurecode); ++i) {
778 if (http_code == AST_VECTOR_GET(&hasfailurecode,i)){
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) : ": ",
782 args->url,
783 http_code);
784 ret=-1;
785 break;
786 }
787 }
788 AST_VECTOR_FREE(&hasfailurecode); /* Release the vector*/
789
790 if (store) {
791 AST_LIST_UNLOCK(list);
792 }
793 curl_slist_free_all(headers);
794
795 if (args->postdata) {
796 curl_easy_setopt(*curl, CURLOPT_POST, 0);
797 }
798
799 if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
800 ast_str_trim_blanks(args->cb_data.str);
801
802 ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
803 if (hashcompat) {
804 char *remainder = ast_str_buffer(args->cb_data.str);
805 char *piece;
806 struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
807 struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
808 int rowcount = 0;
809 while (fields && values && (piece = strsep(&remainder, "&"))) {
810 char *name = strsep(&piece, "=");
812 if (piece) {
813 ast_uri_decode(piece, mode);
814 }
815 ast_uri_decode(name, mode);
816 ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
817 ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
818 rowcount++;
819 }
820 pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
821 ast_str_set(&args->cb_data.str, 0, "%s", ast_str_buffer(values));
822 ast_free(fields);
824 }
825 }
826
827 if (chan) {
829 }
830
831 return ret;
832}
const char * ast_channel_name(const struct ast_channel *chan)
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:266
#define ast_channel_lock(chan)
Definition: channel.h:2922
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:200
#define ast_channel_unlock(chan)
Definition: channel.h:2923
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.
Definition: channel.c:2399
static const char name[]
Definition: format_mp3.c:68
#define CURLOPT_SPECIAL_HASHCOMPAT
Definition: func_curl.c:210
static const struct ast_datastore_info curl_info
Definition: func_curl.c:216
#define CURLOPT_SPECIAL_FAILURE_CODE
Definition: func_curl.c:212
static struct ast_threadstorage thread_escapebuf
Definition: func_curl.c:640
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
Definition: func_curl.c:658
static struct ast_threadstorage curl_instance
Definition: func_curl.c:639
hashcompat
Definition: func_curl.c:251
char * strsep(char **str, const char *delims)
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Definition: linkedlists.h:173
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 NULL
Definition: resample.c:96
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
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.
Definition: strings.h:1069
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
Definition: strings.h:719
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
struct ast_str * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:909
Structure for a data store object.
Definition: datastore.h:64
void * data
Definition: datastore.h:66
Structure used to handle boolean flags.
Definition: utils.h:199
Support for dynamic strings.
Definition: strings.h:623
Integer vector definition.
Definition: vector.h:52
void * value
Definition: func_curl.c:224
CURLoption key
Definition: func_curl.c:223
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
const struct ast_flags ast_uri_http
Definition: utils.c:719
void ast_uri_decode(char *s, struct ast_flags spec)
Decode URI, URN, URL (overwrite string)
Definition: utils.c:762
const struct ast_flags ast_uri_http_legacy
Definition: utils.c:720
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680

References args, ast_autoservice_start(), ast_autoservice_stop(), ast_channel_datastore_find(), ast_channel_lock, ast_channel_name(), ast_channel_unlock, ast_debug, ast_free, AST_LIST_HEAD, AST_LIST_LOCK, AST_LIST_TRAVERSE, AST_LIST_UNLOCK, ast_log, ast_str_append(), ast_str_buffer(), ast_str_create, ast_str_set(), ast_str_set_escapecommas(), ast_str_strlen(), ast_str_thread_get(), ast_str_trim_blanks(), ast_threadstorage_get(), ast_uri_decode(), ast_uri_http, ast_uri_http_legacy, AST_VECTOR_APPEND, AST_VECTOR_FREE, AST_VECTOR_GET, AST_VECTOR_INIT, AST_VECTOR_SIZE, curl_info, curl_instance, CURLOPT_SPECIAL_FAILURE_CODE, CURLOPT_SPECIAL_HASHCOMPAT, ast_datastore::data, HASHCOMPAT_LEGACY, curl_settings::key, LOG_ERROR, LOG_NOTICE, LOG_WARNING, name, NULL, pbx_builtin_setvar_helper(), S_OR, strsep(), thread_escapebuf, url_is_vulnerable(), and curl_settings::value.

Referenced by acf_curl_exec(), and acf_curl_write().

◆ acf_curl_write()

static int acf_curl_write ( struct ast_channel chan,
const char *  cmd,
char *  name,
const char *  value 
)
static

Definition at line 865 of file func_curl.c.

866{
867 struct curl_args curl_params = { 0, };
868 int res;
869 char *args_value = ast_strdupa(value);
871 AST_APP_ARG(file_path);
872 );
873
874 AST_STANDARD_APP_ARGS(args, args_value);
875
876 if (ast_strlen_zero(name)) {
877 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
878 return -1;
879 }
880
881 if (ast_strlen_zero(args.file_path)) {
882 ast_log(LOG_WARNING, "CURL requires a file to write\n");
883 return -1;
884 }
885
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",
890 args.file_path,
891 strerror(errno),
892 errno);
893 return -1;
894 }
895
896 res = acf_curl_helper(chan, &curl_params);
897
898 fclose(curl_params.cb_data.out_file);
899
900 return res;
901}
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
int errno
FILE * out_file
If a file is being retrieved, the file to write to.
Definition: func_curl.c:597
int value
Definition: syslog.c:37

References acf_curl_helper(), args, AST_APP_ARG, AST_DECLARE_APP_ARGS, ast_log, AST_STANDARD_APP_ARGS, ast_strdupa, ast_strlen_zero(), curl_args::cb_data, errno, LOG_WARNING, name, curl_write_callback_data::out_file, curl_args::url, and value.

◆ acf_curlopt_helper()

static int acf_curlopt_helper ( struct ast_channel chan,
const char *  cmd,
char *  data,
char *  buf,
struct ast_str **  bufstr,
ssize_t  len 
)
static

Definition at line 465 of file func_curl.c.

466{
467 struct ast_datastore *store;
468 struct global_curl_info *list[2] = { &global_curl_info, NULL };
469 struct curl_settings *cur = NULL;
470 CURLoption key;
471 enum optiontype ot;
472 int i;
473
474 if (parse_curlopt_key(data, &key, &ot)) {
475 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
476 return -1;
477 }
478
479 if (chan) {
480 /* If we have a channel, we want to read the options set there before
481 falling back to the global settings */
482 ast_channel_lock(chan);
484 ast_channel_unlock(chan);
485
486 if (store) {
487 list[0] = store->data;
489 }
490 }
491
492 for (i = 0; i < 2; i++) {
493 if (!list[i]) {
494 break;
495 }
497 AST_LIST_TRAVERSE(list[i], cur, list) {
498 if (cur->key == key) {
499 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
500 if (buf) {
501 snprintf(buf, len, "%ld", (long) cur->value);
502 } else {
503 ast_str_set(bufstr, len, "%ld", (long) cur->value);
504 }
505 } else if (ot == OT_INTEGER_MS) {
506 if ((long) cur->value % 1000 == 0) {
507 if (buf) {
508 snprintf(buf, len, "%ld", (long)cur->value / 1000);
509 } else {
510 ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
511 }
512 } else {
513 if (buf) {
514 snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
515 } else {
516 ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
517 }
518 }
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);
521 if (buf) {
523 } else {
524 ast_str_set(bufstr, 0, "%s", (char *) cur->value);
525 }
526 } else if (key == CURLOPT_PROXYTYPE) {
527 const char *strval = "unknown";
528 if (0) {
529#if CURLVERSION_ATLEAST(7,15,2)
530 } else if ((long)cur->value == CURLPROXY_SOCKS4) {
531 strval = "socks4";
532#endif
533#if CURLVERSION_ATLEAST(7,18,0)
534 } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
535 strval = "socks4a";
536#endif
537 } else if ((long)cur->value == CURLPROXY_SOCKS5) {
538 strval = "socks5";
539#if CURLVERSION_ATLEAST(7,18,0)
540 } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
541 strval = "socks5hostname";
542#endif
543#if CURLVERSION_ATLEAST(7,10,0)
544 } else if ((long)cur->value == CURLPROXY_HTTP) {
545 strval = "http";
546#endif
547 }
548 if (buf) {
549 ast_copy_string(buf, strval, len);
550 } else {
551 ast_str_set(bufstr, 0, "%s", strval);
552 }
553 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
554 const char *strval = "unknown";
555 if ((long) cur->value == HASHCOMPAT_LEGACY) {
556 strval = "legacy";
557 } else if ((long) cur->value == HASHCOMPAT_YES) {
558 strval = "yes";
559 } else if ((long) cur->value == HASHCOMPAT_NO) {
560 strval = "no";
561 }
562 if (buf) {
563 ast_copy_string(buf, strval, len);
564 } else {
565 ast_str_set(bufstr, 0, "%s", strval);
566 }
567 }
568 break;
569 }
570 }
572 if (cur) {
573 break;
574 }
575 }
576
577 return cur ? 0 : -1;
578}
optiontype
Definition: func_curl.c:243
struct global_curl_info global_curl_info
static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
Definition: func_curl.c:257
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
struct curl_settings::@168 list

References ast_channel_datastore_find(), ast_channel_lock, ast_channel_unlock, ast_copy_string(), ast_debug, AST_LIST_LOCK, AST_LIST_TRAVERSE, AST_LIST_UNLOCK, ast_log, ast_str_set(), buf, curl_info, CURLOPT_SPECIAL_HASHCOMPAT, ast_datastore::data, global_curl_info, HASHCOMPAT_LEGACY, HASHCOMPAT_NO, HASHCOMPAT_YES, curl_settings::key, len(), curl_settings::list, LOG_ERROR, NULL, OT_BOOLEAN, OT_INTEGER, OT_INTEGER_MS, OT_STRING, parse_curlopt_key(), and curl_settings::value.

Referenced by acf_curlopt_read(), and acf_curlopt_read2().

◆ acf_curlopt_read()

static int acf_curlopt_read ( struct ast_channel chan,
const char *  cmd,
char *  data,
char *  buf,
size_t  len 
)
static

Definition at line 580 of file func_curl.c.

581{
582 return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
583}
static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
Definition: func_curl.c:465

References acf_curlopt_helper(), buf, len(), and NULL.

◆ acf_curlopt_read2()

static int acf_curlopt_read2 ( struct ast_channel chan,
const char *  cmd,
char *  data,
struct ast_str **  buf,
ssize_t  len 
)
static

Definition at line 585 of file func_curl.c.

586{
587 return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
588}

References acf_curlopt_helper(), buf, len(), and NULL.

◆ acf_curlopt_write()

static int acf_curlopt_write ( struct ast_channel chan,
const char *  cmd,
char *  name,
const char *  value 
)
static

Definition at line 335 of file func_curl.c.

336{
337 struct ast_datastore *store;
338 struct global_curl_info *list;
339 struct curl_settings *cur, *new = NULL;
340 CURLoption key;
341 enum optiontype ot;
342
343 if (chan) {
344 ast_channel_lock(chan);
345 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
346 /* Create a new datastore */
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);
350 return -1;
351 }
352
353 if (!(list = ast_calloc(1, sizeof(*list)))) {
354 ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
355 ast_datastore_free(store);
356 ast_channel_unlock(chan);
357 return -1;
358 }
359
360 store->data = list;
362 ast_channel_datastore_add(chan, store);
363 } else {
364 list = store->data;
365 }
366 ast_channel_unlock(chan);
367 } else {
368 /* Populate the global structure */
370 }
371
372 if (!parse_curlopt_key(name, &key, &ot)) {
373 if (ot == OT_BOOLEAN) {
374 if ((new = ast_calloc(1, sizeof(*new)))) {
375 new->value = (void *)((long) ast_true(value));
376 }
377 } else if (ot == OT_INTEGER) {
378 long tmp = atol(value);
379 if ((new = ast_calloc(1, sizeof(*new)))) {
380 new->value = (void *)tmp;
381 }
382 } else if (ot == OT_INTEGER_MS) {
383 long tmp = atof(value) * 1000.0;
384 if ((new = ast_calloc(1, sizeof(*new)))) {
385 new->value = (void *)tmp;
386 }
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);
391 }
392 } else if (ot == OT_ENUM) {
393 if (key == CURLOPT_PROXYTYPE) {
394 long ptype =
395#if CURLVERSION_ATLEAST(7,10,0)
396 CURLPROXY_HTTP;
397#else
398 CURLPROXY_SOCKS5;
399#endif
400 if (0) {
401#if CURLVERSION_ATLEAST(7,15,2)
402 } else if (!strcasecmp(value, "socks4")) {
403 ptype = CURLPROXY_SOCKS4;
404#endif
405#if CURLVERSION_ATLEAST(7,18,0)
406 } else if (!strcasecmp(value, "socks4a")) {
407 ptype = CURLPROXY_SOCKS4A;
408#endif
409#if CURLVERSION_ATLEAST(7,18,0)
410 } else if (!strcasecmp(value, "socks5")) {
411 ptype = CURLPROXY_SOCKS5;
412#endif
413#if CURLVERSION_ATLEAST(7,18,0)
414 } else if (!strncasecmp(value, "socks5", 6)) {
415 ptype = CURLPROXY_SOCKS5_HOSTNAME;
416#endif
417 }
418
419 if ((new = ast_calloc(1, sizeof(*new)))) {
420 new->value = (void *)ptype;
421 }
422 } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
423 if ((new = ast_calloc(1, sizeof(*new)))) {
424 new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
425 }
426 } else {
427 /* Highly unlikely */
428 goto yuck;
429 }
430 }
431
432 /* Memory allocation error */
433 if (!new) {
434 return -1;
435 }
436
437 new->key = key;
438 } else {
439yuck:
440 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
441 return -1;
442 }
443
444 /* Remove any existing entry, only http headers are left */
446 if (new->key != CURLOPT_HTTPHEADER) {
448 if (cur->key == new->key) {
450 ast_free(cur);
451 break;
452 }
453 }
455 }
456
457 /* Insert new entry */
458 ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
461
462 return 0;
463}
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
static int tmp()
Definition: bt_open.c:389
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:85
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:626
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199

References ast_calloc, ast_channel_datastore_add(), ast_channel_datastore_find(), ast_channel_lock, ast_channel_unlock, ast_datastore_alloc, ast_datastore_free(), ast_debug, ast_free, AST_LIST_HEAD_INIT, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_REMOVE_CURRENT, AST_LIST_TRAVERSE_SAFE_BEGIN, AST_LIST_TRAVERSE_SAFE_END, AST_LIST_UNLOCK, ast_log, ast_true(), curl_info, CURLOPT_SPECIAL_HASHCOMPAT, ast_datastore::data, global_curl_info, HASHCOMPAT_LEGACY, HASHCOMPAT_NO, HASHCOMPAT_YES, curl_settings::key, curl_settings::list, LOG_ERROR, name, NULL, OT_BOOLEAN, OT_ENUM, OT_INTEGER, OT_INTEGER_MS, OT_STRING, parse_curlopt_key(), tmp(), and value.

◆ AST_MODULE_SELF_SYM()

struct ast_module * AST_MODULE_SELF_SYM ( void  )

Definition at line 996 of file func_curl.c.

◆ curl_instance_cleanup()

static void curl_instance_cleanup ( void *  data)
static

Definition at line 630 of file func_curl.c.

631{
632 CURL **curl = data;
633
634 curl_easy_cleanup(*curl);
635
636 ast_free(data);
637}

References ast_free.

◆ curl_instance_init()

static int curl_instance_init ( void *  data)
static

Definition at line 615 of file func_curl.c.

616{
617 CURL **curl = data;
618
619 if (!(*curl = curl_easy_init()))
620 return -1;
621
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);
626
627 return 0;
628}
#define AST_CURL_USER_AGENT
Definition: asterisk.h:44
static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
Definition: func_curl.c:600

References AST_CURL_USER_AGENT, and WriteMemoryCallback().

◆ curlds_free()

static void curlds_free ( void *  data)
static

Definition at line 229 of file func_curl.c.

230{
232 struct curl_settings *setting;
233 if (!list) {
234 return;
235 }
236 while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
237 ast_free(setting);
238 }
240 ast_free(list);
241}
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:653
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833

References ast_free, AST_LIST_HEAD, AST_LIST_HEAD_DESTROY, AST_LIST_REMOVE_HEAD, and curl_settings::list.

◆ load_module()

static int load_module ( void  )
static

Definition at line 978 of file func_curl.c.

979{
980 int res;
981
984
985 AST_TEST_REGISTER(vulnerable_url);
986
987 return res;
988}
static struct ast_custom_function acf_curlopt
Definition: func_curl.c:909
static struct ast_custom_function acf_curl
Definition: func_curl.c:903
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1567
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
@ AST_CFE_WRITE
Definition: pbx.h:1551
#define AST_TEST_REGISTER(cb)
Definition: test.h:127

References acf_curl, acf_curlopt, AST_CFE_WRITE, ast_custom_function_register, ast_custom_function_register_escalating, and AST_TEST_REGISTER.

◆ parse_curlopt_key()

static int parse_curlopt_key ( const char *  name,
CURLoption *  key,
enum optiontype ot 
)
static

Definition at line 257 of file func_curl.c.

258{
259 if (!strcasecmp(name, "header")) {
260 *key = CURLOPT_HEADER;
261 *ot = OT_BOOLEAN;
262 } else if (!strcasecmp(name, "httpheader")) {
263 *key = CURLOPT_HTTPHEADER;
264 *ot = OT_STRING;
265 } else if (!strcasecmp(name, "proxy")) {
266 *key = CURLOPT_PROXY;
267 *ot = OT_STRING;
268 } else if (!strcasecmp(name, "proxyport")) {
269 *key = CURLOPT_PROXYPORT;
270 *ot = OT_INTEGER;
271 } else if (!strcasecmp(name, "proxytype")) {
272 *key = CURLOPT_PROXYTYPE;
273 *ot = OT_ENUM;
274 } else if (!strcasecmp(name, "dnstimeout")) {
275 *key = CURLOPT_DNS_CACHE_TIMEOUT;
276 *ot = OT_INTEGER;
277 } else if (!strcasecmp(name, "userpwd")) {
278 *key = CURLOPT_USERPWD;
279 *ot = OT_STRING;
280 } else if (!strcasecmp(name, "proxyuserpwd")) {
281 *key = CURLOPT_PROXYUSERPWD;
282 *ot = OT_STRING;
283 } else if (!strcasecmp(name, "followlocation")) {
284 *key = CURLOPT_FOLLOWLOCATION;
285 *ot = OT_BOOLEAN;
286 } else if (!strcasecmp(name, "maxredirs")) {
287 *key = CURLOPT_MAXREDIRS;
288 *ot = OT_INTEGER;
289 } else if (!strcasecmp(name, "referer")) {
290 *key = CURLOPT_REFERER;
291 *ot = OT_STRING;
292 } else if (!strcasecmp(name, "useragent")) {
293 *key = CURLOPT_USERAGENT;
294 *ot = OT_STRING;
295 } else if (!strcasecmp(name, "cookie")) {
296 *key = CURLOPT_COOKIE;
297 *ot = OT_STRING;
298 } else if (!strcasecmp(name, "ftptimeout")) {
299 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
300 *ot = OT_INTEGER;
301 } else if (!strcasecmp(name, "httptimeout")) {
302#if CURLVERSION_ATLEAST(7,16,2)
303 *key = CURLOPT_TIMEOUT_MS;
304 *ot = OT_INTEGER_MS;
305#else
306 *key = CURLOPT_TIMEOUT;
307 *ot = OT_INTEGER;
308#endif
309 } else if (!strcasecmp(name, "conntimeout")) {
310#if CURLVERSION_ATLEAST(7,16,2)
311 *key = CURLOPT_CONNECTTIMEOUT_MS;
312 *ot = OT_INTEGER_MS;
313#else
314 *key = CURLOPT_CONNECTTIMEOUT;
315 *ot = OT_INTEGER;
316#endif
317 } else if (!strcasecmp(name, "ftptext")) {
318 *key = CURLOPT_TRANSFERTEXT;
319 *ot = OT_BOOLEAN;
320 } else if (!strcasecmp(name, "ssl_verifypeer")) {
321 *key = CURLOPT_SSL_VERIFYPEER;
322 *ot = OT_BOOLEAN;
323 } else if (!strcasecmp(name, "hashcompat")) {
325 *ot = OT_ENUM;
326 } else if (!strcasecmp(name, "failurecodes")) {
328 *ot = OT_STRING;
329 } else {
330 return -1;
331 }
332 return 0;
333}

References CURLOPT_SPECIAL_FAILURE_CODE, CURLOPT_SPECIAL_HASHCOMPAT, curl_settings::key, name, OT_BOOLEAN, OT_ENUM, OT_INTEGER, OT_INTEGER_MS, and OT_STRING.

Referenced by acf_curlopt_helper(), and acf_curlopt_write().

◆ unload_module()

static int unload_module ( void  )
static

Definition at line 966 of file func_curl.c.

967{
968 int res;
969
972
973 AST_TEST_UNREGISTER(vulnerable_url);
974
975 return res;
976}
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128

References acf_curl, acf_curlopt, ast_custom_function_unregister(), and AST_TEST_UNREGISTER.

◆ url_is_vulnerable()

static int url_is_vulnerable ( const char *  url)
static

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.

Parameters
urlThe URL to check for vulnerability
Return values
0The URL is not vulnerable
1The URL is vulnerable.

Definition at line 658 of file func_curl.c.

659{
660 if (strpbrk(url, "\r\n")) {
661 return 1;
662 }
663
664 return 0;
665}

References url.

Referenced by acf_curl_helper().

◆ WriteMemoryCallback()

static size_t WriteMemoryCallback ( void *  ptr,
size_t  size,
size_t  nmemb,
void *  data 
)
static

Definition at line 600 of file func_curl.c.

601{
602 register int realsize = 0;
603 struct curl_write_callback_data *cb_data = data;
604
605 if (cb_data->str) {
606 realsize = size * nmemb;
607 ast_str_append_substr(&cb_data->str, 0, ptr, realsize);
608 } else if (cb_data->out_file) {
609 realsize = fwrite(ptr, size, nmemb, cb_data->out_file);
610 }
611
612 return realsize;
613}
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.
Definition: strings.h:1062
Callback data passed to WriteMemoryCallback.
Definition: func_curl.c:591

References ast_str_append_substr(), curl_write_callback_data::out_file, and curl_write_callback_data::str.

Referenced by curl_instance_init().

Variable Documentation

◆ __mod_info

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Load external URL" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_REALTIME_DEPEND2, .requires = "res_curl", }
static

Definition at line 996 of file func_curl.c.

◆ acf_curl

struct ast_custom_function acf_curl
static
Initial value:
= {
.name = "CURL",
.read2 = acf_curl_exec,
.write = acf_curl_write,
}
static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:834
static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:865

Definition at line 903 of file func_curl.c.

Referenced by load_module(), and unload_module().

◆ acf_curlopt

struct ast_custom_function acf_curlopt
static

Definition at line 909 of file func_curl.c.

Referenced by load_module(), and unload_module().

◆ ast_module_info

const struct ast_module_info* ast_module_info = &__mod_info
static

Definition at line 996 of file func_curl.c.

◆ curl_info

const struct ast_datastore_info curl_info
static
Initial value:
= {
.type = "CURL",
.destroy = curlds_free,
}
static void curlds_free(void *data)
Definition: func_curl.c:229

Definition at line 216 of file func_curl.c.

Referenced by acf_curl_helper(), acf_curlopt_helper(), and acf_curlopt_write().

◆ curl_instance

struct ast_threadstorage curl_instance = { .once = PTHREAD_ONCE_INIT , .key_init = __init_curl_instance , .custom_init = curl_instance_init , }
static

Definition at line 639 of file func_curl.c.

Referenced by acf_curl_helper().

◆ global_curl_info

struct global_curl_info global_curl_info = { .first = NULL, .last = NULL, .lock = { PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP , NULL, {1, 0} } , }

◆ thread_escapebuf

struct ast_threadstorage thread_escapebuf = { .once = PTHREAD_ONCE_INIT , .key_init = __init_thread_escapebuf , .custom_init = NULL , }
static

Definition at line 640 of file func_curl.c.

Referenced by acf_curl_helper().