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