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