xref: /freebsd/usr.sbin/cron/lib/entry.c (revision 817420dc8eac7df799c78f5309b75092b7f7cd40)
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  *
4  * Distribute freely, except: don't remove my name from the source or
5  * documentation (don't take credit for my work), mark your changes (don't
6  * get me blamed for your possible bugs), don't alter or remove this
7  * notice.  May be sold if buildable source is provided to buyer.  No
8  * warrantee of any kind, express or implied, is included with this
9  * software; use at your own risk, responsibility for damages (if any) to
10  * anyone resulting from the use of this software rests entirely with the
11  * user.
12  *
13  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14  * I'll try to keep a version up to date.  I can be reached as follows:
15  * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16  */
17 
18 #if !defined(lint) && !defined(LINT)
19 static const char rcsid[] =
20   "$FreeBSD$";
21 #endif
22 
23 /* vix 26jan87 [RCS'd; rest of log is in RCS file]
24  * vix 01jan87 [added line-level error recovery]
25  * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
26  * vix 30dec86 [written]
27  */
28 
29 
30 #include "cron.h"
31 #include <grp.h>
32 #ifdef LOGIN_CAP
33 #include <login_cap.h>
34 #endif
35 
36 typedef	enum ecode {
37 	e_none, e_minute, e_hour, e_dom, e_month, e_dow,
38 	e_cmd, e_timespec, e_username, e_group, e_mem
39 #ifdef LOGIN_CAP
40 	, e_class
41 #endif
42 } ecode_e;
43 
44 static char	get_list __P((bitstr_t *, int, int, char *[], int, FILE *)),
45 		get_range __P((bitstr_t *, int, int, char *[], int, FILE *)),
46 		get_number __P((int *, int, char *[], int, FILE *));
47 static int	set_element __P((bitstr_t *, int, int, int));
48 
49 static char *ecodes[] =
50 	{
51 		"no error",
52 		"bad minute",
53 		"bad hour",
54 		"bad day-of-month",
55 		"bad month",
56 		"bad day-of-week",
57 		"bad command",
58 		"bad time specifier",
59 		"bad username",
60 		"bad group name",
61 		"out of memory",
62 #ifdef LOGIN_CAP
63 		"bad class name",
64 #endif
65 	};
66 
67 
68 void
69 free_entry(e)
70 	entry	*e;
71 {
72 #ifdef LOGIN_CAP
73 	if (e->class != NULL)
74 		free(e->class);
75 #endif
76 	free(e->cmd);
77 	env_free(e->envp);
78 	free(e);
79 }
80 
81 
82 /* return NULL if eof or syntax error occurs;
83  * otherwise return a pointer to a new entry.
84  */
85 entry *
86 load_entry(file, error_func, pw, envp)
87 	FILE		*file;
88 	void		(*error_func)();
89 	struct passwd	*pw;
90 	char		**envp;
91 {
92 	/* this function reads one crontab entry -- the next -- from a file.
93 	 * it skips any leading blank lines, ignores comments, and returns
94 	 * EOF if for any reason the entry can't be read and parsed.
95 	 *
96 	 * the entry is also parsed here.
97 	 *
98 	 * syntax:
99 	 *   user crontab:
100 	 *	minutes hours doms months dows cmd\n
101 	 *   system crontab (/etc/crontab):
102 	 *	minutes hours doms months dows USERNAME cmd\n
103 	 */
104 
105 	ecode_e	ecode = e_none;
106 	entry	*e;
107 	int	ch;
108 	char	cmd[MAX_COMMAND];
109 	char	envstr[MAX_ENVSTR];
110 	char	**prev_env;
111 
112 	Debug(DPARS, ("load_entry()...about to eat comments\n"))
113 
114 	skip_comments(file);
115 
116 	ch = get_char(file);
117 	if (ch == EOF)
118 		return NULL;
119 
120 	/* ch is now the first useful character of a useful line.
121 	 * it may be an @special or it may be the first character
122 	 * of a list of minutes.
123 	 */
124 
125 	e = (entry *) calloc(sizeof(entry), sizeof(char));
126 
127 	if (e == NULL) {
128 		warn("load_entry: calloc failed");
129 		return NULL;
130 	}
131 
132 	if (ch == '@') {
133 		/* all of these should be flagged and load-limited; i.e.,
134 		 * instead of @hourly meaning "0 * * * *" it should mean
135 		 * "close to the front of every hour but not 'til the
136 		 * system load is low".  Problems are: how do you know
137 		 * what "low" means? (save me from /etc/cron.conf!) and:
138 		 * how to guarantee low variance (how low is low?), which
139 		 * means how to we run roughly every hour -- seems like
140 		 * we need to keep a history or let the first hour set
141 		 * the schedule, which means we aren't load-limited
142 		 * anymore.  too much for my overloaded brain. (vix, jan90)
143 		 * HINT
144 		 */
145 		Debug(DPARS, ("load_entry()...about to test shortcuts\n"))
146 		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
147 		if (!strcmp("reboot", cmd)) {
148 			Debug(DPARS, ("load_entry()...reboot shortcut\n"))
149 			e->flags |= WHEN_REBOOT;
150 		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
151 			Debug(DPARS, ("load_entry()...yearly shortcut\n"))
152 			bit_set(e->minute, 0);
153 			bit_set(e->hour, 0);
154 			bit_set(e->dom, 0);
155 			bit_set(e->month, 0);
156 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
157 		} else if (!strcmp("monthly", cmd)) {
158 			Debug(DPARS, ("load_entry()...monthly shortcut\n"))
159 			bit_set(e->minute, 0);
160 			bit_set(e->hour, 0);
161 			bit_set(e->dom, 0);
162 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
163 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
164 		} else if (!strcmp("weekly", cmd)) {
165 			Debug(DPARS, ("load_entry()...weekly shortcut\n"))
166 			bit_set(e->minute, 0);
167 			bit_set(e->hour, 0);
168 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
169 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
170 			bit_set(e->dow, 0);
171 		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
172 			Debug(DPARS, ("load_entry()...daily shortcut\n"))
173 			bit_set(e->minute, 0);
174 			bit_set(e->hour, 0);
175 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
176 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
177 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
178 		} else if (!strcmp("hourly", cmd)) {
179 			Debug(DPARS, ("load_entry()...hourly shortcut\n"))
180 			bit_set(e->minute, 0);
181 			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
182 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
183 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
184 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
185 		} else {
186 			ecode = e_timespec;
187 			goto eof;
188 		}
189 		/* Advance past whitespace between shortcut and
190 		 * username/command.
191 		 */
192 		Skip_Blanks(ch, file);
193 		if (ch == EOF) {
194 			ecode = e_cmd;
195 			goto eof;
196 		}
197 	} else {
198 		Debug(DPARS, ("load_entry()...about to parse numerics\n"))
199 
200 		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
201 			      PPC_NULL, ch, file);
202 		if (ch == EOF) {
203 			ecode = e_minute;
204 			goto eof;
205 		}
206 
207 		/* hours
208 		 */
209 
210 		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
211 			      PPC_NULL, ch, file);
212 		if (ch == EOF) {
213 			ecode = e_hour;
214 			goto eof;
215 		}
216 
217 		/* DOM (days of month)
218 		 */
219 
220 		if (ch == '*')
221 			e->flags |= DOM_STAR;
222 		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
223 			      PPC_NULL, ch, file);
224 		if (ch == EOF) {
225 			ecode = e_dom;
226 			goto eof;
227 		}
228 
229 		/* month
230 		 */
231 
232 		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
233 			      MonthNames, ch, file);
234 		if (ch == EOF) {
235 			ecode = e_month;
236 			goto eof;
237 		}
238 
239 		/* DOW (days of week)
240 		 */
241 
242 		if (ch == '*')
243 			e->flags |= DOW_STAR;
244 		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
245 			      DowNames, ch, file);
246 		if (ch == EOF) {
247 			ecode = e_dow;
248 			goto eof;
249 		}
250 	}
251 
252 	/* make sundays equivilent */
253 	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
254 		bit_set(e->dow, 0);
255 		bit_set(e->dow, 7);
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 		char            *s, *group;
264 		struct group    *grp;
265 
266 		Debug(DPARS, ("load_entry()...about to parse username\n"))
267 		ch = get_string(username, MAX_COMMAND, file, " \t");
268 
269 		Debug(DPARS, ("load_entry()...got %s\n",username))
270 		if (ch == EOF) {
271 			ecode = e_cmd;
272 			goto eof;
273 		}
274 
275 #ifdef LOGIN_CAP
276 		if ((s = strrchr(username, '/')) != NULL) {
277 			*s = '\0';
278 			e->class = strdup(s + 1);
279 			if (e->class == NULL)
280 				warn("strdup(\"%s\")", s + 1);
281 		} else {
282 			e->class = strdup(RESOURCE_RC);
283 			if (e->class == NULL)
284 				warn("strdup(\"%s\")", RESOURCE_RC);
285 		}
286 		if (e->class == NULL) {
287 			ecode = e_mem;
288 			goto eof;
289 		}
290 		if (login_getclass(e->class) == NULL) {
291 			ecode = e_class;
292 			goto eof;
293 		}
294 #endif
295 		grp = NULL;
296 		if ((s = strrchr(username, ':')) != NULL) {
297 			*s = '\0';
298 			if ((grp = getgrnam(s + 1)) == NULL) {
299 				ecode = e_group;
300 				goto eof;
301 			}
302 		}
303 
304 		pw = getpwnam(username);
305 		if (pw == NULL) {
306 			ecode = e_username;
307 			goto eof;
308 		}
309 		if (grp != NULL)
310 			pw->pw_gid = grp->gr_gid;
311 		Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
312 #ifdef LOGIN_CAP
313 		Debug(DPARS, ("load_entry()...class %s\n",e->class))
314 #endif
315 	}
316 
317 	if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
318 		ecode = e_username;
319 		goto eof;
320 	}
321 
322 	e->uid = pw->pw_uid;
323 	e->gid = pw->pw_gid;
324 
325 	/* copy and fix up environment.  some variables are just defaults and
326 	 * others are overrides.
327 	 */
328 	e->envp = env_copy(envp);
329 	if (e->envp == NULL) {
330 		warn("env_copy");
331 		ecode = e_mem;
332 		goto eof;
333 	}
334 	if (!env_get("SHELL", e->envp)) {
335 		prev_env = e->envp;
336 		sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
337 		e->envp = env_set(e->envp, envstr);
338 		if (e->envp == NULL) {
339 			warn("env_set(%s)", envstr);
340 			env_free(prev_env);
341 			ecode = e_mem;
342 			goto eof;
343 		}
344 	}
345 	prev_env = e->envp;
346 	sprintf(envstr, "HOME=%s", pw->pw_dir);
347 	e->envp = env_set(e->envp, envstr);
348 	if (e->envp == NULL) {
349 		warn("env_set(%s)", envstr);
350 		env_free(prev_env);
351 		ecode = e_mem;
352 		goto eof;
353 	}
354 	if (!env_get("PATH", e->envp)) {
355 		prev_env = e->envp;
356 		sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
357 		e->envp = env_set(e->envp, envstr);
358 		if (e->envp == NULL) {
359 			warn("env_set(%s)", envstr);
360 			env_free(prev_env);
361 			ecode = e_mem;
362 			goto eof;
363 		}
364 	}
365 	prev_env = e->envp;
366 	sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
367 	e->envp = env_set(e->envp, envstr);
368 	if (e->envp == NULL) {
369 		warn("env_set(%s)", envstr);
370 		env_free(prev_env);
371 		ecode = e_mem;
372 		goto eof;
373 	}
374 #if defined(BSD)
375 	prev_env = e->envp;
376 	sprintf(envstr, "%s=%s", "USER", pw->pw_name);
377 	e->envp = env_set(e->envp, envstr);
378 	if (e->envp == NULL) {
379 		warn("env_set(%s)", envstr);
380 		env_free(prev_env);
381 		ecode = e_mem;
382 		goto eof;
383 	}
384 #endif
385 
386 	Debug(DPARS, ("load_entry()...about to parse command\n"))
387 
388 	/* Everything up to the next \n or EOF is part of the command...
389 	 * too bad we don't know in advance how long it will be, since we
390 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
391 	 * XXX - should use realloc().
392 	 */
393 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
394 
395 	/* a file without a \n before the EOF is rude, so we'll complain...
396 	 */
397 	if (ch == EOF) {
398 		env_free(e->envp);
399 		ecode = e_cmd;
400 		goto eof;
401 	}
402 
403 	/* got the command in the 'cmd' string; save it in *e.
404 	 */
405 	e->cmd = strdup(cmd);
406 	if (e->cmd == NULL) {
407 		warn("strdup(\"%d\")", cmd);
408 		env_free(e->envp);
409 		ecode = e_mem;
410 		goto eof;
411 	}
412 	Debug(DPARS, ("load_entry()...returning successfully\n"))
413 
414 	/* success, fini, return pointer to the entry we just created...
415 	 */
416 	return e;
417 
418  eof:
419 	free(e);
420 	if (ecode != e_none && error_func)
421 		(*error_func)(ecodes[(int)ecode]);
422 	while (ch != EOF && ch != '\n')
423 		ch = get_char(file);
424 	return NULL;
425 }
426 
427 
428 static char
429 get_list(bits, low, high, names, ch, file)
430 	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
431 	int		low, high;	/* bounds, impl. offset for bitstr */
432 	char		*names[];	/* NULL or *[] of names for these elements */
433 	int		ch;		/* current character being processed */
434 	FILE		*file;		/* file being read */
435 {
436 	register int	done;
437 
438 	/* we know that we point to a non-blank character here;
439 	 * must do a Skip_Blanks before we exit, so that the
440 	 * next call (or the code that picks up the cmd) can
441 	 * assume the same thing.
442 	 */
443 
444 	Debug(DPARS|DEXT, ("get_list()...entered\n"))
445 
446 	/* list = range {"," range}
447 	 */
448 
449 	/* clear the bit string, since the default is 'off'.
450 	 */
451 	bit_nclear(bits, 0, (high-low+1));
452 
453 	/* process all ranges
454 	 */
455 	done = FALSE;
456 	while (!done) {
457 		ch = get_range(bits, low, high, names, ch, file);
458 		if (ch == ',')
459 			ch = get_char(file);
460 		else
461 			done = TRUE;
462 	}
463 
464 	/* exiting.  skip to some blanks, then skip over the blanks.
465 	 */
466 	Skip_Nonblanks(ch, file)
467 	Skip_Blanks(ch, file)
468 
469 	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
470 
471 	return ch;
472 }
473 
474 
475 static char
476 get_range(bits, low, high, names, ch, file)
477 	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
478 	int		low, high;	/* bounds, impl. offset for bitstr */
479 	char		*names[];	/* NULL or names of elements */
480 	int		ch;		/* current character being processed */
481 	FILE		*file;		/* file being read */
482 {
483 	/* range = number | number "-" number [ "/" number ]
484 	 */
485 
486 	register int	i;
487 	auto int	num1, num2, num3;
488 
489 	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
490 
491 	if (ch == '*') {
492 		/* '*' means "first-last" but can still be modified by /step
493 		 */
494 		num1 = low;
495 		num2 = high;
496 		ch = get_char(file);
497 		if (ch == EOF)
498 			return EOF;
499 	} else {
500 		if (EOF == (ch = get_number(&num1, low, names, ch, file)))
501 			return EOF;
502 
503 		if (ch != '-') {
504 			/* not a range, it's a single number.
505 			 */
506 			if (EOF == set_element(bits, low, high, num1))
507 				return EOF;
508 			return ch;
509 		} else {
510 			/* eat the dash
511 			 */
512 			ch = get_char(file);
513 			if (ch == EOF)
514 				return EOF;
515 
516 			/* get the number following the dash
517 			 */
518 			ch = get_number(&num2, low, names, ch, file);
519 			if (ch == EOF)
520 				return EOF;
521 		}
522 	}
523 
524 	/* check for step size
525 	 */
526 	if (ch == '/') {
527 		/* eat the slash
528 		 */
529 		ch = get_char(file);
530 		if (ch == EOF)
531 			return EOF;
532 
533 		/* get the step size -- note: we don't pass the
534 		 * names here, because the number is not an
535 		 * element id, it's a step size.  'low' is
536 		 * sent as a 0 since there is no offset either.
537 		 */
538 		ch = get_number(&num3, 0, PPC_NULL, ch, file);
539 		if (ch == EOF)
540 			return EOF;
541 	} else {
542 		/* no step.  default==1.
543 		 */
544 		num3 = 1;
545 	}
546 
547 	/* range. set all elements from num1 to num2, stepping
548 	 * by num3.  (the step is a downward-compatible extension
549 	 * proposed conceptually by bob@acornrc, syntactically
550 	 * designed then implmented by paul vixie).
551 	 */
552 	for (i = num1;  i <= num2;  i += num3)
553 		if (EOF == set_element(bits, low, high, i))
554 			return EOF;
555 
556 	return ch;
557 }
558 
559 
560 static char
561 get_number(numptr, low, names, ch, file)
562 	int	*numptr;	/* where does the result go? */
563 	int	low;		/* offset applied to result if symbolic enum used */
564 	char	*names[];	/* symbolic names, if any, for enums */
565 	int	ch;		/* current character */
566 	FILE	*file;		/* source */
567 {
568 	char	temp[MAX_TEMPSTR], *pc;
569 	int	len, i, all_digits;
570 
571 	/* collect alphanumerics into our fixed-size temp array
572 	 */
573 	pc = temp;
574 	len = 0;
575 	all_digits = TRUE;
576 	while (isalnum(ch)) {
577 		if (++len >= MAX_TEMPSTR)
578 			return EOF;
579 
580 		*pc++ = ch;
581 
582 		if (!isdigit(ch))
583 			all_digits = FALSE;
584 
585 		ch = get_char(file);
586 	}
587 	*pc = '\0';
588 
589 	/* try to find the name in the name list
590 	 */
591 	if (names) {
592 		for (i = 0;  names[i] != NULL;  i++) {
593 			Debug(DPARS|DEXT,
594 				("get_num, compare(%s,%s)\n", names[i], temp))
595 			if (!strcasecmp(names[i], temp)) {
596 				*numptr = i+low;
597 				return ch;
598 			}
599 		}
600 	}
601 
602 	/* no name list specified, or there is one and our string isn't
603 	 * in it.  either way: if it's all digits, use its magnitude.
604 	 * otherwise, it's an error.
605 	 */
606 	if (all_digits) {
607 		*numptr = atoi(temp);
608 		return ch;
609 	}
610 
611 	return EOF;
612 }
613 
614 
615 static int
616 set_element(bits, low, high, number)
617 	bitstr_t	*bits; 		/* one bit per flag, default=FALSE */
618 	int		low;
619 	int		high;
620 	int		number;
621 {
622 	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
623 
624 	if (number < low || number > high)
625 		return EOF;
626 
627 	bit_set(bits, (number-low));
628 	return OK;
629 }
630