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 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 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 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 485 sigchld_handler(int x) 486 { 487 got_sigchld = 1; 488 } 489 490 static 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 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