xref: /openbsd-src/usr.sbin/cron/database.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: database.c,v 1.34 2016/01/11 14:23:50 millert 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 "macros.h"
38 #include "structs.h"
39 #include "funcs.h"
40 
41 #define HASH(a,b) ((a)+(b))
42 
43 static	void		process_crontab(int, const char *, const char *,
44 					struct stat *, cron_db *, cron_db *);
45 
46 void
47 load_database(cron_db **db)
48 {
49 	struct stat statbuf, syscron_stat;
50 	cron_db *new_db, *old_db = *db;
51 	struct timespec mtime;
52 	struct dirent *dp;
53 	DIR *dir;
54 	user *u;
55 
56 	/* before we start loading any data, do a stat on _PATH_CRON_SPOOL
57 	 * so that if anything changes as of this moment (i.e., before we've
58 	 * cached any of the database), we'll see the changes next time.
59 	 */
60 	if (stat(_PATH_CRON_SPOOL, &statbuf) < 0) {
61 		syslog(LOG_ERR, "(CRON) STAT FAILED (%s)", _PATH_CRON_SPOOL);
62 		return;
63 	}
64 
65 	/* track system crontab file
66 	 */
67 	if (stat(_PATH_SYS_CRONTAB, &syscron_stat) < 0)
68 		timespecclear(&syscron_stat.st_mtim);
69 
70 	/* hash mtime of system crontab file and crontab dir
71 	 */
72 	mtime.tv_sec =
73 	    HASH(statbuf.st_mtim.tv_sec, syscron_stat.st_mtim.tv_sec);
74 	mtime.tv_nsec =
75 	    HASH(statbuf.st_mtim.tv_nsec, syscron_stat.st_mtim.tv_nsec);
76 
77 	/* if spooldir's mtime has not changed, we don't need to fiddle with
78 	 * the database.
79 	 */
80 	if (old_db != NULL && timespeccmp(&mtime, &old_db->mtime, ==))
81 		return;
82 
83 	/* something's different.  make a new database, moving unchanged
84 	 * elements from the old database, reloading elements that have
85 	 * actually changed.  Whatever is left in the old database when
86 	 * we're done is chaff -- crontabs that disappeared.
87 	 */
88 	if ((new_db = malloc(sizeof(*new_db))) == NULL)
89 		return;
90 	new_db->mtime = mtime;
91 	TAILQ_INIT(&new_db->users);
92 
93 	if (timespecisset(&syscron_stat.st_mtim)) {
94 		process_crontab(AT_FDCWD, "*system*", _PATH_SYS_CRONTAB,
95 				&syscron_stat, new_db, old_db);
96 	}
97 
98 	/* we used to keep this dir open all the time, for the sake of
99 	 * efficiency.  however, we need to close it in every fork, and
100 	 * we fork a lot more often than the mtime of the dir changes.
101 	 */
102 	if (!(dir = opendir(_PATH_CRON_SPOOL))) {
103 		syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_CRON_SPOOL);
104 		/* Restore system crontab entry as needed. */
105 		if (!TAILQ_EMPTY(&new_db->users) &&
106 		    (u = TAILQ_FIRST(&old_db->users))) {
107 			if (strcmp(u->name, "*system*") == 0) {
108 				TAILQ_REMOVE(&old_db->users, u, entries);
109 				free_user(u);
110 				TAILQ_INSERT_HEAD(&old_db->users,
111 				    TAILQ_FIRST(&new_db->users), entries);
112 			}
113 		}
114 		free(new_db);
115 		return;
116 	}
117 
118 	while (NULL != (dp = readdir(dir))) {
119 		/* avoid file names beginning with ".".  this is good
120 		 * because we would otherwise waste two guaranteed calls
121 		 * to getpwnam() for . and .., and also because user names
122 		 * starting with a period are just too nasty to consider.
123 		 */
124 		if (dp->d_name[0] == '.')
125 			continue;
126 
127 		process_crontab(dirfd(dir), dp->d_name, dp->d_name,
128 				&statbuf, new_db, old_db);
129 	}
130 	closedir(dir);
131 
132 	/* if we don't do this, then when our children eventually call
133 	 * getpwnam() in do_command.c's child_process to verify MAILTO=,
134 	 * they will screw us up (and v-v).
135 	 */
136 	endpwent();
137 
138 	/* whatever's left in the old database is now junk.
139 	 */
140 	if (old_db != NULL) {
141 		while ((u = TAILQ_FIRST(&old_db->users))) {
142 			TAILQ_REMOVE(&old_db->users, u, entries);
143 			free_user(u);
144 		}
145 		free(old_db);
146 	}
147 
148 	/* overwrite the database control block with the new one.
149 	 */
150 	*db = new_db;
151 }
152 
153 user *
154 find_user(cron_db *db, const char *name)
155 {
156 	user *u = NULL;
157 
158 	if (db != NULL) {
159 		TAILQ_FOREACH(u, &db->users, entries) {
160 			if (strcmp(u->name, name) == 0)
161 				break;
162 		}
163 	}
164 	return (u);
165 }
166 
167 static void
168 process_crontab(int dfd, const char *uname, const char *fname,
169 		struct stat *statbuf, cron_db *new_db, cron_db *old_db)
170 {
171 	struct passwd *pw = NULL;
172 	int crontab_fd = -1;
173 	user *u;
174 
175 	/* Note: pw must remain NULL for system crontab (see below). */
176 	if (fname[0] != '/' && (pw = getpwnam(uname)) == NULL) {
177 		/* file doesn't have a user in passwd file.
178 		 */
179 		syslog(LOG_WARNING, "(%s) ORPHAN (no passwd entry)", uname);
180 		goto next_crontab;
181 	}
182 
183 	crontab_fd = openat(dfd, fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW);
184 	if (crontab_fd < 0) {
185 		/* crontab not accessible?
186 		 */
187 		syslog(LOG_ERR, "(%s) CAN'T OPEN (%s)", uname, fname);
188 		goto next_crontab;
189 	}
190 
191 	if (fstat(crontab_fd, statbuf) < 0) {
192 		syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", uname, fname);
193 		goto next_crontab;
194 	}
195 	if (!S_ISREG(statbuf->st_mode)) {
196 		syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", uname, fname);
197 		goto next_crontab;
198 	}
199 	if (pw != NULL) {
200 		/* Looser permissions on system crontab. */
201 		if ((statbuf->st_mode & 077) != 0) {
202 			syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)",
203 			    uname, fname);
204 			goto next_crontab;
205 		}
206 	}
207 	if (statbuf->st_uid != 0 && (pw == NULL ||
208 	    statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
209 		syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", uname, fname);
210 		goto next_crontab;
211 	}
212 	if (pw != NULL && statbuf->st_nlink != 1) {
213 		syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", uname, fname);
214 		goto next_crontab;
215 	}
216 
217 	u = find_user(old_db, fname);
218 	if (u != NULL) {
219 		/* if crontab has not changed since we last read it
220 		 * in, then we can just use our existing entry.
221 		 */
222 		if (timespeccmp(&u->mtime, &statbuf->st_mtim, ==)) {
223 			TAILQ_REMOVE(&old_db->users, u, entries);
224 			TAILQ_INSERT_TAIL(&new_db->users, u, entries);
225 			goto next_crontab;
226 		}
227 
228 		/* before we fall through to the code that will reload
229 		 * the user, let's deallocate and unlink the user in
230 		 * the old database.  This is more a point of memory
231 		 * efficiency than anything else, since all leftover
232 		 * users will be deleted from the old database when
233 		 * we finish with the crontab...
234 		 */
235 		TAILQ_REMOVE(&old_db->users, u, entries);
236 		free_user(u);
237 		syslog(LOG_INFO, "(%s) RELOAD (%s)", uname, fname);
238 	}
239 	u = load_user(crontab_fd, pw, fname);
240 	if (u != NULL) {
241 		u->mtime = statbuf->st_mtim;
242 		TAILQ_INSERT_TAIL(&new_db->users, u, entries);
243 	}
244 
245  next_crontab:
246 	if (crontab_fd >= 0) {
247 		close(crontab_fd);
248 	}
249 }
250