1 /* $OpenBSD: entry.c,v 1.48 2015/11/14 13:09:14 millert 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 'q': 342 e->flags |= DONT_LOG; 343 Skip_Nonblanks(ch, file) 344 break; 345 default: 346 ecode = e_option; 347 goto eof; 348 } 349 Skip_Blanks(ch, file) 350 if (ch == EOF || ch == '\n') { 351 ecode = e_cmd; 352 goto eof; 353 } 354 } 355 unget_char(ch, file); 356 357 /* Everything up to the next \n or EOF is part of the command... 358 * too bad we don't know in advance how long it will be, since we 359 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 360 */ 361 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 362 363 /* a file without a \n before the EOF is rude, so we'll complain... 364 */ 365 if (ch == EOF) { 366 ecode = e_cmd; 367 goto eof; 368 } 369 370 /* got the command in the 'cmd' string; save it in *e. 371 */ 372 if ((e->cmd = strdup(cmd)) == NULL) { 373 ecode = e_memory; 374 goto eof; 375 } 376 377 /* success, fini, return pointer to the entry we just created... 378 */ 379 return (e); 380 381 eof: 382 if (e) 383 free_entry(e); 384 while (ch != '\n' && !feof(file)) 385 ch = get_char(file); 386 if (ecode != e_none && error_func) 387 (*error_func)(ecodes[(int)ecode]); 388 return (NULL); 389 } 390 391 static int 392 get_list(bitstr_t *bits, int low, int high, const char *names[], 393 int ch, FILE *file) 394 { 395 int done; 396 397 /* we know that we point to a non-blank character here; 398 * must do a Skip_Blanks before we exit, so that the 399 * next call (or the code that picks up the cmd) can 400 * assume the same thing. 401 */ 402 403 /* list = range {"," range} 404 */ 405 406 /* clear the bit string, since the default is 'off'. 407 */ 408 bit_nclear(bits, 0, (high-low+1)); 409 410 /* process all ranges 411 */ 412 done = FALSE; 413 while (!done) { 414 if (EOF == (ch = get_range(bits, low, high, names, ch, file))) 415 return (EOF); 416 if (ch == ',') 417 ch = get_char(file); 418 else 419 done = TRUE; 420 } 421 422 /* exiting. skip to some blanks, then skip over the blanks. 423 */ 424 Skip_Nonblanks(ch, file) 425 Skip_Blanks(ch, file) 426 427 return (ch); 428 } 429 430 431 static int 432 get_range(bitstr_t *bits, int low, int high, const char *names[], 433 int ch, FILE *file) 434 { 435 /* range = number | number "-" number [ "/" number ] 436 */ 437 438 int i, num1, num2, num3; 439 440 if (ch == '*') { 441 /* '*' means "first-last" but can still be modified by /step 442 */ 443 num1 = low; 444 num2 = high; 445 ch = get_char(file); 446 if (ch == EOF) 447 return (EOF); 448 } else { 449 ch = get_number(&num1, low, names, ch, file, ",- \t\n"); 450 if (ch == EOF) 451 return (EOF); 452 453 if (ch != '-') { 454 /* not a range, it's a single number. 455 */ 456 if (EOF == set_element(bits, low, high, num1)) { 457 unget_char(ch, file); 458 return (EOF); 459 } 460 return (ch); 461 } else { 462 /* eat the dash 463 */ 464 ch = get_char(file); 465 if (ch == EOF) 466 return (EOF); 467 468 /* get the number following the dash 469 */ 470 ch = get_number(&num2, low, names, ch, file, "/, \t\n"); 471 if (ch == EOF || num1 > num2) 472 return (EOF); 473 } 474 } 475 476 /* check for step size 477 */ 478 if (ch == '/') { 479 /* eat the slash 480 */ 481 ch = get_char(file); 482 if (ch == EOF) 483 return (EOF); 484 485 /* get the step size -- note: we don't pass the 486 * names here, because the number is not an 487 * element id, it's a step size. 'low' is 488 * sent as a 0 since there is no offset either. 489 */ 490 ch = get_number(&num3, 0, NULL, ch, file, ", \t\n"); 491 if (ch == EOF || num3 == 0) 492 return (EOF); 493 } else { 494 /* no step. default==1. 495 */ 496 num3 = 1; 497 } 498 499 /* range. set all elements from num1 to num2, stepping 500 * by num3. (the step is a downward-compatible extension 501 * proposed conceptually by bob@acornrc, syntactically 502 * designed then implemented by paul vixie). 503 */ 504 for (i = num1; i <= num2; i += num3) 505 if (EOF == set_element(bits, low, high, i)) { 506 unget_char(ch, file); 507 return (EOF); 508 } 509 510 return (ch); 511 } 512 513 static int 514 get_number(int *numptr, int low, const char *names[], int ch, FILE *file, 515 const char *terms) 516 { 517 char temp[MAX_TEMPSTR], *pc; 518 int len, i; 519 520 pc = temp; 521 len = 0; 522 523 /* first look for a number */ 524 while (isdigit((unsigned char)ch)) { 525 if (++len >= MAX_TEMPSTR) 526 goto bad; 527 *pc++ = ch; 528 ch = get_char(file); 529 } 530 *pc = '\0'; 531 if (len != 0) { 532 /* got a number, check for valid terminator */ 533 if (!strchr(terms, ch)) 534 goto bad; 535 *numptr = atoi(temp); 536 return (ch); 537 } 538 539 /* no numbers, look for a string if we have any */ 540 if (names) { 541 while (isalpha((unsigned char)ch)) { 542 if (++len >= MAX_TEMPSTR) 543 goto bad; 544 *pc++ = ch; 545 ch = get_char(file); 546 } 547 *pc = '\0'; 548 if (len != 0 && strchr(terms, ch)) { 549 for (i = 0; names[i] != NULL; i++) { 550 if (!strcasecmp(names[i], temp)) { 551 *numptr = i+low; 552 return (ch); 553 } 554 } 555 } 556 } 557 558 bad: 559 unget_char(ch, file); 560 return (EOF); 561 } 562 563 static int 564 set_element(bitstr_t *bits, int low, int high, int number) 565 { 566 567 if (number < low || number > high) 568 return (EOF); 569 570 bit_set(bits, (number-low)); 571 return (0); 572 } 573