1*df69c215Sderaadt /* $OpenBSD: database.c,v 1.38 2019/06/28 13:32:47 deraadt Exp $ */
2e134e629Smillert
3df930be7Sderaadt /* Copyright 1988,1990,1993,1994 by Paul Vixie
4a5198fa1Smillert * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5f454ebdeSmillert * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6df930be7Sderaadt *
7f454ebdeSmillert * Permission to use, copy, modify, and distribute this software for any
8f454ebdeSmillert * purpose with or without fee is hereby granted, provided that the above
9f454ebdeSmillert * copyright notice and this permission notice appear in all copies.
10df930be7Sderaadt *
11a5198fa1Smillert * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
12a5198fa1Smillert * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13a5198fa1Smillert * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
14a5198fa1Smillert * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a5198fa1Smillert * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a5198fa1Smillert * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17a5198fa1Smillert * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18df930be7Sderaadt */
19df930be7Sderaadt
20fa575ea2Smillert #include <sys/types.h>
21fa575ea2Smillert #include <sys/stat.h>
2295f2b4e2Smillert #include <sys/time.h>
23fa575ea2Smillert
24fa575ea2Smillert #include <bitstring.h> /* for structs.h */
25fa575ea2Smillert #include <dirent.h>
26fa575ea2Smillert #include <fcntl.h>
27fa575ea2Smillert #include <limits.h>
28fa575ea2Smillert #include <pwd.h>
29fa575ea2Smillert #include <stdio.h>
30fa575ea2Smillert #include <stdlib.h>
31fa575ea2Smillert #include <string.h>
32a38a3395Smillert #include <syslog.h>
33fa575ea2Smillert #include <time.h> /* for structs.h */
34fa575ea2Smillert #include <unistd.h>
35fa575ea2Smillert
36fa575ea2Smillert #include "pathnames.h"
37a9e807deSmillert #include "globals.h"
38fa575ea2Smillert #include "macros.h"
39fa575ea2Smillert #include "structs.h"
40fa575ea2Smillert #include "funcs.h"
41df930be7Sderaadt
42f326c411Sderaadt #define HASH(a,b) ((a)+(b))
43df930be7Sderaadt
4488959323Smillert static void process_crontab(int, const char *, const char *,
4588959323Smillert struct stat *, cron_db *, cron_db *);
46df930be7Sderaadt
47df930be7Sderaadt void
load_database(cron_db ** db)488a6f8ff3Smillert load_database(cron_db **db)
49785ecd14Stedu {
50f454ebdeSmillert struct stat statbuf, syscron_stat;
518a6f8ff3Smillert cron_db *new_db, *old_db = *db;
5295f2b4e2Smillert struct timespec mtime;
53afc409ecSmillert struct dirent *dp;
54f454ebdeSmillert DIR *dir;
558a6f8ff3Smillert user *u;
56df930be7Sderaadt
5788959323Smillert /* before we start loading any data, do a stat on _PATH_CRON_SPOOL
58df930be7Sderaadt * so that if anything changes as of this moment (i.e., before we've
59df930be7Sderaadt * cached any of the database), we'll see the changes next time.
60df930be7Sderaadt */
61*df69c215Sderaadt if (stat(_PATH_CRON_SPOOL, &statbuf) == -1) {
62a38a3395Smillert syslog(LOG_ERR, "(CRON) STAT FAILED (%s)", _PATH_CRON_SPOOL);
63051581bbSmillert return;
64df930be7Sderaadt }
65df930be7Sderaadt
66df930be7Sderaadt /* track system crontab file
67df930be7Sderaadt */
68*df69c215Sderaadt if (stat(_PATH_SYS_CRONTAB, &syscron_stat) == -1)
6995f2b4e2Smillert timespecclear(&syscron_stat.st_mtim);
7095f2b4e2Smillert
7195f2b4e2Smillert /* hash mtime of system crontab file and crontab dir
7295f2b4e2Smillert */
7395f2b4e2Smillert mtime.tv_sec =
7495f2b4e2Smillert HASH(statbuf.st_mtim.tv_sec, syscron_stat.st_mtim.tv_sec);
7595f2b4e2Smillert mtime.tv_nsec =
7695f2b4e2Smillert HASH(statbuf.st_mtim.tv_nsec, syscron_stat.st_mtim.tv_nsec);
77df930be7Sderaadt
78df930be7Sderaadt /* if spooldir's mtime has not changed, we don't need to fiddle with
79df930be7Sderaadt * the database.
80df930be7Sderaadt */
8195f2b4e2Smillert if (old_db != NULL && timespeccmp(&mtime, &old_db->mtime, ==))
82df930be7Sderaadt return;
83df930be7Sderaadt
84df930be7Sderaadt /* something's different. make a new database, moving unchanged
85df930be7Sderaadt * elements from the old database, reloading elements that have
86df930be7Sderaadt * actually changed. Whatever is left in the old database when
87df930be7Sderaadt * we're done is chaff -- crontabs that disappeared.
88df930be7Sderaadt */
898a6f8ff3Smillert if ((new_db = malloc(sizeof(*new_db))) == NULL)
908a6f8ff3Smillert return;
9195f2b4e2Smillert new_db->mtime = mtime;
928a6f8ff3Smillert TAILQ_INIT(&new_db->users);
93df930be7Sderaadt
9495f2b4e2Smillert if (timespecisset(&syscron_stat.st_mtim)) {
9588959323Smillert process_crontab(AT_FDCWD, "*system*", _PATH_SYS_CRONTAB,
9688959323Smillert &syscron_stat, new_db, old_db);
97df930be7Sderaadt }
98df930be7Sderaadt
99df930be7Sderaadt /* we used to keep this dir open all the time, for the sake of
100df930be7Sderaadt * efficiency. however, we need to close it in every fork, and
101df930be7Sderaadt * we fork a lot more often than the mtime of the dir changes.
102df930be7Sderaadt */
10388959323Smillert if (!(dir = opendir(_PATH_CRON_SPOOL))) {
104a38a3395Smillert syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_CRON_SPOOL);
1058a6f8ff3Smillert /* Restore system crontab entry as needed. */
1068a6f8ff3Smillert if (!TAILQ_EMPTY(&new_db->users) &&
1078a6f8ff3Smillert (u = TAILQ_FIRST(&old_db->users))) {
1088a6f8ff3Smillert if (strcmp(u->name, "*system*") == 0) {
1098a6f8ff3Smillert TAILQ_REMOVE(&old_db->users, u, entries);
1108a6f8ff3Smillert free_user(u);
1118a6f8ff3Smillert TAILQ_INSERT_HEAD(&old_db->users,
1128a6f8ff3Smillert TAILQ_FIRST(&new_db->users), entries);
1138a6f8ff3Smillert }
1148a6f8ff3Smillert }
1158a6f8ff3Smillert free(new_db);
116051581bbSmillert return;
117df930be7Sderaadt }
118df930be7Sderaadt
119df930be7Sderaadt while (NULL != (dp = readdir(dir))) {
120df930be7Sderaadt /* avoid file names beginning with ".". this is good
121df930be7Sderaadt * because we would otherwise waste two guaranteed calls
122df930be7Sderaadt * to getpwnam() for . and .., and also because user names
123df930be7Sderaadt * starting with a period are just too nasty to consider.
124df930be7Sderaadt */
125df930be7Sderaadt if (dp->d_name[0] == '.')
126df930be7Sderaadt continue;
127df930be7Sderaadt
12888959323Smillert process_crontab(dirfd(dir), dp->d_name, dp->d_name,
1298a6f8ff3Smillert &statbuf, new_db, old_db);
130df930be7Sderaadt }
131df930be7Sderaadt closedir(dir);
132df930be7Sderaadt
133df930be7Sderaadt /* if we don't do this, then when our children eventually call
134df930be7Sderaadt * getpwnam() in do_command.c's child_process to verify MAILTO=,
135df930be7Sderaadt * they will screw us up (and v-v).
136df930be7Sderaadt */
137df930be7Sderaadt endpwent();
138df930be7Sderaadt
139df930be7Sderaadt /* whatever's left in the old database is now junk.
140df930be7Sderaadt */
1418a6f8ff3Smillert if (old_db != NULL) {
1428a6f8ff3Smillert while ((u = TAILQ_FIRST(&old_db->users))) {
1438a6f8ff3Smillert TAILQ_REMOVE(&old_db->users, u, entries);
144df930be7Sderaadt free_user(u);
145df930be7Sderaadt }
1468a6f8ff3Smillert free(old_db);
1478a6f8ff3Smillert }
148df930be7Sderaadt
149df930be7Sderaadt /* overwrite the database control block with the new one.
150df930be7Sderaadt */
1518a6f8ff3Smillert *db = new_db;
152df930be7Sderaadt }
153df930be7Sderaadt
154df930be7Sderaadt user *
find_user(cron_db * db,const char * name)155785ecd14Stedu find_user(cron_db *db, const char *name)
156785ecd14Stedu {
1578a6f8ff3Smillert user *u = NULL;
158df930be7Sderaadt
1598a6f8ff3Smillert if (db != NULL) {
1608a6f8ff3Smillert TAILQ_FOREACH(u, &db->users, entries) {
161f454ebdeSmillert if (strcmp(u->name, name) == 0)
162df930be7Sderaadt break;
1638a6f8ff3Smillert }
1648a6f8ff3Smillert }
165f454ebdeSmillert return (u);
166df930be7Sderaadt }
167df930be7Sderaadt
168df930be7Sderaadt static void
process_crontab(int dfd,const char * uname,const char * fname,struct stat * statbuf,cron_db * new_db,cron_db * old_db)16988959323Smillert process_crontab(int dfd, const char *uname, const char *fname,
170f454ebdeSmillert struct stat *statbuf, cron_db *new_db, cron_db *old_db)
171df930be7Sderaadt {
172df930be7Sderaadt struct passwd *pw = NULL;
17348565214Smillert FILE *crontab_fp = NULL;
174a9e807deSmillert user *u, *new_u;
175a9e807deSmillert mode_t tabmask, tabperm;
17648565214Smillert int fd;
177df930be7Sderaadt
17888959323Smillert /* Note: pw must remain NULL for system crontab (see below). */
17988959323Smillert if (fname[0] != '/' && (pw = getpwnam(uname)) == NULL) {
180df930be7Sderaadt /* file doesn't have a user in passwd file.
181df930be7Sderaadt */
182a38a3395Smillert syslog(LOG_WARNING, "(%s) ORPHAN (no passwd entry)", uname);
183df930be7Sderaadt goto next_crontab;
184df930be7Sderaadt }
185df930be7Sderaadt
18648565214Smillert fd = openat(dfd, fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);
187*df69c215Sderaadt if (fd == -1) {
188df930be7Sderaadt /* crontab not accessible?
189df930be7Sderaadt */
190a38a3395Smillert syslog(LOG_ERR, "(%s) CAN'T OPEN (%s)", uname, fname);
191df930be7Sderaadt goto next_crontab;
192df930be7Sderaadt }
19348565214Smillert if (!(crontab_fp = fdopen(fd, "r"))) {
19448565214Smillert syslog(LOG_ERR, "(%s) FDOPEN (%m)", fname);
19548565214Smillert close(fd);
19648565214Smillert goto next_crontab;
19748565214Smillert }
198df930be7Sderaadt
199*df69c215Sderaadt if (fstat(fileno(crontab_fp), statbuf) == -1) {
200a38a3395Smillert syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", uname, fname);
201df930be7Sderaadt goto next_crontab;
202df930be7Sderaadt }
20315237702Smillert if (!S_ISREG(statbuf->st_mode)) {
204a38a3395Smillert syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", uname, fname);
20515237702Smillert goto next_crontab;
20615237702Smillert }
207ef0c9e3eSmillert /* Looser permissions on system crontab. */
208a9e807deSmillert tabmask = pw ? ALLPERMS : (ALLPERMS & ~(S_IWUSR|S_IRGRP|S_IROTH));
209a9e807deSmillert tabperm = pw ? (S_IRUSR|S_IWUSR) : S_IRUSR;
210a9e807deSmillert if ((statbuf->st_mode & tabmask) != tabperm) {
211a9e807deSmillert syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", uname, fname);
21215237702Smillert goto next_crontab;
21315237702Smillert }
2145c7b7de1Smillert if (statbuf->st_uid != 0 && (pw == NULL ||
215d6f5d839Smillert statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
216a38a3395Smillert syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", uname, fname);
21715237702Smillert goto next_crontab;
21815237702Smillert }
219a9e807deSmillert if (pw != NULL && statbuf->st_gid != cron_gid) {
220a9e807deSmillert syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", uname, fname);
221a9e807deSmillert goto next_crontab;
222a9e807deSmillert }
223ef0c9e3eSmillert if (pw != NULL && statbuf->st_nlink != 1) {
224a38a3395Smillert syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", uname, fname);
22515237702Smillert goto next_crontab;
22615237702Smillert }
227df930be7Sderaadt
228df930be7Sderaadt u = find_user(old_db, fname);
229df930be7Sderaadt if (u != NULL) {
230df930be7Sderaadt /* if crontab has not changed since we last read it
231df930be7Sderaadt * in, then we can just use our existing entry.
232df930be7Sderaadt */
23395f2b4e2Smillert if (timespeccmp(&u->mtime, &statbuf->st_mtim, ==)) {
2348a6f8ff3Smillert TAILQ_REMOVE(&old_db->users, u, entries);
2358a6f8ff3Smillert TAILQ_INSERT_TAIL(&new_db->users, u, entries);
236df930be7Sderaadt goto next_crontab;
237df930be7Sderaadt }
238a38a3395Smillert syslog(LOG_INFO, "(%s) RELOAD (%s)", uname, fname);
239df930be7Sderaadt }
240a9e807deSmillert
24148565214Smillert new_u = load_user(crontab_fp, pw, fname);
242a9e807deSmillert if (new_u != NULL) {
243a9e807deSmillert /* Insert user into the new database and remove from old. */
244a9e807deSmillert new_u->mtime = statbuf->st_mtim;
245a9e807deSmillert TAILQ_INSERT_TAIL(&new_db->users, new_u, entries);
246df930be7Sderaadt if (u != NULL) {
247a9e807deSmillert TAILQ_REMOVE(&old_db->users, u, entries);
248a9e807deSmillert free_user(u);
249a9e807deSmillert }
250a9e807deSmillert } else if (u != NULL) {
251a9e807deSmillert /* New user crontab failed to load, preserve the old one. */
252a9e807deSmillert TAILQ_REMOVE(&old_db->users, u, entries);
2538a6f8ff3Smillert TAILQ_INSERT_TAIL(&new_db->users, u, entries);
254df930be7Sderaadt }
255df930be7Sderaadt
256df930be7Sderaadt next_crontab:
25748565214Smillert if (crontab_fp != NULL) {
25848565214Smillert fclose(crontab_fp);
259df930be7Sderaadt }
260df930be7Sderaadt }
261