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