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