/* 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 "sh_utils.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 --- */ #include "zAVLTree.h" typedef struct { int watch; int flag; char * file; } sh_watch; /************************************************** * * 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__, ifd, F_GETFD, 0); retry_fcntl(FIL__, __LINE__, ifd, 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 static void sh_inotify_init(sh_watches * watches) { watches->list_of_watches = NULL; watches->count = 0; watches->max_count = 0; return; } static void sh_inotify_free_watch(void * item) { sh_watch * this = (sh_watch *) item; if (this->file) SH_FREE(this->file); SH_FREE(this); return; } static sh_watch * sh_inotify_create_watch(char * file, int nwatch, int flag) { sh_watch * this = SH_ALLOC(sizeof(sh_watch)); this->file = sh_util_strdup(file); this->watch = nwatch; this->flag = flag; return this; } /********** List Handling ******************/ struct sh_inotify_litem { sh_watch * watch; struct sh_inotify_litem * next; }; typedef struct { struct sh_inotify_litem *prenode; struct sh_inotify_litem *curnode; } sh_inotify_listCursor; static sh_watch * sh_inotify_list_first(sh_inotify_listCursor * listcursor, sh_watches * watches) { listcursor->prenode = watches->dormant_watches; listcursor->curnode = watches->dormant_watches; return listcursor->curnode->watch; } static sh_watch * sh_inotify_list_next(sh_inotify_listCursor * listcursor, sh_watches * watches) { (void) watches; listcursor->prenode = listcursor->curnode; listcursor->curnode = listcursor->curnode->next; if (listcursor->curnode) return listcursor->curnode->watch; return NULL; } static void sh_inotify_listitem_destroy(struct sh_inotify_litem * this) { SH_FREE(this); return; } static sh_watch * sh_inotify_list_del_current(sh_inotify_listCursor * listcursor, sh_watches * watches) { sh_watch * ret = NULL; if (listcursor->curnode) { struct sh_inotify_litem * this = listcursor->curnode; if (listcursor->prenode == this) { watches->dormant_watches = this->next; listcursor->prenode = watches->dormant_watches; listcursor->curnode = watches->dormant_watches; } else { listcursor->prenode->next = this->next; listcursor->curnode = this->next; } ret = listcursor->curnode->watch; sh_inotify_listitem_destroy(this); } return ret; } static int sh_inotify_add_dormant(sh_watches * watches, sh_watch * item) { struct sh_inotify_litem * this = SH_ALLOC(sizeof(struct sh_inotify_litem)); this->watch = item; this->next = (struct sh_inotify_litem *) watches->dormant_watches; watches->dormant_watches = this; return 0; } /********** End List Handling **************/ static zAVLKey sh_inotify_getkey(void const *item) { return (&((sh_watch *)item)->watch); } /* This function removes all watches from the list, * and closes the inode file descriptor in this thread. */ void sh_inotify_remove(sh_watches * watches) { int ifd = sh_inotify_getfd(); zAVLTree * all_watches = (zAVLTree *)(watches->list_of_watches); if (all_watches) zAVLFreeTree(all_watches, sh_inotify_free_watch); sh_inotify_init(watches); if (ifd >= 0) close(ifd); sh_set_inotify_fd(-1); watches->count = 0; return; } static int index_watched_file(char * filename, sh_watches * watches) { sh_watch * item; zAVLCursor avlcursor; zAVLTree * all_watches = (zAVLTree *)(watches->list_of_watches); if (all_watches) { for (item = (sh_watch *) zAVLFirst(&avlcursor, all_watches); item; item = (sh_watch *) zAVLNext(&avlcursor)) { if (item->file) { if (0 == strcmp(filename, item->file)) return item->watch; } } } 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) { *errnum = 0; if (filename) { int nwatch; sh_watch * item; 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; } item = sh_inotify_create_watch(filename, nwatch, /* flag */ 0); if (NULL == watches->list_of_watches) watches->list_of_watches = zAVLAllocTree (sh_inotify_getkey, zAVL_KEY_INT); if (watches->list_of_watches) { *errnum = zAVLInsert((zAVLTree *)(watches->list_of_watches), item); if (*errnum != 0) { sh_inotify_free_watch(item); return -1; } } else { *errnum = -1; return -1; } ++(watches->count); } } return 0; } int sh_inotify_wait_for_change(char * filename, sh_watches * watches, int * errnum, int waitsec) { sh_watch * item; zAVLTree * all_watches = (zAVLTree *)(watches->list_of_watches); 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]; sh_inotify_listCursor listcursor; /* -- Add watch if required */ if (filename) { if (sh_inotify_add_watch(filename, watches, errnum) < 0) { retry_msleep(waitsec, 0); return -1; } } /* -- Check dormant watches for reopening. */ for (item = sh_inotify_list_first(&listcursor, watches); item; item = sh_inotify_list_next(&listcursor, watches)) { have_next: if (item->file && item->watch == -1) { item->watch = inotify_add_watch (ifd, item->file, IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT); if (item->watch >= 0) { zAVLInsert(all_watches, item); item = sh_inotify_list_del_current(&listcursor, watches); goto have_next; } } } /* -- Blocking read on inotify file descriptor */ do { len = read (ifd, &buffer, sizeof(buffer)); } while (len < 0 || errno == EINTR); if (len > 0) { struct inotify_event *event; i = 0; while (i < len) { event = (struct inotify_event *) &buffer[i]; item = zAVLSearch(all_watches, &(event->wd)); if (item) { if (event->mask & IN_MODIFY) { item->flag |= SH_INOTIFY_MODIFY; flag |= SH_INOTIFY_MODIFY; } else if (event->mask & IN_DELETE_SELF || event->mask & IN_UNMOUNT || event->mask & IN_MOVE_SELF ) { item->flag |= SH_INOTIFY_REOPEN; (void) inotify_rm_watch(ifd, item->watch); zAVLDelete(all_watches, item); sh_inotify_add_dormant(watches, item); item->watch = -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" #include "sh_inotify.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