1 /*        $NetBSD: database.c,v 1.9 2017/06/09 17:36:30 christos Exp $          */
2 
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4  * All rights reserved
5  */
6 
7 /*
8  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 #include <sys/cdefs.h>
24 #if !defined(lint) && !defined(LINT)
25 #if 0
26 static char rcsid[] = "Id: database.c,v 1.7 2004/01/23 18:56:42 vixie Exp";
27 #else
28 __RCSID("$NetBSD: database.c,v 1.9 2017/06/09 17:36:30 christos Exp $");
29 #endif
30 #endif
31 
32 /* vix 26jan87 [RCS has the log]
33  */
34 
35 #include "cron.h"
36 
37 #define TMAX(a,b) ((a)>(b)?(a):(b))
38 
39 struct spooldir {
40           const char *path;
41           const char *uname;
42           const char *fname;
43           struct stat st;
44 };
45 
46 static struct spooldir spools[] = {
47           { .path = SPOOL_DIR, },
48           { .path = CROND_DIR, .uname = "root", .fname = "*system*", },
49           { .path = NULL, }
50 };
51 
52 static    void                process_crontab(const char *, const char *,
53                                                   const char *, struct stat *,
54                                                   cron_db *, cron_db *);
55 
56 static void
process_dir(struct spooldir * sp,cron_db * new_db,cron_db * old_db)57 process_dir(struct spooldir *sp, cron_db *new_db, cron_db *old_db)
58 {
59           DIR *dir;
60           DIR_T *dp;
61           const char *dname = sp->path;
62           struct stat *st = &sp->st;
63 
64           if (st->st_mtime == 0)
65                     return;
66 
67           /* we used to keep this dir open all the time, for the sake of
68            * efficiency.  however, we need to close it in every fork, and
69            * we fork a lot more often than the mtime of the dir changes.
70            */
71           if (!(dir = opendir(dname))) {
72                     log_it("CRON", getpid(), "OPENDIR FAILED", dname);
73                     (void) exit(ERROR_EXIT);
74           }
75 
76           while (NULL != (dp = readdir(dir))) {
77                     char      fname[MAXNAMLEN+1], tabname[MAXNAMLEN+1];
78                     size_t i, len;
79                     /*
80                      * Homage to...
81                      */
82                     static const char *junk[] = {
83                               "rpmsave", "rpmorig", "rpmnew",
84                     };
85 
86                     /* avoid file names beginning with ".".  this is good
87                      * because we would otherwise waste two guaranteed calls
88                      * to getpwnam() for . and .., and there shouldn't be
89                      * hidden files in here anyway (in the non system case).
90                      */
91                     if (dp->d_name[0] == '.')
92                               continue;
93 
94                     /* ignore files starting with # ... */
95                     if (dp->d_name[0] == '#')
96                               continue;
97 
98                     len = strlen(dp->d_name);
99 
100                     /* ... or too big or to small ... */
101                     if (len == 0 || len >= sizeof(fname)) {
102                               log_it(dp->d_name, getpid(), "ORPHAN",
103                                   "name too short or long");
104                               continue;
105                     }
106 
107                     /* ... or ending with ~ ... */
108                     if (dp->d_name[len - 1] == '~')
109                               continue;
110 
111                     (void)strlcpy(fname, dp->d_name, sizeof(fname));
112 
113                     /* ... or look for blacklisted extensions */
114                     for (i = 0; i < __arraycount(junk); i++) {
115                               char *p;
116                               if ((p = strrchr(fname, '.')) != NULL &&
117                                   strcmp(p + 1, junk[i]) == 0)
118                                         break;
119                     }
120                     if (i != __arraycount(junk))
121                               continue;
122 
123                     if (!glue_strings(tabname, sizeof tabname, dname, fname, '/')) {
124                               log_it(fname, getpid(), "ORPHAN",
125                                   "could not glue strings");
126                               continue;
127                     }
128 
129                     process_crontab(sp->uname ? sp->uname : fname,
130                                         sp->fname ? sp->fname : fname,
131                                         tabname, st, new_db, old_db);
132           }
133           (void)closedir(dir);
134 }
135 
136 void
load_database(cron_db * old_db)137 load_database(cron_db *old_db) {
138           struct stat syscron_stat;
139           cron_db new_db;
140           user *u, *nu;
141           time_t maxtime;
142 
143           Debug(DLOAD, ("[%ld] load_database()\n", (long)getpid()));
144 
145           /* track system crontab file
146            */
147           if (stat(SYSCRONTAB, &syscron_stat) < OK)
148                     syscron_stat.st_mtime = 0;
149 
150           maxtime = syscron_stat.st_mtime;
151           for (struct spooldir *p = spools; p->path; p++) {
152                     if (stat(p->path, &p->st) < OK) {
153                               if (errno == ENOENT) {
154                                         p->st.st_mtime = 0;
155                                         continue;
156                               }
157                               log_it("CRON", getpid(), "STAT FAILED", p->path);
158                               (void) exit(ERROR_EXIT);
159                     }
160                     if (p->st.st_mtime > maxtime)
161                               maxtime = p->st.st_mtime;
162           }
163 
164           /* if spooldir's mtime has not changed, we don't need to fiddle with
165            * the database.
166            *
167            * Note that old_db->mtime is initialized to 0 in main(), and
168            * so is guaranteed to be different than the stat() mtime the first
169            * time this function is called.
170            */
171           if (old_db->mtime == maxtime) {
172                     Debug(DLOAD, ("[%ld] spool dir mtime unch, no load needed.\n",
173                                     (long)getpid()));
174                     return;
175           }
176 
177           /* something's different.  make a new database, moving unchanged
178            * elements from the old database, reloading elements that have
179            * actually changed.  Whatever is left in the old database when
180            * we're done is chaff -- crontabs that disappeared.
181            */
182           new_db.mtime = maxtime;
183           new_db.head = new_db.tail = NULL;
184 
185           if (syscron_stat.st_mtime)
186                     process_crontab("root", "*system*", SYSCRONTAB,
187                         &syscron_stat, &new_db, old_db);
188 
189           for (struct spooldir *p = spools; p->path; p++)
190                     process_dir(p, &new_db, old_db);
191 
192           /* if we don't do this, then when our children eventually call
193            * getpwnam() in do_command.c's child_process to verify MAILTO=,
194            * they will screw us up (and v-v).
195            */
196           endpwent();
197 
198           /* whatever's left in the old database is now junk.
199            */
200           Debug(DLOAD, ("unlinking old database:\n"));
201           for (u = old_db->head;  u != NULL;  u = nu) {
202                     Debug(DLOAD, ("\t%s\n", u->name));
203                     nu = u->next;
204                     unlink_user(old_db, u);
205                     free_user(u);
206           }
207 
208           /* overwrite the database control block with the new one.
209            */
210           *old_db = new_db;
211           Debug(DLOAD, ("load_database is done\n"));
212 }
213 
214 void
link_user(cron_db * db,user * u)215 link_user(cron_db *db, user *u) {
216           if (db->head == NULL)
217                     db->head = u;
218           if (db->tail)
219                     db->tail->next = u;
220           u->prev = db->tail;
221           u->next = NULL;
222           db->tail = u;
223 }
224 
225 void
unlink_user(cron_db * db,user * u)226 unlink_user(cron_db *db, user *u) {
227           if (u->prev == NULL)
228                     db->head = u->next;
229           else
230                     u->prev->next = u->next;
231 
232           if (u->next == NULL)
233                     db->tail = u->prev;
234           else
235                     u->next->prev = u->prev;
236 }
237 
238 user *
find_user(cron_db * db,const char * name)239 find_user(cron_db *db, const char *name) {
240           user *u;
241 
242           for (u = db->head;  u != NULL;  u = u->next)
243                     if (strcmp(u->name, name) == 0)
244                               break;
245           return (u);
246 }
247 
248 static void
process_crontab(const char * uname,const char * fname,const char * tabname,struct stat * statbuf,cron_db * new_db,cron_db * old_db)249 process_crontab(const char *uname, const char *fname, const char *tabname,
250                     struct stat *statbuf, cron_db *new_db, cron_db *old_db)
251 {
252           struct passwd *pw = NULL;
253           int crontab_fd = OK - 1;
254           mode_t eqmode = 0400, badmode = 0;
255           user *u;
256 
257           if (strcmp(fname, "*system*") == 0) {
258                     /*
259                      * SYSCRONTAB:
260                      * Allow it to become readable by group and others, but
261                      * not writable.
262                      */
263                     eqmode = 0;
264                     badmode = 022;
265           } else if ((pw = getpwnam(uname)) == NULL) {
266                     /* file doesn't have a user in passwd file.
267                      */
268                     log_it(fname, getpid(), "ORPHAN", "no passwd entry");
269                     goto next_crontab;
270           }
271 
272           if ((crontab_fd = open(tabname, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < OK) {
273                     /* crontab not accessible?
274                      */
275                     log_it(fname, getpid(), "CAN'T OPEN", tabname);
276                     goto next_crontab;
277           }
278 
279           if (fstat(crontab_fd, statbuf) < OK) {
280                     log_it(fname, getpid(), "FSTAT FAILED", tabname);
281                     goto next_crontab;
282           }
283           if (!S_ISREG(statbuf->st_mode)) {
284                     log_it(fname, getpid(), "NOT REGULAR", tabname);
285                     goto next_crontab;
286           }
287           if ((eqmode && (statbuf->st_mode & 07577) != eqmode) ||
288               (badmode && (statbuf->st_mode & badmode) != 0)) {
289                     log_it(fname, getpid(), "BAD FILE MODE", tabname);
290                     goto next_crontab;
291           }
292           if (statbuf->st_uid != ROOT_UID && (pw == NULL ||
293               statbuf->st_uid != pw->pw_uid || strcmp(uname, pw->pw_name) != 0)) {
294                     log_it(fname, getpid(), "WRONG FILE OWNER", tabname);
295                     goto next_crontab;
296           }
297           if (statbuf->st_nlink != 1) {
298                     log_it(fname, getpid(), "BAD LINK COUNT", tabname);
299                     goto next_crontab;
300           }
301 
302           Debug(DLOAD, ("\t%s:", fname));
303           u = find_user(old_db, fname);
304           if (u != NULL) {
305                     /* if crontab has not changed since we last read it
306                      * in, then we can just use our existing entry.
307                      */
308                     if (u->mtime == statbuf->st_mtime) {
309                               Debug(DLOAD, (" [no change, using old data]"));
310                               unlink_user(old_db, u);
311                               link_user(new_db, u);
312                               goto next_crontab;
313                     }
314 
315                     /* before we fall through to the code that will reload
316                      * the user, let's deallocate and unlink the user in
317                      * the old database.  This is more a point of memory
318                      * efficiency than anything else, since all leftover
319                      * users will be deleted from the old database when
320                      * we finish with the crontab...
321                      */
322                     Debug(DLOAD, (" [delete old data]"));
323                     unlink_user(old_db, u);
324                     free_user(u);
325                     log_it(fname, getpid(), "RELOAD", tabname);
326           }
327           u = load_user(crontab_fd, pw, fname);
328           if (u != NULL) {
329                     u->mtime = statbuf->st_mtime;
330                     link_user(new_db, u);
331           }
332 
333  next_crontab:
334           if (crontab_fd >= OK) {
335                     Debug(DLOAD, (" [done]\n"));
336                     (void)close(crontab_fd);
337           }
338 }
339