Merge "res_pjsip: New endpoint option "refer_blind_progress""
[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                 cat = ast_category_new_anonymous();
186                 if (!cat) {
187                         continue;
188                 }
189
190                 while ((pair = strsep(&line, "&"))) {
191                         key = strsep(&pair, "=");
192                         ast_uri_decode(key, ast_uri_http);
193                         if (pair) {
194                                 ast_uri_decode(pair, ast_uri_http);
195                         }
196
197                         if (!strcasecmp(key, initfield) && pair) {
198                                 ast_category_rename(cat, pair);
199                         }
200
201                         if (!ast_strlen_zero(key)) {
202                                 var = ast_variable_new(key, S_OR(pair, ""), "");
203                                 ast_variable_append(cat, var);
204                         }
205                 }
206                 ast_category_append(cfg, cat);
207         }
208
209         return cfg;
210 }
211
212 /*!
213  * \brief Execute an UPDATE query
214  * \param url
215  * \param unused
216  * \param keyfield where clause field
217  * \param lookup value of field for where clause
218  * \param fields list containing one or more field/value set(s).
219  *
220  * Update a database table, prepare the sql statement using keyfield and lookup
221  * control the number of records to change. All values to be changed are stored in ap list.
222  * Sub-in the values to the prepared statement and execute it.
223  *
224  * \retval number of rows affected
225  * \retval -1 on failure
226 */
227 static int update_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, const struct ast_variable *fields)
228 {
229         struct ast_str *query, *buffer;
230         char buf1[256], buf2[256];
231         const struct ast_variable *field;
232         char *stringp;
233         int start = 1, rowcount = -1;
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_thread_get(&query_buf, 16))) {
241                 return -1;
242         }
243
244         if (!(buffer = ast_str_thread_get(&result_buf, 16))) {
245                 return -1;
246         }
247
248         ast_uri_encode(keyfield, buf1, sizeof(buf1), ast_uri_http);
249         ast_uri_encode(lookup, buf2, sizeof(buf2), ast_uri_http);
250         ast_str_set(&query, 0, "${CURL(%s/update?%s=%s,", url, buf1, buf2);
251
252         for (field = fields; field; field = field->next) {
253                 ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http);
254                 ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http);
255                 ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2);
256                 start = 0;
257         }
258
259         ast_str_append(&query, 0, ")}");
260         ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
261
262         /* Line oriented output */
263         stringp = ast_str_buffer(buffer);
264         while (*stringp <= ' ') {
265                 stringp++;
266         }
267         sscanf(stringp, "%30d", &rowcount);
268
269         if (rowcount >= 0) {
270                 return (int)rowcount;
271         }
272
273         return -1;
274 }
275
276 static int update2_curl(const char *url, const char *unused, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
277 {
278         struct ast_str *query, *buffer;
279         char buf1[200], buf2[200];
280         const struct ast_variable *field;
281         char *stringp;
282         unsigned int start = 1;
283         int rowcount = -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 (field = lookup_fields; field; field = field->next) {
300                 ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http);
301                 ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http);
302                 ast_str_append(&query, 0, "%s%s=%s", !start ? "" : "&", buf1, buf2);
303                 start = 0;
304         }
305         ast_str_append(&query, 0, ",");
306         start = 1;
307
308         for (field = update_fields; field; field = field->next) {
309                 ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http);
310                 ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http);
311                 ast_str_append(&query, 0, "%s%s=%s", !start ? "" : "&", buf1, buf2);
312                 start = 0;
313         }
314
315         ast_str_append(&query, 0, ")}");
316         /* Proxies work, by setting CURLOPT options in the [globals] section of
317          * extensions.conf.  Unfortunately, this means preloading pbx_config.so
318          * so that they have an opportunity to be set prior to startup realtime
319          * queries. */
320         ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
321
322         /* Line oriented output */
323         stringp = ast_str_buffer(buffer);
324         while (*stringp <= ' ') {
325                 stringp++;
326         }
327         sscanf(stringp, "%30d", &rowcount);
328
329         if (rowcount >= 0) {
330                 return (int)rowcount;
331         }
332
333         return -1;
334 }
335
336 /*!
337  * \brief Execute an INSERT query
338  * \param url
339  * \param unused
340  * \param fields list containing one or more field/value set(s)
341  *
342  * Insert a new record into database table, prepare the sql statement.
343  * All values to be changed are stored in ap list.
344  * Sub-in the values to the prepared statement and execute it.
345  *
346  * \retval number of rows affected
347  * \retval -1 on failure
348 */
349 static int store_curl(const char *url, const char *unused, const struct ast_variable *fields)
350 {
351         struct ast_str *query, *buffer;
352         char buf1[256], buf2[256];
353         const struct ast_variable *field;
354         char *stringp;
355         int start = 1, rowcount = -1;
356
357         if (!ast_custom_function_find("CURL")) {
358                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
359                 return -1;
360         }
361
362         if (!(query = ast_str_thread_get(&query_buf, 1000))) {
363                 return -1;
364         }
365
366         if (!(buffer = ast_str_thread_get(&result_buf, 16))) {
367                 return -1;
368         }
369
370         ast_str_set(&query, 0, "${CURL(%s/store,", url);
371
372         for (field = fields; field; field = field->next) {
373                 ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http);
374                 ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http);
375                 ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2);
376                 start = 0;
377         }
378
379         ast_str_append(&query, 0, ")}");
380         ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
381
382         stringp = ast_str_buffer(buffer);
383         while (*stringp <= ' ') {
384                 stringp++;
385         }
386         sscanf(stringp, "%30d", &rowcount);
387
388         if (rowcount >= 0) {
389                 return rowcount;
390         }
391
392         return -1;
393 }
394
395 /*!
396  * \brief Execute an DELETE query
397  * \param url
398  * \param unused
399  * \param keyfield where clause field
400  * \param lookup value of field for where clause
401  * \param fields list containing one or more field/value set(s)
402  *
403  * Delete a row from a database table, prepare the sql statement using keyfield and lookup
404  * control the number of records to change. Additional params to match rows are stored in ap list.
405  * Sub-in the values to the prepared statement and execute it.
406  *
407  * \retval number of rows affected
408  * \retval -1 on failure
409 */
410 static int destroy_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, const struct ast_variable *fields)
411 {
412         struct ast_str *query, *buffer;
413         char buf1[200], buf2[200];
414         const struct ast_variable *field;
415         char *stringp;
416         int start = 1, rowcount = -1;
417
418         if (!ast_custom_function_find("CURL")) {
419                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
420                 return -1;
421         }
422
423         if (!(query = ast_str_thread_get(&query_buf, 1000))) {
424                 return -1;
425         }
426
427         if (!(buffer = ast_str_thread_get(&result_buf, 16))) {
428                 return -1;
429         }
430
431         ast_uri_encode(keyfield, buf1, sizeof(buf1), ast_uri_http);
432         ast_uri_encode(lookup, buf2, sizeof(buf2), ast_uri_http);
433         ast_str_set(&query, 0, "${CURL(%s/destroy,%s=%s&", url, buf1, buf2);
434
435         for (field = fields; field; field = field->next) {
436                 ast_uri_encode(field->name, buf1, sizeof(buf1), ast_uri_http);
437                 ast_uri_encode(field->value, buf2, sizeof(buf2), ast_uri_http);
438                 ast_str_append(&query, 0, "%s%s=%s", !start ? "&" : "", buf1, buf2);
439                 start = 0;
440         }
441
442         ast_str_append(&query, 0, ")}");
443         ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
444
445         /* Line oriented output */
446         stringp = ast_str_buffer(buffer);
447         while (*stringp <= ' ') {
448                 stringp++;
449         }
450         sscanf(stringp, "%30d", &rowcount);
451
452         if (rowcount >= 0) {
453                 return (int)rowcount;
454         }
455
456         return -1;
457 }
458
459 static int require_curl(const char *url, const char *unused, va_list ap)
460 {
461         struct ast_str *query, *buffer;
462         char *elm, field[256];
463         int type, size, i = 0;
464
465         if (!ast_custom_function_find("CURL")) {
466                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
467                 return -1;
468         }
469
470         if (!(query = ast_str_thread_get(&query_buf, 100))) {
471                 return -1;
472         }
473
474         if (!(buffer = ast_str_thread_get(&result_buf, 16))) {
475                 return -1;
476         }
477
478         ast_str_set(&query, 0, "${CURL(%s/require,", url);
479
480         while ((elm = va_arg(ap, char *))) {
481                 type = va_arg(ap, require_type);
482                 size = va_arg(ap, int);
483                 ast_uri_encode(elm, field, sizeof(field), ast_uri_http);
484                 ast_str_append(&query, 0, "%s%s=%s%%3A%d",
485                         i > 0 ? "&" : "",
486                         field,
487                         type == RQ_CHAR ? "char" :
488                         type == RQ_INTEGER1 ? "integer1" :
489                         type == RQ_UINTEGER1 ? "uinteger1" :
490                         type == RQ_INTEGER2 ? "integer2" :
491                         type == RQ_UINTEGER2 ? "uinteger2" :
492                         type == RQ_INTEGER3 ? "integer3" :
493                         type == RQ_UINTEGER3 ? "uinteger3" :
494                         type == RQ_INTEGER4 ? "integer4" :
495                         type == RQ_UINTEGER4 ? "uinteger4" :
496                         type == RQ_INTEGER8 ? "integer8" :
497                         type == RQ_UINTEGER8 ? "uinteger8" :
498                         type == RQ_DATE ? "date" :
499                         type == RQ_DATETIME ? "datetime" :
500                         type == RQ_FLOAT ? "float" :
501                         "unknown", size);
502                 i++;
503         }
504
505         ast_str_append(&query, 0, ")}");
506         ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
507         return atoi(ast_str_buffer(buffer));
508 }
509
510 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)
511 {
512         struct ast_str *query, *buffer;
513         char buf1[200];
514         char *stringp, *line, *pair, *key;
515         int last_cat_metric = -1, cat_metric = -1;
516         struct ast_category *cat = NULL;
517         char *cur_cat = "";
518         char *category = "", *var_name = "", *var_val = "";
519         struct ast_flags loader_flags = { 0 };
520
521         if (!ast_custom_function_find("CURL")) {
522                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
523                 return NULL;
524         }
525
526         if (!(query = ast_str_thread_get(&query_buf, 100))) {
527                 return NULL;
528         }
529
530         if (!(buffer = ast_str_thread_get(&result_buf, 16))) {
531                 return NULL;
532         }
533
534         ast_uri_encode(file, buf1, sizeof(buf1), ast_uri_http);
535         ast_str_set(&query, 0, "${CURL(%s/static?file=%s)}", url, buf1);
536
537         /* Do the CURL query */
538         ast_str_substitute_variables(&buffer, 0, NULL, ast_str_buffer(query));
539
540         /* Line oriented output */
541         stringp = ast_str_buffer(buffer);
542         cat = ast_config_get_current_category(cfg);
543
544         while ((line = strsep(&stringp, "\r\n"))) {
545                 if (ast_strlen_zero(line)) {
546                         continue;
547                 }
548
549                 while ((pair = strsep(&line, "&"))) {
550                         key = strsep(&pair, "=");
551                         ast_uri_decode(key, ast_uri_http);
552                         if (pair) {
553                                 ast_uri_decode(pair, ast_uri_http);
554                         }
555
556                         if (!strcasecmp(key, "category")) {
557                                 category = S_OR(pair, "");
558                         } else if (!strcasecmp(key, "var_name")) {
559                                 var_name = S_OR(pair, "");
560                         } else if (!strcasecmp(key, "var_val")) {
561                                 var_val = S_OR(pair, "");
562                         } else if (!strcasecmp(key, "cat_metric")) {
563                                 cat_metric = pair ? atoi(pair) : 0;
564                         }
565                 }
566
567                 if (!strcmp(var_name, "#include")) {
568                         if (!ast_config_internal_load(var_val, cfg, loader_flags, "", who_asked))
569                                 return NULL;
570                 }
571
572                 if (!cat || strcmp(category, cur_cat) || last_cat_metric != cat_metric) {
573                         cat = ast_category_new_dynamic(category);
574                         if (!cat) {
575                                 break;
576                         }
577                         cur_cat = category;
578                         last_cat_metric = cat_metric;
579                         ast_category_append(cfg, cat);
580                 }
581                 ast_variable_append(cat, ast_variable_new(var_name, var_val, ""));
582         }
583
584         return cfg;
585 }
586
587 static struct ast_config_engine curl_engine = {
588         .name = "curl",
589         .load_func = config_curl,
590         .realtime_func = realtime_curl,
591         .realtime_multi_func = realtime_multi_curl,
592         .store_func = store_curl,
593         .destroy_func = destroy_curl,
594         .update_func = update_curl,
595         .update2_func = update2_curl,
596         .require_func = require_curl,
597 };
598
599 static int reload_module(void)
600 {
601         struct ast_flags flags = { CONFIG_FLAG_NOREALTIME };
602         struct ast_config *cfg;
603         struct ast_variable *var;
604
605         if (!(cfg = ast_config_load("res_curl.conf", flags))) {
606                 return 0;
607         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
608                 ast_log(LOG_WARNING, "res_curl.conf could not be parsed!\n");
609                 return 0;
610         }
611
612         if (!(var = ast_variable_browse(cfg, "globals")) && !(var = ast_variable_browse(cfg, "global")) && !(var = ast_variable_browse(cfg, "general"))) {
613                 ast_log(LOG_WARNING, "[globals] not found in res_curl.conf\n");
614                 ast_config_destroy(cfg);
615                 return 0;
616         }
617
618         for (; var; var = var->next) {
619                 if (strncmp(var->name, "CURLOPT(", 8)) {
620                         char name[256];
621                         snprintf(name, sizeof(name), "CURLOPT(%s)", var->name);
622                         pbx_builtin_setvar_helper(NULL, name, var->value);
623                 } else {
624                         pbx_builtin_setvar_helper(NULL, var->name, var->value);
625                 }
626         }
627         ast_config_destroy(cfg);
628         return 0;
629 }
630
631 static int unload_module(void)
632 {
633         ast_config_engine_deregister(&curl_engine);
634
635         return 0;
636 }
637
638 static int load_module(void)
639 {
640         if (!ast_module_check("res_curl.so")) {
641                 if (ast_load_resource("res_curl.so") != AST_MODULE_LOAD_SUCCESS) {
642                         ast_log(LOG_ERROR, "Cannot load res_curl, so res_config_curl cannot be loaded\n");
643                         return AST_MODULE_LOAD_DECLINE;
644                 }
645         }
646
647         if (!ast_module_check("func_curl.so")) {
648                 if (ast_load_resource("func_curl.so") != AST_MODULE_LOAD_SUCCESS) {
649                         ast_log(LOG_ERROR, "Cannot load func_curl, so res_config_curl cannot be loaded\n");
650                         return AST_MODULE_LOAD_DECLINE;
651                 }
652         }
653
654         reload_module();
655
656         ast_config_engine_register(&curl_engine);
657
658         return 0;
659 }
660
661 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Realtime Curl configuration",
662         .support_level = AST_MODULE_SUPPORT_CORE,
663         .load = load_module,
664         .unload = unload_module,
665         .reload = reload_module,
666         .load_pri = AST_MODPRI_REALTIME_DRIVER,
667 );