xref: /freebsd/usr.sbin/cron/lib/entry.c (revision 5521ff5a4d1929056e7ffc982fac3341ca54df7c)
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 #ifdef LOGIN_CAP
266 		login_cap_t *lc;
267 #endif
268 
269 		Debug(DPARS, ("load_entry()...about to parse username\n"))
270 		ch = get_string(username, MAX_COMMAND, file, " \t");
271 
272 		Debug(DPARS, ("load_entry()...got %s\n",username))
273 		if (ch == EOF) {
274 			ecode = e_cmd;
275 			goto eof;
276 		}
277 
278 #ifdef LOGIN_CAP
279 		if ((s = strrchr(username, '/')) != NULL) {
280 			*s = '\0';
281 			e->class = strdup(s + 1);
282 			if (e->class == NULL)
283 				warn("strdup(\"%s\")", s + 1);
284 		} else {
285 			e->class = strdup(RESOURCE_RC);
286 			if (e->class == NULL)
287 				warn("strdup(\"%s\")", RESOURCE_RC);
288 		}
289 		if (e->class == NULL) {
290 			ecode = e_mem;
291 			goto eof;
292 		}
293 		if ((lc = login_getclass(e->class)) == NULL) {
294 			ecode = e_class;
295 			goto eof;
296 		}
297 		login_close(lc);
298 #endif
299 		grp = NULL;
300 		if ((s = strrchr(username, ':')) != NULL) {
301 			*s = '\0';
302 			if ((grp = getgrnam(s + 1)) == NULL) {
303 				ecode = e_group;
304 				goto eof;
305 			}
306 		}
307 
308 		pw = getpwnam(username);
309 		if (pw == NULL) {
310 			ecode = e_username;
311 			goto eof;
312 		}
313 		if (grp != NULL)
314 			pw->pw_gid = grp->gr_gid;
315 		Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
316 #ifdef LOGIN_CAP
317 		Debug(DPARS, ("load_entry()...class %s\n",e->class))
318 #endif
319 	}
320 
321 	if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
322 		ecode = e_username;
323 		goto eof;
324 	}
325 
326 	e->uid = pw->pw_uid;
327 	e->gid = pw->pw_gid;
328 
329 	/* copy and fix up environment.  some variables are just defaults and
330 	 * others are overrides.
331 	 */
332 	e->envp = env_copy(envp);
333 	if (e->envp == NULL) {
334 		warn("env_copy");
335 		ecode = e_mem;
336 		goto eof;
337 	}
338 	if (!env_get("SHELL", e->envp)) {
339 		prev_env = e->envp;
340 		sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
341 		e->envp = env_set(e->envp, envstr);
342 		if (e->envp == NULL) {
343 			warn("env_set(%s)", envstr);
344 			env_free(prev_env);
345 			ecode = e_mem;
346 			goto eof;
347 		}
348 	}
349 	prev_env = e->envp;
350 	sprintf(envstr, "HOME=%s", pw->pw_dir);
351 	e->envp = env_set(e->envp, envstr);
352 	if (e->envp == NULL) {
353 		warn("env_set(%s)", envstr);
354 		env_free(prev_env);
355 		ecode = e_mem;
356 		goto eof;
357 	}
358 	if (!env_get("PATH", e->envp)) {
359 		prev_env = e->envp;
360 		sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
361 		e->envp = env_set(e->envp, envstr);
362 		if (e->envp == NULL) {
363 			warn("env_set(%s)", envstr);
364 			env_free(prev_env);
365 			ecode = e_mem;
366 			goto eof;
367 		}
368 	}
369 	prev_env = e->envp;
370 	sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
371 	e->envp = env_set(e->envp, envstr);
372 	if (e->envp == NULL) {
373 		warn("env_set(%s)", envstr);
374 		env_free(prev_env);
375 		ecode = e_mem;
376 		goto eof;
377 	}
378 #if defined(BSD)
379 	prev_env = e->envp;
380 	sprintf(envstr, "%s=%s", "USER", pw->pw_name);
381 	e->envp = env_set(e->envp, envstr);
382 	if (e->envp == NULL) {
383 		warn("env_set(%s)", envstr);
384 		env_free(prev_env);
385 		ecode = e_mem;
386 		goto eof;
387 	}
388 #endif
389 
390 	Debug(DPARS, ("load_entry()...about to parse command\n"))
391 
392 	/* Everything up to the next \n or EOF is part of the command...
393 	 * too bad we don't know in advance how long it will be, since we
394 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
395 	 * XXX - should use realloc().
396 	 */
397 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
398 
399 	/* a file without a \n before the EOF is rude, so we'll complain...
400 	 */
401 	if (ch == EOF) {
402 		env_free(e->envp);
403 		ecode = e_cmd;
404 		goto eof;
405 	}
406 
407 	/* got the command in the 'cmd' string; save it in *e.
408 	 */
409 	e->cmd = strdup(cmd);
410 	if (e->cmd == NULL) {
411 		warn("strdup(\"%d\")", cmd);
412 		env_free(e->envp);
413 		ecode = e_mem;
414 		goto eof;
415 	}
416 	Debug(DPARS, ("load_entry()...returning successfully\n"))
417 
418 	/* success, fini, return pointer to the entry we just created...
419 	 */
420 	return e;
421 
422  eof:
423 	free_entry(e);
424 	if (ecode != e_none && error_func)
425 		(*error_func)(ecodes[(int)ecode]);
426 	while (ch != EOF && ch != '\n')
427 		ch = get_char(file);
428 	return NULL;
429 }
430 
431 
432 static char
433 get_list(bits, low, high, names, ch, file)
434 	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
435 	int		low, high;	/* bounds, impl. offset for bitstr */
436 	char		*names[];	/* NULL or *[] of names for these elements */
437 	int		ch;		/* current character being processed */
438 	FILE		*file;		/* file being read */
439 {
440 	register int	done;
441 
442 	/* we know that we point to a non-blank character here;
443 	 * must do a Skip_Blanks before we exit, so that the
444 	 * next call (or the code that picks up the cmd) can
445 	 * assume the same thing.
446 	 */
447 
448 	Debug(DPARS|DEXT, ("get_list()...entered\n"))
449 
450 	/* list = range {"," range}
451 	 */
452 
453 	/* clear the bit string, since the default is 'off'.
454 	 */
455 	bit_nclear(bits, 0, (high-low+1));
456 
457 	/* process all ranges
458 	 */
459 	done = FALSE;
460 	while (!done) {
461 		ch = get_range(bits, low, high, names, ch, file);
462 		if (ch == ',')
463 			ch = get_char(file);
464 		else
465 			done = TRUE;
466 	}
467 
468 	/* exiting.  skip to some blanks, then skip over the blanks.
469 	 */
470 	Skip_Nonblanks(ch, file)
471 	Skip_Blanks(ch, file)
472 
473 	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
474 
475 	return ch;
476 }
477 
478 
479 static char
480 get_range(bits, low, high, names, ch, file)
481 	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
482 	int		low, high;	/* bounds, impl. offset for bitstr */
483 	char		*names[];	/* NULL or names of elements */
484 	int		ch;		/* current character being processed */
485 	FILE		*file;		/* file being read */
486 {
487 	/* range = number | number "-" number [ "/" number ]
488 	 */
489 
490 	register int	i;
491 	auto int	num1, num2, num3;
492 
493 	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
494 
495 	if (ch == '*') {
496 		/* '*' means "first-last" but can still be modified by /step
497 		 */
498 		num1 = low;
499 		num2 = high;
500 		ch = get_char(file);
501 		if (ch == EOF)
502 			return EOF;
503 	} else {
504 		if (EOF == (ch = get_number(&num1, low, names, ch, file)))
505 			return EOF;
506 
507 		if (ch != '-') {
508 			/* not a range, it's a single number.
509 			 */
510 			if (EOF == set_element(bits, low, high, num1))
511 				return EOF;
512 			return ch;
513 		} else {
514 			/* eat the dash
515 			 */
516 			ch = get_char(file);
517 			if (ch == EOF)
518 				return EOF;
519 
520 			/* get the number following the dash
521 			 */
522 			ch = get_number(&num2, low, names, ch, file);
523 			if (ch == EOF)
524 				return EOF;
525 		}
526 	}
527 
528 	/* check for step size
529 	 */
530 	if (ch == '/') {
531 		/* eat the slash
532 		 */
533 		ch = get_char(file);
534 		if (ch == EOF)
535 			return EOF;
536 
537 		/* get the step size -- note: we don't pass the
538 		 * names here, because the number is not an
539 		 * element id, it's a step size.  'low' is
540 		 * sent as a 0 since there is no offset either.
541 		 */
542 		ch = get_number(&num3, 0, PPC_NULL, ch, file);
543 		if (ch == EOF)
544 			return EOF;
545 	} else {
546 		/* no step.  default==1.
547 		 */
548 		num3 = 1;
549 	}
550 
551 	/* range. set all elements from num1 to num2, stepping
552 	 * by num3.  (the step is a downward-compatible extension
553 	 * proposed conceptually by bob@acornrc, syntactically
554 	 * designed then implmented by paul vixie).
555 	 */
556 	for (i = num1;  i <= num2;  i += num3)
557 		if (EOF == set_element(bits, low, high, i))
558 			return EOF;
559 
560 	return ch;
561 }
562 
563 
564 static char
565 get_number(numptr, low, names, ch, file)
566 	int	*numptr;	/* where does the result go? */
567 	int	low;		/* offset applied to result if symbolic enum used */
568 	char	*names[];	/* symbolic names, if any, for enums */
569 	int	ch;		/* current character */
570 	FILE	*file;		/* source */
571 {
572 	char	temp[MAX_TEMPSTR], *pc;
573 	int	len, i, all_digits;
574 
575 	/* collect alphanumerics into our fixed-size temp array
576 	 */
577 	pc = temp;
578 	len = 0;
579 	all_digits = TRUE;
580 	while (isalnum(ch)) {
581 		if (++len >= MAX_TEMPSTR)
582 			return EOF;
583 
584 		*pc++ = ch;
585 
586 		if (!isdigit(ch))
587 			all_digits = FALSE;
588 
589 		ch = get_char(file);
590 	}
591 	*pc = '\0';
592 
593 	/* try to find the name in the name list
594 	 */
595 	if (names) {
596 		for (i = 0;  names[i] != NULL;  i++) {
597 			Debug(DPARS|DEXT,
598 				("get_num, compare(%s,%s)\n", names[i], temp))
599 			if (!strcasecmp(names[i], temp)) {
600 				*numptr = i+low;
601 				return ch;
602 			}
603 		}
604 	}
605 
606 	/* no name list specified, or there is one and our string isn't
607 	 * in it.  either way: if it's all digits, use its magnitude.
608 	 * otherwise, it's an error.
609 	 */
610 	if (all_digits) {
611 		*numptr = atoi(temp);
612 		return ch;
613 	}
614 
615 	return EOF;
616 }
617 
618 
619 static int
620 set_element(bits, low, high, number)
621 	bitstr_t	*bits; 		/* one bit per flag, default=FALSE */
622 	int		low;
623 	int		high;
624 	int		number;
625 {
626 	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
627 
628 	if (number < low || number > high)
629 		return EOF;
630 
631 	bit_set(bits, (number-low));
632 	return OK;
633 }
634