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