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