func_curl.c 28 KB

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