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