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