xref: /netbsd-src/external/bsd/cron/dist/entry.c (revision 493e0895137e403239a95ca551c9d92f197bdcae)
1cd115652Schristos /*	$OpenBSD: entry.c,v 1.51 2020/04/16 17:51:56 millert Exp $	*/
2032a4398Schristos 
30061c6a5Schristos /*
40061c6a5Schristos  * Copyright 1988,1990,1993,1994 by Paul Vixie
50061c6a5Schristos  * All rights reserved
60061c6a5Schristos  */
70061c6a5Schristos 
80061c6a5Schristos /*
90061c6a5Schristos  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
100061c6a5Schristos  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
110061c6a5Schristos  *
120061c6a5Schristos  * Permission to use, copy, modify, and distribute this software for any
130061c6a5Schristos  * purpose with or without fee is hereby granted, provided that the above
140061c6a5Schristos  * copyright notice and this permission notice appear in all copies.
150061c6a5Schristos  *
160061c6a5Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
170061c6a5Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
180061c6a5Schristos  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
190061c6a5Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
200061c6a5Schristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
210061c6a5Schristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
220061c6a5Schristos  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
230061c6a5Schristos  */
24032a4398Schristos #include <sys/cdefs.h>
250061c6a5Schristos #if !defined(lint) && !defined(LINT)
26032a4398Schristos #if 0
270061c6a5Schristos static char rcsid[] = "Id: entry.c,v 1.17 2004/01/23 18:56:42 vixie Exp";
28032a4398Schristos #else
29*493e0895Schristos __RCSID("$NetBSD: entry.c,v 1.13 2024/08/19 23:50:23 christos Exp $");
30032a4398Schristos #endif
310061c6a5Schristos #endif
320061c6a5Schristos 
330061c6a5Schristos /* vix 26jan87 [RCS'd; rest of log is in RCS file]
340061c6a5Schristos  * vix 01jan87 [added line-level error recovery]
350061c6a5Schristos  * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
360061c6a5Schristos  * vix 30dec86 [written]
370061c6a5Schristos  */
380061c6a5Schristos 
390061c6a5Schristos #include "cron.h"
400061c6a5Schristos 
410061c6a5Schristos typedef	enum ecode {
420061c6a5Schristos 	e_none, e_minute, e_hour, e_dom, e_month, e_dow,
430061c6a5Schristos 	e_cmd, e_timespec, e_username, e_option, e_memory
440061c6a5Schristos } ecode_e;
450061c6a5Schristos 
46032a4398Schristos static const char * const ecodes[] =
470061c6a5Schristos 	{
480061c6a5Schristos 		"no error",
490061c6a5Schristos 		"bad minute",
500061c6a5Schristos 		"bad hour",
510061c6a5Schristos 		"bad day-of-month",
520061c6a5Schristos 		"bad month",
530061c6a5Schristos 		"bad day-of-week",
540061c6a5Schristos 		"bad command",
550061c6a5Schristos 		"bad time specifier",
560061c6a5Schristos 		"bad username",
570061c6a5Schristos 		"bad option",
580061c6a5Schristos 		"out of memory"
590061c6a5Schristos 	};
600061c6a5Schristos 
61032a4398Schristos static int	get_list(bitstr_t *, int, int, const char * const [], int, FILE *),
62032a4398Schristos 		get_range(bitstr_t *, int, int, const char * const [], int, FILE *),
63032a4398Schristos 		get_number(int *, int, const char * const [], int, FILE *, const char *),
640061c6a5Schristos 		set_element(bitstr_t *, int, int, int);
650061c6a5Schristos 
660061c6a5Schristos void
670061c6a5Schristos free_entry(entry *e) {
680061c6a5Schristos 	free(e->cmd);
690061c6a5Schristos 	free(e->pwd);
700061c6a5Schristos 	env_free(e->envp);
710061c6a5Schristos 	free(e);
720061c6a5Schristos }
730061c6a5Schristos 
740061c6a5Schristos /* return NULL if eof or syntax error occurs;
750061c6a5Schristos  * otherwise return a pointer to a new entry.
760061c6a5Schristos  */
770061c6a5Schristos entry *
78032a4398Schristos load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw,
79032a4398Schristos     char **envp) {
800061c6a5Schristos 	/* this function reads one crontab entry -- the next -- from a file.
810061c6a5Schristos 	 * it skips any leading blank lines, ignores comments, and returns
820061c6a5Schristos 	 * NULL if for any reason the entry can't be read and parsed.
830061c6a5Schristos 	 *
840061c6a5Schristos 	 * the entry is also parsed here.
850061c6a5Schristos 	 *
860061c6a5Schristos 	 * syntax:
870061c6a5Schristos 	 *   user crontab:
880061c6a5Schristos 	 *	minutes hours doms months dows cmd\n
890061c6a5Schristos 	 *   system crontab (/etc/crontab):
900061c6a5Schristos 	 *	minutes hours doms months dows USERNAME cmd\n
910061c6a5Schristos 	 */
920061c6a5Schristos 
930061c6a5Schristos 	ecode_e	ecode = e_none;
940061c6a5Schristos 	entry *e;
950061c6a5Schristos 	int ch;
960061c6a5Schristos 	char cmd[MAX_COMMAND];
970061c6a5Schristos 	char envstr[MAX_ENVSTR];
980061c6a5Schristos 	char **tenvp;
990061c6a5Schristos 
100032a4398Schristos 	Debug(DPARS, ("load_entry()...about to eat comments\n"));
1010061c6a5Schristos 
1020061c6a5Schristos 	skip_comments(file);
1030061c6a5Schristos 
1040061c6a5Schristos 	ch = get_char(file);
1050061c6a5Schristos 	if (ch == EOF)
1060061c6a5Schristos 		return (NULL);
1070061c6a5Schristos 
1080061c6a5Schristos 	/* ch is now the first useful character of a useful line.
1090061c6a5Schristos 	 * it may be an @special or it may be the first character
1100061c6a5Schristos 	 * of a list of minutes.
1110061c6a5Schristos 	 */
1120061c6a5Schristos 
1137035a6c5Schristos 	e = calloc(sizeof(*e), sizeof(char));
1140061c6a5Schristos 
1150061c6a5Schristos 	if (ch == '@') {
1160061c6a5Schristos 		/* all of these should be flagged and load-limited; i.e.,
1170061c6a5Schristos 		 * instead of @hourly meaning "0 * * * *" it should mean
1180061c6a5Schristos 		 * "close to the front of every hour but not 'til the
1190061c6a5Schristos 		 * system load is low".  Problems are: how do you know
1200061c6a5Schristos 		 * what "low" means? (save me from /etc/cron.conf!) and:
1210061c6a5Schristos 		 * how to guarantee low variance (how low is low?), which
1220061c6a5Schristos 		 * means how to we run roughly every hour -- seems like
1230061c6a5Schristos 		 * we need to keep a history or let the first hour set
1240061c6a5Schristos 		 * the schedule, which means we aren't load-limited
1250061c6a5Schristos 		 * anymore.  too much for my overloaded brain. (vix, jan90)
1260061c6a5Schristos 		 * HINT
1270061c6a5Schristos 		 */
1280061c6a5Schristos 		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
1290061c6a5Schristos 		if (!strcmp("reboot", cmd)) {
1300061c6a5Schristos 			e->flags |= WHEN_REBOOT;
1310061c6a5Schristos 		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
1320061c6a5Schristos 			bit_set(e->minute, 0);
1330061c6a5Schristos 			bit_set(e->hour, 0);
1340061c6a5Schristos 			bit_set(e->dom, 0);
1350061c6a5Schristos 			bit_set(e->month, 0);
1360061c6a5Schristos 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
1370061c6a5Schristos 			e->flags |= DOW_STAR;
1380061c6a5Schristos 		} else if (!strcmp("monthly", cmd)) {
1390061c6a5Schristos 			bit_set(e->minute, 0);
1400061c6a5Schristos 			bit_set(e->hour, 0);
1410061c6a5Schristos 			bit_set(e->dom, 0);
1420061c6a5Schristos 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1430061c6a5Schristos 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
1440061c6a5Schristos 			e->flags |= DOW_STAR;
1450061c6a5Schristos 		} else if (!strcmp("weekly", cmd)) {
1460061c6a5Schristos 			bit_set(e->minute, 0);
1470061c6a5Schristos 			bit_set(e->hour, 0);
1480061c6a5Schristos 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
1490061c6a5Schristos 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1500061c6a5Schristos 			bit_set(e->dow, 0);
151032a4398Schristos 			e->flags |= DOM_STAR;
1520061c6a5Schristos 		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
1530061c6a5Schristos 			bit_set(e->minute, 0);
1540061c6a5Schristos 			bit_set(e->hour, 0);
1550061c6a5Schristos 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
1560061c6a5Schristos 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1570061c6a5Schristos 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
158032a4398Schristos 			e->flags |= DOM_STAR | DOW_STAR;
1590061c6a5Schristos 		} else if (!strcmp("hourly", cmd)) {
1600061c6a5Schristos 			bit_set(e->minute, 0);
1610061c6a5Schristos 			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
1620061c6a5Schristos 			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
1630061c6a5Schristos 			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
1640061c6a5Schristos 			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
165032a4398Schristos 			e->flags |= DOM_STAR | DOW_STAR;
1660061c6a5Schristos 		} else {
1670061c6a5Schristos 			ecode = e_timespec;
1680061c6a5Schristos 			goto eof;
1690061c6a5Schristos 		}
1700061c6a5Schristos 		/* Advance past whitespace between shortcut and
1710061c6a5Schristos 		 * username/command.
1720061c6a5Schristos 		 */
1730061c6a5Schristos 		Skip_Blanks(ch, file);
1740061c6a5Schristos 		if (ch == EOF || ch == '\n') {
1750061c6a5Schristos 			ecode = e_cmd;
1760061c6a5Schristos 			goto eof;
1770061c6a5Schristos 		}
1780061c6a5Schristos 	} else {
179032a4398Schristos 		Debug(DPARS, ("load_entry()...about to parse numerics\n"));
1800061c6a5Schristos 
1810061c6a5Schristos 		if (ch == '*')
1820061c6a5Schristos 			e->flags |= MIN_STAR;
1830061c6a5Schristos 		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
1840061c6a5Schristos 			      PPC_NULL, ch, file);
1850061c6a5Schristos 		if (ch == EOF) {
1860061c6a5Schristos 			ecode = e_minute;
1870061c6a5Schristos 			goto eof;
1880061c6a5Schristos 		}
1890061c6a5Schristos 
1900061c6a5Schristos 		/* hours
1910061c6a5Schristos 		 */
1920061c6a5Schristos 
1930061c6a5Schristos 		if (ch == '*')
1940061c6a5Schristos 			e->flags |= HR_STAR;
1950061c6a5Schristos 		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
1960061c6a5Schristos 			      PPC_NULL, ch, file);
1970061c6a5Schristos 		if (ch == EOF) {
1980061c6a5Schristos 			ecode = e_hour;
1990061c6a5Schristos 			goto eof;
2000061c6a5Schristos 		}
2010061c6a5Schristos 
2020061c6a5Schristos 		/* DOM (days of month)
2030061c6a5Schristos 		 */
2040061c6a5Schristos 
2050061c6a5Schristos 		if (ch == '*')
2060061c6a5Schristos 			e->flags |= DOM_STAR;
2070061c6a5Schristos 		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
2080061c6a5Schristos 			      PPC_NULL, ch, file);
2090061c6a5Schristos 		if (ch == EOF) {
2100061c6a5Schristos 			ecode = e_dom;
2110061c6a5Schristos 			goto eof;
2120061c6a5Schristos 		}
2130061c6a5Schristos 
2140061c6a5Schristos 		/* month
2150061c6a5Schristos 		 */
2160061c6a5Schristos 
2170061c6a5Schristos 		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
2180061c6a5Schristos 			      MonthNames, ch, file);
2190061c6a5Schristos 		if (ch == EOF) {
2200061c6a5Schristos 			ecode = e_month;
2210061c6a5Schristos 			goto eof;
2220061c6a5Schristos 		}
2230061c6a5Schristos 
2240061c6a5Schristos 		/* DOW (days of week)
2250061c6a5Schristos 		 */
2260061c6a5Schristos 
2270061c6a5Schristos 		if (ch == '*')
2280061c6a5Schristos 			e->flags |= DOW_STAR;
2290061c6a5Schristos 		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
2300061c6a5Schristos 			      DowNames, ch, file);
2310061c6a5Schristos 		if (ch == EOF) {
2320061c6a5Schristos 			ecode = e_dow;
2330061c6a5Schristos 			goto eof;
2340061c6a5Schristos 		}
2350061c6a5Schristos 	}
2360061c6a5Schristos 
2370061c6a5Schristos 	/* make sundays equivalent */
2380061c6a5Schristos 	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
2390061c6a5Schristos 		bit_set(e->dow, 0);
2400061c6a5Schristos 		bit_set(e->dow, 7);
2410061c6a5Schristos 	}
2420061c6a5Schristos 
2430061c6a5Schristos 	/* check for permature EOL and catch a common typo */
2440061c6a5Schristos 	if (ch == '\n' || ch == '*') {
2450061c6a5Schristos 		ecode = e_cmd;
2460061c6a5Schristos 		goto eof;
2470061c6a5Schristos 	}
2480061c6a5Schristos 
2490061c6a5Schristos 	/* ch is the first character of a command, or a username */
2500061c6a5Schristos 	unget_char(ch, file);
2510061c6a5Schristos 
2520061c6a5Schristos 	if (!pw) {
2530061c6a5Schristos 		char		*username = cmd;	/* temp buffer */
2540061c6a5Schristos 
255032a4398Schristos 		Debug(DPARS, ("load_entry()...about to parse username\n"));
2560061c6a5Schristos 		ch = get_string(username, MAX_COMMAND, file, " \t\n");
2570061c6a5Schristos 
258032a4398Schristos 		Debug(DPARS, ("load_entry()...got %s\n",username));
2590061c6a5Schristos 		if (ch == EOF || ch == '\n' || ch == '*') {
2600061c6a5Schristos 			ecode = e_cmd;
2610061c6a5Schristos 			goto eof;
2620061c6a5Schristos 		}
2630061c6a5Schristos 
2640061c6a5Schristos 		pw = getpwnam(username);
2650061c6a5Schristos 		if (pw == NULL) {
2660061c6a5Schristos 			ecode = e_username;
2670061c6a5Schristos 			goto eof;
2680061c6a5Schristos 		}
2690061c6a5Schristos 		Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n",
270032a4398Schristos 			      (long)pw->pw_uid, (long)pw->pw_gid));
2710061c6a5Schristos 	}
2720061c6a5Schristos 
2730061c6a5Schristos 	if ((e->pwd = pw_dup(pw)) == NULL) {
2740061c6a5Schristos 		ecode = e_memory;
2750061c6a5Schristos 		goto eof;
2760061c6a5Schristos 	}
277032a4398Schristos 	(void)memset(e->pwd->pw_passwd, 0, strlen(e->pwd->pw_passwd));
2780061c6a5Schristos 
2790061c6a5Schristos 	/* copy and fix up environment.  some variables are just defaults and
2800061c6a5Schristos 	 * others are overrides.
2810061c6a5Schristos 	 */
2820061c6a5Schristos 	if ((e->envp = env_copy(envp)) == NULL) {
2830061c6a5Schristos 		ecode = e_memory;
2840061c6a5Schristos 		goto eof;
2850061c6a5Schristos 	}
2860061c6a5Schristos 	if (!env_get("SHELL", e->envp)) {
2870061c6a5Schristos 		if (glue_strings(envstr, sizeof envstr, "SHELL",
2880061c6a5Schristos 				 _PATH_BSHELL, '=')) {
2890061c6a5Schristos 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
2900061c6a5Schristos 				ecode = e_memory;
2910061c6a5Schristos 				goto eof;
2920061c6a5Schristos 			}
2930061c6a5Schristos 			e->envp = tenvp;
2940061c6a5Schristos 		} else
2950061c6a5Schristos 			log_it("CRON", getpid(), "error", "can't set SHELL");
2960061c6a5Schristos 	}
2970061c6a5Schristos 	if (!env_get("HOME", e->envp)) {
2980061c6a5Schristos 		if (glue_strings(envstr, sizeof envstr, "HOME",
2990061c6a5Schristos 				 pw->pw_dir, '=')) {
3000061c6a5Schristos 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
3010061c6a5Schristos 				ecode = e_memory;
3020061c6a5Schristos 				goto eof;
3030061c6a5Schristos 			}
3040061c6a5Schristos 			e->envp = tenvp;
3050061c6a5Schristos 		} else
3060061c6a5Schristos 			log_it("CRON", getpid(), "error", "can't set HOME");
3070061c6a5Schristos 	}
3080061c6a5Schristos 	/* If login.conf is in used we will get the default PATH later. */
3090061c6a5Schristos 	if (!env_get("PATH", e->envp)) {
3100061c6a5Schristos 		if (glue_strings(envstr, sizeof envstr, "PATH",
3110061c6a5Schristos 				 _PATH_DEFPATH, '=')) {
3120061c6a5Schristos 			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
3130061c6a5Schristos 				ecode = e_memory;
3140061c6a5Schristos 				goto eof;
3150061c6a5Schristos 			}
3160061c6a5Schristos 			e->envp = tenvp;
3170061c6a5Schristos 		} else
3180061c6a5Schristos 			log_it("CRON", getpid(), "error", "can't set PATH");
3190061c6a5Schristos 	}
3200061c6a5Schristos 	if (glue_strings(envstr, sizeof envstr, "LOGNAME",
3210061c6a5Schristos 			 pw->pw_name, '=')) {
3220061c6a5Schristos 		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
3230061c6a5Schristos 			ecode = e_memory;
3240061c6a5Schristos 			goto eof;
3250061c6a5Schristos 		}
3260061c6a5Schristos 		e->envp = tenvp;
3270061c6a5Schristos 	} else
3280061c6a5Schristos 		log_it("CRON", getpid(), "error", "can't set LOGNAME");
3290061c6a5Schristos #if defined(BSD) || defined(__linux)
3300061c6a5Schristos 	if (glue_strings(envstr, sizeof envstr, "USER",
3310061c6a5Schristos 			 pw->pw_name, '=')) {
3320061c6a5Schristos 		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
3330061c6a5Schristos 			ecode = e_memory;
3340061c6a5Schristos 			goto eof;
3350061c6a5Schristos 		}
3360061c6a5Schristos 		e->envp = tenvp;
3370061c6a5Schristos 	} else
3380061c6a5Schristos 		log_it("CRON", getpid(), "error", "can't set USER");
3390061c6a5Schristos #endif
3400061c6a5Schristos 
341032a4398Schristos 	Debug(DPARS, ("load_entry()...about to parse command\n"));
3420061c6a5Schristos 
3430061c6a5Schristos 	/* If the first character of the command is '-' it is a cron option.
3440061c6a5Schristos 	 */
345fdf2ea13Schristos 	ch = get_char(file);
346fdf2ea13Schristos 	while (ch == '-') {
3470061c6a5Schristos 		switch (ch = get_char(file)) {
348fdf2ea13Schristos 		case 'n':
349fdf2ea13Schristos 			/* only allow the user to set the option once */
350fdf2ea13Schristos 			if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) {
351fdf2ea13Schristos 				ecode = e_option;
352fdf2ea13Schristos 				goto eof;
353fdf2ea13Schristos 			}
354fdf2ea13Schristos 			e->flags |= MAIL_WHEN_ERR;
355fdf2ea13Schristos 			break;
3560061c6a5Schristos 		case 'q':
357fdf2ea13Schristos 			/* only allow the user to set the option once */
358fdf2ea13Schristos 			if ((e->flags & DONT_LOG) == DONT_LOG) {
359fdf2ea13Schristos 				ecode = e_option;
360fdf2ea13Schristos 				goto eof;
361fdf2ea13Schristos 			}
3620061c6a5Schristos 			e->flags |= DONT_LOG;
3630061c6a5Schristos 			break;
364cd115652Schristos 		case 's':
365cd115652Schristos 			/* only allow the user to set the option once */
366cd115652Schristos 			if ((e->flags & SINGLE_JOB) == SINGLE_JOB) {
367cd115652Schristos 				ecode = e_option;
368cd115652Schristos 				goto eof;
369cd115652Schristos 			}
370cd115652Schristos 			e->flags |= SINGLE_JOB;
371cd115652Schristos 			break;
3720061c6a5Schristos 		default:
3730061c6a5Schristos 			ecode = e_option;
3740061c6a5Schristos 			goto eof;
3750061c6a5Schristos 		}
376fdf2ea13Schristos 		ch = get_char(file);
377fdf2ea13Schristos 		if (ch != '\t' && ch != ' ') {
378fdf2ea13Schristos 			ecode = e_option;
379fdf2ea13Schristos 			goto eof;
380fdf2ea13Schristos 		}
381032a4398Schristos 		Skip_Blanks(ch, file);
3820061c6a5Schristos 		if (ch == EOF || ch == '\n') {
3830061c6a5Schristos 			ecode = e_cmd;
3840061c6a5Schristos 			goto eof;
3850061c6a5Schristos 		}
3860061c6a5Schristos 	}
3870061c6a5Schristos 	unget_char(ch, file);
3880061c6a5Schristos 
3890061c6a5Schristos 	/* Everything up to the next \n or EOF is part of the command...
3900061c6a5Schristos 	 * too bad we don't know in advance how long it will be, since we
3910061c6a5Schristos 	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
3920061c6a5Schristos 	 */
3930061c6a5Schristos 	ch = get_string(cmd, MAX_COMMAND, file, "\n");
3940061c6a5Schristos 
3950061c6a5Schristos 	/* a file without a \n before the EOF is rude, so we'll complain...
3960061c6a5Schristos 	 */
3970061c6a5Schristos 	if (ch == EOF) {
3980061c6a5Schristos 		ecode = e_cmd;
3990061c6a5Schristos 		goto eof;
4000061c6a5Schristos 	}
4010061c6a5Schristos 
4020061c6a5Schristos 	/* got the command in the 'cmd' string; save it in *e.
4030061c6a5Schristos 	 */
4040061c6a5Schristos 	if ((e->cmd = strdup(cmd)) == NULL) {
4050061c6a5Schristos 		ecode = e_memory;
4060061c6a5Schristos 		goto eof;
4070061c6a5Schristos 	}
4080061c6a5Schristos 
409032a4398Schristos 	Debug(DPARS, ("load_entry()...returning successfully\n"));
4100061c6a5Schristos 
4110061c6a5Schristos 	/* success, fini, return pointer to the entry we just created...
4120061c6a5Schristos 	 */
4130061c6a5Schristos 	return (e);
4140061c6a5Schristos 
4150061c6a5Schristos  eof:
4160061c6a5Schristos 	if (e->envp)
4170061c6a5Schristos 		env_free(e->envp);
4180061c6a5Schristos 	if (e->pwd)
4190061c6a5Schristos 		free(e->pwd);
4200061c6a5Schristos 	if (e->cmd)
4210061c6a5Schristos 		free(e->cmd);
4220061c6a5Schristos 	free(e);
4230061c6a5Schristos 	while (ch != '\n' && !feof(file))
4240061c6a5Schristos 		ch = get_char(file);
4250061c6a5Schristos 	if (ecode != e_none && error_func)
4260061c6a5Schristos 		(*error_func)(ecodes[(int)ecode]);
4270061c6a5Schristos 	return (NULL);
4280061c6a5Schristos }
4290061c6a5Schristos 
4300061c6a5Schristos static int
431032a4398Schristos get_list(bitstr_t *bits, int low, int high, const char * const names[],
4320061c6a5Schristos 	 int ch, FILE *file)
4330061c6a5Schristos {
4340061c6a5Schristos 	int done;
4350061c6a5Schristos 
4360061c6a5Schristos 	/* we know that we point to a non-blank character here;
4370061c6a5Schristos 	 * must do a Skip_Blanks before we exit, so that the
4380061c6a5Schristos 	 * next call (or the code that picks up the cmd) can
4390061c6a5Schristos 	 * assume the same thing.
4400061c6a5Schristos 	 */
4410061c6a5Schristos 
442032a4398Schristos 	Debug(DPARS|DEXT, ("get_list()...entered\n"));
4430061c6a5Schristos 
4440061c6a5Schristos 	/* list = range {"," range}
4450061c6a5Schristos 	 */
4460061c6a5Schristos 
4470061c6a5Schristos 	/* clear the bit string, since the default is 'off'.
4480061c6a5Schristos 	 */
449bc0d7f41Schristos 	bit_nclear(bits, 0, (size_t)(high-low+1));
4500061c6a5Schristos 
4510061c6a5Schristos 	/* process all ranges
4520061c6a5Schristos 	 */
4530061c6a5Schristos 	done = FALSE;
4540061c6a5Schristos 	while (!done) {
4550061c6a5Schristos 		if (EOF == (ch = get_range(bits, low, high, names, ch, file)))
4560061c6a5Schristos 			return (EOF);
4570061c6a5Schristos 		if (ch == ',')
4580061c6a5Schristos 			ch = get_char(file);
4590061c6a5Schristos 		else
4600061c6a5Schristos 			done = TRUE;
4610061c6a5Schristos 	}
4620061c6a5Schristos 
4630061c6a5Schristos 	/* exiting.  skip to some blanks, then skip over the blanks.
4640061c6a5Schristos 	 */
465032a4398Schristos 	Skip_Nonblanks(ch, file);
466032a4398Schristos 	Skip_Blanks(ch, file);
4670061c6a5Schristos 
468032a4398Schristos 	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch));
4690061c6a5Schristos 
4700061c6a5Schristos 	return (ch);
4710061c6a5Schristos }
4720061c6a5Schristos 
4730061c6a5Schristos 
4740061c6a5Schristos static int
475032a4398Schristos random_with_range(int low, int high)
476032a4398Schristos {
477032a4398Schristos 	/* Kind of crappy error detection, but...
478032a4398Schristos 	 */
4797801b53dSkre 	if (low < 0 || low >= high)
480032a4398Schristos 		return low;
481032a4398Schristos 	else
4828650a842Skre 		return (int)(arc4random() % (unsigned)((high - low + 1) + low));
483032a4398Schristos }
484032a4398Schristos 
485032a4398Schristos static int
486032a4398Schristos get_range(bitstr_t *bits, int low, int high, const char * const names[],
4870061c6a5Schristos 	  int ch, FILE *file)
4880061c6a5Schristos {
4890061c6a5Schristos 	/* range = number | number "-" number [ "/" number ]
4900061c6a5Schristos 	 */
4910061c6a5Schristos 
4920061c6a5Schristos 	int i, num1, num2, num3;
493032a4398Schristos 	int	qmark, star;
4940061c6a5Schristos 
495032a4398Schristos 	qmark = star = FALSE;
496032a4398Schristos 	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"));
4970061c6a5Schristos 
4980061c6a5Schristos 	if (ch == '*') {
4990061c6a5Schristos 		/* '*' means "first-last" but can still be modified by /step
5000061c6a5Schristos 		 */
501032a4398Schristos 		star = TRUE;
5020061c6a5Schristos 		num1 = low;
5030061c6a5Schristos 		num2 = high;
5040061c6a5Schristos 		ch = get_char(file);
5050061c6a5Schristos 		if (ch == EOF)
5060061c6a5Schristos 			return (EOF);
507032a4398Schristos 	} else if (ch == '?') {
508032a4398Schristos 		qmark = TRUE;
509032a4398Schristos 		ch = get_char(file);
510032a4398Schristos 		if (ch == EOF)
511032a4398Schristos 			return EOF;
512032a4398Schristos 		if (!isdigit(ch)) {
513032a4398Schristos 			num1 = random_with_range(low, high);
514032a4398Schristos 			if (EOF == set_element(bits, low, high, num1))
515032a4398Schristos 				return EOF;
516032a4398Schristos 			return ch;
517032a4398Schristos 		}
518032a4398Schristos 	}
519032a4398Schristos 
520032a4398Schristos 	if (!star) {
5210061c6a5Schristos 		ch = get_number(&num1, low, names, ch, file, ",- \t\n");
522*493e0895Schristos 		if (ch == EOF)
5230061c6a5Schristos 			return (EOF);
5240061c6a5Schristos 
5250061c6a5Schristos 		if (ch != '-') {
5260061c6a5Schristos 			/* not a range, it's a single number.
527032a4398Schristos 			 * a single number after '?' is bogus.
5280061c6a5Schristos 			 */
529032a4398Schristos 			if (qmark)
530032a4398Schristos 				return EOF;
5310061c6a5Schristos 			if (EOF == set_element(bits, low, high, num1)) {
5320061c6a5Schristos 				unget_char(ch, file);
5330061c6a5Schristos 				return (EOF);
5340061c6a5Schristos 			}
5350061c6a5Schristos 			return (ch);
5360061c6a5Schristos 		} else {
5370061c6a5Schristos 			/* eat the dash
5380061c6a5Schristos 			 */
5390061c6a5Schristos 			ch = get_char(file);
5400061c6a5Schristos 			if (ch == EOF)
5410061c6a5Schristos 				return (EOF);
5420061c6a5Schristos 
5430061c6a5Schristos 			/* get the number following the dash
5440061c6a5Schristos 			 */
5450061c6a5Schristos 			ch = get_number(&num2, low, names, ch, file, "/, \t\n");
5460061c6a5Schristos 			if (ch == EOF || num1 > num2)
5470061c6a5Schristos 				return (EOF);
548032a4398Schristos 
549032a4398Schristos 			/* if we have a random range, it is really
550032a4398Schristos 			 * like having a single number.
551032a4398Schristos 			 */
552032a4398Schristos 			if (qmark) {
553032a4398Schristos 				if (num1 > num2)
554032a4398Schristos 					return EOF;
555032a4398Schristos 				num1 = random_with_range(num1, num2);
556032a4398Schristos 				if (EOF == set_element(bits, low, high, num1))
557032a4398Schristos 					return EOF;
558032a4398Schristos 				return ch;
559032a4398Schristos 			}
5600061c6a5Schristos 		}
5610061c6a5Schristos 	}
5620061c6a5Schristos 
5630061c6a5Schristos 	/* check for step size
5640061c6a5Schristos 	 */
5650061c6a5Schristos 	if (ch == '/') {
566032a4398Schristos 		/* '?' is incompatible with '/'
567032a4398Schristos 		 */
568032a4398Schristos 		if (qmark)
569032a4398Schristos 			return EOF;
5700061c6a5Schristos 		/* eat the slash
5710061c6a5Schristos 		 */
5720061c6a5Schristos 		ch = get_char(file);
5730061c6a5Schristos 		if (ch == EOF)
5740061c6a5Schristos 			return (EOF);
5750061c6a5Schristos 
5760061c6a5Schristos 		/* get the step size -- note: we don't pass the
5770061c6a5Schristos 		 * names here, because the number is not an
5780061c6a5Schristos 		 * element id, it's a step size.  'low' is
5790061c6a5Schristos 		 * sent as a 0 since there is no offset either.
5800061c6a5Schristos 		 */
5810061c6a5Schristos 		ch = get_number(&num3, 0, PPC_NULL, ch, file, ", \t\n");
582*493e0895Schristos 		if (ch == EOF || num3 == 0)
5830061c6a5Schristos 			return (EOF);
5840061c6a5Schristos 	} else {
5850061c6a5Schristos 		/* no step.  default==1.
5860061c6a5Schristos 		 */
5870061c6a5Schristos 		num3 = 1;
5880061c6a5Schristos 	}
5890061c6a5Schristos 
5900061c6a5Schristos 	/* range. set all elements from num1 to num2, stepping
5910061c6a5Schristos 	 * by num3.  (the step is a downward-compatible extension
5920061c6a5Schristos 	 * proposed conceptually by bob@acornrc, syntactically
5930061c6a5Schristos 	 * designed then implemented by paul vixie).
5940061c6a5Schristos 	 */
5950061c6a5Schristos 	for (i = num1;  i <= num2;  i += num3)
5960061c6a5Schristos 		if (EOF == set_element(bits, low, high, i)) {
5970061c6a5Schristos 			unget_char(ch, file);
5980061c6a5Schristos 			return (EOF);
5990061c6a5Schristos 		}
6000061c6a5Schristos 
6010061c6a5Schristos 	return (ch);
6020061c6a5Schristos }
6030061c6a5Schristos 
6040061c6a5Schristos static int
605032a4398Schristos get_number(int *numptr, int low, const char * const names[], int ch, FILE *file,
6060061c6a5Schristos     const char *terms) {
6070061c6a5Schristos 	char temp[MAX_TEMPSTR], *pc;
6080061c6a5Schristos 	int len, i;
6090061c6a5Schristos 
6100061c6a5Schristos 	pc = temp;
6110061c6a5Schristos 	len = 0;
6120061c6a5Schristos 
6130061c6a5Schristos 	/* first look for a number */
6140061c6a5Schristos 	while (isdigit((unsigned char)ch)) {
6150061c6a5Schristos 		if (++len >= MAX_TEMPSTR)
6160061c6a5Schristos 			goto bad;
617ae5dea0aSchristos 		*pc++ = (char)ch;
6180061c6a5Schristos 		ch = get_char(file);
6190061c6a5Schristos 	}
6200061c6a5Schristos 	*pc = '\0';
6210061c6a5Schristos 	if (len != 0) {
6220061c6a5Schristos 		/* got a number, check for valid terminator */
6230061c6a5Schristos 		if (!strchr(terms, ch))
6240061c6a5Schristos 			goto bad;
6250061c6a5Schristos 		*numptr = atoi(temp);
6260061c6a5Schristos 		return (ch);
6270061c6a5Schristos 	}
6280061c6a5Schristos 
6290061c6a5Schristos 	/* no numbers, look for a string if we have any */
6300061c6a5Schristos 	if (names) {
6310061c6a5Schristos 		while (isalpha((unsigned char)ch)) {
6320061c6a5Schristos 			if (++len >= MAX_TEMPSTR)
6330061c6a5Schristos 				goto bad;
634ae5dea0aSchristos 			*pc++ = (char)ch;
6350061c6a5Schristos 			ch = get_char(file);
6360061c6a5Schristos 		}
6370061c6a5Schristos 		*pc = '\0';
6380061c6a5Schristos 		if (len != 0 && strchr(terms, ch)) {
6390061c6a5Schristos 			for (i = 0;  names[i] != NULL;  i++) {
6400061c6a5Schristos 				Debug(DPARS|DEXT,
6410061c6a5Schristos 					("get_num, compare(%s,%s)\n", names[i],
642032a4398Schristos 					temp));
6430061c6a5Schristos 				if (!strcasecmp(names[i], temp)) {
6440061c6a5Schristos 					*numptr = i+low;
6450061c6a5Schristos 					return (ch);
6460061c6a5Schristos 				}
6470061c6a5Schristos 			}
6480061c6a5Schristos 		}
6490061c6a5Schristos 	}
6500061c6a5Schristos 
6510061c6a5Schristos bad:
6520061c6a5Schristos 	unget_char(ch, file);
6530061c6a5Schristos 	return (EOF);
6540061c6a5Schristos }
6550061c6a5Schristos 
6560061c6a5Schristos static int
6570061c6a5Schristos set_element(bitstr_t *bits, int low, int high, int number) {
658032a4398Schristos 	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number));
6590061c6a5Schristos 
6600061c6a5Schristos 	if (number < low || number > high)
6610061c6a5Schristos 		return (EOF);
6620061c6a5Schristos 
6630061c6a5Schristos 	bit_set(bits, (number-low));
6640061c6a5Schristos 	return (OK);
6650061c6a5Schristos }
666