1 /*
2 * Copyright 1988,1990,1993,1994 by Paul Vixie
3 * All rights reserved
4 */
5
6 /*
7 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
8 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
9 *
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
20 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 */
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 #include "config.h"
30
31 #include <ctype.h>
32 #include <pwd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <unistd.h>
38 #include <errno.h>
39
40 #include "bitstring.h"
41 #include "funcs.h"
42 #include "globals.h"
43 #include "macros.h"
44 #include "pathnames.h"
45
46 typedef enum ecode {
47 e_none, e_minute, e_hour, e_dom, e_month, e_dow,
48 e_cmd, e_timespec, e_username, e_option, e_memory
49 } ecode_e;
50
51 static const char *ecodes[] = {
52 "no error",
53 "bad minute",
54 "bad hour",
55 "bad day-of-month",
56 "bad month",
57 "bad day-of-week",
58 "bad command",
59 "bad time specifier",
60 "bad username",
61 "bad option",
62 "out of memory"
63 };
64
65 static int get_list(bitstr_t *, int, int, const char *[], int, FILE *),
66 get_range(bitstr_t *, int, int, const char *[], int, FILE *),
67 get_number(int *, int, const char *[], int, FILE *, const char *),
68 set_element(bitstr_t *, int, int, int);
69
70 void free_entry(entry * e) {
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
71 free(e->cmd);
72 free(e->pwd);
73 env_free(e->envp);
74 free(e);
75 }
76
77 /* return NULL if eof or syntax error occurs;
78 * otherwise return a pointer to a new entry.
79 */
80 entry *load_entry(FILE * file, void (*error_func) (), struct passwd *pw,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
81 char **envp) {
82 /* this function reads one crontab entry -- the next -- from a file.
83 * it skips any leading blank lines, ignores comments, and returns
84 * NULL if for any reason the entry can't be read and parsed.
85 *
86 * the entry is also parsed here.
87 *
88 * syntax:
89 * user crontab:
90 * minutes hours doms months dows cmd\n
91 * system crontab (/etc/crontab):
92 * minutes hours doms months dows USERNAME cmd\n
93 */
94
95 ecode_e ecode = e_none;
96 entry *e;
97 int ch;
98 char cmd[MAX_COMMAND];
99 char envstr[MAX_ENVSTR];
100 char **tenvp;
101 char *p;
102 struct passwd temppw;
103
104 Debug(DPARS, ("load_entry()...about to eat comments\n"));
105
106 skip_comments(file);
107
108 ch = get_char(file);
109 if (ch == EOF)
110 return (NULL);
111
112 /* ch is now the first useful character of a useful line.
113 * it may be an @special or it may be the first character
114 * of a list of minutes.
115 */
116
117 e = (entry *) calloc(sizeof (entry), sizeof (char));
118
119 /* check for '-' as a first character, this option will disable
120 * writing a syslog message about command getting executed
121 */
122 if (ch == '-') {
123 /* if we are editing system crontab or user uid is 0 (root)
124 * we are allowed to disable logging
125 */
126 if (pw == NULL || pw->pw_uid == 0)
127 e->flags |= DONT_LOG;
128 else {
129 log_it("CRON", getpid(), "ERROR", "Only privileged user can disable logging", 0);
130 ecode = e_option;
131 goto eof;
132 }
133 ch = get_char(file);
134 if (ch == EOF) {
135 free(e);
136 return NULL;
137 }
138 }
139
140 if (ch == '@') {
141 /* all of these should be flagged and load-limited; i.e.,
142 * instead of @hourly meaning "0 * * * *" it should mean
143 * "close to the front of every hour but not 'til the
144 * system load is low". Problems are: how do you know
145 * what "low" means? (save me from /etc/cron.conf!) and:
146 * how to guarantee low variance (how low is low?), which
147 * means how to we run roughly every hour -- seems like
148 * we need to keep a history or let the first hour set
149 * the schedule, which means we aren't load-limited
150 * anymore. too much for my overloaded brain. (vix, jan90)
151 * HINT
152 */
153 ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
154 if (!strcmp("reboot", cmd)) {
155 e->flags |= WHEN_REBOOT;
156 }
157 else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)) {
158 bit_set(e->minute, 0);
159 bit_set(e->hour, 0);
160 bit_set(e->dom, 0);
161 bit_set(e->month, 0);
162 bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
163 e->flags |= DOW_STAR;
164 }
165 else if (!strcmp("monthly", cmd)) {
166 bit_set(e->minute, 0);
167 bit_set(e->hour, 0);
168 bit_set(e->dom, 0);
169 bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
170 bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
171 e->flags |= DOW_STAR;
172 }
173 else if (!strcmp("weekly", cmd)) {
174 bit_set(e->minute, 0);
175 bit_set(e->hour, 0);
176 bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
177 bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
178 bit_set(e->dow, 0);
179 e->flags |= DOW_STAR;
180 }
181 else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
182 bit_set(e->minute, 0);
183 bit_set(e->hour, 0);
184 bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
185 bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
186 bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
187 }
188 else if (!strcmp("hourly", cmd)) {
189 bit_set(e->minute, 0);
190 bit_nset(e->hour, 0, LAST_HOUR - FIRST_HOUR);
191 bit_nset(e->dom, 0, LAST_DOM - FIRST_DOM);
192 bit_nset(e->month, 0, LAST_MONTH - FIRST_MONTH);
193 bit_nset(e->dow, 0, LAST_DOW - FIRST_DOW);
194 e->flags |= HR_STAR;
195 }
196 else {
197 ecode = e_timespec;
198 goto eof;
199 }
200 /* Advance past whitespace between shortcut and
201 * username/command.
202 */
203 Skip_Blanks(ch, file);
204 if (ch == EOF || ch == '\n') {
205 ecode = e_cmd;
206 goto eof;
207 }
208 }
209 else {
210 Debug(DPARS, ("load_entry()...about to parse numerics\n"));
211
212 if (ch == '*')
213 e->flags |= MIN_STAR;
214 ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file);
215 if (ch == EOF) {
216 ecode = e_minute;
217 goto eof;
218 }
219
220 /* hours
221 */
222
223 if (ch == '*')
224 e->flags |= HR_STAR;
225 ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file);
226 if (ch == EOF) {
227 ecode = e_hour;
228 goto eof;
229 }
230
231 /* DOM (days of month)
232 */
233
234 if (ch == '*')
235 e->flags |= DOM_STAR;
236 ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
237 if (ch == EOF) {
238 ecode = e_dom;
239 goto eof;
240 }
241
242 /* month
243 */
244
245 ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MonthNames, ch, file);
246 if (ch == EOF) {
247 ecode = e_month;
248 goto eof;
249 }
250
251 /* DOW (days of week)
252 */
253
254 if (ch == '*')
255 e->flags |= DOW_STAR;
256 ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DowNames, ch, file);
257 if (ch == EOF) {
258 ecode = e_dow;
259 goto eof;
260 }
261 }
262
263 /* make sundays equivalent */
264 if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
265 bit_set(e->dow, 0);
266 bit_set(e->dow, 7);
267 }
268
269 /* check for permature EOL and catch a common typo */
270 if (ch == '\n' || ch == '*') {
271 ecode = e_cmd;
272 goto eof;
273 }
274
275 /* ch is the first character of a command, or a username */
276 unget_char(ch, file);
277
278 if (!pw) {
279 char *username = cmd; /* temp buffer */
280
281 Debug(DPARS, ("load_entry()...about to parse username\n"));
282 ch = get_string(username, MAX_COMMAND, file, " \t\n");
283
284 Debug(DPARS, ("load_entry()...got %s\n", username));
285 if (ch == EOF || ch == '\n' || ch == '*') {
286 ecode = e_cmd;
287 goto eof;
288 }
289
290 pw = getpwnam(username);
291 if (pw == NULL) {
292 Debug(DPARS, ("load_entry()...unknown user entry\n"));
293 memset(&temppw, 0, sizeof (temppw));
294 temppw.pw_name = username;
295 temppw.pw_passwd = "";
296 pw = &temppw;
297 } else {
298 Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n",
299 (long) pw->pw_uid, (long) pw->pw_gid));
300 }
301 }
302
303 if ((e->pwd = pw_dup(pw)) == NULL) {
304 ecode = e_memory;
305 goto eof;
306 }
307 memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd));
308
309 p = env_get("RANDOM_DELAY", envp);
310 if (p) {
311 char *endptr;
312 long val;
313
314 errno = 0; /* To distinguish success/failure after call */
315 val = strtol(p, &endptr, 10);
316 if (errno != 0 || val < 0 || val > 24*60) {
317 log_it("CRON", getpid(), "ERROR", "bad value of RANDOM_DELAY", 0);
318 } else {
319 e->delay = (int)((double)val * RandomScale);
320 }
321 }
322
323 /* copy and fix up environment. some variables are just defaults and
324 * others are overrides.
325 */
326 if ((e->envp = env_copy(envp)) == NULL) {
327 ecode = e_memory;
328 goto eof;
329 }
330 if (!env_get("SHELL", e->envp)) {
331 if (glue_strings(envstr, sizeof envstr, "SHELL", _PATH_BSHELL, '=')) {
332 if ((tenvp = env_set(e->envp, envstr)) == NULL) {
333 ecode = e_memory;
334 goto eof;
335 }
336 e->envp = tenvp;
337 }
338 else
339 log_it("CRON", getpid(), "ERROR", "can't set SHELL", 0);
340 }
341 if ((tenvp = env_update_home(e->envp, pw->pw_dir)) == NULL) {
342 ecode = e_memory;
343 goto eof;
344 }
345 e->envp = tenvp;
346 #ifndef LOGIN_CAP
347 /* If login.conf is in used we will get the default PATH later. */
348 if (!env_get("PATH", e->envp)) {
349 char *defpath;
350
351 if (ChangePath)
352 defpath = _PATH_DEFPATH;
353 else {
354 defpath = getenv("PATH");
355 if (defpath == NULL)
356 defpath = _PATH_DEFPATH;
357 }
358
359 if (glue_strings(envstr, sizeof envstr, "PATH", defpath, '=')) {
360 if ((tenvp = env_set(e->envp, envstr)) == NULL) {
361 ecode = e_memory;
362 goto eof;
363 }
364 e->envp = tenvp;
365 }
366 else
367 log_it("CRON", getpid(), "ERROR", "can't set PATH", 0);
368 }
369 #endif /* LOGIN_CAP */
370 if (glue_strings(envstr, sizeof envstr, "LOGNAME", pw->pw_name, '=')) {
371 if ((tenvp = env_set(e->envp, envstr)) == NULL) {
372 ecode = e_memory;
373 goto eof;
374 }
375 e->envp = tenvp;
376 }
377 else
378 log_it("CRON", getpid(), "ERROR", "can't set LOGNAME", 0);
379 #if defined(BSD) || defined(__linux)
380 if (glue_strings(envstr, sizeof envstr, "USER", pw->pw_name, '=')) {
381 if ((tenvp = env_set(e->envp, envstr)) == NULL) {
382 ecode = e_memory;
383 goto eof;
384 }
385 e->envp = tenvp;
386 }
387 else
388 log_it("CRON", getpid(), "ERROR", "can't set USER", 0);
389 #endif
390
391 Debug(DPARS, ("load_entry()...about to parse command\n"));
392
393 /* Everything up to the next \n or EOF is part of the command...
394 * too bad we don't know in advance how long it will be, since we
395 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
396 */
397 ch = get_string(cmd, MAX_COMMAND, file, "\n");
398
399 /* a file without a \n before the EOF is rude, so we'll complain...
400 */
401 if (ch == EOF) {
402 ecode = e_cmd;
403 goto eof;
404 }
405
406 /* got the command in the 'cmd' string; save it in *e.
407 */
408 if ((e->cmd = strdup(cmd)) == NULL) {
409 ecode = e_memory;
410 goto eof;
411 }
412
413 Debug(DPARS, ("load_entry()...returning successfully\n"));
414
415 /* success, fini, return pointer to the entry we just created...
416 */
417 return (e);
418
419 eof:
420 if (e->envp)
421 env_free(e->envp);
422 free(e->pwd);
423 free(e->cmd);
424 free(e);
425 while (ch != '\n' && !feof(file))
426 ch = get_char(file);
427 if (ecode != e_none && error_func)
428 (*error_func) (ecodes[(int) ecode]);
429 return (NULL);
430 }
431
432 static int
433 get_list(bitstr_t * bits, int low, int high, const char *names[],
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
434 int ch, FILE * file) {
435 int done;
436
437 /* we know that we point to a non-blank character here;
438 * must do a Skip_Blanks before we exit, so that the
439 * next call (or the code that picks up the cmd) can
440 * assume the same thing.
441 */
442
443 Debug(DPARS | DEXT, ("get_list()...entered\n"));
444
445 /* list = range {"," range}
446 */
447 /* clear the bit string, since the default is 'off'.
448 */
449 bit_nclear(bits, 0, (high - low));
450
451 /* process all ranges
452 */
453 done = FALSE;
454 while (!done) {
455 if (EOF == (ch = get_range(bits, low, high, names, ch, file)))
456 return (EOF);
457 if (ch == ',')
458 ch = get_char(file);
459 else
460 done = TRUE;
461 }
462
463 /* exiting. skip to some blanks, then skip over the blanks.
464 */
465 Skip_Nonblanks(ch, file)
466 Skip_Blanks(ch, file)
467
468 Debug(DPARS | DEXT, ("get_list()...exiting w/ %02x\n", ch));
469
470 return (ch);
471 }
472
473
474 static int
475 get_range(bitstr_t * bits, int low, int high, const char *names[],
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
476 int ch, FILE * file) {
477 /* range = number | number "-" number [ "/" number ]
478 */
479
480 int i, num1, num2, num3;
481
482 Debug(DPARS | DEXT, ("get_range()...entering, exit won't show\n"));
483
484 if (ch == '*') {
485 /* '*' means "first-last" but can still be modified by /step
486 */
487 num1 = low;
488 num2 = high;
489 ch = get_char(file);
490 if (ch == EOF)
491 return (EOF);
492 }
493 else {
494 ch = get_number(&num1, low, names, ch, file, ",- \t\n");
495 if (ch == EOF)
496 return (EOF);
497
498 if (ch != '-') {
499 /* not a range, it's a single number.
500 */
501 if (EOF == set_element(bits, low, high, num1)) {
502 unget_char(ch, file);
503 return (EOF);
504 }
505 return (ch);
506 }
507 else {
508 /* eat the dash
509 */
510 ch = get_char(file);
511 if (ch == EOF)
512 return (EOF);
513
514 /* get the number following the dash
515 */
516 ch = get_number(&num2, low, names, ch, file, "/, \t\n");
517 if (ch == EOF || num1 > num2)
518 return (EOF);
519 }
520 }
521
522 /* check for step size
523 */
524 if (ch == '/') {
525 /* eat the slash
526 */
527 ch = get_char(file);
528 if (ch == EOF)
529 return (EOF);
530
531 /* get the step size -- note: we don't pass the
532 * names here, because the number is not an
533 * element id, it's a step size. 'low' is
534 * sent as a 0 since there is no offset either.
535 */
536 ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n");
537 if (ch == EOF || num3 == 0)
538 return (EOF);
539 }
540 else {
541 /* no step. default==1.
542 */
543 num3 = 1;
544 }
545
546 /* range. set all elements from num1 to num2, stepping
547 * by num3. (the step is a downward-compatible extension
548 * proposed conceptually by bob@acornrc, syntactically
549 * designed then implemented by paul vixie).
550 */
551 for (i = num1; i <= num2; i += num3)
552 if (EOF == set_element(bits, low, high, i)) {
553 unget_char(ch, file);
554 return (EOF);
555 }
556
557 return (ch);
558 }
559
560 static int
561 get_number(int *numptr, int low, const char *names[], int ch, FILE * file,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
562 const char *terms) {
563 char temp[MAX_TEMPSTR], *pc;
564 int len, i;
565
566 pc = temp;
567 len = 0;
568
569 /* first look for a number */
570 while (isdigit((unsigned char) ch)) {
571 if (++len >= MAX_TEMPSTR)
572 goto bad;
573 *pc++ = (char)ch;
574 ch = get_char(file);
575 }
576 *pc = '\0';
577 if (len != 0) {
578 /* got a number, check for valid terminator */
579 if (!strchr(terms, ch))
580 goto bad;
581 *numptr = atoi(temp);
582 return (ch);
583 }
584
585 /* no numbers, look for a string if we have any */
586 if (names) {
587 while (isalpha((unsigned char) ch)) {
588 if (++len >= MAX_TEMPSTR)
589 goto bad;
590 *pc++ = (char)ch;
591 ch = get_char(file);
592 }
593 *pc = '\0';
594 if (len != 0 && strchr(terms, ch)) {
595 for (i = 0; names[i] != NULL; i++) {
596 Debug(DPARS | DEXT,
597 ("get_num, compare(%s,%s)\n", names[i], temp));
598 if (!strcasecmp(names[i], temp)) {
599 *numptr = i + low;
600 return (ch);
601 }
602 }
603 }
604 }
605
606 bad:
607 unget_char(ch, file);
608 return (EOF);
609 }
610
611 static int set_element(bitstr_t * bits, int low, int high, int number) {
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
612 Debug(DPARS | DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number));
613
614 if (number < low || number > high)
615 return (EOF);
616
617 bit_set(bits, (number - low));
618 return (OK);
619 }