Index: trunk/include/sh_fInotify.h
===================================================================
--- trunk/include/sh_fInotify.h	(revision 368)
+++ trunk/include/sh_fInotify.h	(revision 368)
@@ -0,0 +1,13 @@
+
+#ifndef SH_F_INOTIFY_H
+#define SH_F_INOTIFY_H
+
+int sh_fInotify_init(struct mod_type * arg);
+int sh_fInotify_timer(time_t tcurrent);
+int sh_fInotify_run(void);
+int sh_fInotify_reconf(void);
+int sh_fInotify_cleanup(void);
+
+extern sh_rconf sh_fInotify_table[];
+
+#endif
Index: trunk/src/sh_fInotify.c
===================================================================
--- trunk/src/sh_fInotify.c	(revision 368)
+++ trunk/src/sh_fInotify.c	(revision 368)
@@ -0,0 +1,497 @@
+/* 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 <sys/inotify.h>
+
+/* --- 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
