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