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