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