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