/* SAMHAIN file system integrity testing */ /* Copyright (C) 2011 Rainer Wichmann */ /* */ /* This program is free software; you can redistribute it */ /* and/or modify */ /* it under the terms of the GNU General Public License as */ /* published by */ /* the Free Software Foundation; either version 2 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /*************************************************************************** * * This file provides a module for samhain to use inotify for file checking. * */ #include "config_xor.h" #if (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE)) #include "samhain.h" #include "sh_utils.h" #include "sh_modules.h" #include "sh_pthread.h" #include "sh_inotify.h" #include "sh_unix.h" #include "sh_hash.h" #include "sh_files.h" #include "sh_ignore.h" #define FIL__ _("sh_fInotify.c") sh_watches sh_file_watches = SH_INOTIFY_INITIALIZER; static sh_watches sh_file_missing = SH_INOTIFY_INITIALIZER; #if defined(HAVE_SYS_INOTIFY_H) #include /* --- Configuration ------- */ static int ShfInotifyActive = S_FALSE; static unsigned long ShfInotifyWatches = 0; static int sh_fInotify_active(const char *s) { int value; SL_ENTER(_("sh_fInotify_active")); value = sh_util_flagval(s, &ShfInotifyActive); if (value == 0 && ShfInotifyActive != S_FALSE) { sh.flag.inotify |= SH_INOTIFY_USE; sh.flag.inotify |= SH_INOTIFY_DOSCAN; sh.flag.inotify |= SH_INOTIFY_NEEDINIT; } if (value == 0 && ShfInotifyActive == S_FALSE) { sh.flag.inotify = 0; } SL_RETURN((value), _("sh_fInotify_active")); } static int sh_fInotify_watches(const char *s) { int retval = -1; char * foo; unsigned long value; SL_ENTER(_("sh_fInotify_watches")); value = strtoul(s, &foo, 0); if (*foo == '\0') { ShfInotifyWatches = (value > 2147483647) ? 2147483647 /* MAX_INT_32 */: value; retval = 0; } SL_RETURN((retval), _("sh_fInotify_watches")); } sh_rconf sh_fInotify_table[] = { { N_("inotifyactive"), sh_fInotify_active, }, { N_("inotifywatches"), sh_fInotify_watches, }, { NULL, NULL } }; /* --- End Configuration --- */ static int sh_fInotify_init_internal(void); static int sh_fInotify_process(struct inotify_event * event); static int sh_fInotify_report(struct inotify_event * event, char * filename, int class, unsigned long check_mask); int sh_fInotify_init(struct mod_type * arg) { #ifndef HAVE_PTHREAD (void) arg; return SH_MOD_FAILED; #else if (ShfInotifyActive == S_FALSE) return SH_MOD_FAILED; if (sh.flag.checkSum == SH_CHECK_INIT) return SH_MOD_FAILED; if (arg != NULL && arg->initval < 0 && (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE)) { /* Init from main thread */ sh.flag.inotify |= SH_INOTIFY_DOSCAN; sh.flag.inotify |= SH_INOTIFY_NEEDINIT; if (0 == sh_pthread_create(sh_threaded_module_run, (void *)arg)) { return SH_MOD_THREAD; } else { sh.flag.inotify = 0; return SH_MOD_FAILED; } } else if (arg != NULL && arg->initval == SH_MOD_THREAD && (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE)) { /* Reconfigure from main thread */ /* sh_fInotify_init_internal(); */ sh.flag.inotify |= SH_INOTIFY_DOSCAN; sh.flag.inotify |= SH_INOTIFY_NEEDINIT; return SH_MOD_THREAD; } /* Within thread, init */ return sh_fInotify_init_internal(); #endif } int sh_fInotify_run() { ssize_t len = -1; char * buffer = SH_ALLOC(16384); if (ShfInotifyActive == S_FALSE) return SH_MOD_FAILED; if ( (sh.flag.inotify & SH_INOTIFY_DOSCAN) || (sh.flag.inotify & SH_INOTIFY_NEEDINIT)) { if (0 != sh_fInotify_init_internal()) return SH_MOD_FAILED; } /* Blocking read from inotify file descriptor. */ len = sh_inotify_read(buffer, 16384); if (len > 0) { struct inotify_event *event; int i = 0; while (i < len) { event = (struct inotify_event *) &(buffer[i]); sh_fInotify_process(event); i += sizeof (struct inotify_event) + event->len; } if ( (sh.flag.inotify & SH_INOTIFY_DOSCAN) || (sh.flag.inotify & SH_INOTIFY_NEEDINIT)) { if (0 != sh_fInotify_init_internal()) return SH_MOD_FAILED; } } /* Re-scan 'dormant' list of sh_file_missing. */ sh_inotify_recheck_watches (&sh_file_watches, &sh_file_missing); return 0; } /* We block in the read() call on the inotify descriptor, * so we always run. */ int sh_fInotify_timer(time_t tcurrent) { (void) tcurrent; return 1; } int sh_fInotify_cleanup() { sh_inotify_purge_dormant(&sh_file_watches); sh_inotify_remove(&sh_file_watches); sh_inotify_init(&sh_file_watches); return 0; } int sh_fInotify_reconf() { sh.flag.inotify = 0; ShfInotifyWatches = 0; ShfInotifyActive = 0; return sh_fInotify_cleanup(); } #define PROC_WATCHES_MAX _("/proc/sys/fs/inotify/max_user_watches") static void sh_fInotify_set_nwatches() { if (ShfInotifyWatches == 0) return; if (0 == access(PROC_WATCHES_MAX, R_OK|W_OK)) /* flawfinder: ignore */ { FILE * fd; if (NULL != (fd = fopen(PROC_WATCHES_MAX, "r+"))) { char str[128]; char * ret; char * ptr; unsigned long wn; str[0] = '\0'; ret = fgets(str, 128, fd); if (ret && *str != '\0') { wn = strtoul(str, &ptr, 0); if (*ptr == '\0' || *ptr == '\n') { if (wn < ShfInotifyWatches) { sl_snprintf(str, sizeof(str), "%lu\n", ShfInotifyWatches); (void) fseek(fd, 0L, SEEK_SET); fputs(str, fd); } sl_fclose(FIL__, __LINE__, fd); return; } } sl_fclose(FIL__, __LINE__, fd); } } SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN, _("Cannot set max_user_watches"), _("sh_fInotify_set_nwatches")); SH_MUTEX_UNLOCK(mutex_thread_nolog); return; } /* The watch fd is thread specific. To have it in the fInotify thread, * the main thread writes a list of files/dirs to watch, and here we * now pop files from the list to add watches for them. */ static int sh_fInotify_init_internal() { char * filename; int class; unsigned long check_mask; int retval; int errnum; if (ShfInotifyActive == S_FALSE) return SH_MOD_FAILED; /* Wait until file scan is finished. */ while((sh.flag.inotify & SH_INOTIFY_DOSCAN) != 0) { retry_msleep(1,0); if (ShfInotifyActive == S_FALSE) return SH_MOD_FAILED; } sh_fInotify_set_nwatches(); while (NULL != (filename = sh_inotify_pop_dormant(&sh_file_watches, &class, &check_mask))) { retval = sh_inotify_add_watch(filename, &sh_file_watches, &errnum, class, check_mask); if (retval < 0) { char errbuf[SH_ERRBUF_SIZE]; sh_error_message(errnum, errbuf, sizeof(errbuf)); if ((errnum == ENOENT) || (errnum == EEXIST)) { char * epath = sh_util_safe_name (filename); SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle( (class == SH_LEVEL_ALLIGNORE) ? ShDFLevel[class] : ShDFLevel[SH_ERR_T_FILE], FIL__, __LINE__, errnum, MSG_E_SUBGPATH, errbuf, _("sh_fInotify_init_internal"), epath); SH_MUTEX_UNLOCK(mutex_thread_nolog); SH_FREE(epath); } else { SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle((-1), FIL__, __LINE__, errnum, MSG_E_SUBGEN, errbuf, _("sh_fInotify_init_internal")); SH_MUTEX_UNLOCK(mutex_thread_nolog); } } } /* Need this because mod_check() may run after * DOSCAN is finished, hence wouldn't call init(). */ sh.flag.inotify &= ~SH_INOTIFY_NEEDINIT; return 0; } static int sh_fInotify_process(struct inotify_event * event) { int class; unsigned long check_mask; char * filename; if (event->wd >= 0) { filename = sh_inotify_search_item(&sh_file_watches, event->wd, &class, &check_mask); if (filename) { sh_fInotify_report(event, filename, class, check_mask); SH_FREE(filename); } else if (sh.flag.inotify & SH_INOTIFY_NEEDINIT) { return 1; } else if ((event->mask & IN_UNMOUNT) == 0 && (event->mask & IN_IGNORED) == 0) { /* Remove watch ? Seems reasonable. */ sh_inotify_rm_watch(NULL, NULL, event->wd); SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle((-1), FIL__, __LINE__, event->wd, MSG_E_SUBGEN, _("Watch removed: internal error - file path unknown"), _("sh_fInotify_process")); SH_MUTEX_UNLOCK(mutex_thread_nolog); } } else if ((event->mask & IN_Q_OVERFLOW) != 0) { sh.flag.inotify |= SH_INOTIFY_DOSCAN; sh.flag.inotify |= SH_INOTIFY_NEEDINIT; return 1; } return 0; } void sh_fInotify_report_add(char * path, int class, unsigned long check_mask) { if (S_FALSE == sh_ignore_chk_new(path)) { int reported = 0; sh_files_clear_file_reported(path); sh_files_search_file(path, &class, &check_mask, &reported); sh_files_filecheck (class, check_mask, path, NULL, &reported, 0); if (SH_FFLAG_REPORTED_SET(reported)) sh_files_set_file_reported(path); } return; } static void sh_fInotify_report_miss(char * name, int level) { char * tmp = sh_util_safe_name (name); SH_MUTEX_LOCK(mutex_thread_nolog); sh_error_handle (level, FIL__, __LINE__, 0, MSG_FI_MISS, tmp); SH_MUTEX_UNLOCK(mutex_thread_nolog); SH_FREE(tmp); return; } static int sh_fInotify_report(struct inotify_event * event, char * filename, int class, unsigned long check_mask) { char * fullpath = NULL; char * path; int reported; if (event->len > 0) { fullpath = sh_util_strconcat(filename, "/", event->name, NULL); path = fullpath; } else { path = filename; } if ( (event->mask & (IN_ACCESS|IN_MODIFY)) != 0) { sh_files_search_file(path, &class, &check_mask, &reported); sh_files_filecheck (class, check_mask, filename, (event->len > 0) ? event->name : NULL, &reported, 0); } else if ((event->mask & (IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF|IN_MOVED_FROM)) != 0) { int isdir = (event->mask & IN_ISDIR); int level = (class == SH_LEVEL_ALLIGNORE) ? ShDFLevel[class] : ShDFLevel[(isdir == 0) ? SH_ERR_T_FILE : SH_ERR_T_DIR]; if (S_FALSE == sh_ignore_chk_del(path)) { if (0 != hashreport_missing(path, level)) { sh_fInotify_report_miss(path, level); } } #ifndef REPLACE_OLD sh_hash_set_visited_true(path); #else sh_hash_set_missing(path); #endif if (sh.flag.reportonce == S_TRUE) sh_files_set_file_reported(path); /* Move to 'dormant' list. */ sh_inotify_rm_watch(&sh_file_watches, &sh_file_missing, event->wd); } else if((event->mask & (IN_CREATE|IN_MOVED_TO)) != 0) { if (S_FALSE == sh_ignore_chk_new(path)) { sh_files_clear_file_reported(path); sh_files_search_file(path, &class, &check_mask, &reported); sh_files_filecheck (class, check_mask, filename, (event->len > 0) ? event->name : NULL, &reported, 0); if (SH_FFLAG_REPORTED_SET(reported)) sh_files_set_file_reported(path); } } return 0; } #endif #endif