xref: /openbsd-src/usr.sbin/cron/entry.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: entry.c,v 1.33 2011/05/19 15:00:17 phessler 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 	if (e->envp)
63 		env_free(e->envp);
64 	free(e);
65 }
66 
67 /* return NULL if eof or syntax error occurs;
68  * otherwise return a pointer to a new entry.
69  */
70 entry *
71 load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw,
72     char **envp) {
73 	/* this function reads one crontab entry -- the next -- from a file.
74 	 * it skips any leading blank lines, ignores comments, and returns
75 	 * NULL if for any reason the entry can't be read and parsed.
76 	 *
77 	 * the entry is also parsed here.
78 	 *
79 	 * syntax:
80 	 *   user crontab:
81 	 *	minutes hours doms months dows cmd\n
82 	 *   system crontab (/etc/crontab):
83 	 *	minutes hours doms months dows USERNAME cmd\n
84 	 */
85 
86 	ecode_e	ecode = e_none;
87 	entry *e;
88 	int ch;
89 	char cmd[MAX_COMMAND];
90 	char envstr[MAX_ENVSTR];
91 	char **tenvp;
92 
93 	Debug(DPARS, ("load_entry()...about to eat comments\n"))
94 
95 	skip_comments(file);
96 
97 	ch = get_char(file);
98 	if (ch == EOF)
99 		return (NULL);
100 
101 	/* ch is now the first useful character of a useful line.
102 	 * it may be an @special or it may be the first character
103 	 * of a list of minutes.
104 	 */
105 
106 	e = (entry *) calloc(sizeof(entry), sizeof(char));
107 	if (e == NULL) {
108 		ecode = e_memory;
109 		goto eof;
110 	}
111 
112 	if (ch == '@') {
113 		/* all of these should be flagged and load-limited; i.e.,
114 		 * instead of @hourly meaning "0 * * * *" it should mean
115 		 * "close to the front of every hour but not 'til the
116 		 * system load is low".  Problems are: how do you know
117 		 * what "low" means? (save me from /etc/cron.conf!) and:
118 		 * how to guarantee low variance (how low is low?), which
119 		 * means how to we run roughly every hour -- seems like
120 		 * we need to keep a history or let the first hour set
121 		 * the schedule, which means we aren't load-limited
122 		 * anymore.  too much for my overloaded brain. (vix, jan90)
123 		 * HINT
124 		 */
125 		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
126 		if (!strcmp("reboot", cmd)) {
127 			e->flags |= WHEN_REBOOT;
128 		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
129 			bit_set(e->minute, 0);
130 			bit_set(e->hour, 0);
131 			bit_set(e->dom, 0);
132 			bit_set(e->month, 0);
133 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
134 			e->flags |= DOW_STAR;
135 		} else if (!strcmp("monthly", cmd)) {
136 			bit_set(e->minute, 0);
137 			bit_set(e->hour, 0);
138 			bit_set(e->dom, 0);
139 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
140 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
141 			e->flags |= DOW_STAR;
142 		} else if (!strcmp("weekly", cmd)) {
143 			bit_set(e->minute, 0);
144 			bit_set(e->hour, 0);
145 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
146 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
147 			bit_set(e->dow, 0);
148 			e->flags |= DOW_STAR;
149 		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
150 			bit_set(e->minute, 0);
151 			bit_set(e->hour, 0);
152 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
153 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
154 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
155 		} else if (!strcmp("hourly", cmd)) {
156 			bit_set(e->minute, 0);
157 			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
158 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
159 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
160 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
161 			e->flags |= HR_STAR;
162 		} else {
163 			ecode = e_timespec;
164 			goto eof;
165 		}
166 		/* Advance past whitespace between shortcut and
167 		 * username/command.
168 		 */
169 		Skip_Blanks(ch, file);
170 		if (ch == EOF || ch == '\n') {
171 			ecode = e_cmd;
172 			goto eof;
173 		}
174 	} else {
175 		Debug(DPARS, ("load_entry()...about to parse numerics\n"))
176 
177 		if (ch == '*')
178 			e->flags |= MIN_STAR;
179 		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
180 			      PPC_NULL, ch, file);
181 		if (ch == EOF) {
182 			ecode = e_minute;
183 			goto eof;
184 		}
185 
186 		/* hours
187 		 */
188 
189 		if (ch == '*')
190 			e->flags |= HR_STAR;
191 		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
192 			      PPC_NULL, ch, file);
193 		if (ch == EOF) {
194 			ecode = e_hour;
195 			goto eof;
196 		}
197 
198 		/* DOM (days of month)
199 		 */
200 
201 		if (ch == '*')
202 			e->flags |= DOM_STAR;
203 		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
204 			      PPC_NULL, ch, file);
205 		if (ch == EOF) {
206 			ecode = e_dom;
207 			goto eof;
208 		}
209 
210 		/* month
211 		 */
212 
213 		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
214 			      MonthNames, ch, file);
215 		if (ch == EOF) {
216 			ecode = e_month;
217 			goto eof;
218 		}
219 
220 		/* DOW (days of week)
221 		 */
222 
223 		if (ch == '*')
224 			e->flags |= DOW_STAR;
225 		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
226 			      DowNames, ch, file);
227 		if (ch == EOF) {
228 			ecode = e_dow;
229 			goto eof;
230 		}
231 	}
232 
233 	/* make sundays equivalent */
234 	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
235 		bit_set(e->dow, 0);
236 		bit_set(e->dow, 7);
237 	}
238 
239 	/* check for permature EOL and catch a common typo */
240 	if (ch == '\n' || ch == '*') {
241 		ecode = e_cmd;
242 		goto eof;
243 	}
244 
245 	/* ch is the first character of a command, or a username */
246 	unget_char(ch, file);
247 
248 	if (!pw) {
249 		char		*username = cmd;	/* temp buffer */
250 
251 		Debug(DPARS, ("load_entry()...about to parse username\n"))
252 		ch = get_string(username, MAX_COMMAND, file, " \t\n");
253 
254 		Debug(DPARS, ("load_entry()...got %s\n",username))
255 		if (ch == EOF || ch == '\n' || ch == '*') {
256 			ecode = e_cmd;
257 			goto eof;
258 		}
259 
260 		pw = getpwnam(username);
261 		if (pw == NULL) {
262 			ecode = e_username;
263 			goto eof;
264 		}
265 		Debug(DPARS, ("load_entry()...uid %lu, gid %lu\n",
266 			      (unsigned long)pw->pw_uid,
267 			      (unsigned long)pw->pw_gid))
268 	}
269 
270 	if ((e->pwd = pw_dup(pw)) == NULL) {
271 		ecode = e_memory;
272 		goto eof;
273 	}
274 	bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd));
275 
276 	/* copy and fix up environment.  some variables are just defaults and
277 	 * others are overrides.
278 	 */
279 	if ((e->envp = env_copy(envp)) == NULL) {
280 		ecode = e_memory;
281 		goto eof;
282 	}
283 	if (!env_get("SHELL", e->envp)) {
284 		if (snprintf(envstr, sizeof envstr, "SHELL=%s", _PATH_BSHELL) >=
285 		    sizeof(envstr))
286 			log_it("CRON", getpid(), "error", "can't set SHELL");
287 		else {
288 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
289 				ecode = e_memory;
290 				goto eof;
291 			}
292 			e->envp = tenvp;
293 		}
294 	}
295 	if (!env_get("HOME", e->envp)) {
296 		if (snprintf(envstr, sizeof envstr, "HOME=%s", pw->pw_dir) >=
297 		    sizeof(envstr))
298 			log_it("CRON", getpid(), "error", "can't set HOME");
299 		else {
300 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
301 				ecode = e_memory;
302 				goto eof;
303 			}
304 			e->envp = tenvp;
305 		}
306 	}
307 #ifndef LOGIN_CAP
308 	/* If login.conf is in use we will get the default PATH later. */
309 	if (!env_get("PATH", e->envp)) {
310 		if (snprintf(envstr, sizeof envstr, "PATH=%s", _PATH_DEFPATH) >=
311 		    sizeof(envstr))
312 			log_it("CRON", getpid(), "error", "can't set PATH");
313 		else {
314 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
315 				ecode = e_memory;
316 				goto eof;
317 			}
318 			e->envp = tenvp;
319 		}
320 	}
321 #endif /* LOGIN_CAP */
322 	if (snprintf(envstr, sizeof envstr, "LOGNAME=%s", pw->pw_name) >=
323 		sizeof(envstr))
324 		log_it("CRON", getpid(), "error", "can't set LOGNAME");
325 	else {
326 		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
327 			ecode = e_memory;
328 			goto eof;
329 		}
330 		e->envp = tenvp;
331 	}
332 #if defined(BSD) || defined(__linux)
333 	if (snprintf(envstr, sizeof envstr, "USER=%s", pw->pw_name) >=
334 		sizeof(envstr))
335 		log_it("CRON", getpid(), "error", "can't set USER");
336 	else {
337 		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
338 			ecode = e_memory;
339 			goto eof;
340 		}
341 		e->envp = tenvp;
342 	}
343 #endif
344 
345 	Debug(DPARS, ("load_entry()...about to parse command\n"))
346 
347 	/* If the first character of the command is '-' it is a cron option.
348 	 */
349 	ch = get_char(file);
350 	while (ch == '-') {
351 		switch (ch = get_char(file)) {
352 		case 'q':
353 			e->flags |= DONT_LOG;
354 			Skip_Nonblanks(ch, file)
355 			break;
356 		default:
357 			ecode = e_option;
358 			goto eof;
359 		}
360 		Skip_Blanks(ch, file)
361 		if (ch == EOF || ch == '\n') {
362 			ecode = e_cmd;
363 			goto eof;
364 		}
365 	}
366 	unget_char(ch, file);
367 
368 	/* Everything up to the next \n or EOF is part of the command...
369 	 * too bad we don't know in advance how long it will be, since we
370 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
371 	 */
372 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
373 
374 	/* a file without a \n before the EOF is rude, so we'll complain...
375 	 */
376 	if (ch == EOF) {
377 		ecode = e_cmd;
378 		goto eof;
379 	}
380 
381 	/* got the command in the 'cmd' string; save it in *e.
382 	 */
383 	if ((e->cmd = strdup(cmd)) == NULL) {
384 		ecode = e_memory;
385 		goto eof;
386 	}
387 
388 	Debug(DPARS, ("load_entry()...returning successfully\n"))
389 
390 	/* success, fini, return pointer to the entry we just created...
391 	 */
392 	return (e);
393 
394  eof:
395 	if (e)
396 		free_entry(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