1/* $OpenBSD: entry.c,v 1.59 2023/07/19 21:26:02 millert Exp $ */ 2 3/* 4 * Copyright 1988,1990,1993,1994 by Paul Vixie 5 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 6 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 18 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21#include <sys/types.h> 22 23#include <bitstring.h> /* for structs.h */ 24#include <ctype.h> 25#include <pwd.h> 26#include <stdio.h> 27#include <stdlib.h> 28#include <string.h> 29#include <syslog.h> 30#include <time.h> /* for structs.h */ 31#include <unistd.h> 32 33#include "pathnames.h" 34#include "macros.h" 35#include "structs.h" 36#include "funcs.h" 37 38typedef enum ecode { 39 e_none, e_minute, e_hour, e_dom, e_month, e_dow, 40 e_cmd, e_timespec, e_username, e_option, e_memory, e_flags 41} ecode_e; 42 43static const char *ecodes[] = { 44 "no error", 45 "bad minute", 46 "bad hour", 47 "bad day-of-month", 48 "bad month", 49 "bad day-of-week", 50 "bad command", 51 "bad time specifier", 52 "bad username", 53 "bad option", 54 "out of memory", 55 "bad flags" 56}; 57 58static const char *MonthNames[] = { 59 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 60 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 61 NULL 62}; 63 64static const char *DowNames[] = { 65 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", 66 NULL 67}; 68 69static int get_list(bitstr_t *, int, int, const char *[], int, FILE *), 70 get_range(bitstr_t *, int, int, const char *[], int, FILE *), 71 get_number(int *, int, const char *[], int, FILE *, const char *), 72 set_element(bitstr_t *, int, int, int), 73 set_range(bitstr_t *, int, int, int, int, int); 74 75void 76free_entry(entry *e) 77{ 78 free(e->cmd); 79 free(e->pwd); 80 if (e->envp) 81 env_free(e->envp); 82 free(e); 83} 84 85/* return NULL if eof or syntax error occurs; 86 * otherwise return a pointer to a new entry. 87 */ 88entry * 89load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, 90 char **envp) 91{ 92 /* this function reads one crontab entry -- the next -- from a file. 93 * it skips any leading blank lines, ignores comments, and returns 94 * NULL if for any reason the entry can't be read and parsed. 95 * 96 * the entry is also parsed here. 97 * 98 * syntax: 99 * user crontab: 100 * minutes hours doms months dows cmd\n 101 * system crontab (/etc/crontab): 102 * minutes hours doms months dows USERNAME cmd\n 103 */ 104 105 ecode_e ecode = e_none; 106 entry *e; 107 int ch; 108 char cmd[MAX_COMMAND]; 109 char envstr[MAX_ENVSTR]; 110 char **tenvp; 111 112 skip_comments(file); 113 114 ch = get_char(file); 115 if (ch == EOF) 116 return (NULL); 117 118 /* ch is now the first useful character of a useful line. 119 * it may be an @special or it may be the first character 120 * of a list of minutes. 121 */ 122 123 e = calloc(sizeof(entry), 1); 124 if (e == NULL) { 125 ecode = e_memory; 126 goto eof; 127 } 128 129 if (ch == '@') { 130 /* all of these should be flagged and load-limited; i.e., 131 * instead of @hourly meaning "0 * * * *" it should mean 132 * "close to the front of every hour but not 'til the 133 * system load is low". Problems are: how do you know 134 * what "low" means? (save me from /etc/cron.conf!) and: 135 * how to guarantee low variance (how low is low?), which 136 * means how to we run roughly every hour -- seems like 137 * we need to keep a history or let the first hour set 138 * the schedule, which means we aren't load-limited 139 * anymore. too much for my overloaded brain. (vix, jan90) 140 * HINT 141 */ 142 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 143 if (!strcmp("reboot", cmd)) { 144 e->flags |= WHEN_REBOOT; 145 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 146 set_element(e->minute, FIRST_MINUTE, LAST_MINUTE, 147 FIRST_MINUTE); 148 set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR); 149 set_element(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM); 150 set_element(e->month, FIRST_MONTH, LAST_MONTH, 151 FIRST_MONTH); 152 set_range(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW, 153 LAST_DOW, 1); 154 e->flags |= DOW_STAR; 155 } else if (!strcmp("monthly", cmd)) { 156 set_element(e->minute, FIRST_MINUTE, LAST_MINUTE, 157 FIRST_MINUTE); 158 set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR); 159 set_element(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM); 160 set_range(e->month, FIRST_MONTH, LAST_MONTH, 161 FIRST_MONTH, LAST_MONTH, 1); 162 set_range(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW, 163 LAST_DOW, 1); 164 e->flags |= DOW_STAR; 165 } else if (!strcmp("weekly", cmd)) { 166 set_element(e->minute, FIRST_MINUTE, LAST_MINUTE, 167 FIRST_MINUTE); 168 set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR); 169 set_range(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM, 170 LAST_DOM, 1); 171 set_range(e->month, FIRST_MONTH, LAST_MONTH, 172 FIRST_MONTH, LAST_MONTH, 1); 173 set_element(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW); 174 e->flags |= DOW_STAR; 175 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 176 set_element(e->minute, FIRST_MINUTE, LAST_MINUTE, 177 FIRST_MINUTE); 178 set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR); 179 set_range(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM, 180 LAST_DOM, 1); 181 set_range(e->month, FIRST_MONTH, LAST_MONTH, 182 FIRST_MONTH, LAST_MONTH, 1); 183 set_range(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW, 184 LAST_DOW, 1); 185 } else if (!strcmp("hourly", cmd)) { 186 set_element(e->minute, FIRST_MINUTE, LAST_MINUTE, 187 FIRST_MINUTE); 188 set_range(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR, 189 LAST_HOUR, 1); 190 set_range(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM, 191 LAST_DOM, 1); 192 set_range(e->month, FIRST_MONTH, LAST_MONTH, 193 FIRST_MONTH, LAST_MONTH, 1); 194 set_range(e->dow, FIRST_DOW, LAST_DOW, 195 FIRST_DOW, LAST_DOW, 1); 196 e->flags |= HR_STAR; 197 } else { 198 ecode = e_timespec; 199 goto eof; 200 } 201 /* Advance past whitespace between shortcut and 202 * username/command. 203 */ 204 Skip_Blanks(ch, file); 205 if (ch == EOF || ch == '\n') { 206 ecode = e_cmd; 207 goto eof; 208 } 209 } else { 210 if (ch == '*') 211 e->flags |= MIN_STAR; 212 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 213 NULL, ch, file); 214 if (ch == EOF) { 215 ecode = e_minute; 216 goto eof; 217 } 218 219 /* hours 220 */ 221 222 if (ch == '*') 223 e->flags |= HR_STAR; 224 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 225 NULL, ch, file); 226 if (ch == EOF) { 227 ecode = e_hour; 228 goto eof; 229 } 230 231 /* DOM (days of month) 232 */ 233 234 if (ch == '*') 235 e->flags |= DOM_STAR; 236 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 237 NULL, ch, file); 238 if (ch == EOF) { 239 ecode = e_dom; 240 goto eof; 241 } 242 243 /* month 244 */ 245 246 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 247 MonthNames, ch, file); 248 if (ch == EOF) { 249 ecode = e_month; 250 goto eof; 251 } 252 253 /* DOW (days of week) 254 */ 255 256 if (ch == '*') 257 e->flags |= DOW_STAR; 258 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 259 DowNames, ch, file); 260 if (ch == EOF) { 261 ecode = e_dow; 262 goto eof; 263 } 264 } 265 266 /* make sundays equivalent */ 267 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 268 bit_set(e->dow, 0); 269 bit_set(e->dow, 7); 270 } 271 272 /* check for permature EOL and catch a common typo */ 273 if (ch == '\n' || ch == '*') { 274 ecode = e_cmd; 275 goto eof; 276 } 277 278 if (!pw) { 279 char *username = cmd; /* temp buffer */ 280 281 unget_char(ch, file); 282 ch = get_string(username, MAX_COMMAND, file, " \t\n"); 283 284 if (ch == EOF || ch == '\n' || ch == '*') { 285 ecode = e_cmd; 286 goto eof; 287 } 288 Skip_Blanks(ch, file) 289 290 pw = getpwnam(username); 291 if (pw == NULL) { 292 ecode = e_username; 293 goto eof; 294 } 295 } 296 297 if ((e->pwd = pw_dup(pw)) == NULL) { 298 ecode = e_memory; 299 goto eof; 300 } 301 explicit_bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd)); 302 303 /* copy and fix up environment. some variables are just defaults and 304 * others are overrides. 305 */ 306 if ((e->envp = env_copy(envp)) == NULL) { 307 ecode = e_memory; 308 goto eof; 309 } 310 if (!env_get("SHELL", e->envp)) { 311 if (snprintf(envstr, sizeof envstr, "SHELL=%s", _PATH_BSHELL) >= 312 sizeof(envstr)) 313 syslog(LOG_ERR, "(CRON) ERROR (can't set SHELL)"); 314 else { 315 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 316 ecode = e_memory; 317 goto eof; 318 } 319 e->envp = tenvp; 320 } 321 } 322 if (!env_get("HOME", e->envp)) { 323 if (snprintf(envstr, sizeof envstr, "HOME=%s", pw->pw_dir) >= 324 sizeof(envstr)) 325 syslog(LOG_ERR, "(CRON) ERROR (can't set HOME)"); 326 else { 327 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 328 ecode = e_memory; 329 goto eof; 330 } 331 e->envp = tenvp; 332 } 333 } 334 if (snprintf(envstr, sizeof envstr, "LOGNAME=%s", pw->pw_name) >= 335 sizeof(envstr)) 336 syslog(LOG_ERR, "(CRON) ERROR (can't set LOGNAME)"); 337 else { 338 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 339 ecode = e_memory; 340 goto eof; 341 } 342 e->envp = tenvp; 343 } 344 if (snprintf(envstr, sizeof envstr, "USER=%s", pw->pw_name) >= 345 sizeof(envstr)) 346 syslog(LOG_ERR, "(CRON) ERROR (can't set USER)"); 347 else { 348 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 349 ecode = e_memory; 350 goto eof; 351 } 352 e->envp = tenvp; 353 } 354 355 /* An optional series of '-'-prefixed flags in getopt style can 356 * occur before the command. 357 */ 358 while (ch == '-') { 359 int flags = 0, loop = 1; 360 361 while (loop) { 362 switch (ch = get_char(file)) { 363 case 'n': 364 flags |= MAIL_WHEN_ERR; 365 break; 366 case 'q': 367 flags |= DONT_LOG; 368 break; 369 case 's': 370 flags |= SINGLE_JOB; 371 break; 372 case ' ': 373 case '\t': 374 Skip_Blanks(ch, file) 375 loop = 0; 376 break; 377 case EOF: 378 case '\n': 379 ecode = e_cmd; 380 goto eof; 381 default: 382 ecode = e_flags; 383 goto eof; 384 } 385 } 386 387 if (flags == 0) { 388 ecode = e_flags; 389 goto eof; 390 } 391 e->flags |= flags; 392 } 393 unget_char(ch, file); 394 395 /* Everything up to the next \n or EOF is part of the command... 396 * too bad we don't know in advance how long it will be, since we 397 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 398 */ 399 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 400 401 /* a file without a \n before the EOF is rude, so we'll complain... 402 */ 403 if (ch == EOF) { 404 ecode = e_cmd; 405 goto eof; 406 } 407 408 /* got the command in the 'cmd' string; save it in *e. 409 */ 410 if ((e->cmd = strdup(cmd)) == NULL) { 411 ecode = e_memory; 412 goto eof; 413 } 414 415 /* success, fini, return pointer to the entry we just created... 416 */ 417 return (e); 418 419 eof: 420 if (e) 421 free_entry(e); 422 while (ch != '\n' && !feof(file)) 423 ch = get_char(file); 424 if (ecode != e_none && error_func) 425 (*error_func)(ecodes[(int)ecode]); 426 return (NULL); 427} 428 429static int 430get_list(bitstr_t *bits, int low, int high, const char *names[], 431 int ch, FILE *file) 432{ 433 int done; 434 435 /* we know that we point to a non-blank character here; 436 * must do a Skip_Blanks before we exit, so that the 437 * next call (or the code that picks up the cmd) can 438 * assume the same thing. 439 */ 440 441 /* list = range {"," range} 442 */ 443 444 /* clear the bit string, since the default is 'off'. 445 */ 446 bit_nclear(bits, 0, high - low); 447 448 /* process all ranges 449 */ 450 done = FALSE; 451 while (!done) { 452 if ((ch = get_range(bits, low, high, names, ch, file)) == EOF) 453 return (EOF); 454 if (ch == ',') 455 ch = get_char(file); 456 else 457 done = TRUE; 458 } 459 460 /* exiting. skip to some blanks, then skip over the blanks. 461 */ 462 Skip_Nonblanks(ch, file) 463 Skip_Blanks(ch, file) 464 465 return (ch); 466} 467 468 469static int 470get_range(bitstr_t *bits, int low, int high, const char *names[], 471 int ch, FILE *file) 472{ 473 /* range = number | 474 * [number] "~" [number] ["/" number] | 475 * number "-" number ["/" number] 476 */ 477 478 int num1, num2, num3, rndstep; 479 480 num1 = low; 481 num2 = high; 482 rndstep = 0; 483 484 if (ch == '*') { 485 /* '*' means [low, high] but can still be modified by /step 486 */ 487 ch = get_char(file); 488 if (ch == EOF) 489 return (EOF); 490 } else { 491 if (ch != '~') { 492 ch = get_number(&num1, low, names, ch, file, ",-~ \t\n"); 493 if (ch == EOF) 494 return (EOF); 495 } 496 497 switch (ch) { 498 case '-': 499 /* eat the dash 500 */ 501 ch = get_char(file); 502 if (ch == EOF) 503 return (EOF); 504 505 /* get the number following the dash 506 */ 507 ch = get_number(&num2, low, names, ch, file, "/, \t\n"); 508 if (ch == EOF || num1 > num2) 509 return (EOF); 510 break; 511 case '~': 512 /* eat the tilde 513 */ 514 ch = get_char(file); 515 if (ch == EOF) 516 return (EOF); 517 518 /* get the (optional) number following the tilde 519 */ 520 ch = get_number(&num2, low, names, ch, file, "/, \t\n"); 521 if (ch == EOF) { 522 /* no second number, check for valid terminator 523 */ 524 ch = get_char(file); 525 if (!strchr("/, \t\n", ch)) { 526 unget_char(ch, file); 527 return (EOF); 528 } 529 } 530 if (ch == EOF || num1 > num2) { 531 unget_char(ch, file); 532 return (EOF); 533 } 534 535 /* we must perform the bounds checking ourselves 536 */ 537 if (num1 < low || num2 > high) 538 return (EOF); 539 540 if (ch == '/') { 541 /* randomize the step value instead of num1 542 */ 543 rndstep = 1; 544 break; 545 } 546 547 /* get a random number in the interval [num1, num2] 548 */ 549 num3 = num1; 550 num1 = arc4random_uniform(num2 - num3 + 1) + num3; 551 /* FALLTHROUGH */ 552 default: 553 /* not a range, it's a single number. 554 */ 555 if (set_element(bits, low, high, num1) == EOF) { 556 unget_char(ch, file); 557 return (EOF); 558 } 559 return (ch); 560 } 561 } 562 563 /* check for step size 564 */ 565 if (ch == '/') { 566 /* eat the slash 567 */ 568 ch = get_char(file); 569 if (ch == EOF) 570 return (EOF); 571 572 /* get the step size -- note: we don't pass the 573 * names here, because the number is not an 574 * element id, it's a step size. 'low' is 575 * sent as a 0 since there is no offset either. 576 */ 577 ch = get_number(&num3, 0, NULL, ch, file, ", \t\n"); 578 if (ch == EOF || num3 == 0) 579 return (EOF); 580 if (rndstep) { 581 /* 582 * use a random offset smaller than the step size 583 * and the difference between high and low values. 584 */ 585 num1 += arc4random_uniform(MINIMUM(num3, num2 - num1)); 586 } 587 } else { 588 /* no step. default==1. 589 */ 590 num3 = 1; 591 } 592 593 /* range. set all elements from num1 to num2, stepping 594 * by num3. (the step is a downward-compatible extension 595 * proposed conceptually by bob@acornrc, syntactically 596 * designed then implemented by paul vixie). 597 */ 598 if (set_range(bits, low, high, num1, num2, num3) == EOF) { 599 unget_char(ch, file); 600 return (EOF); 601 } 602 603 return (ch); 604} 605 606static int 607get_number(int *numptr, int low, const char *names[], int ch, FILE *file, 608 const char *terms) 609{ 610 char temp[MAX_TEMPSTR], *pc; 611 int len, i; 612 613 pc = temp; 614 len = 0; 615 616 /* first look for a number */ 617 while (isdigit((unsigned char)ch)) { 618 if (++len >= MAX_TEMPSTR) 619 goto bad; 620 *pc++ = ch; 621 ch = get_char(file); 622 } 623 *pc = '\0'; 624 if (len != 0) { 625 /* got a number, check for valid terminator */ 626 if (!strchr(terms, ch)) 627 goto bad; 628 *numptr = atoi(temp); 629 return (ch); 630 } 631 632 /* no numbers, look for a string if we have any */ 633 if (names) { 634 while (isalpha((unsigned char)ch)) { 635 if (++len >= MAX_TEMPSTR) 636 goto bad; 637 *pc++ = ch; 638 ch = get_char(file); 639 } 640 *pc = '\0'; 641 if (len != 0 && strchr(terms, ch)) { 642 for (i = 0; names[i] != NULL; i++) { 643 if (!strcasecmp(names[i], temp)) { 644 *numptr = i+low; 645 return (ch); 646 } 647 } 648 } 649 } 650 651bad: 652 unget_char(ch, file); 653 return (EOF); 654} 655 656static int 657set_element(bitstr_t *bits, int low, int high, int number) 658{ 659 660 if (number < low || number > high) 661 return (EOF); 662 number -= low; 663 664 bit_set(bits, number); 665 return (0); 666} 667 668static int 669set_range(bitstr_t *bits, int low, int high, int start, int stop, int step) 670{ 671 int i; 672 673 if (start < low || stop > high) 674 return (EOF); 675 start -= low; 676 stop -= low; 677 678 if (step == 1) { 679 bit_nset(bits, start, stop); 680 } else { 681 for (i = start; i <= stop; i += step) 682 bit_set(bits, i); 683 } 684 return (0); 685} 686