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