1 /* $OpenBSD: database.c,v 1.38 2019/06/28 13:32:47 deraadt 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 "globals.h" 38 #include "macros.h" 39 #include "structs.h" 40 #include "funcs.h" 41 42 #define HASH(a,b) ((a)+(b)) 43 44 static void process_crontab(int, const char *, const char *, 45 struct stat *, cron_db *, cron_db *); 46 47 void 48 load_database(cron_db **db) 49 { 50 struct stat statbuf, syscron_stat; 51 cron_db *new_db, *old_db = *db; 52 struct timespec mtime; 53 struct dirent *dp; 54 DIR *dir; 55 user *u; 56 57 /* before we start loading any data, do a stat on _PATH_CRON_SPOOL 58 * so that if anything changes as of this moment (i.e., before we've 59 * cached any of the database), we'll see the changes next time. 60 */ 61 if (stat(_PATH_CRON_SPOOL, &statbuf) == -1) { 62 syslog(LOG_ERR, "(CRON) STAT FAILED (%s)", _PATH_CRON_SPOOL); 63 return; 64 } 65 66 /* track system crontab file 67 */ 68 if (stat(_PATH_SYS_CRONTAB, &syscron_stat) == -1) 69 timespecclear(&syscron_stat.st_mtim); 70 71 /* hash mtime of system crontab file and crontab dir 72 */ 73 mtime.tv_sec = 74 HASH(statbuf.st_mtim.tv_sec, syscron_stat.st_mtim.tv_sec); 75 mtime.tv_nsec = 76 HASH(statbuf.st_mtim.tv_nsec, syscron_stat.st_mtim.tv_nsec); 77 78 /* if spooldir's mtime has not changed, we don't need to fiddle with 79 * the database. 80 */ 81 if (old_db != NULL && timespeccmp(&mtime, &old_db->mtime, ==)) 82 return; 83 84 /* something's different. make a new database, moving unchanged 85 * elements from the old database, reloading elements that have 86 * actually changed. Whatever is left in the old database when 87 * we're done is chaff -- crontabs that disappeared. 88 */ 89 if ((new_db = malloc(sizeof(*new_db))) == NULL) 90 return; 91 new_db->mtime = mtime; 92 TAILQ_INIT(&new_db->users); 93 94 if (timespecisset(&syscron_stat.st_mtim)) { 95 process_crontab(AT_FDCWD, "*system*", _PATH_SYS_CRONTAB, 96 &syscron_stat, new_db, old_db); 97 } 98 99 /* we used to keep this dir open all the time, for the sake of 100 * efficiency. however, we need to close it in every fork, and 101 * we fork a lot more often than the mtime of the dir changes. 102 */ 103 if (!(dir = opendir(_PATH_CRON_SPOOL))) { 104 syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_CRON_SPOOL); 105 /* Restore system crontab entry as needed. */ 106 if (!TAILQ_EMPTY(&new_db->users) && 107 (u = TAILQ_FIRST(&old_db->users))) { 108 if (strcmp(u->name, "*system*") == 0) { 109 TAILQ_REMOVE(&old_db->users, u, entries); 110 free_user(u); 111 TAILQ_INSERT_HEAD(&old_db->users, 112 TAILQ_FIRST(&new_db->users), entries); 113 } 114 } 115 free(new_db); 116 return; 117 } 118 119 while (NULL != (dp = readdir(dir))) { 120 /* avoid file names beginning with ".". this is good 121 * because we would otherwise waste two guaranteed calls 122 * to getpwnam() for . and .., and also because user names 123 * starting with a period are just too nasty to consider. 124 */ 125 if (dp->d_name[0] == '.') 126 continue; 127 128 process_crontab(dirfd(dir), dp->d_name, dp->d_name, 129 &statbuf, new_db, old_db); 130 } 131 closedir(dir); 132 133 /* if we don't do this, then when our children eventually call 134 * getpwnam() in do_command.c's child_process to verify MAILTO=, 135 * they will screw us up (and v-v). 136 */ 137 endpwent(); 138 139 /* whatever's left in the old database is now junk. 140 */ 141 if (old_db != NULL) { 142 while ((u = TAILQ_FIRST(&old_db->users))) { 143 TAILQ_REMOVE(&old_db->users, u, entries); 144 free_user(u); 145 } 146 free(old_db); 147 } 148 149 /* overwrite the database control block with the new one. 150 */ 151 *db = new_db; 152 } 153 154 user * 155 find_user(cron_db *db, const char *name) 156 { 157 user *u = NULL; 158 159 if (db != NULL) { 160 TAILQ_FOREACH(u, &db->users, entries) { 161 if (strcmp(u->name, name) == 0) 162 break; 163 } 164 } 165 return (u); 166 } 167 168 static void 169 process_crontab(int dfd, const char *uname, const char *fname, 170 struct stat *statbuf, cron_db *new_db, cron_db *old_db) 171 { 172 struct passwd *pw = NULL; 173 FILE *crontab_fp = NULL; 174 user *u, *new_u; 175 mode_t tabmask, tabperm; 176 int fd; 177 178 /* Note: pw must remain NULL for system crontab (see below). */ 179 if (fname[0] != '/' && (pw = getpwnam(uname)) == NULL) { 180 /* file doesn't have a user in passwd file. 181 */ 182 syslog(LOG_WARNING, "(%s) ORPHAN (no passwd entry)", uname); 183 goto next_crontab; 184 } 185 186 fd = openat(dfd, fname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC); 187 if (fd == -1) { 188 /* crontab not accessible? 189 */ 190 syslog(LOG_ERR, "(%s) CAN'T OPEN (%s)", uname, fname); 191 goto next_crontab; 192 } 193 if (!(crontab_fp = fdopen(fd, "r"))) { 194 syslog(LOG_ERR, "(%s) FDOPEN (%m)", fname); 195 close(fd); 196 goto next_crontab; 197 } 198 199 if (fstat(fileno(crontab_fp), statbuf) == -1) { 200 syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", uname, fname); 201 goto next_crontab; 202 } 203 if (!S_ISREG(statbuf->st_mode)) { 204 syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", uname, fname); 205 goto next_crontab; 206 } 207 /* Looser permissions on system crontab. */ 208 tabmask = pw ? ALLPERMS : (ALLPERMS & ~(S_IWUSR|S_IRGRP|S_IROTH)); 209 tabperm = pw ? (S_IRUSR|S_IWUSR) : S_IRUSR; 210 if ((statbuf->st_mode & tabmask) != tabperm) { 211 syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", uname, fname); 212 goto next_crontab; 213 } 214 if (statbuf->st_uid != 0 && (pw == NULL || 215 statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) { 216 syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", uname, fname); 217 goto next_crontab; 218 } 219 if (pw != NULL && statbuf->st_gid != cron_gid) { 220 syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", uname, fname); 221 goto next_crontab; 222 } 223 if (pw != NULL && statbuf->st_nlink != 1) { 224 syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", uname, fname); 225 goto next_crontab; 226 } 227 228 u = find_user(old_db, fname); 229 if (u != NULL) { 230 /* if crontab has not changed since we last read it 231 * in, then we can just use our existing entry. 232 */ 233 if (timespeccmp(&u->mtime, &statbuf->st_mtim, ==)) { 234 TAILQ_REMOVE(&old_db->users, u, entries); 235 TAILQ_INSERT_TAIL(&new_db->users, u, entries); 236 goto next_crontab; 237 } 238 syslog(LOG_INFO, "(%s) RELOAD (%s)", uname, fname); 239 } 240 241 new_u = load_user(crontab_fp, pw, fname); 242 if (new_u != NULL) { 243 /* Insert user into the new database and remove from old. */ 244 new_u->mtime = statbuf->st_mtim; 245 TAILQ_INSERT_TAIL(&new_db->users, new_u, entries); 246 if (u != NULL) { 247 TAILQ_REMOVE(&old_db->users, u, entries); 248 free_user(u); 249 } 250 } else if (u != NULL) { 251 /* New user crontab failed to load, preserve the old one. */ 252 TAILQ_REMOVE(&old_db->users, u, entries); 253 TAILQ_INSERT_TAIL(&new_db->users, u, entries); 254 } 255 256 next_crontab: 257 if (crontab_fp != NULL) { 258 fclose(crontab_fp); 259 } 260 } 261