func_curl.c 25 KB

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