1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 */ 17 18 #if !defined(lint) && !defined(LINT) 19 static const char rcsid[] = 20 "$FreeBSD$"; 21 #endif 22 23 /* vix 26jan87 [RCS'd; rest of log is in RCS file] 24 * vix 01jan87 [added line-level error recovery] 25 * vix 31dec86 [added /step to the from-to range, per bob@acornrc] 26 * vix 30dec86 [written] 27 */ 28 29 30 #include "cron.h" 31 #include <grp.h> 32 #ifdef LOGIN_CAP 33 #include <login_cap.h> 34 #endif 35 36 typedef enum ecode { 37 e_none, e_minute, e_hour, e_dom, e_month, e_dow, 38 e_cmd, e_timespec, e_username, e_group, e_option, 39 e_mem 40 #ifdef LOGIN_CAP 41 , e_class 42 #endif 43 } ecode_e; 44 45 static char get_list(bitstr_t *, int, int, char *[], int, FILE *), 46 get_range(bitstr_t *, int, int, char *[], int, FILE *), 47 get_number(int *, int, char *[], int, FILE *); 48 static int set_element(bitstr_t *, int, int, int); 49 50 static char *ecodes[] = 51 { 52 "no error", 53 "bad minute", 54 "bad hour", 55 "bad day-of-month", 56 "bad month", 57 "bad day-of-week", 58 "bad command", 59 "bad time specifier", 60 "bad username", 61 "bad group name", 62 "bad option", 63 "out of memory", 64 #ifdef LOGIN_CAP 65 "bad class name", 66 #endif 67 }; 68 69 70 void 71 free_entry(entry *e) 72 { 73 #ifdef LOGIN_CAP 74 if (e->class != NULL) 75 free(e->class); 76 #endif 77 if (e->cmd != NULL) 78 free(e->cmd); 79 if (e->envp != NULL) 80 env_free(e->envp); 81 free(e); 82 } 83 84 85 /* return NULL if eof or syntax error occurs; 86 * otherwise return a pointer to a new entry. 87 */ 88 entry * 89 load_entry(FILE *file, void (*error_func)(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 * EOF 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 **prev_env; 111 112 Debug(DPARS, ("load_entry()...about to eat comments\n")) 113 114 skip_comments(file); 115 116 ch = get_char(file); 117 if (ch == EOF) 118 return NULL; 119 120 /* ch is now the first useful character of a useful line. 121 * it may be an @special or it may be the first character 122 * of a list of minutes. 123 */ 124 125 e = (entry *) calloc(sizeof(entry), sizeof(char)); 126 127 if (e == NULL) { 128 warn("load_entry: calloc failed"); 129 return NULL; 130 } 131 132 if (ch == '@') { 133 long interval; 134 char *endptr; 135 136 /* all of these should be flagged and load-limited; i.e., 137 * instead of @hourly meaning "0 * * * *" it should mean 138 * "close to the front of every hour but not 'til the 139 * system load is low". Problems are: how do you know 140 * what "low" means? (save me from /etc/cron.conf!) and: 141 * how to guarantee low variance (how low is low?), which 142 * means how to we run roughly every hour -- seems like 143 * we need to keep a history or let the first hour set 144 * the schedule, which means we aren't load-limited 145 * anymore. too much for my overloaded brain. (vix, jan90) 146 * HINT 147 */ 148 Debug(DPARS, ("load_entry()...about to test shortcuts\n")) 149 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 150 if (!strcmp("reboot", cmd)) { 151 Debug(DPARS, ("load_entry()...reboot shortcut\n")) 152 e->flags |= WHEN_REBOOT; 153 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 154 Debug(DPARS, ("load_entry()...yearly shortcut\n")) 155 bit_set(e->second, 0); 156 bit_set(e->minute, 0); 157 bit_set(e->hour, 0); 158 bit_set(e->dom, 0); 159 bit_set(e->month, 0); 160 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 161 e->flags |= DOW_STAR; 162 } else if (!strcmp("monthly", cmd)) { 163 Debug(DPARS, ("load_entry()...monthly shortcut\n")) 164 bit_set(e->second, 0); 165 bit_set(e->minute, 0); 166 bit_set(e->hour, 0); 167 bit_set(e->dom, 0); 168 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 169 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 170 e->flags |= DOW_STAR; 171 } else if (!strcmp("weekly", cmd)) { 172 Debug(DPARS, ("load_entry()...weekly shortcut\n")) 173 bit_set(e->second, 0); 174 bit_set(e->minute, 0); 175 bit_set(e->hour, 0); 176 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 177 e->flags |= DOM_STAR; 178 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 179 bit_set(e->dow, 0); 180 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 181 Debug(DPARS, ("load_entry()...daily shortcut\n")) 182 bit_set(e->second, 0); 183 bit_set(e->minute, 0); 184 bit_set(e->hour, 0); 185 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 186 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 187 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 188 } else if (!strcmp("hourly", cmd)) { 189 Debug(DPARS, ("load_entry()...hourly shortcut\n")) 190 bit_set(e->second, 0); 191 bit_set(e->minute, 0); 192 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 193 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 194 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 195 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 196 } else if (!strcmp("every_minute", cmd)) { 197 Debug(DPARS, ("load_entry()...every_minute shortcut\n")) 198 bit_set(e->second, 0); 199 bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1)); 200 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 201 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 202 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 203 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 204 } else if (!strcmp("every_second", cmd)) { 205 Debug(DPARS, ("load_entry()...every_second shortcut\n")) 206 e->flags |= SEC_RES; 207 bit_nset(e->second, 0, (LAST_SECOND-FIRST_SECOND+1)); 208 bit_nset(e->minute, 0, (LAST_MINUTE-FIRST_MINUTE+1)); 209 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 210 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 211 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 212 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 213 } else if (*cmd != '\0' && 214 (interval = strtol(cmd, &endptr, 10)) > 0 && 215 *endptr == '\0') { 216 Debug(DPARS, ("load_entry()... %ld seconds " 217 "since last run\n", interval)) 218 e->interval = interval; 219 e->flags = INTERVAL; 220 } else { 221 ecode = e_timespec; 222 goto eof; 223 } 224 /* Advance past whitespace between shortcut and 225 * username/command. 226 */ 227 Skip_Blanks(ch, file); 228 if (ch == EOF) { 229 ecode = e_cmd; 230 goto eof; 231 } 232 } else { 233 Debug(DPARS, ("load_entry()...about to parse numerics\n")) 234 bit_set(e->second, 0); 235 236 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 237 PPC_NULL, ch, file); 238 if (ch == EOF) { 239 ecode = e_minute; 240 goto eof; 241 } 242 243 /* hours 244 */ 245 246 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 247 PPC_NULL, ch, file); 248 if (ch == EOF) { 249 ecode = e_hour; 250 goto eof; 251 } 252 253 /* DOM (days of month) 254 */ 255 256 if (ch == '*') 257 e->flags |= DOM_STAR; 258 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 259 PPC_NULL, ch, file); 260 if (ch == EOF) { 261 ecode = e_dom; 262 goto eof; 263 } 264 265 /* month 266 */ 267 268 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 269 MonthNames, ch, file); 270 if (ch == EOF) { 271 ecode = e_month; 272 goto eof; 273 } 274 275 /* DOW (days of week) 276 */ 277 278 if (ch == '*') 279 e->flags |= DOW_STAR; 280 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 281 DowNames, ch, file); 282 if (ch == EOF) { 283 ecode = e_dow; 284 goto eof; 285 } 286 } 287 288 /* make sundays equivalent */ 289 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 290 bit_set(e->dow, 0); 291 bit_set(e->dow, 7); 292 } 293 294 /* ch is the first character of a command, or a username */ 295 unget_char(ch, file); 296 297 if (!pw) { 298 char *username = cmd; /* temp buffer */ 299 char *s; 300 struct group *grp; 301 #ifdef LOGIN_CAP 302 login_cap_t *lc; 303 #endif 304 305 Debug(DPARS, ("load_entry()...about to parse username\n")) 306 ch = get_string(username, MAX_COMMAND, file, " \t"); 307 308 Debug(DPARS, ("load_entry()...got %s\n",username)) 309 if (ch == EOF) { 310 ecode = e_cmd; 311 goto eof; 312 } 313 314 /* need to have consumed blanks when checking options below */ 315 Skip_Blanks(ch, file) 316 unget_char(ch, file); 317 #ifdef LOGIN_CAP 318 if ((s = strrchr(username, '/')) != NULL) { 319 *s = '\0'; 320 e->class = strdup(s + 1); 321 if (e->class == NULL) 322 warn("strdup(\"%s\")", s + 1); 323 } else { 324 e->class = strdup(RESOURCE_RC); 325 if (e->class == NULL) 326 warn("strdup(\"%s\")", RESOURCE_RC); 327 } 328 if (e->class == NULL) { 329 ecode = e_mem; 330 goto eof; 331 } 332 if ((lc = login_getclass(e->class)) == NULL) { 333 ecode = e_class; 334 goto eof; 335 } 336 login_close(lc); 337 #endif 338 grp = NULL; 339 if ((s = strrchr(username, ':')) != NULL) { 340 *s = '\0'; 341 if ((grp = getgrnam(s + 1)) == NULL) { 342 ecode = e_group; 343 goto eof; 344 } 345 } 346 347 pw = getpwnam(username); 348 if (pw == NULL) { 349 ecode = e_username; 350 goto eof; 351 } 352 if (grp != NULL) 353 pw->pw_gid = grp->gr_gid; 354 Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid)) 355 #ifdef LOGIN_CAP 356 Debug(DPARS, ("load_entry()...class %s\n",e->class)) 357 #endif 358 } 359 360 #ifndef PAM /* PAM takes care of account expiration by itself */ 361 if (pw->pw_expire && time(NULL) >= pw->pw_expire) { 362 ecode = e_username; 363 goto eof; 364 } 365 #endif /* !PAM */ 366 367 e->uid = pw->pw_uid; 368 e->gid = pw->pw_gid; 369 370 /* copy and fix up environment. some variables are just defaults and 371 * others are overrides; we process only the overrides here, defaults 372 * are handled in do_command after login.conf is processed. 373 */ 374 e->envp = env_copy(envp); 375 if (e->envp == NULL) { 376 warn("env_copy"); 377 ecode = e_mem; 378 goto eof; 379 } 380 if (!env_get("SHELL", e->envp)) { 381 prev_env = e->envp; 382 sprintf(envstr, "SHELL=%s", _PATH_BSHELL); 383 e->envp = env_set(e->envp, envstr); 384 if (e->envp == NULL) { 385 warn("env_set(%s)", envstr); 386 env_free(prev_env); 387 ecode = e_mem; 388 goto eof; 389 } 390 } 391 /* If LOGIN_CAP, this is deferred to do_command where the login class 392 * is processed. If !LOGIN_CAP, do it here. 393 */ 394 #ifndef LOGIN_CAP 395 if (!env_get("HOME", e->envp)) { 396 prev_env = e->envp; 397 sprintf(envstr, "HOME=%s", pw->pw_dir); 398 e->envp = env_set(e->envp, envstr); 399 if (e->envp == NULL) { 400 warn("env_set(%s)", envstr); 401 env_free(prev_env); 402 ecode = e_mem; 403 goto eof; 404 } 405 } 406 #endif 407 prev_env = e->envp; 408 sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name); 409 e->envp = env_set(e->envp, envstr); 410 if (e->envp == NULL) { 411 warn("env_set(%s)", envstr); 412 env_free(prev_env); 413 ecode = e_mem; 414 goto eof; 415 } 416 #if defined(BSD) 417 prev_env = e->envp; 418 sprintf(envstr, "%s=%s", "USER", pw->pw_name); 419 e->envp = env_set(e->envp, envstr); 420 if (e->envp == NULL) { 421 warn("env_set(%s)", envstr); 422 env_free(prev_env); 423 ecode = e_mem; 424 goto eof; 425 } 426 #endif 427 428 Debug(DPARS, ("load_entry()...checking for command options\n")) 429 430 ch = get_char(file); 431 432 while (ch == '-') { 433 Debug(DPARS|DEXT, ("load_entry()...expecting option\n")) 434 switch (ch = get_char(file)) { 435 case 'n': 436 Debug(DPARS|DEXT, ("load_entry()...got MAIL_WHEN_ERR ('n') option\n")) 437 /* only allow the user to set the option once */ 438 if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) { 439 Debug(DPARS|DEXT, ("load_entry()...duplicate MAIL_WHEN_ERR ('n') option\n")) 440 ecode = e_option; 441 goto eof; 442 } 443 e->flags |= MAIL_WHEN_ERR; 444 break; 445 case 'q': 446 Debug(DPARS|DEXT, ("load_entry()...got DONT_LOG ('q') option\n")) 447 /* only allow the user to set the option once */ 448 if ((e->flags & DONT_LOG) == DONT_LOG) { 449 Debug(DPARS|DEXT, ("load_entry()...duplicate DONT_LOG ('q') option\n")) 450 ecode = e_option; 451 goto eof; 452 } 453 e->flags |= DONT_LOG; 454 break; 455 default: 456 Debug(DPARS|DEXT, ("load_entry()...invalid option '%c'\n", ch)) 457 ecode = e_option; 458 goto eof; 459 } 460 ch = get_char(file); 461 if (ch!='\t' && ch!=' ') { 462 ecode = e_option; 463 goto eof; 464 } 465 466 Skip_Blanks(ch, file) 467 if (ch == EOF || ch == '\n') { 468 ecode = e_cmd; 469 goto eof; 470 } 471 } 472 473 unget_char(ch, file); 474 475 Debug(DPARS, ("load_entry()...about to parse command\n")) 476 477 /* Everything up to the next \n or EOF is part of the command... 478 * too bad we don't know in advance how long it will be, since we 479 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 480 * XXX - should use realloc(). 481 */ 482 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 483 484 /* a file without a \n before the EOF is rude, so we'll complain... 485 */ 486 if (ch == EOF) { 487 ecode = e_cmd; 488 goto eof; 489 } 490 491 /* got the command in the 'cmd' string; save it in *e. 492 */ 493 e->cmd = strdup(cmd); 494 if (e->cmd == NULL) { 495 warn("strdup(\"%s\")", cmd); 496 ecode = e_mem; 497 goto eof; 498 } 499 Debug(DPARS, ("load_entry()...returning successfully\n")) 500 501 /* success, fini, return pointer to the entry we just created... 502 */ 503 return e; 504 505 eof: 506 free_entry(e); 507 if (ecode != e_none && error_func) 508 (*error_func)(ecodes[(int)ecode]); 509 while (ch != EOF && ch != '\n') 510 ch = get_char(file); 511 return NULL; 512 } 513 514 515 static char 516 get_list(bitstr_t *bits, int low, int high, char *names[], int ch, FILE *file) 517 { 518 register int done; 519 520 /* we know that we point to a non-blank character here; 521 * must do a Skip_Blanks before we exit, so that the 522 * next call (or the code that picks up the cmd) can 523 * assume the same thing. 524 */ 525 526 Debug(DPARS|DEXT, ("get_list()...entered\n")) 527 528 /* list = range {"," range} 529 */ 530 531 /* clear the bit string, since the default is 'off'. 532 */ 533 bit_nclear(bits, 0, (high-low+1)); 534 535 /* process all ranges 536 */ 537 done = FALSE; 538 while (!done) { 539 ch = get_range(bits, low, high, names, ch, file); 540 if (ch == ',') 541 ch = get_char(file); 542 else 543 done = TRUE; 544 } 545 546 /* exiting. skip to some blanks, then skip over the blanks. 547 */ 548 Skip_Nonblanks(ch, file) 549 Skip_Blanks(ch, file) 550 551 Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) 552 553 return ch; 554 } 555 556 557 static char 558 get_range(bitstr_t *bits, int low, int high, char *names[], int ch, FILE *file) 559 { 560 /* range = number | number "-" number [ "/" number ] 561 */ 562 563 register int i; 564 auto int num1, num2, num3; 565 566 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) 567 568 if (ch == '*') { 569 /* '*' means "first-last" but can still be modified by /step 570 */ 571 num1 = low; 572 num2 = high; 573 ch = get_char(file); 574 if (ch == EOF) 575 return EOF; 576 } else { 577 if (EOF == (ch = get_number(&num1, low, names, ch, file))) 578 return EOF; 579 580 if (ch == '/') 581 num2 = high; 582 else if (ch != '-') { 583 /* not a range, it's a single number. 584 */ 585 if (EOF == set_element(bits, low, high, num1)) 586 return EOF; 587 return ch; 588 } else { 589 /* eat the dash 590 */ 591 ch = get_char(file); 592 if (ch == EOF) 593 return EOF; 594 595 /* get the number following the dash 596 */ 597 ch = get_number(&num2, low, names, ch, file); 598 if (ch == EOF) 599 return EOF; 600 } 601 } 602 603 /* check for step size 604 */ 605 if (ch == '/') { 606 /* eat the slash 607 */ 608 ch = get_char(file); 609 if (ch == EOF) 610 return EOF; 611 612 /* get the step size -- note: we don't pass the 613 * names here, because the number is not an 614 * element id, it's a step size. 'low' is 615 * sent as a 0 since there is no offset either. 616 */ 617 ch = get_number(&num3, 0, PPC_NULL, ch, file); 618 if (ch == EOF || num3 == 0) 619 return EOF; 620 } else { 621 /* no step. default==1. 622 */ 623 num3 = 1; 624 } 625 626 /* range. set all elements from num1 to num2, stepping 627 * by num3. (the step is a downward-compatible extension 628 * proposed conceptually by bob@acornrc, syntactically 629 * designed then implmented by paul vixie). 630 */ 631 for (i = num1; i <= num2; i += num3) 632 if (EOF == set_element(bits, low, high, i)) 633 return EOF; 634 635 return ch; 636 } 637 638 639 static char 640 get_number(int *numptr, int low, char *names[], int ch, FILE *file) 641 { 642 char temp[MAX_TEMPSTR], *pc; 643 int len, i, all_digits; 644 645 /* collect alphanumerics into our fixed-size temp array 646 */ 647 pc = temp; 648 len = 0; 649 all_digits = TRUE; 650 while (isalnum(ch)) { 651 if (++len >= MAX_TEMPSTR) 652 return EOF; 653 654 *pc++ = ch; 655 656 if (!isdigit(ch)) 657 all_digits = FALSE; 658 659 ch = get_char(file); 660 } 661 *pc = '\0'; 662 if (len == 0) 663 return (EOF); 664 665 /* try to find the name in the name list 666 */ 667 if (names) { 668 for (i = 0; names[i] != NULL; i++) { 669 Debug(DPARS|DEXT, 670 ("get_num, compare(%s,%s)\n", names[i], temp)) 671 if (!strcasecmp(names[i], temp)) { 672 *numptr = i+low; 673 return ch; 674 } 675 } 676 } 677 678 /* no name list specified, or there is one and our string isn't 679 * in it. either way: if it's all digits, use its magnitude. 680 * otherwise, it's an error. 681 */ 682 if (all_digits) { 683 *numptr = atoi(temp); 684 return ch; 685 } 686 687 return EOF; 688 } 689 690 691 static int 692 set_element(bitstr_t *bits, int low, int high, int number) 693 { 694 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) 695 696 if (number < low || number > high) 697 return EOF; 698 699 bit_set(bits, (number-low)); 700 return OK; 701 } 702