#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <bitstring.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include "pathnames.h"
#include "globals.h"
#include "macros.h"
#include "structs.h"
#include "funcs.h"
#define HASH(a,b) ((a)+(b))
static void process_crontab(int, const char *, const char *,
struct stat *, cron_db *, cron_db *);
void
load_database(cron_db **db)
{
struct stat statbuf, syscron_stat;
cron_db *new_db, *old_db = *db;
struct timespec mtime;
struct dirent *dp;
DIR *dir;
user *u;
if (stat(_PATH_CRON_SPOOL, &statbuf) == -1) {
syslog(LOG_ERR, "(CRON) STAT FAILED (%s)", _PATH_CRON_SPOOL);
return;
}
if (stat(_PATH_SYS_CRONTAB, &syscron_stat) == -1)
timespecclear(&syscron_stat.st_mtim);
mtime.tv_sec =
HASH(statbuf.st_mtim.tv_sec, syscron_stat.st_mtim.tv_sec);
mtime.tv_nsec =
HASH(statbuf.st_mtim.tv_nsec, syscron_stat.st_mtim.tv_nsec);
if (old_db != NULL && timespeccmp(&mtime, &old_db->mtime, ==))
return;
if ((new_db = malloc(sizeof(*new_db))) == NULL)
return;
new_db->mtime = mtime;
TAILQ_INIT(&new_db->users);
if (timespecisset(&syscron_stat.st_mtim)) {
process_crontab(AT_FDCWD, "*system*", _PATH_SYS_CRONTAB,
&syscron_stat, new_db, old_db);
}
if (!(dir = opendir(_PATH_CRON_SPOOL))) {
syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_CRON_SPOOL);
if (!TAILQ_EMPTY(&new_db->users) &&
(u = TAILQ_FIRST(&old_db->users))) {
if (strcmp(u->name, "*system*") == 0) {
TAILQ_REMOVE(&old_db->users, u, entries);
free_user(u);
TAILQ_INSERT_HEAD(&old_db->users,
TAILQ_FIRST(&new_db->users), entries);
}
}
free(new_db);
return;
}
while (NULL != (dp = readdir(dir))) {
if (dp->d_name[0] == '.')
continue;
process_crontab(dirfd(dir), dp->d_name, dp->d_name,
&statbuf, new_db, old_db);
}
closedir(dir);
endpwent();
if (old_db != NULL) {
while ((u = TAILQ_FIRST(&old_db->users))) {
TAILQ_REMOVE(&old_db->users, u, entries);
free_user(u);
}
free(old_db);
}
*db = new_db;
}
user *
find_user(cron_db *db, const char *name)
{
user *u = NULL;
if (db != NULL) {
TAILQ_FOREACH(u, &db->users, entries) {
if (strcmp(u->name, name) == 0)
break;
}
}
return (u);
}
static void
process_crontab(int dfd, const char *uname, const char *fname,
struct stat *statbuf, cron_db *new_db, cron_db *old_db)
{
struct passwd *pw = NULL;
FILE *crontab_fp = NULL;
user *u, *new_u;
mode_t tabmask, tabperm;
int fd;
if (fname[0] != '/' && (pw = getpwnam(uname)) == NULL) {
syslog(LOG_WARNING, "(%s) ORPHAN (no passwd entry)", uname);
goto next_crontab;
}
fd = openat(dfd, fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);
if (fd == -1) {
syslog(LOG_ERR, "(%s) CAN'T OPEN (%s)", uname, fname);
goto next_crontab;
}
if (!(crontab_fp = fdopen(fd, "r"))) {
syslog(LOG_ERR, "(%s) FDOPEN (%m)", fname);
close(fd);
goto next_crontab;
}
if (fstat(fileno(crontab_fp), statbuf) == -1) {
syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", uname, fname);
goto next_crontab;
}
if (!S_ISREG(statbuf->st_mode)) {
syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", uname, fname);
goto next_crontab;
}
tabmask = pw ? ALLPERMS : (ALLPERMS & ~(S_IWUSR|S_IRGRP|S_IROTH));
tabperm = pw ? (S_IRUSR|S_IWUSR) : S_IRUSR;
if ((statbuf->st_mode & tabmask) != tabperm) {
syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", uname, fname);
goto next_crontab;
}
if (statbuf->st_uid != 0 && (pw == NULL ||
statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", uname, fname);
goto next_crontab;
}
if (pw != NULL && statbuf->st_gid != cron_gid) {
syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", uname, fname);
goto next_crontab;
}
if (pw != NULL && statbuf->st_nlink != 1) {
syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", uname, fname);
goto next_crontab;
}
u = find_user(old_db, fname);
if (u != NULL) {
if (timespeccmp(&u->mtime, &statbuf->st_mtim, ==)) {
TAILQ_REMOVE(&old_db->users, u, entries);
TAILQ_INSERT_TAIL(&new_db->users, u, entries);
goto next_crontab;
}
syslog(LOG_INFO, "(%s) RELOAD (%s)", uname, fname);
}
new_u = load_user(crontab_fp, pw, fname);
if (new_u != NULL) {
new_u->mtime = statbuf->st_mtim;
TAILQ_INSERT_TAIL(&new_db->users, new_u, entries);
if (u != NULL) {
TAILQ_REMOVE(&old_db->users, u, entries);
free_user(u);
}
} else if (u != NULL) {
TAILQ_REMOVE(&old_db->users, u, entries);
TAILQ_INSERT_TAIL(&new_db->users, u, entries);
}
next_crontab:
if (crontab_fp != NULL) {
fclose(crontab_fp);
}
}