1 /* $OpenBSD: entry.c,v 1.32 2009/10/29 18:56:47 markus 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 env_free(e->envp); 63 free(e); 64 } 65 66 /* return NULL if eof or syntax error occurs; 67 * otherwise return a pointer to a new entry. 68 */ 69 entry * 70 load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, 71 char **envp) { 72 /* this function reads one crontab entry -- the next -- from a file. 73 * it skips any leading blank lines, ignores comments, and returns 74 * NULL if for any reason the entry can't be read and parsed. 75 * 76 * the entry is also parsed here. 77 * 78 * syntax: 79 * user crontab: 80 * minutes hours doms months dows cmd\n 81 * system crontab (/etc/crontab): 82 * minutes hours doms months dows USERNAME cmd\n 83 */ 84 85 ecode_e ecode = e_none; 86 entry *e; 87 int ch; 88 char cmd[MAX_COMMAND]; 89 char envstr[MAX_ENVSTR]; 90 char **tenvp; 91 92 Debug(DPARS, ("load_entry()...about to eat comments\n")) 93 94 skip_comments(file); 95 96 ch = get_char(file); 97 if (ch == EOF) 98 return (NULL); 99 100 /* ch is now the first useful character of a useful line. 101 * it may be an @special or it may be the first character 102 * of a list of minutes. 103 */ 104 105 e = (entry *) calloc(sizeof(entry), sizeof(char)); 106 107 if (ch == '@') { 108 /* all of these should be flagged and load-limited; i.e., 109 * instead of @hourly meaning "0 * * * *" it should mean 110 * "close to the front of every hour but not 'til the 111 * system load is low". Problems are: how do you know 112 * what "low" means? (save me from /etc/cron.conf!) and: 113 * how to guarantee low variance (how low is low?), which 114 * means how to we run roughly every hour -- seems like 115 * we need to keep a history or let the first hour set 116 * the schedule, which means we aren't load-limited 117 * anymore. too much for my overloaded brain. (vix, jan90) 118 * HINT 119 */ 120 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 121 if (!strcmp("reboot", cmd)) { 122 e->flags |= WHEN_REBOOT; 123 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 124 bit_set(e->minute, 0); 125 bit_set(e->hour, 0); 126 bit_set(e->dom, 0); 127 bit_set(e->month, 0); 128 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 129 e->flags |= DOW_STAR; 130 } else if (!strcmp("monthly", cmd)) { 131 bit_set(e->minute, 0); 132 bit_set(e->hour, 0); 133 bit_set(e->dom, 0); 134 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 135 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 136 e->flags |= DOW_STAR; 137 } else if (!strcmp("weekly", cmd)) { 138 bit_set(e->minute, 0); 139 bit_set(e->hour, 0); 140 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 141 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 142 bit_set(e->dow, 0); 143 e->flags |= DOW_STAR; 144 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 145 bit_set(e->minute, 0); 146 bit_set(e->hour, 0); 147 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 148 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 149 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 150 } else if (!strcmp("hourly", cmd)) { 151 bit_set(e->minute, 0); 152 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 153 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 154 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 155 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 156 e->flags |= HR_STAR; 157 } else { 158 ecode = e_timespec; 159 goto eof; 160 } 161 /* Advance past whitespace between shortcut and 162 * username/command. 163 */ 164 Skip_Blanks(ch, file); 165 if (ch == EOF || ch == '\n') { 166 ecode = e_cmd; 167 goto eof; 168 } 169 } else { 170 Debug(DPARS, ("load_entry()...about to parse numerics\n")) 171 172 if (ch == '*') 173 e->flags |= MIN_STAR; 174 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 175 PPC_NULL, ch, file); 176 if (ch == EOF) { 177 ecode = e_minute; 178 goto eof; 179 } 180 181 /* hours 182 */ 183 184 if (ch == '*') 185 e->flags |= HR_STAR; 186 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 187 PPC_NULL, ch, file); 188 if (ch == EOF) { 189 ecode = e_hour; 190 goto eof; 191 } 192 193 /* DOM (days of month) 194 */ 195 196 if (ch == '*') 197 e->flags |= DOM_STAR; 198 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 199 PPC_NULL, ch, file); 200 if (ch == EOF) { 201 ecode = e_dom; 202 goto eof; 203 } 204 205 /* month 206 */ 207 208 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 209 MonthNames, ch, file); 210 if (ch == EOF) { 211 ecode = e_month; 212 goto eof; 213 } 214 215 /* DOW (days of week) 216 */ 217 218 if (ch == '*') 219 e->flags |= DOW_STAR; 220 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 221 DowNames, ch, file); 222 if (ch == EOF) { 223 ecode = e_dow; 224 goto eof; 225 } 226 } 227 228 /* make sundays equivalent */ 229 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 230 bit_set(e->dow, 0); 231 bit_set(e->dow, 7); 232 } 233 234 /* check for permature EOL and catch a common typo */ 235 if (ch == '\n' || ch == '*') { 236 ecode = e_cmd; 237 goto eof; 238 } 239 240 /* ch is the first character of a command, or a username */ 241 unget_char(ch, file); 242 243 if (!pw) { 244 char *username = cmd; /* temp buffer */ 245 246 Debug(DPARS, ("load_entry()...about to parse username\n")) 247 ch = get_string(username, MAX_COMMAND, file, " \t\n"); 248 249 Debug(DPARS, ("load_entry()...got %s\n",username)) 250 if (ch == EOF || ch == '\n' || ch == '*') { 251 ecode = e_cmd; 252 goto eof; 253 } 254 255 pw = getpwnam(username); 256 if (pw == NULL) { 257 ecode = e_username; 258 goto eof; 259 } 260 Debug(DPARS, ("load_entry()...uid %lu, gid %lu\n", 261 (unsigned long)pw->pw_uid, 262 (unsigned long)pw->pw_gid)) 263 } 264 265 if ((e->pwd = pw_dup(pw)) == NULL) { 266 ecode = e_memory; 267 goto eof; 268 } 269 bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd)); 270 271 /* copy and fix up environment. some variables are just defaults and 272 * others are overrides. 273 */ 274 if ((e->envp = env_copy(envp)) == NULL) { 275 ecode = e_memory; 276 goto eof; 277 } 278 if (!env_get("SHELL", e->envp)) { 279 if (snprintf(envstr, sizeof envstr, "SHELL=%s", _PATH_BSHELL) >= 280 sizeof(envstr)) 281 log_it("CRON", getpid(), "error", "can't set SHELL"); 282 else { 283 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 284 ecode = e_memory; 285 goto eof; 286 } 287 e->envp = tenvp; 288 } 289 } 290 if (!env_get("HOME", e->envp)) { 291 if (snprintf(envstr, sizeof envstr, "HOME=%s", pw->pw_dir) >= 292 sizeof(envstr)) 293 log_it("CRON", getpid(), "error", "can't set HOME"); 294 else { 295 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 296 ecode = e_memory; 297 goto eof; 298 } 299 e->envp = tenvp; 300 } 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 (snprintf(envstr, sizeof envstr, "PATH=%s", _PATH_DEFPATH) >= 306 sizeof(envstr)) 307 log_it("CRON", getpid(), "error", "can't set PATH"); 308 else { 309 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 310 ecode = e_memory; 311 goto eof; 312 } 313 e->envp = tenvp; 314 } 315 } 316 #endif /* LOGIN_CAP */ 317 if (snprintf(envstr, sizeof envstr, "LOGNAME=%s", pw->pw_name) >= 318 sizeof(envstr)) 319 log_it("CRON", getpid(), "error", "can't set LOGNAME"); 320 else { 321 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 322 ecode = e_memory; 323 goto eof; 324 } 325 e->envp = tenvp; 326 } 327 #if defined(BSD) || defined(__linux) 328 if (snprintf(envstr, sizeof envstr, "USER=%s", pw->pw_name) >= 329 sizeof(envstr)) 330 log_it("CRON", getpid(), "error", "can't set USER"); 331 else { 332 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 333 ecode = e_memory; 334 goto eof; 335 } 336 e->envp = tenvp; 337 } 338 #endif 339 340 Debug(DPARS, ("load_entry()...about to parse command\n")) 341 342 /* If the first character of the command is '-' it is a cron option. 343 */ 344 ch = get_char(file); 345 while (ch == '-') { 346 switch (ch = get_char(file)) { 347 case 'q': 348 e->flags |= DONT_LOG; 349 Skip_Nonblanks(ch, file) 350 break; 351 default: 352 ecode = e_option; 353 goto eof; 354 } 355 Skip_Blanks(ch, file) 356 if (ch == EOF || ch == '\n') { 357 ecode = e_cmd; 358 goto eof; 359 } 360 } 361 unget_char(ch, file); 362 363 /* Everything up to the next \n or EOF is part of the command... 364 * too bad we don't know in advance how long it will be, since we 365 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 366 */ 367 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 368 369 /* a file without a \n before the EOF is rude, so we'll complain... 370 */ 371 if (ch == EOF) { 372 ecode = e_cmd; 373 goto eof; 374 } 375 376 /* got the command in the 'cmd' string; save it in *e. 377 */ 378 if ((e->cmd = strdup(cmd)) == NULL) { 379 ecode = e_memory; 380 goto eof; 381 } 382 383 Debug(DPARS, ("load_entry()...returning successfully\n")) 384 385 /* success, fini, return pointer to the entry we just created... 386 */ 387 return (e); 388 389 eof: 390 if (e->envp) 391 env_free(e->envp); 392 if (e->pwd) 393 free(e->pwd); 394 if (e->cmd) 395 free(e->cmd); 396 free(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