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