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