Wed Jan 8 2020 09:49:47

Asterisk developer's documentation


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>curl</depend>
35  <support_level>core</support_level>
36  ***/
37 
38 #include "asterisk.h"
39 
40 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 431331 $")
41 
42 #include <curl/curl.h>
43 
44 #include "asterisk/lock.h"
45 #include "asterisk/file.h"
46 #include "asterisk/channel.h"
47 #include "asterisk/pbx.h"
48 #include "asterisk/cli.h"
49 #include "asterisk/module.h"
50 #include "asterisk/app.h"
51 #include "asterisk/utils.h"
52 #include "asterisk/threadstorage.h"
53 #include "asterisk/test.h"
54 
55 /*** DOCUMENTATION
56  <function name="CURL" language="en_US">
57  <synopsis>
58  Retrieve content from a remote web or ftp server
59  </synopsis>
60  <syntax>
61  <parameter name="url" required="true" />
62  <parameter name="post-data">
63  <para>If specified, an <literal>HTTP POST</literal> will be
64  performed with the content of
65  <replaceable>post-data</replaceable>, instead of an
66  <literal>HTTP GET</literal> (default).</para>
67  </parameter>
68  </syntax>
69  <description />
70  <see-also>
71  <ref type="function">CURLOPT</ref>
72  </see-also>
73  </function>
74  <function name="CURLOPT" language="en_US">
75  <synopsis>
76  Sets various options for future invocations of CURL.
77  </synopsis>
78  <syntax>
79  <parameter name="key" required="yes">
80  <enumlist>
81  <enum name="cookie">
82  <para>A cookie to send with the request. Multiple
83  cookies are supported.</para>
84  </enum>
85  <enum name="conntimeout">
86  <para>Number of seconds to wait for a connection to succeed</para>
87  </enum>
88  <enum name="dnstimeout">
89  <para>Number of seconds to wait for DNS to be resolved</para>
90  </enum>
91  <enum name="ftptext">
92  <para>For FTP URIs, force a text transfer (boolean)</para>
93  </enum>
94  <enum name="ftptimeout">
95  <para>For FTP URIs, number of seconds to wait for a
96  server response</para>
97  </enum>
98  <enum name="header">
99  <para>Include header information in the result
100  (boolean)</para>
101  </enum>
102  <enum name="httptimeout">
103  <para>For HTTP(S) URIs, number of seconds to wait for a
104  server response</para>
105  </enum>
106  <enum name="maxredirs">
107  <para>Maximum number of redirects to follow</para>
108  </enum>
109  <enum name="proxy">
110  <para>Hostname or IP address to use as a proxy server</para>
111  </enum>
112  <enum name="proxytype">
113  <para>Type of <literal>proxy</literal></para>
114  <enumlist>
115  <enum name="http" />
116  <enum name="socks4" />
117  <enum name="socks5" />
118  </enumlist>
119  </enum>
120  <enum name="proxyport">
121  <para>Port number of the <literal>proxy</literal></para>
122  </enum>
123  <enum name="proxyuserpwd">
124  <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
125  combination to use for authenticating requests through a
126  <literal>proxy</literal></para>
127  </enum>
128  <enum name="referer">
129  <para>Referer URL to use for the request</para>
130  </enum>
131  <enum name="useragent">
132  <para>UserAgent string to use for the request</para>
133  </enum>
134  <enum name="userpwd">
135  <para>A <replaceable>username</replaceable><literal>:</literal><replaceable>password</replaceable>
136  to use for authentication when the server response to
137  an initial request indicates a 401 status code.</para>
138  </enum>
139  <enum name="ssl_verifypeer">
140  <para>Whether to verify the server certificate against
141  a list of known root certificate authorities (boolean).</para>
142  </enum>
143  <enum name="hashcompat">
144  <para>Assuming the responses will be in <literal>key1=value1&amp;key2=value2</literal>
145  format, reformat the response such that it can be used
146  by the <literal>HASH</literal> function.</para>
147  </enum>
148  </enumlist>
149  </parameter>
150  </syntax>
151  <description>
152  <para>Options may be set globally or per channel. Per-channel
153  settings will override global settings.</para>
154  </description>
155  <see-also>
156  <ref type="function">CURL</ref>
157  <ref type="function">HASH</ref>
158  </see-also>
159  </function>
160  ***/
161 
162 #define CURLVERSION_ATLEAST(a,b,c) \
163  ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
164 
165 #define CURLOPT_SPECIAL_HASHCOMPAT -500
166 
167 static void curlds_free(void *data);
168 
169 static const struct ast_datastore_info curl_info = {
170  .type = "CURL",
171  .destroy = curlds_free,
172 };
173 
176  CURLoption key;
177  void *value;
178 };
179 
181 
182 static void curlds_free(void *data)
183 {
184  AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
185  struct curl_settings *setting;
186  if (!list) {
187  return;
188  }
189  while ((setting = AST_LIST_REMOVE_HEAD(list, list))) {
190  free(setting);
191  }
192  AST_LIST_HEAD_DESTROY(list);
193 }
194 
201 };
202 
203 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
204 {
205  if (!strcasecmp(name, "header")) {
206  *key = CURLOPT_HEADER;
207  *ot = OT_BOOLEAN;
208  } else if (!strcasecmp(name, "proxy")) {
209  *key = CURLOPT_PROXY;
210  *ot = OT_STRING;
211  } else if (!strcasecmp(name, "proxyport")) {
212  *key = CURLOPT_PROXYPORT;
213  *ot = OT_INTEGER;
214  } else if (!strcasecmp(name, "proxytype")) {
215  *key = CURLOPT_PROXYTYPE;
216  *ot = OT_ENUM;
217  } else if (!strcasecmp(name, "dnstimeout")) {
218  *key = CURLOPT_DNS_CACHE_TIMEOUT;
219  *ot = OT_INTEGER;
220  } else if (!strcasecmp(name, "userpwd")) {
221  *key = CURLOPT_USERPWD;
222  *ot = OT_STRING;
223  } else if (!strcasecmp(name, "proxyuserpwd")) {
224  *key = CURLOPT_PROXYUSERPWD;
225  *ot = OT_STRING;
226  } else if (!strcasecmp(name, "maxredirs")) {
227  *key = CURLOPT_MAXREDIRS;
228  *ot = OT_INTEGER;
229  } else if (!strcasecmp(name, "referer")) {
230  *key = CURLOPT_REFERER;
231  *ot = OT_STRING;
232  } else if (!strcasecmp(name, "useragent")) {
233  *key = CURLOPT_USERAGENT;
234  *ot = OT_STRING;
235  } else if (!strcasecmp(name, "cookie")) {
236  *key = CURLOPT_COOKIE;
237  *ot = OT_STRING;
238  } else if (!strcasecmp(name, "ftptimeout")) {
239  *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
240  *ot = OT_INTEGER;
241  } else if (!strcasecmp(name, "httptimeout")) {
242 #if CURLVERSION_ATLEAST(7,16,2)
243  *key = CURLOPT_TIMEOUT_MS;
244  *ot = OT_INTEGER_MS;
245 #else
246  *key = CURLOPT_TIMEOUT;
247  *ot = OT_INTEGER;
248 #endif
249  } else if (!strcasecmp(name, "conntimeout")) {
250 #if CURLVERSION_ATLEAST(7,16,2)
251  *key = CURLOPT_CONNECTTIMEOUT_MS;
252  *ot = OT_INTEGER_MS;
253 #else
254  *key = CURLOPT_CONNECTTIMEOUT;
255  *ot = OT_INTEGER;
256 #endif
257  } else if (!strcasecmp(name, "ftptext")) {
258  *key = CURLOPT_TRANSFERTEXT;
259  *ot = OT_BOOLEAN;
260  } else if (!strcasecmp(name, "ssl_verifypeer")) {
261  *key = CURLOPT_SSL_VERIFYPEER;
262  *ot = OT_BOOLEAN;
263  } else if (!strcasecmp(name, "hashcompat")) {
265  *ot = OT_BOOLEAN;
266  } else {
267  return -1;
268  }
269  return 0;
270 }
271 
272 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
273 {
274  struct ast_datastore *store;
275  struct global_curl_info *list;
276  struct curl_settings *cur, *new = NULL;
277  CURLoption key;
278  enum optiontype ot;
279 
280  if (chan) {
281  if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
282  /* Create a new datastore */
283  if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
284  ast_log(LOG_ERROR, "Unable to allocate new datastore. Cannot set any CURL options\n");
285  return -1;
286  }
287 
288  if (!(list = ast_calloc(1, sizeof(*list)))) {
289  ast_log(LOG_ERROR, "Unable to allocate list head. Cannot set any CURL options\n");
290  ast_datastore_free(store);
291  return -1;
292  }
293 
294  store->data = list;
295  AST_LIST_HEAD_INIT(list);
296  ast_channel_datastore_add(chan, store);
297  } else {
298  list = store->data;
299  }
300  } else {
301  /* Populate the global structure */
302  list = &global_curl_info;
303  }
304 
305  if (!parse_curlopt_key(name, &key, &ot)) {
306  if (ot == OT_BOOLEAN) {
307  if ((new = ast_calloc(1, sizeof(*new)))) {
308  new->value = (void *)((long) ast_true(value));
309  }
310  } else if (ot == OT_INTEGER) {
311  long tmp = atol(value);
312  if ((new = ast_calloc(1, sizeof(*new)))) {
313  new->value = (void *)tmp;
314  }
315  } else if (ot == OT_INTEGER_MS) {
316  long tmp = atof(value) * 1000.0;
317  if ((new = ast_calloc(1, sizeof(*new)))) {
318  new->value = (void *)tmp;
319  }
320  } else if (ot == OT_STRING) {
321  if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
322  new->value = (char *)new + sizeof(*new);
323  strcpy(new->value, value);
324  }
325  } else if (ot == OT_ENUM) {
326  if (key == CURLOPT_PROXYTYPE) {
327  long ptype =
328 #if CURLVERSION_ATLEAST(7,10,0)
329  CURLPROXY_HTTP;
330 #else
331  CURLPROXY_SOCKS5;
332 #endif
333  if (0) {
334 #if CURLVERSION_ATLEAST(7,15,2)
335  } else if (!strcasecmp(value, "socks4")) {
336  ptype = CURLPROXY_SOCKS4;
337 #endif
338 #if CURLVERSION_ATLEAST(7,18,0)
339  } else if (!strcasecmp(value, "socks4a")) {
340  ptype = CURLPROXY_SOCKS4A;
341 #endif
342 #if CURLVERSION_ATLEAST(7,18,0)
343  } else if (!strcasecmp(value, "socks5")) {
344  ptype = CURLPROXY_SOCKS5;
345 #endif
346 #if CURLVERSION_ATLEAST(7,18,0)
347  } else if (!strncasecmp(value, "socks5", 6)) {
348  ptype = CURLPROXY_SOCKS5_HOSTNAME;
349 #endif
350  }
351 
352  if ((new = ast_calloc(1, sizeof(*new)))) {
353  new->value = (void *)ptype;
354  }
355  } else {
356  /* Highly unlikely */
357  goto yuck;
358  }
359  }
360 
361  /* Memory allocation error */
362  if (!new) {
363  return -1;
364  }
365 
366  new->key = key;
367  } else {
368 yuck:
369  ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
370  return -1;
371  }
372 
373  /* Remove any existing entry */
374  AST_LIST_LOCK(list);
375  AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
376  if (cur->key == new->key) {
378  free(cur);
379  break;
380  }
381  }
383 
384  /* Insert new entry */
385  ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
386  AST_LIST_INSERT_TAIL(list, new, list);
387  AST_LIST_UNLOCK(list);
388 
389  return 0;
390 }
391 
392 static int acf_curlopt_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, ssize_t len)
393 {
394  struct ast_datastore *store;
395  struct global_curl_info *list[2] = { &global_curl_info, NULL };
396  struct curl_settings *cur = NULL;
397  CURLoption key;
398  enum optiontype ot;
399  int i;
400 
401  if (parse_curlopt_key(data, &key, &ot)) {
402  ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
403  return -1;
404  }
405 
406  if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
407  list[0] = store->data;
408  list[1] = &global_curl_info;
409  }
410 
411  for (i = 0; i < 2; i++) {
412  if (!list[i]) {
413  break;
414  }
415  AST_LIST_LOCK(list[i]);
416  AST_LIST_TRAVERSE(list[i], cur, list) {
417  if (cur->key == key) {
418  if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
419  if (buf) {
420  snprintf(buf, len, "%ld", (long) cur->value);
421  } else {
422  ast_str_set(bufstr, len, "%ld", (long) cur->value);
423  }
424  } else if (ot == OT_INTEGER_MS) {
425  if ((long) cur->value % 1000 == 0) {
426  if (buf) {
427  snprintf(buf, len, "%ld", (long)cur->value / 1000);
428  } else {
429  ast_str_set(bufstr, len, "%ld", (long) cur->value / 1000);
430  }
431  } else {
432  if (buf) {
433  snprintf(buf, len, "%.3f", (double) ((long) cur->value) / 1000.0);
434  } else {
435  ast_str_set(bufstr, len, "%.3f", (double) ((long) cur->value) / 1000.0);
436  }
437  }
438  } else if (ot == OT_STRING) {
439  ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
440  if (buf) {
441  ast_copy_string(buf, cur->value, len);
442  } else {
443  ast_str_set(bufstr, 0, "%s", (char *) cur->value);
444  }
445  } else if (key == CURLOPT_PROXYTYPE) {
446  if (0) {
447 #if CURLVERSION_ATLEAST(7,15,2)
448  } else if ((long)cur->value == CURLPROXY_SOCKS4) {
449  if (buf) {
450  ast_copy_string(buf, "socks4", len);
451  } else {
452  ast_str_set(bufstr, 0, "socks4");
453  }
454 #endif
455 #if CURLVERSION_ATLEAST(7,18,0)
456  } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
457  if (buf) {
458  ast_copy_string(buf, "socks4a", len);
459  } else {
460  ast_str_set(bufstr, 0, "socks4a");
461  }
462 #endif
463  } else if ((long)cur->value == CURLPROXY_SOCKS5) {
464  if (buf) {
465  ast_copy_string(buf, "socks5", len);
466  } else {
467  ast_str_set(bufstr, 0, "socks5");
468  }
469 #if CURLVERSION_ATLEAST(7,18,0)
470  } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
471  if (buf) {
472  ast_copy_string(buf, "socks5hostname", len);
473  } else {
474  ast_str_set(bufstr, 0, "socks5hostname");
475  }
476 #endif
477 #if CURLVERSION_ATLEAST(7,10,0)
478  } else if ((long)cur->value == CURLPROXY_HTTP) {
479  if (buf) {
480  ast_copy_string(buf, "http", len);
481  } else {
482  ast_str_set(bufstr, 0, "http");
483  }
484 #endif
485  } else {
486  if (buf) {
487  ast_copy_string(buf, "unknown", len);
488  } else {
489  ast_str_set(bufstr, 0, "unknown");
490  }
491  }
492  }
493  break;
494  }
495  }
496  AST_LIST_UNLOCK(list[i]);
497  if (cur) {
498  break;
499  }
500  }
501 
502  return cur ? 0 : -1;
503 }
504 
505 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
506 {
507  return acf_curlopt_helper(chan, cmd, data, buf, NULL, len);
508 }
509 
510 static int acf_curlopt_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
511 {
512  return acf_curlopt_helper(chan, cmd, data, NULL, buf, len);
513 }
514 
515 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
516 {
517  register int realsize = size * nmemb;
518  struct ast_str **pstr = (struct ast_str **)data;
519 
520  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));
521 
522  ast_str_append_substr(pstr, 0, ptr, realsize);
523 
524  ast_debug(3, "Now, len=%zu, used=%zu\n", ast_str_size(*pstr), ast_str_strlen(*pstr));
525 
526  return realsize;
527 }
528 
529 static const char * const global_useragent = "asterisk-libcurl-agent/1.0";
530 
531 static int curl_instance_init(void *data)
532 {
533  CURL **curl = data;
534 
535  if (!(*curl = curl_easy_init()))
536  return -1;
537 
538  curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
539  curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
540  curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
541  curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
542 
543  return 0;
544 }
545 
546 static void curl_instance_cleanup(void *data)
547 {
548  CURL **curl = data;
549 
550  curl_easy_cleanup(*curl);
551 
552  ast_free(data);
553 }
554 
557 
558 /*!
559  * \brief Check for potential HTTP injection risk.
560  *
561  * CVE-2014-8150 brought up the fact that HTTP proxies are subject to injection
562  * attacks. An HTTP URL sent to a proxy contains a carriage-return linefeed combination,
563  * followed by a complete HTTP request. Proxies will handle this as two separate HTTP
564  * requests rather than as a malformed URL.
565  *
566  * libcURL patched this vulnerability in version 7.40.0, but we have no guarantee that
567  * Asterisk systems will be using an up-to-date cURL library. Therefore, we implement
568  * the same fix as libcURL for determining if a URL is vulnerable to an injection attack.
569  *
570  * \param url The URL to check for vulnerability
571  * \retval 0 The URL is not vulnerable
572  * \retval 1 The URL is vulnerable.
573  */
574 static int url_is_vulnerable(const char *url)
575 {
576  if (strpbrk(url, "\r\n")) {
577  return 1;
578  }
579 
580  return 0;
581 }
582 
583 static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
584 {
585  struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16);
586  struct ast_str *str = ast_str_create(16);
587  int ret = -1;
589  AST_APP_ARG(url);
590  AST_APP_ARG(postdata);
591  );
592  CURL **curl;
593  struct curl_settings *cur;
594  struct ast_datastore *store = NULL;
595  int hashcompat = 0;
597  char curl_errbuf[CURL_ERROR_SIZE + 1]; /* add one to be safe */
598 
599  if (buf) {
600  *buf = '\0';
601  }
602 
603  if (!str) {
604  return -1;
605  }
606 
607  if (!escapebuf) {
608  ast_free(str);
609  return -1;
610  }
611 
612  if (ast_strlen_zero(info)) {
613  ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
614  ast_free(str);
615  return -1;
616  }
617 
619 
620  if (url_is_vulnerable(args.url)) {
621  ast_log(LOG_ERROR, "URL '%s' is vulnerable to HTTP injection attacks. Aborting CURL() call.\n", args.url);
622  return -1;
623  }
624 
625  if (chan) {
626  ast_autoservice_start(chan);
627  }
628 
629  if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
630  ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
631  ast_free(str);
632  return -1;
633  }
634 
637  if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
638  hashcompat = (cur->value != NULL) ? 1 : 0;
639  } else {
640  curl_easy_setopt(*curl, cur->key, cur->value);
641  }
642  }
643 
644  if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
645  list = store->data;
647  AST_LIST_TRAVERSE(list, cur, list) {
648  if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) {
649  hashcompat = (cur->value != NULL) ? 1 : 0;
650  } else {
651  curl_easy_setopt(*curl, cur->key, cur->value);
652  }
653  }
654  }
655 
656  curl_easy_setopt(*curl, CURLOPT_URL, args.url);
657  curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
658 
659  if (args.postdata) {
660  curl_easy_setopt(*curl, CURLOPT_POST, 1);
661  curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
662  }
663 
664  /* Temporarily assign a buffer for curl to write errors to. */
665  curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0';
666  curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, curl_errbuf);
667 
668  if (curl_easy_perform(*curl) != 0) {
669  ast_log(LOG_WARNING, "%s ('%s')\n", curl_errbuf, args.url);
670  }
671 
672  /* Reset buffer to NULL so curl doesn't try to write to it when the
673  * buffer is deallocated. Documentation is vague about allowing NULL
674  * here, but the source allows it. See: "typecheck: allow NULL to unset
675  * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */
676  curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL);
677 
678  if (store) {
680  }
682 
683  if (args.postdata) {
684  curl_easy_setopt(*curl, CURLOPT_POST, 0);
685  }
686 
687  if (ast_str_strlen(str)) {
688  ast_str_trim_blanks(str);
689 
690  ast_debug(3, "str='%s'\n", ast_str_buffer(str));
691  if (hashcompat) {
692  char *remainder = ast_str_buffer(str);
693  char *piece;
694  struct ast_str *fields = ast_str_create(ast_str_strlen(str) / 2);
695  struct ast_str *values = ast_str_create(ast_str_strlen(str) / 2);
696  int rowcount = 0;
697  while (fields && values && (piece = strsep(&remainder, "&"))) {
698  char *name = strsep(&piece, "=");
699  if (piece) {
700  ast_uri_decode(piece);
701  }
702  ast_uri_decode(name);
703  ast_str_append(&fields, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, name, INT_MAX));
704  ast_str_append(&values, 0, "%s%s", rowcount ? "," : "", ast_str_set_escapecommas(&escapebuf, 0, S_OR(piece, ""), INT_MAX));
705  rowcount++;
706  }
707  pbx_builtin_setvar_helper(chan, "~ODBCFIELDS~", ast_str_buffer(fields));
708  if (buf) {
709  ast_copy_string(buf, ast_str_buffer(values), len);
710  } else {
711  ast_str_set(input_str, len, "%s", ast_str_buffer(values));
712  }
713  ast_free(fields);
714  ast_free(values);
715  } else {
716  if (buf) {
717  ast_copy_string(buf, ast_str_buffer(str), len);
718  } else {
719  ast_str_set(input_str, len, "%s", ast_str_buffer(str));
720  }
721  }
722  ret = 0;
723  }
724  ast_free(str);
725 
726  if (chan)
727  ast_autoservice_stop(chan);
728 
729  return ret;
730 }
731 
732 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
733 {
734  return acf_curl_helper(chan, cmd, info, buf, NULL, len);
735 }
736 
737 static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
738 {
739  return acf_curl_helper(chan, cmd, info, NULL, buf, len);
740 }
741 
742 static struct ast_custom_function acf_curl = {
743  .name = "CURL",
744  .synopsis = "Retrieves the contents of a URL",
745  .syntax = "CURL(url[,post-data])",
746  .desc =
747  " url - URL to retrieve\n"
748  " post-data - Optional data to send as a POST (GET is default action)\n",
749  .read = acf_curl_exec,
750  .read2 = acf_curl2_exec,
751 };
752 
754  .name = "CURLOPT",
755  .synopsis = "Set options for use with the CURL() function",
756  .syntax = "CURLOPT(<option>)",
757  .desc =
758 " cookie - Send cookie with request [none]\n"
759 " conntimeout - Number of seconds to wait for connection\n"
760 " dnstimeout - Number of seconds to wait for DNS response\n"
761 " ftptext - For FTP, force a text transfer (boolean)\n"
762 " ftptimeout - For FTP, the server response timeout\n"
763 " header - Retrieve header information (boolean)\n"
764 " httptimeout - Number of seconds to wait for HTTP response\n"
765 " maxredirs - Maximum number of redirects to follow\n"
766 " proxy - Hostname or IP to use as a proxy\n"
767 " proxytype - http, socks4, or socks5\n"
768 " proxyport - port number of the proxy\n"
769 " proxyuserpwd - A <user>:<pass> to use for authentication\n"
770 " referer - Referer URL to use for the request\n"
771 " useragent - UserAgent string to use\n"
772 " userpwd - A <user>:<pass> to use for authentication\n"
773 " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n"
774 " hashcompat - Result data will be compatible for use with HASH()\n"
775 "",
776  .read = acf_curlopt_read,
777  .read2 = acf_curlopt_read2,
778  .write = acf_curlopt_write,
779 };
780 
781 AST_TEST_DEFINE(vulnerable_url)
782 {
783  const char *bad_urls [] = {
784  "http://example.com\r\nDELETE http://example.com/everything",
785  "http://example.com\rDELETE http://example.com/everything",
786  "http://example.com\nDELETE http://example.com/everything",
787  "\r\nhttp://example.com",
788  "\rhttp://example.com",
789  "\nhttp://example.com",
790  "http://example.com\r\n",
791  "http://example.com\r",
792  "http://example.com\n",
793  };
794  const char *good_urls [] = {
795  "http://example.com",
796  "http://example.com/%5Cr%5Cn",
797  };
798  int i;
800 
801  switch (cmd) {
802  case TEST_INIT:
803  info->name = "vulnerable_url";
804  info->category = "/funcs/func_curl/";
805  info->summary = "cURL vulnerable URL test";
806  info->description =
807  "Ensure that any combination of '\\r' or '\\n' in a URL invalidates the URL";
808  case TEST_EXECUTE:
809  break;
810  }
811 
812  for (i = 0; i < ARRAY_LEN(bad_urls); ++i) {
813  if (!url_is_vulnerable(bad_urls[i])) {
814  ast_test_status_update(test, "String '%s' detected as valid when it should be invalid\n", bad_urls[i]);
815  res = AST_TEST_FAIL;
816  }
817  }
818 
819  for (i = 0; i < ARRAY_LEN(good_urls); ++i) {
820  if (url_is_vulnerable(good_urls[i])) {
821  ast_test_status_update(test, "String '%s' detected as invalid when it should be valid\n", good_urls[i]);
822  res = AST_TEST_FAIL;
823  }
824  }
825 
826  return res;
827 }
828 
829 static int unload_module(void)
830 {
831  int res;
832 
835 
836  AST_TEST_UNREGISTER(vulnerable_url);
837 
838  return res;
839 }
840 
841 static int load_module(void)
842 {
843  int res;
844 
845  if (!ast_module_check("res_curl.so")) {
846  if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
847  ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
849  }
850  }
851 
854 
855  AST_TEST_REGISTER(vulnerable_url);
856 
857  return res;
858 }
859 
861  .load = load_module,
862  .unload = unload_module,
863  .load_pri = AST_MODPRI_REALTIME_DEPEND2,
864  );
865 
const char * type
Definition: datastore.h:32
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:81
Main Channel structure associated with a channel.
Definition: channel.h:742
static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
Definition: func_curl.c:272
#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
static int acf_curl2_exec(struct ast_channel *chan, const char *cmd, char *info, struct ast_str **buf, ssize_t len)
Definition: func_curl.c:737
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:179
char * strsep(char **str, const char *delims)
enum ast_module_load_result ast_load_resource(const char *resource_name)
Load a module.
Definition: loader.c:947
static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
Definition: func_curl.c:515
static struct ast_custom_function acf_curlopt
Definition: func_curl.c:753
size_t ast_str_size(const struct ast_str *buf)
Returns the current maximum length (without reallocation) of the current buffer.
Definition: strings.h:482
void ast_uri_decode(char *s)
Decode URI, URN, URL (overwrite string)
Definition: utils.c:484
#define LOG_WARNING
Definition: logger.h:144
#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:497
static struct ast_threadstorage curl_instance
Definition: func_curl.c:555
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Definition: app.h:572
static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
Definition: func_curl.c:203
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:510
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:900
struct ast_str * ast_str_create(size_t init_len)
Create a malloc&#39;ed dynamic length string.
Definition: strings.h:420
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:374
Structure for a data store object.
Definition: datastore.h:54
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:2604
const char * str
Definition: app_jack.c:144
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:600
struct global_curl_info global_curl_info
static const char *const global_useragent
Definition: func_curl.c:529
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
Definition: pbx.c:3814
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:65
int ast_module_check(const char *name)
Check if module with the name given is loaded.
Definition: loader.c:1255
Utility functions.
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:638
struct curl_settings::@131 list
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:874
static int url_is_vulnerable(const char *url)
Check for potential HTTP injection risk.
Definition: func_curl.c:574
CURLoption key
Definition: func_curl.c:176
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:236
void * value
Definition: func_curl.c:177
General Asterisk PBX channel definitions.
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
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:823
Data structure associated with a custom dialplan function.
Definition: pbx.h:95
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:554
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:818
Core PBX routines and definitions.
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:238
#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:531
static int acf_curl_helper(struct ast_channel *chan, const char *cmd, char *info, char *buf, struct ast_str **input_str, ssize_t len)
Definition: func_curl.c:583
#define LOG_ERROR
Definition: logger.h:155
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:716
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is &quot;true&quot;. This function checks to see whether a string passed to it is an indication of an &quot;true&quot; value. It checks to see if the string is &quot;yes&quot;, &quot;true&quot;, &quot;y&quot;, &quot;t&quot;, &quot;on&quot; or &quot;1&quot;.
Definition: utils.c:1533
static struct ast_custom_function acf_curl
Definition: func_curl.c:742
The descriptor of a dynamic string XXX storage will be optimized later if needed We use the ts field ...
Definition: strings.h:364
#define free(a)
Definition: astmm.h:94
static struct @350 args
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
static int load_module(void)
Definition: func_curl.c:841
static struct ast_threadstorage thread_escapebuf
Definition: func_curl.c:556
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
static int unload_module(void)
Definition: func_curl.c:829
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
struct ast_datastore * ast_datastore_alloc(const struct ast_datastore_info *info, const char *uid)
Definition: datastore.c:98
#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
static const char name[]
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:611
#define ast_free(a)
Definition: astmm.h:97
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:392
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...
Definition: pbx.c:10546
#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:830
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:471
void * data
Definition: datastore.h:56
static void curlds_free(void *data)
Definition: func_curl.c:182
Standard Command Line Interface.
#define ast_calloc(a, b)
Definition: astmm.h:82
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:223
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:77
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
const char * name
Definition: pbx.h:96
static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_curl.c:505
#define AST_APP_ARG(name)
Define an application argument.
Definition: app.h:555
#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 * ast_str_thread_get(struct ast_threadstorage *ts, size_t init_len)
Retrieve a thread locally stored dynamic string.
Definition: strings.h:669
void ast_str_trim_blanks(struct ast_str *buf)
Trims trailing whitespace characters from an ast_str string.
Definition: strings.h:461
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
Definition: app.h:604
static struct ast_datastore_info curl_info
Definition: func_curl.c:169
static char url[512]
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:38
optiontype
Definition: func_curl.c:195
Asterisk module definitions.
static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
Definition: func_curl.c:732
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2590
#define CURLOPT_SPECIAL_HASHCOMPAT
Definition: func_curl.c:165
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1164
ast_test_result_state
Definition: test.h:189
#define ASTERISK_FILE_VERSION(file, version)
Register/unregister a source code file with the core.
Definition: asterisk.h:180
static void curl_instance_cleanup(void *data)
Definition: func_curl.c:546