1 /* $OpenBSD: entry.c,v 1.33 2011/05/19 15:00:17 phessler Exp $ */ 2 3 /* 4 * Copyright 1988,1990,1993,1994 by Paul Vixie 5 * All rights reserved 6 */ 7 8 /* 9 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 10 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. 11 * 12 * Permission to use, copy, modify, and distribute this software for any 13 * purpose with or without fee is hereby granted, provided that the above 14 * copyright notice and this permission notice appear in all copies. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 22 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 */ 24 25 /* vix 26jan87 [RCS'd; rest of log is in RCS file] 26 * vix 01jan87 [added line-level error recovery] 27 * vix 31dec86 [added /step to the from-to range, per bob@acornrc] 28 * vix 30dec86 [written] 29 */ 30 31 #include "cron.h" 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_option, e_memory 36 } ecode_e; 37 38 static const char *ecodes[] = 39 { 40 "no error", 41 "bad minute", 42 "bad hour", 43 "bad day-of-month", 44 "bad month", 45 "bad day-of-week", 46 "bad command", 47 "bad time specifier", 48 "bad username", 49 "bad option", 50 "out of memory" 51 }; 52 53 static int get_list(bitstr_t *, int, int, const char *[], int, FILE *), 54 get_range(bitstr_t *, int, int, const char *[], int, FILE *), 55 get_number(int *, int, const char *[], int, FILE *, const char *), 56 set_element(bitstr_t *, int, int, int); 57 58 void 59 free_entry(entry *e) { 60 free(e->cmd); 61 free(e->pwd); 62 if (e->envp) 63 env_free(e->envp); 64 free(e); 65 } 66 67 /* return NULL if eof or syntax error occurs; 68 * otherwise return a pointer to a new entry. 69 */ 70 entry * 71 load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, 72 char **envp) { 73 /* this function reads one crontab entry -- the next -- from a file. 74 * it skips any leading blank lines, ignores comments, and returns 75 * NULL if for any reason the entry can't be read and parsed. 76 * 77 * the entry is also parsed here. 78 * 79 * syntax: 80 * user crontab: 81 * minutes hours doms months dows cmd\n 82 * system crontab (/etc/crontab): 83 * minutes hours doms months dows USERNAME cmd\n 84 */ 85 86 ecode_e ecode = e_none; 87 entry *e; 88 int ch; 89 char cmd[MAX_COMMAND]; 90 char envstr[MAX_ENVSTR]; 91 char **tenvp; 92 93 Debug(DPARS, ("load_entry()...about to eat comments\n")) 94 95 skip_comments(file); 96 97 ch = get_char(file); 98 if (ch == EOF) 99 return (NULL); 100 101 /* ch is now the first useful character of a useful line. 102 * it may be an @special or it may be the first character 103 * of a list of minutes. 104 */ 105 106 e = (entry *) calloc(sizeof(entry), sizeof(char)); 107 if (e == NULL) { 108 ecode = e_memory; 109 goto eof; 110 } 111 112 if (ch == '@') { 113 /* all of these should be flagged and load-limited; i.e., 114 * instead of @hourly meaning "0 * * * *" it should mean 115 * "close to the front of every hour but not 'til the 116 * system load is low". Problems are: how do you know 117 * what "low" means? (save me from /etc/cron.conf!) and: 118 * how to guarantee low variance (how low is low?), which 119 * means how to we run roughly every hour -- seems like 120 * we need to keep a history or let the first hour set 121 * the schedule, which means we aren't load-limited 122 * anymore. too much for my overloaded brain. (vix, jan90) 123 * HINT 124 */ 125 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 126 if (!strcmp("reboot", cmd)) { 127 e->flags |= WHEN_REBOOT; 128 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 129 bit_set(e->minute, 0); 130 bit_set(e->hour, 0); 131 bit_set(e->dom, 0); 132 bit_set(e->month, 0); 133 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 134 e->flags |= DOW_STAR; 135 } else if (!strcmp("monthly", cmd)) { 136 bit_set(e->minute, 0); 137 bit_set(e->hour, 0); 138 bit_set(e->dom, 0); 139 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 140 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 141 e->flags |= DOW_STAR; 142 } else if (!strcmp("weekly", cmd)) { 143 bit_set(e->minute, 0); 144 bit_set(e->hour, 0); 145 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 146 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 147 bit_set(e->dow, 0); 148 e->flags |= DOW_STAR; 149 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 150 bit_set(e->minute, 0); 151 bit_set(e->hour, 0); 152 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 153 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 154 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 155 } else if (!strcmp("hourly", cmd)) { 156 bit_set(e->minute, 0); 157 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 158 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 159 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 160 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 161 e->flags |= HR_STAR; 162 } else { 163 ecode = e_timespec; 164 goto eof; 165 } 166 /* Advance past whitespace between shortcut and 167 * username/command. 168 */ 169 Skip_Blanks(ch, file); 170 if (ch == EOF || ch == '\n') { 171 ecode = e_cmd; 172 goto eof; 173 } 174 } else { 175 Debug(DPARS, ("load_entry()...about to parse numerics\n")) 176 177 if (ch == '*') 178 e->flags |= MIN_STAR; 179 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 180 PPC_NULL, ch, file); 181 if (ch == EOF) { 182 ecode = e_minute; 183 goto eof; 184 } 185 186 /* hours 187 */ 188 189 if (ch == '*') 190 e->flags |= HR_STAR; 191 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 192 PPC_NULL, ch, file); 193 if (ch == EOF) { 194 ecode = e_hour; 195 goto eof; 196 } 197 198 /* DOM (days of month) 199 */ 200 201 if (ch == '*') 202 e->flags |= DOM_STAR; 203 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 204 PPC_NULL, ch, file); 205 if (ch == EOF) { 206 ecode = e_dom; 207 goto eof; 208 } 209 210 /* month 211 */ 212 213 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 214 MonthNames, ch, file); 215 if (ch == EOF) { 216 ecode = e_month; 217 goto eof; 218 } 219 220 /* DOW (days of week) 221 */ 222 223 if (ch == '*') 224 e->flags |= DOW_STAR; 225 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 226 DowNames, ch, file); 227 if (ch == EOF) { 228 ecode = e_dow; 229 goto eof; 230 } 231 } 232 233 /* make sundays equivalent */ 234 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 235 bit_set(e->dow, 0); 236 bit_set(e->dow, 7); 237 } 238 239 /* check for permature EOL and catch a common typo */ 240 if (ch == '\n' || ch == '*') { 241 ecode = e_cmd; 242 goto eof; 243 } 244 245 /* ch is the first character of a command, or a username */ 246 unget_char(ch, file); 247 248 if (!pw) { 249 char *username = cmd; /* temp buffer */ 250 251 Debug(DPARS, ("load_entry()...about to parse username\n")) 252 ch = get_string(username, MAX_COMMAND, file, " \t\n"); 253 254 Debug(DPARS, ("load_entry()...got %s\n",username)) 255 if (ch == EOF || ch == '\n' || ch == '*') { 256 ecode = e_cmd; 257 goto eof; 258 } 259 260 pw = getpwnam(username); 261 if (pw == NULL) { 262 ecode = e_username; 263 goto eof; 264 } 265 Debug(DPARS, ("load_entry()...uid %lu, gid %lu\n", 266 (unsigned long)pw->pw_uid, 267 (unsigned long)pw->pw_gid)) 268 } 269 270 if ((e->pwd = pw_dup(pw)) == NULL) { 271 ecode = e_memory; 272 goto eof; 273 } 274 bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd)); 275 276 /* copy and fix up environment. some variables are just defaults and 277 * others are overrides. 278 */ 279 if ((e->envp = env_copy(envp)) == NULL) { 280 ecode = e_memory; 281 goto eof; 282 } 283 if (!env_get("SHELL", e->envp)) { 284 if (snprintf(envstr, sizeof envstr, "SHELL=%s", _PATH_BSHELL) >= 285 sizeof(envstr)) 286 log_it("CRON", getpid(), "error", "can't set SHELL"); 287 else { 288 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 289 ecode = e_memory; 290 goto eof; 291 } 292 e->envp = tenvp; 293 } 294 } 295 if (!env_get("HOME", e->envp)) { 296 if (snprintf(envstr, sizeof envstr, "HOME=%s", pw->pw_dir) >= 297 sizeof(envstr)) 298 log_it("CRON", getpid(), "error", "can't set HOME"); 299 else { 300 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 301 ecode = e_memory; 302 goto eof; 303 } 304 e->envp = tenvp; 305 } 306 } 307 #ifndef LOGIN_CAP 308 /* If login.conf is in use we will get the default PATH later. */ 309 if (!env_get("PATH", e->envp)) { 310 if (snprintf(envstr, sizeof envstr, "PATH=%s", _PATH_DEFPATH) >= 311 sizeof(envstr)) 312 log_it("CRON", getpid(), "error", "can't set PATH"); 313 else { 314 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 315 ecode = e_memory; 316 goto eof; 317 } 318 e->envp = tenvp; 319 } 320 } 321 #endif /* LOGIN_CAP */ 322 if (snprintf(envstr, sizeof envstr, "LOGNAME=%s", pw->pw_name) >= 323 sizeof(envstr)) 324 log_it("CRON", getpid(), "error", "can't set LOGNAME"); 325 else { 326 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 327 ecode = e_memory; 328 goto eof; 329 } 330 e->envp = tenvp; 331 } 332 #if defined(BSD) || defined(__linux) 333 if (snprintf(envstr, sizeof envstr, "USER=%s", pw->pw_name) >= 334 sizeof(envstr)) 335 log_it("CRON", getpid(), "error", "can't set USER"); 336 else { 337 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 338 ecode = e_memory; 339 goto eof; 340 } 341 e->envp = tenvp; 342 } 343 #endif 344 345 Debug(DPARS, ("load_entry()...about to parse command\n")) 346 347 /* If the first character of the command is '-' it is a cron option. 348 */ 349 ch = get_char(file); 350 while (ch == '-') { 351 switch (ch = get_char(file)) { 352 case 'q': 353 e->flags |= DONT_LOG; 354 Skip_Nonblanks(ch, file) 355 break; 356 default: 357 ecode = e_option; 358 goto eof; 359 } 360 Skip_Blanks(ch, file) 361 if (ch == EOF || ch == '\n') { 362 ecode = e_cmd; 363 goto eof; 364 } 365 } 366 unget_char(ch, file); 367 368 /* Everything up to the next \n or EOF is part of the command... 369 * too bad we don't know in advance how long it will be, since we 370 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 371 */ 372 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 373 374 /* a file without a \n before the EOF is rude, so we'll complain... 375 */ 376 if (ch == EOF) { 377 ecode = e_cmd; 378 goto eof; 379 } 380 381 /* got the command in the 'cmd' string; save it in *e. 382 */ 383 if ((e->cmd = strdup(cmd)) == NULL) { 384 ecode = e_memory; 385 goto eof; 386 } 387 388 Debug(DPARS, ("load_entry()...returning successfully\n")) 389 390 /* success, fini, return pointer to the entry we just created... 391 */ 392 return (e); 393 394 eof: 395 if (e) 396 free_entry(e); 397 while (ch != '\n' && !feof(file)) 398 ch = get_char(file); 399 if (ecode != e_none && error_func) 400 (*error_func)(ecodes[(int)ecode]); 401 return (NULL); 402 } 403 404 static int 405 get_list(bitstr_t *bits, int low, int high, const char *names[], 406 int ch, FILE *file) 407 { 408 int done; 409 410 /* we know that we point to a non-blank character here; 411 * must do a Skip_Blanks before we exit, so that the 412 * next call (or the code that picks up the cmd) can 413 * assume the same thing. 414 */ 415 416 Debug(DPARS|DEXT, ("get_list()...entered\n")) 417 418 /* list = range {"," range} 419 */ 420 421 /* clear the bit string, since the default is 'off'. 422 */ 423 bit_nclear(bits, 0, (high-low+1)); 424 425 /* process all ranges 426 */ 427 done = FALSE; 428 while (!done) { 429 if (EOF == (ch = get_range(bits, low, high, names, ch, file))) 430 return (EOF); 431 if (ch == ',') 432 ch = get_char(file); 433 else 434 done = TRUE; 435 } 436 437 /* exiting. skip to some blanks, then skip over the blanks. 438 */ 439 Skip_Nonblanks(ch, file) 440 Skip_Blanks(ch, file) 441 442 Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) 443 444 return (ch); 445 } 446 447 448 static int 449 get_range(bitstr_t *bits, int low, int high, const char *names[], 450 int ch, FILE *file) 451 { 452 /* range = number | number "-" number [ "/" number ] 453 */ 454 455 int i, num1, num2, num3; 456 457 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) 458 459 if (ch == '*') { 460 /* '*' means "first-last" but can still be modified by /step 461 */ 462 num1 = low; 463 num2 = high; 464 ch = get_char(file); 465 if (ch == EOF) 466 return (EOF); 467 } else { 468 ch = get_number(&num1, low, names, ch, file, ",- \t\n"); 469 if (ch == EOF) 470 return (EOF); 471 472 if (ch != '-') { 473 /* not a range, it's a single number. 474 */ 475 if (EOF == set_element(bits, low, high, num1)) { 476 unget_char(ch, file); 477 return (EOF); 478 } 479 return (ch); 480 } else { 481 /* eat the dash 482 */ 483 ch = get_char(file); 484 if (ch == EOF) 485 return (EOF); 486 487 /* get the number following the dash 488 */ 489 ch = get_number(&num2, low, names, ch, file, "/, \t\n"); 490 if (ch == EOF || num1 > num2) 491 return (EOF); 492 } 493 } 494 495 /* check for step size 496 */ 497 if (ch == '/') { 498 /* eat the slash 499 */ 500 ch = get_char(file); 501 if (ch == EOF) 502 return (EOF); 503 504 /* get the step size -- note: we don't pass the 505 * names here, because the number is not an 506 * element id, it's a step size. 'low' is 507 * sent as a 0 since there is no offset either. 508 */ 509 ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n"); 510 if (ch == EOF || num3 == 0) 511 return (EOF); 512 } else { 513 /* no step. default==1. 514 */ 515 num3 = 1; 516 } 517 518 /* range. set all elements from num1 to num2, stepping 519 * by num3. (the step is a downward-compatible extension 520 * proposed conceptually by bob@acornrc, syntactically 521 * designed then implemented by paul vixie). 522 */ 523 for (i = num1; i <= num2; i += num3) 524 if (EOF == set_element(bits, low, high, i)) { 525 unget_char(ch, file); 526 return (EOF); 527 } 528 529 return (ch); 530 } 531 532 static int 533 get_number(int *numptr, int low, const char *names[], int ch, FILE *file, 534 const char *terms) { 535 char temp[MAX_TEMPSTR], *pc; 536 int len, i; 537 538 pc = temp; 539 len = 0; 540 541 /* first look for a number */ 542 while (isdigit((unsigned char)ch)) { 543 if (++len >= MAX_TEMPSTR) 544 goto bad; 545 *pc++ = ch; 546 ch = get_char(file); 547 } 548 *pc = '\0'; 549 if (len != 0) { 550 /* got a number, check for valid terminator */ 551 if (!strchr(terms, ch)) 552 goto bad; 553 *numptr = atoi(temp); 554 return (ch); 555 } 556 557 /* no numbers, look for a string if we have any */ 558 if (names) { 559 while (isalpha((unsigned char)ch)) { 560 if (++len >= MAX_TEMPSTR) 561 goto bad; 562 *pc++ = ch; 563 ch = get_char(file); 564 } 565 *pc = '\0'; 566 if (len != 0 && strchr(terms, ch)) { 567 for (i = 0; names[i] != NULL; i++) { 568 Debug(DPARS|DEXT, 569 ("get_num, compare(%s,%s)\n", names[i], 570 temp)) 571 if (!strcasecmp(names[i], temp)) { 572 *numptr = i+low; 573 return (ch); 574 } 575 } 576 } 577 } 578 579 bad: 580 unget_char(ch, file); 581 return (EOF); 582 } 583 584 static int 585 set_element(bitstr_t *bits, int low, int high, int number) { 586 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) 587 588 if (number < low || number > high) 589 return (EOF); 590 591 bit_set(bits, (number-low)); 592 return (OK); 593 } 594