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) 19static const char rcsid[] = 20 "$FreeBSD: src/usr.sbin/cron/lib/entry.c,v 1.17 2006/09/26 18:06:09 brian Exp $"; 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 36typedef 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 44static char get_list __P((bitstr_t *, int, int, char *[], int, FILE *)), 45 get_range __P((bitstr_t *, int, int, char *[], int, FILE *)), 46 get_number __P((int *, int, char *[], int, FILE *)); 47static int set_element __P((bitstr_t *, int, int, int)); 48 49static 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 68void 69free_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#ifdef __APPLE__ 88entry * 89load_entry(file, error_func, uname, envp) 90 FILE *file; 91 void (*error_func)(); 92 char *uname; 93 char **envp; 94#else 95entry * 96load_entry(file, error_func, pw, envp) 97 FILE *file; 98 void (*error_func)(); 99 struct passwd *pw; 100 char **envp; 101#endif 102{ 103 /* this function reads one crontab entry -- the next -- from a file. 104 * it skips any leading blank lines, ignores comments, and returns 105 * EOF if for any reason the entry can't be read and parsed. 106 * 107 * the entry is also parsed here. 108 * 109 * syntax: 110 * user crontab: 111 * minutes hours doms months dows cmd\n 112 * system crontab (/etc/crontab): 113 * minutes hours doms months dows USERNAME cmd\n 114 */ 115 116 ecode_e ecode = e_none; 117 entry *e; 118 int ch; 119 char cmd[MAX_COMMAND]; 120 char envstr[MAX_ENVSTR]; 121 char **prev_env; 122 123#ifdef __APPLE__ 124 // Compatibility hack to minimize source diffs below 125 struct passwd _pw, *pw = NULL; 126 memset(&_pw, 0, sizeof(_pw)); 127 _pw.pw_name = uname; 128 if (uname) { 129 pw = &_pw; 130 } 131#endif /* __APPLE__ */ 132 133 Debug(DPARS, ("load_entry()...about to eat comments\n")) 134 135 skip_comments(file); 136 137 ch = get_char(file); 138 if (ch == EOF) 139 return NULL; 140 141 /* ch is now the first useful character of a useful line. 142 * it may be an @special or it may be the first character 143 * of a list of minutes. 144 */ 145 146 e = (entry *) calloc(sizeof(entry), sizeof(char)); 147 148 if (e == NULL) { 149 warn("load_entry: calloc failed"); 150 return NULL; 151 } 152 153 if (ch == '@') { 154 /* all of these should be flagged and load-limited; i.e., 155 * instead of @hourly meaning "0 * * * *" it should mean 156 * "close to the front of every hour but not 'til the 157 * system load is low". Problems are: how do you know 158 * what "low" means? (save me from /etc/cron.conf!) and: 159 * how to guarantee low variance (how low is low?), which 160 * means how to we run roughly every hour -- seems like 161 * we need to keep a history or let the first hour set 162 * the schedule, which means we aren't load-limited 163 * anymore. too much for my overloaded brain. (vix, jan90) 164 * HINT 165 */ 166 Debug(DPARS, ("load_entry()...about to test shortcuts\n")) 167 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 168 if (!strcmp("reboot", cmd)) { 169 Debug(DPARS, ("load_entry()...reboot shortcut\n")) 170 e->flags |= WHEN_REBOOT; 171 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 172 Debug(DPARS, ("load_entry()...yearly shortcut\n")) 173 bit_set(e->minute, 0); 174 bit_set(e->hour, 0); 175 bit_set(e->dom, 0); 176 bit_set(e->month, 0); 177 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 178 e->flags |= DOW_STAR; 179 } else if (!strcmp("monthly", cmd)) { 180 Debug(DPARS, ("load_entry()...monthly shortcut\n")) 181 bit_set(e->minute, 0); 182 bit_set(e->hour, 0); 183 bit_set(e->dom, 0); 184 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 185 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 186 e->flags |= DOW_STAR; 187 } else if (!strcmp("weekly", cmd)) { 188 Debug(DPARS, ("load_entry()...weekly shortcut\n")) 189 bit_set(e->minute, 0); 190 bit_set(e->hour, 0); 191 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 192 e->flags |= DOM_STAR; 193 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 194 bit_set(e->dow, 0); 195 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 196 Debug(DPARS, ("load_entry()...daily shortcut\n")) 197 bit_set(e->minute, 0); 198 bit_set(e->hour, 0); 199 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 200 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 201 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 202 } else if (!strcmp("hourly", cmd)) { 203 Debug(DPARS, ("load_entry()...hourly shortcut\n")) 204 bit_set(e->minute, 0); 205 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 206 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 207 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 208 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 209 } else { 210 ecode = e_timespec; 211 goto eof; 212 } 213 /* Advance past whitespace between shortcut and 214 * username/command. 215 */ 216 Skip_Blanks(ch, file); 217 if (ch == EOF) { 218 ecode = e_cmd; 219 goto eof; 220 } 221 } else { 222 Debug(DPARS, ("load_entry()...about to parse numerics\n")) 223 224 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 225 PPC_NULL, ch, file); 226 if (ch == EOF) { 227 ecode = e_minute; 228 goto eof; 229 } 230 231 /* hours 232 */ 233 234 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 235 PPC_NULL, ch, file); 236 if (ch == EOF) { 237 ecode = e_hour; 238 goto eof; 239 } 240 241 /* DOM (days of month) 242 */ 243 244 if (ch == '*') 245 e->flags |= DOM_STAR; 246 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 247 PPC_NULL, ch, file); 248 if (ch == EOF) { 249 ecode = e_dom; 250 goto eof; 251 } 252 253 /* month 254 */ 255 256 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 257 MonthNames, ch, file); 258 if (ch == EOF) { 259 ecode = e_month; 260 goto eof; 261 } 262 263 /* DOW (days of week) 264 */ 265 266 if (ch == '*') 267 e->flags |= DOW_STAR; 268 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 269 DowNames, ch, file); 270 if (ch == EOF) { 271 ecode = e_dow; 272 goto eof; 273 } 274 } 275 276 /* make sundays equivilent */ 277 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 278 bit_set(e->dow, 0); 279 bit_set(e->dow, 7); 280 } 281 282 /* ch is the first character of a command, or a username */ 283 unget_char(ch, file); 284 285 if (!pw) { 286 char *username = cmd; /* temp buffer */ 287 char *s; 288#ifndef __APPLE__ 289 struct group *grp; 290#endif 291#ifdef LOGIN_CAP 292 login_cap_t *lc; 293#endif 294 295 Debug(DPARS, ("load_entry()...about to parse username\n")) 296 ch = get_string(username, MAX_COMMAND, file, " \t"); 297 298 Debug(DPARS, ("load_entry()...got %s\n",username)) 299 if (ch == EOF) { 300 ecode = e_cmd; 301 goto eof; 302 } 303 304#ifdef LOGIN_CAP 305 if ((s = strrchr(username, '/')) != NULL) { 306 *s = '\0'; 307 e->class = strdup(s + 1); 308 if (e->class == NULL) 309 warn("strdup(\"%s\")", s + 1); 310 } else { 311 e->class = strdup(RESOURCE_RC); 312 if (e->class == NULL) 313 warn("strdup(\"%s\")", RESOURCE_RC); 314 } 315 if (e->class == NULL) { 316 ecode = e_mem; 317 goto eof; 318 } 319 if ((lc = login_getclass(e->class)) == NULL) { 320 ecode = e_class; 321 goto eof; 322 } 323 login_close(lc); 324#endif 325#ifdef __APPLE__ 326 if ((s = strrchr(username, ':')) != NULL) { 327 *s = '\0'; 328 strcpy(e->gname, s + 1); 329 } 330 strcpy(e->uname, username); 331 pw = &_pw; 332 } else { 333 strcpy(e->uname, uname); 334 strcpy(e->gname, ""); 335 } 336 Debug(DPARS, ("load_entry()...user: %s group:\n",e->uname,e->gname)) 337#else /* __APPLE__ */ 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 if (pw->pw_expire && time(NULL) >= pw->pw_expire) { 361 ecode = e_username; 362 goto eof; 363 } 364 365 e->uid = pw->pw_uid; 366 e->gid = pw->pw_gid; 367#endif /* __APPLE__ */ 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 prev_env = e->envp; 390#ifndef __APPLE__ 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#endif /* !__APPLE__ */ 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#ifdef __APPLE__ 457 if( e->cmd[0] == '@' ) { 458 if( strncmp(e->cmd+1, "AppleNotOnBattery", 17) == 0 ) { 459 e->flags |= NOT_BATTERY; 460 e->cmd += 18; 461 for( ; isspace(e->cmd[0]); e->cmd++ ); 462 } 463 } 464#endif /* __APPLE__ */ 465 Debug(DPARS, ("load_entry()...returning successfully\n")) 466 467 /* success, fini, return pointer to the entry we just created... 468 */ 469 return e; 470 471 eof: 472 free_entry(e); 473 if (ecode != e_none && error_func) 474 (*error_func)(ecodes[(int)ecode]); 475 while (ch != EOF && ch != '\n') 476 ch = get_char(file); 477 return NULL; 478} 479 480 481static char 482get_list(bits, low, high, names, ch, file) 483 bitstr_t *bits; /* one bit per flag, default=FALSE */ 484 int low, high; /* bounds, impl. offset for bitstr */ 485 char *names[]; /* NULL or *[] of names for these elements */ 486 int ch; /* current character being processed */ 487 FILE *file; /* file being read */ 488{ 489 register int done; 490 491 /* we know that we point to a non-blank character here; 492 * must do a Skip_Blanks before we exit, so that the 493 * next call (or the code that picks up the cmd) can 494 * assume the same thing. 495 */ 496 497 Debug(DPARS|DEXT, ("get_list()...entered\n")) 498 499 /* list = range {"," range} 500 */ 501 502 /* clear the bit string, since the default is 'off'. 503 */ 504 bit_nclear(bits, 0, (high-low+1)); 505 506 /* process all ranges 507 */ 508 done = FALSE; 509 while (!done) { 510 ch = get_range(bits, low, high, names, ch, file); 511 if (ch == ',') 512 ch = get_char(file); 513 else 514 done = TRUE; 515 } 516 517 /* exiting. skip to some blanks, then skip over the blanks. 518 */ 519 Skip_Nonblanks(ch, file) 520 Skip_Blanks(ch, file) 521 522 Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) 523 524 return ch; 525} 526 527 528static char 529get_range(bits, low, high, names, ch, file) 530 bitstr_t *bits; /* one bit per flag, default=FALSE */ 531 int low, high; /* bounds, impl. offset for bitstr */ 532 char *names[]; /* NULL or names of elements */ 533 int ch; /* current character being processed */ 534 FILE *file; /* file being read */ 535{ 536 /* range = number | number "-" number [ "/" number ] 537 */ 538 539 register int i; 540 auto int num1, num2, num3; 541 542 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) 543 544 if (ch == '*') { 545 /* '*' means "first-last" but can still be modified by /step 546 */ 547 num1 = low; 548 num2 = high; 549 ch = get_char(file); 550 if (ch == EOF) 551 return EOF; 552 } else { 553 if (EOF == (ch = get_number(&num1, low, names, ch, file))) 554 return EOF; 555 556 if (ch == '/') 557 num2 = high; 558 else if (ch != '-') { 559 /* not a range, it's a single number. 560 */ 561 if (EOF == set_element(bits, low, high, num1)) 562 return EOF; 563 return ch; 564 } else { 565 /* eat the dash 566 */ 567 ch = get_char(file); 568 if (ch == EOF) 569 return EOF; 570 571 /* get the number following the dash 572 */ 573 ch = get_number(&num2, low, names, ch, file); 574 if (ch == EOF) 575 return EOF; 576 } 577 } 578 579 /* check for step size 580 */ 581 if (ch == '/') { 582 /* eat the slash 583 */ 584 ch = get_char(file); 585 if (ch == EOF) 586 return EOF; 587 588 /* get the step size -- note: we don't pass the 589 * names here, because the number is not an 590 * element id, it's a step size. 'low' is 591 * sent as a 0 since there is no offset either. 592 */ 593 ch = get_number(&num3, 0, PPC_NULL, ch, file); 594 if (ch == EOF || num3 == 0) 595 return EOF; 596 } else { 597 /* no step. default==1. 598 */ 599 num3 = 1; 600 } 601 602 /* range. set all elements from num1 to num2, stepping 603 * by num3. (the step is a downward-compatible extension 604 * proposed conceptually by bob@acornrc, syntactically 605 * designed then implmented by paul vixie). 606 */ 607 for (i = num1; i <= num2; i += num3) 608 if (EOF == set_element(bits, low, high, i)) 609 return EOF; 610 611 return ch; 612} 613 614 615static char 616get_number(numptr, low, names, ch, file) 617 int *numptr; /* where does the result go? */ 618 int low; /* offset applied to result if symbolic enum used */ 619 char *names[]; /* symbolic names, if any, for enums */ 620 int ch; /* current character */ 621 FILE *file; /* source */ 622{ 623 char temp[MAX_TEMPSTR], *pc; 624 int len, i, all_digits; 625 626 /* collect alphanumerics into our fixed-size temp array 627 */ 628 pc = temp; 629 len = 0; 630 all_digits = TRUE; 631 while (isalnum(ch)) { 632 if (++len >= MAX_TEMPSTR) 633 return EOF; 634 635 *pc++ = ch; 636 637 if (!isdigit(ch)) 638 all_digits = FALSE; 639 640 ch = get_char(file); 641 } 642 *pc = '\0'; 643 if (len == 0) 644 return (EOF); 645 646 /* try to find the name in the name list 647 */ 648 if (names) { 649 for (i = 0; names[i] != NULL; i++) { 650 Debug(DPARS|DEXT, 651 ("get_num, compare(%s,%s)\n", names[i], temp)) 652 if (!strcasecmp(names[i], temp)) { 653 *numptr = i+low; 654 return ch; 655 } 656 } 657 } 658 659 /* no name list specified, or there is one and our string isn't 660 * in it. either way: if it's all digits, use its magnitude. 661 * otherwise, it's an error. 662 */ 663 if (all_digits) { 664 *numptr = atoi(temp); 665 return ch; 666 } 667 668 return EOF; 669} 670 671 672static int 673set_element(bits, low, high, number) 674 bitstr_t *bits; /* one bit per flag, default=FALSE */ 675 int low; 676 int high; 677 int number; 678{ 679 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) 680 681 if (number < low || number > high) 682 return EOF; 683 684 bit_set(bits, (number-low)); 685 return OK; 686} 687