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