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