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