func_curl.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C)  2004 - 2006, Tilghman Lesher
00005  *
00006  * Tilghman Lesher <curl-20050919@the-tilghman.com>
00007  * and Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option)
00008  *
00009  * app_curl.c is distributed with no restrictions on usage or
00010  * redistribution.
00011  *
00012  * See http://www.asterisk.org for more information about
00013  * the Asterisk project. Please do not directly contact
00014  * any of the maintainers of this project for assistance;
00015  * the project provides a web site, mailing lists and IRC
00016  * channels for your use.
00017  *
00018  */
00019 
00020 /*! \file
00021  * 
00022  * \brief Curl - Load a URL
00023  *
00024  * \author Tilghman Lesher <curl-20050919@the-tilghman.com>
00025  *
00026  * \note Brian Wilkins <bwilkins@cfl.rr.com> (Added POST option) 
00027  *
00028  * \extref Depends on the CURL library  - http://curl.haxx.se/
00029  * 
00030  * \ingroup functions
00031  */
00032  
00033 /*** MODULEINFO
00034    <depend>curl</depend>
00035    <support_level>core</support_level>
00036  ***/
00037 
00038 #include "asterisk.h"
00039 
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 434471 $")
00041 
00042 #include <curl/curl.h>
00043 
00044 #include "asterisk/lock.h"
00045 #include "asterisk/file.h"
00046 #include "asterisk/channel.h"
00047 #include "asterisk/pbx.h"
00048 #include "asterisk/cli.h"
00049 #include "asterisk/module.h"
00050 #include "asterisk/app.h"
00051 #include "asterisk/utils.h"
00052 #include "asterisk/threadstorage.h"
00053 #include "asterisk/test.h"
00054 
00055 /*** DOCUMENTATION
00056    <function name="CURL" language="en_US">
00057       <synopsis>
00058          Retrieve content from a remote web or ftp server
00059       </synopsis>
00060       <syntax>
00061          <parameter name="url" required="true" />
00062          <parameter name="post-data">
00063             <para>If specified, an <literal>HTTP POST</literal> will be
00064             performed with the content of
00065             <replaceable>post-data</replaceable>, instead of an
00066             <literal>HTTP GET</literal> (default).</para>
00067          </parameter>
00068       </syntax>
00069       <description />
00070       <see-also>
00071          <ref type="function">CURLOPT</ref>
00072       </see-also>
00073    </function>
00074    <function name="CURLOPT" language="en_US">
00075       <synopsis>
00076          Sets various options for future invocations of CURL.
00077       </synopsis>
00078       <syntax>
00079          <parameter name="key" required="yes">
00080             <enumlist>
00081                <enum name="cookie">
00082                   <para>A cookie to send with the request.  Multiple
00083                   cookies are supported.</para>
00084                </enum>
00085                <enum name="conntimeout">
00086                   <para>Number of seconds to wait for a connection to succeed</para>
00087                </enum>
00088                <enum name="dnstimeout">
00089                   <para>Number of seconds to wait for DNS to be resolved</para>
00090                </enum>
00091                <enum name="ftptext">
00092                   <para>For FTP URIs, force a text transfer (boolean)</para>
00093                </enum>
00094                <enum name="ftptimeout">
00095                   <para>For FTP URIs, number of seconds to wait for a
00096                   server response</para>
00097                </enum>
00098                <enum name="header">
00099                   <para>Include header information in the result
00100                   (boolean)</para>
00101                </enum>
00102                <enum name="httptimeout">
00103                   <para>For HTTP(S) URIs, number of seconds to wait for a
00104                   server response</para>
00105                </enum>
00106                <enum name="maxredirs">
00107                   <para>Maximum number of redirects to follow</para>
00108                </enum>
00109                <enum name="proxy">
00110                   <para>Hostname or IP address to use as a proxy server</para>
00111                </enum>
00112                <enum name="proxytype">
00113                   <para>Type of <literal>proxy</literal></para>
00114                   <enumlist>
00115                      <enum name="http" />
00116                      <enum name="socks4" />
00117                      <enum name="socks5" />
00118                   </enumlist>
00119                </enum>
00120                <enum name="proxyport">
00121                   <para>Port number of the <literal>proxy</literal></para>
00122                </enum>
00123                <enum name="proxyuserpwd">
00124                   <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
00125                   combination to use for authenticating requests through a
00126                   <literal>proxy</literal></para>
00127                </enum>
00128                <enum name="referer">
00129                   <para>Referer URL to use for the request</para>
00130                </enum>
00131                <enum name="useragent">
00132                   <para>UserAgent string to use for the request</para>
00133                </enum>
00134                <enum name="userpwd">
00135                   <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
00136                   to use for authentication when the server response to
00137                   an initial request indicates a 401 status code.</para>
00138                </enum>
00139                <enum name="ssl_verifypeer">
00140                   <para>Whether to verify the server certificate against
00141                   a list of known root certificate authorities (boolean).</para>
00142                </enum>
00143                <enum name="hashcompat">
00144                   <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
00145                   format, reformat the response such that it can be used
00146                   by the <literal>HASH</literal> function.</para>
00147                   <enumlist>
00148                      <enum name="yes" />
00149                      <enum name="no" />
00150                      <enum name="legacy">
00151                         <para>Also translate <literal>+</literal> to the
00152                         space character, in violation of current RFC
00153                         standards.</para>
00154                      </enum>
00155                   </enumlist>
00156                </enum>
00157             </enumlist>
00158          </parameter>
00159       </syntax>
00160       <description>
00161          <para>Options may be set globally or per channel.  Per-channel
00162          settings will override global settings.</para>
00163       </description>
00164       <see-also>
00165          <ref type="function">CURL</ref>
00166          <ref type="function">HASH</ref>
00167       </see-also>
00168    </function>
00169  ***/
00170 
00171 #define CURLVERSION_ATLEAST(a,b,c) \
00172    ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
00173 
00174 #define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
00175 
00176 static void curlds_free(void *data);
00177 
00178 static const struct ast_datastore_info curl_info = {
00179    .type = "CURL",
00180    .destroy = curlds_free,
00181 };
00182 
00183 struct curl_settings {
00184    AST_LIST_ENTRY(curl_settings) list;
00185    CURLoption key;
00186    void *value;
00187 };
00188 
00189 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
00190 
00191 static void curlds_free(void *data)
00192 {
00193    AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
00194    struct curl_settings *setting;
00195    if (!list) {
00196       return;
00197    }
00198    while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
00199       ast_free(setting);
00200    }
00201    AST_LIST_HEAD_DESTROY(list);
00202    ast_free(list);
00203 }
00204 
00205 enum optiontype {
00206    OT_BOOLEAN,
00207    OT_INTEGER,
00208    OT_INTEGER_MS,
00209    OT_STRING,
00210    OT_ENUM,
00211 };
00212 
00213 enum hashcompat {
00214    HASHCOMPAT_NO = 0,
00215    HASHCOMPAT_YES,
00216    HASHCOMPAT_LEGACY,
00217 };
00218 
00219 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
00220 {
00221    if (!strcasecmp(name, "header")) {
00222       *key = CURLOPT_HEADER;
00223       *ot = OT_BOOLEAN;
00224    } else if (!strcasecmp(name, "proxy")) {
00225       *key = CURLOPT_PROXY;
00226       *ot = OT_STRING;
00227    } else if (!strcasecmp(name, "proxyport")) {
00228       *key = CURLOPT_PROXYPORT;
00229       *ot = OT_INTEGER;
00230    } else if (!strcasecmp(name, "proxytype")) {
00231       *key = CURLOPT_PROXYTYPE;
00232       *ot = OT_ENUM;
00233    } else if (!strcasecmp(name, "dnstimeout")) {
00234       *key = CURLOPT_DNS_CACHE_TIMEOUT;
00235       *ot = OT_INTEGER;
00236    } else if (!strcasecmp(name, "userpwd")) {
00237       *key = CURLOPT_USERPWD;
00238       *ot = OT_STRING;
00239    } else if (!strcasecmp(name, "proxyuserpwd")) {
00240       *key = CURLOPT_PROXYUSERPWD;
00241       *ot = OT_STRING;
00242    } else if (!strcasecmp(name, "maxredirs")) {
00243       *key = CURLOPT_MAXREDIRS;
00244       *ot = OT_INTEGER;
00245    } else if (!strcasecmp(name, "referer")) {
00246       *key = CURLOPT_REFERER;
00247       *ot = OT_STRING;
00248    } else if (!strcasecmp(name, "useragent")) {
00249       *key = CURLOPT_USERAGENT;
00250       *ot = OT_STRING;
00251    } else if (!strcasecmp(name, "cookie")) {
00252       *key = CURLOPT_COOKIE;
00253       *ot = OT_STRING;
00254    } else if (!strcasecmp(name, "ftptimeout")) {
00255       *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
00256       *ot = OT_INTEGER;
00257    } else if (!strcasecmp(name, "httptimeout")) {
00258 #if CURLVERSION_ATLEAST(7,16,2)
00259       *key = CURLOPT_TIMEOUT_MS;
00260       *ot = OT_INTEGER_MS;
00261 #else
00262       *key = CURLOPT_TIMEOUT;
00263       *ot = OT_INTEGER;
00264 #endif
00265    } else if (!strcasecmp(name, "conntimeout")) {
00266 #if CURLVERSION_ATLEAST(7,16,2)
00267       *key = CURLOPT_CONNECTTIMEOUT_MS;
00268       *ot = OT_INTEGER_MS;
00269 #else
00270       *key = CURLOPT_CONNECTTIMEOUT;
00271       *ot = OT_INTEGER;
00272 #endif
00273    } else if (!strcasecmp(name, "ftptext")) {
00274       *key = CURLOPT_TRANSFERTEXT;
00275       *ot = OT_BOOLEAN;
00276    } else if (!strcasecmp(name, "ssl_verifypeer")) {
00277       *key = CURLOPT_SSL_VERIFYPEER;
00278       *ot = OT_BOOLEAN;
00279    } else if (!strcasecmp(name, "hashcompat")) {
00280       *key = CURLOPT_SPECIAL_HASHCOMPAT;
00281       *ot = OT_ENUM;
00282    } else {
00283       return -1;
00284    }
00285    return 0;
00286 }
00287 
00288 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
00289 {
00290    struct ast_datastore *store;
00291    struct global_curl_info *list;
00292    struct curl_settings *cur, *new = NULL;
00293    CURLoption key;
00294    enum optiontype ot;
00295 
00296    if (chan) {
00297       if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00298          /* Create a new datastore */
00299          if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
00300             ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
00301             return -1;
00302          }
00303 
00304          if (!(list = ast_calloc(1, sizeof(*list)))) {
00305             ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
00306             ast_datastore_free(store);
00307             return -1;
00308          }
00309 
00310          store->data = list;
00311          AST_LIST_HEAD_INIT(list);
00312          ast_channel_datastore_add(chan, store);
00313       } else {
00314          list = store->data;
00315       }
00316    } else {
00317       /* Populate the global structure */
00318       list = &global_curl_info;
00319    }
00320 
00321    if (!parse_curlopt_key(name, &key, &ot)) {
00322       if (ot == OT_BOOLEAN) {
00323          if ((new = ast_calloc(1, sizeof(*new)))) {
00324             new->value = (void *)((long) ast_true(value));
00325          }
00326       } else if (ot == OT_INTEGER) {
00327          long tmp = atol(value);
00328          if ((new = ast_calloc(1, sizeof(*new)))) {
00329             new->value = (void *)tmp;
00330          }
00331       } else if (ot == OT_INTEGER_MS) {
00332          long tmp = atof(value) * 1000.0;
00333          if ((new = ast_calloc(1, sizeof(*new)))) {
00334             new->value = (void *)tmp;
00335          }
00336       } else if (ot == OT_STRING) {
00337          if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
00338             new->value = (char *)new + sizeof(*new);
00339             strcpy(new->value, value);
00340          }
00341       } else if (ot == OT_ENUM) {
00342          if (key == CURLOPT_PROXYTYPE) {
00343             long ptype =
00344 #if CURLVERSION_ATLEAST(7,10,0)
00345                CURLPROXY_HTTP;
00346 #else
00347                CURLPROXY_SOCKS5;
00348 #endif
00349             if (0) {
00350 #if CURLVERSION_ATLEAST(7,15,2)
00351             } else if (!strcasecmp(value, "socks4")) {
00352                ptype = CURLPROXY_SOCKS4;
00353 #endif
00354 #if CURLVERSION_ATLEAST(7,18,0)
00355             } else if (!strcasecmp(value, "socks4a")) {
00356                ptype = CURLPROXY_SOCKS4A;
00357 #endif
00358 #if CURLVERSION_ATLEAST(7,18,0)
00359             } else if (!strcasecmp(value, "socks5")) {
00360                ptype = CURLPROXY_SOCKS5;
00361 #endif
00362 #if CURLVERSION_ATLEAST(7,18,0)
00363             } else if (!strncasecmp(value, "socks5", 6)) {
00364                ptype = CURLPROXY_SOCKS5_HOSTNAME;
00365 #endif
00366             }
00367 
00368             if ((new = ast_calloc(1, sizeof(*new)))) {
00369                new->value = (void *)ptype;
00370             }
00371          } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
00372             if ((new = ast_calloc(1, sizeof(*new)))) {
00373                new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
00374             }
00375          } else {
00376             /* Highly unlikely */
00377             goto yuck;
00378          }
00379       }
00380 
00381       /* Memory allocation error */
00382       if (!new) {
00383          return -1;
00384       }
00385 
00386       new->key = key;
00387    } else {
00388 yuck:
00389       ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
00390       return -1;
00391    }
00392 
00393    /* Remove any existing entry */
00394    AST_LIST_LOCK(list);
00395    AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
00396       if (cur->key == new->key) {
00397          AST_LIST_REMOVE_CURRENT(list);
00398          ast_free(cur);
00399          break;
00400       }
00401    }
00402    AST_LIST_TRAVERSE_SAFE_END
00403 
00404    /* Insert new entry */
00405    ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
00406    AST_LIST_INSERT_TAIL(list, new, list);
00407    AST_LIST_UNLOCK(list);
00408 
00409    return 0;
00410 }
00411 
00412 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
00413 {
00414    struct ast_datastore *store;
00415    struct global_curl_info *list[2] = { &global_curl_info, NULL };
00416    struct curl_settings *cur = NULL;
00417    CURLoption key;
00418    enum optiontype ot;
00419    int i;
00420 
00421    if (parse_curlopt_key(data, &key, &ot)) {
00422       ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
00423       return -1;
00424    }
00425 
00426    if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00427       list[0] = store->data;
00428       list[1] = &global_curl_info;
00429    }
00430 
00431    for (i = 0; i < 2; i++) {
00432       if (!list[i]) {
00433          break;
00434       }
00435       AST_LIST_LOCK(list[i]);
00436       AST_LIST_TRAVERSE(list[i], cur, list) {
00437          if (cur->key == key) {
00438             if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
00439                if (buf) {
00440                   snprintf(buf, len, "%ld", (long) cur->value);
00441                } else {
00442                   ast_str_set(bufstr, len, "%ld", (long) cur->value);
00443                }
00444             } else if (ot == OT_INTEGER_MS) {
00445                if ((long) cur->value % 1000 == 0) {
00446                   if (buf) {
00447                      snprintf(buf, len, "%ld", (long)cur->value / 1000);
00448                   } else {
00449                      ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
00450                   }
00451                } else {
00452                   if (buf) {
00453                      snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00454                   } else {
00455                      ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
00456                   }
00457                }
00458             } else if (ot == OT_STRING) {
00459                ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
00460                if (buf) {
00461                   ast_copy_string(buf, cur->value, len);
00462                } else {
00463                   ast_str_set(bufstr, 0, "%s", (char *) cur->value);
00464                }
00465             } else if (key == CURLOPT_PROXYTYPE) {
00466                const char *strval = "unknown";
00467                if (0) {
00468 #if CURLVERSION_ATLEAST(7,15,2)
00469                } else if ((long)cur->value == CURLPROXY_SOCKS4) {
00470                   strval = "socks4";
00471 #endif
00472 #if CURLVERSION_ATLEAST(7,18,0)
00473                } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
00474                   strval = "socks4a";
00475 #endif
00476                } else if ((long)cur->value == CURLPROXY_SOCKS5) {
00477                   strval = "socks5";
00478 #if CURLVERSION_ATLEAST(7,18,0)
00479                } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
00480                   strval = "socks5hostname";
00481 #endif
00482 #if CURLVERSION_ATLEAST(7,10,0)
00483                } else if ((long)cur->value == CURLPROXY_HTTP) {
00484                   strval = "http";
00485 #endif
00486                }
00487                if (buf) {
00488                   ast_copy_string(buf, strval, len);
00489                } else {
00490                   ast_str_set(bufstr, 0, "%s", strval);
00491                }
00492             } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
00493                const char *strval = "unknown";
00494                if ((long) cur->value == HASHCOMPAT_LEGACY) {
00495                   strval = "legacy";
00496                } else if ((long) cur->value == HASHCOMPAT_YES) {
00497                   strval = "yes";
00498                } else if ((long) cur->value == HASHCOMPAT_NO) {
00499                   strval = "no";
00500                }
00501                if (buf) {
00502                   ast_copy_string(buf, strval, len);
00503                } else {
00504                   ast_str_set(bufstr, 0, "%s", strval);
00505                }
00506             }
00507             break;
00508          }
00509       }
00510       AST_LIST_UNLOCK(list[i]);
00511       if (cur) {
00512          break;
00513       }
00514    }
00515 
00516    return cur ? 0 : -1;
00517 }
00518 
00519 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00520 {
00521    return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
00522 }
00523 
00524 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
00525 {
00526    return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
00527 }
00528 
00529 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
00530 {
00531    register int realsize = size * nmemb;
00532    struct ast_str **pstr = (struct ast_str **)data;
00533 
00534    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));
00535 
00536    ast_str_append_substr(pstr, 0, ptr, realsize);
00537 
00538    ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
00539 
00540    return realsize;
00541 }
00542 
00543 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
00544 
00545 static int curl_instance_init(void *data)
00546 {
00547    CURL **curl = data;
00548 
00549    if (!(*curl = curl_easy_init()))
00550       return -1;
00551 
00552    curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
00553    curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
00554    curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
00555    curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
00556 
00557    return 0;
00558 }
00559 
00560 static void curl_instance_cleanup(void *data)
00561 {
00562    CURL **curl = data;
00563 
00564    curl_easy_cleanup(*curl);
00565 
00566    ast_free(data);
00567 }
00568 
00569 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
00570 AST_THREADSTORAGE(thread_escapebuf);
00571 
00572 /*!
00573  * \brief Check for potential HTTP injection risk.
00574  *
00575  * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
00576  * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
00577  * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
00578  * requests rather than as a malformed URL.
00579  *
00580  * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
00581  * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
00582  * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
00583  *
00584  * \param url The URL to check for vulnerability
00585  * \retval 0 The URL is not vulnerable
00586  * \retval 1 The URL is vulnerable.
00587  */
00588 static int url_is_vulnerable(const char *url)
00589 {
00590    if (strpbrk(url, "\r\n")) {
00591       return 1;
00592    }
00593 
00594    return 0;
00595 }
00596 
00597 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
00598 {
00599    struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
00600    struct ast_str *str = ast_str_create(16);
00601    int ret = -1;
00602    AST_DECLARE_APP_ARGS(args,
00603       AST_APP_ARG(url);
00604       AST_APP_ARG(postdata);
00605    );
00606    CURL **curl;
00607    struct curl_settings *cur;
00608    struct ast_datastore *store = NULL;
00609    int hashcompat = 0;
00610    AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
00611    char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
00612 
00613    if (buf) {
00614       *buf = '\0';
00615    }
00616 
00617    if (!str) {
00618       return -1;
00619    }
00620 
00621    if (!escapebuf) {
00622       ast_free(str);
00623       return -1;
00624    }
00625 
00626    if (ast_strlen_zero(info)) {
00627       ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
00628       ast_free(str);
00629       return -1;
00630    }
00631 
00632    AST_STANDARD_APP_ARGS(args, info);
00633 
00634    if (url_is_vulnerable(args.url)) {
00635       ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args.url);
00636       return -1;
00637    }
00638 
00639    if (chan) {
00640       ast_autoservice_start(chan);
00641    }
00642 
00643    if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
00644       ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
00645       ast_free(str);
00646       return -1;
00647    }
00648 
00649    AST_LIST_LOCK(&global_curl_info);
00650    AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
00651       if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00652          hashcompat = (long) cur->value;
00653       } else {
00654          curl_easy_setopt(*curl, cur->key, cur->value);
00655       }
00656    }
00657    AST_LIST_UNLOCK(&global_curl_info);
00658 
00659    if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
00660       list = store->data;
00661       AST_LIST_LOCK(list);
00662       AST_LIST_TRAVERSE(list, cur, list) {
00663          if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
00664             hashcompat = (long) cur->value;
00665          } else {
00666             curl_easy_setopt(*curl, cur->key, cur->value);
00667          }
00668       }
00669    }
00670 
00671    curl_easy_setopt(*curl, CURLOPT_URL, args.url);
00672    curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
00673 
00674    if (args.postdata) {
00675       curl_easy_setopt(*curl, CURLOPT_POST, 1);
00676       curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
00677    }
00678 
00679    /* Temporarily assign a buffer for curl to write errors to. */
00680    curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
00681    curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
00682 
00683    if (curl_easy_perform(*curl) != 0) {
00684       ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args.url);
00685    }
00686 
00687    /* Reset buffer to NULL so curl doesn't try to write to it when the
00688     * buffer is deallocated. Documentation is vague about allowing NULL
00689     * here, but the source allows it. See: "typecheck: allow NULL to unset
00690     * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
00691    curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
00692 
00693    if (store) {
00694       AST_LIST_UNLOCK(list);
00695    }
00696 
00697    if (args.postdata) {
00698       curl_easy_setopt(*curl, CURLOPT_POST, 0);
00699    }
00700 
00701    if (ast_str_strlen(str)) {
00702       ast_str_trim_blanks(str);
00703 
00704       ast_debug(3, "str='%s'\n", ast_str_buffer(str));
00705       if (hashcompat) {
00706          char *remainder = ast_str_buffer(str);
00707          char *piece;
00708          struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
00709          struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
00710          int rowcount = 0;
00711          while (fields && values && (piece = strsep(&remainder, "&"))) {
00712             char *name = strsep(&piece, "=");
00713             struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
00714             if (piece) {
00715                ast_uri_decode(piece, mode);
00716             }
00717             ast_uri_decode(name, mode);
00718             ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
00719             ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
00720             rowcount++;
00721          }
00722          pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
00723          if (buf) {
00724             ast_copy_string(buf, ast_str_buffer(values), len);
00725          } else {
00726             ast_str_set(input_str, len, "%s", ast_str_buffer(values));
00727          }
00728          ast_free(fields);
00729          ast_free(values);
00730       } else {
00731          if (buf) {
00732             ast_copy_string(buf, ast_str_buffer(str), len);
00733          } else {
00734             ast_str_set(input_str, len, "%s", ast_str_buffer(str));
00735          }
00736       }
00737       ret = 0;
00738    }
00739    ast_free(str);
00740 
00741    if (chan)
00742       ast_autoservice_stop(chan);
00743 
00744    return ret;
00745 }
00746 
00747 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
00748 {
00749    return acf_curl_helper(chan, cmd, info, buf, NULL, len);
00750 }
00751 
00752 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
00753 {
00754    return acf_curl_helper(chan, cmd, info, NULL, buf, len);
00755 }
00756 
00757 static struct ast_custom_function acf_curl = {
00758    .name = "CURL",
00759    .synopsis = "Retrieves the contents of a URL",
00760    .syntax = "CURL(url[,post-data])",
00761    .desc =
00762    "  url       - URL to retrieve\n"
00763    "  post-data - Optional data to send as a POST (GET is default action)\n",
00764    .read = acf_curl_exec,
00765    .read2 = acf_curl2_exec,
00766 };
00767 
00768 static struct ast_custom_function acf_curlopt = {
00769    .name = "CURLOPT",
00770    .synopsis = "Set options for use with the CURL() function",
00771    .syntax = "CURLOPT(<option>)",
00772    .desc =
00773 "  cookie         - Send cookie with request [none]\n"
00774 "  conntimeout    - Number of seconds to wait for connection\n"
00775 "  dnstimeout     - Number of seconds to wait for DNS response\n"
00776 "  ftptext        - For FTP, force a text transfer (boolean)\n"
00777 "  ftptimeout     - For FTP, the server response timeout\n"
00778 "  header         - Retrieve header information (boolean)\n"
00779 "  httptimeout    - Number of seconds to wait for HTTP response\n"
00780 "  maxredirs      - Maximum number of redirects to follow\n"
00781 "  proxy          - Hostname or IP to use as a proxy\n"
00782 "  proxytype      - http, socks4, or socks5\n"
00783 "  proxyport      - port number of the proxy\n"
00784 "  proxyuserpwd   - A <user>:<pass> to use for authentication\n"
00785 "  referer        - Referer URL to use for the request\n"
00786 "  useragent      - UserAgent string to use\n"
00787 "  userpwd        - A <user>:<pass> to use for authentication\n"
00788 "  ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
00789 "  hashcompat     - Result data will be compatible for use with HASH()\n"
00790 "                 - if value is \"legacy\", will translate '+' to ' '\n"
00791 "",
00792    .read = acf_curlopt_read,
00793    .read2 = acf_curlopt_read2,
00794    .write = acf_curlopt_write,
00795 };
00796 
00797 AST_TEST_DEFINE(vulnerable_url)
00798 {
00799    const char *bad_urls [] = {
00800       "http://example.com\r\nDELETE http://example.com/everything",
00801       "http://example.com\rDELETE http://example.com/everything",
00802       "http://example.com\nDELETE http://example.com/everything",
00803       "\r\nhttp://example.com",
00804       "\rhttp://example.com",
00805       "\nhttp://example.com",
00806       "http://example.com\r\n",
00807       "http://example.com\r",
00808       "http://example.com\n",
00809    };
00810    const char *good_urls [] = {
00811       "http://example.com",
00812       "http://example.com/%5Cr%5Cn",
00813    };
00814    int i;
00815    enum ast_test_result_state res = AST_TEST_PASS;
00816 
00817    switch (cmd) {
00818    case TEST_INIT:
00819       info->name = "vulnerable_url";
00820       info->category = "/funcs/func_curl/";
00821       info->summary = "cURL vulnerable URL test";
00822       info->description =
00823          "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
00824    case TEST_EXECUTE:
00825       break;
00826    }
00827 
00828    for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
00829       if (!url_is_vulnerable(bad_urls[i])) {
00830          ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
00831          res = AST_TEST_FAIL;
00832       }
00833    }
00834 
00835    for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
00836       if (url_is_vulnerable(good_urls[i])) {
00837          ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
00838          res = AST_TEST_FAIL;
00839       }
00840    }
00841 
00842    return res;
00843 }
00844 
00845 static int unload_module(void)
00846 {
00847    int res;
00848 
00849    res = ast_custom_function_unregister(&acf_curl);
00850    res |= ast_custom_function_unregister(&acf_curlopt);
00851 
00852    AST_TEST_UNREGISTER(vulnerable_url);
00853 
00854    return res;
00855 }
00856 
00857 static int load_module(void)
00858 {
00859    int res;
00860 
00861    if (!ast_module_check("res_curl.so")) {
00862       if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
00863          ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
00864          return AST_MODULE_LOAD_DECLINE;
00865       }
00866    }
00867 
00868    res = ast_custom_function_register(&acf_curl);
00869    res |= ast_custom_function_register(&acf_curlopt);
00870 
00871    AST_TEST_REGISTER(vulnerable_url);
00872 
00873    return res;
00874 }
00875 
00876 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
00877       .support_level = AST_MODULE_SUPPORT_CORE,
00878       .load = load_module,
00879       .unload = unload_module,
00880       .load_pri = AST_MODPRI_REALTIME_DEPEND2,
00881    );
00882 

Generated on Thu Apr 16 06:27:35 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6