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