/* 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;
}