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