source: trunk/src/sh_fInotify.c@ 375

Last change on this file since 375 was 373, checked in by katerina, 13 years ago

Patch for ticket #265 (inotify). Handling of added subdirectories and file list rescan.

File size: 17.7 KB
Line 
1/* SAMHAIN file system integrity testing */
2/* Copyright (C) 2011 Rainer Wichmann */
3/* */
4/* This program is free software; you can redistribute it */
5/* and/or modify */
6/* it under the terms of the GNU General Public License as */
7/* published by */
8/* the Free Software Foundation; either version 2 of the License, or */
9/* (at your option) any later version. */
10/* */
11/* This program is distributed in the hope that it will be useful, */
12/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14/* GNU General Public License for more details. */
15/* */
16/* You should have received a copy of the GNU General Public License */
17/* along with this program; if not, write to the Free Software */
18/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
19
20/***************************************************************************
21 *
22 * This file provides a module for samhain to use inotify for file checking.
23 *
24 */
25
26#include "config_xor.h"
27
28#if (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE))
29
30#include "samhain.h"
31#include "sh_utils.h"
32#include "sh_modules.h"
33#include "sh_pthread.h"
34#include "sh_inotify.h"
35#include "sh_unix.h"
36#include "sh_hash.h"
37#include "sh_files.h"
38#include "sh_ignore.h"
39
40#define FIL__ _("sh_fInotify.c")
41
42sh_watches sh_file_watches = SH_INOTIFY_INITIALIZER;
43
44#if defined(HAVE_SYS_INOTIFY_H)
45
46static sh_watches sh_file_missing = SH_INOTIFY_INITIALIZER;
47
48#include <sys/inotify.h>
49
50/* --- Configuration ------- */
51
52static int ShfInotifyActive = S_FALSE;
53
54static unsigned long ShfInotifyWatches = 0;
55
56static int sh_fInotify_active(const char *s)
57{
58 int value;
59
60 SL_ENTER(_("sh_fInotify_active"));
61 value = sh_util_flagval(s, &ShfInotifyActive);
62 if (value == 0 && ShfInotifyActive != S_FALSE)
63 {
64 sh.flag.inotify |= SH_INOTIFY_USE;
65 sh.flag.inotify |= SH_INOTIFY_DOSCAN;
66 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
67 }
68 if (value == 0 && ShfInotifyActive == S_FALSE)
69 {
70 sh.flag.inotify = 0;
71 }
72 SL_RETURN((value), _("sh_fInotify_active"));
73}
74
75static int sh_fInotify_watches(const char *s)
76{
77 int retval = -1;
78 char * foo;
79 unsigned long value;
80
81 SL_ENTER(_("sh_fInotify_watches"));
82
83 value = strtoul(s, &foo, 0);
84 if (*foo == '\0')
85 {
86 ShfInotifyWatches = (value > 2147483647) ? 2147483647 /* MAX_INT_32 */: value;
87 retval = 0;
88 }
89 SL_RETURN((retval), _("sh_fInotify_watches"));
90}
91
92
93sh_rconf sh_fInotify_table[] = {
94 {
95 N_("inotifyactive"),
96 sh_fInotify_active,
97 },
98 {
99 N_("inotifywatches"),
100 sh_fInotify_watches,
101 },
102 {
103 NULL,
104 NULL
105 }
106};
107
108/* --- End Configuration --- */
109
110static int sh_fInotify_init_internal(void);
111static int sh_fInotify_process(struct inotify_event * event);
112static int sh_fInotify_report(struct inotify_event * event, char * filename,
113 int class, unsigned long check_mask, int ftype, int rdepth);
114
115int sh_fInotify_init(struct mod_type * arg)
116{
117#ifndef HAVE_PTHREAD
118 (void) arg;
119 return SH_MOD_FAILED;
120#else
121
122 if (ShfInotifyActive == S_FALSE)
123 return SH_MOD_FAILED;
124
125 if (sh.flag.checkSum == SH_CHECK_INIT)
126 return SH_MOD_FAILED;
127
128 if (arg != NULL && arg->initval < 0 &&
129 (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE))
130 {
131 /* Init from main thread */
132 sh.flag.inotify |= SH_INOTIFY_DOSCAN;
133 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
134
135 if (0 == sh_pthread_create(sh_threaded_module_run, (void *)arg))
136 {
137 return SH_MOD_THREAD;
138 }
139 else
140 {
141 sh.flag.inotify = 0;
142 return SH_MOD_FAILED;
143 }
144 }
145 else if (arg != NULL && arg->initval < 0 &&
146 (sh.flag.isdaemon != S_TRUE && sh.flag.loop != S_TRUE))
147 {
148 sh.flag.inotify = 0;
149 return SH_MOD_FAILED;
150 }
151 else if (arg != NULL && arg->initval == SH_MOD_THREAD &&
152 (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE))
153 {
154 /* Reconfigure from main thread */
155 /* sh_fInotify_init_internal(); */
156 sh.flag.inotify |= SH_INOTIFY_DOSCAN;
157 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
158 return SH_MOD_THREAD;
159 }
160
161 /* Within thread, init */
162 return sh_fInotify_init_internal();
163#endif
164}
165
166int sh_fInotify_run()
167{
168 ssize_t len = -1;
169 char * buffer = SH_ALLOC(16384);
170 static int count = 0;
171 static int count2 = 0;
172
173 if (ShfInotifyActive == S_FALSE)
174 return SH_MOD_FAILED;
175
176 if ( (sh.flag.inotify & SH_INOTIFY_DOSCAN) ||
177 (sh.flag.inotify & SH_INOTIFY_NEEDINIT))
178 {
179 if (0 != sh_fInotify_init_internal())
180 return SH_MOD_FAILED;
181 }
182
183 /* Blocking read from inotify file descriptor.
184 */
185 len = sh_inotify_read_timeout(buffer, 16384, 1);
186
187 if (len > 0)
188 {
189 struct inotify_event *event;
190 int i = 0;
191
192 while (i < len)
193 {
194 event = (struct inotify_event *) &(buffer[i]);
195
196 sh_fInotify_process(event);
197
198 i += sizeof (struct inotify_event) + event->len;
199 }
200
201 if ( (sh.flag.inotify & SH_INOTIFY_DOSCAN) ||
202 (sh.flag.inotify & SH_INOTIFY_NEEDINIT))
203 {
204 if (0 != sh_fInotify_init_internal())
205 return SH_MOD_FAILED;
206 }
207 }
208
209 /* Re-scan 'dormant' list of sh_file_missing.
210 */
211 sh_inotify_recheck_watches (&sh_file_watches, &sh_file_missing);
212
213 ++count;
214 ++count2;
215
216 if (count >= 10)
217 {
218 count = 0; /* Re-expand glob patterns to discover added files. */
219 sh.flag.inotify |= SH_INOTIFY_INSCAN;
220 sh_files_check_globFilePatterns();
221 sh.flag.inotify &= ~SH_INOTIFY_INSCAN;
222 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
223 }
224
225 if (count2 >= 300)
226 {
227 count2 = 0; /* Update baseline database. */
228 if (sh.flag.checkSum == SH_CHECK_CHECK && sh.flag.update == S_TRUE)
229 sh_hash_writeout ();
230 }
231
232 return 0;
233}
234
235/* We block in the read() call on the inotify descriptor,
236 * so we always run.
237 */
238int sh_fInotify_timer(time_t tcurrent)
239{
240 (void) tcurrent;
241 return 1;
242}
243
244int sh_fInotify_cleanup()
245{
246 sh_inotify_purge_dormant(&sh_file_watches);
247 sh_inotify_remove(&sh_file_watches);
248 sh_inotify_init(&sh_file_watches);
249 return 0;
250}
251
252int sh_fInotify_reconf()
253{
254 sh.flag.inotify = 0;
255
256 ShfInotifyWatches = 0;
257 ShfInotifyActive = 0;
258
259 return sh_fInotify_cleanup();
260}
261
262#define PROC_WATCHES_MAX _("/proc/sys/fs/inotify/max_user_watches")
263
264static void sh_fInotify_set_nwatches()
265{
266 if (ShfInotifyWatches == 0)
267 return;
268
269 if (0 == access(PROC_WATCHES_MAX, R_OK|W_OK)) /* flawfinder: ignore */
270 {
271 FILE * fd;
272
273 if (NULL != (fd = fopen(PROC_WATCHES_MAX, "r+")))
274 {
275 char str[128];
276 char * ret;
277 char * ptr;
278 unsigned long wn;
279
280 str[0] = '\0';
281 ret = fgets(str, 128, fd);
282 if (ret && *str != '\0')
283 {
284 wn = strtoul(str, &ptr, 0);
285 if (*ptr == '\0' || *ptr == '\n')
286 {
287 if (wn < ShfInotifyWatches)
288 {
289 sl_snprintf(str, sizeof(str), "%lu\n", ShfInotifyWatches);
290 (void) fseek(fd, 0L, SEEK_SET);
291 fputs(str, fd);
292 }
293 sl_fclose(FIL__, __LINE__, fd);
294 return;
295 }
296 }
297 sl_fclose(FIL__, __LINE__, fd);
298 }
299 }
300 SH_MUTEX_LOCK(mutex_thread_nolog);
301 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
302 _("Cannot set max_user_watches"),
303 _("sh_fInotify_set_nwatches"));
304 SH_MUTEX_UNLOCK(mutex_thread_nolog);
305 return;
306}
307
308/* The watch fd is thread specific. To have it in the fInotify thread,
309 * the main thread writes a list of files/dirs to watch, and here we
310 * now pop files from the list to add watches for them.
311 */
312static int sh_fInotify_init_internal()
313{
314 char * filename;
315 int class;
316 int type;
317 int rdepth;
318 unsigned long check_mask;
319 int retval;
320 int errnum;
321
322 if (ShfInotifyActive == S_FALSE)
323 return SH_MOD_FAILED;
324
325 /* Wait until file scan is finished.
326 */
327 while((sh.flag.inotify & SH_INOTIFY_DOSCAN) != 0)
328 {
329 retry_msleep(1,0);
330
331 if (ShfInotifyActive == S_FALSE)
332 return SH_MOD_FAILED;
333 }
334
335 sh_fInotify_set_nwatches();
336
337 while (NULL != (filename = sh_inotify_pop_dormant(&sh_file_watches,
338 &class, &check_mask,
339 &type, &rdepth)))
340 {
341 retval = sh_inotify_add_watch(filename, &sh_file_watches, &errnum,
342 class, check_mask, type, rdepth);
343
344 if (retval < 0)
345 {
346 char errbuf[SH_ERRBUF_SIZE];
347
348 sh_error_message(errnum, errbuf, sizeof(errbuf));
349
350 if ((errnum == ENOENT) || (errnum == EEXIST))
351 {
352 char * epath = sh_util_safe_name (filename);
353 SH_MUTEX_LOCK(mutex_thread_nolog);
354 sh_error_handle( (class == SH_LEVEL_ALLIGNORE) ?
355 ShDFLevel[class] :
356 ShDFLevel[SH_ERR_T_FILE],
357 FIL__, __LINE__, errnum, MSG_E_SUBGPATH,
358 errbuf, _("sh_fInotify_init_internal"), epath);
359 SH_MUTEX_UNLOCK(mutex_thread_nolog);
360 SH_FREE(epath);
361 }
362 else
363 {
364 SH_MUTEX_LOCK(mutex_thread_nolog);
365 sh_error_handle((-1), FIL__, __LINE__, errnum, MSG_E_SUBGEN,
366 errbuf, _("sh_fInotify_init_internal"));
367 SH_MUTEX_UNLOCK(mutex_thread_nolog);
368 }
369 }
370 }
371
372 /* Need this because mod_check() may run after
373 * DOSCAN is finished, hence wouldn't call init().
374 */
375 sh.flag.inotify &= ~SH_INOTIFY_NEEDINIT;
376
377 return 0;
378}
379
380static void sh_fInotify_logmask(struct inotify_event * event)
381{
382 char dbgbuf[256];
383
384 sl_strlcpy (dbgbuf, "inotify mask: ", sizeof(dbgbuf));
385
386 if (event->mask & IN_ACCESS) sl_strlcat(dbgbuf, "IN_ACCESS ", sizeof(dbgbuf));
387 if (event->mask & IN_ATTRIB) sl_strlcat(dbgbuf, "IN_ATTRIB ", sizeof(dbgbuf));
388 if (event->mask & IN_CLOSE_WRITE) sl_strlcat(dbgbuf, "IN_CLOSE_WRITE ", sizeof(dbgbuf));
389 if (event->mask & IN_CLOSE_NOWRITE) sl_strlcat(dbgbuf, "IN_CLOSE_NOWRITE ", sizeof(dbgbuf));
390 if (event->mask & IN_CREATE) sl_strlcat(dbgbuf, "IN_CREATE ", sizeof(dbgbuf));
391 if (event->mask & IN_DELETE) sl_strlcat(dbgbuf, "IN_DELETE ", sizeof(dbgbuf));
392 if (event->mask & IN_DELETE_SELF) sl_strlcat(dbgbuf, "IN_DELETE_SELF ", sizeof(dbgbuf));
393 if (event->mask & IN_MODIFY) sl_strlcat(dbgbuf, "IN_MODIFY ", sizeof(dbgbuf));
394 if (event->mask & IN_MOVE_SELF) sl_strlcat(dbgbuf, "IN_MOVE_SELF ", sizeof(dbgbuf));
395 if (event->mask & IN_MOVED_FROM) sl_strlcat(dbgbuf, "IN_MOVED_FROM ", sizeof(dbgbuf));
396 if (event->mask & IN_MOVED_TO) sl_strlcat(dbgbuf, "IN_MOVED_TO ", sizeof(dbgbuf));
397 if (event->mask & IN_OPEN) sl_strlcat(dbgbuf, "IN_OPEN ", sizeof(dbgbuf));
398 if (event->mask & IN_IGNORED) sl_strlcat(dbgbuf, "IN_IGNORED ", sizeof(dbgbuf));
399 if (event->mask & IN_ISDIR) sl_strlcat(dbgbuf, "IN_ISDIR ", sizeof(dbgbuf));
400 if (event->mask & IN_Q_OVERFLOW) sl_strlcat(dbgbuf, "IN_Q_OVERFLOW ", sizeof(dbgbuf));
401 if (event->mask & IN_UNMOUNT) sl_strlcat(dbgbuf, "IN_UNMOUNT ", sizeof(dbgbuf));
402
403 /* fprintf(stderr, "FIXME: %s\n", dbgbuf); */
404
405 SH_MUTEX_LOCK(mutex_thread_nolog);
406 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, 0, MSG_E_SUBGEN,
407 dbgbuf, _("sh_fInotify_process"));
408 SH_MUTEX_UNLOCK(mutex_thread_nolog);
409}
410
411static int sh_fInotify_process(struct inotify_event * event)
412{
413 int class;
414 int ftype;
415 int rdepth;
416 unsigned long check_mask;
417 char * filename;
418 extern int flag_err_debug;
419
420 if (flag_err_debug == SL_TRUE)
421 {
422 sh_fInotify_logmask(event);
423 }
424
425 if (event->wd >= 0)
426 {
427 filename = sh_inotify_search_item(&sh_file_watches, event->wd,
428 &class, &check_mask, &ftype, &rdepth);
429
430 if (filename)
431 {
432 sh_fInotify_report(event, filename, class, check_mask, ftype, rdepth);
433 SH_FREE(filename);
434 }
435 else if (sh.flag.inotify & SH_INOTIFY_NEEDINIT)
436 {
437 return 1;
438 }
439 else if ((event->mask & IN_UNMOUNT) == 0 && (event->mask & IN_IGNORED) == 0)
440 {
441 /* Remove watch ? Seems reasonable. */
442 sh_inotify_rm_watch(NULL, NULL, event->wd);
443
444 SH_MUTEX_LOCK(mutex_thread_nolog);
445 sh_error_handle((-1), FIL__, __LINE__, event->wd, MSG_E_SUBGEN,
446 _("Watch removed: file path unknown"),
447 _("sh_fInotify_process"));
448 SH_MUTEX_UNLOCK(mutex_thread_nolog);
449 }
450 }
451 else if ((event->mask & IN_Q_OVERFLOW) != 0)
452 {
453 sh.flag.inotify |= SH_INOTIFY_DOSCAN;
454 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
455 SH_MUTEX_LOCK(mutex_thread_nolog);
456 sh_error_handle(SH_ERR_WARN, FIL__, __LINE__, event->wd, MSG_E_SUBGEN,
457 _("Inotify queue overflow"),
458 _("sh_fInotify_process"));
459 SH_MUTEX_UNLOCK(mutex_thread_nolog);
460 return 1;
461 }
462
463 return 0;
464}
465
466void sh_fInotify_report_add(char * path, int class, unsigned long check_mask)
467{
468 if (S_FALSE == sh_ignore_chk_new(path))
469 {
470 int reported = 0;
471
472 sh_files_clear_file_reported(path);
473
474 sh_files_search_file(path, &class, &check_mask, &reported);
475
476 sh_files_filecheck (class, check_mask, path, NULL,
477 &reported, 0);
478 if (SH_FFLAG_REPORTED_SET(reported))
479 sh_files_set_file_reported(path);
480 }
481 return;
482}
483
484
485static void sh_fInotify_report_miss(char * name, int level)
486{
487 char * tmp = sh_util_safe_name (name);
488
489 SH_MUTEX_LOCK(mutex_thread_nolog);
490 sh_error_handle (level, FIL__, __LINE__, 0, MSG_FI_MISS, tmp);
491 SH_MUTEX_UNLOCK(mutex_thread_nolog);
492
493 SH_FREE(tmp);
494 return;
495}
496
497static int sh_fInotify_report_change (struct inotify_event * event,
498 char * path, char * filename,
499 int class, unsigned long check_mask, int ftype)
500{
501 int reported;
502 int ret = sh_files_search_file(path, &class, &check_mask, &reported);
503
504 if ((ret == 0) && (event->len > 0) && (ftype == SH_INOTIFY_FILE))
505 {
506 ; /* do nothing, watch was for directory monitored as file only */
507 }
508 else
509 {
510 sh_files_filecheck (class, check_mask, filename,
511 (event->len > 0) ? event->name : NULL,
512 &reported, 0);
513 }
514 return 0;
515}
516
517
518static int sh_fInotify_report_missing (struct inotify_event * event,
519 char * path,
520 int class, unsigned long check_mask, int ftype)
521{
522 int reported;
523 int isdir = (event->mask & IN_ISDIR);
524 int level = (class == SH_LEVEL_ALLIGNORE) ?
525 ShDFLevel[class] :
526 ShDFLevel[(isdir == 0) ? SH_ERR_T_FILE : SH_ERR_T_DIR];
527
528 if (S_FALSE == sh_ignore_chk_del(path))
529 {
530 if (0 != hashreport_missing(path, level))
531 {
532 int ret = sh_files_search_file(path, &class, &check_mask, &reported);
533
534 if ((ret == 0) && (event->len > 0) && (ftype == SH_INOTIFY_FILE))
535 {
536 ; /* do nothing, watch was for directory monitored as file only */
537 }
538 else
539 {
540 /* Removal of a directory triggers:
541 * (1) IN_DELETE IN_ISDIR
542 * (2) IN_DELETE_SELF
543 */
544 if ((event->mask & IN_DELETE_SELF) == 0)
545 sh_fInotify_report_miss(path, level);
546 }
547 }
548 }
549
550#ifndef REPLACE_OLD
551 sh_hash_set_visited_true(path);
552#else
553 sh_hash_set_missing(path);
554#endif
555 if (sh.flag.reportonce == S_TRUE)
556 sh_files_set_file_reported(path);
557
558 /* Move to 'dormant' list, if not file within directory.
559 */
560 if (event->len == 0)
561 sh_inotify_rm_watch(&sh_file_watches, &sh_file_missing, event->wd);
562
563 return 0;
564}
565
566static int sh_fInotify_report_added (struct inotify_event * event,
567 char * path, char * filename,
568 int class, unsigned long check_mask,
569 int ftype, int rdepth)
570{
571 if (S_FALSE == sh_ignore_chk_new(path))
572 {
573 int reported;
574 int ret;
575 int retD = 0;
576 int rdepthD = rdepth;
577
578 sh_files_clear_file_reported(path);
579
580 ret = sh_files_search_file(path, &class, &check_mask, &reported);
581
582 if ((ret == 0) && (event->len > 0) && (ftype == SH_INOTIFY_FILE))
583 {
584 ; /* do nothing, watch was for directory monitored as file only */
585 }
586 else
587 {
588 int classD = class;
589 int reportedD = reported;
590 unsigned long check_maskD = check_mask;
591
592 if (event->mask & IN_ISDIR)
593 {
594 retD = sh_files_search_dir(path, &classD, &check_maskD,
595 &reportedD, &rdepthD);
596 if (retD != 0)
597 {
598 if (ret == 0)
599 {
600 class = classD;
601 check_mask = check_maskD;
602 }
603 }
604 }
605
606 sh_files_filecheck (class, check_mask, filename,
607 (event->len > 0) ? event->name : NULL,
608 &reported, 0);
609
610 if (event->mask & IN_ISDIR)
611 {
612 sh.flag.inotify |= SH_INOTIFY_INSCAN;
613 sh_files_checkdir (classD, check_maskD, rdepthD,
614 path, (event->len > 0) ? event->name : NULL);
615 sh.flag.inotify &= ~SH_INOTIFY_INSCAN;
616 sh.flag.inotify |= SH_INOTIFY_NEEDINIT;
617 sh_dirs_reset ();
618 sh_files_reset ();
619 }
620
621 }
622
623 if (SH_FFLAG_REPORTED_SET(reported))
624 sh_files_set_file_reported(path);
625
626 if ((ret != 0) || (event->mask & IN_ISDIR))
627 {
628 sh_inotify_add_watch(path, &sh_file_watches, &ret,
629 class, check_mask,
630 (event->mask & IN_ISDIR)?SH_INOTIFY_DIR:SH_INOTIFY_FILE,
631 rdepthD);
632 }
633 }
634 return 0;
635}
636
637static int sh_fInotify_report(struct inotify_event * event, char * filename,
638 int class, unsigned long check_mask, int ftype, int rdepth)
639{
640 char * fullpath = NULL;
641 char * path;
642
643 if (event->len > 0)
644 {
645 fullpath = sh_util_strconcat(filename, "/", event->name, NULL);
646 path = fullpath;
647 }
648 else
649 {
650 path = filename;
651 }
652
653 if ( (event->mask & (IN_ATTRIB|IN_MODIFY)) != 0)
654 {
655 sh_fInotify_report_change (event, path, filename,
656 class, check_mask, ftype);
657 }
658 else if ((event->mask & (IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF|IN_MOVED_FROM)) != 0)
659 {
660 sh_fInotify_report_missing (event, path,
661 class, check_mask, ftype);
662 }
663 else if((event->mask & (IN_CREATE|IN_MOVED_TO)) != 0)
664 {
665 sh_fInotify_report_added (event, path, filename,
666 class, check_mask,
667 ftype, rdepth);
668 }
669
670 if (fullpath)
671 SH_FREE(fullpath);
672
673 return 0;
674}
675
676
677#endif
678
679#endif
Note: See TracBrowser for help on using the repository browser.