Fix func_curl compilation
[asterisk/asterisk.git] / funcs / func_curl.c
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  ***/
36
37 #include "asterisk.h"
38
39 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
40
41 #include <curl/curl.h>
42
43 #include "asterisk/lock.h"
44 #include "asterisk/file.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/cli.h"
48 #include "asterisk/module.h"
49 #include "asterisk/app.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/threadstorage.h"
52
53 #define CURLVERSION_ATLEAST(a,b,c) \
54         ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c))))
55
56 static void curlds_free(void *data);
57
58 static struct ast_datastore_info curl_info = {
59         .type = "CURL",
60         .destroy = curlds_free,
61 };
62
63 struct curl_settings {
64         AST_LIST_ENTRY(curl_settings) list;
65         CURLoption key;
66         void *value;
67 };
68
69 AST_LIST_HEAD_STATIC(global_curl_info, curl_settings);
70
71 static void curlds_free(void *data)
72 {
73         AST_LIST_HEAD(global_curl_info, curl_settings) *list = data;
74         struct curl_settings *cur;
75         if (!list) {
76                 return;
77         }
78         while ((cur = AST_LIST_REMOVE_HEAD(list, list))) {
79                 free(cur);
80         }
81         AST_LIST_HEAD_DESTROY(list);
82 }
83
84 enum optiontype {
85         OT_BOOLEAN,
86         OT_INTEGER,
87         OT_INTEGER_MS,
88         OT_STRING,
89         OT_ENUM,
90 };
91
92 static int parse_curlopt_key(const char *name, CURLoption *key, enum optiontype *ot)
93 {
94         if (!strcasecmp(name, "header")) {
95                 *key = CURLOPT_HEADER;
96                 *ot = OT_BOOLEAN;
97         } else if (!strcasecmp(name, "proxy")) {
98                 *key = CURLOPT_PROXY;
99                 *ot = OT_STRING;
100         } else if (!strcasecmp(name, "proxyport")) {
101                 *key = CURLOPT_PROXYPORT;
102                 *ot = OT_INTEGER;
103         } else if (!strcasecmp(name, "proxytype")) {
104                 *key = CURLOPT_PROXYTYPE;
105                 *ot = OT_ENUM;
106         } else if (!strcasecmp(name, "dnstimeout")) {
107                 *key = CURLOPT_DNS_CACHE_TIMEOUT;
108                 *ot = OT_INTEGER;
109         } else if (!strcasecmp(name, "userpwd")) {
110                 *key = CURLOPT_USERPWD;
111                 *ot = OT_STRING;
112         } else if (!strcasecmp(name, "proxyuserpwd")) {
113                 *key = CURLOPT_PROXYUSERPWD;
114                 *ot = OT_STRING;
115         } else if (!strcasecmp(name, "maxredirs")) {
116                 *key = CURLOPT_MAXREDIRS;
117                 *ot = OT_INTEGER;
118         } else if (!strcasecmp(name, "referer")) {
119                 *key = CURLOPT_REFERER;
120                 *ot = OT_STRING;
121         } else if (!strcasecmp(name, "useragent")) {
122                 *key = CURLOPT_USERAGENT;
123                 *ot = OT_STRING;
124         } else if (!strcasecmp(name, "cookie")) {
125                 *key = CURLOPT_COOKIE;
126                 *ot = OT_STRING;
127         } else if (!strcasecmp(name, "ftptimeout")) {
128                 *key = CURLOPT_FTP_RESPONSE_TIMEOUT;
129                 *ot = OT_INTEGER;
130         } else if (!strcasecmp(name, "httptimeout")) {
131 #if CURLVERSION_ATLEAST(7,16,2)
132                 *key = CURLOPT_TIMEOUT_MS;
133                 *ot = OT_INTEGER_MS;
134 #else
135                 *key = CURLOPT_TIMEOUT;
136                 *ot = OT_INTEGER;
137 #endif
138         } else if (!strcasecmp(name, "conntimeout")) {
139 #if CURLVERSION_ATLEAST(7,16,2)
140                 *key = CURLOPT_CONNECTTIMEOUT_MS;
141                 *ot = OT_INTEGER_MS;
142 #else
143                 *key = CURLOPT_CONNECTTIMEOUT;
144                 *ot = OT_INTEGER;
145 #endif
146         } else if (!strcasecmp(name, "ftptext")) {
147                 *key = CURLOPT_TRANSFERTEXT;
148                 *ot = OT_BOOLEAN;
149         } else {
150                 return -1;
151         }
152         return 0;
153 }
154
155 static int acf_curlopt_write(struct ast_channel *chan, const char *cmd, char *name, const char *value)
156 {
157         struct ast_datastore *store;
158         AST_LIST_HEAD(global_curl_info, curl_settings) *list;
159         struct curl_settings *cur, *new = NULL;
160         CURLoption key;
161         enum optiontype ot;
162
163         if (chan) {
164                 if (!(store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
165                         /* Create a new datastore */
166                         if (!(store = ast_datastore_alloc(&curl_info, NULL))) {
167                                 ast_log(LOG_ERROR, "Unable to allocate new datastore.  Cannot set any CURL options\n");
168                                 return -1;
169                         }
170
171                         if (!(list = ast_calloc(1, sizeof(*list)))) {
172                                 ast_log(LOG_ERROR, "Unable to allocate list head.  Cannot set any CURL options\n");
173                                 ast_datastore_free(store);
174                         }
175
176                         store->data = list;
177                         AST_LIST_HEAD_INIT(list);
178                         ast_channel_datastore_add(chan, store);
179                 } else {
180                         list = store->data;
181                 }
182         } else {
183                 /* Populate the global structure */
184                 list = (struct global_curl_info *)&global_curl_info;
185         }
186
187         if (!parse_curlopt_key(name, &key, &ot)) {
188                 if (ot == OT_BOOLEAN) {
189                         if ((new = ast_calloc(1, sizeof(*new)))) {
190                                 new->value = (void *)((long) ast_true(value));
191                         }
192                 } else if (ot == OT_INTEGER) {
193                         long tmp = atol(value);
194                         if ((new = ast_calloc(1, sizeof(*new)))) {
195                                 new->value = (void *)tmp;
196                         }
197                 } else if (ot == OT_INTEGER_MS) {
198                         long tmp = atof(value) * 1000.0;
199                         if ((new = ast_calloc(1, sizeof(*new)))) {
200                                 new->value = (void *)tmp;
201                         }
202                 } else if (ot == OT_STRING) {
203                         if ((new = ast_calloc(1, sizeof(*new) + strlen(value) + 1))) {
204                                 new->value = (char *)new + sizeof(*new);
205                                 strcpy(new->value, value);
206                         }
207                 } else if (ot == OT_ENUM) {
208                         if (key == CURLOPT_PROXYTYPE) {
209                                 long ptype =
210 #if CURLVERSION_ATLEAST(7,10,0)
211                                         CURLPROXY_HTTP;
212 #else
213                                         CURLPROXY_SOCKS5;
214 #endif
215                                 if (0) {
216 #if CURLVERSION_ATLEAST(7,15,2)
217                                 } else if (!strcasecmp(value, "socks4")) {
218                                         ptype = CURLPROXY_SOCKS4;
219 #endif
220 #if CURLVERSION_ATLEAST(7,18,0)
221                                 } else if (!strcasecmp(value, "socks4a")) {
222                                         ptype = CURLPROXY_SOCKS4A;
223 #endif
224 #if CURLVERSION_ATLEAST(7,18,0)
225                                 } else if (!strcasecmp(value, "socks5")) {
226                                         ptype = CURLPROXY_SOCKS5;
227 #endif
228 #if CURLVERSION_ATLEAST(7,18,0)
229                                 } else if (!strncasecmp(value, "socks5", 6)) {
230                                         ptype = CURLPROXY_SOCKS5_HOSTNAME;
231 #endif
232                                 }
233
234                                 if ((new = ast_calloc(1, sizeof(*new)))) {
235                                         new->value = (void *)ptype;
236                                 }
237                         } else {
238                                 /* Highly unlikely */
239                                 goto yuck;
240                         }
241                 }
242
243                 /* Memory allocation error */
244                 if (!new) {
245                         return -1;
246                 }
247
248                 new->key = key;
249         } else {
250 yuck:
251                 ast_log(LOG_ERROR, "Unrecognized option: %s\n", name);
252                 return -1;
253         }
254
255         /* Remove any existing entry */
256         AST_LIST_LOCK(list);
257         AST_LIST_TRAVERSE_SAFE_BEGIN(list, cur, list) {
258                 if (cur->key == new->key) {
259                         AST_LIST_REMOVE_CURRENT(list);
260                         free(cur);
261                         break;
262                 }
263         }
264         AST_LIST_TRAVERSE_SAFE_END
265
266         /* Insert new entry */
267         ast_debug(1, "Inserting entry %p with key %d and value %p\n", new, new->key, new->value);
268         AST_LIST_INSERT_TAIL(list, new, list);
269         AST_LIST_UNLOCK(list);
270
271         return 0;
272 }
273
274 static int acf_curlopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
275 {
276         struct ast_datastore *store;
277         AST_LIST_HEAD(global_curl_info, curl_settings) *list[2] = { (struct global_curl_info *)&global_curl_info, NULL };
278         struct curl_settings *cur;
279         CURLoption key;
280         enum optiontype ot;
281         int i;
282
283         if (parse_curlopt_key(data, &key, &ot)) {
284                 ast_log(LOG_ERROR, "Unrecognized option: '%s'\n", data);
285                 return -1;
286         }
287
288         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
289                 list[0] = store->data;
290                 list[1] = (struct global_curl_info *)&global_curl_info;
291         }
292
293         for (i = 0; i < 2; i++) {
294                 if (!list[i]) {
295                         break;
296                 }
297                 AST_LIST_LOCK(list[i]);
298                 AST_LIST_TRAVERSE(list[i], cur, list) {
299                         if (cur->key == key) {
300                                 if (ot == OT_BOOLEAN || ot == OT_INTEGER) {
301                                         snprintf(buf, len, "%ld", (long)cur->value);
302                                 } else if (ot == OT_INTEGER_MS) {
303                                         if ((long)cur->value % 1000 == 0) {
304                                                 snprintf(buf, len, "%ld", (long)cur->value / 1000);
305                                         } else {
306                                                 snprintf(buf, len, "%.3f", (double)((long)cur->value) / 1000.0);
307                                         }
308                                 } else if (ot == OT_STRING) {
309                                         ast_debug(1, "Found entry %p, with key %d and value %p\n", cur, cur->key, cur->value);
310                                         ast_copy_string(buf, cur->value, len);
311                                 } else if (key == CURLOPT_PROXYTYPE) {
312                                         if (0) {
313 #if CURLVERSION_ATLEAST(7,15,2)
314                                         } else if ((long)cur->value == CURLPROXY_SOCKS4) {
315                                                 ast_copy_string(buf, "socks4", len);
316 #endif
317 #if CURLVERSION_ATLEAST(7,18,0)
318                                         } else if ((long)cur->value == CURLPROXY_SOCKS4A) {
319                                                 ast_copy_string(buf, "socks4a", len);
320 #endif
321                                         } else if ((long)cur->value == CURLPROXY_SOCKS5) {
322                                                 ast_copy_string(buf, "socks5", len);
323 #if CURLVERSION_ATLEAST(7,18,0)
324                                         } else if ((long)cur->value == CURLPROXY_SOCKS5_HOSTNAME) {
325                                                 ast_copy_string(buf, "socks5hostname", len);
326 #endif
327 #if CURLVERSION_ATLEAST(7,10,0)
328                                         } else if ((long)cur->value == CURLPROXY_HTTP) {
329                                                 ast_copy_string(buf, "http", len);
330 #endif
331                                         } else {
332                                                 ast_copy_string(buf, "unknown", len);
333                                         }
334                                 }
335                                 break;
336                         }
337                 }
338                 AST_LIST_UNLOCK(list[i]);
339                 if (cur) {
340                         break;
341                 }
342         }
343
344         return cur ? 0 : -1;
345 }
346
347 static size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
348 {
349         register int realsize = size * nmemb;
350         struct ast_str **pstr = (struct ast_str **)data;
351
352         ast_debug(3, "Called with data=%p, str=%p, realsize=%d, len=%zu, used=%zu\n", data, *pstr, realsize, (*pstr)->len, (*pstr)->used);
353
354         if (ast_str_make_space(pstr, (((*pstr)->used + realsize + 1) / 512 + 1) * 512 + 470) == 0) {
355                 memcpy(&((*pstr)->str[(*pstr)->used]), ptr, realsize);
356                 (*pstr)->used += realsize;
357         }
358
359         ast_debug(3, "Now, len=%zu, used=%zu\n", (*pstr)->len, (*pstr)->used);
360
361         return realsize;
362 }
363
364 static const char *global_useragent = "asterisk-libcurl-agent/1.0";
365
366 static int curl_instance_init(void *data)
367 {
368         CURL **curl = data;
369
370         if (!(*curl = curl_easy_init()))
371                 return -1;
372
373         curl_easy_setopt(*curl, CURLOPT_NOSIGNAL, 1);
374         curl_easy_setopt(*curl, CURLOPT_TIMEOUT, 180);
375         curl_easy_setopt(*curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
376         curl_easy_setopt(*curl, CURLOPT_USERAGENT, global_useragent);
377
378         return 0;
379 }
380
381 static void curl_instance_cleanup(void *data)
382 {
383         CURL **curl = data;
384
385         curl_easy_cleanup(*curl);
386
387         ast_free(data);
388 }
389
390 AST_THREADSTORAGE_CUSTOM(curl_instance, curl_instance_init, curl_instance_cleanup);
391
392 static int acf_curl_exec(struct ast_channel *chan, const char *cmd, char *info, char *buf, size_t len)
393 {
394         struct ast_str *str = ast_str_create(16);
395         AST_DECLARE_APP_ARGS(args,
396                 AST_APP_ARG(url);
397                 AST_APP_ARG(postdata);
398         );
399         CURL **curl;
400         struct curl_settings *cur;
401         struct ast_datastore *store = NULL;
402         AST_LIST_HEAD(global_curl_info, curl_settings) *list = NULL;
403
404         *buf = '\0';
405         
406         if (ast_strlen_zero(info)) {
407                 ast_log(LOG_WARNING, "CURL requires an argument (URL)\n");
408                 ast_free(str);
409                 return -1;
410         }
411
412         AST_STANDARD_APP_ARGS(args, info);      
413
414         if (chan) {
415                 ast_autoservice_start(chan);
416         }
417
418         if (!(curl = ast_threadstorage_get(&curl_instance, sizeof(*curl)))) {
419                 ast_log(LOG_ERROR, "Cannot allocate curl structure\n");
420                 return -1;
421         }
422
423         AST_LIST_LOCK(&global_curl_info);
424         AST_LIST_TRAVERSE(&global_curl_info, cur, list) {
425                 curl_easy_setopt(*curl, cur->key, cur->value);
426         }
427
428         if (chan && (store = ast_channel_datastore_find(chan, &curl_info, NULL))) {
429                 list = store->data;
430                 AST_LIST_LOCK(list);
431                 AST_LIST_TRAVERSE(list, cur, list) {
432                         curl_easy_setopt(*curl, cur->key, cur->value);
433                 }
434         }
435
436         curl_easy_setopt(*curl, CURLOPT_URL, args.url);
437         curl_easy_setopt(*curl, CURLOPT_FILE, (void *) &str);
438
439         if (args.postdata) {
440                 curl_easy_setopt(*curl, CURLOPT_POST, 1);
441                 curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args.postdata);
442         }
443
444         curl_easy_perform(*curl);
445
446         if (store) {
447                 AST_LIST_UNLOCK(list);
448         }
449         AST_LIST_UNLOCK(&global_curl_info);
450
451         if (args.postdata) {
452                 curl_easy_setopt(*curl, CURLOPT_POST, 0);
453         }
454
455         if (str->used) {
456                 str->str[str->used] = '\0';
457                 if (str->str[str->used - 1] == '\n') {
458                         str->str[str->used - 1] = '\0';
459                 }
460
461                 ast_copy_string(buf, str->str, len);
462         }
463         ast_free(str);
464
465         if (chan)
466                 ast_autoservice_stop(chan);
467         
468         return 0;
469 }
470
471 struct ast_custom_function acf_curl = {
472         .name = "CURL",
473         .synopsis = "Retrieves the contents of a URL",
474         .syntax = "CURL(url[,post-data])",
475         .desc =
476         "  url       - URL to retrieve\n"
477         "  post-data - Optional data to send as a POST (GET is default action)\n",
478         .read = acf_curl_exec,
479 };
480
481 struct ast_custom_function acf_curlopt = {
482         .name = "CURLOPT",
483         .synopsis = "Set options for use with the CURL() function",
484         .syntax = "CURLOPT(<option>)",
485         .desc =
486 "  cookie       - Send cookie with request\n"
487 "  conntimeout  - Number of seconds to wait for connection\n"
488 "  dnstimeout   - Number of seconds to wait for DNS response\n"
489 "  ftptext      - For FTP, force a text transfer (boolean)\n"
490 "  ftptimeout   - For FTP, the server response timeout\n"
491 "  header       - Retrieve header information (boolean)\n"
492 "  httptimeout  - Number of seconds to wait for HTTP response\n"
493 "  maxredirs    - Maximum number of redirects to follow\n"
494 "  proxy        - Hostname or IP to use as a proxy\n"
495 "  proxytype    - http, socks4, or socks5\n"
496 "  proxyport    - port number of the proxy\n"
497 "  proxyuserpwd - A <user>:<pass> to use for authentication\n"
498 "  referer      - Referer URL to use for the request\n"
499 "  useragent    - UserAgent string to use\n"
500 "  userpwd      - A <user>:<pass> to use for authentication\n"
501 "",
502         .read = acf_curlopt_read,
503         .write = acf_curlopt_write,
504 };
505
506 static int unload_module(void)
507 {
508         int res;
509
510         res = ast_custom_function_unregister(&acf_curl);
511         res |= ast_custom_function_unregister(&acf_curlopt);
512
513         return res;
514 }
515
516 static int load_module(void)
517 {
518         int res;
519
520         if (!ast_module_check("res_curl.so")) {
521                 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
522                         ast_log(LOG_ERROR, "Cannot load res_curl, so func_curl cannot be loaded\n");
523                         return AST_MODULE_LOAD_DECLINE;
524                 }
525         }
526
527         res = ast_custom_function_register(&acf_curl);
528         res |= ast_custom_function_register(&acf_curlopt);
529
530         return res;
531 }
532
533 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Load external URL");
534