freeswitch plugin: Changed malloc and snprintf size to +3 instead of +2
[collectd.git] / src / freeswitch.c
1 /**
2  * collectd - src/freeswitch.c
3  * Copyright (C) 2005-2007  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  *   Leon de Rooij <leon@scarlet-internet.nl>
21  **/
22
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_match.h"
27 #include <esl.h>
28
29 #define FS_DEF_HOST "127.0.0.1"
30 #define FS_DEF_PORT "8021"
31 #define FS_DEF_PASS "ClueCon"
32
33 /*
34  *      <Plugin freeswitch>
35  *              Host "127.0.0.1"
36  *              Port "8021"
37  *              Pass "ClueCon"
38  *              <Command "api sofia status profile res-public">
39  *                      Instance "profile-sofia-res-public"
40  *                      <Match>
41  *                              Regex "\\<CALLS-IN\s+(\d+)\\>"
42  *                              DSType "CounterInc"
43  *                              Type "counter"
44  *                              Instance "calls-in"
45  *                      </Match>
46  *              </Command>
47  *      </Plugin>
48  */
49
50 /*
51  * Data types
52  */
53 struct fs_match_s;
54 typedef struct fs_match_s fs_match_t;
55 struct fs_match_s
56 {
57         char *regex;
58         int dstype;
59         char *type;
60         char *instance;
61         cu_match_t *match;
62         fs_match_t *next;
63 };
64
65 struct fs_command_s;
66 typedef struct fs_command_s fs_command_t;
67 struct fs_command_s
68 {
69         char *line;             // "api sofia status profile res-public"
70         char *instance;         // "profile-sofia-res-public"
71         char *buffer;           // <output from esl command as a char*>
72         size_t buffer_size;     // sizeof(*buffer)
73         size_t buffer_fill;     // 0 or 1
74         fs_match_t *matches;
75         fs_command_t *next;
76 };
77
78 static fs_command_t *fs_commands_g = NULL;
79
80 static char *fs_host = NULL;
81 static char *fs_port = NULL;
82 static char *fs_pass = NULL;
83
84 static esl_handle_t esl_handle = {{0}};
85 // static int thread_running = 0; // for when subscribing to esl events
86
87 /*
88  * Private functions
89  */
90
91 static int fs_config_add_string (const char *name, char **dest, oconfig_item_t *ci)
92 {
93         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
94         {
95                 WARNING ("freeswitch plugin: '%s' needs exactly one string argument.", name);
96                 return (-1);
97         }
98
99         sfree (*dest);
100         *dest = strdup (ci->values[0].value.string);
101         if (*dest == NULL)
102                 return (-1);
103
104         return (0);
105 } /* int fs_config_add_string */
106
107 static void fs_match_free (fs_match_t *fm)
108 {
109         if (fm == NULL)
110                 return;
111
112         sfree (fm->regex);
113         sfree (fm->type);
114         sfree (fm->instance);
115         match_destroy (fm->match);
116         fs_match_free (fm->next);
117         sfree (fm);
118 } /* void fs_match_free */
119
120 static void fs_command_free (fs_command_t *fc)
121 {
122         if (fc == NULL)
123                 return;
124
125         sfree (fc->line);
126         sfree (fc->instance);
127         sfree (fc->buffer);
128         fs_match_free (fc->matches);
129         fs_command_free (fc->next);
130         sfree (fc);
131 } /* void fs_command_free */
132
133 static int fs_config_add_match_dstype (int *dstype_ret, oconfig_item_t *ci)
134 {
135         DEBUG ("freeswitch plugin: in fs_config_add_match_dstype");
136
137         int dstype;
138
139         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
140         {
141                 WARNING ("freeswitch plugin: 'DSType' needs exactly one string argument.");
142                 return (-1);
143         }
144
145         if (strncasecmp ("Gauge", ci->values[0].value.string, strlen ("Gauge")) == 0)
146         {
147                 dstype = UTILS_MATCH_DS_TYPE_GAUGE;
148                 if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
149                         dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
150                 else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
151                         dstype |= UTILS_MATCH_CF_GAUGE_MIN;
152                 else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
153                         dstype |= UTILS_MATCH_CF_GAUGE_MAX;
154                 else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
155                         dstype |= UTILS_MATCH_CF_GAUGE_LAST;
156                 else
157                         dstype = 0;
158         }
159         else if (strncasecmp ("Counter", ci->values[0].value.string, strlen ("Counter")) == 0)
160         {
161                 dstype = UTILS_MATCH_DS_TYPE_COUNTER;
162                 if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
163                         dstype |= UTILS_MATCH_CF_COUNTER_SET;
164                 else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
165                         dstype |= UTILS_MATCH_CF_COUNTER_ADD;
166                 else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
167                         dstype |= UTILS_MATCH_CF_COUNTER_INC;
168                 else
169                         dstype = 0;
170         }
171         else
172         {
173                 dstype = 0;
174         }
175
176         if (dstype == 0)
177         {
178                 WARNING ("freeswitch plugin: `%s' is not a valid argument to `DSType'.",
179                 ci->values[0].value.string);
180                 return (-1);
181         }
182
183         *dstype_ret = dstype;
184         return (0);
185 } /* int fs_config_add_match_dstype */
186
187 static int fs_config_add_match (fs_command_t *fs_command, oconfig_item_t *ci)
188 {
189         DEBUG ("freeswitch plugin: in fs_config_add_match");
190
191         fs_match_t *fs_match;
192         int status;
193         int i;
194
195         if (ci->values_num != 0)
196         {
197                 WARNING ("freeswitch plugin: Ignoring arguments for the 'Match' block.");
198         }
199
200         fs_match = (fs_match_t *) malloc (sizeof (*fs_match));
201         if (fs_match == NULL)
202         {
203                 ERROR ("freeswitch plugin: malloc failed.");
204                 return (-1);
205         }
206         memset (fs_match, 0, sizeof (*fs_match));
207
208         status = 0;
209         for (i = 0; i < ci->children_num; i++)
210         {
211                 oconfig_item_t *child = ci->children + i;
212
213                 if (strcasecmp ("Regex", child->key) == 0)
214                         status = fs_config_add_string ("Regex", &fs_match->regex, child);
215                 else if (strcasecmp ("DSType", child->key) == 0)
216                         status = fs_config_add_match_dstype (&fs_match->dstype, child);
217                 else if (strcasecmp ("Type", child->key) == 0)
218                         status = fs_config_add_string ("Type", &fs_match->type, child);
219                 else if (strcasecmp ("Instance", child->key) == 0)
220                         status = fs_config_add_string ("Instance", &fs_match->instance, child);
221                 else
222                 {
223                         WARNING ("freeswitch plugin: Option `%s' not allowed here.", child->key);
224                         status = -1;
225                 }
226
227                 if (status != 0)
228                         break;
229         } /* for (i = 0; i < ci->children_num; i++) */
230
231         while (status == 0)
232         {
233                 if (fs_match->regex == NULL)
234                 {
235                         WARNING ("freeswitch plugin: `Regex' missing in `Match' block.");
236                         status = -1;
237                 }
238
239                 if (fs_match->type == NULL)
240                 {
241                         WARNING ("freeswitch plugin: `Type' missing in `Match' block.");
242                         status = -1;
243                 }
244
245                 if (fs_match->dstype == 0)
246                 {
247                         WARNING ("freeswitch plugin: `DSType' missing in `Match' block.");
248                         status = -1;
249                 }
250
251                 break;
252         } /* while (status == 0) */
253
254         if (status != 0)
255         return (status);
256
257         fs_match->match = match_create_simple (fs_match->regex, fs_match->dstype);
258         if (fs_match->match == NULL)
259         {
260                 ERROR ("freeswitch plugin: tail_match_add_match_simple failed.");
261                 fs_match_free (fs_match);
262                 return (-1);
263         }
264         else
265         {
266                 fs_match_t *prev;
267
268                 prev = fs_command->matches;
269                 while ((prev != NULL) && (prev->next != NULL))
270                         prev = prev->next;
271
272                 if (prev == NULL)
273                         fs_command->matches = fs_match;
274                 else
275                         prev->next = fs_match;
276         }
277
278         return (0);
279 } /* int fs_config_add_match */
280
281 static int fs_config_add_command (oconfig_item_t *ci)
282 {
283         DEBUG ("freeswitch plugin: in fs_config_add_command");
284
285         fs_command_t *command;
286         int status;
287         int i;
288
289         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
290         {
291                 WARNING ("freeswitch plugin: 'Command' blocks need exactly one string argument.");
292                 return (-1);
293         }
294
295         command = (fs_command_t *) malloc (sizeof (*command));
296         if (command == NULL)
297         {
298                 ERROR ("freeswitch plugin: malloc failed.");
299                 return (-1);
300         }
301         memset (command, 0, sizeof (*command));
302
303         command->line = NULL;
304         command->line = strdup (ci->values[0].value.string);
305
306         if (command->line == NULL)
307         {
308                 ERROR ("freeswitch plugin: strdup failed.");
309                 sfree (command);
310                 return (-1);
311         }
312
313         /* Process all children */
314         status = 0;
315         for (i = 0; i < ci->children_num; i++)
316         {
317                 oconfig_item_t *child = ci->children + i;
318
319                 if (strcasecmp ("Instance", child->key) == 0)
320                         status = fs_config_add_string ("Instance", &command->instance, child);
321                 else if (strcasecmp ("Match", child->key) == 0)
322                         fs_config_add_match (command, child);
323                 else
324                 {
325                         WARNING ("freeswitch plugin: Option '%s' not allowed here.", child->key);
326                         status = -1;
327                 }
328
329                 if (status != 0)
330                         break;
331         }
332
333         if (status != 0)
334         {
335                 fs_command_free (command);
336                 return (status);
337         }
338
339         /* Add the new command to the linked list */
340         if (fs_commands_g == NULL)
341                 fs_commands_g = command;
342         else
343         {
344                 fs_command_t *prev;
345
346                 prev = fs_commands_g;
347                 while ((prev != NULL) && (prev->next != NULL))
348                         prev = prev->next;
349                 prev->next = command;
350         }
351
352         return (0);
353 } /* int fs_config_add_command */
354
355 static int fs_complex_config (oconfig_item_t *ci)
356 {
357         int success;
358         int errors;
359         int status;
360         int i;
361
362         DEBUG ("freeswitch plugin: reading config");
363
364         success = 0;
365         errors = 0;
366
367         for (i = 0; i < ci->children_num; i++)
368         {
369                 oconfig_item_t *child = ci->children + i;
370
371                 if (strcasecmp ("Host", child->key) == 0)
372                 {
373                         if (fs_host != NULL) free (fs_host);
374                         fs_host = strdup(child->values[0].value.string);
375                 }
376                 else if (strcasecmp ("Port", child->key) == 0)
377                 {
378                         if (fs_port != NULL) free (fs_port);
379                         fs_port = strdup(child->values[0].value.string);
380                 }
381                 else if (strcasecmp ("Pass", child->key) == 0)
382                 {
383                         if (fs_pass != NULL) free (fs_pass);
384                         fs_pass = strdup(child->values[0].value.string);
385                 }
386                 else if (strcasecmp ("Command", child->key) == 0)
387                 {
388                         status = fs_config_add_command(child);
389                         if (status == 0)
390                                 success++;
391                         else
392                                 errors++;
393                 }
394                 else
395                 {
396                         WARNING ("freeswitch plugin: Option '%s' not allowed here.", child->key);
397                         errors++;
398                 }
399         }
400
401         if ((success == 0) && (errors > 0))
402         {
403                 ERROR ("freeswitch plugin: All statements failed.");
404                 return (-1);
405         }
406
407         return (0);
408 } /* int fs_complex_config */
409
410 static void fs_submit (const fs_command_t *fc,
411         const fs_match_t *fm, const cu_match_value_t *mv)
412 {
413         DEBUG ("freeswitch plugin: in fs_submit");
414
415         DEBUG ("fc->instance");
416         DEBUG (fc->instance);
417
418         DEBUG ("fm->type");
419         DEBUG (fm->type);
420
421         DEBUG ("mv->value");
422         DEBUG (mv->value);
423
424         value_t values[1];
425         value_list_t vl = VALUE_LIST_INIT;
426
427         values[0] = mv->value;
428
429         vl.values = values;
430         vl.values_len = 1;
431         vl.time = time (NULL);
432
433         strncpy (vl.host, hostname_g, sizeof (vl.host));
434         strncpy (vl.plugin, "freeswitch", sizeof (vl.plugin));
435         strncpy (vl.plugin_instance, fc->instance, sizeof (vl.plugin_instance));
436         strncpy (vl.type, fm->type, sizeof (vl.type));
437         strncpy (vl.type_instance, fm->instance, sizeof (vl.type_instance));
438
439         plugin_dispatch_values (&vl);
440 } /* void fs_submit */
441
442 static int fs_read_command (fs_command_t *fc)
443 {
444         DEBUG ("freeswitch plugin: in fs_read_command");
445
446         fs_match_t *fm;
447         int status;
448
449         /* can't the following be done nicer ? */
450         char *line;
451         line = (char *) malloc (strlen(fc->line)+3);
452         snprintf(line, strlen(fc->line)+3, "%s\n\n", fc->line);
453         esl_send_recv(&esl_handle, line);
454
455         fc->buffer_fill = 0;
456
457         if (esl_handle.last_sr_event && esl_handle.last_sr_event->body)
458         {
459                 DEBUG ("freeswitch plugin: output from esl (truncated):\n%s\n\n", esl_handle.last_sr_event->body);
460                 sfree(fc->buffer);
461                 fc->buffer = strdup(esl_handle.last_sr_event->body);
462                 fc->buffer_size = strlen(fc->buffer);
463                 fc->buffer_fill = 1;
464         }
465
466         for (fm = fc->matches; fm != NULL; fm = fm->next)
467         {
468                 cu_match_value_t *mv;
469
470                 status = match_apply (fm->match, fc->buffer);
471                 if (status != 0)
472                 {
473                         WARNING ("freeswitch plugin: match_apply failed.");
474                         continue;
475                 }
476
477                 mv = match_get_user_data (fm->match);
478                 if (mv == NULL)
479                 {
480                         WARNING ("freeswitch plugin: match_get_user_data returned NULL.");
481                         continue;
482                 }
483
484                 fs_submit (fc, fm, mv);
485         } /* for (fm = fc->matches; fm != NULL; fm = fm->next) */
486
487         return (0);
488 } /* int fs_read_command */
489
490 static int fs_read (void)
491 {
492         fs_command_t *fc;
493
494         DEBUG ("freeswitch plugin: read poll");
495
496         for (fc = fs_commands_g; fc != NULL; fc = fc->next)
497                 fs_read_command (fc);
498
499         return (0);
500 } /* int fs_read */
501
502 /*
503 static void *msg_thread_run(esl_thread_t *me, void *obj)
504 {
505         esl_handle_t *esl_handle = (esl_handle_t *) obj;
506         thread_running = 1;
507
508         // Maybe do some more in this loop later, like receive subscribed events,
509         // and create statistics of them
510         // see fs_cli.c function static void *msg_thread_run(), around line 198
511         while (thread_running && esl_handle->connected)
512         {
513                 esl_status_t status = esl_recv_event_timed(esl_handle, 10, 1, NULL);
514                 if (status == ESL_FAIL)
515                 {
516                         //DEBUG ("Disconnected [%s]\n", ESL_LOG_WARNING); // todo fixit
517                         DEBUG ("Disconnected [%s]\n", "ESL_LOG_WARNING");
518                         thread_running = 0;
519                 }
520                 usleep(1000);
521         }
522
523         thread_running = 0;
524         return (NULL);
525 } */ /* void *msg_thread_run */
526
527 static int fs_init (void)
528 {
529         /* Set some default configuration variables */
530         if (fs_host == NULL) fs_host = FS_DEF_HOST;
531         if (fs_port == NULL) fs_port = FS_DEF_PORT;
532         if (fs_pass == NULL) fs_pass = FS_DEF_PASS;
533
534         /* Connect to FreeSWITCH over ESL */
535         DEBUG ("freeswitch plugin: making ESL connection to %s %s %s\n", fs_host, fs_port, fs_pass);
536         esl_connect(&esl_handle, fs_host, atoi(fs_port), fs_pass);
537
538         /* Start a seperate thread for incoming events here */
539         //esl_thread_create_detached(msg_thread_run, &esl_handle);
540
541         return(0);
542 } /* int fs_init */
543
544 static int fs_shutdown (void)
545 {
546         DEBUG ("freeswitch plugin: disconnecting");
547         esl_disconnect(&esl_handle);
548         fs_command_free (fs_commands_g);
549         fs_commands_g = NULL;
550         return (0);
551 } /* int fs_shutdown */
552
553 void module_register (void)
554 {
555         plugin_register_complex_config ("freeswitch", fs_complex_config);
556         plugin_register_init ("freeswitch", fs_init);
557         plugin_register_read ("freeswitch", fs_read);
558         plugin_register_shutdown ("freeswitch", fs_shutdown);
559 } /* void module_register */