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