xref: /openbsd/usr.sbin/cron/cron.c (revision 9c847fa8)
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