#define _GNU_SOURCE #include <sys/inotify.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <ctype.h> #include <sys/types.h> #include <syslog.h> #include "restore.h" #include <glob.h> #include <libgen.h> #include <sys/stat.h> #include <string.h> #include <stdio.h> #include <fcntl.h> #include <selinux/selinux.h> #include "restorecond.h" #include "stringslist.h" #include "utmpwatcher.h" #ifndef GLOB_TILDE #define GLOB_TILDE 0 #endif #ifndef GLOB_BRACE #define GLOB_BRACE 0 #endif /* size of the event structure, not counting name */ #define EVENT_SIZE (sizeof (struct inotify_event)) /* reasonable guess as to size of 1024 events */ #define BUF_LEN (1024 * (EVENT_SIZE + 16)) struct watchList { struct watchList *next; int wd; char *dir; struct stringsList *files; }; struct watchList *firstDir = NULL; int watch_list_isempty(void) { return firstDir == NULL; } void watch_list_add(int fd, const char *path) { struct watchList *ptr = NULL; size_t i = 0; struct watchList *prev = NULL; glob_t globbuf; char *x = strdup(path); if (!x) exitApp("Out of Memory"); char *file = basename(x); char *dir = dirname(x); ptr = firstDir; int len; globbuf.gl_offs = 1; if (glob(path, GLOB_TILDE | GLOB_PERIOD, NULL, &globbuf) >= 0) { for (i = 0; i < globbuf.gl_pathc; i++) { len = strlen(globbuf.gl_pathv[i]) - 2; if (len > 0 && strcmp(&globbuf.gl_pathv[i][len--], "/.") == 0) continue; if (len > 0 && strcmp(&globbuf.gl_pathv[i][len], "/..") == 0) continue; selinux_restorecon(globbuf.gl_pathv[i], r_opts.restorecon_flags); } globfree(&globbuf); } while (ptr != NULL) { if (strcmp(dir, ptr->dir) == 0) { strings_list_add(&ptr->files, file); goto end; } prev = ptr; ptr = ptr->next; } ptr = calloc(1, sizeof(struct watchList)); if (!ptr) exitApp("Out of Memory"); ptr->wd = inotify_add_watch(fd, dir, IN_CREATE | IN_MOVED_TO); if (ptr->wd == -1) { free(ptr); if (! run_as_user) syslog(LOG_ERR, "Unable to watch (%s) %s\n", path, strerror(errno)); goto end; } ptr->dir = strdup(dir); if (!ptr->dir) exitApp("Out of Memory"); strings_list_add(&ptr->files, file); if (prev) prev->next = ptr; else firstDir = ptr; if (debug_mode) printf("%d: Dir=%s, File=%s\n", ptr->wd, ptr->dir, file); end: free(x); return; } /* A file was in a direcroty has been created. This function checks to see if it is one that we are watching. */ int watch_list_find(int wd, const char *file) { struct watchList *ptr = NULL; ptr = firstDir; if (debug_mode) printf("%d: File=%s\n", wd, file); while (ptr != NULL) { if (ptr->wd == wd) { int exact=0; if (strings_list_find(ptr->files, file, &exact) == 0) { char *path = NULL; if (asprintf(&path, "%s/%s", ptr->dir, file) < 0) exitApp("Error allocating memory."); selinux_restorecon(path, r_opts.restorecon_flags); free(path); return 0; } if (debug_mode) strings_list_print(ptr->files); /* Not found in this directory */ return -1; } ptr = ptr->next; } /* Did not find a directory */ return -1; } void watch_list_free(int fd) { struct watchList *ptr = NULL; struct watchList *prev = NULL; ptr = firstDir; while (ptr != NULL) { inotify_rm_watch(fd, ptr->wd); strings_list_free(ptr->files); free(ptr->dir); prev = ptr; ptr = ptr->next; free(prev); } firstDir = NULL; } /* Inotify watch loop */ int watch(int fd, const char *watch_file) { char buf[BUF_LEN]; int len, i = 0; if (firstDir == NULL) return 0; len = read(fd, buf, BUF_LEN); if (len < 0) { if (terminate == 0) { syslog(LOG_ERR, "Read error (%s)", strerror(errno)); return 0; } syslog(LOG_INFO, "terminated"); return -1; } else if (!len) /* BUF_LEN too small? */ return -1; while (i < len) { struct inotify_event *event; event = (struct inotify_event *)&buf[i]; if (debug_mode) printf("wd=%d mask=%u cookie=%u len=%u\n", event->wd, event->mask, event->cookie, event->len); if (event->mask & ~IN_IGNORED) { if (event->wd == master_wd) read_config(fd, watch_file); else { switch (utmpwatcher_handle(fd, event->wd)) { case -1: /* Message was not for utmpwatcher */ if (event->len) watch_list_find(event->wd, event->name); break; case 1: /* utmp has changed need to reload */ read_config(fd, watch_file); break; default: /* No users logged in or out */ break; } } } i += EVENT_SIZE + event->len; } return 0; } static void process_config(int fd, FILE * cfg) { char *line_buf = NULL; size_t len = 0; while (getline(&line_buf, &len, cfg) > 0) { char *buffer = line_buf; while (isspace(*buffer)) buffer++; if (buffer[0] == '#') continue; int l = strlen(buffer) - 1; if (l <= 0) continue; buffer[l] = 0; if (buffer[0] == '~') { if (run_as_user) { char *ptr=NULL; if (asprintf(&ptr, "%s%s", homedir, &buffer[1]) < 0) exitApp("Error allocating memory."); watch_list_add(fd, ptr); free(ptr); } else { utmpwatcher_add(fd, &buffer[1]); } } else { watch_list_add(fd, buffer); } } free(line_buf); } /* Read config file ignoring Comment lines Files specified one per line. Files with "~" will be expanded to the logged in users homedirs. */ void read_config(int fd, const char *watch_file_path) { FILE *cfg = NULL; if (debug_mode) printf("Read Config\n"); watch_list_free(fd); cfg = fopen(watch_file_path, "r"); if (!cfg){ perror(watch_file_path); exitApp("Error reading config file"); } process_config(fd, cfg); fclose(cfg); inotify_rm_watch(fd, master_wd); master_wd = inotify_add_watch(fd, watch_file_path, IN_MOVED_FROM | IN_MODIFY); if (master_wd == -1) exitApp("Error watching config file."); }