12311Sjkh/* Copyright 1988,1990,1993,1994 by Paul Vixie
22311Sjkh * All rights reserved
32311Sjkh *
42311Sjkh * Distribute freely, except: don't remove my name from the source or
52311Sjkh * documentation (don't take credit for my work), mark your changes (don't
62311Sjkh * get me blamed for your possible bugs), don't alter or remove this
72311Sjkh * notice.  May be sold if buildable source is provided to buyer.  No
82311Sjkh * warrantee of any kind, express or implied, is included with this
92311Sjkh * software; use at your own risk, responsibility for damages (if any) to
102311Sjkh * anyone resulting from the use of this software rests entirely with the
112311Sjkh * user.
122311Sjkh *
132311Sjkh * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
142311Sjkh * I'll try to keep a version up to date.  I can be reached as follows:
152311Sjkh * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
162311Sjkh */
172311Sjkh
182311Sjkh#if !defined(lint) && !defined(LINT)
1929452Scharnierstatic const char rcsid[] =
2050479Speter  "$FreeBSD$";
212311Sjkh#endif
222311Sjkh
232311Sjkh/* vix 26jan87 [RCS has the log]
242311Sjkh */
252311Sjkh
262311Sjkh
272311Sjkh#include "cron.h"
282311Sjkh#include <fcntl.h>
292311Sjkh#include <sys/stat.h>
302311Sjkh#include <sys/file.h>
312311Sjkh
322311Sjkh
332311Sjkh#define TMAX(a,b) ((a)>(b)?(a):(b))
342311Sjkh
352311Sjkh
36173412Skevlostatic	void		process_crontab(char *, char *, char *,
372311Sjkh					     struct stat *,
38173412Skevlo					     cron_db *, cron_db *);
392311Sjkh
402311Sjkh
412311Sjkhvoid
422311Sjkhload_database(old_db)
432311Sjkh	cron_db		*old_db;
442311Sjkh{
452311Sjkh	DIR		*dir;
462311Sjkh	struct stat	statbuf;
472311Sjkh	struct stat	syscron_stat;
482311Sjkh	DIR_T   	*dp;
492311Sjkh	cron_db		new_db;
502311Sjkh	user		*u, *nu;
512311Sjkh
522311Sjkh	Debug(DLOAD, ("[%d] load_database()\n", getpid()))
532311Sjkh
542311Sjkh	/* before we start loading any data, do a stat on SPOOL_DIR
552311Sjkh	 * so that if anything changes as of this moment (i.e., before we've
562311Sjkh	 * cached any of the database), we'll see the changes next time.
572311Sjkh	 */
582311Sjkh	if (stat(SPOOL_DIR, &statbuf) < OK) {
592311Sjkh		log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR);
602311Sjkh		(void) exit(ERROR_EXIT);
612311Sjkh	}
622311Sjkh
632311Sjkh	/* track system crontab file
642311Sjkh	 */
652311Sjkh	if (stat(SYSCRONTAB, &syscron_stat) < OK)
662311Sjkh		syscron_stat.st_mtime = 0;
672311Sjkh
682311Sjkh	/* if spooldir's mtime has not changed, we don't need to fiddle with
692311Sjkh	 * the database.
702311Sjkh	 *
712311Sjkh	 * Note that old_db->mtime is initialized to 0 in main(), and
722311Sjkh	 * so is guaranteed to be different than the stat() mtime the first
732311Sjkh	 * time this function is called.
742311Sjkh	 */
752311Sjkh	if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) {
762311Sjkh		Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
772311Sjkh			      getpid()))
782311Sjkh		return;
792311Sjkh	}
802311Sjkh
812311Sjkh	/* something's different.  make a new database, moving unchanged
822311Sjkh	 * elements from the old database, reloading elements that have
832311Sjkh	 * actually changed.  Whatever is left in the old database when
842311Sjkh	 * we're done is chaff -- crontabs that disappeared.
852311Sjkh	 */
862311Sjkh	new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime);
872311Sjkh	new_db.head = new_db.tail = NULL;
882311Sjkh
892311Sjkh	if (syscron_stat.st_mtime) {
90170890Syar		process_crontab("root", SYS_NAME,
912311Sjkh				SYSCRONTAB, &syscron_stat,
922311Sjkh				&new_db, old_db);
932311Sjkh	}
942311Sjkh
952311Sjkh	/* we used to keep this dir open all the time, for the sake of
962311Sjkh	 * efficiency.  however, we need to close it in every fork, and
972311Sjkh	 * we fork a lot more often than the mtime of the dir changes.
982311Sjkh	 */
992311Sjkh	if (!(dir = opendir(SPOOL_DIR))) {
1002311Sjkh		log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR);
1012311Sjkh		(void) exit(ERROR_EXIT);
1022311Sjkh	}
1032311Sjkh
1042311Sjkh	while (NULL != (dp = readdir(dir))) {
1052311Sjkh		char	fname[MAXNAMLEN+1],
1062311Sjkh			tabname[MAXNAMLEN+1];
1072311Sjkh
1082311Sjkh		/* avoid file names beginning with ".".  this is good
1092311Sjkh		 * because we would otherwise waste two guaranteed calls
1102311Sjkh		 * to getpwnam() for . and .., and also because user names
1112311Sjkh		 * starting with a period are just too nasty to consider.
1122311Sjkh		 */
1132311Sjkh		if (dp->d_name[0] == '.')
1142311Sjkh			continue;
1152311Sjkh
11620573Spst		(void) strncpy(fname, dp->d_name, sizeof(fname));
11720573Spst		fname[sizeof(fname)-1] = '\0';
11820573Spst		(void) snprintf(tabname, sizeof tabname, CRON_TAB(fname));
1192311Sjkh
1202311Sjkh		process_crontab(fname, fname, tabname,
1212311Sjkh				&statbuf, &new_db, old_db);
1222311Sjkh	}
1232311Sjkh	closedir(dir);
1242311Sjkh
1252311Sjkh	/* if we don't do this, then when our children eventually call
1262311Sjkh	 * getpwnam() in do_command.c's child_process to verify MAILTO=,
1272311Sjkh	 * they will screw us up (and v-v).
1282311Sjkh	 */
1292311Sjkh	endpwent();
1302311Sjkh
1312311Sjkh	/* whatever's left in the old database is now junk.
1322311Sjkh	 */
1332311Sjkh	Debug(DLOAD, ("unlinking old database:\n"))
1342311Sjkh	for (u = old_db->head;  u != NULL;  u = nu) {
1352311Sjkh		Debug(DLOAD, ("\t%s\n", u->name))
1362311Sjkh		nu = u->next;
1372311Sjkh		unlink_user(old_db, u);
1382311Sjkh		free_user(u);
1392311Sjkh	}
1402311Sjkh
1412311Sjkh	/* overwrite the database control block with the new one.
1422311Sjkh	 */
1432311Sjkh	*old_db = new_db;
1442311Sjkh	Debug(DLOAD, ("load_database is done\n"))
1452311Sjkh}
1462311Sjkh
1472311Sjkh
1482311Sjkhvoid
1492311Sjkhlink_user(db, u)
1502311Sjkh	cron_db	*db;
1512311Sjkh	user	*u;
1522311Sjkh{
1532311Sjkh	if (db->head == NULL)
1542311Sjkh		db->head = u;
1552311Sjkh	if (db->tail)
1562311Sjkh		db->tail->next = u;
1572311Sjkh	u->prev = db->tail;
1582311Sjkh	u->next = NULL;
1592311Sjkh	db->tail = u;
1602311Sjkh}
1612311Sjkh
1622311Sjkh
1632311Sjkhvoid
1642311Sjkhunlink_user(db, u)
1652311Sjkh	cron_db	*db;
1662311Sjkh	user	*u;
1672311Sjkh{
1682311Sjkh	if (u->prev == NULL)
1692311Sjkh		db->head = u->next;
1702311Sjkh	else
1712311Sjkh		u->prev->next = u->next;
1722311Sjkh
1732311Sjkh	if (u->next == NULL)
1742311Sjkh		db->tail = u->prev;
1752311Sjkh	else
1762311Sjkh		u->next->prev = u->prev;
1772311Sjkh}
1782311Sjkh
1792311Sjkh
1802311Sjkhuser *
1812311Sjkhfind_user(db, name)
1822311Sjkh	cron_db	*db;
1832311Sjkh	char	*name;
1842311Sjkh{
1852311Sjkh	char	*env_get();
1862311Sjkh	user	*u;
1872311Sjkh
1882311Sjkh	for (u = db->head;  u != NULL;  u = u->next)
1892311Sjkh		if (!strcmp(u->name, name))
1902311Sjkh			break;
1912311Sjkh	return u;
1922311Sjkh}
1932311Sjkh
1942311Sjkh
1952311Sjkhstatic void
1962311Sjkhprocess_crontab(uname, fname, tabname, statbuf, new_db, old_db)
1972311Sjkh	char		*uname;
1982311Sjkh	char		*fname;
1992311Sjkh	char		*tabname;
2002311Sjkh	struct stat	*statbuf;
2012311Sjkh	cron_db		*new_db;
2022311Sjkh	cron_db		*old_db;
2032311Sjkh{
2042311Sjkh	struct passwd	*pw = NULL;
2052311Sjkh	int		crontab_fd = OK - 1;
2062311Sjkh	user		*u;
2072311Sjkh
208170890Syar	if (strcmp(fname, SYS_NAME) && !(pw = getpwnam(uname))) {
2092311Sjkh		/* file doesn't have a user in passwd file.
2102311Sjkh		 */
2112311Sjkh		log_it(fname, getpid(), "ORPHAN", "no passwd entry");
2122311Sjkh		goto next_crontab;
2132311Sjkh	}
2142311Sjkh
2152311Sjkh	if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) {
2162311Sjkh		/* crontab not accessible?
2172311Sjkh		 */
2182311Sjkh		log_it(fname, getpid(), "CAN'T OPEN", tabname);
2192311Sjkh		goto next_crontab;
2202311Sjkh	}
2212311Sjkh
2222311Sjkh	if (fstat(crontab_fd, statbuf) < OK) {
2232311Sjkh		log_it(fname, getpid(), "FSTAT FAILED", tabname);
2242311Sjkh		goto next_crontab;
2252311Sjkh	}
2262311Sjkh
2272311Sjkh	Debug(DLOAD, ("\t%s:", fname))
2282311Sjkh	u = find_user(old_db, fname);
2292311Sjkh	if (u != NULL) {
2302311Sjkh		/* if crontab has not changed since we last read it
2312311Sjkh		 * in, then we can just use our existing entry.
2322311Sjkh		 */
2332311Sjkh		if (u->mtime == statbuf->st_mtime) {
2342311Sjkh			Debug(DLOAD, (" [no change, using old data]"))
2352311Sjkh			unlink_user(old_db, u);
2362311Sjkh			link_user(new_db, u);
2372311Sjkh			goto next_crontab;
2382311Sjkh		}
2392311Sjkh
2402311Sjkh		/* before we fall through to the code that will reload
2412311Sjkh		 * the user, let's deallocate and unlink the user in
2422311Sjkh		 * the old database.  This is more a point of memory
2432311Sjkh		 * efficiency than anything else, since all leftover
2442311Sjkh		 * users will be deleted from the old database when
2452311Sjkh		 * we finish with the crontab...
2462311Sjkh		 */
2472311Sjkh		Debug(DLOAD, (" [delete old data]"))
2482311Sjkh		unlink_user(old_db, u);
2492311Sjkh		free_user(u);
2502311Sjkh		log_it(fname, getpid(), "RELOAD", tabname);
2512311Sjkh	}
2522311Sjkh	u = load_user(crontab_fd, pw, fname);
2532311Sjkh	if (u != NULL) {
2542311Sjkh		u->mtime = statbuf->st_mtime;
2552311Sjkh		link_user(new_db, u);
2562311Sjkh	}
2572311Sjkh
2582311Sjkhnext_crontab:
2592311Sjkh	if (crontab_fd >= OK) {
2602311Sjkh		Debug(DLOAD, (" [done]\n"))
2612311Sjkh		close(crontab_fd);
2622311Sjkh	}
2632311Sjkh}
264