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