1*0061c6a5Schristos /* Copyright 1988,1990,1993,1994 by Paul Vixie 2*0061c6a5Schristos * All rights reserved 3*0061c6a5Schristos */ 4*0061c6a5Schristos 5*0061c6a5Schristos /* 6*0061c6a5Schristos * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 7*0061c6a5Schristos * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 8*0061c6a5Schristos * 9*0061c6a5Schristos * Permission to use, copy, modify, and distribute this software for any 10*0061c6a5Schristos * purpose with or without fee is hereby granted, provided that the above 11*0061c6a5Schristos * copyright notice and this permission notice appear in all copies. 12*0061c6a5Schristos * 13*0061c6a5Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 14*0061c6a5Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15*0061c6a5Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 16*0061c6a5Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17*0061c6a5Schristos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18*0061c6a5Schristos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 19*0061c6a5Schristos * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20*0061c6a5Schristos */ 21*0061c6a5Schristos 22*0061c6a5Schristos #if !defined(lint) && !defined(LINT) 23*0061c6a5Schristos static char rcsid[] = "Id: database.c,v 1.7 2004/01/23 18:56:42 vixie Exp"; 24*0061c6a5Schristos #endif 25*0061c6a5Schristos 26*0061c6a5Schristos /* vix 26jan87 [RCS has the log] 27*0061c6a5Schristos */ 28*0061c6a5Schristos 29*0061c6a5Schristos #include "cron.h" 30*0061c6a5Schristos 31*0061c6a5Schristos #define TMAX(a,b) ((a)>(b)?(a):(b)) 32*0061c6a5Schristos 33*0061c6a5Schristos static void process_crontab(const char *, const char *, 34*0061c6a5Schristos const char *, struct stat *, 35*0061c6a5Schristos cron_db *, cron_db *); 36*0061c6a5Schristos 37*0061c6a5Schristos void 38*0061c6a5Schristos load_database(cron_db *old_db) { 39*0061c6a5Schristos struct stat statbuf, syscron_stat; 40*0061c6a5Schristos cron_db new_db; 41*0061c6a5Schristos DIR_T *dp; 42*0061c6a5Schristos DIR *dir; 43*0061c6a5Schristos user *u, *nu; 44*0061c6a5Schristos 45*0061c6a5Schristos Debug(DLOAD, ("[%ld] load_database()\n", (long)getpid())) 46*0061c6a5Schristos 47*0061c6a5Schristos /* before we start loading any data, do a stat on SPOOL_DIR 48*0061c6a5Schristos * so that if anything changes as of this moment (i.e., before we've 49*0061c6a5Schristos * cached any of the database), we'll see the changes next time. 50*0061c6a5Schristos */ 51*0061c6a5Schristos if (stat(SPOOL_DIR, &statbuf) < OK) { 52*0061c6a5Schristos log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); 53*0061c6a5Schristos (void) exit(ERROR_EXIT); 54*0061c6a5Schristos } 55*0061c6a5Schristos 56*0061c6a5Schristos /* track system crontab file 57*0061c6a5Schristos */ 58*0061c6a5Schristos if (stat(SYSCRONTAB, &syscron_stat) < OK) 59*0061c6a5Schristos syscron_stat.st_mtime = 0; 60*0061c6a5Schristos 61*0061c6a5Schristos /* if spooldir's mtime has not changed, we don't need to fiddle with 62*0061c6a5Schristos * the database. 63*0061c6a5Schristos * 64*0061c6a5Schristos * Note that old_db->mtime is initialized to 0 in main(), and 65*0061c6a5Schristos * so is guaranteed to be different than the stat() mtime the first 66*0061c6a5Schristos * time this function is called. 67*0061c6a5Schristos */ 68*0061c6a5Schristos if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) { 69*0061c6a5Schristos Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n", 70*0061c6a5Schristos (long)getpid())) 71*0061c6a5Schristos return; 72*0061c6a5Schristos } 73*0061c6a5Schristos 74*0061c6a5Schristos /* something's different. make a new database, moving unchanged 75*0061c6a5Schristos * elements from the old database, reloading elements that have 76*0061c6a5Schristos * actually changed. Whatever is left in the old database when 77*0061c6a5Schristos * we're done is chaff -- crontabs that disappeared. 78*0061c6a5Schristos */ 79*0061c6a5Schristos new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); 80*0061c6a5Schristos new_db.head = new_db.tail = NULL; 81*0061c6a5Schristos 82*0061c6a5Schristos if (syscron_stat.st_mtime) 83*0061c6a5Schristos process_crontab("root", NULL, SYSCRONTAB, &syscron_stat, 84*0061c6a5Schristos &new_db, old_db); 85*0061c6a5Schristos 86*0061c6a5Schristos /* we used to keep this dir open all the time, for the sake of 87*0061c6a5Schristos * efficiency. however, we need to close it in every fork, and 88*0061c6a5Schristos * we fork a lot more often than the mtime of the dir changes. 89*0061c6a5Schristos */ 90*0061c6a5Schristos if (!(dir = opendir(SPOOL_DIR))) { 91*0061c6a5Schristos log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); 92*0061c6a5Schristos (void) exit(ERROR_EXIT); 93*0061c6a5Schristos } 94*0061c6a5Schristos 95*0061c6a5Schristos while (NULL != (dp = readdir(dir))) { 96*0061c6a5Schristos char fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1]; 97*0061c6a5Schristos 98*0061c6a5Schristos /* avoid file names beginning with ".". this is good 99*0061c6a5Schristos * because we would otherwise waste two guaranteed calls 100*0061c6a5Schristos * to getpwnam() for . and .., and also because user names 101*0061c6a5Schristos * starting with a period are just too nasty to consider. 102*0061c6a5Schristos */ 103*0061c6a5Schristos if (dp->d_name[0] == '.') 104*0061c6a5Schristos continue; 105*0061c6a5Schristos 106*0061c6a5Schristos if (strlen(dp->d_name) >= sizeof fname) 107*0061c6a5Schristos continue; /* XXX log? */ 108*0061c6a5Schristos (void) strcpy(fname, dp->d_name); 109*0061c6a5Schristos 110*0061c6a5Schristos if (!glue_strings(tabname, sizeof tabname, SPOOL_DIR, 111*0061c6a5Schristos fname, '/')) 112*0061c6a5Schristos continue; /* XXX log? */ 113*0061c6a5Schristos 114*0061c6a5Schristos process_crontab(fname, fname, tabname, 115*0061c6a5Schristos &statbuf, &new_db, old_db); 116*0061c6a5Schristos } 117*0061c6a5Schristos closedir(dir); 118*0061c6a5Schristos 119*0061c6a5Schristos /* if we don't do this, then when our children eventually call 120*0061c6a5Schristos * getpwnam() in do_command.c's child_process to verify MAILTO=, 121*0061c6a5Schristos * they will screw us up (and v-v). 122*0061c6a5Schristos */ 123*0061c6a5Schristos endpwent(); 124*0061c6a5Schristos 125*0061c6a5Schristos /* whatever's left in the old database is now junk. 126*0061c6a5Schristos */ 127*0061c6a5Schristos Debug(DLOAD, ("unlinking old database:\n")) 128*0061c6a5Schristos for (u = old_db->head; u != NULL; u = nu) { 129*0061c6a5Schristos Debug(DLOAD, ("\t%s\n", u->name)) 130*0061c6a5Schristos nu = u->next; 131*0061c6a5Schristos unlink_user(old_db, u); 132*0061c6a5Schristos free_user(u); 133*0061c6a5Schristos } 134*0061c6a5Schristos 135*0061c6a5Schristos /* overwrite the database control block with the new one. 136*0061c6a5Schristos */ 137*0061c6a5Schristos *old_db = new_db; 138*0061c6a5Schristos Debug(DLOAD, ("load_database is done\n")) 139*0061c6a5Schristos } 140*0061c6a5Schristos 141*0061c6a5Schristos void 142*0061c6a5Schristos link_user(cron_db *db, user *u) { 143*0061c6a5Schristos if (db->head == NULL) 144*0061c6a5Schristos db->head = u; 145*0061c6a5Schristos if (db->tail) 146*0061c6a5Schristos db->tail->next = u; 147*0061c6a5Schristos u->prev = db->tail; 148*0061c6a5Schristos u->next = NULL; 149*0061c6a5Schristos db->tail = u; 150*0061c6a5Schristos } 151*0061c6a5Schristos 152*0061c6a5Schristos void 153*0061c6a5Schristos unlink_user(cron_db *db, user *u) { 154*0061c6a5Schristos if (u->prev == NULL) 155*0061c6a5Schristos db->head = u->next; 156*0061c6a5Schristos else 157*0061c6a5Schristos u->prev->next = u->next; 158*0061c6a5Schristos 159*0061c6a5Schristos if (u->next == NULL) 160*0061c6a5Schristos db->tail = u->prev; 161*0061c6a5Schristos else 162*0061c6a5Schristos u->next->prev = u->prev; 163*0061c6a5Schristos } 164*0061c6a5Schristos 165*0061c6a5Schristos user * 166*0061c6a5Schristos find_user(cron_db *db, const char *name) { 167*0061c6a5Schristos user *u; 168*0061c6a5Schristos 169*0061c6a5Schristos for (u = db->head; u != NULL; u = u->next) 170*0061c6a5Schristos if (strcmp(u->name, name) == 0) 171*0061c6a5Schristos break; 172*0061c6a5Schristos return (u); 173*0061c6a5Schristos } 174*0061c6a5Schristos 175*0061c6a5Schristos static void 176*0061c6a5Schristos process_crontab(const char *uname, const char *fname, const char *tabname, 177*0061c6a5Schristos struct stat *statbuf, cron_db *new_db, cron_db *old_db) 178*0061c6a5Schristos { 179*0061c6a5Schristos struct passwd *pw = NULL; 180*0061c6a5Schristos int crontab_fd = OK - 1; 181*0061c6a5Schristos user *u; 182*0061c6a5Schristos 183*0061c6a5Schristos if (fname == NULL) { 184*0061c6a5Schristos /* must be set to something for logging purposes. 185*0061c6a5Schristos */ 186*0061c6a5Schristos fname = "*system*"; 187*0061c6a5Schristos } else if ((pw = getpwnam(uname)) == NULL) { 188*0061c6a5Schristos /* file doesn't have a user in passwd file. 189*0061c6a5Schristos */ 190*0061c6a5Schristos log_it(fname, getpid(), "ORPHAN", "no passwd entry"); 191*0061c6a5Schristos goto next_crontab; 192*0061c6a5Schristos } 193*0061c6a5Schristos 194*0061c6a5Schristos if ((crontab_fd = open(tabname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < OK) { 195*0061c6a5Schristos /* crontab not accessible? 196*0061c6a5Schristos */ 197*0061c6a5Schristos log_it(fname, getpid(), "CAN'T OPEN", tabname); 198*0061c6a5Schristos goto next_crontab; 199*0061c6a5Schristos } 200*0061c6a5Schristos 201*0061c6a5Schristos if (fstat(crontab_fd, statbuf) < OK) { 202*0061c6a5Schristos log_it(fname, getpid(), "FSTAT FAILED", tabname); 203*0061c6a5Schristos goto next_crontab; 204*0061c6a5Schristos } 205*0061c6a5Schristos if (!S_ISREG(statbuf->st_mode)) { 206*0061c6a5Schristos log_it(fname, getpid(), "NOT REGULAR", tabname); 207*0061c6a5Schristos goto next_crontab; 208*0061c6a5Schristos } 209*0061c6a5Schristos if ((statbuf->st_mode & 07777) != 0600) { 210*0061c6a5Schristos log_it(fname, getpid(), "BAD FILE MODE", tabname); 211*0061c6a5Schristos goto next_crontab; 212*0061c6a5Schristos } 213*0061c6a5Schristos if (statbuf->st_uid != ROOT_UID && (pw == NULL || 214*0061c6a5Schristos statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) { 215*0061c6a5Schristos log_it(fname, getpid(), "WRONG FILE OWNER", tabname); 216*0061c6a5Schristos goto next_crontab; 217*0061c6a5Schristos } 218*0061c6a5Schristos if (statbuf->st_nlink != 1) { 219*0061c6a5Schristos log_it(fname, getpid(), "BAD LINK COUNT", tabname); 220*0061c6a5Schristos goto next_crontab; 221*0061c6a5Schristos } 222*0061c6a5Schristos 223*0061c6a5Schristos Debug(DLOAD, ("\t%s:", fname)) 224*0061c6a5Schristos u = find_user(old_db, fname); 225*0061c6a5Schristos if (u != NULL) { 226*0061c6a5Schristos /* if crontab has not changed since we last read it 227*0061c6a5Schristos * in, then we can just use our existing entry. 228*0061c6a5Schristos */ 229*0061c6a5Schristos if (u->mtime == statbuf->st_mtime) { 230*0061c6a5Schristos Debug(DLOAD, (" [no change, using old data]")) 231*0061c6a5Schristos unlink_user(old_db, u); 232*0061c6a5Schristos link_user(new_db, u); 233*0061c6a5Schristos goto next_crontab; 234*0061c6a5Schristos } 235*0061c6a5Schristos 236*0061c6a5Schristos /* before we fall through to the code that will reload 237*0061c6a5Schristos * the user, let's deallocate and unlink the user in 238*0061c6a5Schristos * the old database. This is more a point of memory 239*0061c6a5Schristos * efficiency than anything else, since all leftover 240*0061c6a5Schristos * users will be deleted from the old database when 241*0061c6a5Schristos * we finish with the crontab... 242*0061c6a5Schristos */ 243*0061c6a5Schristos Debug(DLOAD, (" [delete old data]")) 244*0061c6a5Schristos unlink_user(old_db, u); 245*0061c6a5Schristos free_user(u); 246*0061c6a5Schristos log_it(fname, getpid(), "RELOAD", tabname); 247*0061c6a5Schristos } 248*0061c6a5Schristos u = load_user(crontab_fd, pw, fname); 249*0061c6a5Schristos if (u != NULL) { 250*0061c6a5Schristos u->mtime = statbuf->st_mtime; 251*0061c6a5Schristos link_user(new_db, u); 252*0061c6a5Schristos } 253*0061c6a5Schristos 254*0061c6a5Schristos next_crontab: 255*0061c6a5Schristos if (crontab_fd >= OK) { 256*0061c6a5Schristos Debug(DLOAD, (" [done]\n")) 257*0061c6a5Schristos close(crontab_fd); 258*0061c6a5Schristos } 259*0061c6a5Schristos } 260