xref: /openbsd/usr.sbin/cron/entry.c (revision 8787d0be)
1 /*	$OpenBSD: entry.c,v 1.59 2023/07/19 21:26:02 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, e_flags
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 	"bad flags"
56 };
57 
58 static const char *MonthNames[] = {
59 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
60 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
61 	NULL
62 };
63 
64 static const char *DowNames[] = {
65 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
66 	NULL
67 };
68 
69 static int	get_list(bitstr_t *, int, int, const char *[], int, FILE *),
70 		get_range(bitstr_t *, int, int, const char *[], int, FILE *),
71 		get_number(int *, int, const char *[], int, FILE *, const char *),
72 		set_element(bitstr_t *, int, int, int),
73 		set_range(bitstr_t *, int, int, int, int, int);
74 
75 void
free_entry(entry * e)76 free_entry(entry *e)
77 {
78 	free(e->cmd);
79 	free(e->pwd);
80 	if (e->envp)
81 		env_free(e->envp);
82 	free(e);
83 }
84 
85 /* return NULL if eof or syntax error occurs;
86  * otherwise return a pointer to a new entry.
87  */
88 entry *
load_entry(FILE * file,void (* error_func)(const char *),struct passwd * pw,char ** envp)89 load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw,
90     char **envp)
91 {
92 	/* this function reads one crontab entry -- the next -- from a file.
93 	 * it skips any leading blank lines, ignores comments, and returns
94 	 * NULL if for any reason the entry can't be read and parsed.
95 	 *
96 	 * the entry is also parsed here.
97 	 *
98 	 * syntax:
99 	 *   user crontab:
100 	 *	minutes hours doms months dows cmd\n
101 	 *   system crontab (/etc/crontab):
102 	 *	minutes hours doms months dows USERNAME cmd\n
103 	 */
104 
105 	ecode_e	ecode = e_none;
106 	entry *e;
107 	int ch;
108 	char cmd[MAX_COMMAND];
109 	char envstr[MAX_ENVSTR];
110 	char **tenvp;
111 
112 	skip_comments(file);
113 
114 	ch = get_char(file);
115 	if (ch == EOF)
116 		return (NULL);
117 
118 	/* ch is now the first useful character of a useful line.
119 	 * it may be an @special or it may be the first character
120 	 * of a list of minutes.
121 	 */
122 
123 	e = calloc(sizeof(entry), 1);
124 	if (e == NULL) {
125 		ecode = e_memory;
126 		goto eof;
127 	}
128 
129 	if (ch == '@') {
130 		/* all of these should be flagged and load-limited; i.e.,
131 		 * instead of @hourly meaning "0 * * * *" it should mean
132 		 * "close to the front of every hour but not 'til the
133 		 * system load is low".  Problems are: how do you know
134 		 * what "low" means? (save me from /etc/cron.conf!) and:
135 		 * how to guarantee low variance (how low is low?), which
136 		 * means how to we run roughly every hour -- seems like
137 		 * we need to keep a history or let the first hour set
138 		 * the schedule, which means we aren't load-limited
139 		 * anymore.  too much for my overloaded brain. (vix, jan90)
140 		 * HINT
141 		 */
142 		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
143 		if (!strcmp("reboot", cmd)) {
144 			e->flags |= WHEN_REBOOT;
145 		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
146 			set_element(e->minute, FIRST_MINUTE, LAST_MINUTE,
147 			    FIRST_MINUTE);
148 			set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR);
149 			set_element(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM);
150 			set_element(e->month, FIRST_MONTH, LAST_MONTH,
151 			    FIRST_MONTH);
152 			set_range(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW,
153 			    LAST_DOW, 1);
154 			e->flags |= DOW_STAR;
155 		} else if (!strcmp("monthly", cmd)) {
156 			set_element(e->minute, FIRST_MINUTE, LAST_MINUTE,
157 			    FIRST_MINUTE);
158 			set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR);
159 			set_element(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM);
160 			set_range(e->month, FIRST_MONTH, LAST_MONTH,
161 			    FIRST_MONTH, LAST_MONTH, 1);
162 			set_range(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW,
163 			    LAST_DOW, 1);
164 			e->flags |= DOW_STAR;
165 		} else if (!strcmp("weekly", cmd)) {
166 			set_element(e->minute, FIRST_MINUTE, LAST_MINUTE,
167 			    FIRST_MINUTE);
168 			set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR);
169 			set_range(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM,
170 			    LAST_DOM, 1);
171 			set_range(e->month, FIRST_MONTH, LAST_MONTH,
172 			    FIRST_MONTH, LAST_MONTH, 1);
173 			set_element(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW);
174 			e->flags |= DOW_STAR;
175 		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
176 			set_element(e->minute, FIRST_MINUTE, LAST_MINUTE,
177 			    FIRST_MINUTE);
178 			set_element(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR);
179 			set_range(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM,
180 			    LAST_DOM, 1);
181 			set_range(e->month, FIRST_MONTH, LAST_MONTH,
182 			    FIRST_MONTH, LAST_MONTH, 1);
183 			set_range(e->dow, FIRST_DOW, LAST_DOW, FIRST_DOW,
184 			    LAST_DOW, 1);
185 		} else if (!strcmp("hourly", cmd)) {
186 			set_element(e->minute, FIRST_MINUTE, LAST_MINUTE,
187 			    FIRST_MINUTE);
188 			set_range(e->hour, FIRST_HOUR, LAST_HOUR, FIRST_HOUR,
189 			    LAST_HOUR, 1);
190 			set_range(e->dom, FIRST_DOM, LAST_DOM, FIRST_DOM,
191 			    LAST_DOM, 1);
192 			set_range(e->month, FIRST_MONTH, LAST_MONTH,
193 			    FIRST_MONTH, LAST_MONTH, 1);
194 			set_range(e->dow, FIRST_DOW, LAST_DOW,
195 			    FIRST_DOW, LAST_DOW, 1);
196 			e->flags |= HR_STAR;
197 		} else {
198 			ecode = e_timespec;
199 			goto eof;
200 		}
201 		/* Advance past whitespace between shortcut and
202 		 * username/command.
203 		 */
204 		Skip_Blanks(ch, file);
205 		if (ch == EOF || ch == '\n') {
206 			ecode = e_cmd;
207 			goto eof;
208 		}
209 	} else {
210 		if (ch == '*')
211 			e->flags |= MIN_STAR;
212 		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
213 		    NULL, ch, file);
214 		if (ch == EOF) {
215 			ecode = e_minute;
216 			goto eof;
217 		}
218 
219 		/* hours
220 		 */
221 
222 		if (ch == '*')
223 			e->flags |= HR_STAR;
224 		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
225 		    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,
237 		    NULL, ch, file);
238 		if (ch == EOF) {
239 			ecode = e_dom;
240 			goto eof;
241 		}
242 
243 		/* month
244 		 */
245 
246 		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
247 		    MonthNames, ch, file);
248 		if (ch == EOF) {
249 			ecode = e_month;
250 			goto eof;
251 		}
252 
253 		/* DOW (days of week)
254 		 */
255 
256 		if (ch == '*')
257 			e->flags |= DOW_STAR;
258 		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
259 		    DowNames, ch, file);
260 		if (ch == EOF) {
261 			ecode = e_dow;
262 			goto eof;
263 		}
264 	}
265 
266 	/* make sundays equivalent */
267 	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
268 		bit_set(e->dow, 0);
269 		bit_set(e->dow, 7);
270 	}
271 
272 	/* check for permature EOL and catch a common typo */
273 	if (ch == '\n' || ch == '*') {
274 		ecode = e_cmd;
275 		goto eof;
276 	}
277 
278 	if (!pw) {
279 		char		*username = cmd;	/* temp buffer */
280 
281 		unget_char(ch, file);
282 		ch = get_string(username, MAX_COMMAND, file, " \t\n");
283 
284 		if (ch == EOF || ch == '\n' || ch == '*') {
285 			ecode = e_cmd;
286 			goto eof;
287 		}
288 		Skip_Blanks(ch, file)
289 
290 		pw = getpwnam(username);
291 		if (pw == NULL) {
292 			ecode = e_username;
293 			goto eof;
294 		}
295 	}
296 
297 	if ((e->pwd = pw_dup(pw)) == NULL) {
298 		ecode = e_memory;
299 		goto eof;
300 	}
301 	explicit_bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd));
302 
303 	/* copy and fix up environment.  some variables are just defaults and
304 	 * others are overrides.
305 	 */
306 	if ((e->envp = env_copy(envp)) == NULL) {
307 		ecode = e_memory;
308 		goto eof;
309 	}
310 	if (!env_get("SHELL", e->envp)) {
311 		if (snprintf(envstr, sizeof envstr, "SHELL=%s", _PATH_BSHELL) >=
312 		    sizeof(envstr))
313 			syslog(LOG_ERR, "(CRON) ERROR (can't set SHELL)");
314 		else {
315 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
316 				ecode = e_memory;
317 				goto eof;
318 			}
319 			e->envp = tenvp;
320 		}
321 	}
322 	if (!env_get("HOME", e->envp)) {
323 		if (snprintf(envstr, sizeof envstr, "HOME=%s", pw->pw_dir) >=
324 		    sizeof(envstr))
325 			syslog(LOG_ERR, "(CRON) ERROR (can't set HOME)");
326 		else {
327 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
328 				ecode = e_memory;
329 				goto eof;
330 			}
331 			e->envp = tenvp;
332 		}
333 	}
334 	if (snprintf(envstr, sizeof envstr, "LOGNAME=%s", pw->pw_name) >=
335 		sizeof(envstr))
336 		syslog(LOG_ERR, "(CRON) ERROR (can't set LOGNAME)");
337 	else {
338 		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
339 			ecode = e_memory;
340 			goto eof;
341 		}
342 		e->envp = tenvp;
343 	}
344 	if (snprintf(envstr, sizeof envstr, "USER=%s", pw->pw_name) >=
345 		sizeof(envstr))
346 		syslog(LOG_ERR, "(CRON) ERROR (can't set USER)");
347 	else {
348 		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
349 			ecode = e_memory;
350 			goto eof;
351 		}
352 		e->envp = tenvp;
353 	}
354 
355 	/* An optional series of '-'-prefixed flags in getopt style can
356 	 * occur before the command.
357 	 */
358 	while (ch == '-') {
359 		int flags = 0, loop = 1;
360 
361 		while (loop) {
362 			switch (ch = get_char(file)) {
363 			case 'n':
364 				flags |= MAIL_WHEN_ERR;
365 				break;
366 			case 'q':
367 				flags |= DONT_LOG;
368 				break;
369 			case 's':
370 				flags |= SINGLE_JOB;
371 				break;
372 			case ' ':
373 			case '\t':
374 				Skip_Blanks(ch, file)
375 				loop = 0;
376 				break;
377 			case EOF:
378 			case '\n':
379 				ecode = e_cmd;
380 				goto eof;
381 			default:
382 				ecode = e_flags;
383 				goto eof;
384 			}
385 		}
386 
387 		if (flags == 0) {
388 			ecode = e_flags;
389 			goto eof;
390 		}
391 		e->flags |= flags;
392 	}
393 	unget_char(ch, file);
394 
395 	/* Everything up to the next \n or EOF is part of the command...
396 	 * too bad we don't know in advance how long it will be, since we
397 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
398 	 */
399 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
400 
401 	/* a file without a \n before the EOF is rude, so we'll complain...
402 	 */
403 	if (ch == EOF) {
404 		ecode = e_cmd;
405 		goto eof;
406 	}
407 
408 	/* got the command in the 'cmd' string; save it in *e.
409 	 */
410 	if ((e->cmd = strdup(cmd)) == NULL) {
411 		ecode = e_memory;
412 		goto eof;
413 	}
414 
415 	/* success, fini, return pointer to the entry we just created...
416 	 */
417 	return (e);
418 
419  eof:
420 	if (e)
421 		free_entry(e);
422 	while (ch != '\n' && !feof(file))
423 		ch = get_char(file);
424 	if (ecode != e_none && error_func)
425 		(*error_func)(ecodes[(int)ecode]);
426 	return (NULL);
427 }
428 
429 static int
get_list(bitstr_t * bits,int low,int high,const char * names[],int ch,FILE * file)430 get_list(bitstr_t *bits, int low, int high, const char *names[],
431 	 int ch, FILE *file)
432 {
433 	int done;
434 
435 	/* we know that we point to a non-blank character here;
436 	 * must do a Skip_Blanks before we exit, so that the
437 	 * next call (or the code that picks up the cmd) can
438 	 * assume the same thing.
439 	 */
440 
441 	/* list = range {"," range}
442 	 */
443 
444 	/* clear the bit string, since the default is 'off'.
445 	 */
446 	bit_nclear(bits, 0, high - low);
447 
448 	/* process all ranges
449 	 */
450 	done = FALSE;
451 	while (!done) {
452 		if ((ch = get_range(bits, low, high, names, ch, file)) == EOF)
453 			return (EOF);
454 		if (ch == ',')
455 			ch = get_char(file);
456 		else
457 			done = TRUE;
458 	}
459 
460 	/* exiting.  skip to some blanks, then skip over the blanks.
461 	 */
462 	Skip_Nonblanks(ch, file)
463 	Skip_Blanks(ch, file)
464 
465 	return (ch);
466 }
467 
468 
469 static int
get_range(bitstr_t * bits,int low,int high,const char * names[],int ch,FILE * file)470 get_range(bitstr_t *bits, int low, int high, const char *names[],
471 	  int ch, FILE *file)
472 {
473 	/* range = number |
474 	 * [number] "~" [number] ["/" number] |
475 	 * number "-" number ["/" number]
476 	 */
477 
478 	int num1, num2, num3, rndstep;
479 
480 	num1 = low;
481 	num2 = high;
482 	rndstep = 0;
483 
484 	if (ch == '*') {
485 		/* '*' means [low, high] but can still be modified by /step
486 		 */
487 		ch = get_char(file);
488 		if (ch == EOF)
489 			return (EOF);
490 	} else {
491 		if (ch != '~') {
492 			ch = get_number(&num1, low, names, ch, file, ",-~ \t\n");
493 			if (ch == EOF)
494 				return (EOF);
495 		}
496 
497 		switch (ch) {
498 		case '-':
499 			/* eat the dash
500 			 */
501 			ch = get_char(file);
502 			if (ch == EOF)
503 				return (EOF);
504 
505 			/* get the number following the dash
506 			 */
507 			ch = get_number(&num2, low, names, ch, file, "/, \t\n");
508 			if (ch == EOF || num1 > num2)
509 				return (EOF);
510 			break;
511 		case '~':
512 			/* eat the tilde
513 			 */
514 			ch = get_char(file);
515 			if (ch == EOF)
516 				return (EOF);
517 
518 			/* get the (optional) number following the tilde
519 			 */
520 			ch = get_number(&num2, low, names, ch, file, "/, \t\n");
521 			if (ch == EOF) {
522 				/* no second number, check for valid terminator
523 				 */
524 				ch = get_char(file);
525 				if (!strchr("/, \t\n", ch)) {
526 					unget_char(ch, file);
527 					return (EOF);
528 				}
529 			}
530 			if (ch == EOF || num1 > num2) {
531 				unget_char(ch, file);
532 				return (EOF);
533 			}
534 
535 			/* we must perform the bounds checking ourselves
536 			 */
537 			if (num1 < low || num2 > high)
538 				return (EOF);
539 
540 			if (ch == '/') {
541 				/* randomize the step value instead of num1
542 				 */
543 				rndstep = 1;
544 				break;
545 			}
546 
547 			/* get a random number in the interval [num1, num2]
548 			 */
549 			num3 = num1;
550 			num1 = arc4random_uniform(num2 - num3 + 1) + num3;
551 			/* FALLTHROUGH */
552 		default:
553 			/* not a range, it's a single number.
554 			 */
555 			if (set_element(bits, low, high, num1) == EOF) {
556 				unget_char(ch, file);
557 				return (EOF);
558 			}
559 			return (ch);
560 		}
561 	}
562 
563 	/* check for step size
564 	 */
565 	if (ch == '/') {
566 		/* eat the slash
567 		 */
568 		ch = get_char(file);
569 		if (ch == EOF)
570 			return (EOF);
571 
572 		/* get the step size -- note: we don't pass the
573 		 * names here, because the number is not an
574 		 * element id, it's a step size.  'low' is
575 		 * sent as a 0 since there is no offset either.
576 		 */
577 		ch = get_number(&num3, 0, NULL, ch, file, ", \t\n");
578 		if (ch == EOF || num3 == 0)
579 			return (EOF);
580 		if (rndstep) {
581 			/*
582 			 * use a random offset smaller than the step size
583 			 * and the difference between high and low values.
584 			 */
585 			num1 += arc4random_uniform(MINIMUM(num3, num2 - num1));
586 		}
587 	} else {
588 		/* no step.  default==1.
589 		 */
590 		num3 = 1;
591 	}
592 
593 	/* range. set all elements from num1 to num2, stepping
594 	 * by num3.  (the step is a downward-compatible extension
595 	 * proposed conceptually by bob@acornrc, syntactically
596 	 * designed then implemented by paul vixie).
597 	 */
598 	if (set_range(bits, low, high, num1, num2, num3) == EOF) {
599 		unget_char(ch, file);
600 		return (EOF);
601 	}
602 
603 	return (ch);
604 }
605 
606 static int
get_number(int * numptr,int low,const char * names[],int ch,FILE * file,const char * terms)607 get_number(int *numptr, int low, const char *names[], int ch, FILE *file,
608     const char *terms)
609 {
610 	char temp[MAX_TEMPSTR], *pc;
611 	int len, i;
612 
613 	pc = temp;
614 	len = 0;
615 
616 	/* first look for a number */
617 	while (isdigit((unsigned char)ch)) {
618 		if (++len >= MAX_TEMPSTR)
619 			goto bad;
620 		*pc++ = ch;
621 		ch = get_char(file);
622 	}
623 	*pc = '\0';
624 	if (len != 0) {
625 		/* got a number, check for valid terminator */
626 		if (!strchr(terms, ch))
627 			goto bad;
628 		*numptr = atoi(temp);
629 		return (ch);
630 	}
631 
632 	/* no numbers, look for a string if we have any */
633 	if (names) {
634 		while (isalpha((unsigned char)ch)) {
635 			if (++len >= MAX_TEMPSTR)
636 				goto bad;
637 			*pc++ = ch;
638 			ch = get_char(file);
639 		}
640 		*pc = '\0';
641 		if (len != 0 && strchr(terms, ch)) {
642 			for (i = 0;  names[i] != NULL;  i++) {
643 				if (!strcasecmp(names[i], temp)) {
644 					*numptr = i+low;
645 					return (ch);
646 				}
647 			}
648 		}
649 	}
650 
651 bad:
652 	unget_char(ch, file);
653 	return (EOF);
654 }
655 
656 static int
set_element(bitstr_t * bits,int low,int high,int number)657 set_element(bitstr_t *bits, int low, int high, int number)
658 {
659 
660 	if (number < low || number > high)
661 		return (EOF);
662 	number -= low;
663 
664 	bit_set(bits, number);
665 	return (0);
666 }
667 
668 static int
set_range(bitstr_t * bits,int low,int high,int start,int stop,int step)669 set_range(bitstr_t *bits, int low, int high, int start, int stop, int step)
670 {
671 	int i;
672 
673 	if (start < low || stop > high)
674 		return (EOF);
675 	start -= low;
676 	stop -= low;
677 
678 	if (step == 1) {
679 		bit_nset(bits, start, stop);
680 	} else {
681 		for (i = start; i <= stop; i += step)
682 			bit_set(bits, i);
683 	}
684 	return (0);
685 }
686