Merge realtime_update2 branch, which adds a new realtime API call named
[asterisk/asterisk.git] / res / res_config_curl.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2008, Digium, Inc.
5  *
6  * Tilghman Lesher <res_config_curl_v1@the-tilghman.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief curl plugin for portable configuration engine
22  *
23  * \author Tilghman Lesher <res_config_curl_v1@the-tilghman.com>
24  *
25  * \extref Depends on the CURL library  - http://curl.haxx.se/
26  * 
27  */
28
29 /*** MODULEINFO
30         <depend>curl</depend>
31  ***/
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include <curl/curl.h>
38
39 #include "asterisk/file.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/config.h"
43 #include "asterisk/module.h"
44 #include "asterisk/lock.h"
45 #include "asterisk/utils.h"
46
47 /*!
48  * \brief Execute a curl query and return ast_variable list
49  * \param url The base URL from which to retrieve data
50  * \param unused Not currently used
51  * \param ap list containing one or more field/operator/value set.
52  *
53  * \retval var on success
54  * \retval NULL on failure
55 */
56 static struct ast_variable *realtime_curl(const char *url, const char *unused, va_list ap)
57 {
58         struct ast_str *query;
59         char buf1[200], buf2[200];
60         const char *newparam, *newval;
61         char *stringp, *pair, *key;
62         int i;
63         struct ast_variable *var=NULL, *prev=NULL;
64         const int EncodeSpecialChars = 1, bufsize = 64000;
65         char *buffer;
66
67         if (!ast_custom_function_find("CURL")) {
68                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
69                 return NULL;
70         }
71
72         if (!(query = ast_str_create(1000)))
73                 return NULL;
74
75         if (!(buffer = ast_malloc(bufsize))) {
76                 ast_free(query);
77                 return NULL;
78         }
79
80         ast_str_set(&query, 0, "${CURL(%s/single,", url);
81
82         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
83                 newval = va_arg(ap, const char *);
84                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
85                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
86                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
87         }
88         va_end(ap);
89
90         ast_str_append(&query, 0, ")}");
91         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
92
93         /* Remove any trailing newline characters */
94         if ((stringp = strchr(buffer, '\r')) || (stringp = strchr(buffer, '\n')))
95                 *stringp = '\0';
96
97         stringp = buffer;
98         while ((pair = strsep(&stringp, "&"))) {
99                 key = strsep(&pair, "=");
100                 ast_uri_decode(key);
101                 if (pair)
102                         ast_uri_decode(pair);
103
104                 if (!ast_strlen_zero(key)) {
105                         if (prev) {
106                                 prev->next = ast_variable_new(key, S_OR(pair, ""), "");
107                                 if (prev->next)
108                                         prev = prev->next;
109                         } else 
110                                 prev = var = ast_variable_new(key, S_OR(pair, ""), "");
111                 }
112         }
113
114         ast_free(buffer);
115         ast_free(query);
116         return var;
117 }
118
119 /*!
120  * \brief Excute an Select query and return ast_config list
121  * \param url
122  * \param unused
123  * \param ap list containing one or more field/operator/value set.
124  *
125  * \retval struct ast_config pointer on success
126  * \retval NULL on failure
127 */
128 static struct ast_config *realtime_multi_curl(const char *url, const char *unused, va_list ap)
129 {
130         struct ast_str *query;
131         char buf1[200], buf2[200];
132         const char *newparam, *newval;
133         char *stringp, *line, *pair, *key, *initfield = NULL;
134         int i;
135         const int EncodeSpecialChars = 1, bufsize = 256000;
136         struct ast_variable *var=NULL;
137         struct ast_config *cfg=NULL;
138         struct ast_category *cat=NULL;
139         char *buffer;
140
141         if (!ast_custom_function_find("CURL")) {
142                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
143                 return NULL;
144         }
145
146         if (!(query = ast_str_create(1000)))
147                 return NULL;
148
149         if (!(buffer = ast_malloc(bufsize))) {
150                 ast_free(query);
151                 return NULL;
152         }
153
154         ast_str_set(&query, 0, "${CURL(%s/multi,", url);
155
156         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
157                 newval = va_arg(ap, const char *);
158                 if (i == 0) {
159                         char *op;
160                         initfield = ast_strdupa(newparam);
161                         if ((op = strchr(initfield, ' ')))
162                                 *op = '\0';
163                 }
164                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
165                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
166                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
167         }
168         va_end(ap);
169
170         ast_str_append(&query, 0, ")}");
171
172         /* Do the CURL query */
173         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
174
175         if (!(cfg = ast_config_new()))
176                 goto exit_multi;
177
178         /* Line oriented output */
179         stringp = buffer;
180         while ((line = strsep(&stringp, "\r\n"))) {
181                 if (ast_strlen_zero(line))
182                         continue;
183
184                 if (!(cat = ast_category_new("", "", 99999)))
185                         continue;
186
187                 while ((pair = strsep(&line, "&"))) {
188                         key = strsep(&pair, "=");
189                         ast_uri_decode(key);
190                         if (pair)
191                                 ast_uri_decode(pair);
192
193                         if (!strcasecmp(key, initfield) && pair)
194                                 ast_category_rename(cat, pair);
195
196                         if (!ast_strlen_zero(key)) {
197                                 var = ast_variable_new(key, S_OR(pair, ""), "");
198                                 ast_variable_append(cat, var);
199                         }
200                 }
201                 ast_category_append(cfg, cat);
202         }
203
204 exit_multi:
205         ast_free(buffer);
206         ast_free(query);
207         return cfg;
208 }
209
210 /*!
211  * \brief Execute an UPDATE query
212  * \param url
213  * \param unused
214  * \param keyfield where clause field
215  * \param lookup value of field for where clause
216  * \param ap list containing one or more field/value set(s).
217  *
218  * Update a database table, prepare the sql statement using keyfield and lookup
219  * control the number of records to change. All values to be changed are stored in ap list.
220  * Sub-in the values to the prepared statement and execute it.
221  *
222  * \retval number of rows affected
223  * \retval -1 on failure
224 */
225 static int update_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap)
226 {
227         struct ast_str *query;
228         char buf1[200], buf2[200];
229         const char *newparam, *newval;
230         char *stringp;
231         int i, rowcount = -1;
232         const int EncodeSpecialChars = 1, bufsize = 100;
233         char *buffer;
234
235         if (!ast_custom_function_find("CURL")) {
236                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
237                 return -1;
238         }
239
240         if (!(query = ast_str_create(1000)))
241                 return -1;
242
243         if (!(buffer = ast_malloc(bufsize))) {
244                 ast_free(query);
245                 return -1;
246         }
247
248         ast_uri_encode(keyfield, buf1, sizeof(buf1), EncodeSpecialChars);
249         ast_uri_encode(lookup, buf2, sizeof(buf2), EncodeSpecialChars);
250         ast_str_set(&query, 0, "${CURL(%s/update?%s=%s,", url, buf1, buf2);
251
252         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
253                 newval = va_arg(ap, const char *);
254                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
255                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
256                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
257         }
258         va_end(ap);
259
260         ast_str_append(&query, 0, ")}");
261         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
262
263         /* Line oriented output */
264         stringp = buffer;
265         while (*stringp <= ' ')
266                 stringp++;
267         sscanf(stringp, "%d", &rowcount);
268
269         ast_free(buffer);
270         ast_free(query);
271
272         if (rowcount >= 0)
273                 return (int)rowcount;
274
275         return -1;
276 }
277
278 static int update2_curl(const char *url, const char *unused, va_list ap)
279 {
280         struct ast_str *query;
281         char buf1[200], buf2[200];
282         const char *newparam, *newval;
283         char *stringp;
284         int rowcount = -1, lookup = 1, first = 1;
285         const int EncodeSpecialChars = 1, bufsize = 100;
286         char *buffer;
287
288         if (!ast_custom_function_find("CURL")) {
289                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
290                 return -1;
291         }
292
293         if (!(query = ast_str_create(1000)))
294                 return -1;
295
296         if (!(buffer = ast_malloc(bufsize))) {
297                 ast_free(query);
298                 return -1;
299         }
300
301         ast_str_set(&query, 0, "${CURL(%s/update?", url);
302
303         for (;;) {
304                 if ((newparam = va_arg(ap, const char *)) == SENTINEL) {
305                         if (lookup) {
306                                 lookup = 0;
307                                 ast_str_append(&query, 0, ",");
308                                 /* Back to the first parameter; we don't need a starting '&' */
309                                 first = 1;
310                                 continue;
311                         } else {
312                                 break;
313                         }
314                 }
315                 newval = va_arg(ap, const char *);
316                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
317                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
318                 ast_str_append(&query, 0, "%s%s=%s", first ? "" : "&", buf1, buf2);
319         }
320         va_end(ap);
321
322         ast_str_append(&query, 0, ")}");
323         /* TODO: Make proxies work */
324         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
325
326         /* Line oriented output */
327         stringp = buffer;
328         while (*stringp <= ' ')
329                 stringp++;
330         sscanf(stringp, "%d", &rowcount);
331
332         ast_free(buffer);
333         ast_free(query);
334
335         if (rowcount >= 0)
336                 return (int)rowcount;
337
338         return -1;
339 }
340
341 /*!
342  * \brief Execute an INSERT query
343  * \param url
344  * \param unused
345  * \param ap list containing one or more field/value set(s)
346  *
347  * Insert a new record into database table, prepare the sql statement.
348  * All values to be changed are stored in ap list.
349  * Sub-in the values to the prepared statement and execute it.
350  *
351  * \retval number of rows affected
352  * \retval -1 on failure
353 */
354 static int store_curl(const char *url, const char *unused, va_list ap)
355 {
356         struct ast_str *query;
357         char buf1[200], buf2[200];
358         const char *newparam, *newval;
359         char *stringp;
360         int i, rowcount = -1;
361         const int EncodeSpecialChars = 1, bufsize = 100;
362         char *buffer;
363
364         if (!ast_custom_function_find("CURL")) {
365                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
366                 return -1;
367         }
368
369         if (!(query = ast_str_create(1000)))
370                 return -1;
371
372         if (!(buffer = ast_malloc(bufsize))) {
373                 ast_free(query);
374                 return -1;
375         }
376
377         ast_str_set(&query, 0, "${CURL(%s/store,", url);
378
379         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
380                 newval = va_arg(ap, const char *);
381                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
382                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
383                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
384         }
385         va_end(ap);
386
387         ast_str_append(&query, 0, ")}");
388         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
389
390         stringp = buffer;
391         while (*stringp <= ' ')
392                 stringp++;
393         sscanf(stringp, "%d", &rowcount);
394
395         ast_free(buffer);
396         ast_free(query);
397
398         if (rowcount >= 0)
399                 return (int)rowcount;
400
401         return -1;
402 }
403
404 /*!
405  * \brief Execute an DELETE query
406  * \param url
407  * \param unused
408  * \param keyfield where clause field
409  * \param lookup value of field for where clause
410  * \param ap list containing one or more field/value set(s)
411  *
412  * Delete a row from a database table, prepare the sql statement using keyfield and lookup
413  * control the number of records to change. Additional params to match rows are stored in ap list.
414  * Sub-in the values to the prepared statement and execute it.
415  *
416  * \retval number of rows affected
417  * \retval -1 on failure
418 */
419 static int destroy_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap)
420 {
421         struct ast_str *query;
422         char buf1[200], buf2[200];
423         const char *newparam, *newval;
424         char *stringp;
425         int i, rowcount = -1;
426         const int EncodeSpecialChars = 1, bufsize = 100;
427         char *buffer;
428
429         if (!ast_custom_function_find("CURL")) {
430                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
431                 return -1;
432         }
433
434         if (!(query = ast_str_create(1000)))
435                 return -1;
436
437         if (!(buffer = ast_malloc(bufsize))) {
438                 ast_free(query);
439                 return -1;
440         }
441
442         ast_uri_encode(keyfield, buf1, sizeof(buf1), EncodeSpecialChars);
443         ast_uri_encode(lookup, buf2, sizeof(buf2), EncodeSpecialChars);
444         ast_str_set(&query, 0, "${CURL(%s/destroy,%s=%s&", url, buf1, buf2);
445
446         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
447                 newval = va_arg(ap, const char *);
448                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
449                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
450                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
451         }
452         va_end(ap);
453
454         ast_str_append(&query, 0, ")}");
455         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
456
457         /* Line oriented output */
458         stringp = buffer;
459         while (*stringp <= ' ')
460                 stringp++;
461         sscanf(stringp, "%d", &rowcount);
462
463         ast_free(buffer);
464         ast_free(query);
465
466         if (rowcount >= 0)
467                 return (int)rowcount;
468
469         return -1;
470 }
471
472 static int require_curl(const char *url, const char *unused, va_list ap)
473 {
474         struct ast_str *query;
475         char *elm, field[256], buffer[128];
476         int type, size;
477         const int EncodeSpecialChars = 1;
478
479         if (!ast_custom_function_find("CURL")) {
480                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
481                 return -1;
482         }
483
484         if (!(query = ast_str_create(100))) {
485                 return -1;
486         }
487
488         ast_str_set(&query, 0, "${CURL(%s/require,", url);
489
490         while ((elm = va_arg(ap, char *))) {
491                 type = va_arg(ap, require_type);
492                 size = va_arg(ap, int);
493                 ast_uri_encode(elm, field, sizeof(field), EncodeSpecialChars);
494                 ast_str_append(&query, 0, "%s=%s%%3A%d", field,
495                         type == RQ_CHAR ? "char" :
496                         type == RQ_INTEGER1 ? "integer1" :
497                         type == RQ_UINTEGER1 ? "uinteger1" :
498                         type == RQ_INTEGER2 ? "integer2" :
499                         type == RQ_UINTEGER2 ? "uinteger2" :
500                         type == RQ_INTEGER3 ? "integer3" :
501                         type == RQ_UINTEGER3 ? "uinteger3" :
502                         type == RQ_INTEGER4 ? "integer4" :
503                         type == RQ_UINTEGER4 ? "uinteger4" :
504                         type == RQ_INTEGER8 ? "integer8" :
505                         type == RQ_UINTEGER8 ? "uinteger8" :
506                         type == RQ_DATE ? "date" :
507                         type == RQ_DATETIME ? "datetime" :
508                         type == RQ_FLOAT ? "float" :
509                         "unknown", size);
510         }
511         va_end(ap);
512
513         ast_str_append(&query, 0, ")}");
514         pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer));
515         return atoi(buffer);
516 }
517
518 static struct ast_config *config_curl(const char *url, const char *unused, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl, const char *who_asked)
519 {
520         struct ast_str *query;
521         char buf1[200];
522         char *stringp, *line, *pair, *key;
523         const int EncodeSpecialChars = 1, bufsize = 256000;
524         int last_cat_metric = -1, cat_metric = -1;
525         struct ast_category *cat=NULL;
526         char *buffer, *cur_cat = "";
527         char *category = "", *var_name = "", *var_val = "";
528         struct ast_flags loader_flags = { 0 };
529
530         if (!ast_custom_function_find("CURL")) {
531                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
532                 return NULL;
533         }
534
535         if (!(query = ast_str_create(1000)))
536                 return NULL;
537
538         if (!(buffer = ast_malloc(bufsize))) {
539                 ast_free(query);
540                 return NULL;
541         }
542
543         ast_uri_encode(file, buf1, sizeof(buf1), EncodeSpecialChars);
544         ast_str_set(&query, 0, "${CURL(%s/static?file=%s)}", url, buf1);
545
546         /* Do the CURL query */
547         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
548
549         /* Line oriented output */
550         stringp = buffer;
551         cat = ast_config_get_current_category(cfg);
552
553         while ((line = strsep(&stringp, "\r\n"))) {
554                 if (ast_strlen_zero(line))
555                         continue;
556
557                 while ((pair = strsep(&line, "&"))) {
558                         key = strsep(&pair, "=");
559                         ast_uri_decode(key);
560                         if (pair)
561                                 ast_uri_decode(pair);
562
563                         if (!strcasecmp(key, "category"))
564                                 category = S_OR(pair, "");
565                         else if (!strcasecmp(key, "var_name"))
566                                 var_name = S_OR(pair, "");
567                         else if (!strcasecmp(key, "var_val"))
568                                 var_val = S_OR(pair, "");
569                         else if (!strcasecmp(key, "cat_metric"))
570                                 cat_metric = pair ? atoi(pair) : 0;
571                 }
572
573                 if (!strcmp(var_name, "#include")) {
574                         if (!ast_config_internal_load(var_val, cfg, loader_flags, "", who_asked))
575                                 return NULL;
576                 }
577
578                 if (strcmp(category, cur_cat) || last_cat_metric != cat_metric) {
579                         if (!(cat = ast_category_new(category, "", 99999)))
580                                 break;
581                         cur_cat = category;
582                         last_cat_metric = cat_metric;
583                         ast_category_append(cfg, cat);
584                 }
585                 ast_variable_append(cat, ast_variable_new(var_name, var_val, ""));
586         }
587
588         ast_free(buffer);
589         ast_free(query);
590         return cfg;
591 }
592
593 static struct ast_config_engine curl_engine = {
594         .name = "curl",
595         .load_func = config_curl,
596         .realtime_func = realtime_curl,
597         .realtime_multi_func = realtime_multi_curl,
598         .store_func = store_curl,
599         .destroy_func = destroy_curl,
600         .update_func = update_curl,
601         .update2_func = update2_curl,
602         .require_func = require_curl,
603 };
604
605 static int unload_module(void)
606 {
607         ast_config_engine_deregister(&curl_engine);
608         ast_verb(1, "res_config_curl unloaded.\n");
609         return 0;
610 }
611
612 static int load_module(void)
613 {
614         if (!ast_module_check("res_curl.so")) {
615                 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
616                         ast_log(LOG_ERROR, "Cannot load res_curl, so res_config_curl cannot be loaded\n");
617                         return AST_MODULE_LOAD_DECLINE;
618                 }
619         }
620
621         ast_config_engine_register(&curl_engine);
622         ast_verb(1, "res_config_curl loaded.\n");
623         return 0;
624 }
625
626 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Realtime Curl configuration");