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