Asterisk - The Open Source Telephony Project  18.5.0
func_curl.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2004 - 2006, Tilghman Lesher
5  *
6  * Tilghman Lesher <[email protected]>
7  * and Brian Wilkins <[email protected]> (Added POST option)
8  *
9  * app_curl.c is distributed with no restrictions on usage or
10  * redistribution.
11  *
12  * See http://www.asterisk.org for more information about
13  * the Asterisk project. Please do not directly contact
14  * any of the maintainers of this project for assistance;
15  * the project provides a web site, mailing lists and IRC
16  * channels for your use.
17  *
18  */
19 
20 /*! \file
21  *
22  * \brief Curl - Load a URL
23  *
24  * \author Tilghman Lesher <[email protected]>
25  *
26  * \note Brian Wilkins <[email protected]> (Added POST option)
27  *
28  * \extref Depends on the CURL library - http://curl.haxx.se/
29  *
30  * \ingroup functions
31  */
32 
33 /*** MODULEINFO
34  <depend>res_curl</depend>
35  <depend>curl</depend>
36  <support_level>core</support_level>
37  ***/
38 
39 #include "asterisk.h"
40 
41 #include <curl/curl.h>
42 
43 #include "asterisk/lock.h"
44 #include "asterisk/file.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/cli.h"
48 #include "asterisk/module.h"
49 #include "asterisk/app.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/threadstorage.h"
52 #include "asterisk/test.h"
53 
54 /*** DOCUMENTATION
55  <function name="CURL" language="en_US">
56  <synopsis>
57  Retrieve content from a remote web or ftp server
58  </synopsis>
59  <syntax>
60  <parameter name="url" required="true">
61  <para>The full URL for the resource to retrieve.</para>
62  </parameter>
63  <parameter name="post-data">
64  <para><emphasis>Read Only</emphasis></para>
65  <para>If specified, an <literal>HTTP POST</literal> will be
66  performed with the content of
67  <replaceable>post-data</replaceable>, instead of an
68  <literal>HTTP GET</literal> (default).</para>
69  </parameter>
70  </syntax>
71  <description>
72  <para>When this function is read, a <literal>HTTP GET</literal>
73  (by default) will be used to retrieve the contents of the provided
74  <replaceable>url</replaceable>. The contents are returned as the
75  result of the function.</para>
76  <example title="Displaying contents of a page" language="text">
77  exten => s,1,Verbose(0, ${CURL(http://localhost:8088/static/astman.css)})
78  </example>
79  <para>When this function is written to, a <literal>HTTP GET</literal>
80  will be used to retrieve the contents of the provided
81  <replaceable>url</replaceable>. The value written to the function
82  specifies the destination file of the cURL'd resource.</para>
83  <example title="Retrieving a file" language="text">
84  exten => s,1,Set(CURL(http://localhost:8088/static/astman.css)=/var/spool/asterisk/tmp/astman.css))
85  </example>
86  <note>
87  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
88  is set to <literal>no</literal>, this function can only be written to from the
89  dialplan, and not directly from external protocols. Read operations are
90  unaffected.</para>
91  </note>
92  </description>
93  <see-also>
94  <ref type="function">CURLOPT</ref>
95  </see-also>
96  </function>
97  <function name="CURLOPT" language="en_US">
98  <synopsis>
99  Sets various options for future invocations of CURL.
100  </synopsis>
101  <syntax>
102  <parameter name="key" required="yes">
103  <enumlist>
104  <enum name="cookie">
105  <para>A cookie to send with the request. Multiple
106  cookies are supported.</para>
107  </enum>
108  <enum name="conntimeout">
109  <para>Number of seconds to wait for a connection to succeed</para>
110  </enum>
111  <enum name="dnstimeout">
112  <para>Number of seconds to wait for DNS to be resolved</para>
113  </enum>
114  <enum name="followlocation">
115  <para>Whether or not to follow HTTP 3xx redirects (boolean)</para>
116  </enum>
117  <enum name="ftptext">
118  <para>For FTP URIs, force a text transfer (boolean)</para>
119  </enum>
120  <enum name="ftptimeout">
121  <para>For FTP URIs, number of seconds to wait for a
122  server response</para>
123  </enum>
124  <enum name="header">
125  <para>Include header information in the result
126  (boolean)</para>
127  </enum>
128  <enum name="httpheader">
129  <para>Add HTTP header. Multiple calls add multiple headers.
130  Setting of any header will remove the default
131  "Content-Type application/x-www-form-urlencoded"</para>
132  </enum>
133  <enum name="httptimeout">
134  <para>For HTTP(S) URIs, number of seconds to wait for a
135  server response</para>
136  </enum>
137  <enum name="maxredirs">
138  <para>Maximum number of redirects to follow. The default is -1,
139  which allows for unlimited redirects. This only makes sense when
140  followlocation is also set.</para>
141  </enum>
142  <enum name="proxy">
143  <para>Hostname or IP address to use as a proxy server</para>
144  </enum>
145  <enum name="proxytype">
146  <para>Type of <literal>proxy</literal></para>
147  <enumlist>
148  <enum name="http" />
149  <enum name="socks4" />
150  <enum name="socks5" />
151  </enumlist>
152  </enum>
153  <enum name="proxyport">
154  <para>Port number of the <literal>proxy</literal></para>
155  </enum>
156  <enum name="proxyuserpwd">
157  <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
158  combination to use for authenticating requests through a
159  <literal>proxy</literal></para>
160  </enum>
161  <enum name="referer">
162  <para>Referer URL to use for the request</para>
163  </enum>
164  <enum name="useragent">
165  <para>UserAgent string to use for the request</para>
166  </enum>
167  <enum name="userpwd">
168  <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
169  to use for authentication when the server response to
170  an initial request indicates a 401 status code.</para>
171  </enum>
172  <enum name="ssl_verifypeer">
173  <para>Whether to verify the server certificate against
174  a list of known root certificate authorities (boolean).</para>
175  </enum>
176  <enum name="hashcompat">
177  <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
178  format, reformat the response such that it can be used
179  by the <literal>HASH</literal> function.</para>
180  <enumlist>
181  <enum name="yes" />
182  <enum name="no" />
183  <enum name="legacy">
184  <para>Also translate <literal>+</literal> to the
185  space character, in violation of current RFC
186  standards.</para>
187  </enum>
188  </enumlist>
189  </enum>
190  <enum name="failurecodes">
191  <para>A comma separated list of HTTP response codes to be treated as errors</para>
192  </enum>
193  </enumlist>
194  </parameter>
195  </syntax>
196  <description>
197  <para>Options may be set globally or per channel. Per-channel
198  settings will override global settings. Only HTTP headers are added instead of overriding</para>
199  </description>
200  <see-also>
201  <ref type="function">CURL</ref>
202  <ref type="function">HASH</ref>
203  </see-also>
204  </function>
205  ***/
206 
207 #define CURLVERSION_ATLEAST(a,b,c) \
208  ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
209 
210 #define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500)
211 
212 #define CURLOPT_SPECIAL_FAILURE_CODE 999
213 
214 static void curlds_free(void *data);
215 
216 static const struct ast_datastore_info curl_info = {
217  .type = "CURL",
218  .destroy = curlds_free,
219 };
220 
223  CURLoption key;
224  void *value;
225 };
226 
228 
229 static void curlds_free(void *data)
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 }
242 
249 };
250 
255 };
256 
257 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
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 }
334 
335 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
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  if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
345  /* Create a new datastore */
346  if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
347  ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
348  return -1;
349  }
350 
351  if (!(list = ast_calloc(1, sizeof(*list)))) {
352  ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
353  ast_datastore_free(store);
354  return -1;
355  }
356 
357  store->data = list;
358  AST_LIST_HEAD_INIT(list);
359  ast_channel_datastore_add(chan, store);
360  } else {
361  list = store->data;
362  }
363  } else {
364  /* Populate the global structure */
365  list = &global_curl_info;
366  }
367 
368  if (!parse_curlopt_key(name, &key, &ot)) {
369  if (ot == OT_BOOLEAN) {
370  if ((new = ast_calloc(1, sizeof(*new)))) {
371  new->value = (void *)((long) ast_true(value));
372  }
373  } else if (ot == OT_INTEGER) {
374  long tmp = atol(value);
375  if ((new = ast_calloc(1, sizeof(*new)))) {
376  new->value = (void *)tmp;
377  }
378  } else if (ot == OT_INTEGER_MS) {
379  long tmp = atof(value) * 1000.0;
380  if ((new = ast_calloc(1, sizeof(*new)))) {
381  new->value = (void *)tmp;
382  }
383  } else if (ot == OT_STRING) {
384  if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
385  new->value = (char *)new + sizeof(*new);
386  strcpy(new->value, value);
387  }
388  } else if (ot == OT_ENUM) {
389  if (key == CURLOPT_PROXYTYPE) {
390  long ptype =
391 #if CURLVERSION_ATLEAST(7,10,0)
392  CURLPROXY_HTTP;
393 #else
394  CURLPROXY_SOCKS5;
395 #endif
396  if (0) {
397 #if CURLVERSION_ATLEAST(7,15,2)
398  } else if (!strcasecmp(value, "socks4")) {
399  ptype = CURLPROXY_SOCKS4;
400 #endif
401 #if CURLVERSION_ATLEAST(7,18,0)
402  } else if (!strcasecmp(value, "socks4a")) {
403  ptype = CURLPROXY_SOCKS4A;
404 #endif
405 #if CURLVERSION_ATLEAST(7,18,0)
406  } else if (!strcasecmp(value, "socks5")) {
407  ptype = CURLPROXY_SOCKS5;
408 #endif
409 #if CURLVERSION_ATLEAST(7,18,0)
410  } else if (!strncasecmp(value, "socks5", 6)) {
411  ptype = CURLPROXY_SOCKS5_HOSTNAME;
412 #endif
413  }
414 
415  if ((new = ast_calloc(1, sizeof(*new)))) {
416  new->value = (void *)ptype;
417  }
418  } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
419  if ((new = ast_calloc(1, sizeof(*new)))) {
420  new->value = (void *) (long) (!strcasecmp(value, "legacy") ? HASHCOMPAT_LEGACY : ast_true(value) ? HASHCOMPAT_YES : HASHCOMPAT_NO);
421  }
422  } else {
423  /* Highly unlikely */
424  goto yuck;
425  }
426  }
427 
428  /* Memory allocation error */
429  if (!new) {
430  return -1;
431  }
432 
433  new->key = key;
434  } else {
435 yuck:
436  ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
437  return -1;
438  }
439 
440  /* Remove any existing entry, only http headers are left */
441  AST_LIST_LOCK(list);
442  if (new->key != CURLOPT_HTTPHEADER) {
443  AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
444  if (cur->key == new->key) {
446  ast_free(cur);
447  break;
448  }
449  }
451  }
452 
453  /* Insert new entry */
454  ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
455  AST_LIST_INSERT_TAIL(list, new, list);
456  AST_LIST_UNLOCK(list);
457 
458  return 0;
459 }
460 
461 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
462 {
463  struct ast_datastore *store;
464  struct global_curl_info *list[2] = { &global_curl_info, NULL };
465  struct curl_settings *cur = NULL;
466  CURLoption key;
467  enum optiontype ot;
468  int i;
469 
470  if (parse_curlopt_key(data, &key, &ot)) {
471  ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
472  return -1;
473  }
474 
475  if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
476  list[0] = store->data;
477  list[1] = &global_curl_info;
478  }
479 
480  for (i = 0; i < 2; i++) {
481  if (!list[i]) {
482  break;
483  }
484  AST_LIST_LOCK(list[i]);
485  AST_LIST_TRAVERSE(list[i], cur, list) {
486  if (cur->key == key) {
487  if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
488  if (buf) {
489  snprintf(buf, len, "%ld", (long) cur->value);
490  } else {
491  ast_str_set(bufstr, len, "%ld", (long) cur->value);
492  }
493  } else if (ot == OT_INTEGER_MS) {
494  if ((long) cur->value % 1000 == 0) {
495  if (buf) {
496  snprintf(buf, len, "%ld", (long)cur->value / 1000);
497  } else {
498  ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
499  }
500  } else {
501  if (buf) {
502  snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
503  } else {
504  ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
505  }
506  }
507  } else if (ot == OT_STRING) {
508  ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
509  if (buf) {
510  ast_copy_string(buf, cur->value, len);
511  } else {
512  ast_str_set(bufstr, 0, "%s", (char *) cur->value);
513  }
514  } else if (key == CURLOPT_PROXYTYPE) {
515  const char *strval = "unknown";
516  if (0) {
517 #if CURLVERSION_ATLEAST(7,15,2)
518  } else if ((long)cur->value == CURLPROXY_SOCKS4) {
519  strval = "socks4";
520 #endif
521 #if CURLVERSION_ATLEAST(7,18,0)
522  } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
523  strval = "socks4a";
524 #endif
525  } else if ((long)cur->value == CURLPROXY_SOCKS5) {
526  strval = "socks5";
527 #if CURLVERSION_ATLEAST(7,18,0)
528  } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
529  strval = "socks5hostname";
530 #endif
531 #if CURLVERSION_ATLEAST(7,10,0)
532  } else if ((long)cur->value == CURLPROXY_HTTP) {
533  strval = "http";
534 #endif
535  }
536  if (buf) {
537  ast_copy_string(buf, strval, len);
538  } else {
539  ast_str_set(bufstr, 0, "%s", strval);
540  }
541  } else if (key == CURLOPT_SPECIAL_HASHCOMPAT) {
542  const char *strval = "unknown";
543  if ((long) cur->value == HASHCOMPAT_LEGACY) {
544  strval = "legacy";
545  } else if ((long) cur->value == HASHCOMPAT_YES) {
546  strval = "yes";
547  } else if ((long) cur->value == HASHCOMPAT_NO) {
548  strval = "no";
549  }
550  if (buf) {
551  ast_copy_string(buf, strval, len);
552  } else {
553  ast_str_set(bufstr, 0, "%s", strval);
554  }
555  }
556  break;
557  }
558  }
559  AST_LIST_UNLOCK(list[i]);
560  if (cur) {
561  break;
562  }
563  }
564 
565  return cur ? 0 : -1;
566 }
567 
568 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
569 {
570  return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
571 }
572 
573 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
574 {
575  return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
576 }
577 
578 /*! \brief Callback data passed to \ref WriteMemoryCallback */
580  /*! \brief If a string is being built, the string buffer */
581  struct ast_str *str;
582  /*! \brief The max size of \ref str */
583  ssize_t len;
584  /*! \brief If a file is being retrieved, the file to write to */
585  FILE *out_file;
586 };
587 
588 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
589 {
590  register int realsize = 0;
591  struct curl_write_callback_data *cb_data = data;
592 
593  if (cb_data->str) {
594  realsize = size * nmemb;
595  ast_str_append_substr(&cb_data->str, 0, ptr, realsize);
596  } else if (cb_data->out_file) {
597  realsize = fwrite(ptr, size, nmemb, cb_data->out_file);
598  }
599 
600  return realsize;
601 }
602 
603 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
604 
605 static int curl_instance_init(void *data)
606 {
607  CURL **curl = data;
608 
609  if (!(*curl = curl_easy_init()))
610  return -1;
611 
612  curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
613  curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
614  curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
615  curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
616 
617  return 0;
618 }
619 
620 static void curl_instance_cleanup(void *data)
621 {
622  CURL **curl = data;
623 
624  curl_easy_cleanup(*curl);
625 
626  ast_free(data);
627 }
628 
631 
632 /*!
633  * \brief Check for potential HTTP injection risk.
634  *
635  * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
636  * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
637  * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
638  * requests rather than as a malformed URL.
639  *
640  * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
641  * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
642  * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
643  *
644  * \param url The URL to check for vulnerability
645  * \retval 0 The URL is not vulnerable
646  * \retval 1 The URL is vulnerable.
647  */
648 static int url_is_vulnerable(const char *url)
649 {
650  if (strpbrk(url, "\r\n")) {
651  return 1;
652  }
653 
654  return 0;
655 }
656 
657 struct curl_args {
658  const char *url;
659  const char *postdata;
660  struct curl_write_callback_data cb_data;
661 };
662 
663 static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
664 {
665  struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
666  int ret = 0;
667  long http_code = 0; /* read curl response */
668  size_t i;
669  struct ast_vector_int hasfailurecode = { NULL };
670  char *failurecodestrings,*found;
671  CURL **curl;
672  struct curl_settings *cur;
673  struct curl_slist *headers = NULL;
674  struct ast_datastore *store = NULL;
675  int hashcompat = 0;
677  char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
678 
679  if (!escapebuf) {
680  return -1;
681  }
682 
683  if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
684  ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
685  return -1;
686  }
687 
688  if (url_is_vulnerable(args->url)) {
689  ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args->url);
690  return -1;
691  }
692 
693  if (chan) {
694  ast_autoservice_start(chan);
695  }
696 
697  AST_VECTOR_INIT(&hasfailurecode, 0); /*Initialize vector*/
700  if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
701  hashcompat = (long) cur->value;
702  } else if (cur->key == CURLOPT_HTTPHEADER) {
703  headers = curl_slist_append(headers, (char*) cur->value);
704  } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) {
705  failurecodestrings = (char*) cur->value;
706  while( (found = strsep(&failurecodestrings, ",")) != NULL) {
707  AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
708  }
709  } else {
710  curl_easy_setopt(*curl, cur->key, cur->value);
711  }
712  }
714 
715  if (chan) {
716  ast_channel_lock(chan);
717  store = ast_channel_datastore_find(chan, &curl_info, NULL);
718  ast_channel_unlock(chan);
719  if (store) {
720  list = store->data;
722  AST_LIST_TRAVERSE(list, cur, list) {
723  if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
724  hashcompat = (long) cur->value;
725  } else if (cur->key == CURLOPT_HTTPHEADER) {
726  headers = curl_slist_append(headers, (char*) cur->value);
727  } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) {
728  failurecodestrings = (char*) cur->value;
729  while( (found = strsep(&failurecodestrings, ",")) != NULL) {
730  AST_VECTOR_APPEND(&hasfailurecode, atoi(found));
731  }
732  } else {
733  curl_easy_setopt(*curl, cur->key, cur->value);
734  }
735  }
736  }
737  }
738 
739  curl_easy_setopt(*curl, CURLOPT_URL, args->url);
740  curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &args->cb_data);
741 
742  if (args->postdata) {
743  curl_easy_setopt(*curl, CURLOPT_POST, 1);
744  curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata);
745  }
746 
747  /* Always assign the headers - even when NULL - in case we had
748  * custom headers the last time we used this shared cURL
749  * instance */
750  curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers);
751 
752  /* Temporarily assign a buffer for curl to write errors to. */
753  curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
754  curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
755 
756  if (curl_easy_perform(*curl) != 0) {
757  ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args->url);
758  }
759 
760  /* Reset buffer to NULL so curl doesn't try to write to it when the
761  * buffer is deallocated. Documentation is vague about allowing NULL
762  * here, but the source allows it. See: "typecheck: allow NULL to unset
763  * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
764  curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
765  curl_easy_getinfo (*curl, CURLINFO_RESPONSE_CODE, &http_code);
766 
767  for (i = 0; i < AST_VECTOR_SIZE(&hasfailurecode); ++i) {
768  if (http_code == AST_VECTOR_GET(&hasfailurecode,i)){
769  ast_log(LOG_NOTICE, "%s%sCURL '%s' returned response code (%ld).\n",
770  chan ? ast_channel_name(chan) : "",
771  chan ? ast_channel_name(chan) : ": ",
772  args->url,
773  http_code);
774  ret=-1;
775  break;
776  }
777  }
778  AST_VECTOR_FREE(&hasfailurecode); /* Release the vector*/
779 
780  if (store) {
782  }
783  curl_slist_free_all(headers);
784 
785  if (args->postdata) {
786  curl_easy_setopt(*curl, CURLOPT_POST, 0);
787  }
788 
789  if (args->cb_data.str && ast_str_strlen(args->cb_data.str)) {
791 
792  ast_debug(3, "CURL returned str='%s'\n", ast_str_buffer(args->cb_data.str));
793  if (hashcompat) {
794  char *remainder = ast_str_buffer(args->cb_data.str);
795  char *piece;
796  struct ast_str *fields = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
797  struct ast_str *values = ast_str_create(ast_str_strlen(args->cb_data.str) / 2);
798  int rowcount = 0;
799  while (fields && values && (piece = strsep(&remainder, "&"))) {
800  char *name = strsep(&piece, "=");
801  struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
802  if (piece) {
803  ast_uri_decode(piece, mode);
804  }
805  ast_uri_decode(name, mode);
806  ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
807  ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
808  rowcount++;
809  }
810  pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
811  ast_str_set(&args->cb_data.str, 0, "%s", ast_str_buffer(values));
812  ast_free(fields);
813  ast_free(values);
814  }
815  }
816 
817  if (chan) {
818  ast_autoservice_stop(chan);
819  }
820 
821  return ret;
822 }
823 
824 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
825 {
826  struct curl_args curl_params = { 0, };
827  int res;
828 
830  AST_APP_ARG(url);
832  );
833 
835 
836  if (ast_strlen_zero(info)) {
837  ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
838  return -1;
839  }
840 
841  curl_params.url = args.url;
842  curl_params.postdata = args.postdata;
843  curl_params.cb_data.str = ast_str_create(16);
844  if (!curl_params.cb_data.str) {
845  return -1;
846  }
847 
848  res = acf_curl_helper(chan, &curl_params);
849  ast_str_set(buf, len, "%s", ast_str_buffer(curl_params.cb_data.str));
850  ast_free(curl_params.cb_data.str);
851 
852  return res;
853 }
854 
855 static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
856 {
857  struct curl_args curl_params = { 0, };
858  int res;
859  char *args_value = ast_strdupa(value);
861  AST_APP_ARG(file_path);
862  );
863 
864  AST_STANDARD_APP_ARGS(args, args_value);
865 
866  if (ast_strlen_zero(name)) {
867  ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
868  return -1;
869  }
870 
871  if (ast_strlen_zero(args.file_path)) {
872  ast_log(LOG_WARNING, "CURL requires a file to write\n");
873  return -1;
874  }
875 
876  curl_params.url = name;
877  curl_params.cb_data.out_file = fopen(args.file_path, "w");
878  if (!curl_params.cb_data.out_file) {
879  ast_log(LOG_WARNING, "Failed to open file %s: %s (%d)\n",
880  args.file_path,
881  strerror(errno),
882  errno);
883  return -1;
884  }
885 
886  res = acf_curl_helper(chan, &curl_params);
887 
888  fclose(curl_params.cb_data.out_file);
889 
890  return res;
891 }
892 
893 static struct ast_custom_function acf_curl = {
894  .name = "CURL",
895  .read2 = acf_curl_exec,
896  .write = acf_curl_write,
897 };
898 
900  .name = "CURLOPT",
901  .synopsis = "Set options for use with the CURL() function",
902  .syntax = "CURLOPT(<option>)",
903  .desc =
904 " cookie - Send cookie with request [none]\n"
905 " conntimeout - Number of seconds to wait for connection\n"
906 " dnstimeout - Number of seconds to wait for DNS response\n"
907 " followlocation - Follow HTTP 3xx redirects (boolean)\n"
908 " ftptext - For FTP, force a text transfer (boolean)\n"
909 " ftptimeout - For FTP, the server response timeout\n"
910 " header - Retrieve header information (boolean)\n"
911 " httpheader - Add new custom http header (string)\n"
912 " httptimeout - Number of seconds to wait for HTTP response\n"
913 " maxredirs - Maximum number of redirects to follow\n"
914 " proxy - Hostname or IP to use as a proxy\n"
915 " proxytype - http, socks4, or socks5\n"
916 " proxyport - port number of the proxy\n"
917 " proxyuserpwd - A <user>:<pass> to use for authentication\n"
918 " referer - Referer URL to use for the request\n"
919 " useragent - UserAgent string to use\n"
920 " userpwd - A <user>:<pass> to use for authentication\n"
921 " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
922 " hashcompat - Result data will be compatible for use with HASH()\n"
923 " - if value is \"legacy\", will translate '+' to ' '\n"
924 " failurecodes - A comma separated list of HTTP response codes to be treated as errors\n"
925 "",
926  .read = acf_curlopt_read,
927  .read2 = acf_curlopt_read2,
928  .write = acf_curlopt_write,
929 };
930 
931 #ifdef TEST_FRAMEWORK
932 AST_TEST_DEFINE(vulnerable_url)
933 {
934  const char *bad_urls [] = {
935  "http://example.com\r\nDELETE http://example.com/everything",
936  "http://example.com\rDELETE http://example.com/everything",
937  "http://example.com\nDELETE http://example.com/everything",
938  "\r\nhttp://example.com",
939  "\rhttp://example.com",
940  "\nhttp://example.com",
941  "http://example.com\r\n",
942  "http://example.com\r",
943  "http://example.com\n",
944  };
945  const char *good_urls [] = {
946  "http://example.com",
947  "http://example.com/%5Cr%5Cn",
948  };
949  int i;
951 
952  switch (cmd) {
953  case TEST_INIT:
954  info->name = "vulnerable_url";
955  info->category = "/funcs/func_curl/";
956  info->summary = "cURL vulnerable URL test";
957  info->description =
958  "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
959  case TEST_EXECUTE:
960  break;
961  }
962 
963  for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
964  if (!url_is_vulnerable(bad_urls[i])) {
965  ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
966  res = AST_TEST_FAIL;
967  }
968  }
969 
970  for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
971  if (url_is_vulnerable(good_urls[i])) {
972  ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
973  res = AST_TEST_FAIL;
974  }
975  }
976 
977  return res;
978 }
979 #endif
980 
981 static int unload_module(void)
982 {
983  int res;
984 
985  res = ast_custom_function_unregister(&acf_curl);
986  res |= ast_custom_function_unregister(&acf_curlopt);
987 
988  AST_TEST_UNREGISTER(vulnerable_url);
989 
990  return res;
991 }
992 
993 static int load_module(void)
994 {
995  int res;
996 
998  res |= ast_custom_function_register(&acf_curlopt);
999 
1000  AST_TEST_REGISTER(vulnerable_url);
1001 
1002  return res;
1003 }
1004 
1006  .support_level = AST_MODULE_SUPPORT_CORE,
1007  .load = load_module,
1008  .unload = unload_module,
1009  .load_pri = AST_MODPRI_REALTIME_DEPEND2,
1010  .requires = "res_curl",
1011 );
void ast_uri_decode(char *s, struct ast_flags spec)
Decode URI, URN, URL (overwrite string)
Definition: main/utils.c:616
const char * name
Definition: pbx.h:119
const char * type
Definition: datastore.h:32
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:84
#define ast_channel_lock(chan)
Definition: channel.h:2945
Main Channel structure associated with a channel.
static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:335
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:39
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Definition: linkedlists.h:172
#define ARRAY_LEN(a)
Definition: isdn_lib.c:42
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:200
static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
Definition: func_curl.c:588
static struct ast_custom_function acf_curlopt
Definition: func_curl.c:899
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
#define LOG_WARNING
Definition: logger.h:274
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:139
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:714
Callback data passed to WriteMemoryCallback.
Definition: func_curl.c:579
static struct ast_threadstorage curl_instance
Definition: func_curl.c:629
static int tmp()
Definition: bt_open.c:389
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
Definition: func_curl.c:257
Test Framework API.
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
Structure for a data store type.
Definition: datastore.h:31
static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:573
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:1091
Structure for a data store object.
Definition: datastore.h:68
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:2404
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
const char * args
#define NULL
Definition: resample.c:96
Definitions to aid in the use of thread local storage.
const char * url
Definition: func_curl.c:658
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:614
struct global_curl_info global_curl_info
static const char *const global_useragent
Definition: func_curl.c:603
hashcompat
Definition: func_curl.c:251
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
Utility functions.
#define ast_strlen_zero(foo)
Definition: strings.h:52
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:652
#define CURLOPT_SPECIAL_FAILURE_CODE
Definition: func_curl.c:212
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:1065
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
Definition: func_curl.c:648
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1517
CURLoption key
Definition: func_curl.c:223
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
void * value
Definition: func_curl.c:224
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
General Asterisk PBX channel definitions.
const char * postdata
Definition: func_curl.c:659
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
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:1014
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:556
struct curl_settings::@205 list
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
FILE * out_file
If a file is being retrieved, the file to write to.
Definition: func_curl.c:585
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:832
struct curl_write_callback_data cb_data
Definition: func_curl.c:660
AST_TEST_DEFINE(vulnerable_url)
Definition: func_curl.c:932
Core PBX routines and definitions.
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:266
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
Definition: linkedlists.h:290
static int curl_instance_init(void *data)
Definition: func_curl.c:605
static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:855
ssize_t len
The max size of str.
Definition: func_curl.c:583
#define LOG_ERROR
Definition: logger.h:285
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:730
const struct ast_flags ast_uri_http_legacy
Definition: main/utils.c:574
static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
Definition: func_curl.c:663
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: main/utils.c:1951
static struct ast_custom_function acf_curl
Definition: func_curl.c:893
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:584
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
static int load_module(void)
Definition: func_curl.c:993
static struct ast_threadstorage thread_escapebuf
Definition: func_curl.c:630
def info(msg)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
int errno
static int unload_module(void)
Definition: func_curl.c:981
#define LOG_NOTICE
Definition: logger.h:263
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:409
#define ast_channel_unlock(chan)
Definition: channel.h:2946
static const char name[]
Definition: cdr_mysql.c:74
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:625
#define ast_free(a)
Definition: astmm.h:182
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:824
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
Structure used to handle boolean flags.
Definition: utils.h:199
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
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:461
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 AST_THREADSTORAGE_CUSTOM(a, b, c)
Define a thread storage variable, with custom initialization and cleanup.
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:1021
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:688
void * data
Definition: datastore.h:70
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:682
char * strsep(char **str, const char *delims)
static void curlds_free(void *data)
Definition: func_curl.c:229
Standard Command Line Interface.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:79
const char * ast_channel_name(const struct ast_channel *chan)
const struct ast_flags ast_uri_http
Definition: main/utils.c:573
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:89
static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_curl.c:568
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:528
struct ast_str * str
If a string is being built, the string buffer.
Definition: func_curl.c:581
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:861
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
Definition: strings.h:678
static char url[512]
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
optiontype
Definition: func_curl.c:243
Asterisk module definitions.
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2390
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define CURLOPT_SPECIAL_HASHCOMPAT
Definition: func_curl.c:210
static const struct ast_datastore_info curl_info
Definition: func_curl.c:216
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1508
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:611
ast_test_result_state
Definition: test.h:200
#define ast_str_create(init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:620
#define AST_APP_ARG(name)
Define an application argument.
static void curl_instance_cleanup(void *data)
Definition: func_curl.c:620