1 /* $OpenBSD: database.c,v 1.38 2019/06/28 13:32:47 deraadt Exp $ */
2
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/time.h>
23
24 #include <bitstring.h> /* for structs.h */
25 #include <dirent.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <pwd.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <syslog.h>
33 #include <time.h> /* for structs.h */
34 #include <unistd.h>
35
36 #include "pathnames.h"
37 #include "globals.h"
38 #include "macros.h"
39 #include "structs.h"
40 #include "funcs.h"
41
42 #define HASH(a,b) ((a)+(b))
43
44 static void process_crontab(int, const char *, const char *,
45 struct stat *, cron_db *, cron_db *);
46
47 void
load_database(cron_db ** db)48 load_database(cron_db **db)
49 {
50 struct stat statbuf, syscron_stat;
51 cron_db *new_db, *old_db = *db;
52 struct timespec mtime;
53 struct dirent *dp;
54 DIR *dir;
55 user *u;
56
57 /* before we start loading any data, do a stat on _PATH_CRON_SPOOL
58 * so that if anything changes as of this moment (i.e., before we've
59 * cached any of the database), we'll see the changes next time.
60 */
61 if (stat(_PATH_CRON_SPOOL, &statbuf) == -1) {
62 syslog(LOG_ERR, "(CRON) STAT FAILED (%s)", _PATH_CRON_SPOOL);
63 return;
64 }
65
66 /* track system crontab file
67 */
68 if (stat(_PATH_SYS_CRONTAB, &syscron_stat) == -1)
69 timespecclear(&syscron_stat.st_mtim);
70
71 /* hash mtime of system crontab file and crontab dir
72 */
73 mtime.tv_sec =
74 HASH(statbuf.st_mtim.tv_sec, syscron_stat.st_mtim.tv_sec);
75 mtime.tv_nsec =
76 HASH(statbuf.st_mtim.tv_nsec, syscron_stat.st_mtim.tv_nsec);
77
78 /* if spooldir's mtime has not changed, we don't need to fiddle with
79 * the database.
80 */
81 if (old_db != NULL && timespeccmp(&mtime, &old_db->mtime, ==))
82 return;
83
84 /* something's different. make a new database, moving unchanged
85 * elements from the old database, reloading elements that have
86 * actually changed. Whatever is left in the old database when
87 * we're done is chaff -- crontabs that disappeared.
88 */
89 if ((new_db = malloc(sizeof(*new_db))) == NULL)
90 return;
91 new_db->mtime = mtime;
92 TAILQ_INIT(&new_db->users);
93
94 if (timespecisset(&syscron_stat.st_mtim)) {
95 process_crontab(AT_FDCWD, "*system*", _PATH_SYS_CRONTAB,
96 &syscron_stat, new_db, old_db);
97 }
98
99 /* we used to keep this dir open all the time, for the sake of
100 * efficiency. however, we need to close it in every fork, and
101 * we fork a lot more often than the mtime of the dir changes.
102 */
103 if (!(dir = opendir(_PATH_CRON_SPOOL))) {
104 syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_CRON_SPOOL);
105 /* Restore system crontab entry as needed. */
106 if (!TAILQ_EMPTY(&new_db->users) &&
107 (u = TAILQ_FIRST(&old_db->users))) {
108 if (strcmp(u->name, "*system*") == 0) {
109 TAILQ_REMOVE(&old_db->users, u, entries);
110 free_user(u);
111 TAILQ_INSERT_HEAD(&old_db->users,
112 TAILQ_FIRST(&new_db->users), entries);
113 }
114 }
115 free(new_db);
116 return;
117 }
118
119 while (NULL != (dp = readdir(dir))) {
120 /* avoid file names beginning with ".". this is good
121 * because we would otherwise waste two guaranteed calls
122 * to getpwnam() for . and .., and also because user names
123 * starting with a period are just too nasty to consider.
124 */
125 if (dp->d_name[0] == '.')
126 continue;
127
128 process_crontab(dirfd(dir), dp->d_name, dp->d_name,
129 &statbuf, new_db, old_db);
130 }
131 closedir(dir);
132
133 /* if we don't do this, then when our children eventually call
134 * getpwnam() in do_command.c's child_process to verify MAILTO=,
135 * they will screw us up (and v-v).
136 */
137 endpwent();
138
139 /* whatever's left in the old database is now junk.
140 */
141 if (old_db != NULL) {
142 while ((u = TAILQ_FIRST(&old_db->users))) {
143 TAILQ_REMOVE(&old_db->users, u, entries);
144 free_user(u);
145 }
146 free(old_db);
147 }
148
149 /* overwrite the database control block with the new one.
150 */
151 *db = new_db;
152 }
153
154 user *
find_user(cron_db * db,const char * name)155 find_user(cron_db *db, const char *name)
156 {
157 user *u = NULL;
158
159 if (db != NULL) {
160 TAILQ_FOREACH(u, &db->users, entries) {
161 if (strcmp(u->name, name) == 0)
162 break;
163 }
164 }
165 return (u);
166 }
167
168 static void
process_crontab(int dfd,const char * uname,const char * fname,struct stat * statbuf,cron_db * new_db,cron_db * old_db)169 process_crontab(int dfd, const char *uname, const char *fname,
170 struct stat *statbuf, cron_db *new_db, cron_db *old_db)
171 {
172 struct passwd *pw = NULL;
173 FILE *crontab_fp = NULL;
174 user *u, *new_u;
175 mode_t tabmask, tabperm;
176 int fd;
177
178 /* Note: pw must remain NULL for system crontab (see below). */
179 if (fname[0] != '/' && (pw = getpwnam(uname)) == NULL) {
180 /* file doesn't have a user in passwd file.
181 */
182 syslog(LOG_WARNING, "(%s) ORPHAN (no passwd entry)", uname);
183 goto next_crontab;
184 }
185
186 fd = openat(dfd, fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);
187 if (fd == -1) {
188 /* crontab not accessible?
189 */
190 syslog(LOG_ERR, "(%s) CAN'T OPEN (%s)", uname, fname);
191 goto next_crontab;
192 }
193 if (!(crontab_fp = fdopen(fd, "r"))) {
194 syslog(LOG_ERR, "(%s) FDOPEN (%m)", fname);
195 close(fd);
196 goto next_crontab;
197 }
198
199 if (fstat(fileno(crontab_fp), statbuf) == -1) {
200 syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", uname, fname);
201 goto next_crontab;
202 }
203 if (!S_ISREG(statbuf->st_mode)) {
204 syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", uname, fname);
205 goto next_crontab;
206 }
207 /* Looser permissions on system crontab. */
208 tabmask = pw ? ALLPERMS : (ALLPERMS & ~(S_IWUSR|S_IRGRP|S_IROTH));
209 tabperm = pw ? (S_IRUSR|S_IWUSR) : S_IRUSR;
210 if ((statbuf->st_mode & tabmask) != tabperm) {
211 syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", uname, fname);
212 goto next_crontab;
213 }
214 if (statbuf->st_uid != 0 && (pw == NULL ||
215 statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
216 syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", uname, fname);
217 goto next_crontab;
218 }
219 if (pw != NULL && statbuf->st_gid != cron_gid) {
220 syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", uname, fname);
221 goto next_crontab;
222 }
223 if (pw != NULL && statbuf->st_nlink != 1) {
224 syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", uname, fname);
225 goto next_crontab;
226 }
227
228 u = find_user(old_db, fname);
229 if (u != NULL) {
230 /* if crontab has not changed since we last read it
231 * in, then we can just use our existing entry.
232 */
233 if (timespeccmp(&u->mtime, &statbuf->st_mtim, ==)) {
234 TAILQ_REMOVE(&old_db->users, u, entries);
235 TAILQ_INSERT_TAIL(&new_db->users, u, entries);
236 goto next_crontab;
237 }
238 syslog(LOG_INFO, "(%s) RELOAD (%s)", uname, fname);
239 }
240
241 new_u = load_user(crontab_fp, pw, fname);
242 if (new_u != NULL) {
243 /* Insert user into the new database and remove from old. */
244 new_u->mtime = statbuf->st_mtim;
245 TAILQ_INSERT_TAIL(&new_db->users, new_u, entries);
246 if (u != NULL) {
247 TAILQ_REMOVE(&old_db->users, u, entries);
248 free_user(u);
249 }
250 } else if (u != NULL) {
251 /* New user crontab failed to load, preserve the old one. */
252 TAILQ_REMOVE(&old_db->users, u, entries);
253 TAILQ_INSERT_TAIL(&new_db->users, u, entries);
254 }
255
256 next_crontab:
257 if (crontab_fp != NULL) {
258 fclose(crontab_fp);
259 }
260 }
261