1 /* $OpenBSD: cron.c,v 1.82 2022/07/08 20:47:24 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/socket.h>
22 #include <sys/stat.h>
23 #include <sys/time.h>
24 #include <sys/un.h>
25 #include <sys/wait.h>
26
27 #include <bitstring.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <grp.h>
31 #include <poll.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <syslog.h>
37 #include <time.h>
38 #include <unistd.h>
39
40 #include "config.h"
41 #include "pathnames.h"
42 #include "macros.h"
43 #include "structs.h"
44 #include "funcs.h"
45 #include "globals.h"
46
47 enum timejump { negative, small, medium, large };
48
49 static void usage(void),
50 run_reboot_jobs(cron_db *),
51 find_jobs(time_t, cron_db *, int, int),
52 set_time(int),
53 cron_sleep(time_t, sigset_t *),
54 sigchld_handler(int),
55 sigchld_reaper(void),
56 parse_args(int c, char *v[]);
57
58 static int open_socket(void);
59
60 static volatile sig_atomic_t got_sigchld;
61 static time_t timeRunning, virtualTime, clockTime;
62 static long GMToff;
63 static cron_db *database;
64 static at_db *at_database;
65 static double batch_maxload = BATCH_MAXLOAD;
66 static int NoFork;
67 static time_t StartTime;
68 gid_t cron_gid;
69 int cronSock;
70
71 static void
usage(void)72 usage(void)
73 {
74
75 fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", __progname);
76 exit(EXIT_FAILURE);
77 }
78
79 int
main(int argc,char * argv[])80 main(int argc, char *argv[])
81 {
82 struct sigaction sact;
83 sigset_t blocked, omask;
84 struct group *grp;
85
86 setvbuf(stdout, NULL, _IOLBF, 0);
87 setvbuf(stderr, NULL, _IOLBF, 0);
88
89 parse_args(argc, argv);
90
91 bzero((char *)&sact, sizeof sact);
92 sigemptyset(&sact.sa_mask);
93 sact.sa_flags = SA_RESTART;
94 sact.sa_handler = sigchld_handler;
95 (void) sigaction(SIGCHLD, &sact, NULL);
96 sact.sa_handler = SIG_IGN;
97 (void) sigaction(SIGHUP, &sact, NULL);
98 (void) sigaction(SIGPIPE, &sact, NULL);
99
100 openlog(__progname, LOG_PID, LOG_CRON);
101
102 if (pledge("stdio rpath wpath cpath fattr getpw unix id dns proc exec",
103 NULL) == -1) {
104 warn("pledge");
105 syslog(LOG_ERR, "(CRON) PLEDGE (%m)");
106 exit(EXIT_FAILURE);
107 }
108
109 if ((grp = getgrnam(CRON_GROUP)) == NULL) {
110 warnx("can't find cron group %s", CRON_GROUP);
111 syslog(LOG_ERR, "(CRON) DEATH (can't find cron group)");
112 exit(EXIT_FAILURE);
113 }
114 cron_gid = grp->gr_gid;
115
116 cronSock = open_socket();
117
118 if (putenv("PATH="_PATH_DEFPATH) < 0) {
119 warn("putenv");
120 syslog(LOG_ERR, "(CRON) DEATH (%m)");
121 exit(EXIT_FAILURE);
122 }
123
124 if (NoFork == 0) {
125 if (daemon(0, 0) == -1) {
126 syslog(LOG_ERR, "(CRON) DEATH (%m)");
127 exit(EXIT_FAILURE);
128 }
129 syslog(LOG_INFO, "(CRON) STARTUP (%s)", CRON_VERSION);
130 }
131
132 load_database(&database);
133 scan_atjobs(&at_database, NULL);
134 set_time(TRUE);
135 run_reboot_jobs(database);
136 timeRunning = virtualTime = clockTime;
137
138 /*
139 * We block SIGHUP and SIGCHLD while running jobs and receive them
140 * only while sleeping in ppoll(). This ensures no signal is lost.
141 */
142 sigemptyset(&blocked);
143 sigaddset(&blocked, SIGCHLD);
144 sigaddset(&blocked, SIGHUP);
145 sigprocmask(SIG_BLOCK, &blocked, &omask);
146
147 /*
148 * Too many clocks, not enough time (Al. Einstein)
149 * These clocks are in minutes since the epoch, adjusted for timezone.
150 * virtualTime: is the time it *would* be if we woke up
151 * promptly and nobody ever changed the clock. It is
152 * monotonically increasing... unless a timejump happens.
153 * At the top of the loop, all jobs for 'virtualTime' have run.
154 * timeRunning: is the time we last awakened.
155 * clockTime: is the time when set_time was last called.
156 */
157 while (TRUE) {
158 int timeDiff;
159 enum timejump wakeupKind;
160
161 /* ... wait for the time (in minutes) to change ... */
162 do {
163 cron_sleep(timeRunning + 1, &omask);
164 set_time(FALSE);
165 } while (clockTime == timeRunning);
166 timeRunning = clockTime;
167
168 /*
169 * Calculate how the current time differs from our virtual
170 * clock. Classify the change into one of 4 cases.
171 */
172 timeDiff = timeRunning - virtualTime;
173
174 /* shortcut for the most common case */
175 if (timeDiff == 1) {
176 virtualTime = timeRunning;
177 find_jobs(virtualTime, database, TRUE, TRUE);
178 } else {
179 if (timeDiff > (3*MINUTE_COUNT) ||
180 timeDiff < -(3*MINUTE_COUNT))
181 wakeupKind = large;
182 else if (timeDiff > 5)
183 wakeupKind = medium;
184 else if (timeDiff > 0)
185 wakeupKind = small;
186 else
187 wakeupKind = negative;
188
189 switch (wakeupKind) {
190 case small:
191 /*
192 * case 1: timeDiff is a small positive number
193 * (wokeup late) run jobs for each virtual
194 * minute until caught up.
195 */
196 do {
197 if (job_runqueue())
198 sleep(10);
199 virtualTime++;
200 find_jobs(virtualTime, database,
201 TRUE, TRUE);
202 } while (virtualTime < timeRunning);
203 break;
204
205 case medium:
206 /*
207 * case 2: timeDiff is a medium-sized positive
208 * number, for example because we went to DST
209 * run wildcard jobs once, then run any
210 * fixed-time jobs that would otherwise be
211 * skipped if we use up our minute (possible,
212 * if there are a lot of jobs to run) go
213 * around the loop again so that wildcard jobs
214 * have a chance to run, and we do our
215 * housekeeping.
216 */
217 /* run wildcard jobs for current minute */
218 find_jobs(timeRunning, database, TRUE, FALSE);
219
220 /* run fixed-time jobs for each minute missed */
221 do {
222 if (job_runqueue())
223 sleep(10);
224 virtualTime++;
225 find_jobs(virtualTime, database,
226 FALSE, TRUE);
227 set_time(FALSE);
228 } while (virtualTime< timeRunning &&
229 clockTime == timeRunning);
230 break;
231
232 case negative:
233 /*
234 * case 3: timeDiff is a small or medium-sized
235 * negative num, eg. because of DST ending.
236 * Just run the wildcard jobs. The fixed-time
237 * jobs probably have already run, and should
238 * not be repeated. Virtual time does not
239 * change until we are caught up.
240 */
241 find_jobs(timeRunning, database, TRUE, FALSE);
242 break;
243 default:
244 /*
245 * other: time has changed a *lot*,
246 * jump virtual time, and run everything
247 */
248 virtualTime = timeRunning;
249 find_jobs(timeRunning, database, TRUE, TRUE);
250 }
251 }
252
253 /* Jobs to be run (if any) are loaded; clear the queue. */
254 job_runqueue();
255
256 /* Run any jobs in the at queue. */
257 atrun(at_database, batch_maxload,
258 timeRunning * SECONDS_PER_MINUTE - GMToff);
259
260 /* Reload jobs as needed. */
261 load_database(&database);
262 scan_atjobs(&at_database, NULL);
263 }
264 }
265
266 static void
run_reboot_jobs(cron_db * db)267 run_reboot_jobs(cron_db *db)
268 {
269 user *u;
270 entry *e;
271
272 TAILQ_FOREACH(u, &db->users, entries) {
273 SLIST_FOREACH(e, &u->crontab, entries) {
274 if (e->flags & WHEN_REBOOT)
275 job_add(e, u);
276 }
277 }
278 (void) job_runqueue();
279 }
280
281 static void
find_jobs(time_t vtime,cron_db * db,int doWild,int doNonWild)282 find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild)
283 {
284 time_t virtualSecond = vtime * SECONDS_PER_MINUTE;
285 struct tm *tm = gmtime(&virtualSecond);
286 int minute, hour, dom, month, dow;
287 user *u;
288 entry *e;
289
290 /* make 0-based values out of these so we can use them as indices
291 */
292 minute = tm->tm_min -FIRST_MINUTE;
293 hour = tm->tm_hour -FIRST_HOUR;
294 dom = tm->tm_mday -FIRST_DOM;
295 month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
296 dow = tm->tm_wday -FIRST_DOW;
297
298 /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
299 * first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
300 * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
301 * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
302 * like many bizarre things, it's the standard.
303 */
304 TAILQ_FOREACH(u, &db->users, entries) {
305 SLIST_FOREACH(e, &u->crontab, entries) {
306 if (bit_test(e->minute, minute) &&
307 bit_test(e->hour, hour) &&
308 bit_test(e->month, month) &&
309 ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
310 ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
311 : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
312 )
313 ) {
314 if ((doNonWild &&
315 !(e->flags & (MIN_STAR|HR_STAR))) ||
316 (doWild && (e->flags & (MIN_STAR|HR_STAR))))
317 job_add(e, u);
318 }
319 }
320 }
321 }
322
323 /*
324 * Set StartTime and clockTime to the current time.
325 * These are used for computing what time it really is right now.
326 * Note that clockTime is a unix wallclock time converted to minutes.
327 */
328 static void
set_time(int initialize)329 set_time(int initialize)
330 {
331 struct tm tm;
332 static int isdst;
333
334 StartTime = time(NULL);
335
336 /* We adjust the time to GMT so we can catch DST changes. */
337 tm = *localtime(&StartTime);
338 if (initialize || tm.tm_isdst != isdst) {
339 isdst = tm.tm_isdst;
340 GMToff = get_gmtoff(&StartTime, &tm);
341 }
342 clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
343 }
344
345 /*
346 * Try to just hit the next minute.
347 */
348 static void
cron_sleep(time_t target,sigset_t * mask)349 cron_sleep(time_t target, sigset_t *mask)
350 {
351 int fd, nfds;
352 unsigned char poke;
353 struct timespec t1, t2, timeout;
354 struct sockaddr_un s_un;
355 socklen_t sunlen;
356 static struct pollfd pfd[1];
357
358 clock_gettime(CLOCK_REALTIME, &t1);
359 t1.tv_sec += GMToff;
360 timeout.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1;
361 if (timeout.tv_sec < 0)
362 timeout.tv_sec = 0;
363 timeout.tv_nsec = 0;
364
365 pfd[0].fd = cronSock;
366 pfd[0].events = POLLIN;
367
368 while (timespecisset(&timeout) && timeout.tv_sec < 65) {
369 poke = RELOAD_CRON | RELOAD_AT;
370
371 /* Sleep until we time out, get a poke, or get a signal. */
372 nfds = ppoll(pfd, 1, &timeout, mask);
373 switch (nfds) {
374 case -1:
375 if (errno != EINTR && errno != EAGAIN) {
376 syslog(LOG_ERR, "(CRON) DEATH (ppoll failure: %m)");
377 exit(EXIT_FAILURE);
378 }
379 if (errno == EINTR) {
380 if (got_sigchld) {
381 got_sigchld = 0;
382 sigchld_reaper();
383 }
384 }
385 break;
386 case 0:
387 /* done sleeping */
388 return;
389 default:
390 sunlen = sizeof(s_un);
391 fd = accept4(cronSock, (struct sockaddr *)&s_un,
392 &sunlen, SOCK_NONBLOCK);
393 if (fd >= 0) {
394 (void) read(fd, &poke, 1);
395 close(fd);
396 if (poke & RELOAD_CRON) {
397 timespecclear(&database->mtime);
398 load_database(&database);
399 }
400 if (poke & RELOAD_AT) {
401 /*
402 * We run any pending at jobs right
403 * away so that "at now" really runs
404 * jobs immediately.
405 */
406 clock_gettime(CLOCK_REALTIME, &t2);
407 timespecclear(&at_database->mtime);
408 if (scan_atjobs(&at_database, &t2))
409 atrun(at_database,
410 batch_maxload, t2.tv_sec);
411 }
412 }
413 }
414
415 /* Adjust tv and continue where we left off. */
416 clock_gettime(CLOCK_REALTIME, &t2);
417 t2.tv_sec += GMToff;
418 timespecsub(&t2, &t1, &t1);
419 timespecsub(&timeout, &t1, &timeout);
420 memcpy(&t1, &t2, sizeof(t1));
421 if (timeout.tv_sec < 0)
422 timespecclear(&timeout);
423 }
424 }
425
426 /* int open_socket(void)
427 * opens a UNIX domain socket that crontab uses to poke cron.
428 * If the socket is already in use, return an error.
429 */
430 static int
open_socket(void)431 open_socket(void)
432 {
433 int sock, rc;
434 mode_t omask;
435 struct sockaddr_un s_un;
436
437 sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
438 if (sock == -1) {
439 warn("socket");
440 syslog(LOG_ERR, "(CRON) DEATH (can't create socket)");
441 exit(EXIT_FAILURE);
442 }
443 bzero(&s_un, sizeof(s_un));
444 if (strlcpy(s_un.sun_path, _PATH_CRON_SOCK, sizeof(s_un.sun_path))
445 >= sizeof(s_un.sun_path)) {
446 warnc(ENAMETOOLONG, _PATH_CRON_SOCK);
447 syslog(LOG_ERR, "(CRON) DEATH (socket path too long)");
448 exit(EXIT_FAILURE);
449 }
450 s_un.sun_family = AF_UNIX;
451
452 if (connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) {
453 warnx("already running");
454 syslog(LOG_ERR, "(CRON) DEATH (already running)");
455 exit(EXIT_FAILURE);
456 }
457 if (errno != ENOENT)
458 unlink(s_un.sun_path);
459
460 omask = umask(007);
461 rc = bind(sock, (struct sockaddr *)&s_un, sizeof(s_un));
462 umask(omask);
463 if (rc != 0) {
464 warn("bind");
465 syslog(LOG_ERR, "(CRON) DEATH (can't bind socket)");
466 exit(EXIT_FAILURE);
467 }
468 if (listen(sock, SOMAXCONN)) {
469 warn("listen");
470 syslog(LOG_ERR, "(CRON) DEATH (can't listen on socket)");
471 exit(EXIT_FAILURE);
472 }
473
474 /* pledge won't let us change files to a foreign group. */
475 if (setegid(cron_gid) == 0) {
476 chown(s_un.sun_path, -1, cron_gid);
477 (void)setegid(getgid());
478 }
479 chmod(s_un.sun_path, 0660);
480
481 return(sock);
482 }
483
484 static void
sigchld_handler(int x)485 sigchld_handler(int x)
486 {
487 got_sigchld = 1;
488 }
489
490 static void
sigchld_reaper(void)491 sigchld_reaper(void)
492 {
493 int waiter;
494 pid_t pid;
495
496 do {
497 pid = waitpid(-1, &waiter, WNOHANG);
498 switch (pid) {
499 case -1:
500 if (errno == EINTR)
501 continue;
502 break;
503 case 0:
504 break;
505 default:
506 job_exit(pid);
507 break;
508 }
509 } while (pid > 0);
510 }
511
512 static void
parse_args(int argc,char * argv[])513 parse_args(int argc, char *argv[])
514 {
515 int argch;
516 char *ep;
517
518 while (-1 != (argch = getopt(argc, argv, "l:n"))) {
519 switch (argch) {
520 case 'l':
521 errno = 0;
522 batch_maxload = strtod(optarg, &ep);
523 if (*ep != '\0' || ep == optarg || errno == ERANGE ||
524 batch_maxload < 0) {
525 warnx("illegal load average: %s", optarg);
526 usage();
527 }
528 break;
529 case 'n':
530 NoFork = 1;
531 break;
532 default:
533 usage();
534 }
535 }
536 }
537