GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/cron/database.c Lines: 0 90 0.0 %
Date: 2017-11-13 Branches: 0 96 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: database.c,v 1.36 2017/10/25 17:08:58 jca 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) < 0) {
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) < 0)
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
	int crontab_fd = -1;
174
	user *u, *new_u;
175
	mode_t tabmask, tabperm;
176
177
	/* Note: pw must remain NULL for system crontab (see below). */
178
	if (fname[0] != '/' && (pw = getpwnam(uname)) == NULL) {
179
		/* file doesn't have a user in passwd file.
180
		 */
181
		syslog(LOG_WARNING, "(%s) ORPHAN (no passwd entry)", uname);
182
		goto next_crontab;
183
	}
184
185
	crontab_fd = openat(dfd, fname,
186
	    O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);
187
	if (crontab_fd < 0) {
188
		/* crontab not accessible?
189
		 */
190
		syslog(LOG_ERR, "(%s) CAN'T OPEN (%s)", uname, fname);
191
		goto next_crontab;
192
	}
193
194
	if (fstat(crontab_fd, statbuf) < 0) {
195
		syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", uname, fname);
196
		goto next_crontab;
197
	}
198
	if (!S_ISREG(statbuf->st_mode)) {
199
		syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", uname, fname);
200
		goto next_crontab;
201
	}
202
	/* Looser permissions on system crontab. */
203
	tabmask = pw ? ALLPERMS : (ALLPERMS & ~(S_IWUSR|S_IRGRP|S_IROTH));
204
	tabperm = pw ? (S_IRUSR|S_IWUSR) : S_IRUSR;
205
	if ((statbuf->st_mode & tabmask) != tabperm) {
206
		syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", uname, fname);
207
		goto next_crontab;
208
	}
209
	if (statbuf->st_uid != 0 && (pw == NULL ||
210
	    statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
211
		syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", uname, fname);
212
		goto next_crontab;
213
	}
214
	if (pw != NULL && statbuf->st_gid != cron_gid) {
215
		syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", uname, fname);
216
		goto next_crontab;
217
	}
218
	if (pw != NULL && statbuf->st_nlink != 1) {
219
		syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", uname, fname);
220
		goto next_crontab;
221
	}
222
223
	u = find_user(old_db, fname);
224
	if (u != NULL) {
225
		/* if crontab has not changed since we last read it
226
		 * in, then we can just use our existing entry.
227
		 */
228
		if (timespeccmp(&u->mtime, &statbuf->st_mtim, ==)) {
229
			TAILQ_REMOVE(&old_db->users, u, entries);
230
			TAILQ_INSERT_TAIL(&new_db->users, u, entries);
231
			goto next_crontab;
232
		}
233
		syslog(LOG_INFO, "(%s) RELOAD (%s)", uname, fname);
234
	}
235
236
	new_u = load_user(crontab_fd, pw, fname);
237
	if (new_u != NULL) {
238
		/* Insert user into the new database and remove from old. */
239
		new_u->mtime = statbuf->st_mtim;
240
		TAILQ_INSERT_TAIL(&new_db->users, new_u, entries);
241
		if (u != NULL) {
242
			TAILQ_REMOVE(&old_db->users, u, entries);
243
			free_user(u);
244
		}
245
	} else if (u != NULL) {
246
		/* New user crontab failed to load, preserve the old one. */
247
		TAILQ_REMOVE(&old_db->users, u, entries);
248
		TAILQ_INSERT_TAIL(&new_db->users, u, entries);
249
	}
250
251
 next_crontab:
252
	if (crontab_fd >= 0) {
253
		close(crontab_fd);
254
	}
255
}