xref: /openbsd-src/usr.sbin/cron/entry.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: entry.c,v 1.48 2015/11/14 13:09:14 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
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 'q':
342 			e->flags |= DONT_LOG;
343 			Skip_Nonblanks(ch, file)
344 			break;
345 		default:
346 			ecode = e_option;
347 			goto eof;
348 		}
349 		Skip_Blanks(ch, file)
350 		if (ch == EOF || ch == '\n') {
351 			ecode = e_cmd;
352 			goto eof;
353 		}
354 	}
355 	unget_char(ch, file);
356 
357 	/* Everything up to the next \n or EOF is part of the command...
358 	 * too bad we don't know in advance how long it will be, since we
359 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
360 	 */
361 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
362 
363 	/* a file without a \n before the EOF is rude, so we'll complain...
364 	 */
365 	if (ch == EOF) {
366 		ecode = e_cmd;
367 		goto eof;
368 	}
369 
370 	/* got the command in the 'cmd' string; save it in *e.
371 	 */
372 	if ((e->cmd = strdup(cmd)) == NULL) {
373 		ecode = e_memory;
374 		goto eof;
375 	}
376 
377 	/* success, fini, return pointer to the entry we just created...
378 	 */
379 	return (e);
380 
381  eof:
382 	if (e)
383 		free_entry(e);
384 	while (ch != '\n' && !feof(file))
385 		ch = get_char(file);
386 	if (ecode != e_none && error_func)
387 		(*error_func)(ecodes[(int)ecode]);
388 	return (NULL);
389 }
390 
391 static int
392 get_list(bitstr_t *bits, int low, int high, const char *names[],
393 	 int ch, FILE *file)
394 {
395 	int done;
396 
397 	/* we know that we point to a non-blank character here;
398 	 * must do a Skip_Blanks before we exit, so that the
399 	 * next call (or the code that picks up the cmd) can
400 	 * assume the same thing.
401 	 */
402 
403 	/* list = range {"," range}
404 	 */
405 
406 	/* clear the bit string, since the default is 'off'.
407 	 */
408 	bit_nclear(bits, 0, (high-low+1));
409 
410 	/* process all ranges
411 	 */
412 	done = FALSE;
413 	while (!done) {
414 		if (EOF == (ch = get_range(bits, low, high, names, ch, file)))
415 			return (EOF);
416 		if (ch == ',')
417 			ch = get_char(file);
418 		else
419 			done = TRUE;
420 	}
421 
422 	/* exiting.  skip to some blanks, then skip over the blanks.
423 	 */
424 	Skip_Nonblanks(ch, file)
425 	Skip_Blanks(ch, file)
426 
427 	return (ch);
428 }
429 
430 
431 static int
432 get_range(bitstr_t *bits, int low, int high, const char *names[],
433 	  int ch, FILE *file)
434 {
435 	/* range = number | number "-" number [ "/" number ]
436 	 */
437 
438 	int i, num1, num2, num3;
439 
440 	if (ch == '*') {
441 		/* '*' means "first-last" but can still be modified by /step
442 		 */
443 		num1 = low;
444 		num2 = high;
445 		ch = get_char(file);
446 		if (ch == EOF)
447 			return (EOF);
448 	} else {
449 		ch = get_number(&num1, low, names, ch, file, ",- \t\n");
450 		if (ch == EOF)
451 			return (EOF);
452 
453 		if (ch != '-') {
454 			/* not a range, it's a single number.
455 			 */
456 			if (EOF == set_element(bits, low, high, num1)) {
457 				unget_char(ch, file);
458 				return (EOF);
459 			}
460 			return (ch);
461 		} else {
462 			/* eat the dash
463 			 */
464 			ch = get_char(file);
465 			if (ch == EOF)
466 				return (EOF);
467 
468 			/* get the number following the dash
469 			 */
470 			ch = get_number(&num2, low, names, ch, file, "/, \t\n");
471 			if (ch == EOF || num1 > num2)
472 				return (EOF);
473 		}
474 	}
475 
476 	/* check for step size
477 	 */
478 	if (ch == '/') {
479 		/* eat the slash
480 		 */
481 		ch = get_char(file);
482 		if (ch == EOF)
483 			return (EOF);
484 
485 		/* get the step size -- note: we don't pass the
486 		 * names here, because the number is not an
487 		 * element id, it's a step size.  'low' is
488 		 * sent as a 0 since there is no offset either.
489 		 */
490 		ch = get_number(&num3, 0, NULL, ch, file, ", \t\n");
491 		if (ch == EOF || num3 == 0)
492 			return (EOF);
493 	} else {
494 		/* no step.  default==1.
495 		 */
496 		num3 = 1;
497 	}
498 
499 	/* range. set all elements from num1 to num2, stepping
500 	 * by num3.  (the step is a downward-compatible extension
501 	 * proposed conceptually by bob@acornrc, syntactically
502 	 * designed then implemented by paul vixie).
503 	 */
504 	for (i = num1;  i <= num2;  i += num3)
505 		if (EOF == set_element(bits, low, high, i)) {
506 			unget_char(ch, file);
507 			return (EOF);
508 		}
509 
510 	return (ch);
511 }
512 
513 static int
514 get_number(int *numptr, int low, const char *names[], int ch, FILE *file,
515     const char *terms)
516 {
517 	char temp[MAX_TEMPSTR], *pc;
518 	int len, i;
519 
520 	pc = temp;
521 	len = 0;
522 
523 	/* first look for a number */
524 	while (isdigit((unsigned char)ch)) {
525 		if (++len >= MAX_TEMPSTR)
526 			goto bad;
527 		*pc++ = ch;
528 		ch = get_char(file);
529 	}
530 	*pc = '\0';
531 	if (len != 0) {
532 		/* got a number, check for valid terminator */
533 		if (!strchr(terms, ch))
534 			goto bad;
535 		*numptr = atoi(temp);
536 		return (ch);
537 	}
538 
539 	/* no numbers, look for a string if we have any */
540 	if (names) {
541 		while (isalpha((unsigned char)ch)) {
542 			if (++len >= MAX_TEMPSTR)
543 				goto bad;
544 			*pc++ = ch;
545 			ch = get_char(file);
546 		}
547 		*pc = '\0';
548 		if (len != 0 && strchr(terms, ch)) {
549 			for (i = 0;  names[i] != NULL;  i++) {
550 				if (!strcasecmp(names[i], temp)) {
551 					*numptr = i+low;
552 					return (ch);
553 				}
554 			}
555 		}
556 	}
557 
558 bad:
559 	unget_char(ch, file);
560 	return (EOF);
561 }
562 
563 static int
564 set_element(bitstr_t *bits, int low, int high, int number)
565 {
566 
567 	if (number < low || number > high)
568 		return (EOF);
569 
570 	bit_set(bits, (number-low));
571 	return (0);
572 }
573