xref: /netbsd-src/external/bsd/cron/dist/crontab.c (revision ea01913fc3f2a350fb1f7d4c1610ec151618cdb8)
1*ea01913fShtodd /*	$NetBSD: crontab.c,v 1.15 2018/03/06 21:21:27 htodd Exp $	*/
2032a4398Schristos 
30061c6a5Schristos /* Copyright 1988,1990,1993,1994 by Paul Vixie
40061c6a5Schristos  * All rights reserved
50061c6a5Schristos  */
60061c6a5Schristos 
70061c6a5Schristos /*
80061c6a5Schristos  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
90061c6a5Schristos  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
100061c6a5Schristos  *
110061c6a5Schristos  * Permission to use, copy, modify, and distribute this software for any
120061c6a5Schristos  * purpose with or without fee is hereby granted, provided that the above
130061c6a5Schristos  * copyright notice and this permission notice appear in all copies.
140061c6a5Schristos  *
150061c6a5Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
160061c6a5Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
170061c6a5Schristos  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
180061c6a5Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
190061c6a5Schristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
200061c6a5Schristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
210061c6a5Schristos  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
220061c6a5Schristos  */
23032a4398Schristos #include <sys/cdefs.h>
240061c6a5Schristos #if !defined(lint) && !defined(LINT)
25032a4398Schristos #if 0
260061c6a5Schristos static char rcsid[] = "Id: crontab.c,v 1.12 2004/01/23 18:56:42 vixie Exp";
27032a4398Schristos #else
28*ea01913fShtodd __RCSID("$NetBSD: crontab.c,v 1.15 2018/03/06 21:21:27 htodd Exp $");
29032a4398Schristos #endif
300061c6a5Schristos #endif
310061c6a5Schristos 
320061c6a5Schristos /* crontab - install and manage per-user crontab files
330061c6a5Schristos  * vix 02may87 [RCS has the rest of the log]
340061c6a5Schristos  * vix 26jan87 [original]
350061c6a5Schristos  */
360061c6a5Schristos 
370061c6a5Schristos #define	MAIN_PROGRAM
380061c6a5Schristos 
390061c6a5Schristos #include "cron.h"
400061c6a5Schristos 
410061c6a5Schristos #define NHEADER_LINES 3
420061c6a5Schristos 
430061c6a5Schristos enum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
440061c6a5Schristos 
450061c6a5Schristos #if DEBUGGING
46032a4398Schristos static const char	*Options[] = {
47032a4398Schristos     "???", "list", "delete", "edit", "replace" };
48032a4398Schristos static const char	*getoptargs = "u:lerx:";
490061c6a5Schristos #else
50032a4398Schristos static const char	*getoptargs = "u:ler";
510061c6a5Schristos #endif
520061c6a5Schristos 
530061c6a5Schristos static	PID_T		Pid;
540061c6a5Schristos static	char		User[MAX_UNAME], RealUser[MAX_UNAME];
550061c6a5Schristos static	char		Filename[MAX_FNAME], TempFilename[MAX_FNAME];
560061c6a5Schristos static	FILE		*NewCrontab;
570061c6a5Schristos static	int		CheckErrorCount;
580061c6a5Schristos static	enum opt_t	Option;
590061c6a5Schristos static	struct passwd	*pw;
600061c6a5Schristos static	void		list_cmd(void),
610061c6a5Schristos 			delete_cmd(void),
620061c6a5Schristos 			edit_cmd(void),
630061c6a5Schristos 			poke_daemon(void),
640061c6a5Schristos 			check_error(const char *),
65032a4398Schristos 			parse_args(int c, char *v[]);
660061c6a5Schristos static	int		replace_cmd(void);
67032a4398Schristos static  int		allowed(const char *, const char *, const char *);
68032a4398Schristos static  int		in_file(const char *, FILE *, int);
69*ea01913fShtodd static  int 		relinquish_priv(void);
702ed8e2f0Schristos static  int 		regain_priv(void);
710061c6a5Schristos 
725f34e14bSjoerg static __dead void
usage(const char * msg)730061c6a5Schristos usage(const char *msg) {
74032a4398Schristos 	(void)fprintf(stderr, "%s: usage error: %s\n", getprogname(), msg);
75032a4398Schristos 	(void)fprintf(stderr, "usage:\t%s [-u user] file\n", getprogname());
76032a4398Schristos 	(void)fprintf(stderr, "\t%s [-u user] [ -e | -l | -r ]\n", getprogname());
77032a4398Schristos 	(void)fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n");
78032a4398Schristos 	(void)fprintf(stderr, "\t-e\t(edit user's crontab)\n");
79032a4398Schristos 	(void)fprintf(stderr, "\t-l\t(list user's crontab)\n");
80032a4398Schristos 	(void)fprintf(stderr, "\t-r\t(delete user's crontab)\n");
810061c6a5Schristos 	exit(ERROR_EXIT);
820061c6a5Schristos }
830061c6a5Schristos 
842ed8e2f0Schristos static uid_t euid, ruid;
852ed8e2f0Schristos static gid_t egid, rgid;
862ed8e2f0Schristos 
870061c6a5Schristos int
main(int argc,char * argv[])880061c6a5Schristos main(int argc, char *argv[]) {
890061c6a5Schristos 	int exitstatus;
900061c6a5Schristos 
91032a4398Schristos 	setprogname(argv[0]);
920061c6a5Schristos 	Pid = getpid();
93032a4398Schristos 	(void)setlocale(LC_ALL, "");
940061c6a5Schristos 
952ed8e2f0Schristos 	euid = geteuid();
962ed8e2f0Schristos 	egid = getegid();
972ed8e2f0Schristos 	ruid = getuid();
982ed8e2f0Schristos 	rgid = getgid();
992ed8e2f0Schristos 
100d2071ea3Stron 	if (euid == ruid && ruid != 0)
101d2071ea3Stron 		errx(ERROR_EXIT, "Not installed setuid root");
1022ed8e2f0Schristos 
103032a4398Schristos 	(void)setvbuf(stderr, NULL, _IOLBF, 0);
1040061c6a5Schristos 	parse_args(argc, argv);		/* sets many globals, opens a file */
1050061c6a5Schristos 	set_cron_cwd();
1060061c6a5Schristos 	if (!allowed(RealUser, CRON_ALLOW, CRON_DENY)) {
107032a4398Schristos 		(void)fprintf(stderr,
108032a4398Schristos 			"You `%s' are not allowed to use this program `%s'\n",
109032a4398Schristos 			User, getprogname());
110032a4398Schristos 		(void)fprintf(stderr, "See crontab(1) for more information\n");
1110061c6a5Schristos 		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
1120061c6a5Schristos 		exit(ERROR_EXIT);
1130061c6a5Schristos 	}
1140061c6a5Schristos 	exitstatus = OK_EXIT;
1150061c6a5Schristos 	switch (Option) {
1160061c6a5Schristos 	case opt_unknown:
117032a4398Schristos 		usage("unrecognized option");
1180061c6a5Schristos 		exitstatus = ERROR_EXIT;
1190061c6a5Schristos 		break;
1200061c6a5Schristos 	case opt_list:
1210061c6a5Schristos 		list_cmd();
1220061c6a5Schristos 		break;
1230061c6a5Schristos 	case opt_delete:
1240061c6a5Schristos 		delete_cmd();
1250061c6a5Schristos 		break;
1260061c6a5Schristos 	case opt_edit:
1270061c6a5Schristos 		edit_cmd();
1280061c6a5Schristos 		break;
1290061c6a5Schristos 	case opt_replace:
1300061c6a5Schristos 		if (replace_cmd() < 0)
1310061c6a5Schristos 			exitstatus = ERROR_EXIT;
1320061c6a5Schristos 		break;
1330061c6a5Schristos 	default:
1340061c6a5Schristos 		abort();
1350061c6a5Schristos 	}
1360061c6a5Schristos 	exit(exitstatus);
1370061c6a5Schristos 	/*NOTREACHED*/
1380061c6a5Schristos }
1390061c6a5Schristos 
1400061c6a5Schristos static void
get_time(const struct stat * st,struct timespec * ts)1410a00da6dSchristos get_time(const struct stat *st, struct timespec *ts)
1420a00da6dSchristos {
1430a00da6dSchristos 	ts[0].tv_sec = st->st_atime;
1440a00da6dSchristos 	ts[0].tv_nsec = st->st_atimensec;
1450a00da6dSchristos 	ts[1].tv_sec = st->st_mtime;
1460a00da6dSchristos 	ts[1].tv_nsec = st->st_mtimensec;
1470a00da6dSchristos }
1480a00da6dSchristos 
1490a00da6dSchristos static int
change_time(const char * name,const struct timespec * ts)1500a00da6dSchristos change_time(const char *name, const struct timespec *ts)
1510a00da6dSchristos {
1520a00da6dSchristos #if defined(HAVE_UTIMENSAT)
1530a00da6dSchristos 	return utimensat(AT_FDCWD, name, ts, 0);
1540a00da6dSchristos #elif defined(HAVE_UTIMES)
1550a00da6dSchristos 	struct timeval tv[2];
1560a00da6dSchristos 	TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]);
1570a00da6dSchristos 	TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]);
15898b021fdSchristos 	return utimes(name, tv);
1590a00da6dSchristos #else
16098b021fdSchristos 	struct utimbuf ut;
16198b021fdSchristos 	ut.actime = ts[0].tv_sec;
16298b021fdSchristos 	ut.modtime = ts[1].tv_sec;
1630a00da6dSchristos 	return utime(name, &ut);
1640a00da6dSchristos #endif
1650a00da6dSchristos }
1660a00da6dSchristos 
1670a00da6dSchristos static int
compare_time(const struct stat * st,const struct timespec * ts2)1680a00da6dSchristos compare_time(const struct stat *st, const struct timespec *ts2)
1690a00da6dSchristos {
1700a00da6dSchristos 	struct timespec ts1[2];
1710a00da6dSchristos 	get_time(st, ts1);
1720a00da6dSchristos 
1730a00da6dSchristos 	return ts1[1].tv_sec == ts2[1].tv_sec
1740a00da6dSchristos #if defined(HAVE_UTIMENSAT)
1750a00da6dSchristos 	    && ts1[1].tv_nsec == ts2[1].tv_nsec
1760a00da6dSchristos #elif defined(HAVE_UTIMES)
17798b021fdSchristos 	    && ts1[1].tv_nsec / 1000 == ts2[1].tv_nsec / 1000
1780a00da6dSchristos #endif
1790a00da6dSchristos 	;
1800a00da6dSchristos }
1810a00da6dSchristos 
1820a00da6dSchristos static void
parse_args(int argc,char * argv[])1830061c6a5Schristos parse_args(int argc, char *argv[]) {
1840061c6a5Schristos 	int argch;
1850061c6a5Schristos 
1860061c6a5Schristos 	if (!(pw = getpwuid(getuid()))) {
187032a4398Schristos 		errx(ERROR_EXIT,
188032a4398Schristos 		    "your UID isn't in the passwd file. bailingo out");
1890061c6a5Schristos 	}
1900061c6a5Schristos 	if (strlen(pw->pw_name) >= sizeof User) {
191032a4398Schristos 		errx(ERROR_EXIT, "username too long");
1920061c6a5Schristos 	}
193032a4398Schristos 	(void)strlcpy(User, pw->pw_name, sizeof(User));
194032a4398Schristos 	(void)strlcpy(RealUser, User, sizeof(RealUser));
1950061c6a5Schristos 	Filename[0] = '\0';
1960061c6a5Schristos 	Option = opt_unknown;
1970061c6a5Schristos 	while (-1 != (argch = getopt(argc, argv, getoptargs))) {
1980061c6a5Schristos 		switch (argch) {
1990061c6a5Schristos #if DEBUGGING
2000061c6a5Schristos 		case 'x':
2010061c6a5Schristos 			if (!set_debug_flags(optarg))
2020061c6a5Schristos 				usage("bad debug option");
2030061c6a5Schristos 			break;
2040061c6a5Schristos #endif
2050061c6a5Schristos 		case 'u':
2060061c6a5Schristos 			if (MY_UID(pw) != ROOT_UID) {
207032a4398Schristos 				errx(ERROR_EXIT,
208032a4398Schristos 				    "must be privileged to use -u");
2090061c6a5Schristos 			}
2100061c6a5Schristos 			if (!(pw = getpwnam(optarg))) {
211032a4398Schristos 				errx(ERROR_EXIT, "user `%s' unknown", optarg);
2120061c6a5Schristos 			}
2130061c6a5Schristos 			if (strlen(optarg) >= sizeof User)
2140061c6a5Schristos 				usage("username too long");
215032a4398Schristos 			(void) strlcpy(User, optarg, sizeof(User));
2160061c6a5Schristos 			break;
2170061c6a5Schristos 		case 'l':
2180061c6a5Schristos 			if (Option != opt_unknown)
2190061c6a5Schristos 				usage("only one operation permitted");
2200061c6a5Schristos 			Option = opt_list;
2210061c6a5Schristos 			break;
2220061c6a5Schristos 		case 'r':
2230061c6a5Schristos 			if (Option != opt_unknown)
2240061c6a5Schristos 				usage("only one operation permitted");
2250061c6a5Schristos 			Option = opt_delete;
2260061c6a5Schristos 			break;
2270061c6a5Schristos 		case 'e':
2280061c6a5Schristos 			if (Option != opt_unknown)
2290061c6a5Schristos 				usage("only one operation permitted");
2300061c6a5Schristos 			Option = opt_edit;
2310061c6a5Schristos 			break;
2320061c6a5Schristos 		default:
2330061c6a5Schristos 			usage("unrecognized option");
2340061c6a5Schristos 		}
2350061c6a5Schristos 	}
2360061c6a5Schristos 
2370061c6a5Schristos 	endpwent();
2380061c6a5Schristos 
2390061c6a5Schristos 	if (Option != opt_unknown) {
2400061c6a5Schristos 		if (argv[optind] != NULL)
2410061c6a5Schristos 			usage("no arguments permitted after this option");
2420061c6a5Schristos 	} else {
2430061c6a5Schristos 		if (argv[optind] != NULL) {
2440061c6a5Schristos 			Option = opt_replace;
2450061c6a5Schristos 			if (strlen(argv[optind]) >= sizeof Filename)
2460061c6a5Schristos 				usage("filename too long");
247032a4398Schristos 			(void)strlcpy(Filename, argv[optind], sizeof(Filename));
2480061c6a5Schristos 		} else
2490061c6a5Schristos 			usage("file name must be specified for replace");
2500061c6a5Schristos 	}
2510061c6a5Schristos 
2520061c6a5Schristos 	if (Option == opt_replace) {
2530061c6a5Schristos 		/* we have to open the file here because we're going to
2540061c6a5Schristos 		 * chdir(2) into /var/cron before we get around to
2550061c6a5Schristos 		 * reading the file.
2560061c6a5Schristos 		 */
2570061c6a5Schristos 		if (!strcmp(Filename, "-"))
2580061c6a5Schristos 			NewCrontab = stdin;
2590061c6a5Schristos 		else {
2600061c6a5Schristos 			/* relinquish the setuid status of the binary during
2610061c6a5Schristos 			 * the open, lest nonroot users read files they should
2620061c6a5Schristos 			 * not be able to read.  we can't use access() here
2630061c6a5Schristos 			 * since there's a race condition.  thanks go out to
2640061c6a5Schristos 			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
2650061c6a5Schristos 			 * the race.
2660061c6a5Schristos 			 */
2670061c6a5Schristos 
268*ea01913fShtodd 			if (relinquish_priv() < OK) {
269032a4398Schristos 				err(ERROR_EXIT, "swapping uids");
2700061c6a5Schristos 			}
2710061c6a5Schristos 			if (!(NewCrontab = fopen(Filename, "r"))) {
272032a4398Schristos 				err(ERROR_EXIT, "cannot open `%s'", Filename);
2730061c6a5Schristos 			}
2742ed8e2f0Schristos 			if (regain_priv() < OK) {
275032a4398Schristos 				err(ERROR_EXIT, "swapping uids back");
2760061c6a5Schristos 			}
2770061c6a5Schristos 		}
2780061c6a5Schristos 	}
2790061c6a5Schristos 
2800061c6a5Schristos 	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
281032a4398Schristos 		      User, Filename, Options[(int)Option]));
282032a4398Schristos }
283032a4398Schristos 
284032a4398Schristos static void
skip_header(int * pch,FILE * f)285032a4398Schristos skip_header(int *pch, FILE *f)
286032a4398Schristos {
287032a4398Schristos 	int ch;
288032a4398Schristos 	int x;
289032a4398Schristos 
290032a4398Schristos 	/* ignore the top few comments since we probably put them there.
291032a4398Schristos 	 */
292032a4398Schristos 	for (x = 0;  x < NHEADER_LINES;  x++) {
293032a4398Schristos 		ch = get_char(f);
294032a4398Schristos 		if (EOF == ch)
295032a4398Schristos 			break;
296032a4398Schristos 		if ('#' != ch)
297032a4398Schristos 			break;
298032a4398Schristos 		while (EOF != (ch = get_char(f)))
299032a4398Schristos 			if (ch == '\n')
300032a4398Schristos 				break;
301032a4398Schristos 		if (EOF == ch)
302032a4398Schristos 			break;
303032a4398Schristos 	}
304032a4398Schristos 	if (ch == '\n')
305032a4398Schristos 		ch = get_char(f);
306032a4398Schristos 
307032a4398Schristos 	*pch = ch;
3080061c6a5Schristos }
3090061c6a5Schristos 
3100061c6a5Schristos static void
list_cmd(void)3110061c6a5Schristos list_cmd(void) {
3120061c6a5Schristos 	char n[MAX_FNAME];
3130061c6a5Schristos 	FILE *f;
3140061c6a5Schristos 	int ch;
3150061c6a5Schristos 
3160061c6a5Schristos 	log_it(RealUser, Pid, "LIST", User);
3170061c6a5Schristos 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
318032a4398Schristos 		errx(ERROR_EXIT, "path too long");
3190061c6a5Schristos 	}
3200061c6a5Schristos 	if (!(f = fopen(n, "r"))) {
3210061c6a5Schristos 		if (errno == ENOENT)
322032a4398Schristos 			errx(ERROR_EXIT, "no crontab for `%s'", User);
3230061c6a5Schristos 		else
324032a4398Schristos 			err(ERROR_EXIT, "Cannot open `%s'", n);
3250061c6a5Schristos 	}
3260061c6a5Schristos 
3270061c6a5Schristos 	/* file is open. copy to stdout, close.
3280061c6a5Schristos 	 */
329032a4398Schristos 	Set_LineNum(1);
330032a4398Schristos 	skip_header(&ch, f);
331032a4398Schristos 	for (; EOF != ch;  ch = get_char(f))
332032a4398Schristos 		(void)putchar(ch);
333032a4398Schristos 	(void)fclose(f);
3340061c6a5Schristos }
3350061c6a5Schristos 
3360061c6a5Schristos static void
delete_cmd(void)3370061c6a5Schristos delete_cmd(void) {
3380061c6a5Schristos 	char n[MAX_FNAME];
3390061c6a5Schristos 
3400061c6a5Schristos 	log_it(RealUser, Pid, "DELETE", User);
3410061c6a5Schristos 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
342032a4398Schristos 		errx(ERROR_EXIT, "path too long");
3430061c6a5Schristos 	}
3440061c6a5Schristos 	if (unlink(n) != 0) {
3450061c6a5Schristos 		if (errno == ENOENT)
346032a4398Schristos 			errx(ERROR_EXIT, "no crontab for `%s'", User);
3470061c6a5Schristos 		else
348032a4398Schristos 			err(ERROR_EXIT, "cannot unlink `%s'", n);
3490061c6a5Schristos 	}
3500061c6a5Schristos 	poke_daemon();
3510061c6a5Schristos }
3520061c6a5Schristos 
3530061c6a5Schristos static void
check_error(const char * msg)3540061c6a5Schristos check_error(const char *msg) {
3550061c6a5Schristos 	CheckErrorCount++;
356032a4398Schristos 	(void)fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
3570061c6a5Schristos }
3580061c6a5Schristos 
3590061c6a5Schristos static void
edit_cmd(void)3600061c6a5Schristos edit_cmd(void) {
361032a4398Schristos 	char n[MAX_FNAME], q[MAX_TEMPSTR];
362032a4398Schristos 	const char *editor;
3630061c6a5Schristos 	FILE *f;
3640061c6a5Schristos 	int ch, t, x;
365032a4398Schristos 	sig_t oint, oabrt, oquit, ohup;
3660061c6a5Schristos 	struct stat statbuf;
3670061c6a5Schristos 	WAIT_T waiter;
3680061c6a5Schristos 	PID_T pid, xpid;
3690a00da6dSchristos 	struct timespec ts[2];
3700061c6a5Schristos 
3710061c6a5Schristos 	log_it(RealUser, Pid, "BEGIN EDIT", User);
3720061c6a5Schristos 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
373032a4398Schristos 		errx(ERROR_EXIT, "path too long");
3740061c6a5Schristos 	}
3750061c6a5Schristos 	if (!(f = fopen(n, "r"))) {
3760061c6a5Schristos 		if (errno != ENOENT) {
377032a4398Schristos 			err(ERROR_EXIT, "cannot open `%s'", n);
3780061c6a5Schristos 		}
379032a4398Schristos 		warnx("no crontab for `%s' - using an empty one", User);
3800061c6a5Schristos 		if (!(f = fopen(_PATH_DEVNULL, "r"))) {
381032a4398Schristos 			err(ERROR_EXIT, "cannot open `%s'", _PATH_DEVNULL);
3820061c6a5Schristos 		}
3830061c6a5Schristos 	}
3840061c6a5Schristos 
3850061c6a5Schristos 	if (fstat(fileno(f), &statbuf) < 0) {
386032a4398Schristos 		warn("cannot stat crontab file");
3870061c6a5Schristos 		goto fatal;
3880061c6a5Schristos 	}
3890a00da6dSchristos 	get_time(&statbuf, ts);
3900061c6a5Schristos 
3910061c6a5Schristos 	/* Turn off signals. */
392032a4398Schristos 	ohup = signal(SIGHUP, SIG_IGN);
393032a4398Schristos 	oint = signal(SIGINT, SIG_IGN);
394032a4398Schristos 	oquit = signal(SIGQUIT, SIG_IGN);
395032a4398Schristos 	oabrt = signal(SIGABRT, SIG_IGN);
3960061c6a5Schristos 
3970061c6a5Schristos 	if (!glue_strings(Filename, sizeof Filename, _PATH_TMP,
3980061c6a5Schristos 	    "crontab.XXXXXXXXXX", '/')) {
399032a4398Schristos 		warnx("path too long");
4000061c6a5Schristos 		goto fatal;
4010061c6a5Schristos 	}
4020061c6a5Schristos 	if (-1 == (t = mkstemp(Filename))) {
403032a4398Schristos 		warn("cannot create `%s'", Filename);
4040061c6a5Schristos 		goto fatal;
4050061c6a5Schristos 	}
4060061c6a5Schristos #ifdef HAS_FCHOWN
407032a4398Schristos 	x = fchown(t, MY_UID(pw), MY_GID(pw));
4080061c6a5Schristos #else
409032a4398Schristos 	x = chown(Filename, MY_UID(pw), MY_GID(pw));
4100061c6a5Schristos #endif
411032a4398Schristos 	if (x < 0) {
412032a4398Schristos 		warn("cannot chown `%s'", Filename);
413032a4398Schristos 		goto fatal;
414032a4398Schristos 	}
4150061c6a5Schristos 	if (!(NewCrontab = fdopen(t, "r+"))) {
416032a4398Schristos 		warn("cannot open fd");
4170061c6a5Schristos 		goto fatal;
4180061c6a5Schristos 	}
4190061c6a5Schristos 
420032a4398Schristos 	Set_LineNum(1);
4210061c6a5Schristos 
422032a4398Schristos 	skip_header(&ch, f);
4230061c6a5Schristos 
4240061c6a5Schristos 	/* copy the rest of the crontab (if any) to the temp file.
4250061c6a5Schristos 	 */
426032a4398Schristos 	for (; EOF != ch; ch = get_char(f))
427032a4398Schristos 		(void)putc(ch, NewCrontab);
428032a4398Schristos 	(void)fclose(f);
4290061c6a5Schristos 	if (fflush(NewCrontab) < OK) {
430032a4398Schristos 		err(ERROR_EXIT, "cannot flush output for `%s'", Filename);
4310061c6a5Schristos 	}
432065057e6Schristos #ifdef HAVE_FUTIMENS
433065057e6Schristos 	if (futimens(t, ts) == -1)
434065057e6Schristos #else
4350a00da6dSchristos 	if (change_time(Filename, ts) == -1)
436065057e6Schristos #endif
4370a00da6dSchristos 		err(ERROR_EXIT, "cannot set time info for `%s'", Filename);
4380061c6a5Schristos  again:
4390061c6a5Schristos 	rewind(NewCrontab);
4400061c6a5Schristos 	if (ferror(NewCrontab)) {
441032a4398Schristos 		warn("error while writing new crontab to `%s'", Filename);
4420061c6a5Schristos  fatal:
443032a4398Schristos 		(void)unlink(Filename);
4440061c6a5Schristos 		exit(ERROR_EXIT);
4450061c6a5Schristos 	}
4460061c6a5Schristos 
4470061c6a5Schristos 	if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') &&
4480061c6a5Schristos 	    ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) {
4490061c6a5Schristos 		editor = EDITOR;
4500061c6a5Schristos 	}
4510061c6a5Schristos 
4520061c6a5Schristos 	/* we still have the file open.  editors will generally rewrite the
4530061c6a5Schristos 	 * original file rather than renaming/unlinking it and starting a
4540061c6a5Schristos 	 * new one; even backup files are supposed to be made by copying
4550061c6a5Schristos 	 * rather than by renaming.  if some editor does not support this,
4560061c6a5Schristos 	 * then don't use it.  the security problems are more severe if we
4570061c6a5Schristos 	 * close and reopen the file around the edit.
4580061c6a5Schristos 	 */
4590061c6a5Schristos 
4600061c6a5Schristos 	switch (pid = fork()) {
4610061c6a5Schristos 	case -1:
462032a4398Schristos 		warn("cannot fork");
4630061c6a5Schristos 		goto fatal;
4640061c6a5Schristos 	case 0:
4650061c6a5Schristos 		/* child */
4660061c6a5Schristos 		if (setgid(MY_GID(pw)) < 0) {
467032a4398Schristos 			err(ERROR_EXIT, "cannot setgid(getgid())");
4680061c6a5Schristos 		}
4690061c6a5Schristos 		if (setuid(MY_UID(pw)) < 0) {
470032a4398Schristos 			err(ERROR_EXIT, "cannot setuid(getuid())");
4710061c6a5Schristos 		}
472065057e6Schristos 		if (close_all(3)) {
473065057e6Schristos 			err(ERROR_EXIT, "cannot close files");
474065057e6Schristos 		}
4750061c6a5Schristos 		if (chdir(_PATH_TMP) < 0) {
476032a4398Schristos 			err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP);
4770061c6a5Schristos 		}
4780061c6a5Schristos 		if (!glue_strings(q, sizeof q, editor, Filename, ' ')) {
479032a4398Schristos 			errx(ERROR_EXIT, "editor command line too long");
4800061c6a5Schristos 		}
481032a4398Schristos 		(void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0);
482032a4398Schristos 		err(ERROR_EXIT, "cannot start `%s'", editor);
4830061c6a5Schristos 		/*NOTREACHED*/
4840061c6a5Schristos 	default:
4850061c6a5Schristos 		/* parent */
4860061c6a5Schristos 		break;
4870061c6a5Schristos 	}
4880061c6a5Schristos 
4890061c6a5Schristos 	/* parent */
4900061c6a5Schristos 	for (;;) {
4910061c6a5Schristos 		xpid = waitpid(pid, &waiter, WUNTRACED);
4920061c6a5Schristos 		if (xpid == -1) {
4930061c6a5Schristos 			if (errno != EINTR)
494032a4398Schristos 				warn("waitpid() failed waiting for PID %ld "
495032a4398Schristos 				    "from `%s'", (long)pid, editor);
4960061c6a5Schristos 		} else if (xpid != pid) {
497032a4398Schristos 			warnx("wrong PID (%ld != %ld) from `%s'",
498032a4398Schristos 			    (long)xpid, (long)pid, editor);
4990061c6a5Schristos 			goto fatal;
5000061c6a5Schristos 		} else if (WIFSTOPPED(waiter)) {
501032a4398Schristos 			(void)kill(getpid(), WSTOPSIG(waiter));
5020061c6a5Schristos 		} else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
503032a4398Schristos 			warnx("`%s' exited with status %d\n",
504032a4398Schristos 			    editor, WEXITSTATUS(waiter));
5050061c6a5Schristos 			goto fatal;
5060061c6a5Schristos 		} else if (WIFSIGNALED(waiter)) {
507032a4398Schristos 			warnx("`%s' killed; signal %d (%score dumped)",
508032a4398Schristos 			    editor, WTERMSIG(waiter),
5090061c6a5Schristos 			    WCOREDUMP(waiter) ? "" : "no ");
5100061c6a5Schristos 			goto fatal;
5110061c6a5Schristos 		} else
5120061c6a5Schristos 			break;
5130061c6a5Schristos 	}
514032a4398Schristos 	(void)signal(SIGHUP, ohup);
515032a4398Schristos 	(void)signal(SIGINT, oint);
516032a4398Schristos 	(void)signal(SIGQUIT, oquit);
517032a4398Schristos 	(void)signal(SIGABRT, oabrt);
518032a4398Schristos 
5190061c6a5Schristos 	if (fstat(t, &statbuf) < 0) {
520032a4398Schristos 		warn("cannot stat `%s'", Filename);
5210061c6a5Schristos 		goto fatal;
5220061c6a5Schristos 	}
5230a00da6dSchristos 	if (compare_time(&statbuf, ts)) {
524032a4398Schristos 		warnx("no changes made to crontab");
5250061c6a5Schristos 		goto remove;
5260061c6a5Schristos 	}
527032a4398Schristos 	warnx("installing new crontab");
5280061c6a5Schristos 	switch (replace_cmd()) {
5290061c6a5Schristos 	case 0:
5300061c6a5Schristos 		break;
5310061c6a5Schristos 	case -1:
5320061c6a5Schristos 		for (;;) {
533032a4398Schristos 			(void)fpurge(stdin);
534032a4398Schristos 			(void)printf("Do you want to retry the same edit? ");
535032a4398Schristos 			(void)fflush(stdout);
5360061c6a5Schristos 			q[0] = '\0';
537032a4398Schristos 			(void) fgets(q, (int)sizeof(q), stdin);
5380061c6a5Schristos 			switch (q[0]) {
5390061c6a5Schristos 			case 'y':
5400061c6a5Schristos 			case 'Y':
5410061c6a5Schristos 				goto again;
5420061c6a5Schristos 			case 'n':
5430061c6a5Schristos 			case 'N':
5440061c6a5Schristos 				goto abandon;
5450061c6a5Schristos 			default:
546032a4398Schristos 				(void)printf("Enter Y or N\n");
5470061c6a5Schristos 			}
5480061c6a5Schristos 		}
5490061c6a5Schristos 		/*NOTREACHED*/
5500061c6a5Schristos 	case -2:
5510061c6a5Schristos 	abandon:
552032a4398Schristos 		warnx("edits left in `%s'", Filename);
5530061c6a5Schristos 		goto done;
5540061c6a5Schristos 	default:
555032a4398Schristos 		warnx("panic: bad switch() in replace_cmd()");
5560061c6a5Schristos 		goto fatal;
5570061c6a5Schristos 	}
5580061c6a5Schristos  remove:
559032a4398Schristos 	(void)unlink(Filename);
5600061c6a5Schristos  done:
5610061c6a5Schristos 	log_it(RealUser, Pid, "END EDIT", User);
5620061c6a5Schristos }
5630061c6a5Schristos 
5642ef06139Schristos static size_t
getmaxtabsize(void)5652ef06139Schristos getmaxtabsize(void)
5662ef06139Schristos {
5672ef06139Schristos 	char n2[MAX_FNAME];
5682ef06139Schristos 	FILE *fmaxtabsize;
569032a4398Schristos 	size_t maxtabsize;
570032a4398Schristos 
571032a4398Schristos 	/* Make sure that the crontab is not an unreasonable size.
572032a4398Schristos 	 *
573032a4398Schristos 	 * XXX This is subject to a race condition--the user could
574032a4398Schristos 	 * add stuff to the file after we've checked the size but
575032a4398Schristos 	 * before we slurp it in and write it out. We can't just move
576032a4398Schristos 	 * the test to test the temp file we later create, because by
577032a4398Schristos 	 * that time we've already filled up the crontab disk. Probably
578032a4398Schristos 	 * the right thing to do is to do a bytecount in the copy loop
579032a4398Schristos 	 * rather than stating the file we're about to read.
580032a4398Schristos 	 */
581032a4398Schristos 	(void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE);
582032a4398Schristos 	if ((fmaxtabsize = fopen(n2, "r")) != NULL)  {
583032a4398Schristos 		if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL)  {
584032a4398Schristos 			maxtabsize = 0;
585032a4398Schristos 		} else {
586ae5dea0aSchristos 			maxtabsize = (size_t)atoi(n2);
587032a4398Schristos 		}
588032a4398Schristos 		(void)fclose(fmaxtabsize);
589032a4398Schristos 	} else {
590032a4398Schristos 		maxtabsize = MAXTABSIZE_DEFAULT;
591032a4398Schristos 	}
5922ef06139Schristos 	return maxtabsize;
5932ef06139Schristos }
594032a4398Schristos 
5952ef06139Schristos static int
checkmaxtabsize(FILE * fp,size_t maxtabsize)5962ef06139Schristos checkmaxtabsize(FILE *fp, size_t maxtabsize)
5972ef06139Schristos {
5982ef06139Schristos 	struct	stat statbuf;
5992ef06139Schristos 
6002ef06139Schristos 	if (fstat(fileno(fp), &statbuf))  {
601032a4398Schristos 		warn("error stat'ing crontab input");
6022ef06139Schristos 		return 0;
6032ef06139Schristos 	}
6042ef06139Schristos 	if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize)  {
6052ef06139Schristos 		warnx("%ju bytes is larger than the maximum size of %ju bytes",
6062ef06139Schristos 		    (uintmax_t)statbuf.st_size, (uintmax_t)maxtabsize);
6072ef06139Schristos 		return 0;
6082ef06139Schristos 	}
6092ef06139Schristos 	return 1;
6102ef06139Schristos }
6112ef06139Schristos 
6122ef06139Schristos static void
cleanTempFile(void)6132ef06139Schristos cleanTempFile(void)
6142ef06139Schristos {
6152ef06139Schristos 
6162ef06139Schristos 	if (TempFilename[0]) {
6172ef06139Schristos 		(void) unlink(TempFilename);
6182ef06139Schristos 		TempFilename[0] = '\0';
6192ef06139Schristos 	}
6202ef06139Schristos }
6212ef06139Schristos 
6220b40943bSjoerg static __dead void
bail(int signo)6232ef06139Schristos bail(int signo)
6242ef06139Schristos {
6252ef06139Schristos 
6262ef06139Schristos 	cleanTempFile();
6272ef06139Schristos 	errx(ERROR_EXIT, "Exiting on signal %d", signo);
6282ef06139Schristos }
6292ef06139Schristos 
6302ef06139Schristos /* returns	0	on success
6312ef06139Schristos  *		-1	on syntax error
6322ef06139Schristos  *		-2	on install error
6332ef06139Schristos  */
6342ef06139Schristos static int
replace_cmd(void)6352ef06139Schristos replace_cmd(void) {
6362ef06139Schristos 	char n[MAX_FNAME], envstr[MAX_ENVSTR];
6372ef06139Schristos 	int lastch;
6382ef06139Schristos 	FILE *tmp = NULL;
6392ef06139Schristos 	int ch, eof, fd;
6402ef06139Schristos 	int error = 0;
6412ef06139Schristos 	entry *e;
6422ef06139Schristos 	sig_t oint, oabrt, oquit, ohup;
6432ef06139Schristos 	uid_t file_owner;
6442ef06139Schristos 	time_t now = time(NULL);
6452ef06139Schristos 	char **envp = env_init();
6462ef06139Schristos 	size_t i, maxtabsize;
6472ef06139Schristos 
6482ef06139Schristos 	if (envp == NULL) {
6492ef06139Schristos 		warn("Cannot allocate memory.");
6502ef06139Schristos 		return (-2);
6512ef06139Schristos 	}
6522ef06139Schristos 
6532ef06139Schristos 	if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR,
6542ef06139Schristos 	    "tmp.XXXXXXXXXX", '/')) {
6552ef06139Schristos 		TempFilename[0] = '\0';
6562ef06139Schristos 		warnx("path too long");
6572ef06139Schristos 		return (-2);
6582ef06139Schristos 	}
6592ef06139Schristos 
6602ef06139Schristos 	/* Interruptible while doing I/O */
6612ef06139Schristos 	ohup = signal(SIGHUP, bail);
6622ef06139Schristos 	oint = signal(SIGINT, bail);
6632ef06139Schristos 	oquit = signal(SIGQUIT, bail);
6642ef06139Schristos 	oabrt = signal(SIGABRT, bail);
6652ef06139Schristos 
6662ef06139Schristos 	if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) {
6672ef06139Schristos 		warn("cannot create `%s'", TempFilename);
6682ef06139Schristos 		if (fd != -1)
6692ef06139Schristos 			(void)close(fd);
670032a4398Schristos 		error = -2;
671032a4398Schristos 		goto done;
672032a4398Schristos 	}
6732ef06139Schristos 
6742ef06139Schristos 	maxtabsize = getmaxtabsize();
6752ef06139Schristos 
6762ef06139Schristos 	/* This does not work for stdin, so we'll also check later */
6772ef06139Schristos 	if (!checkmaxtabsize(NewCrontab, maxtabsize)) {
678032a4398Schristos 		error = -2;
679032a4398Schristos 		goto done;
680032a4398Schristos 	}
6810061c6a5Schristos 
6820061c6a5Schristos 	/* write a signature at the top of the file.
6830061c6a5Schristos 	 *
6840061c6a5Schristos 	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
6850061c6a5Schristos 	 */
6862ef06139Schristos 	(void)fprintf(tmp,
6872ef06139Schristos 	    "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
6882ef06139Schristos 	(void)fprintf(tmp,
6892ef06139Schristos 	    "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
6902ef06139Schristos 	(void)fprintf(tmp,
6912ef06139Schristos 	    "# (Cron version %s -- %s)\n", CRON_VERSION,
692*ea01913fShtodd 	    "$NetBSD: crontab.c,v 1.15 2018/03/06 21:21:27 htodd Exp $");
6930061c6a5Schristos 
6940061c6a5Schristos 	/* copy the crontab to the tmp
6950061c6a5Schristos 	 */
696032a4398Schristos 	(void)rewind(NewCrontab);
697032a4398Schristos 	Set_LineNum(1);
698032a4398Schristos 	lastch = EOF;
6992ef06139Schristos 	for (i = 0; i < maxtabsize && EOF != (ch = get_char(NewCrontab)); i++)
700ae5dea0aSchristos 		(void)putc(lastch = (char)ch, tmp);
701032a4398Schristos 
7022ef06139Schristos 	/* Non-Interruptible while installing */
7032ef06139Schristos 	(void)signal(SIGHUP, SIG_IGN);
7042ef06139Schristos 	(void)signal(SIGINT, SIG_IGN);
7052ef06139Schristos 	(void)signal(SIGQUIT, SIG_IGN);
7062ef06139Schristos 	(void)signal(SIGABRT, SIG_IGN);
7072ef06139Schristos 
7082ef06139Schristos 	if (i == maxtabsize) {
7092ef06139Schristos 		warnx("is larger than the maximum size of %ju bytes",
7102ef06139Schristos 		    (uintmax_t)maxtabsize);
7112ef06139Schristos 		error = -2;
7122ef06139Schristos 		goto done;
7132ef06139Schristos 	}
7142ef06139Schristos 
715734041fcSchristos 	if (lastch != EOF && lastch != '\n') {
716032a4398Schristos 		warnx("missing trailing newline in `%s'", Filename);
717032a4398Schristos 		error = -1;
718032a4398Schristos 		goto done;
719032a4398Schristos 	}
720032a4398Schristos 
721032a4398Schristos 	if (ferror(NewCrontab)) {
722032a4398Schristos 		warn("error while reading `%s'", Filename);
723032a4398Schristos 		error = -2;
724032a4398Schristos 		goto done;
725032a4398Schristos 	}
726032a4398Schristos 
727032a4398Schristos 	(void)ftruncate(fileno(tmp), ftell(tmp));
728032a4398Schristos 	/* XXX this should be a NOOP - is */
729032a4398Schristos 	(void)fflush(tmp);
7300061c6a5Schristos 
7310061c6a5Schristos 	if (ferror(tmp)) {
732032a4398Schristos 		warn("error while writing new crontab to `%s'", TempFilename);
7330061c6a5Schristos 		error = -2;
7340061c6a5Schristos 		goto done;
7350061c6a5Schristos 	}
7360061c6a5Schristos 
7370061c6a5Schristos 	/* check the syntax of the file being installed.
7380061c6a5Schristos 	 */
7390061c6a5Schristos 
7400061c6a5Schristos 	/* BUG: was reporting errors after the EOF if there were any errors
7410061c6a5Schristos 	 * in the file proper -- kludged it by stopping after first error.
7420061c6a5Schristos 	 *		vix 31mar87
7430061c6a5Schristos 	 */
744032a4398Schristos 	Set_LineNum(1 - NHEADER_LINES);
7450061c6a5Schristos 	CheckErrorCount = 0;  eof = FALSE;
746f8739668Schristos 	rewind(tmp);
7470061c6a5Schristos 	while (!CheckErrorCount && !eof) {
7480061c6a5Schristos 		switch (load_env(envstr, tmp)) {
7490061c6a5Schristos 		case ERR:
7500061c6a5Schristos 			/* check for data before the EOF */
7510061c6a5Schristos 			if (envstr[0] != '\0') {
7520061c6a5Schristos 				Set_LineNum(LineNumber + 1);
7530061c6a5Schristos 				check_error("premature EOF");
7540061c6a5Schristos 			}
7550061c6a5Schristos 			eof = TRUE;
7560061c6a5Schristos 			break;
7570061c6a5Schristos 		case FALSE:
7580061c6a5Schristos 			e = load_entry(tmp, check_error, pw, envp);
7590061c6a5Schristos 			if (e)
7600061c6a5Schristos 				free(e);
7610061c6a5Schristos 			break;
7620061c6a5Schristos 		case TRUE:
7630061c6a5Schristos 			break;
7640061c6a5Schristos 		}
7650061c6a5Schristos 	}
7660061c6a5Schristos 
7670061c6a5Schristos 	if (CheckErrorCount != 0) {
768032a4398Schristos 		warnx("errors in crontab file, can't install.");
7690061c6a5Schristos 		error = -1;
7700061c6a5Schristos 		goto done;
7710061c6a5Schristos 	}
7720061c6a5Schristos 
7730061c6a5Schristos 	file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid;
7740061c6a5Schristos 
775032a4398Schristos #ifdef HAVE_FCHOWN
776032a4398Schristos 	error = fchown(fileno(tmp), file_owner, (uid_t)-1);
7770061c6a5Schristos #else
778032a4398Schristos 	error = chown(TempFilename, file_owner, (gid_t)-1);
779032a4398Schristos #endif
7800061c6a5Schristos 
7810061c6a5Schristos 	if (fclose(tmp) == EOF) {
782032a4398Schristos 		warn("error closing file");
7830061c6a5Schristos 		error = -2;
7842ef06139Schristos 		tmp = NULL;
7852ef06139Schristos 		goto done;
7862ef06139Schristos 	}
7872ef06139Schristos 	tmp = NULL;
7882ef06139Schristos 
7892ef06139Schristos 	if (error < OK) {
7902ef06139Schristos 		warn("cannot chown `%s'", TempFilename);
7912ef06139Schristos 		error = -2;
7920061c6a5Schristos 		goto done;
7930061c6a5Schristos 	}
7940061c6a5Schristos 
7950061c6a5Schristos 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
796032a4398Schristos 		warnx("path too long");
7970061c6a5Schristos 		error = -2;
7980061c6a5Schristos 		goto done;
7990061c6a5Schristos 	}
8000061c6a5Schristos 	if (rename(TempFilename, n)) {
801032a4398Schristos 		warn("error renaming `%s' to `%s'", TempFilename, n);
8020061c6a5Schristos 		error = -2;
8030061c6a5Schristos 		goto done;
8040061c6a5Schristos 	}
8050061c6a5Schristos 	TempFilename[0] = '\0';
8060061c6a5Schristos 	log_it(RealUser, Pid, "REPLACE", User);
8070061c6a5Schristos 
8080061c6a5Schristos 	poke_daemon();
8090061c6a5Schristos 
8100061c6a5Schristos done:
811032a4398Schristos 	(void)signal(SIGHUP, ohup);
812032a4398Schristos 	(void)signal(SIGINT, oint);
813032a4398Schristos 	(void)signal(SIGQUIT, oquit);
814032a4398Schristos 	(void)signal(SIGABRT, oabrt);
8152ef06139Schristos 	if (tmp != NULL)
8162ef06139Schristos 		(void)fclose(tmp);
8172ef06139Schristos 	cleanTempFile();
8180061c6a5Schristos 	return (error);
8190061c6a5Schristos }
8200061c6a5Schristos 
8210061c6a5Schristos static void
poke_daemon(void)8220061c6a5Schristos poke_daemon(void) {
8230a00da6dSchristos 	struct timespec ts[2];
8240a00da6dSchristos 	(void) clock_gettime(CLOCK_REALTIME, ts);
8250a00da6dSchristos 	ts[1] = ts[0];
8260a00da6dSchristos 	if (change_time(SPOOL_DIR, ts) == -1)
8270a00da6dSchristos 		warn("can't update times on spooldir %s", SPOOL_DIR);
8280061c6a5Schristos }
8290a00da6dSchristos 
830032a4398Schristos /* int allowed(const char *username, const char *allow_file, const char *deny_file)
831032a4398Schristos  *	returns TRUE if (allow_file exists and user is listed)
832032a4398Schristos  *	or (deny_file exists and user is NOT listed).
833032a4398Schristos  *	root is always allowed.
834032a4398Schristos  */
835032a4398Schristos static int
allowed(const char * username,const char * allow_file,const char * deny_file)836032a4398Schristos allowed(const char *username, const char *allow_file, const char *deny_file) {
837032a4398Schristos 	FILE	*fp;
838032a4398Schristos 	int	isallowed;
839032a4398Schristos 
840032a4398Schristos 	if (strcmp(username, ROOT_USER) == 0)
841032a4398Schristos 		return (TRUE);
842e8894454Schristos #ifdef ALLOW_ONLY_ROOT
843032a4398Schristos 	isallowed = FALSE;
844e8894454Schristos #else
845e8894454Schristos 	isallowed = TRUE;
846e8894454Schristos #endif
847032a4398Schristos 	if ((fp = fopen(allow_file, "r")) != NULL) {
848032a4398Schristos 		isallowed = in_file(username, fp, FALSE);
849032a4398Schristos 		(void)fclose(fp);
850032a4398Schristos 	} else if ((fp = fopen(deny_file, "r")) != NULL) {
851032a4398Schristos 		isallowed = !in_file(username, fp, FALSE);
852032a4398Schristos 		(void)fclose(fp);
853032a4398Schristos 	}
854032a4398Schristos 	return (isallowed);
855032a4398Schristos }
856032a4398Schristos /* int in_file(const char *string, FILE *file, int error)
857032a4398Schristos  *	return TRUE if one of the lines in file matches string exactly,
858032a4398Schristos  *	FALSE if no lines match, and error on error.
859032a4398Schristos  */
860032a4398Schristos static int
in_file(const char * string,FILE * file,int error)861032a4398Schristos in_file(const char *string, FILE *file, int error)
862032a4398Schristos {
863032a4398Schristos 	char line[MAX_TEMPSTR];
864032a4398Schristos 	char *endp;
865032a4398Schristos 
866032a4398Schristos 	if (fseek(file, 0L, SEEK_SET))
867032a4398Schristos 		return (error);
868032a4398Schristos 	while (fgets(line, MAX_TEMPSTR, file)) {
869032a4398Schristos 		if (line[0] != '\0') {
870032a4398Schristos 			endp = &line[strlen(line) - 1];
871032a4398Schristos 			if (*endp != '\n')
872032a4398Schristos 				return (error);
873032a4398Schristos 			*endp = '\0';
874032a4398Schristos 			if (0 == strcmp(line, string))
875032a4398Schristos 				return (TRUE);
876032a4398Schristos 		}
877032a4398Schristos 	}
878032a4398Schristos 	if (ferror(file))
879032a4398Schristos 		return (error);
880032a4398Schristos 	return (FALSE);
8810061c6a5Schristos }
8820061c6a5Schristos 
883032a4398Schristos #ifdef HAVE_SAVED_UIDS
884032a4398Schristos 
relinquish_priv(void)885*ea01913fShtodd static int relinquish_priv(void) {
8862ed8e2f0Schristos 	return (setegid(rgid) || seteuid(ruid)) ? -1 : 0;
8870061c6a5Schristos }
8882ed8e2f0Schristos 
regain_priv(void)8892ed8e2f0Schristos static int regain_priv(void) {
8902ed8e2f0Schristos 	return (setegid(egid) || seteuid(euid)) ? -1 : 0;
891032a4398Schristos }
892032a4398Schristos 
893032a4398Schristos #else /*HAVE_SAVED_UIDS*/
894032a4398Schristos 
relinquish_priv(void)895*ea01913fShtodd static int relinquish_priv(void) {
8962ed8e2f0Schristos 	return (setregid(egid, rgid) || setreuid(euid, ruid)) ? -1 : 0;
897032a4398Schristos }
898032a4398Schristos 
regain_priv(void)8992ed8e2f0Schristos static int regain_priv(void) {
9002ed8e2f0Schristos 	return (setregid(rgid, egid) || setreuid(ruid, euid)) ? -1 : 0;
901032a4398Schristos }
902032a4398Schristos #endif /*HAVE_SAVED_UIDS*/
903