/* hackers_watch.c - watch for changes in hackers.git * * Copyright (C) 2014 Luke Shumaker * * 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 3 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, see . */ #define _GNU_SOURCE #include #include #include /* for asprintf(3) */ #include /* for chdir(3) */ #include "common/inotify_helpers.h" #include "hackers_parse.h" #include "hackers_watch.h" #include "log.h" #define EVENT_FILE_IS(event, str) \ (((event)->len == sizeof(str)) && (strcmp((event)->name, str) == 0)) #define EVENT_CHILD_ADD (IN_CREATE | IN_MOVED_TO) #define EVENT_CHILD_DEL (IN_DELETE | IN_MOVED_FROM) #define EVENT_CHILD_MOD (IN_CLOSE_WRITE | IN_MOVED_TO) #define EVENT_CHILD_ANY (EVENT_CHILD_ADD | EVENT_CHILD_DEL | EVENT_CHILD_MOD) #define WATCH_HOMEDIR(session, i) \ do { \ errno = 0; \ session->in_user_wds[i] = \ inotify_add_watch(session->in_fd, \ session->users[i].pw_dir, \ EVENT_CHILD_ANY | IN_MOVE_SELF); \ if (session->in_user_wds[i] < 0) { \ log_log(LOG_INFO, "could not watch: %s", \ session->users[i].pw_dir); \ /* don't goto error, it OK here */ \ } \ } while(0) struct session * hackers_session_allocate() { int err = 0; struct session *sess; sess = MALLOC(sizeof(struct session)); if ((err = pthread_rwlock_init(&(sess->lock), NULL)) != 0) { errno = err; log_log(LOG_ERR, "could not initialize session rwlock"); goto error; } sess->cnt = 0; sess->users = NULL; sess->yamldir = NULL; sess->in_user_wds = NULL; sess->in_fd = -1; sess->in_wd_home = -1; sess->in_wd_yaml = -1; return sess; error: return NULL; } static int hackers_session_resize(struct session *sess, size_t num_users) { log_log(LOG_DEBUG, "resizing session to %zu users", num_users); if (num_users <= sess->cnt) return 0; sess->users = REALLOC(sess->users , num_users * sizeof(sess->users[0])); sess->in_user_wds = REALLOC(sess->in_user_wds, num_users * sizeof(sess->in_user_wds[0])); for (size_t i = sess->cnt; i < num_users; i++) { ZERO(sess->users[i]); sess->users[i].pw_uid = UID_INVALID; sess->in_user_wds[i] = -1; } sess->cnt = num_users; return 0; error: return -1; } /* does NOT mess with the lock */ static void hackers_session_close(struct session *sess) { log_log(LOG_DEBUG, "closing session"); for (size_t i = 0; i < sess->cnt; i++) { PASSWD_FREE(sess->users[i]); } free(sess->users); sess->users = NULL; sess->cnt = 0; free(sess->yamldir); sess->yamldir = NULL; free(sess->in_user_wds); sess->in_user_wds = NULL; if (sess->in_fd >= 0) { close(sess->in_fd); sess->in_fd = -1; } sess->in_wd_home = -1; sess->in_wd_yaml = -1; } int hackers_session_open(struct session *sess, const char *yamldir) { log_log(LOG_DEBUG, "opening session at `%s'", yamldir); char *glob_pattern; glob_t glob_results; char *filename; hackers_session_close(sess); ASSERT((sess->yamldir = strdup(yamldir)) != NULL); ASSERT((sess->in_fd = inotify_init()) >= 0); ASSERT((sess->in_wd_yaml = inotify_add_watch(sess->in_fd, yamldir, EVENT_CHILD_ANY)) >= 0); ASSERT((sess->in_wd_home = inotify_add_watch(sess->in_fd, "/home" , EVENT_CHILD_ADD)) >= 0); ASSERT(asprintf(&glob_pattern, "%s/*.yml", yamldir) > 0); ASSERT(glob(glob_pattern, 0, NULL, &glob_results) == 0); free(glob_pattern); ASSERT(hackers_session_resize(sess, glob_results.gl_pathc - glob_results.gl_offs)==0); for (size_t i = 0; i < sess->cnt; i++) { filename = glob_results.gl_pathv[glob_results.gl_offs+i]; log_log(LOG_DEBUG, "loading yaml file: %s", filename); if (load_user_yaml(filename, &(sess->users[i]))==0) { log_log(LOG_DEBUG, "... and watching homedir"); WATCH_HOMEDIR(sess, i); } else { log_log(LOG_DEBUG, "... error"); sess->users[i].pw_uid = UID_INVALID; sess->in_user_wds[i] = -1; } } globfree(&glob_results); return 0; error: hackers_session_close(sess); return -1; } static void worker_watch_homedirs(struct session *sess) { pthread_rwlock_wrlock(&(sess->lock)); for (size_t i = 0; i < sess->cnt; i++) { if (sess->users[i].pw_uid != UID_INVALID) { int oldwd = sess->in_user_wds[i]; WATCH_HOMEDIR(sess, i); if (oldwd != sess->in_user_wds[i]) { if (oldwd != -1) inotify_rm_watch(sess->in_fd, oldwd); load_user_password(&(sess->users[i])); } } } pthread_rwlock_unlock(&(sess->lock)); } static int worker_handle_add_yaml(struct session *sess, struct passwd *newdata) { /* We have to first see if this is for an existing UID, then * if it's not, we need to find an empty slot, potentially * realloc()ing the array. */ ssize_t spot = -1; pthread_rwlock_wrlock(&(sess->lock)); for (size_t i = 0; i < sess->cnt; i++) { if (spot < 0 && sess->users[i].pw_uid == UID_INVALID) { spot = i; } if (sess->users[i].pw_uid == newdata->pw_uid) { spot = i; break; } } if (spot < 0) { /* must grow the array */ if (hackers_session_resize(sess, sess->cnt+1) != 0) goto error; } else if (sess->users[spot].pw_uid != UID_INVALID) { PASSWD_FREE(sess->users[spot]); } sess->users[spot] = *newdata; pthread_rwlock_unlock(&(sess->lock)); return 0; error: pthread_rwlock_unlock(&(sess->lock)); return -1; } static void worker_handle_del_yaml(struct session *sess, uid_t uid) { pthread_rwlock_wrlock(&(sess->lock)); for (size_t i = 0; i < sess->cnt; i++) { if (sess->users[i].pw_uid == uid) { PASSWD_FREE(sess->users[i]); inotify_rm_watch(sess->in_fd, sess->in_user_wds[i]); break; } } pthread_rwlock_unlock(&(sess->lock)); } int hackers_session_worker(struct session *sess) { if (chdir(sess->yamldir) < 0) return -1; for (INOTIFY_ITERATOR(sess->in_fd, event)) { if (event->wd == sess->in_wd_yaml) { /* handle updates to yaml files */ if (event->mask & (EVENT_CHILD_ADD | EVENT_CHILD_MOD)) { struct passwd user; ZERO(user); if (load_user_yaml(event->name, &user)==0) { /* User added/updated */ if (worker_handle_add_yaml(sess, &user) !=0) return -1; } else if (user.pw_uid != UID_INVALID) { /* User became invalid */ worker_handle_del_yaml(sess, user.pw_uid); } } else if (event->mask & EVENT_CHILD_DEL) { uid_t uid = filename2uid(event->name); if (uid != UID_INVALID) { worker_handle_del_yaml(sess, uid); } } } else if ((event->wd == sess->in_wd_home) && (event->mask & IN_ISDIR)) { /* handle added home directory */ worker_watch_homedirs(sess); } else { /* handle a change to someone's password */ if (event->mask & IN_MOVE_SELF) { inotify_rm_watch(sess->in_fd, event->wd); worker_watch_homedirs(sess); } if ((event->mask & IN_IGNORED) || EVENT_FILE_IS(event, ".password")) { pthread_rwlock_wrlock(&(sess->lock)); for (size_t i = 0; i < sess->cnt; i++) { if (sess->in_user_wds[i] == event->wd) { if (event->mask & IN_IGNORED) sess->in_user_wds[i] = -1; load_user_password(&(sess->users[i])); break; } } pthread_rwlock_unlock(&(sess->lock)); } } } return -1; }