xref: /netbsd/external/bsd/cron/dist/database.c (revision 1d04827f)
1*1d04827fSchristos /*	$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*1d04827fSchristos __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*1d04827fSchristos struct spooldir {
40*1d04827fSchristos 	const char *path;
41*1d04827fSchristos 	const char *uname;
42*1d04827fSchristos 	const char *fname;
43*1d04827fSchristos 	struct stat st;
44*1d04827fSchristos };
45*1d04827fSchristos 
46*1d04827fSchristos static struct spooldir spools[] = {
47*1d04827fSchristos 	{ .path = SPOOL_DIR, },
48*1d04827fSchristos 	{ .path = CROND_DIR, .uname = "root", .fname = "*system*", },
49*1d04827fSchristos 	{ .path = NULL, }
50*1d04827fSchristos };
51*1d04827fSchristos 
520061c6a5Schristos static	void		process_crontab(const char *, const char *,
530061c6a5Schristos 					const char *, struct stat *,
540061c6a5Schristos 					cron_db *, cron_db *);
550061c6a5Schristos 
5655716f1eSchristos static void
process_dir(struct spooldir * sp,cron_db * new_db,cron_db * old_db)57*1d04827fSchristos process_dir(struct spooldir *sp, cron_db *new_db, cron_db *old_db)
5855716f1eSchristos {
5955716f1eSchristos 	DIR *dir;
6055716f1eSchristos 	DIR_T *dp;
61*1d04827fSchristos 	const char *dname = sp->path;
62*1d04827fSchristos 	struct stat *st = &sp->st;
63*1d04827fSchristos 
64*1d04827fSchristos 	if (st->st_mtime == 0)
65*1d04827fSchristos 		return;
6655716f1eSchristos 
6755716f1eSchristos 	/* we used to keep this dir open all the time, for the sake of
6855716f1eSchristos 	 * efficiency.  however, we need to close it in every fork, and
6955716f1eSchristos 	 * we fork a lot more often than the mtime of the dir changes.
7055716f1eSchristos 	 */
7155716f1eSchristos 	if (!(dir = opendir(dname))) {
7255716f1eSchristos 		log_it("CRON", getpid(), "OPENDIR FAILED", dname);
7355716f1eSchristos 		(void) exit(ERROR_EXIT);
7455716f1eSchristos 	}
7555716f1eSchristos 
7655716f1eSchristos 	while (NULL != (dp = readdir(dir))) {
7755716f1eSchristos 		char	fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
7855716f1eSchristos 		size_t i, len;
7955716f1eSchristos 		/*
8055716f1eSchristos 		 * Homage to...
8155716f1eSchristos 		 */
8255716f1eSchristos 		static const char *junk[] = {
837189cd3bSchristos 			"rpmsave", "rpmorig", "rpmnew",
8455716f1eSchristos 		};
8555716f1eSchristos 
8655716f1eSchristos 		/* avoid file names beginning with ".".  this is good
8755716f1eSchristos 		 * because we would otherwise waste two guaranteed calls
8855716f1eSchristos 		 * to getpwnam() for . and .., and there shouldn't be
8955716f1eSchristos 		 * hidden files in here anyway (in the non system case).
9055716f1eSchristos 		 */
9155716f1eSchristos 		if (dp->d_name[0] == '.')
9255716f1eSchristos 			continue;
9355716f1eSchristos 
9455716f1eSchristos 		/* ignore files starting with # ... */
9555716f1eSchristos 		if (dp->d_name[0] == '#')
9655716f1eSchristos 			continue;
9755716f1eSchristos 
9855716f1eSchristos 		len = strlen(dp->d_name);
9955716f1eSchristos 
10055716f1eSchristos 		/* ... or too big or to small ... */
10155716f1eSchristos 		if (len == 0 || len >= sizeof(fname)) {
10255716f1eSchristos 			log_it(dp->d_name, getpid(), "ORPHAN",
10355716f1eSchristos 			    "name too short or long");
10455716f1eSchristos 			continue;
10555716f1eSchristos 		}
10655716f1eSchristos 
10755716f1eSchristos 		/* ... or ending with ~ ... */
10834fe3d0cSjoerg 		if (dp->d_name[len - 1] == '~')
10955716f1eSchristos 			continue;
11055716f1eSchristos 
11155716f1eSchristos 		(void)strlcpy(fname, dp->d_name, sizeof(fname));
11255716f1eSchristos 
1137189cd3bSchristos 		/* ... or look for blacklisted extensions */
11455716f1eSchristos 		for (i = 0; i < __arraycount(junk); i++) {
11555716f1eSchristos 			char *p;
1167189cd3bSchristos 			if ((p = strrchr(fname, '.')) != NULL &&
1177189cd3bSchristos 			    strcmp(p + 1, junk[i]) == 0)
11855716f1eSchristos 				break;
11955716f1eSchristos 		}
12055716f1eSchristos 		if (i != __arraycount(junk))
12155716f1eSchristos 			continue;
12255716f1eSchristos 
12355716f1eSchristos 		if (!glue_strings(tabname, sizeof tabname, dname, fname, '/')) {
12455716f1eSchristos 			log_it(fname, getpid(), "ORPHAN",
12555716f1eSchristos 			    "could not glue strings");
12655716f1eSchristos 			continue;
12755716f1eSchristos 		}
12855716f1eSchristos 
129*1d04827fSchristos 		process_crontab(sp->uname ? sp->uname : fname,
130*1d04827fSchristos 				sp->fname ? sp->fname : fname,
131*1d04827fSchristos 				tabname, st, new_db, old_db);
13255716f1eSchristos 	}
13355716f1eSchristos 	(void)closedir(dir);
13455716f1eSchristos }
13555716f1eSchristos 
1360061c6a5Schristos void
load_database(cron_db * old_db)1370061c6a5Schristos load_database(cron_db *old_db) {
138*1d04827fSchristos 	struct stat syscron_stat;
1390061c6a5Schristos 	cron_db new_db;
1400061c6a5Schristos 	user *u, *nu;
141*1d04827fSchristos 	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*1d04827fSchristos 	maxtime = syscron_stat.st_mtime;
151*1d04827fSchristos 	for (struct spooldir *p = spools; p->path; p++) {
152*1d04827fSchristos 		if (stat(p->path, &p->st) < OK) {
153*1d04827fSchristos 			if (errno == ENOENT) {
154*1d04827fSchristos 				p->st.st_mtime = 0;
155*1d04827fSchristos 				continue;
156*1d04827fSchristos 			}
157*1d04827fSchristos 			log_it("CRON", getpid(), "STAT FAILED", p->path);
158*1d04827fSchristos 			(void) exit(ERROR_EXIT);
159*1d04827fSchristos 		}
160*1d04827fSchristos 		if (p->st.st_mtime > maxtime)
161*1d04827fSchristos 			maxtime = p->st.st_mtime;
162*1d04827fSchristos 	}
163*1d04827fSchristos 
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*1d04827fSchristos 	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*1d04827fSchristos 	new_db.mtime = maxtime;
1830061c6a5Schristos 	new_db.head = new_db.tail = NULL;
1840061c6a5Schristos 
1850061c6a5Schristos 	if (syscron_stat.st_mtime)
186*1d04827fSchristos 		process_crontab("root", "*system*", SYSCRONTAB,
187*1d04827fSchristos 		    &syscron_stat, &new_db, old_db);
1880061c6a5Schristos 
189*1d04827fSchristos  	for (struct spooldir *p = spools; p->path; p++)
190*1d04827fSchristos 		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;
25405c26827Schristos 	mode_t eqmode = 0400, badmode = 0;
2550061c6a5Schristos 	user *u;
2560061c6a5Schristos 
257*1d04827fSchristos 	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 	}
28705c26827Schristos 	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