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