Asterisk - The Open Source Telephony Project  21.4.1
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 <curl-20050919@the-tilghman.com>
7  * and Brian Wilkins <bwilkins@cfl.rr.com> (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 <curl-20050919@the-tilghman.com>
25  *
26  * \note Brian Wilkins <bwilkins@cfl.rr.com> (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  }
239  AST_LIST_HEAD_DESTROY(list);
240  ast_free(list);
241 }
242 
243 enum optiontype {
244  OT_BOOLEAN,
245  OT_INTEGER,
246  OT_INTEGER_MS,
247  OT_STRING,
248  OT_ENUM,
249 };
250 
251 enum hashcompat {
252  HASHCOMPAT_NO = 0,
253  HASHCOMPAT_YES,
254  HASHCOMPAT_LEGACY,
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")) {
324  *key = CURLOPT_SPECIAL_HASHCOMPAT;
325  *ot = OT_ENUM;
326  } else if (!strcasecmp(name, "failurecodes")) {
327  *key = CURLOPT_SPECIAL_FAILURE_CODE;
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  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;
361  AST_LIST_HEAD_INIT(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 */
369  list = &global_curl_info;
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 {
439 yuck:
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 */
445  AST_LIST_LOCK(list);
446  if (new->key != CURLOPT_HTTPHEADER) {
447  AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
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);
459  AST_LIST_INSERT_TAIL(list, new, list);
460  AST_LIST_UNLOCK(list);
461 
462  return 0;
463 }
464 
465 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
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);
483  store = ast_channel_datastore_find(chan, &curl_info, NULL);
484  ast_channel_unlock(chan);
485 
486  if (store) {
487  list[0] = store->data;
488  list[1] = &global_curl_info;
489  }
490  }
491 
492  for (i = 0; i < 2; i++) {
493  if (!list[i]) {
494  break;
495  }
496  AST_LIST_LOCK(list[i]);
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) {
522  ast_copy_string(buf, cur->value, len);
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  }
571  AST_LIST_UNLOCK(list[i]);
572  if (cur) {
573  break;
574  }
575  }
576 
577  return cur ? 0 : -1;
578 }
579 
580 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
581 {
582  return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
583 }
584 
585 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
586 {
587  return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
588 }
589 
590 /*! \brief Callback data passed to \ref WriteMemoryCallback */
592  /*! \brief If a string is being built, the string buffer */
593  struct ast_str *str;
594  /*! \brief The max size of \ref str */
595  ssize_t len;
596  /*! \brief If a file is being retrieved, the file to write to */
597  FILE *out_file;
598 };
599 
600 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
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 }
614 
615 static int curl_instance_init(void *data)
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 }
629 
630 static void curl_instance_cleanup(void *data)
631 {
632  CURL **curl = data;
633 
634  curl_easy_cleanup(*curl);
635 
636  ast_free(data);
637 }
638 
639 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
640 AST_THREADSTORAGE(thread_escapebuf);
641 
642 /*!
643  * \brief Check for potential HTTP injection risk.
644  *
645  * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
646  * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
647  * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
648  * requests rather than as a malformed URL.
649  *
650  * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
651  * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
652  * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
653  *
654  * \param url The URL to check for vulnerability
655  * \retval 0 The URL is not vulnerable
656  * \retval 1 The URL is vulnerable.
657  */
658 static int url_is_vulnerable(const char *url)
659 {
660  if (strpbrk(url, "\r\n")) {
661  return 1;
662  }
663 
664  return 0;
665 }
666 
667 struct curl_args {
668  const char *url;
669  const char *postdata;
670  struct curl_write_callback_data cb_data;
671 };
672 
673 static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args)
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) {
704  ast_autoservice_start(chan);
705  }
706 
707  AST_VECTOR_INIT(&hasfailurecode, 0); /*Initialize vector*/
709  AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
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);
727  store = ast_channel_datastore_find(chan, &curl_info, NULL);
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, "=");
811  struct ast_flags mode = (hashcompat == HASHCOMPAT_LEGACY ? ast_uri_http_legacy : ast_uri_http);
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);
823  ast_free(values);
824  }
825  }
826 
827  if (chan) {
828  ast_autoservice_stop(chan);
829  }
830 
831  return ret;
832 }
833 
834 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
835 {
836  struct curl_args curl_params = { 0, };
837  int res;
838 
840  AST_APP_ARG(url);
841  AST_APP_ARG(postdata);
842  );
843 
844  AST_STANDARD_APP_ARGS(args, info);
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 }
864 
865 static int acf_curl_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
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 }
902 
903 static struct ast_custom_function acf_curl = {
904  .name = "CURL",
905  .read2 = acf_curl_exec,
906  .write = acf_curl_write,
907 };
908 
909 static struct ast_custom_function acf_curlopt = {
910  .name = "CURLOPT",
911  .read = acf_curlopt_read,
912  .read2 = acf_curlopt_read2,
913  .write = acf_curlopt_write,
914 };
915 
916 #ifdef TEST_FRAMEWORK
917 AST_TEST_DEFINE(vulnerable_url)
918 {
919  const char *bad_urls [] = {
920  "http://example.com\r\nDELETE http://example.com/everything",
921  "http://example.com\rDELETE http://example.com/everything",
922  "http://example.com\nDELETE http://example.com/everything",
923  "\r\nhttp://example.com",
924  "\rhttp://example.com",
925  "\nhttp://example.com",
926  "http://example.com\r\n",
927  "http://example.com\r",
928  "http://example.com\n",
929  };
930  const char *good_urls [] = {
931  "http://example.com",
932  "http://example.com/%5Cr%5Cn",
933  };
934  int i;
935  enum ast_test_result_state res = AST_TEST_PASS;
936 
937  switch (cmd) {
938  case TEST_INIT:
939  info->name = "vulnerable_url";
940  info->category = "/funcs/func_curl/";
941  info->summary = "cURL vulnerable URL test";
942  info->description =
943  "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
944  case TEST_EXECUTE:
945  break;
946  }
947 
948  for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
949  if (!url_is_vulnerable(bad_urls[i])) {
950  ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
951  res = AST_TEST_FAIL;
952  }
953  }
954 
955  for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
956  if (url_is_vulnerable(good_urls[i])) {
957  ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
958  res = AST_TEST_FAIL;
959  }
960  }
961 
962  return res;
963 }
964 #endif
965 
966 static int unload_module(void)
967 {
968  int res;
969 
970  res = ast_custom_function_unregister(&acf_curl);
971  res |= ast_custom_function_unregister(&acf_curlopt);
972 
973  AST_TEST_UNREGISTER(vulnerable_url);
974 
975  return res;
976 }
977 
978 static int load_module(void)
979 {
980  int res;
981 
982  res = ast_custom_function_register_escalating(&acf_curl, AST_CFE_WRITE);
983  res |= ast_custom_function_register(&acf_curlopt);
984 
985  AST_TEST_REGISTER(vulnerable_url);
986 
987  return res;
988 }
989 
990 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Load external URL",
991  .support_level = AST_MODULE_SUPPORT_CORE,
992  .load = load_module,
993  .unload = unload_module,
994  .load_pri = AST_MODPRI_REALTIME_DEPEND2,
995  .requires = "res_curl",
996 );
void ast_uri_decode(char *s, struct ast_flags spec)
Decode URI, URN, URL (overwrite string)
Definition: utils.c:762
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:86
Main Channel structure associated with a channel.
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
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:173
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
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
Callback data passed to WriteMemoryCallback.
Definition: func_curl.c:591
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition: vector.h:256
Test Framework API.
Structure for a data store type.
Definition: datastore.h:31
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
Structure for a data store object.
Definition: datastore.h:64
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
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
Definitions to aid in the use of thread local storage.
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
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_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:653
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
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
Definition: func_curl.c:658
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1567
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
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
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:557
FILE * out_file
If a file is being retrieved, the file to write to.
Definition: func_curl.c:597
#define ast_debug(level,...)
Log a DEBUG message.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
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:291
ssize_t len
The max size of str.
Definition: func_curl.c:595
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
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: utils.c:2199
Support for dynamic strings.
Definition: strings.h:623
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:626
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Structure used to handle boolean flags.
Definition: utils.h:199
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:1069
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
void * data
Definition: datastore.h:66
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680
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:425
#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
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
struct ast_str * str
If a string is being built, the string buffer.
Definition: func_curl.c:593
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
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
Definition: strings.h:719
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
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:2385
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
Integer vector definition.
Definition: vector.h:52
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
#define AST_APP_ARG(name)
Define an application argument.