xref: /dragonfly/usr.sbin/cron/lib/entry.c (revision 1de703da)
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  * $FreeBSD: src/usr.sbin/cron/lib/entry.c,v 1.9.2.5 2001/08/18 04:20:31 mikeh Exp $
18  * $DragonFly: src/usr.sbin/cron/lib/entry.c,v 1.2 2003/06/17 04:29:53 dillon Exp $
19  */
20 
21 /* vix 26jan87 [RCS'd; rest of log is in RCS file]
22  * vix 01jan87 [added line-level error recovery]
23  * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
24  * vix 30dec86 [written]
25  */
26 
27 
28 #include "cron.h"
29 #include <grp.h>
30 #ifdef LOGIN_CAP
31 #include <login_cap.h>
32 #endif
33 
34 typedef	enum ecode {
35 	e_none, e_minute, e_hour, e_dom, e_month, e_dow,
36 	e_cmd, e_timespec, e_username, e_group, e_mem
37 #ifdef LOGIN_CAP
38 	, e_class
39 #endif
40 } ecode_e;
41 
42 static char	get_list __P((bitstr_t *, int, int, char *[], int, FILE *)),
43 		get_range __P((bitstr_t *, int, int, char *[], int, FILE *)),
44 		get_number __P((int *, int, char *[], int, FILE *));
45 static int	set_element __P((bitstr_t *, int, int, int));
46 
47 static char *ecodes[] =
48 	{
49 		"no error",
50 		"bad minute",
51 		"bad hour",
52 		"bad day-of-month",
53 		"bad month",
54 		"bad day-of-week",
55 		"bad command",
56 		"bad time specifier",
57 		"bad username",
58 		"bad group name",
59 		"out of memory",
60 #ifdef LOGIN_CAP
61 		"bad class name",
62 #endif
63 	};
64 
65 
66 void
67 free_entry(e)
68 	entry	*e;
69 {
70 #ifdef LOGIN_CAP
71 	if (e->class != NULL)
72 		free(e->class);
73 #endif
74 	if (e->cmd != NULL)
75 		free(e->cmd);
76 	if (e->envp != NULL)
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 			e->flags |= DOW_STAR;
158 		} else if (!strcmp("monthly", cmd)) {
159 			Debug(DPARS, ("load_entry()...monthly shortcut\n"))
160 			bit_set(e->minute, 0);
161 			bit_set(e->hour, 0);
162 			bit_set(e->dom, 0);
163 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
164 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
165 			e->flags |= DOW_STAR;
166 		} else if (!strcmp("weekly", cmd)) {
167 			Debug(DPARS, ("load_entry()...weekly shortcut\n"))
168 			bit_set(e->minute, 0);
169 			bit_set(e->hour, 0);
170 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
171 			e->flags |= DOM_STAR;
172 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
173 			bit_set(e->dow, 0);
174 		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
175 			Debug(DPARS, ("load_entry()...daily shortcut\n"))
176 			bit_set(e->minute, 0);
177 			bit_set(e->hour, 0);
178 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
179 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
180 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
181 		} else if (!strcmp("hourly", cmd)) {
182 			Debug(DPARS, ("load_entry()...hourly shortcut\n"))
183 			bit_set(e->minute, 0);
184 			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
185 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
186 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
187 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
188 		} else {
189 			ecode = e_timespec;
190 			goto eof;
191 		}
192 		/* Advance past whitespace between shortcut and
193 		 * username/command.
194 		 */
195 		Skip_Blanks(ch, file);
196 		if (ch == EOF) {
197 			ecode = e_cmd;
198 			goto eof;
199 		}
200 	} else {
201 		Debug(DPARS, ("load_entry()...about to parse numerics\n"))
202 
203 		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
204 			      PPC_NULL, ch, file);
205 		if (ch == EOF) {
206 			ecode = e_minute;
207 			goto eof;
208 		}
209 
210 		/* hours
211 		 */
212 
213 		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
214 			      PPC_NULL, ch, file);
215 		if (ch == EOF) {
216 			ecode = e_hour;
217 			goto eof;
218 		}
219 
220 		/* DOM (days of month)
221 		 */
222 
223 		if (ch == '*')
224 			e->flags |= DOM_STAR;
225 		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
226 			      PPC_NULL, ch, file);
227 		if (ch == EOF) {
228 			ecode = e_dom;
229 			goto eof;
230 		}
231 
232 		/* month
233 		 */
234 
235 		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
236 			      MonthNames, ch, file);
237 		if (ch == EOF) {
238 			ecode = e_month;
239 			goto eof;
240 		}
241 
242 		/* DOW (days of week)
243 		 */
244 
245 		if (ch == '*')
246 			e->flags |= DOW_STAR;
247 		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
248 			      DowNames, ch, file);
249 		if (ch == EOF) {
250 			ecode = e_dow;
251 			goto eof;
252 		}
253 	}
254 
255 	/* make sundays equivilent */
256 	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
257 		bit_set(e->dow, 0);
258 		bit_set(e->dow, 7);
259 	}
260 
261 	/* ch is the first character of a command, or a username */
262 	unget_char(ch, file);
263 
264 	if (!pw) {
265 		char		*username = cmd;	/* temp buffer */
266 		char            *s, *group;
267 		struct group    *grp;
268 #ifdef LOGIN_CAP
269 		login_cap_t *lc;
270 #endif
271 
272 		Debug(DPARS, ("load_entry()...about to parse username\n"))
273 		ch = get_string(username, MAX_COMMAND, file, " \t");
274 
275 		Debug(DPARS, ("load_entry()...got %s\n",username))
276 		if (ch == EOF) {
277 			ecode = e_cmd;
278 			goto eof;
279 		}
280 
281 #ifdef LOGIN_CAP
282 		if ((s = strrchr(username, '/')) != NULL) {
283 			*s = '\0';
284 			e->class = strdup(s + 1);
285 			if (e->class == NULL)
286 				warn("strdup(\"%s\")", s + 1);
287 		} else {
288 			e->class = strdup(RESOURCE_RC);
289 			if (e->class == NULL)
290 				warn("strdup(\"%s\")", RESOURCE_RC);
291 		}
292 		if (e->class == NULL) {
293 			ecode = e_mem;
294 			goto eof;
295 		}
296 		if ((lc = login_getclass(e->class)) == NULL) {
297 			ecode = e_class;
298 			goto eof;
299 		}
300 		login_close(lc);
301 #endif
302 		grp = NULL;
303 		if ((s = strrchr(username, ':')) != NULL) {
304 			*s = '\0';
305 			if ((grp = getgrnam(s + 1)) == NULL) {
306 				ecode = e_group;
307 				goto eof;
308 			}
309 		}
310 
311 		pw = getpwnam(username);
312 		if (pw == NULL) {
313 			ecode = e_username;
314 			goto eof;
315 		}
316 		if (grp != NULL)
317 			pw->pw_gid = grp->gr_gid;
318 		Debug(DPARS, ("load_entry()...uid %d, gid %d\n",pw->pw_uid,pw->pw_gid))
319 #ifdef LOGIN_CAP
320 		Debug(DPARS, ("load_entry()...class %s\n",e->class))
321 #endif
322 	}
323 
324 	if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
325 		ecode = e_username;
326 		goto eof;
327 	}
328 
329 	e->uid = pw->pw_uid;
330 	e->gid = pw->pw_gid;
331 
332 	/* copy and fix up environment.  some variables are just defaults and
333 	 * others are overrides.
334 	 */
335 	e->envp = env_copy(envp);
336 	if (e->envp == NULL) {
337 		warn("env_copy");
338 		ecode = e_mem;
339 		goto eof;
340 	}
341 	if (!env_get("SHELL", e->envp)) {
342 		prev_env = e->envp;
343 		sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
344 		e->envp = env_set(e->envp, envstr);
345 		if (e->envp == NULL) {
346 			warn("env_set(%s)", envstr);
347 			env_free(prev_env);
348 			ecode = e_mem;
349 			goto eof;
350 		}
351 	}
352 	prev_env = e->envp;
353 	sprintf(envstr, "HOME=%s", pw->pw_dir);
354 	e->envp = env_set(e->envp, envstr);
355 	if (e->envp == NULL) {
356 		warn("env_set(%s)", envstr);
357 		env_free(prev_env);
358 		ecode = e_mem;
359 		goto eof;
360 	}
361 	if (!env_get("PATH", e->envp)) {
362 		prev_env = e->envp;
363 		sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
364 		e->envp = env_set(e->envp, envstr);
365 		if (e->envp == NULL) {
366 			warn("env_set(%s)", envstr);
367 			env_free(prev_env);
368 			ecode = e_mem;
369 			goto eof;
370 		}
371 	}
372 	prev_env = e->envp;
373 	sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
374 	e->envp = env_set(e->envp, envstr);
375 	if (e->envp == NULL) {
376 		warn("env_set(%s)", envstr);
377 		env_free(prev_env);
378 		ecode = e_mem;
379 		goto eof;
380 	}
381 #if defined(BSD)
382 	prev_env = e->envp;
383 	sprintf(envstr, "%s=%s", "USER", pw->pw_name);
384 	e->envp = env_set(e->envp, envstr);
385 	if (e->envp == NULL) {
386 		warn("env_set(%s)", envstr);
387 		env_free(prev_env);
388 		ecode = e_mem;
389 		goto eof;
390 	}
391 #endif
392 
393 	Debug(DPARS, ("load_entry()...about to parse command\n"))
394 
395 	/* Everything up to the next \n or EOF is part of the command...
396 	 * too bad we don't know in advance how long it will be, since we
397 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
398 	 * XXX - should use realloc().
399 	 */
400 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
401 
402 	/* a file without a \n before the EOF is rude, so we'll complain...
403 	 */
404 	if (ch == EOF) {
405 		ecode = e_cmd;
406 		goto eof;
407 	}
408 
409 	/* got the command in the 'cmd' string; save it in *e.
410 	 */
411 	e->cmd = strdup(cmd);
412 	if (e->cmd == NULL) {
413 		warn("strdup(\"%d\")", cmd);
414 		ecode = e_mem;
415 		goto eof;
416 	}
417 	Debug(DPARS, ("load_entry()...returning successfully\n"))
418 
419 	/* success, fini, return pointer to the entry we just created...
420 	 */
421 	return e;
422 
423  eof:
424 	free_entry(e);
425 	if (ecode != e_none && error_func)
426 		(*error_func)(ecodes[(int)ecode]);
427 	while (ch != EOF && ch != '\n')
428 		ch = get_char(file);
429 	return NULL;
430 }
431 
432 
433 static char
434 get_list(bits, low, high, names, ch, file)
435 	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
436 	int		low, high;	/* bounds, impl. offset for bitstr */
437 	char		*names[];	/* NULL or *[] of names for these elements */
438 	int		ch;		/* current character being processed */
439 	FILE		*file;		/* file being read */
440 {
441 	register int	done;
442 
443 	/* we know that we point to a non-blank character here;
444 	 * must do a Skip_Blanks before we exit, so that the
445 	 * next call (or the code that picks up the cmd) can
446 	 * assume the same thing.
447 	 */
448 
449 	Debug(DPARS|DEXT, ("get_list()...entered\n"))
450 
451 	/* list = range {"," range}
452 	 */
453 
454 	/* clear the bit string, since the default is 'off'.
455 	 */
456 	bit_nclear(bits, 0, (high-low+1));
457 
458 	/* process all ranges
459 	 */
460 	done = FALSE;
461 	while (!done) {
462 		ch = get_range(bits, low, high, names, ch, file);
463 		if (ch == ',')
464 			ch = get_char(file);
465 		else
466 			done = TRUE;
467 	}
468 
469 	/* exiting.  skip to some blanks, then skip over the blanks.
470 	 */
471 	Skip_Nonblanks(ch, file)
472 	Skip_Blanks(ch, file)
473 
474 	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
475 
476 	return ch;
477 }
478 
479 
480 static char
481 get_range(bits, low, high, names, ch, file)
482 	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
483 	int		low, high;	/* bounds, impl. offset for bitstr */
484 	char		*names[];	/* NULL or names of elements */
485 	int		ch;		/* current character being processed */
486 	FILE		*file;		/* file being read */
487 {
488 	/* range = number | number "-" number [ "/" number ]
489 	 */
490 
491 	register int	i;
492 	auto int	num1, num2, num3;
493 
494 	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
495 
496 	if (ch == '*') {
497 		/* '*' means "first-last" but can still be modified by /step
498 		 */
499 		num1 = low;
500 		num2 = high;
501 		ch = get_char(file);
502 		if (ch == EOF)
503 			return EOF;
504 	} else {
505 		if (EOF == (ch = get_number(&num1, low, names, ch, file)))
506 			return EOF;
507 
508 		if (ch != '-') {
509 			/* not a range, it's a single number.
510 			 */
511 			if (EOF == set_element(bits, low, high, num1))
512 				return EOF;
513 			return ch;
514 		} else {
515 			/* eat the dash
516 			 */
517 			ch = get_char(file);
518 			if (ch == EOF)
519 				return EOF;
520 
521 			/* get the number following the dash
522 			 */
523 			ch = get_number(&num2, low, names, ch, file);
524 			if (ch == EOF)
525 				return EOF;
526 		}
527 	}
528 
529 	/* check for step size
530 	 */
531 	if (ch == '/') {
532 		/* eat the slash
533 		 */
534 		ch = get_char(file);
535 		if (ch == EOF)
536 			return EOF;
537 
538 		/* get the step size -- note: we don't pass the
539 		 * names here, because the number is not an
540 		 * element id, it's a step size.  'low' is
541 		 * sent as a 0 since there is no offset either.
542 		 */
543 		ch = get_number(&num3, 0, PPC_NULL, ch, file);
544 		if (ch == EOF)
545 			return EOF;
546 	} else {
547 		/* no step.  default==1.
548 		 */
549 		num3 = 1;
550 	}
551 
552 	/* range. set all elements from num1 to num2, stepping
553 	 * by num3.  (the step is a downward-compatible extension
554 	 * proposed conceptually by bob@acornrc, syntactically
555 	 * designed then implmented by paul vixie).
556 	 */
557 	for (i = num1;  i <= num2;  i += num3)
558 		if (EOF == set_element(bits, low, high, i))
559 			return EOF;
560 
561 	return ch;
562 }
563 
564 
565 static char
566 get_number(numptr, low, names, ch, file)
567 	int	*numptr;	/* where does the result go? */
568 	int	low;		/* offset applied to result if symbolic enum used */
569 	char	*names[];	/* symbolic names, if any, for enums */
570 	int	ch;		/* current character */
571 	FILE	*file;		/* source */
572 {
573 	char	temp[MAX_TEMPSTR], *pc;
574 	int	len, i, all_digits;
575 
576 	/* collect alphanumerics into our fixed-size temp array
577 	 */
578 	pc = temp;
579 	len = 0;
580 	all_digits = TRUE;
581 	while (isalnum(ch)) {
582 		if (++len >= MAX_TEMPSTR)
583 			return EOF;
584 
585 		*pc++ = ch;
586 
587 		if (!isdigit(ch))
588 			all_digits = FALSE;
589 
590 		ch = get_char(file);
591 	}
592 	*pc = '\0';
593 
594 	/* try to find the name in the name list
595 	 */
596 	if (names) {
597 		for (i = 0;  names[i] != NULL;  i++) {
598 			Debug(DPARS|DEXT,
599 				("get_num, compare(%s,%s)\n", names[i], temp))
600 			if (!strcasecmp(names[i], temp)) {
601 				*numptr = i+low;
602 				return ch;
603 			}
604 		}
605 	}
606 
607 	/* no name list specified, or there is one and our string isn't
608 	 * in it.  either way: if it's all digits, use its magnitude.
609 	 * otherwise, it's an error.
610 	 */
611 	if (all_digits) {
612 		*numptr = atoi(temp);
613 		return ch;
614 	}
615 
616 	return EOF;
617 }
618 
619 
620 static int
621 set_element(bits, low, high, number)
622 	bitstr_t	*bits; 		/* one bit per flag, default=FALSE */
623 	int		low;
624 	int		high;
625 	int		number;
626 {
627 	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
628 
629 	if (number < low || number > high)
630 		return EOF;
631 
632 	bit_set(bits, (number-low));
633 	return OK;
634 }
635