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