/* SAMHAIN file system integrity testing */ /* Copyright (C) 2009 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. */ #include "config_xor.h" #if defined(HAVE_SYS_INOTIFY_H) #undef FIL__ #define FIL__ _("sh_inotify.c") /* printf */ #include #include #include #include #include #include #include #include "samhain.h" #include "sh_pthread.h" #include "sh_calls.h" #include "sh_inotify.h" #include "sh_mem.h" #include "slib.h" /************************************************** * * Make the inotify fd thread-specific by * encapsulating it in get/set functions: * sh_get_inotify_fd() / sh_set_inotify_fd() * **************************************************/ #if defined(HAVE_PTHREAD) static pthread_key_t inotify_key; static pthread_once_t inotify_key_once = PTHREAD_ONCE_INIT; static void make_inotify_key() { (void) pthread_key_create(&inotify_key, free); } static int sh_get_inotify_fd() { void * ptr; int * fp; (void) pthread_once(&inotify_key_once, make_inotify_key); if ((ptr = pthread_getspecific(inotify_key)) == NULL) { ptr = malloc(sizeof(int)); if (ptr) { fp = (int*) ptr; *fp = -1; (void) pthread_setspecific(inotify_key, ptr); } else { return -1; } } else { fp = (int*) ptr; } return *fp; } static void sh_set_inotify_fd(int fd) { int * fp; fp = (int*) pthread_getspecific(inotify_key); if (fp) *fp = fd; return; } /* !defined(HAVE_PTHREAD) */ #else static int sh_inotify_fd = -1; static inline int sh_get_inotify_fd() { return sh_inotify_fd; } static inline void sh_set_inotify_fd(int fd) { sh_inotify_fd = fd; } #endif /*--- nothing thread-related below this point --- */ /************************************************** * * Get inotify fd, initialize inotify if necessary * **************************************************/ #define SH_INOTIFY_FAILED -2 static int sh_inotify_getfd() { int ifd = sh_get_inotify_fd(); if (ifd >= 0) { return ifd; } else if (ifd == SH_INOTIFY_FAILED) { return -1; } else /* if (ifd == -1) */ { #if defined(HAVE_INOTIFY_INIT1) ifd = inotify_init1(IN_CLOEXEC); #else ifd = inotify_init(); if (ifd >= 0) { long sflags; sflags = retry_fcntl(FIL__, __LINE__, fd, F_GETFD, 0); retry_fcntl(FIL__, __LINE__, fd, F_SETFD, sflags|FD_CLOEXEC); } #endif if (ifd < 0) { sh_set_inotify_fd(SH_INOTIFY_FAILED); return -1; } sh_set_inotify_fd(ifd); return ifd; } } /************************************************** * * Public function: * int sh_inotify_wait_for_change(char * filename, * int watch, * int * errnum, * int waitsec); * Returns: watch, if nonnegative * -1 on error or reopen required * (check errnum != 0) * * Caller needs to keep track of watch descriptor * **************************************************/ #define SH_INOTIFY_REOPEN 0 #define SH_INOTIFY_MODIFY 1 void sh_inotify_remove(sh_watches * watches) { int i; int ifd = sh_inotify_getfd(); for (i = 0; i < watches->count; ++i) { if (watches->file[i]) { SH_FREE (watches->file[i]); watches->file[i] = 0; } watches->watch[i] = 0; watches->flag[i] = 0; } watches->count = 0; if (ifd >= 0) close(ifd); sh_set_inotify_fd(-1); return; } static int index_watched_file(char * filename, sh_watches * watches) { int i; for (i = 0; i < watches->count; ++i) { if (0 == strcmp(filename, watches->file[i])) return i; } return -1; } /* This function is idempotent; it will add the watch only once */ int sh_inotify_add_watch(char * filename, sh_watches * watches, int * errnum) { size_t len; *errnum = 0; if (filename) { int nwatch; int index = index_watched_file(filename, watches); if (index < 0) { int ifd = sh_inotify_getfd(); if (watches->count == SH_INOTIFY_MAX) { #ifdef EMFILE *errnum = EMFILE; #else *errnum = 24; #endif return -1; } nwatch = inotify_add_watch (ifd, filename, IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT); if (nwatch < 0) { *errnum = errno; return -1; } watches->watch[watches->count] = nwatch; watches->flag[watches->count] = 0; len = strlen(filename) + 1; watches->file[watches->count] = SH_ALLOC(len); sl_strlcpy(watches->file[watches->count], filename, len); ++(watches->count); } } return 0; } int sh_inotify_wait_for_change(char * filename, sh_watches * watches, int * errnum, int waitsec) { int ifd = sh_inotify_getfd(); *errnum = 0; start_it: if (ifd >= 0) { ssize_t len = -1; ssize_t i = 0; int flag = 0; char buffer[1024]; /* -- Add watch if required */ if (filename) { if (sh_inotify_add_watch(filename, watches, errnum) < 0) { retry_msleep(waitsec, 0); return -1; } } for (i = 0; i < watches->count; ++i) { if (watches->watch[i] == -1) watches->watch[i] = inotify_add_watch (ifd, watches->file[i], IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT); } /* -- Blocking read on inotify file descriptor */ do { len = read (ifd, &buffer, sizeof(buffer)); } while (len < 0 || errno == EINTR); fprintf (stderr, "FIXME buflen=%ld\n", (long int) len); if (len > 0) { int j; struct inotify_event *event; i = 0; while (i < len) { event = (struct inotify_event *) &buffer[i]; fprintf (stderr, "FIXME wd=%d mask=%u cookie=%u len=%u\n", event->wd, event->mask, event->cookie, event->len); if (event->len > 0) fprintf (stderr, "FIXME name=%s\n", event->name); for (j = 0; j < watches->count; ++j) { fprintf (stderr, "FIXME %d watch=%d file=%s\n", j, watches->watch[j], watches->file[j]); if (watches->watch[j] == event->wd) { fprintf (stderr, "FIXME wd=%d mask=%u\n", event->wd, event->mask); if (event->mask & IN_MODIFY) { watches->flag[j] |= SH_INOTIFY_MODIFY; flag |= SH_INOTIFY_MODIFY; } else if (event->mask & IN_DELETE_SELF || event->mask & IN_UNMOUNT || event->mask & IN_MOVE_SELF ) { watches->flag[j] |= SH_INOTIFY_REOPEN; (void) inotify_rm_watch(ifd, watches->watch[j]); watches->watch[j] = -1; flag |= SH_INOTIFY_REOPEN; } } } i += sizeof (struct inotify_event) + event->len; } } else if (len == -1) { *errnum = errno; retry_msleep(waitsec, 0); return -1; } if (flag & SH_INOTIFY_REOPEN) { if (flag & SH_INOTIFY_MODIFY) return 0; else goto start_it; } return 0; } /* Inotify not working, sleep */ retry_msleep(waitsec, 0); *errnum = 0; return -1; } /* !defined(HAVE_SYS_INOTIFY_H) */ #else #include "sh_calls.h" void sh_inotify_remove(sh_watches * watches) { (void) watches; return; } int sh_inotify_wait_for_change(char * filename, sh_watches * watches, int * errnum, int waitsec) { (void) filename; (void) watches; /* Inotify not working, sleep for waitsec seconds */ retry_msleep(waitsec, 0); *errnum = 0; return -1; } int sh_inotify_add_watch(char * filename, sh_watches * watches, int * errnum) { (void) filename; (void) watches; *errnum = 0; return 0; } #endif