xref: /netbsd-src/external/bsd/cron/dist/database.c (revision 065057e63525be61eed7190f73cb58a269959e81)
1*065057e6Schristos /*	$NetBSD: database.c,v 1.9 2017/06/09 17:36:30 christos Exp $	*/
2032a4398Schristos 
30061c6a5Schristos /* Copyright 1988,1990,1993,1994 by Paul Vixie
40061c6a5Schristos  * All rights reserved
50061c6a5Schristos  */
60061c6a5Schristos 
70061c6a5Schristos /*
80061c6a5Schristos  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
90061c6a5Schristos  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
100061c6a5Schristos  *
110061c6a5Schristos  * Permission to use, copy, modify, and distribute this software for any
120061c6a5Schristos  * purpose with or without fee is hereby granted, provided that the above
130061c6a5Schristos  * copyright notice and this permission notice appear in all copies.
140061c6a5Schristos  *
150061c6a5Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
160061c6a5Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
170061c6a5Schristos  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
180061c6a5Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
190061c6a5Schristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
200061c6a5Schristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
210061c6a5Schristos  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
220061c6a5Schristos  */
23032a4398Schristos #include <sys/cdefs.h>
240061c6a5Schristos #if !defined(lint) && !defined(LINT)
25032a4398Schristos #if 0
260061c6a5Schristos static char rcsid[] = "Id: database.c,v 1.7 2004/01/23 18:56:42 vixie Exp";
27032a4398Schristos #else
28*065057e6Schristos __RCSID("$NetBSD: database.c,v 1.9 2017/06/09 17:36:30 christos Exp $");
29032a4398Schristos #endif
300061c6a5Schristos #endif
310061c6a5Schristos 
320061c6a5Schristos /* vix 26jan87 [RCS has the log]
330061c6a5Schristos  */
340061c6a5Schristos 
350061c6a5Schristos #include "cron.h"
360061c6a5Schristos 
370061c6a5Schristos #define TMAX(a,b) ((a)>(b)?(a):(b))
380061c6a5Schristos 
39*065057e6Schristos struct spooldir {
40*065057e6Schristos 	const char *path;
41*065057e6Schristos 	const char *uname;
42*065057e6Schristos 	const char *fname;
43*065057e6Schristos 	struct stat st;
44*065057e6Schristos };
45*065057e6Schristos 
46*065057e6Schristos static struct spooldir spools[] = {
47*065057e6Schristos 	{ .path = SPOOL_DIR, },
48*065057e6Schristos 	{ .path = CROND_DIR, .uname = "root", .fname = "*system*", },
49*065057e6Schristos 	{ .path = NULL, }
50*065057e6Schristos };
51*065057e6Schristos 
520061c6a5Schristos static	void		process_crontab(const char *, const char *,
530061c6a5Schristos 					const char *, struct stat *,
540061c6a5Schristos 					cron_db *, cron_db *);
550061c6a5Schristos 
5670b3f8cdSchristos static void
process_dir(struct spooldir * sp,cron_db * new_db,cron_db * old_db)57*065057e6Schristos process_dir(struct spooldir *sp, cron_db *new_db, cron_db *old_db)
5870b3f8cdSchristos {
5970b3f8cdSchristos 	DIR *dir;
6070b3f8cdSchristos 	DIR_T *dp;
61*065057e6Schristos 	const char *dname = sp->path;
62*065057e6Schristos 	struct stat *st = &sp->st;
63*065057e6Schristos 
64*065057e6Schristos 	if (st->st_mtime == 0)
65*065057e6Schristos 		return;
6670b3f8cdSchristos 
6770b3f8cdSchristos 	/* we used to keep this dir open all the time, for the sake of
6870b3f8cdSchristos 	 * efficiency.  however, we need to close it in every fork, and
6970b3f8cdSchristos 	 * we fork a lot more often than the mtime of the dir changes.
7070b3f8cdSchristos 	 */
7170b3f8cdSchristos 	if (!(dir = opendir(dname))) {
7270b3f8cdSchristos 		log_it("CRON", getpid(), "OPENDIR FAILED", dname);
7370b3f8cdSchristos 		(void) exit(ERROR_EXIT);
7470b3f8cdSchristos 	}
7570b3f8cdSchristos 
7670b3f8cdSchristos 	while (NULL != (dp = readdir(dir))) {
7770b3f8cdSchristos 		char	fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
7870b3f8cdSchristos 		size_t i, len;
7970b3f8cdSchristos 		/*
8070b3f8cdSchristos 		 * Homage to...
8170b3f8cdSchristos 		 */
8270b3f8cdSchristos 		static const char *junk[] = {
831a007818Schristos 			"rpmsave", "rpmorig", "rpmnew",
8470b3f8cdSchristos 		};
8570b3f8cdSchristos 
8670b3f8cdSchristos 		/* avoid file names beginning with ".".  this is good
8770b3f8cdSchristos 		 * because we would otherwise waste two guaranteed calls
8870b3f8cdSchristos 		 * to getpwnam() for . and .., and there shouldn't be
8970b3f8cdSchristos 		 * hidden files in here anyway (in the non system case).
9070b3f8cdSchristos 		 */
9170b3f8cdSchristos 		if (dp->d_name[0] == '.')
9270b3f8cdSchristos 			continue;
9370b3f8cdSchristos 
9470b3f8cdSchristos 		/* ignore files starting with # ... */
9570b3f8cdSchristos 		if (dp->d_name[0] == '#')
9670b3f8cdSchristos 			continue;
9770b3f8cdSchristos 
9870b3f8cdSchristos 		len = strlen(dp->d_name);
9970b3f8cdSchristos 
10070b3f8cdSchristos 		/* ... or too big or to small ... */
10170b3f8cdSchristos 		if (len == 0 || len >= sizeof(fname)) {
10270b3f8cdSchristos 			log_it(dp->d_name, getpid(), "ORPHAN",
10370b3f8cdSchristos 			    "name too short or long");
10470b3f8cdSchristos 			continue;
10570b3f8cdSchristos 		}
10670b3f8cdSchristos 
10770b3f8cdSchristos 		/* ... or ending with ~ ... */
108158f0bd1Sjoerg 		if (dp->d_name[len - 1] == '~')
10970b3f8cdSchristos 			continue;
11070b3f8cdSchristos 
11170b3f8cdSchristos 		(void)strlcpy(fname, dp->d_name, sizeof(fname));
11270b3f8cdSchristos 
1131a007818Schristos 		/* ... or look for blacklisted extensions */
11470b3f8cdSchristos 		for (i = 0; i < __arraycount(junk); i++) {
11570b3f8cdSchristos 			char *p;
1161a007818Schristos 			if ((p = strrchr(fname, '.')) != NULL &&
1171a007818Schristos 			    strcmp(p + 1, junk[i]) == 0)
11870b3f8cdSchristos 				break;
11970b3f8cdSchristos 		}
12070b3f8cdSchristos 		if (i != __arraycount(junk))
12170b3f8cdSchristos 			continue;
12270b3f8cdSchristos 
12370b3f8cdSchristos 		if (!glue_strings(tabname, sizeof tabname, dname, fname, '/')) {
12470b3f8cdSchristos 			log_it(fname, getpid(), "ORPHAN",
12570b3f8cdSchristos 			    "could not glue strings");
12670b3f8cdSchristos 			continue;
12770b3f8cdSchristos 		}
12870b3f8cdSchristos 
129*065057e6Schristos 		process_crontab(sp->uname ? sp->uname : fname,
130*065057e6Schristos 				sp->fname ? sp->fname : fname,
131*065057e6Schristos 				tabname, st, new_db, old_db);
13270b3f8cdSchristos 	}
13370b3f8cdSchristos 	(void)closedir(dir);
13470b3f8cdSchristos }
13570b3f8cdSchristos 
1360061c6a5Schristos void
load_database(cron_db * old_db)1370061c6a5Schristos load_database(cron_db *old_db) {
138*065057e6Schristos 	struct stat syscron_stat;
1390061c6a5Schristos 	cron_db new_db;
1400061c6a5Schristos 	user *u, *nu;
141*065057e6Schristos 	time_t maxtime;
1420061c6a5Schristos 
143032a4398Schristos 	Debug(DLOAD, ("[%ld] load_database()\n", (long)getpid()));
1440061c6a5Schristos 
1450061c6a5Schristos 	/* track system crontab file
1460061c6a5Schristos 	 */
1470061c6a5Schristos 	if (stat(SYSCRONTAB, &syscron_stat) < OK)
1480061c6a5Schristos 		syscron_stat.st_mtime = 0;
1490061c6a5Schristos 
150*065057e6Schristos 	maxtime = syscron_stat.st_mtime;
151*065057e6Schristos 	for (struct spooldir *p = spools; p->path; p++) {
152*065057e6Schristos 		if (stat(p->path, &p->st) < OK) {
153*065057e6Schristos 			if (errno == ENOENT) {
154*065057e6Schristos 				p->st.st_mtime = 0;
155*065057e6Schristos 				continue;
156*065057e6Schristos 			}
157*065057e6Schristos 			log_it("CRON", getpid(), "STAT FAILED", p->path);
158*065057e6Schristos 			(void) exit(ERROR_EXIT);
159*065057e6Schristos 		}
160*065057e6Schristos 		if (p->st.st_mtime > maxtime)
161*065057e6Schristos 			maxtime = p->st.st_mtime;
162*065057e6Schristos 	}
163*065057e6Schristos 
1640061c6a5Schristos 	/* if spooldir's mtime has not changed, we don't need to fiddle with
1650061c6a5Schristos 	 * the database.
1660061c6a5Schristos 	 *
1670061c6a5Schristos 	 * Note that old_db->mtime is initialized to 0 in main(), and
1680061c6a5Schristos 	 * so is guaranteed to be different than the stat() mtime the first
1690061c6a5Schristos 	 * time this function is called.
1700061c6a5Schristos 	 */
171*065057e6Schristos 	if (old_db->mtime == maxtime) {
1720061c6a5Schristos 		Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n",
173032a4398Schristos 			      (long)getpid()));
1740061c6a5Schristos 		return;
1750061c6a5Schristos 	}
1760061c6a5Schristos 
1770061c6a5Schristos 	/* something's different.  make a new database, moving unchanged
1780061c6a5Schristos 	 * elements from the old database, reloading elements that have
1790061c6a5Schristos 	 * actually changed.  Whatever is left in the old database when
1800061c6a5Schristos 	 * we're done is chaff -- crontabs that disappeared.
1810061c6a5Schristos 	 */
182*065057e6Schristos 	new_db.mtime = maxtime;
1830061c6a5Schristos 	new_db.head = new_db.tail = NULL;
1840061c6a5Schristos 
1850061c6a5Schristos 	if (syscron_stat.st_mtime)
186*065057e6Schristos 		process_crontab("root", "*system*", SYSCRONTAB,
187*065057e6Schristos 		    &syscron_stat, &new_db, old_db);
1880061c6a5Schristos 
189*065057e6Schristos  	for (struct spooldir *p = spools; p->path; p++)
190*065057e6Schristos 		process_dir(p, &new_db, old_db);
1910061c6a5Schristos 
1920061c6a5Schristos 	/* if we don't do this, then when our children eventually call
1930061c6a5Schristos 	 * getpwnam() in do_command.c's child_process to verify MAILTO=,
1940061c6a5Schristos 	 * they will screw us up (and v-v).
1950061c6a5Schristos 	 */
1960061c6a5Schristos 	endpwent();
1970061c6a5Schristos 
1980061c6a5Schristos 	/* whatever's left in the old database is now junk.
1990061c6a5Schristos 	 */
200032a4398Schristos 	Debug(DLOAD, ("unlinking old database:\n"));
2010061c6a5Schristos 	for (u = old_db->head;  u != NULL;  u = nu) {
202032a4398Schristos 		Debug(DLOAD, ("\t%s\n", u->name));
2030061c6a5Schristos 		nu = u->next;
2040061c6a5Schristos 		unlink_user(old_db, u);
2050061c6a5Schristos 		free_user(u);
2060061c6a5Schristos 	}
2070061c6a5Schristos 
2080061c6a5Schristos 	/* overwrite the database control block with the new one.
2090061c6a5Schristos 	 */
2100061c6a5Schristos 	*old_db = new_db;
211032a4398Schristos 	Debug(DLOAD, ("load_database is done\n"));
2120061c6a5Schristos }
2130061c6a5Schristos 
2140061c6a5Schristos void
link_user(cron_db * db,user * u)2150061c6a5Schristos link_user(cron_db *db, user *u) {
2160061c6a5Schristos 	if (db->head == NULL)
2170061c6a5Schristos 		db->head = u;
2180061c6a5Schristos 	if (db->tail)
2190061c6a5Schristos 		db->tail->next = u;
2200061c6a5Schristos 	u->prev = db->tail;
2210061c6a5Schristos 	u->next = NULL;
2220061c6a5Schristos 	db->tail = u;
2230061c6a5Schristos }
2240061c6a5Schristos 
2250061c6a5Schristos void
unlink_user(cron_db * db,user * u)2260061c6a5Schristos unlink_user(cron_db *db, user *u) {
2270061c6a5Schristos 	if (u->prev == NULL)
2280061c6a5Schristos 		db->head = u->next;
2290061c6a5Schristos 	else
2300061c6a5Schristos 		u->prev->next = u->next;
2310061c6a5Schristos 
2320061c6a5Schristos 	if (u->next == NULL)
2330061c6a5Schristos 		db->tail = u->prev;
2340061c6a5Schristos 	else
2350061c6a5Schristos 		u->next->prev = u->prev;
2360061c6a5Schristos }
2370061c6a5Schristos 
2380061c6a5Schristos user *
find_user(cron_db * db,const char * name)2390061c6a5Schristos find_user(cron_db *db, const char *name) {
2400061c6a5Schristos 	user *u;
2410061c6a5Schristos 
2420061c6a5Schristos 	for (u = db->head;  u != NULL;  u = u->next)
2430061c6a5Schristos 		if (strcmp(u->name, name) == 0)
2440061c6a5Schristos 			break;
2450061c6a5Schristos 	return (u);
2460061c6a5Schristos }
2470061c6a5Schristos 
2480061c6a5Schristos static void
process_crontab(const char * uname,const char * fname,const char * tabname,struct stat * statbuf,cron_db * new_db,cron_db * old_db)2490061c6a5Schristos process_crontab(const char *uname, const char *fname, const char *tabname,
2500061c6a5Schristos 		struct stat *statbuf, cron_db *new_db, cron_db *old_db)
2510061c6a5Schristos {
2520061c6a5Schristos 	struct passwd *pw = NULL;
2530061c6a5Schristos 	int crontab_fd = OK - 1;
254af0a310dSchristos 	mode_t eqmode = 0400, badmode = 0;
2550061c6a5Schristos 	user *u;
2560061c6a5Schristos 
257*065057e6Schristos 	if (strcmp(fname, "*system*") == 0) {
2584d77e7cfSchristos 		/*
2594d77e7cfSchristos 		 * SYSCRONTAB:
2604d77e7cfSchristos 		 * Allow it to become readable by group and others, but
2614d77e7cfSchristos 		 * not writable.
2620061c6a5Schristos 		 */
2634d77e7cfSchristos 		eqmode = 0;
2644d77e7cfSchristos 		badmode = 022;
2650061c6a5Schristos 	} else if ((pw = getpwnam(uname)) == NULL) {
2660061c6a5Schristos 		/* file doesn't have a user in passwd file.
2670061c6a5Schristos 		 */
2680061c6a5Schristos 		log_it(fname, getpid(), "ORPHAN", "no passwd entry");
2690061c6a5Schristos 		goto next_crontab;
2700061c6a5Schristos 	}
2710061c6a5Schristos 
2720061c6a5Schristos 	if ((crontab_fd = open(tabname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < OK) {
2730061c6a5Schristos 		/* crontab not accessible?
2740061c6a5Schristos 		 */
2750061c6a5Schristos 		log_it(fname, getpid(), "CAN'T OPEN", tabname);
2760061c6a5Schristos 		goto next_crontab;
2770061c6a5Schristos 	}
2780061c6a5Schristos 
2790061c6a5Schristos 	if (fstat(crontab_fd, statbuf) < OK) {
2800061c6a5Schristos 		log_it(fname, getpid(), "FSTAT FAILED", tabname);
2810061c6a5Schristos 		goto next_crontab;
2820061c6a5Schristos 	}
2830061c6a5Schristos 	if (!S_ISREG(statbuf->st_mode)) {
2840061c6a5Schristos 		log_it(fname, getpid(), "NOT REGULAR", tabname);
2850061c6a5Schristos 		goto next_crontab;
2860061c6a5Schristos 	}
287af0a310dSchristos 	if ((eqmode && (statbuf->st_mode & 07577) != eqmode) ||
2884d77e7cfSchristos 	    (badmode && (statbuf->st_mode & badmode) != 0)) {
2890061c6a5Schristos 		log_it(fname, getpid(), "BAD FILE MODE", tabname);
2900061c6a5Schristos 		goto next_crontab;
2910061c6a5Schristos 	}
2920061c6a5Schristos 	if (statbuf->st_uid != ROOT_UID && (pw == NULL ||
2930061c6a5Schristos 	    statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
2940061c6a5Schristos 		log_it(fname, getpid(), "WRONG FILE OWNER", tabname);
2950061c6a5Schristos 		goto next_crontab;
2960061c6a5Schristos 	}
2970061c6a5Schristos 	if (statbuf->st_nlink != 1) {
2980061c6a5Schristos 		log_it(fname, getpid(), "BAD LINK COUNT", tabname);
2990061c6a5Schristos 		goto next_crontab;
3000061c6a5Schristos 	}
3010061c6a5Schristos 
302032a4398Schristos 	Debug(DLOAD, ("\t%s:", fname));
3030061c6a5Schristos 	u = find_user(old_db, fname);
3040061c6a5Schristos 	if (u != NULL) {
3050061c6a5Schristos 		/* if crontab has not changed since we last read it
3060061c6a5Schristos 		 * in, then we can just use our existing entry.
3070061c6a5Schristos 		 */
3080061c6a5Schristos 		if (u->mtime == statbuf->st_mtime) {
309032a4398Schristos 			Debug(DLOAD, (" [no change, using old data]"));
3100061c6a5Schristos 			unlink_user(old_db, u);
3110061c6a5Schristos 			link_user(new_db, u);
3120061c6a5Schristos 			goto next_crontab;
3130061c6a5Schristos 		}
3140061c6a5Schristos 
3150061c6a5Schristos 		/* before we fall through to the code that will reload
3160061c6a5Schristos 		 * the user, let's deallocate and unlink the user in
3170061c6a5Schristos 		 * the old database.  This is more a point of memory
3180061c6a5Schristos 		 * efficiency than anything else, since all leftover
3190061c6a5Schristos 		 * users will be deleted from the old database when
3200061c6a5Schristos 		 * we finish with the crontab...
3210061c6a5Schristos 		 */
322032a4398Schristos 		Debug(DLOAD, (" [delete old data]"));
3230061c6a5Schristos 		unlink_user(old_db, u);
3240061c6a5Schristos 		free_user(u);
3250061c6a5Schristos 		log_it(fname, getpid(), "RELOAD", tabname);
3260061c6a5Schristos 	}
3270061c6a5Schristos 	u = load_user(crontab_fd, pw, fname);
3280061c6a5Schristos 	if (u != NULL) {
3290061c6a5Schristos 		u->mtime = statbuf->st_mtime;
3300061c6a5Schristos 		link_user(new_db, u);
3310061c6a5Schristos 	}
3320061c6a5Schristos 
3330061c6a5Schristos  next_crontab:
3340061c6a5Schristos 	if (crontab_fd >= OK) {
335032a4398Schristos 		Debug(DLOAD, (" [done]\n"));
336032a4398Schristos 		(void)close(crontab_fd);
3370061c6a5Schristos 	}
3380061c6a5Schristos }
339