Oops, a sizeof error
[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,", 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                         initfield = ast_strdupa(newparam);
160                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
161                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
162                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
163         }
164         va_end(ap);
165
166         ast_str_append(&query, 0, ")}");
167
168         /* Do the CURL query */
169         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
170
171         if (!(cfg = ast_config_new()))
172                 goto exit_multi;
173
174         /* Line oriented output */
175         stringp = buffer;
176         while ((line = strsep(&stringp, "\r\n"))) {
177                 if (ast_strlen_zero(line))
178                         continue;
179
180                 if (!(cat = ast_category_new("", "", 99999)))
181                         continue;
182
183                 while ((pair = strsep(&line, "&"))) {
184                         key = strsep(&pair, "=");
185                         ast_uri_decode(key);
186                         if (pair)
187                                 ast_uri_decode(pair);
188
189                         if (!strcasecmp(key, initfield) && pair)
190                                 ast_category_rename(cat, pair);
191
192                         if (!ast_strlen_zero(key)) {
193                                 var = ast_variable_new(key, S_OR(pair, ""), "");
194                                 ast_variable_append(cat, var);
195                         }
196                 }
197                 ast_category_append(cfg, cat);
198         }
199
200 exit_multi:
201         ast_free(buffer);
202         ast_free(query);
203         return cfg;
204 }
205
206 /*!
207  * \brief Execute an UPDATE query
208  * \param url
209  * \param unused
210  * \param keyfield where clause field
211  * \param lookup value of field for where clause
212  * \param ap list containing one or more field/value set(s).
213  *
214  * Update a database table, prepare the sql statement using keyfield and lookup
215  * control the number of records to change. All values to be changed are stored in ap list.
216  * Sub-in the values to the prepared statement and execute it.
217  *
218  * \retval number of rows affected
219  * \retval -1 on failure
220 */
221 static int update_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap)
222 {
223         struct ast_str *query;
224         char buf1[200], buf2[200];
225         const char *newparam, *newval;
226         char *stringp;
227         int i, rowcount = -1;
228         const int EncodeSpecialChars = 1, bufsize = 100;
229         char *buffer;
230
231         if (!ast_custom_function_find("CURL")) {
232                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
233                 return -1;
234         }
235
236         if (!(query = ast_str_create(1000)))
237                 return -1;
238
239         if (!(buffer = ast_malloc(bufsize))) {
240                 ast_free(query);
241                 return -1;
242         }
243
244         ast_uri_encode(keyfield, buf1, sizeof(buf1), EncodeSpecialChars);
245         ast_uri_encode(lookup, buf2, sizeof(buf2), EncodeSpecialChars);
246         ast_str_set(&query, 0, "${CURL(%s/update?%s=%s,", url, buf1, buf2);
247
248         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
249                 newval = va_arg(ap, const char *);
250                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
251                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
252                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
253         }
254         va_end(ap);
255
256         ast_str_append(&query, 0, ")}");
257         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
258
259         /* Line oriented output */
260         stringp = buffer;
261         while (*stringp <= ' ')
262                 stringp++;
263         sscanf(stringp, "%d", &rowcount);
264
265         ast_free(buffer);
266         ast_free(query);
267
268         if (rowcount >= 0)
269                 return (int)rowcount;
270
271         return -1;
272 }
273
274 /*!
275  * \brief Execute an INSERT query
276  * \param database
277  * \param table
278  * \param ap list containing one or more field/value set(s)
279  *
280  * Insert a new record into database table, prepare the sql statement.
281  * All values to be changed are stored in ap list.
282  * Sub-in the values to the prepared statement and execute it.
283  *
284  * \retval number of rows affected
285  * \retval -1 on failure
286 */
287 static int store_curl(const char *url, const char *unused, va_list ap)
288 {
289         struct ast_str *query;
290         char buf1[200], buf2[200];
291         const char *newparam, *newval;
292         char *stringp;
293         int i, rowcount = -1;
294         const int EncodeSpecialChars = 1, bufsize = 100;
295         char *buffer;
296
297         if (!ast_custom_function_find("CURL")) {
298                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
299                 return -1;
300         }
301
302         if (!(query = ast_str_create(1000)))
303                 return -1;
304
305         if (!(buffer = ast_malloc(bufsize))) {
306                 ast_free(query);
307                 return -1;
308         }
309
310         ast_str_set(&query, 0, "${CURL(%s/store,", url);
311
312         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
313                 newval = va_arg(ap, const char *);
314                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
315                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
316                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
317         }
318         va_end(ap);
319
320         ast_str_append(&query, 0, ")}");
321         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
322
323         stringp = buffer;
324         while (*stringp <= ' ')
325                 stringp++;
326         sscanf(stringp, "%d", &rowcount);
327
328         ast_free(buffer);
329         ast_free(query);
330
331         if (rowcount >= 0)
332                 return (int)rowcount;
333
334         return -1;
335 }
336
337 /*!
338  * \brief Execute an DELETE query
339  * \param url
340  * \param unused
341  * \param keyfield where clause field
342  * \param lookup value of field for where clause
343  * \param ap list containing one or more field/value set(s)
344  *
345  * Delete a row from a database table, prepare the sql statement using keyfield and lookup
346  * control the number of records to change. Additional params to match rows are stored in ap list.
347  * Sub-in the values to the prepared statement and execute it.
348  *
349  * \retval number of rows affected
350  * \retval -1 on failure
351 */
352 static int destroy_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap)
353 {
354         struct ast_str *query;
355         char buf1[200], buf2[200];
356         const char *newparam, *newval;
357         char *stringp;
358         int i, rowcount = -1;
359         const int EncodeSpecialChars = 1, bufsize = 100;
360         char *buffer;
361
362         if (!ast_custom_function_find("CURL")) {
363                 ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n");
364                 return -1;
365         }
366
367         if (!(query = ast_str_create(1000)))
368                 return -1;
369
370         if (!(buffer = ast_malloc(bufsize))) {
371                 ast_free(query);
372                 return -1;
373         }
374
375         ast_uri_encode(keyfield, buf1, sizeof(buf1), EncodeSpecialChars);
376         ast_uri_encode(lookup, buf2, sizeof(buf2), EncodeSpecialChars);
377         ast_str_set(&query, 0, "${CURL(%s/destroy,%s=%s&", url, buf1, buf2);
378
379         for (i = 0; (newparam = va_arg(ap, const char *)); i++) {
380                 newval = va_arg(ap, const char *);
381                 ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars);
382                 ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars);
383                 ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2);
384         }
385         va_end(ap);
386
387         ast_str_append(&query, 0, ")}");
388         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
389
390         /* Line oriented output */
391         stringp = buffer;
392         while (*stringp <= ' ')
393                 stringp++;
394         sscanf(stringp, "%d", &rowcount);
395
396         ast_free(buffer);
397         ast_free(query);
398
399         if (rowcount >= 0)
400                 return (int)rowcount;
401
402         return -1;
403 }
404
405
406 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)
407 {
408         struct ast_str *query;
409         char buf1[200];
410         char *stringp, *line, *pair, *key;
411         const int EncodeSpecialChars = 1, bufsize = 256000;
412         int last_cat_metric = -1, cat_metric = -1;
413         struct ast_category *cat=NULL;
414         char *buffer, *cur_cat = "";
415         char *category = "", *var_name = "", *var_val = "";
416         struct ast_flags loader_flags = { 0 };
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 NULL;
421         }
422
423         if (!(query = ast_str_create(1000)))
424                 return NULL;
425
426         if (!(buffer = ast_malloc(bufsize))) {
427                 ast_free(query);
428                 return NULL;
429         }
430
431         ast_uri_encode(file, buf1, sizeof(buf1), EncodeSpecialChars);
432         ast_str_set(&query, 0, "${CURL(%s/static?file=%s)}", url, buf1);
433
434         /* Do the CURL query */
435         pbx_substitute_variables_helper(NULL, query->str, buffer, bufsize);
436
437         /* Line oriented output */
438         stringp = buffer;
439         cat = ast_config_get_current_category(cfg);
440
441         while ((line = strsep(&stringp, "\r\n"))) {
442                 if (ast_strlen_zero(line))
443                         continue;
444
445                 while ((pair = strsep(&line, "&"))) {
446                         key = strsep(&pair, "=");
447                         ast_uri_decode(key);
448                         if (pair)
449                                 ast_uri_decode(pair);
450
451                         if (!strcasecmp(key, "category"))
452                                 category = S_OR(pair, "");
453                         else if (!strcasecmp(key, "var_name"))
454                                 var_name = S_OR(pair, "");
455                         else if (!strcasecmp(key, "var_val"))
456                                 var_val = S_OR(pair, "");
457                         else if (!strcasecmp(key, "cat_metric"))
458                                 cat_metric = pair ? atoi(pair) : 0;
459                 }
460
461                 if (!strcmp(var_name, "#include")) {
462                         if (!ast_config_internal_load(var_val, cfg, loader_flags, ""))
463                                 return NULL;
464                 }
465
466                 if (strcmp(category, cur_cat) || last_cat_metric != cat_metric) {
467                         if (!(cat = ast_category_new(category, "", 99999)))
468                                 break;
469                         cur_cat = category;
470                         last_cat_metric = cat_metric;
471                         ast_category_append(cfg, cat);
472                 }
473                 ast_variable_append(cat, ast_variable_new(var_name, var_val, ""));
474         }
475
476         ast_free(buffer);
477         ast_free(query);
478         return cfg;
479 }
480
481 static struct ast_config_engine curl_engine = {
482         .name = "curl",
483         .load_func = config_curl,
484         .realtime_func = realtime_curl,
485         .realtime_multi_func = realtime_multi_curl,
486         .store_func = store_curl,
487         .destroy_func = destroy_curl,
488         .update_func = update_curl
489 };
490
491 static int unload_module (void)
492 {
493         ast_config_engine_deregister(&curl_engine);
494         ast_verb(1, "res_config_curl unloaded.\n");
495         return 0;
496 }
497
498 static int load_module (void)
499 {
500         ast_config_engine_register(&curl_engine);
501         ast_verb(1, "res_config_curl loaded.\n");
502         return 0;
503 }
504
505 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Realtime Curl configuration");