1 /* $OpenBSD: cron.c,v 1.79 2020/04/16 17:51:56 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 72 usage(void) 73 { 74 75 fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", __progname); 76 exit(EXIT_FAILURE); 77 } 78 79 int 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 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 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 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 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 timeout.tv_nsec = 0; 362 363 pfd[0].fd = cronSock; 364 pfd[0].events = POLLIN; 365 366 while (timespecisset(&timeout) && timeout.tv_sec < 65) { 367 poke = RELOAD_CRON | RELOAD_AT; 368 369 /* Sleep until we time out, get a poke, or get a signal. */ 370 nfds = ppoll(pfd, 1, &timeout, mask); 371 if (nfds == 0) 372 break; /* timer expired */ 373 if (nfds == -1 && errno != EINTR) 374 break; /* an error occurred */ 375 if (nfds > 0) { 376 sunlen = sizeof(s_un); 377 fd = accept4(cronSock, (struct sockaddr *)&s_un, 378 &sunlen, SOCK_NONBLOCK); 379 if (fd >= 0) { 380 (void) read(fd, &poke, 1); 381 close(fd); 382 if (poke & RELOAD_CRON) { 383 timespecclear(&database->mtime); 384 load_database(&database); 385 } 386 if (poke & RELOAD_AT) { 387 /* 388 * We run any pending at jobs right 389 * away so that "at now" really runs 390 * jobs immediately. 391 */ 392 clock_gettime(CLOCK_REALTIME, &t2); 393 timespecclear(&at_database->mtime); 394 if (scan_atjobs(&at_database, &t2)) 395 atrun(at_database, 396 batch_maxload, t2.tv_sec); 397 } 398 } 399 } else { 400 /* Interrupted by a signal. */ 401 if (got_sigchld) { 402 got_sigchld = 0; 403 sigchld_reaper(); 404 } 405 } 406 407 /* Adjust tv and continue where we left off. */ 408 clock_gettime(CLOCK_REALTIME, &t2); 409 t2.tv_sec += GMToff; 410 timespecsub(&t2, &t1, &t1); 411 timespecsub(&timeout, &t1, &timeout); 412 memcpy(&t1, &t2, sizeof(t1)); 413 if (timeout.tv_sec < 0) 414 timeout.tv_sec = 0; 415 if (timeout.tv_nsec < 0) 416 timeout.tv_nsec = 0; 417 } 418 } 419 420 /* int open_socket(void) 421 * opens a UNIX domain socket that crontab uses to poke cron. 422 * If the socket is already in use, return an error. 423 */ 424 static int 425 open_socket(void) 426 { 427 int sock, rc; 428 mode_t omask; 429 struct sockaddr_un s_un; 430 431 sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); 432 if (sock == -1) { 433 warn("socket"); 434 syslog(LOG_ERR, "(CRON) DEATH (can't create socket)"); 435 exit(EXIT_FAILURE); 436 } 437 bzero(&s_un, sizeof(s_un)); 438 if (strlcpy(s_un.sun_path, _PATH_CRON_SOCK, sizeof(s_un.sun_path)) 439 >= sizeof(s_un.sun_path)) { 440 warnc(ENAMETOOLONG, _PATH_CRON_SOCK); 441 syslog(LOG_ERR, "(CRON) DEATH (socket path too long)"); 442 exit(EXIT_FAILURE); 443 } 444 s_un.sun_family = AF_UNIX; 445 446 if (connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) { 447 warnx("already running"); 448 syslog(LOG_ERR, "(CRON) DEATH (already running)"); 449 exit(EXIT_FAILURE); 450 } 451 if (errno != ENOENT) 452 unlink(s_un.sun_path); 453 454 omask = umask(007); 455 rc = bind(sock, (struct sockaddr *)&s_un, sizeof(s_un)); 456 umask(omask); 457 if (rc != 0) { 458 warn("bind"); 459 syslog(LOG_ERR, "(CRON) DEATH (can't bind socket)"); 460 exit(EXIT_FAILURE); 461 } 462 if (listen(sock, SOMAXCONN)) { 463 warn("listen"); 464 syslog(LOG_ERR, "(CRON) DEATH (can't listen on socket)"); 465 exit(EXIT_FAILURE); 466 } 467 468 /* pledge won't let us change files to a foreign group. */ 469 if (setegid(cron_gid) == 0) { 470 chown(s_un.sun_path, -1, cron_gid); 471 (void)setegid(getgid()); 472 } 473 chmod(s_un.sun_path, 0660); 474 475 return(sock); 476 } 477 478 static void 479 sigchld_handler(int x) 480 { 481 got_sigchld = 1; 482 } 483 484 static void 485 sigchld_reaper(void) 486 { 487 int waiter; 488 pid_t pid; 489 490 do { 491 pid = waitpid(-1, &waiter, WNOHANG); 492 switch (pid) { 493 case -1: 494 if (errno == EINTR) 495 continue; 496 break; 497 case 0: 498 break; 499 default: 500 job_exit(pid); 501 break; 502 } 503 } while (pid > 0); 504 } 505 506 static void 507 parse_args(int argc, char *argv[]) 508 { 509 int argch; 510 char *ep; 511 512 while (-1 != (argch = getopt(argc, argv, "l:n"))) { 513 switch (argch) { 514 case 'l': 515 errno = 0; 516 batch_maxload = strtod(optarg, &ep); 517 if (*ep != '\0' || ep == optarg || errno == ERANGE || 518 batch_maxload < 0) { 519 warnx("illegal load average: %s", optarg); 520 usage(); 521 } 522 break; 523 case 'n': 524 NoFork = 1; 525 break; 526 default: 527 usage(); 528 } 529 } 530 } 531