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