snort plugin: Assert that the configured "type" has only one data source.
[collectd.git] / src / snort.c
1 /**
2  * collectd - src/snort.c
3  * Copyright (C) 2013 Kris Nielander
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  *   Kris Nielander <nielander@fox-it.com>
20  *
21  * This plugin is based on the snmp plugin by Florian octo Forster.
22  *
23  **/
24
25 #include "collectd.h"
26 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */
27 #include "common.h" /* auxiliary functions */
28 #include <sys/mman.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 struct metric_definition_s {
35     char *name;
36     char *type;
37     int data_source_type;
38     int index;
39     struct metric_definition_s *next;
40 };
41 typedef struct metric_definition_s metric_definition_t;
42
43 struct instance_definition_s {
44     char *name;
45     char *path;
46     metric_definition_t **metric_list;
47     int metric_list_len;
48     cdtime_t last;
49     cdtime_t interval;
50     struct instance_definition_s *next;
51 };
52 typedef struct instance_definition_s instance_definition_t;
53
54 /* Private */
55 static metric_definition_t *metric_head = NULL;
56
57 static int snort_read_submit(instance_definition_t *id, metric_definition_t *md,
58     const char *buf){
59
60     /* Registration variables */
61     value_t value;
62     value_list_t vl = VALUE_LIST_INIT;
63
64     DEBUG("snort plugin: plugin_instance=%s type=%s value=%s", id->name,
65         md->type, buf);
66
67     if (buf == NULL)
68         return (-1);
69
70     /* Parse value */
71     parse_value(buf, &value, md->data_source_type);
72
73     /* Register */
74     vl.values_len = 1;
75     vl.values = &value;
76
77     sstrncpy(vl.host, hostname_g, sizeof (vl.host));
78     sstrncpy(vl.plugin, "snort", sizeof(vl.plugin));
79     sstrncpy(vl.plugin_instance, id->name, sizeof(vl.plugin_instance));
80     sstrncpy(vl.type, md->type, sizeof(vl.type));
81
82     vl.time = id->last;
83     vl.interval = id->interval;
84
85     DEBUG("snort plugin: -> plugin_dispatch_values (&vl);");
86     plugin_dispatch_values(&vl);
87
88     return (0);
89 }
90
91 static int snort_read_buffer (instance_definition_t *id,
92         char const *buffer, size_t buffer_size)
93 {
94     int i;
95
96     char **metrics;
97     int metrics_num;
98
99     char *buf, *buf_ptr;
100
101     /* mmap, char pointers */
102     char const *p_end;
103
104     /* Set the start value count. */
105     metrics_num = 1;
106
107     /* Set the pointer to the last line of the file and count the fields.
108      (Skip the last two characters of the buffer: `\n' and `\0') */
109     for (p_end = (buffer + buffer_size) - 2; p_end > buffer; --p_end){
110         if (*p_end == ','){
111             ++metrics_num;
112         } else if (*p_end == '\n'){
113             ++p_end;
114             break;
115         }
116     }
117
118     if (metrics_num == 1){
119         ERROR("snort plugin: last line of `%s' does not contain enough values.", id->path);
120         return (-1);
121     }
122
123     if (*p_end == '#'){
124         ERROR("snort plugin: last line of `%s' is a comment.", id->path);
125         return (-1);
126     }
127
128     /* Copy the line to the buffer */
129     buf = strdup(p_end);
130
131     /* Create a list of all values */
132     metrics = calloc (metrics_num, sizeof (*metrics));
133     if (metrics == NULL) {
134         ERROR ("snort plugin: calloc failed.");
135         sfree (buf);
136         return (ENOMEM);
137     }
138
139     buf_ptr = buf;
140     i = 0;
141     while (buf_ptr != NULL) {
142         char *next = strchr (buf_ptr, ',');
143         if (next != NULL) {
144             *next = 0;
145             next++;
146         }
147         metrics[i] = buf_ptr;
148         buf_ptr = next;
149         i++;
150     }
151     assert (i == metrics_num);
152
153     /* Set last time */
154     id->last = TIME_T_TO_CDTIME_T(strtol(*metrics, NULL, 0));
155
156     /* Register values */
157     for (i = 0; i < id->metric_list_len; ++i){
158         metric_definition_t *md = id->metric_list[i];
159
160         if (md->index >= metrics_num) {
161             ERROR ("snort plugin: Metric \"%s\": Request for index %i when "
162                     "only %i fields are available.",
163                     md->name, md->index, metrics_num);
164             continue;
165         }
166
167         snort_read_submit(id, md, metrics[md->index]);
168     }
169
170     /* Free up resources */
171     free(metrics);
172     free(buf);
173     return (0);
174 }
175
176 static int snort_read(user_data_t *ud){
177     instance_definition_t *id;
178
179     int fd;
180
181     struct stat sb;
182
183     /* mmap, char pointers */
184     char *p_start;
185
186     id = ud->data;
187     DEBUG("snort plugin: snort_read (instance = %s)", id->name);
188
189     fd = open(id->path, O_RDONLY);
190     if (fd == -1){
191         ERROR("snort plugin: Unable to open `%s'.", id->path);
192         return (-1);
193     }
194
195     if ((fstat(fd, &sb) != 0) || (!S_ISREG(sb.st_mode))){
196         ERROR("snort plugin: `%s' is not a file.", id->path);
197         close (fd);
198         return (-1);
199     }
200
201     if (sb.st_size == 0){
202         ERROR("snort plugin: `%s' is empty.", id->path);
203         close (fd);
204         return (-1);
205     }
206
207     p_start = mmap(/* addr = */ NULL, sb.st_size, PROT_READ, MAP_SHARED, fd,
208         /* offset = */ 0);
209     if (p_start == MAP_FAILED){
210         ERROR("snort plugin: mmap error");
211         close (fd);
212         return (-1);
213     }
214
215     snort_read_buffer (id, p_start, (size_t) sb.st_size);
216
217     /* Done with mmap and file pointer */
218     close(fd);
219     munmap(p_start, sb.st_size);
220     return (0);
221 }
222
223 static void snort_metric_definition_destroy(void *arg){
224     metric_definition_t *md;
225
226     md = arg;
227     if (md == NULL)
228         return;
229
230     if (md->name != NULL)
231         DEBUG("snort plugin: Destroying metric definition `%s'.", md->name);
232
233     sfree(md->name);
234     sfree(md->type);
235     sfree(md);
236 }
237
238 static int snort_config_add_metric_index(metric_definition_t *md, oconfig_item_t *ci){
239     if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)){
240         WARNING("snort plugin: `Index' needs exactly one integer argument.");
241         return (-1);
242     }
243
244     md->index = (int)ci->values[0].value.number;
245     if (md->index <= 0){
246         WARNING("snort plugin: `Index' must be higher than 0.");
247         return (-1);
248     }
249
250     return (0);
251 }
252
253 /* Parse metric  */
254 static int snort_config_add_metric(oconfig_item_t *ci){
255     metric_definition_t *md;
256     const data_set_t *ds;
257     int status = 0;
258     int i;
259
260     md = (metric_definition_t *)malloc(sizeof(*md));
261     if (md == NULL)
262         return (-1);
263     memset(md, 0, sizeof(*md));
264
265     md->name = NULL;
266     status = cf_util_get_string (ci, &md->name);
267     if (status != 0) {
268         sfree (md);
269         return (-1);
270     }
271
272     for (i = 0; i < ci->children_num; ++i){
273         oconfig_item_t *option = ci->children + i;
274         status = 0;
275
276         if (strcasecmp("Type", option->key) == 0)
277             status = cf_util_get_string(option, &md->type);
278         else if (strcasecmp("Index", option->key) == 0)
279             status = snort_config_add_metric_index(md, option);
280         else {
281             WARNING("snort plugin: Option `%s' not allowed here.", option->key);
282             status = -1;
283         }
284
285         if (status != 0)
286             break;
287     }
288
289     if (status != 0){
290         snort_metric_definition_destroy(md);
291         return (-1);
292     }
293
294     /* Verify all necessary options have been set. */
295     if (md->type == NULL){
296         WARNING("snort plugin: Option `Type' must be set.");
297         status = -1;
298     } else if (md->index == 0){
299         WARNING("snort plugin: Option `Index' must be set.");
300         status = -1;
301     }
302
303     if (status != 0){
304         snort_metric_definition_destroy(md);
305         return (-1);
306     }
307
308     /* Retrieve the data source type from the types db. */
309     ds = plugin_get_ds(md->type);
310     if (ds == NULL){
311         ERROR ("snort plugin: Failed to look up type \"%s\". "
312                 "It may not be defined in the types.db file. "
313                 "Please read the types.db(5) manual page for more details.",
314                 md->type);
315         snort_metric_definition_destroy(md);
316         return (-1);
317     } else if (ds->ds_num != 1) {
318         ERROR ("snort plugin: The type \"%s\" has %i data sources. "
319                 "Only types with a single data soure are supported.",
320                 ds->type, ds->ds_num);
321         return (-1);
322     } else {
323         md->data_source_type = ds->ds->type;
324     }
325
326     DEBUG("snort plugin: md = { name = %s, type = %s, data_source_type = %d, index = %d }",
327         md->name, md->type, md->data_source_type, md->index);
328
329     if (metric_head == NULL)
330         metric_head = md;
331     else {
332         metric_definition_t *last;
333         last = metric_head;
334         while (last->next != NULL)
335             last = last->next;
336         last->next = md;
337     }
338
339     return (0);
340 }
341
342 static void snort_instance_definition_destroy(void *arg){
343     instance_definition_t *id;
344
345     id = arg;
346     if (id == NULL)
347         return;
348
349     if (id->name != NULL)
350         DEBUG("snort plugin: Destroying instance definition `%s'.", id->name);
351
352     sfree(id->name);
353     sfree(id->path);
354     sfree(id->metric_list);
355     sfree(id);
356 }
357
358 static int snort_config_add_instance_collect(instance_definition_t *id, oconfig_item_t *ci){
359     metric_definition_t *metric;
360     int i;
361
362     if (ci->values_num < 1){
363         WARNING("snort plugin: The `Collect' config option needs at least one argument.");
364         return (-1);
365     }
366
367     /* Verify string arguments */
368     for (i = 0; i < ci->values_num; ++i)
369         if (ci->values[i].type != OCONFIG_TYPE_STRING){
370             WARNING("snort plugin: All arguments to `Collect' must be strings.");
371             return (-1);
372         }
373
374     id->metric_list = (metric_definition_t **)malloc(sizeof(metric_definition_t *) * ci->values_num);
375     if (id->metric_list == NULL)
376         return (-1);
377
378     for (i = 0; i < ci->values_num; ++i){
379         for (metric = metric_head; metric != NULL; metric = metric->next)
380             if (strcasecmp(ci->values[i].value.string, metric->name) == 0)
381                 break;
382
383         if (metric == NULL){
384             WARNING("snort plugin: `Collect' argument not found `%s'.", ci->values[i].value.string);
385             return (-1);
386         }
387
388         DEBUG("snort plugin: id { name=%s md->name=%s }", id->name, metric->name);
389
390         id->metric_list[i] = metric;
391         id->metric_list_len++;
392     }
393
394     return (0);
395 }
396
397 /* Parse instance  */
398 static int snort_config_add_instance(oconfig_item_t *ci){
399
400     instance_definition_t* id;
401     int status = 0;
402     int i;
403
404     /* Registration variables */
405     char cb_name[DATA_MAX_NAME_LEN];
406     user_data_t cb_data;
407     struct timespec cb_interval;
408
409     if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)){
410         WARNING("snort plugin: The `Instance' config option needs exactly one string argument.");
411         return (-1);
412     }
413
414     id = (instance_definition_t *)malloc(sizeof(*id));
415     if (id == NULL)
416         return (-1);
417     memset(id, 0, sizeof(*id));
418
419     id->name = strdup(ci->values[0].value.string);
420     if (id->name == NULL){
421         free(id);
422         return (-1);
423     }
424
425     /* Use default interval. */
426     id->interval = plugin_get_interval();
427
428     for (i = 0; i < ci->children_num; ++i){
429         oconfig_item_t *option = ci->children + i;
430         status = 0;
431
432         if (strcasecmp("Path", option->key) == 0)
433             status = cf_util_get_string(option, &id->path);
434         else if (strcasecmp("Collect", option->key) == 0)
435             status = snort_config_add_instance_collect(id, option);
436         else if (strcasecmp("Interval", option->key) == 0)
437             cf_util_get_cdtime(option, &id->interval);
438         else {
439             WARNING("snort plugin: Option `%s' not allowed here.", option->key);
440             status = -1;
441         }
442
443         if (status != 0)
444             break;
445     }
446
447     if (status != 0){
448         snort_instance_definition_destroy(id);
449         return (-1);
450     }
451
452     /* Verify all necessary options have been set. */
453     if (id->path == NULL){
454         WARNING("snort plugin: Option `Path' must be set.");
455         status = -1;
456     } else if (id->metric_list == NULL){
457         WARNING("snort plugin: Option `Collect' must be set.");
458         status = -1;
459    }
460
461     if (status != 0){
462         snort_instance_definition_destroy(id);
463         return (-1);
464     }
465
466     DEBUG("snort plugin: id = { name = %s, path = %s }", id->name, id->path);
467
468     ssnprintf (cb_name, sizeof (cb_name), "snort-%s", id->name);
469     memset(&cb_data, 0, sizeof(cb_data));
470     cb_data.data = id;
471     cb_data.free_func = snort_instance_definition_destroy;
472     CDTIME_T_TO_TIMESPEC(id->interval, &cb_interval);
473     status = plugin_register_complex_read(NULL, cb_name, snort_read, &cb_interval, &cb_data);
474
475     if (status != 0){
476         ERROR("snort plugin: Registering complex read function failed.");
477         snort_instance_definition_destroy(id);
478         return (-1);
479     }
480
481     return (0);
482 }
483
484 /* Parse blocks */
485 static int snort_config(oconfig_item_t *ci){
486     int i;
487     for (i = 0; i < ci->children_num; ++i){
488         oconfig_item_t *child = ci->children + i;
489         if (strcasecmp("Metric", child->key) == 0)
490             snort_config_add_metric(child);
491         else if (strcasecmp("Instance", child->key) == 0)
492             snort_config_add_instance(child);
493         else
494             WARNING("snort plugin: Ignore unknown config option `%s'.", child->key);
495     }
496
497     return (0);
498 } /* int snort_config */
499
500 static int snort_shutdown(void){
501     metric_definition_t *metric_this;
502     metric_definition_t *metric_next;
503
504     metric_this = metric_head;
505     metric_head = NULL;
506
507     while (metric_this != NULL){
508         metric_next = metric_this->next;
509         snort_metric_definition_destroy(metric_this);
510         metric_this = metric_next;
511     }
512
513     return (0);
514 }
515
516 void module_register(void){
517     plugin_register_complex_config("snort", snort_config);
518     plugin_register_shutdown("snort", snort_shutdown);
519 }
520