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