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