1 /* Copyright 1988,1990,1993,1994 by Paul Vixie 2 * All rights reserved 3 * 4 * Distribute freely, except: don't remove my name from the source or 5 * documentation (don't take credit for my work), mark your changes (don't 6 * get me blamed for your possible bugs), don't alter or remove this 7 * notice. May be sold if buildable source is provided to buyer. No 8 * warrantee of any kind, express or implied, is included with this 9 * software; use at your own risk, responsibility for damages (if any) to 10 * anyone resulting from the use of this software rests entirely with the 11 * user. 12 * 13 * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14 * I'll try to keep a version up to date. I can be reached as follows: 15 * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16 */ 17 18 #if !defined(lint) && !defined(LINT) 19 static const char rcsid[] = 20 "$Id: entry.c,v 1.6 1997/02/22 16:05:06 peter Exp $"; 21 #endif 22 23 /* vix 26jan87 [RCS'd; rest of log is in RCS file] 24 * vix 01jan87 [added line-level error recovery] 25 * vix 31dec86 [added /step to the from-to range, per bob@acornrc] 26 * vix 30dec86 [written] 27 */ 28 29 30 #include "cron.h" 31 32 33 typedef enum ecode { 34 e_none, e_minute, e_hour, e_dom, e_month, e_dow, 35 e_cmd, e_timespec, e_username 36 } ecode_e; 37 38 static char get_list __P((bitstr_t *, int, int, char *[], int, FILE *)), 39 get_range __P((bitstr_t *, int, int, char *[], int, FILE *)), 40 get_number __P((int *, int, char *[], int, FILE *)); 41 static int set_element __P((bitstr_t *, int, int, int)); 42 43 static char *ecodes[] = 44 { 45 "no error", 46 "bad minute", 47 "bad hour", 48 "bad day-of-month", 49 "bad month", 50 "bad day-of-week", 51 "bad command", 52 "bad time specifier", 53 "bad username", 54 }; 55 56 57 void 58 free_entry(e) 59 entry *e; 60 { 61 free(e->cmd); 62 env_free(e->envp); 63 free(e); 64 } 65 66 67 /* return NULL if eof or syntax error occurs; 68 * otherwise return a pointer to a new entry. 69 */ 70 entry * 71 load_entry(file, error_func, pw, envp) 72 FILE *file; 73 void (*error_func)(); 74 struct passwd *pw; 75 char **envp; 76 { 77 /* this function reads one crontab entry -- the next -- from a file. 78 * it skips any leading blank lines, ignores comments, and returns 79 * EOF if for any reason the entry can't be read and parsed. 80 * 81 * the entry is also parsed here. 82 * 83 * syntax: 84 * user crontab: 85 * minutes hours doms months dows cmd\n 86 * system crontab (/etc/crontab): 87 * minutes hours doms months dows USERNAME cmd\n 88 */ 89 90 ecode_e ecode = e_none; 91 entry *e; 92 int ch; 93 char cmd[MAX_COMMAND]; 94 char envstr[MAX_ENVSTR]; 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 } else if (!strcmp("monthly", cmd)) { 134 bit_set(e->minute, 0); 135 bit_set(e->hour, 0); 136 bit_set(e->dom, 0); 137 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 138 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 139 } else if (!strcmp("weekly", cmd)) { 140 bit_set(e->minute, 0); 141 bit_set(e->hour, 0); 142 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 143 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 144 bit_set(e->dow, 0); 145 } else if (!strcmp("daily", cmd) || !strcmp("midnight", 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_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 151 } else if (!strcmp("hourly", cmd)) { 152 bit_set(e->minute, 0); 153 bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1)); 154 bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); 155 bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); 156 bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); 157 } else { 158 ecode = e_timespec; 159 goto eof; 160 } 161 } else { 162 Debug(DPARS, ("load_entry()...about to parse numerics\n")) 163 164 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, 165 PPC_NULL, ch, file); 166 if (ch == EOF) { 167 ecode = e_minute; 168 goto eof; 169 } 170 171 /* hours 172 */ 173 174 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, 175 PPC_NULL, ch, file); 176 if (ch == EOF) { 177 ecode = e_hour; 178 goto eof; 179 } 180 181 /* DOM (days of month) 182 */ 183 184 if (ch == '*') 185 e->flags |= DOM_STAR; 186 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, 187 PPC_NULL, ch, file); 188 if (ch == EOF) { 189 ecode = e_dom; 190 goto eof; 191 } 192 193 /* month 194 */ 195 196 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, 197 MonthNames, ch, file); 198 if (ch == EOF) { 199 ecode = e_month; 200 goto eof; 201 } 202 203 /* DOW (days of week) 204 */ 205 206 if (ch == '*') 207 e->flags |= DOW_STAR; 208 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, 209 DowNames, ch, file); 210 if (ch == EOF) { 211 ecode = e_dow; 212 goto eof; 213 } 214 } 215 216 /* make sundays equivilent */ 217 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { 218 bit_set(e->dow, 0); 219 bit_set(e->dow, 7); 220 } 221 222 /* ch is the first character of a command, or a username */ 223 unget_char(ch, file); 224 225 if (!pw) { 226 char *username = cmd; /* temp buffer */ 227 228 Debug(DPARS, ("load_entry()...about to parse username\n")) 229 ch = get_string(username, MAX_COMMAND, file, " \t"); 230 231 Debug(DPARS, ("load_entry()...got %s\n",username)) 232 if (ch == EOF) { 233 ecode = e_cmd; 234 goto eof; 235 } 236 237 pw = getpwnam(username); 238 if (pw == NULL) { 239 ecode = e_username; 240 goto eof; 241 } 242 Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid)) 243 } 244 245 if (pw->pw_expire && time(NULL) >= pw->pw_expire) { 246 ecode = e_username; 247 goto eof; 248 } 249 250 e->uid = pw->pw_uid; 251 e->gid = pw->pw_gid; 252 253 /* copy and fix up environment. some variables are just defaults and 254 * others are overrides. 255 */ 256 e->envp = env_copy(envp); 257 if (!env_get("SHELL", e->envp)) { 258 sprintf(envstr, "SHELL=%s", _PATH_BSHELL); 259 e->envp = env_set(e->envp, envstr); 260 } 261 sprintf(envstr, "HOME=%s", pw->pw_dir); 262 e->envp = env_set(e->envp, envstr); 263 if (!env_get("PATH", e->envp)) { 264 sprintf(envstr, "PATH=%s", _PATH_DEFPATH); 265 e->envp = env_set(e->envp, envstr); 266 } 267 sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name); 268 e->envp = env_set(e->envp, envstr); 269 #if defined(BSD) 270 sprintf(envstr, "%s=%s", "USER", pw->pw_name); 271 e->envp = env_set(e->envp, envstr); 272 #endif 273 274 Debug(DPARS, ("load_entry()...about to parse command\n")) 275 276 /* Everything up to the next \n or EOF is part of the command... 277 * too bad we don't know in advance how long it will be, since we 278 * need to malloc a string for it... so, we limit it to MAX_COMMAND. 279 * XXX - should use realloc(). 280 */ 281 ch = get_string(cmd, MAX_COMMAND, file, "\n"); 282 283 /* a file without a \n before the EOF is rude, so we'll complain... 284 */ 285 if (ch == EOF) { 286 ecode = e_cmd; 287 goto eof; 288 } 289 290 /* got the command in the 'cmd' string; save it in *e. 291 */ 292 e->cmd = strdup(cmd); 293 294 Debug(DPARS, ("load_entry()...returning successfully\n")) 295 296 /* success, fini, return pointer to the entry we just created... 297 */ 298 return e; 299 300 eof: 301 free(e); 302 if (ecode != e_none && error_func) 303 (*error_func)(ecodes[(int)ecode]); 304 while (ch != EOF && ch != '\n') 305 ch = get_char(file); 306 return NULL; 307 } 308 309 310 static char 311 get_list(bits, low, high, names, ch, file) 312 bitstr_t *bits; /* one bit per flag, default=FALSE */ 313 int low, high; /* bounds, impl. offset for bitstr */ 314 char *names[]; /* NULL or *[] of names for these elements */ 315 int ch; /* current character being processed */ 316 FILE *file; /* file being read */ 317 { 318 register int done; 319 320 /* we know that we point to a non-blank character here; 321 * must do a Skip_Blanks before we exit, so that the 322 * next call (or the code that picks up the cmd) can 323 * assume the same thing. 324 */ 325 326 Debug(DPARS|DEXT, ("get_list()...entered\n")) 327 328 /* list = range {"," range} 329 */ 330 331 /* clear the bit string, since the default is 'off'. 332 */ 333 bit_nclear(bits, 0, (high-low+1)); 334 335 /* process all ranges 336 */ 337 done = FALSE; 338 while (!done) { 339 ch = get_range(bits, low, high, names, ch, file); 340 if (ch == ',') 341 ch = get_char(file); 342 else 343 done = TRUE; 344 } 345 346 /* exiting. skip to some blanks, then skip over the blanks. 347 */ 348 Skip_Nonblanks(ch, file) 349 Skip_Blanks(ch, file) 350 351 Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) 352 353 return ch; 354 } 355 356 357 static char 358 get_range(bits, low, high, names, ch, file) 359 bitstr_t *bits; /* one bit per flag, default=FALSE */ 360 int low, high; /* bounds, impl. offset for bitstr */ 361 char *names[]; /* NULL or names of elements */ 362 int ch; /* current character being processed */ 363 FILE *file; /* file being read */ 364 { 365 /* range = number | number "-" number [ "/" number ] 366 */ 367 368 register int i; 369 auto int num1, num2, num3; 370 371 Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) 372 373 if (ch == '*') { 374 /* '*' means "first-last" but can still be modified by /step 375 */ 376 num1 = low; 377 num2 = high; 378 ch = get_char(file); 379 if (ch == EOF) 380 return EOF; 381 } else { 382 if (EOF == (ch = get_number(&num1, low, names, ch, file))) 383 return EOF; 384 385 if (ch != '-') { 386 /* not a range, it's a single number. 387 */ 388 if (EOF == set_element(bits, low, high, num1)) 389 return EOF; 390 return ch; 391 } else { 392 /* eat the dash 393 */ 394 ch = get_char(file); 395 if (ch == EOF) 396 return EOF; 397 398 /* get the number following the dash 399 */ 400 ch = get_number(&num2, low, names, ch, file); 401 if (ch == EOF) 402 return EOF; 403 } 404 } 405 406 /* check for step size 407 */ 408 if (ch == '/') { 409 /* eat the slash 410 */ 411 ch = get_char(file); 412 if (ch == EOF) 413 return EOF; 414 415 /* get the step size -- note: we don't pass the 416 * names here, because the number is not an 417 * element id, it's a step size. 'low' is 418 * sent as a 0 since there is no offset either. 419 */ 420 ch = get_number(&num3, 0, PPC_NULL, ch, file); 421 if (ch == EOF) 422 return EOF; 423 } else { 424 /* no step. default==1. 425 */ 426 num3 = 1; 427 } 428 429 /* range. set all elements from num1 to num2, stepping 430 * by num3. (the step is a downward-compatible extension 431 * proposed conceptually by bob@acornrc, syntactically 432 * designed then implmented by paul vixie). 433 */ 434 for (i = num1; i <= num2; i += num3) 435 if (EOF == set_element(bits, low, high, i)) 436 return EOF; 437 438 return ch; 439 } 440 441 442 static char 443 get_number(numptr, low, names, ch, file) 444 int *numptr; /* where does the result go? */ 445 int low; /* offset applied to result if symbolic enum used */ 446 char *names[]; /* symbolic names, if any, for enums */ 447 int ch; /* current character */ 448 FILE *file; /* source */ 449 { 450 char temp[MAX_TEMPSTR], *pc; 451 int len, i, all_digits; 452 453 /* collect alphanumerics into our fixed-size temp array 454 */ 455 pc = temp; 456 len = 0; 457 all_digits = TRUE; 458 while (isalnum(ch)) { 459 if (++len >= MAX_TEMPSTR) 460 return EOF; 461 462 *pc++ = ch; 463 464 if (!isdigit(ch)) 465 all_digits = FALSE; 466 467 ch = get_char(file); 468 } 469 *pc = '\0'; 470 471 /* try to find the name in the name list 472 */ 473 if (names) { 474 for (i = 0; names[i] != NULL; i++) { 475 Debug(DPARS|DEXT, 476 ("get_num, compare(%s,%s)\n", names[i], temp)) 477 if (!strcasecmp(names[i], temp)) { 478 *numptr = i+low; 479 return ch; 480 } 481 } 482 } 483 484 /* no name list specified, or there is one and our string isn't 485 * in it. either way: if it's all digits, use its magnitude. 486 * otherwise, it's an error. 487 */ 488 if (all_digits) { 489 *numptr = atoi(temp); 490 return ch; 491 } 492 493 return EOF; 494 } 495 496 497 static int 498 set_element(bits, low, high, number) 499 bitstr_t *bits; /* one bit per flag, default=FALSE */ 500 int low; 501 int high; 502 int number; 503 { 504 Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) 505 506 if (number < low || number > high) 507 return EOF; 508 509 bit_set(bits, (number-low)); 510 return OK; 511 } 512