1/* $NetBSD: entry.c,v 1.3 2010/07/10 21:42:29 christos 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#include <sys/cdefs.h> 25#if !defined(lint) && !defined(LINT) 26#if 0 27static char rcsid[] = "Id: entry.c,v 1.17 2004/01/23 18:56:42 vixie Exp"; 28#else 29__RCSID("$NetBSD: entry.c,v 1.3 2010/07/10 21:42:29 christos Exp $"); 30#endif 31#endif 32 33/* vix 26jan87 [RCS'd; rest of log is in RCS file] 34 * vix 01jan87 [added line-level error recovery] 35 * vix 31dec86 [added /step to the from-to range, per bob@acornrc] 36 * vix 30dec86 [written] 37 */ 38 39#include "cron.h" 40 41typedef enum ecode { 42 e_none, e_minute, e_hour, e_dom, e_month, e_dow, 43 e_cmd, e_timespec, e_username, e_option, e_memory 44} ecode_e; 45 46static const char * const ecodes[] = 47 { 48 "no error", 49 "bad minute", 50 "bad hour", 51 "bad day-of-month", 52 "bad month", 53 "bad day-of-week", 54 "bad command", 55 "bad time specifier", 56 "bad username", 57 "bad option", 58 "out of memory" 59 }; 60 61static int get_list(bitstr_t *, int, int, const char * const [], int, FILE *), 62 get_range(bitstr_t *, int, int, const char * const [], int, FILE *), 63 get_number(int *, int, const char * const [], int, FILE *, const char *), 64 set_element(bitstr_t *, int, int, int); 65 66void 67free_entry(entry *e) { 68 free(e->cmd); 69 free(e->pwd); 70 env_free(e->envp); 71 free(e); 72} 73 74/* return NULL if eof or syntax error occurs; 75 * otherwise return a pointer to a new entry. 76 */ 77entry * 78load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw, 79 char **envp) { 80 /* this function reads one crontab entry -- the next -- from a file. 81 * it skips any leading blank lines, ignores comments, and returns 82 * NULL if for any reason the entry can't be read and parsed. 83 * 84 * the entry is also parsed here. 85 * 86 * syntax: 87 * user crontab: 88 * minutes hours doms months dows cmd\n 89 * system crontab (/etc/crontab): 90 * minutes hours doms months dows USERNAME cmd\n 91 */ 92 93 ecode_e ecode = e_none; 94 entry *e; 95 int ch; 96 char cmd[MAX_COMMAND]; 97 char envstr[MAX_ENVSTR]; 98 char **tenvp; 99 100 Debug(DPARS, ("load_entry()...about to eat comments\n")); 101 102 skip_comments(file); 103 104 ch = get_char(file); 105 if (ch == EOF) 106 return (NULL); 107 108 /* ch is now the first useful character of a useful line. 109 * it may be an @special or it may be the first character 110 * of a list of minutes. 111 */ 112 113 e = calloc(sizeof(*e), sizeof(char)); 114 115 if (ch == '@') { 116 /* all of these should be flagged and load-limited; i.e., 117 * instead of @hourly meaning "0 * * * *" it should mean 118 * "close to the front of every hour but not 'til the 119 * system load is low". Problems are: how do you know 120 * what "low" means? (save me from /etc/cron.conf!) and: 121 * how to guarantee low variance (how low is low?), which 122 * means how to we run roughly every hour -- seems like 123 * we need to keep a history or let the first hour set 124 * the schedule, which means we aren't load-limited 125 * anymore. too much for my overloaded brain. (vix, jan90) 126 * HINT 127 */ 128 ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); 129 if (!strcmp("reboot", cmd)) { 130 e->flags |= WHEN_REBOOT; 131 } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ 132 bit_set(e->minute, 0); 133 bit_set(e->hour, 0); 134 bit_set(e->dom, 0); 135 bit_set(e->month, 0); 136 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 137 e->flags |= DOW_STAR; 138 } else if (!strcmp("monthly", cmd)) { 139 bit_set(e->minute, 0); 140 bit_set(e->hour, 0); 141 bit_set(e->dom, 0); 142 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 143 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 144 e->flags |= DOW_STAR; 145 } else if (!strcmp("weekly", cmd)) { 146 bit_set(e->minute, 0); 147 bit_set(e->hour, 0); 148 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 149 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 150 bit_set(e->dow, 0); 151 e->flags |= DOM_STAR; 152 } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { 153 bit_set(e->minute, 0); 154 bit_set(e->hour, 0); 155 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 156 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 157 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 158 e->flags |= DOM_STAR | DOW_STAR; 159 } else if (!strcmp("hourly", cmd)) { 160 bit_set(e->minute, 0); 161 bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1)); 162 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 163 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 164 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 165 e->flags |= DOM_STAR | DOW_STAR; 166 } else { 167 ecode = e_timespec; 168 goto eof; 169 } 170 /* Advance past whitespace between shortcut and 171 * username/command. 172 */ 173 Skip_Blanks(ch, file); 174 if (ch == EOF || ch == '\n') { 175 ecode = e_cmd; 176 goto eof; 177 } 178 } else { 179 Debug(DPARS, ("load_entry()...about to parse numerics\n")); 180 181 if (ch == '*') 182 e->flags |= MIN_STAR; 183 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 184 PPC_NULL, ch, file); 185 if (ch == EOF) { 186 ecode = e_minute; 187 goto eof; 188 } 189 190 /* hours 191 */ 192 193 if (ch == '*') 194 e->flags |= HR_STAR; 195 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 196 PPC_NULL, ch, file); 197 if (ch == EOF) { 198 ecode = e_hour; 199 goto eof; 200 } 201 202 /* DOM (days of month) 203 */ 204 205 if (ch == '*') 206 e->flags |= DOM_STAR; 207 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 208 PPC_NULL, ch, file); 209 if (ch == EOF) { 210 ecode = e_dom; 211 goto eof; 212 } 213 214 /* month 215 */ 216 217 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 218 MonthNames, ch, file); 219 if (ch == EOF) { 220 ecode = e_month; 221 goto eof; 222 } 223 224 /* DOW (days of week) 225 */ 226 227 if (ch == '*') 228 e->flags |= DOW_STAR; 229 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 230 DowNames, ch, file); 231 if (ch == EOF) { 232 ecode = e_dow; 233 goto eof; 234 } 235 } 236 237 /* make sundays equivalent */ 238 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 239 bit_set(e->dow, 0); 240 bit_set(e->dow, 7); 241 } 242 243 /* check for permature EOL and catch a common typo */ 244 if (ch == '\n' || ch == '*') { 245 ecode = e_cmd; 246 goto eof; 247 } 248 249 /* ch is the first character of a command, or a username */ 250 unget_char(ch, file); 251 252 if (!pw) { 253 char *username = cmd; /* temp buffer */ 254 255 Debug(DPARS, ("load_entry()...about to parse username\n")); 256 ch = get_string(username, MAX_COMMAND, file, " \t\n"); 257 258 Debug(DPARS, ("load_entry()...got %s\n",username)); 259 if (ch == EOF || ch == '\n' || ch == '*') { 260 ecode = e_cmd; 261 goto eof; 262 } 263 264 pw = getpwnam(username); 265 if (pw == NULL) { 266 ecode = e_username; 267 goto eof; 268 } 269 Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", 270 (long)pw->pw_uid, (long)pw->pw_gid)); 271 } 272 273 if ((e->pwd = pw_dup(pw)) == NULL) { 274 ecode = e_memory; 275 goto eof; 276 } 277 (void)memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd)); 278 279 /* copy and fix up environment. some variables are just defaults and 280 * others are overrides. 281 */ 282 if ((e->envp = env_copy(envp)) == NULL) { 283 ecode = e_memory; 284 goto eof; 285 } 286 if (!env_get("SHELL", e->envp)) { 287 if (glue_strings(envstr, sizeof envstr, "SHELL", 288 _PATH_BSHELL, '=')) { 289 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 290 ecode = e_memory; 291 goto eof; 292 } 293 e->envp = tenvp; 294 } else 295 log_it("CRON", getpid(), "error", "can't set SHELL"); 296 } 297 if (!env_get("HOME", e->envp)) { 298 if (glue_strings(envstr, sizeof envstr, "HOME", 299 pw->pw_dir, '=')) { 300 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 301 ecode = e_memory; 302 goto eof; 303 } 304 e->envp = tenvp; 305 } else 306 log_it("CRON", getpid(), "error", "can't set HOME"); 307 } 308 /* If login.conf is in used we will get the default PATH later. */ 309 if (!env_get("PATH", e->envp)) { 310 if (glue_strings(envstr, sizeof envstr, "PATH", 311 _PATH_DEFPATH, '=')) { 312 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 313 ecode = e_memory; 314 goto eof; 315 } 316 e->envp = tenvp; 317 } else 318 log_it("CRON", getpid(), "error", "can't set PATH"); 319 } 320 if (glue_strings(envstr, sizeof envstr, "LOGNAME", 321 pw->pw_name, '=')) { 322 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 323 ecode = e_memory; 324 goto eof; 325 } 326 e->envp = tenvp; 327 } else 328 log_it("CRON", getpid(), "error", "can't set LOGNAME"); 329#if defined(BSD) || defined(__linux) 330 if (glue_strings(envstr, sizeof envstr, "USER", 331 pw->pw_name, '=')) { 332 if ((tenvp = env_set(e->envp, envstr)) == NULL) { 333 ecode = e_memory; 334 goto eof; 335 } 336 e->envp = tenvp; 337 } else 338 log_it("CRON", getpid(), "error", "can't set USER"); 339#endif 340 341 Debug(DPARS, ("load_entry()...about to parse command\n")); 342 343 /* If the first character of the command is '-' it is a cron option. 344 */ 345 while ((ch = get_char(file)) == '-') { 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 404static int 405get_list(bitstr_t *bits, int low, int high, const char * const 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 448static int 449random_with_range(int low, int high) 450{ 451 /* Kind of crappy error detection, but... 452 */ 453 if (low >= high) 454 return low; 455 else 456 return arc4random() % (high - low + 1) + low; 457} 458 459static int 460get_range(bitstr_t *bits, int low, int high, const char * const names[], 461 int ch, FILE *file) 462{ 463 /* range = number | number "-" number [ "/" number ] 464 */ 465 466 int i, num1, num2, num3; 467 int qmark, star; 468 469 qmark = star = FALSE; 470 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")); 471 472 if (ch == '*') { 473 /* '*' means "first-last" but can still be modified by /step 474 */ 475 star = TRUE; 476 num1 = low; 477 num2 = high; 478 ch = get_char(file); 479 if (ch == EOF) 480 return (EOF); 481 } else if (ch == '?') { 482 } else if (ch == '?') { 483 qmark = TRUE; 484 ch = get_char(file); 485 if (ch == EOF) 486 return EOF; 487 if (!isdigit(ch)) { 488 num1 = random_with_range(low, high); 489 if (EOF == set_element(bits, low, high, num1)) 490 return EOF; 491 return ch; 492 } 493 } 494 495 if (!star) { 496 ch = get_number(&num1, low, names, ch, file, ",- \t\n"); 497 if (ch == EOF) 498 return (EOF); 499 500 if (ch != '-') { 501 /* not a range, it's a single number. 502 * a single number after '?' is bogus. 503 */ 504 if (qmark) 505 return EOF; 506 if (EOF == set_element(bits, low, high, num1)) { 507 unget_char(ch, file); 508 return (EOF); 509 } 510 return (ch); 511 } else { 512 /* eat the dash 513 */ 514 ch = get_char(file); 515 if (ch == EOF) 516 return (EOF); 517 518 /* get the number following the dash 519 */ 520 ch = get_number(&num2, low, names, ch, file, "/, \t\n"); 521 if (ch == EOF || num1 > num2) 522 return (EOF); 523 524 /* if we have a random range, it is really 525 * like having a single number. 526 */ 527 if (qmark) { 528 if (num1 > num2) 529 return EOF; 530 num1 = random_with_range(num1, num2); 531 if (EOF == set_element(bits, low, high, num1)) 532 return EOF; 533 return ch; 534 } 535 } 536 } 537 538 /* check for step size 539 */ 540 if (ch == '/') { 541 /* '?' is incompatible with '/' 542 */ 543 if (qmark) 544 return EOF; 545 /* eat the slash 546 */ 547 ch = get_char(file); 548 if (ch == EOF) 549 return (EOF); 550 551 /* get the step size -- note: we don't pass the 552 * names here, because the number is not an 553 * element id, it's a step size. 'low' is 554 * sent as a 0 since there is no offset either. 555 */ 556 ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n"); 557 if (ch == EOF || num3 == 0) 558 return (EOF); 559 } else { 560 /* no step. default==1. 561 */ 562 num3 = 1; 563 } 564 565 /* range. set all elements from num1 to num2, stepping 566 * by num3. (the step is a downward-compatible extension 567 * proposed conceptually by bob@acornrc, syntactically 568 * designed then implemented by paul vixie). 569 */ 570 for (i = num1; i <= num2; i += num3) 571 if (EOF == set_element(bits, low, high, i)) { 572 unget_char(ch, file); 573 return (EOF); 574 } 575 576 return (ch); 577} 578 579static int 580get_number(int *numptr, int low, const char * const names[], int ch, FILE *file, 581 const char *terms) { 582 char temp[MAX_TEMPSTR], *pc; 583 int len, i; 584 585 pc = temp; 586 len = 0; 587 588 /* first look for a number */ 589 while (isdigit((unsigned char)ch)) { 590 if (++len >= MAX_TEMPSTR) 591 goto bad; 592 *pc++ = ch; 593 ch = get_char(file); 594 } 595 *pc = '\0'; 596 if (len != 0) { 597 /* got a number, check for valid terminator */ 598 if (!strchr(terms, ch)) 599 goto bad; 600 *numptr = atoi(temp); 601 return (ch); 602 } 603 604 /* no numbers, look for a string if we have any */ 605 if (names) { 606 while (isalpha((unsigned char)ch)) { 607 if (++len >= MAX_TEMPSTR) 608 goto bad; 609 *pc++ = ch; 610 ch = get_char(file); 611 } 612 *pc = '\0'; 613 if (len != 0 && strchr(terms, ch)) { 614 for (i = 0; names[i] != NULL; i++) { 615 Debug(DPARS|DEXT, 616 ("get_num, compare(%s,%s)\n", names[i], 617 temp)); 618 if (!strcasecmp(names[i], temp)) { 619 *numptr = i+low; 620 return (ch); 621 } 622 } 623 } 624 } 625 626bad: 627 unget_char(ch, file); 628 return (EOF); 629} 630 631static int 632set_element(bitstr_t *bits, int low, int high, int number) { 633 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)); 634 635 if (number < low || number > high) 636 return (EOF); 637 638 bit_set(bits, (number-low)); 639 return (OK); 640} 641