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