xref: /netbsd/external/bsd/cron/dist/database.c (revision 1d04827f)
1 /*	$NetBSD: database.c,v 1.9 2017/06/09 17:36:30 christos Exp $	*/
2 
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4  * All rights reserved
5  */
6 
7 /*
8  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 #include <sys/cdefs.h>
24 #if !defined(lint) && !defined(LINT)
25 #if 0
26 static char rcsid[] = "Id: database.c,v 1.7 2004/01/23 18:56:42 vixie Exp";
27 #else
28 __RCSID("$NetBSD: database.c,v 1.9 2017/06/09 17:36:30 christos Exp $");
29 #endif
30 #endif
31 
32 /* vix 26jan87 [RCS has the log]
33  */
34 
35 #include "cron.h"
36 
37 #define TMAX(a,b) ((a)>(b)?(a):(b))
38 
39 struct spooldir {
40 	const char *path;
41 	const char *uname;
42 	const char *fname;
43 	struct stat st;
44 };
45 
46 static struct spooldir spools[] = {
47 	{ .path = SPOOL_DIR, },
48 	{ .path = CROND_DIR, .uname = "root", .fname = "*system*", },
49 	{ .path = NULL, }
50 };
51 
52 static	void		process_crontab(const char *, const char *,
53 					const char *, struct stat *,
54 					cron_db *, cron_db *);
55 
56 static void
process_dir(struct spooldir * sp,cron_db * new_db,cron_db * old_db)57 process_dir(struct spooldir *sp, cron_db *new_db, cron_db *old_db)
58 {
59 	DIR *dir;
60 	DIR_T *dp;
61 	const char *dname = sp->path;
62 	struct stat *st = &sp->st;
63 
64 	if (st->st_mtime == 0)
65 		return;
66 
67 	/* we used to keep this dir open all the time, for the sake of
68 	 * efficiency.  however, we need to close it in every fork, and
69 	 * we fork a lot more often than the mtime of the dir changes.
70 	 */
71 	if (!(dir = opendir(dname))) {
72 		log_it("CRON", getpid(), "OPENDIR FAILED", dname);
73 		(void) exit(ERROR_EXIT);
74 	}
75 
76 	while (NULL != (dp = readdir(dir))) {
77 		char	fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
78 		size_t i, len;
79 		/*
80 		 * Homage to...
81 		 */
82 		static const char *junk[] = {
83 			"rpmsave", "rpmorig", "rpmnew",
84 		};
85 
86 		/* avoid file names beginning with ".".  this is good
87 		 * because we would otherwise waste two guaranteed calls
88 		 * to getpwnam() for . and .., and there shouldn't be
89 		 * hidden files in here anyway (in the non system case).
90 		 */
91 		if (dp->d_name[0] == '.')
92 			continue;
93 
94 		/* ignore files starting with # ... */
95 		if (dp->d_name[0] == '#')
96 			continue;
97 
98 		len = strlen(dp->d_name);
99 
100 		/* ... or too big or to small ... */
101 		if (len == 0 || len >= sizeof(fname)) {
102 			log_it(dp->d_name, getpid(), "ORPHAN",
103 			    "name too short or long");
104 			continue;
105 		}
106 
107 		/* ... or ending with ~ ... */
108 		if (dp->d_name[len - 1] == '~')
109 			continue;
110 
111 		(void)strlcpy(fname, dp->d_name, sizeof(fname));
112 
113 		/* ... or look for blacklisted extensions */
114 		for (i = 0; i < __arraycount(junk); i++) {
115 			char *p;
116 			if ((p = strrchr(fname, '.')) != NULL &&
117 			    strcmp(p + 1, junk[i]) == 0)
118 				break;
119 		}
120 		if (i != __arraycount(junk))
121 			continue;
122 
123 		if (!glue_strings(tabname, sizeof tabname, dname, fname, '/')) {
124 			log_it(fname, getpid(), "ORPHAN",
125 			    "could not glue strings");
126 			continue;
127 		}
128 
129 		process_crontab(sp->uname ? sp->uname : fname,
130 				sp->fname ? sp->fname : fname,
131 				tabname, st, new_db, old_db);
132 	}
133 	(void)closedir(dir);
134 }
135 
136 void
load_database(cron_db * old_db)137 load_database(cron_db *old_db) {
138 	struct stat syscron_stat;
139 	cron_db new_db;
140 	user *u, *nu;
141 	time_t maxtime;
142 
143 	Debug(DLOAD, ("[%ld] load_database()\n", (long)getpid()));
144 
145 	/* track system crontab file
146 	 */
147 	if (stat(SYSCRONTAB, &syscron_stat) < OK)
148 		syscron_stat.st_mtime = 0;
149 
150 	maxtime = syscron_stat.st_mtime;
151 	for (struct spooldir *p = spools; p->path; p++) {
152 		if (stat(p->path, &p->st) < OK) {
153 			if (errno == ENOENT) {
154 				p->st.st_mtime = 0;
155 				continue;
156 			}
157 			log_it("CRON", getpid(), "STAT FAILED", p->path);
158 			(void) exit(ERROR_EXIT);
159 		}
160 		if (p->st.st_mtime > maxtime)
161 			maxtime = p->st.st_mtime;
162 	}
163 
164 	/* if spooldir's mtime has not changed, we don't need to fiddle with
165 	 * the database.
166 	 *
167 	 * Note that old_db->mtime is initialized to 0 in main(), and
168 	 * so is guaranteed to be different than the stat() mtime the first
169 	 * time this function is called.
170 	 */
171 	if (old_db->mtime == maxtime) {
172 		Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n",
173 			      (long)getpid()));
174 		return;
175 	}
176 
177 	/* something's different.  make a new database, moving unchanged
178 	 * elements from the old database, reloading elements that have
179 	 * actually changed.  Whatever is left in the old database when
180 	 * we're done is chaff -- crontabs that disappeared.
181 	 */
182 	new_db.mtime = maxtime;
183 	new_db.head = new_db.tail = NULL;
184 
185 	if (syscron_stat.st_mtime)
186 		process_crontab("root", "*system*", SYSCRONTAB,
187 		    &syscron_stat, &new_db, old_db);
188 
189  	for (struct spooldir *p = spools; p->path; p++)
190 		process_dir(p, &new_db, old_db);
191 
192 	/* if we don't do this, then when our children eventually call
193 	 * getpwnam() in do_command.c's child_process to verify MAILTO=,
194 	 * they will screw us up (and v-v).
195 	 */
196 	endpwent();
197 
198 	/* whatever's left in the old database is now junk.
199 	 */
200 	Debug(DLOAD, ("unlinking old database:\n"));
201 	for (u = old_db->head;  u != NULL;  u = nu) {
202 		Debug(DLOAD, ("\t%s\n", u->name));
203 		nu = u->next;
204 		unlink_user(old_db, u);
205 		free_user(u);
206 	}
207 
208 	/* overwrite the database control block with the new one.
209 	 */
210 	*old_db = new_db;
211 	Debug(DLOAD, ("load_database is done\n"));
212 }
213 
214 void
link_user(cron_db * db,user * u)215 link_user(cron_db *db, user *u) {
216 	if (db->head == NULL)
217 		db->head = u;
218 	if (db->tail)
219 		db->tail->next = u;
220 	u->prev = db->tail;
221 	u->next = NULL;
222 	db->tail = u;
223 }
224 
225 void
unlink_user(cron_db * db,user * u)226 unlink_user(cron_db *db, user *u) {
227 	if (u->prev == NULL)
228 		db->head = u->next;
229 	else
230 		u->prev->next = u->next;
231 
232 	if (u->next == NULL)
233 		db->tail = u->prev;
234 	else
235 		u->next->prev = u->prev;
236 }
237 
238 user *
find_user(cron_db * db,const char * name)239 find_user(cron_db *db, const char *name) {
240 	user *u;
241 
242 	for (u = db->head;  u != NULL;  u = u->next)
243 		if (strcmp(u->name, name) == 0)
244 			break;
245 	return (u);
246 }
247 
248 static void
process_crontab(const char * uname,const char * fname,const char * tabname,struct stat * statbuf,cron_db * new_db,cron_db * old_db)249 process_crontab(const char *uname, const char *fname, const char *tabname,
250 		struct stat *statbuf, cron_db *new_db, cron_db *old_db)
251 {
252 	struct passwd *pw = NULL;
253 	int crontab_fd = OK - 1;
254 	mode_t eqmode = 0400, badmode = 0;
255 	user *u;
256 
257 	if (strcmp(fname, "*system*") == 0) {
258 		/*
259 		 * SYSCRONTAB:
260 		 * Allow it to become readable by group and others, but
261 		 * not writable.
262 		 */
263 		eqmode = 0;
264 		badmode = 022;
265 	} else if ((pw = getpwnam(uname)) == NULL) {
266 		/* file doesn't have a user in passwd file.
267 		 */
268 		log_it(fname, getpid(), "ORPHAN", "no passwd entry");
269 		goto next_crontab;
270 	}
271 
272 	if ((crontab_fd = open(tabname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < OK) {
273 		/* crontab not accessible?
274 		 */
275 		log_it(fname, getpid(), "CAN'T OPEN", tabname);
276 		goto next_crontab;
277 	}
278 
279 	if (fstat(crontab_fd, statbuf) < OK) {
280 		log_it(fname, getpid(), "FSTAT FAILED", tabname);
281 		goto next_crontab;
282 	}
283 	if (!S_ISREG(statbuf->st_mode)) {
284 		log_it(fname, getpid(), "NOT REGULAR", tabname);
285 		goto next_crontab;
286 	}
287 	if ((eqmode && (statbuf->st_mode & 07577) != eqmode) ||
288 	    (badmode && (statbuf->st_mode & badmode) != 0)) {
289 		log_it(fname, getpid(), "BAD FILE MODE", tabname);
290 		goto next_crontab;
291 	}
292 	if (statbuf->st_uid != ROOT_UID && (pw == NULL ||
293 	    statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
294 		log_it(fname, getpid(), "WRONG FILE OWNER", tabname);
295 		goto next_crontab;
296 	}
297 	if (statbuf->st_nlink != 1) {
298 		log_it(fname, getpid(), "BAD LINK COUNT", tabname);
299 		goto next_crontab;
300 	}
301 
302 	Debug(DLOAD, ("\t%s:", fname));
303 	u = find_user(old_db, fname);
304 	if (u != NULL) {
305 		/* if crontab has not changed since we last read it
306 		 * in, then we can just use our existing entry.
307 		 */
308 		if (u->mtime == statbuf->st_mtime) {
309 			Debug(DLOAD, (" [no change, using old data]"));
310 			unlink_user(old_db, u);
311 			link_user(new_db, u);
312 			goto next_crontab;
313 		}
314 
315 		/* before we fall through to the code that will reload
316 		 * the user, let's deallocate and unlink the user in
317 		 * the old database.  This is more a point of memory
318 		 * efficiency than anything else, since all leftover
319 		 * users will be deleted from the old database when
320 		 * we finish with the crontab...
321 		 */
322 		Debug(DLOAD, (" [delete old data]"));
323 		unlink_user(old_db, u);
324 		free_user(u);
325 		log_it(fname, getpid(), "RELOAD", tabname);
326 	}
327 	u = load_user(crontab_fd, pw, fname);
328 	if (u != NULL) {
329 		u->mtime = statbuf->st_mtime;
330 		link_user(new_db, u);
331 	}
332 
333  next_crontab:
334 	if (crontab_fd >= OK) {
335 		Debug(DLOAD, (" [done]\n"));
336 		(void)close(crontab_fd);
337 	}
338 }
339