/* 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 /* for asprintf(3) */ #include /* for chdir(3) */ #include "common/inotify_helpers.h" #include "hackers_parse.h" #include "hackers_watch.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) \ session->in_user_wds[i] = \ inotify_add_watch(session->in_fd, \ session->users[i].pw_dir, \ EVENT_CHILD_ANY | IN_MOVE_SELF) int hackers_init(const char *yamldir, struct session *sess) { char *glob_pattern; glob_t glob_results; char *filename; sess->yamldir = strdup(yamldir); pthread_rwlock_init(&(sess->lock), NULL); sess->in_fd = inotify_init(); sess->in_wd_yaml = inotify_add_watch(sess->in_fd, yamldir, EVENT_CHILD_ANY); sess->in_wd_home = inotify_add_watch(sess->in_fd, "/home" , EVENT_CHILD_ADD); asprintf(&glob_pattern, "%s/*.yml", yamldir); glob(glob_pattern, 0, NULL, &glob_results); free(glob_pattern); sess->cnt = glob_results.gl_pathc - glob_results.gl_offs; sess->users = calloc(sess->cnt, sizeof(struct passwd)); sess->in_user_wds = calloc(sess->cnt, sizeof(int)); for (size_t i = 0; i < sess->cnt; i++) { filename = glob_results.gl_pathv[glob_results.gl_offs+i]; if (load_user_yaml(filename, &(sess->users[i]))==0) { WATCH_HOMEDIR(sess, i); } else { sess->users[i].pw_uid = 0; sess->in_user_wds[i] = -1; } } globfree(&glob_results); return 0; } 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 > 0) { 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 void 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. */ pthread_rwlock_wrlock(&(sess->lock)); ssize_t spot = -1; for (size_t i = 0; i < sess->cnt; i++) { if (spot < 0 && sess->users[i].pw_uid == 0) { spot = i; } if (sess->users[i].pw_uid == newdata->pw_uid) { spot = i; break; } } if (spot < 0) { /* must grow the array */ spot = sess->cnt++; sess->users = realloc(sess->users , sess->cnt * sizeof(struct passwd)); sess->in_user_wds = realloc(sess->in_user_wds, sess->cnt * sizeof(int)); ZERO(sess->users[spot]); } else if (sess->users[spot].pw_uid != 0) { PASSWD_FREE(sess->users[spot]); } sess->users[spot] = *newdata; pthread_rwlock_unlock(&(sess->lock)); } 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_worker(struct session *sess) { chdir(sess->yamldir); 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 */ worker_handle_add_yaml(sess, &user); } else if (user.pw_uid > 0) { /* 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 > 0) { 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; }