xref: /freebsd/usr.sbin/cron/crontab/crontab.c (revision fe590ffe40f49fe09d8275fbf29f0d46c5b99dc7)
184f33deaSJordan K. Hubbard /* Copyright 1988,1990,1993,1994 by Paul Vixie
284f33deaSJordan K. Hubbard  * All rights reserved
3*fe590ffeSEric van Gyzen  */
4*fe590ffeSEric van Gyzen 
5*fe590ffeSEric van Gyzen /*
6*fe590ffeSEric van Gyzen  * Copyright (c) 1997 by Internet Software Consortium
784f33deaSJordan K. Hubbard  *
8*fe590ffeSEric van Gyzen  * Permission to use, copy, modify, and distribute this software for any
9*fe590ffeSEric van Gyzen  * purpose with or without fee is hereby granted, provided that the above
10*fe590ffeSEric van Gyzen  * copyright notice and this permission notice appear in all copies.
1184f33deaSJordan K. Hubbard  *
12*fe590ffeSEric van Gyzen  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
13*fe590ffeSEric van Gyzen  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
14*fe590ffeSEric van Gyzen  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
15*fe590ffeSEric van Gyzen  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16*fe590ffeSEric van Gyzen  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17*fe590ffeSEric van Gyzen  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
18*fe590ffeSEric van Gyzen  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
19*fe590ffeSEric van Gyzen  * SOFTWARE.
2084f33deaSJordan K. Hubbard  */
2184f33deaSJordan K. Hubbard 
2284f33deaSJordan K. Hubbard #if !defined(lint) && !defined(LINT)
23401e6468SPhilippe Charnier static const char rcsid[] =
24*fe590ffeSEric van Gyzen     "$Id: crontab.c,v 1.3 1998/08/14 00:32:38 vixie Exp $";
2584f33deaSJordan K. Hubbard #endif
2684f33deaSJordan K. Hubbard 
2784f33deaSJordan K. Hubbard /* crontab - install and manage per-user crontab files
2884f33deaSJordan K. Hubbard  * vix 02may87 [RCS has the rest of the log]
2984f33deaSJordan K. Hubbard  * vix 26jan87 [original]
3084f33deaSJordan K. Hubbard  */
3184f33deaSJordan K. Hubbard 
3284f33deaSJordan K. Hubbard #define	MAIN_PROGRAM
3384f33deaSJordan K. Hubbard 
3484f33deaSJordan K. Hubbard #include "cron.h"
350cd2e3abSDiomidis Spinellis #include <md5.h>
3684f33deaSJordan K. Hubbard 
370cd2e3abSDiomidis Spinellis #define MD5_SIZE 33
3884f33deaSJordan K. Hubbard #define NHEADER_LINES 3
3984f33deaSJordan K. Hubbard 
4084f33deaSJordan K. Hubbard enum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
4184f33deaSJordan K. Hubbard 
4284f33deaSJordan K. Hubbard #if DEBUGGING
4384f33deaSJordan K. Hubbard static char	*Options[] = { "???", "list", "delete", "edit", "replace" };
4484f33deaSJordan K. Hubbard #endif
4584f33deaSJordan K. Hubbard 
4684f33deaSJordan K. Hubbard static	PID_T		Pid;
475e5d4b23SEd Maste static	char		User[MAXLOGNAME], RealUser[MAXLOGNAME];
4884f33deaSJordan K. Hubbard static	char		Filename[MAX_FNAME];
4984f33deaSJordan K. Hubbard static	FILE		*NewCrontab;
5084f33deaSJordan K. Hubbard static	int		CheckErrorCount;
5184f33deaSJordan K. Hubbard static	enum opt_t	Option;
52d21656dcSConrad Meyer static	int		fflag;
5384f33deaSJordan K. Hubbard static	struct passwd	*pw;
54784bddbcSKevin Lo static	void		list_cmd(void),
55784bddbcSKevin Lo 			delete_cmd(void),
56784bddbcSKevin Lo 			edit_cmd(void),
57784bddbcSKevin Lo 			poke_daemon(void),
58*fe590ffeSEric van Gyzen 			check_error(const char *),
59784bddbcSKevin Lo 			parse_args(int c, char *v[]);
60784bddbcSKevin Lo static	int		replace_cmd(void);
6184f33deaSJordan K. Hubbard 
6284f33deaSJordan K. Hubbard static void
usage(const char * msg)63*fe590ffeSEric van Gyzen usage(const char *msg)
6484f33deaSJordan K. Hubbard {
65401e6468SPhilippe Charnier 	fprintf(stderr, "crontab: usage error: %s\n", msg);
66401e6468SPhilippe Charnier 	fprintf(stderr, "%s\n%s\n",
67401e6468SPhilippe Charnier 		"usage: crontab [-u user] file",
68d21656dcSConrad Meyer 		"       crontab [-u user] { -l | -r [-f] | -e }");
6984f33deaSJordan K. Hubbard 	exit(ERROR_EXIT);
7084f33deaSJordan K. Hubbard }
7184f33deaSJordan K. Hubbard 
7284f33deaSJordan K. Hubbard int
main(int argc,char * argv[])7366d48cdaSMatteo Riondato main(int argc, char *argv[])
7484f33deaSJordan K. Hubbard {
7584f33deaSJordan K. Hubbard 	int	exitstatus;
7684f33deaSJordan K. Hubbard 
7784f33deaSJordan K. Hubbard 	Pid = getpid();
7884f33deaSJordan K. Hubbard 	ProgramName = argv[0];
7984f33deaSJordan K. Hubbard 
8084f33deaSJordan K. Hubbard 	setlocale(LC_ALL, "");
8184f33deaSJordan K. Hubbard 
8284f33deaSJordan K. Hubbard #if defined(BSD)
8384f33deaSJordan K. Hubbard 	setlinebuf(stderr);
8484f33deaSJordan K. Hubbard #endif
8584f33deaSJordan K. Hubbard 	parse_args(argc, argv);		/* sets many globals, opens a file */
8684f33deaSJordan K. Hubbard 	set_cron_uid();
8784f33deaSJordan K. Hubbard 	set_cron_cwd();
8884f33deaSJordan K. Hubbard 	if (!allowed(User)) {
89401e6468SPhilippe Charnier 		warnx("you (%s) are not allowed to use this program", User);
9084f33deaSJordan K. Hubbard 		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
9184f33deaSJordan K. Hubbard 		exit(ERROR_EXIT);
9284f33deaSJordan K. Hubbard 	}
9384f33deaSJordan K. Hubbard 	exitstatus = OK_EXIT;
9484f33deaSJordan K. Hubbard 	switch (Option) {
95*fe590ffeSEric van Gyzen 	case opt_list:
96*fe590ffeSEric van Gyzen 		list_cmd();
9784f33deaSJordan K. Hubbard 		break;
98*fe590ffeSEric van Gyzen 	case opt_delete:
99*fe590ffeSEric van Gyzen 		delete_cmd();
10084f33deaSJordan K. Hubbard 		break;
101*fe590ffeSEric van Gyzen 	case opt_edit:
102*fe590ffeSEric van Gyzen 		edit_cmd();
10384f33deaSJordan K. Hubbard 		break;
104*fe590ffeSEric van Gyzen 	case opt_replace:
105*fe590ffeSEric van Gyzen 		if (replace_cmd() < 0)
10684f33deaSJordan K. Hubbard 			exitstatus = ERROR_EXIT;
10784f33deaSJordan K. Hubbard 		break;
108401e6468SPhilippe Charnier 	case opt_unknown:
109*fe590ffeSEric van Gyzen 	default:
110*fe590ffeSEric van Gyzen 		abort();
11184f33deaSJordan K. Hubbard 	}
112e9d295bfSDima Dorfman 	exit(exitstatus);
11384f33deaSJordan K. Hubbard 	/*NOTREACHED*/
11484f33deaSJordan K. Hubbard }
11584f33deaSJordan K. Hubbard 
11684f33deaSJordan K. Hubbard static void
parse_args(int argc,char * argv[])117e93f27e3SJohn Baldwin parse_args(int argc, char *argv[])
11884f33deaSJordan K. Hubbard {
11984f33deaSJordan K. Hubbard 	int argch;
1209896de5aSBrooks Davis 	char resolved_path[PATH_MAX];
12184f33deaSJordan K. Hubbard 
122401e6468SPhilippe Charnier 	if (!(pw = getpwuid(getuid())))
123401e6468SPhilippe Charnier 		errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
124c11807acSMatteo Riondato 	bzero(pw->pw_passwd, strlen(pw->pw_passwd));
125bdddbd2fSPaul Traina 	(void) strncpy(User, pw->pw_name, (sizeof User)-1);
126bdddbd2fSPaul Traina 	User[(sizeof User)-1] = '\0';
12784f33deaSJordan K. Hubbard 	strcpy(RealUser, User);
12884f33deaSJordan K. Hubbard 	Filename[0] = '\0';
12984f33deaSJordan K. Hubbard 	Option = opt_unknown;
130d21656dcSConrad Meyer 	while ((argch = getopt(argc, argv, "u:lerx:f")) != -1) {
13184f33deaSJordan K. Hubbard 		switch (argch) {
13284f33deaSJordan K. Hubbard 		case 'x':
13384f33deaSJordan K. Hubbard 			if (!set_debug_flags(optarg))
13484f33deaSJordan K. Hubbard 				usage("bad debug option");
13584f33deaSJordan K. Hubbard 			break;
13684f33deaSJordan K. Hubbard 		case 'u':
13784f33deaSJordan K. Hubbard 			if (getuid() != ROOT_UID)
138401e6468SPhilippe Charnier 				errx(ERROR_EXIT, "must be privileged to use -u");
13984f33deaSJordan K. Hubbard 			if (!(pw = getpwnam(optarg)))
140401e6468SPhilippe Charnier 				errx(ERROR_EXIT, "user `%s' unknown", optarg);
141c11807acSMatteo Riondato 			bzero(pw->pw_passwd, strlen(pw->pw_passwd));
142bdddbd2fSPaul Traina 			(void) strncpy(User, pw->pw_name, (sizeof User)-1);
143bdddbd2fSPaul Traina 			User[(sizeof User)-1] = '\0';
14484f33deaSJordan K. Hubbard 			break;
14584f33deaSJordan K. Hubbard 		case 'l':
14684f33deaSJordan K. Hubbard 			if (Option != opt_unknown)
14784f33deaSJordan K. Hubbard 				usage("only one operation permitted");
14884f33deaSJordan K. Hubbard 			Option = opt_list;
14984f33deaSJordan K. Hubbard 			break;
15084f33deaSJordan K. Hubbard 		case 'r':
15184f33deaSJordan K. Hubbard 			if (Option != opt_unknown)
15284f33deaSJordan K. Hubbard 				usage("only one operation permitted");
15384f33deaSJordan K. Hubbard 			Option = opt_delete;
15484f33deaSJordan K. Hubbard 			break;
15584f33deaSJordan K. Hubbard 		case 'e':
15684f33deaSJordan K. Hubbard 			if (Option != opt_unknown)
15784f33deaSJordan K. Hubbard 				usage("only one operation permitted");
15884f33deaSJordan K. Hubbard 			Option = opt_edit;
15984f33deaSJordan K. Hubbard 			break;
160d21656dcSConrad Meyer 		case 'f':
161d21656dcSConrad Meyer 			fflag = 1;
162d21656dcSConrad Meyer 			break;
16384f33deaSJordan K. Hubbard 		default:
16484f33deaSJordan K. Hubbard 			usage("unrecognized option");
16584f33deaSJordan K. Hubbard 		}
16684f33deaSJordan K. Hubbard 	}
16784f33deaSJordan K. Hubbard 
16884f33deaSJordan K. Hubbard 	endpwent();
16984f33deaSJordan K. Hubbard 
17084f33deaSJordan K. Hubbard 	if (Option != opt_unknown) {
17184f33deaSJordan K. Hubbard 		if (argv[optind] != NULL) {
17284f33deaSJordan K. Hubbard 			usage("no arguments permitted after this option");
17384f33deaSJordan K. Hubbard 		}
17484f33deaSJordan K. Hubbard 	} else {
17584f33deaSJordan K. Hubbard 		if (argv[optind] != NULL) {
17684f33deaSJordan K. Hubbard 			Option = opt_replace;
177bdddbd2fSPaul Traina 			(void) strncpy (Filename, argv[optind], (sizeof Filename)-1);
178bdddbd2fSPaul Traina 			Filename[(sizeof Filename)-1] = '\0';
179bdddbd2fSPaul Traina 
18084f33deaSJordan K. Hubbard 		} else {
18184f33deaSJordan K. Hubbard 			usage("file name must be specified for replace");
18284f33deaSJordan K. Hubbard 		}
18384f33deaSJordan K. Hubbard 	}
18484f33deaSJordan K. Hubbard 
18584f33deaSJordan K. Hubbard 	if (Option == opt_replace) {
18684f33deaSJordan K. Hubbard 		/* relinquish the setuid status of the binary during
18784f33deaSJordan K. Hubbard 		 * the open, lest nonroot users read files they should
18884f33deaSJordan K. Hubbard 		 * not be able to read.  we can't use access() here
18984f33deaSJordan K. Hubbard 		 * since there's a race condition.  thanks go out to
19084f33deaSJordan K. Hubbard 		 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
19184f33deaSJordan K. Hubbard 		 * the race.
19284f33deaSJordan K. Hubbard 		 */
19384f33deaSJordan K. Hubbard 
194401e6468SPhilippe Charnier 		if (swap_uids() < OK)
195401e6468SPhilippe Charnier 			err(ERROR_EXIT, "swapping uids");
196cc427081SXin LI 
197cc427081SXin LI 		/* we have to open the file here because we're going to
198cc427081SXin LI 		 * chdir(2) into /var/cron before we get around to
199cc427081SXin LI 		 * reading the file.
200cc427081SXin LI 		 */
201cc427081SXin LI 		if (!strcmp(Filename, "-")) {
202cc427081SXin LI 			NewCrontab = stdin;
203cc427081SXin LI 		} else if (realpath(Filename, resolved_path) != NULL &&
204cc427081SXin LI 		    !strcmp(resolved_path, SYSCRONTAB)) {
205cc427081SXin LI 			err(ERROR_EXIT, SYSCRONTAB " must be edited manually");
206cc427081SXin LI 		} else {
207401e6468SPhilippe Charnier 			if (!(NewCrontab = fopen(Filename, "r")))
208401e6468SPhilippe Charnier 				err(ERROR_EXIT, "%s", Filename);
209cc427081SXin LI 		}
21066d48cdaSMatteo Riondato 		if (swap_uids_back() < OK)
211401e6468SPhilippe Charnier 			err(ERROR_EXIT, "swapping uids back");
21284f33deaSJordan K. Hubbard 	}
21384f33deaSJordan K. Hubbard 
21484f33deaSJordan K. Hubbard 	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
21584f33deaSJordan K. Hubbard 		      User, Filename, Options[(int)Option]))
21684f33deaSJordan K. Hubbard }
21784f33deaSJordan K. Hubbard 
2189a2ef7d1SDiomidis Spinellis static void
copy_file(FILE * in,FILE * out)219*fe590ffeSEric van Gyzen copy_file(FILE *in, FILE *out)
220*fe590ffeSEric van Gyzen {
2219a2ef7d1SDiomidis Spinellis 	int x, ch;
2229a2ef7d1SDiomidis Spinellis 
2239a2ef7d1SDiomidis Spinellis 	Set_LineNum(1)
2249a2ef7d1SDiomidis Spinellis 	/* ignore the top few comments since we probably put them there.
2259a2ef7d1SDiomidis Spinellis 	 */
2269a2ef7d1SDiomidis Spinellis 	for (x = 0; x < NHEADER_LINES; x++) {
2279a2ef7d1SDiomidis Spinellis 		ch = get_char(in);
2289a2ef7d1SDiomidis Spinellis 		if (EOF == ch)
2299a2ef7d1SDiomidis Spinellis 			break;
2309a2ef7d1SDiomidis Spinellis 		if ('#' != ch) {
2319a2ef7d1SDiomidis Spinellis 			putc(ch, out);
2329a2ef7d1SDiomidis Spinellis 			break;
2339a2ef7d1SDiomidis Spinellis 		}
2349a2ef7d1SDiomidis Spinellis 		while (EOF != (ch = get_char(in)))
2359a2ef7d1SDiomidis Spinellis 			if (ch == '\n')
2369a2ef7d1SDiomidis Spinellis 				break;
2379a2ef7d1SDiomidis Spinellis 		if (EOF == ch)
2389a2ef7d1SDiomidis Spinellis 			break;
2399a2ef7d1SDiomidis Spinellis 	}
2409a2ef7d1SDiomidis Spinellis 
2419a2ef7d1SDiomidis Spinellis 	/* copy the rest of the crontab (if any) to the output file.
2429a2ef7d1SDiomidis Spinellis 	 */
2439a2ef7d1SDiomidis Spinellis 	if (EOF != ch)
2449a2ef7d1SDiomidis Spinellis 		while (EOF != (ch = get_char(in)))
2459a2ef7d1SDiomidis Spinellis 			putc(ch, out);
2469a2ef7d1SDiomidis Spinellis }
24784f33deaSJordan K. Hubbard 
24884f33deaSJordan K. Hubbard static void
list_cmd(void)249e93f27e3SJohn Baldwin list_cmd(void)
250e93f27e3SJohn Baldwin {
25184f33deaSJordan K. Hubbard 	char n[MAX_FNAME];
25284f33deaSJordan K. Hubbard 	FILE *f;
25384f33deaSJordan K. Hubbard 
25484f33deaSJordan K. Hubbard 	log_it(RealUser, Pid, "LIST", User);
2552b9f079cSMatteo Riondato 	(void) snprintf(n, sizeof(n), CRON_TAB(User));
25684f33deaSJordan K. Hubbard 	if (!(f = fopen(n, "r"))) {
25784f33deaSJordan K. Hubbard 		if (errno == ENOENT)
258401e6468SPhilippe Charnier 			errx(ERROR_EXIT, "no crontab for %s", User);
25984f33deaSJordan K. Hubbard 		else
260401e6468SPhilippe Charnier 			err(ERROR_EXIT, "%s", n);
26184f33deaSJordan K. Hubbard 	}
26284f33deaSJordan K. Hubbard 
26384f33deaSJordan K. Hubbard 	/* file is open. copy to stdout, close.
26484f33deaSJordan K. Hubbard 	 */
2659a2ef7d1SDiomidis Spinellis 	copy_file(f, stdout);
26684f33deaSJordan K. Hubbard 	fclose(f);
26784f33deaSJordan K. Hubbard }
26884f33deaSJordan K. Hubbard 
26984f33deaSJordan K. Hubbard static void
delete_cmd(void)270e93f27e3SJohn Baldwin delete_cmd(void)
271e93f27e3SJohn Baldwin {
27284f33deaSJordan K. Hubbard 	char n[MAX_FNAME];
27330cfb241SPaul Richards 	int ch, first;
27430cfb241SPaul Richards 
275d21656dcSConrad Meyer 	if (!fflag && isatty(STDIN_FILENO)) {
27630cfb241SPaul Richards 		(void)fprintf(stderr, "remove crontab for %s? ", User);
27730cfb241SPaul Richards 		first = ch = getchar();
27830cfb241SPaul Richards 		while (ch != '\n' && ch != EOF)
27930cfb241SPaul Richards 			ch = getchar();
28030cfb241SPaul Richards 		if (first != 'y' && first != 'Y')
28130cfb241SPaul Richards 			return;
28230cfb241SPaul Richards 	}
28384f33deaSJordan K. Hubbard 
28484f33deaSJordan K. Hubbard 	log_it(RealUser, Pid, "DELETE", User);
285*fe590ffeSEric van Gyzen 	if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
286*fe590ffeSEric van Gyzen 		errx(ERROR_EXIT, "path too long");
287*fe590ffeSEric van Gyzen 	if (unlink(n) != 0) {
28884f33deaSJordan K. Hubbard 		if (errno == ENOENT)
289401e6468SPhilippe Charnier 			errx(ERROR_EXIT, "no crontab for %s", User);
29084f33deaSJordan K. Hubbard 		else
291401e6468SPhilippe Charnier 			err(ERROR_EXIT, "%s", n);
29284f33deaSJordan K. Hubbard 	}
29384f33deaSJordan K. Hubbard 	poke_daemon();
29484f33deaSJordan K. Hubbard }
29584f33deaSJordan K. Hubbard 
29684f33deaSJordan K. Hubbard static void
check_error(const char * msg)297*fe590ffeSEric van Gyzen check_error(const char *msg)
29884f33deaSJordan K. Hubbard {
29984f33deaSJordan K. Hubbard 	CheckErrorCount++;
30084f33deaSJordan K. Hubbard 	fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
30184f33deaSJordan K. Hubbard }
30284f33deaSJordan K. Hubbard 
30384f33deaSJordan K. Hubbard static void
edit_cmd(void)304e93f27e3SJohn Baldwin edit_cmd(void)
305e93f27e3SJohn Baldwin {
30684f33deaSJordan K. Hubbard 	char n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
30784f33deaSJordan K. Hubbard 	FILE *f;
3089a2ef7d1SDiomidis Spinellis 	int t;
30925e9ca2bSDavid Malone 	struct stat statbuf, fsbuf;
31084f33deaSJordan K. Hubbard 	WAIT_T waiter;
31184f33deaSJordan K. Hubbard 	PID_T pid, xpid;
312bdddbd2fSPaul Traina 	mode_t um;
31308e019a8SDiomidis Spinellis 	int syntax_error = 0;
3140cd2e3abSDiomidis Spinellis 	char orig_md5[MD5_SIZE];
3150cd2e3abSDiomidis Spinellis 	char new_md5[MD5_SIZE];
31684f33deaSJordan K. Hubbard 
31784f33deaSJordan K. Hubbard 	log_it(RealUser, Pid, "BEGIN EDIT", User);
318*fe590ffeSEric van Gyzen 	if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n))
319*fe590ffeSEric van Gyzen 		errx(ERROR_EXIT, "path too long");
32084f33deaSJordan K. Hubbard 	if (!(f = fopen(n, "r"))) {
321401e6468SPhilippe Charnier 		if (errno != ENOENT)
322401e6468SPhilippe Charnier 			err(ERROR_EXIT, "%s", n);
323401e6468SPhilippe Charnier 		warnx("no crontab for %s - using an empty one", User);
3241a37aa56SDavid E. O'Brien 		if (!(f = fopen(_PATH_DEVNULL, "r")))
3251a37aa56SDavid E. O'Brien 			err(ERROR_EXIT, _PATH_DEVNULL);
32684f33deaSJordan K. Hubbard 	}
32784f33deaSJordan K. Hubbard 
328bdddbd2fSPaul Traina 	um = umask(077);
3292b9f079cSMatteo Riondato 	(void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX");
330bdddbd2fSPaul Traina 	if ((t = mkstemp(Filename)) == -1) {
331401e6468SPhilippe Charnier 		warn("%s", Filename);
332bdddbd2fSPaul Traina 		(void) umask(um);
33384f33deaSJordan K. Hubbard 		goto fatal;
33484f33deaSJordan K. Hubbard 	}
335bdddbd2fSPaul Traina 	(void) umask(um);
33684f33deaSJordan K. Hubbard #ifdef HAS_FCHOWN
33784f33deaSJordan K. Hubbard 	if (fchown(t, getuid(), getgid()) < 0) {
33884f33deaSJordan K. Hubbard #else
33984f33deaSJordan K. Hubbard 	if (chown(Filename, getuid(), getgid()) < 0) {
34084f33deaSJordan K. Hubbard #endif
341401e6468SPhilippe Charnier 		warn("fchown");
34284f33deaSJordan K. Hubbard 		goto fatal;
34384f33deaSJordan K. Hubbard 	}
34425e9ca2bSDavid Malone 	if (!(NewCrontab = fdopen(t, "r+"))) {
345401e6468SPhilippe Charnier 		warn("fdopen");
34684f33deaSJordan K. Hubbard 		goto fatal;
34784f33deaSJordan K. Hubbard 	}
34884f33deaSJordan K. Hubbard 
3499a2ef7d1SDiomidis Spinellis 	copy_file(f, NewCrontab);
35084f33deaSJordan K. Hubbard 	fclose(f);
35125e9ca2bSDavid Malone 	if (fflush(NewCrontab))
352401e6468SPhilippe Charnier 		err(ERROR_EXIT, "%s", Filename);
35325e9ca2bSDavid Malone 	if (fstat(t, &fsbuf) < 0) {
35425e9ca2bSDavid Malone 		warn("unable to fstat temp file");
35525e9ca2bSDavid Malone 		goto fatal;
35625e9ca2bSDavid Malone 	}
35784f33deaSJordan K. Hubbard  again:
358cc427081SXin LI 	if (swap_uids() < OK)
359cc427081SXin LI 		err(ERROR_EXIT, "swapping uids");
3608dac9202SAndrey A. Chernov 	if (stat(Filename, &statbuf) < 0) {
361401e6468SPhilippe Charnier 		warn("stat");
362*fe590ffeSEric van Gyzen  fatal:
363*fe590ffeSEric van Gyzen 		unlink(Filename);
36484f33deaSJordan K. Hubbard 		exit(ERROR_EXIT);
36584f33deaSJordan K. Hubbard 	}
366cc427081SXin LI 	if (swap_uids_back() < OK)
367cc427081SXin LI 		err(ERROR_EXIT, "swapping uids back");
36825e9ca2bSDavid Malone 	if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
36925e9ca2bSDavid Malone 		errx(ERROR_EXIT, "temp file must be edited in place");
3700cd2e3abSDiomidis Spinellis 	if (MD5File(Filename, orig_md5) == NULL) {
3710cd2e3abSDiomidis Spinellis 		warn("MD5");
3720cd2e3abSDiomidis Spinellis 		goto fatal;
3730cd2e3abSDiomidis Spinellis 	}
37484f33deaSJordan K. Hubbard 
375*fe590ffeSEric van Gyzen 	if ((editor = getenv("VISUAL")) == NULL &&
376*fe590ffeSEric van Gyzen 	    (editor = getenv("EDITOR")) == NULL) {
37784f33deaSJordan K. Hubbard 		editor = EDITOR;
37884f33deaSJordan K. Hubbard 	}
37984f33deaSJordan K. Hubbard 
38084f33deaSJordan K. Hubbard 	/* we still have the file open.  editors will generally rewrite the
38184f33deaSJordan K. Hubbard 	 * original file rather than renaming/unlinking it and starting a
38284f33deaSJordan K. Hubbard 	 * new one; even backup files are supposed to be made by copying
38384f33deaSJordan K. Hubbard 	 * rather than by renaming.  if some editor does not support this,
38484f33deaSJordan K. Hubbard 	 * then don't use it.  the security problems are more severe if we
38584f33deaSJordan K. Hubbard 	 * close and reopen the file around the edit.
38684f33deaSJordan K. Hubbard 	 */
38784f33deaSJordan K. Hubbard 
38884f33deaSJordan K. Hubbard 	switch (pid = fork()) {
38984f33deaSJordan K. Hubbard 	case -1:
390401e6468SPhilippe Charnier 		warn("fork");
39184f33deaSJordan K. Hubbard 		goto fatal;
39284f33deaSJordan K. Hubbard 	case 0:
39384f33deaSJordan K. Hubbard 		/* child */
394401e6468SPhilippe Charnier 		if (setuid(getuid()) < 0)
395401e6468SPhilippe Charnier 			err(ERROR_EXIT, "setuid(getuid())");
396401e6468SPhilippe Charnier 		if (chdir("/tmp") < 0)
397401e6468SPhilippe Charnier 			err(ERROR_EXIT, "chdir(/tmp)");
398401e6468SPhilippe Charnier 		if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
399401e6468SPhilippe Charnier 			errx(ERROR_EXIT, "editor or filename too long");
4007bc6d015SBrian Somers 		execlp(editor, editor, Filename, (char *)NULL);
401401e6468SPhilippe Charnier 		err(ERROR_EXIT, "%s", editor);
40284f33deaSJordan K. Hubbard 		/*NOTREACHED*/
40384f33deaSJordan K. Hubbard 	default:
40484f33deaSJordan K. Hubbard 		/* parent */
40584f33deaSJordan K. Hubbard 		break;
40684f33deaSJordan K. Hubbard 	}
40784f33deaSJordan K. Hubbard 
40884f33deaSJordan K. Hubbard 	/* parent */
409fd2c4394SMarc G. Fournier 	{
41066d48cdaSMatteo Riondato 	void (*sig[3])(int signal);
41166d48cdaSMatteo Riondato 	sig[0] = signal(SIGHUP, SIG_IGN);
41266d48cdaSMatteo Riondato 	sig[1] = signal(SIGINT, SIG_IGN);
41366d48cdaSMatteo Riondato 	sig[2] = signal(SIGTERM, SIG_IGN);
41484f33deaSJordan K. Hubbard 	xpid = wait(&waiter);
41566d48cdaSMatteo Riondato 	signal(SIGHUP, sig[0]);
41666d48cdaSMatteo Riondato 	signal(SIGINT, sig[1]);
41766d48cdaSMatteo Riondato 	signal(SIGTERM, sig[2]);
418fd2c4394SMarc G. Fournier 	}
41984f33deaSJordan K. Hubbard 	if (xpid != pid) {
420401e6468SPhilippe Charnier 		warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor);
42184f33deaSJordan K. Hubbard 		goto fatal;
42284f33deaSJordan K. Hubbard 	}
42384f33deaSJordan K. Hubbard 	if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
424401e6468SPhilippe Charnier 		warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter));
42584f33deaSJordan K. Hubbard 		goto fatal;
42684f33deaSJordan K. Hubbard 	}
42784f33deaSJordan K. Hubbard 	if (WIFSIGNALED(waiter)) {
428401e6468SPhilippe Charnier 		warnx("\"%s\" killed; signal %d (%score dumped)",
429401e6468SPhilippe Charnier 			editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no ");
43084f33deaSJordan K. Hubbard 		goto fatal;
43184f33deaSJordan K. Hubbard 	}
432cc427081SXin LI 	if (swap_uids() < OK)
433cc427081SXin LI 		err(ERROR_EXIT, "swapping uids");
4348dac9202SAndrey A. Chernov 	if (stat(Filename, &statbuf) < 0) {
435401e6468SPhilippe Charnier 		warn("stat");
43684f33deaSJordan K. Hubbard 		goto fatal;
43784f33deaSJordan K. Hubbard 	}
43825e9ca2bSDavid Malone 	if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
43925e9ca2bSDavid Malone 		errx(ERROR_EXIT, "temp file must be edited in place");
4400cd2e3abSDiomidis Spinellis 	if (MD5File(Filename, new_md5) == NULL) {
4410cd2e3abSDiomidis Spinellis 		warn("MD5");
4420cd2e3abSDiomidis Spinellis 		goto fatal;
4430cd2e3abSDiomidis Spinellis 	}
444cc427081SXin LI 	if (swap_uids_back() < OK)
445cc427081SXin LI 		err(ERROR_EXIT, "swapping uids back");
4460cd2e3abSDiomidis Spinellis 	if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) {
447401e6468SPhilippe Charnier 		warnx("no changes made to crontab");
44884f33deaSJordan K. Hubbard 		goto remove;
44984f33deaSJordan K. Hubbard 	}
450401e6468SPhilippe Charnier 	warnx("installing new crontab");
45184f33deaSJordan K. Hubbard 	switch (replace_cmd()) {
45208e019a8SDiomidis Spinellis 	case 0:			/* Success */
45384f33deaSJordan K. Hubbard 		break;
45408e019a8SDiomidis Spinellis 	case -1:		/* Syntax error */
45584f33deaSJordan K. Hubbard 		for (;;) {
45684f33deaSJordan K. Hubbard 			printf("Do you want to retry the same edit? ");
45784f33deaSJordan K. Hubbard 			fflush(stdout);
45884f33deaSJordan K. Hubbard 			q[0] = '\0';
45984f33deaSJordan K. Hubbard 			(void) fgets(q, sizeof q, stdin);
46084f33deaSJordan K. Hubbard 			switch (islower(q[0]) ? q[0] : tolower(q[0])) {
46184f33deaSJordan K. Hubbard 			case 'y':
46208e019a8SDiomidis Spinellis 				syntax_error = 1;
46384f33deaSJordan K. Hubbard 				goto again;
46484f33deaSJordan K. Hubbard 			case 'n':
46584f33deaSJordan K. Hubbard 				goto abandon;
46684f33deaSJordan K. Hubbard 			default:
46784f33deaSJordan K. Hubbard 				fprintf(stderr, "Enter Y or N\n");
46884f33deaSJordan K. Hubbard 			}
46984f33deaSJordan K. Hubbard 		}
47084f33deaSJordan K. Hubbard 		/*NOTREACHED*/
47108e019a8SDiomidis Spinellis 	case -2:		/* Install error */
47284f33deaSJordan K. Hubbard 	abandon:
473401e6468SPhilippe Charnier 		warnx("edits left in %s", Filename);
47484f33deaSJordan K. Hubbard 		goto done;
47584f33deaSJordan K. Hubbard 	default:
476401e6468SPhilippe Charnier 		warnx("panic: bad switch() in replace_cmd()");
47784f33deaSJordan K. Hubbard 		goto fatal;
47884f33deaSJordan K. Hubbard 	}
47984f33deaSJordan K. Hubbard  remove:
48084f33deaSJordan K. Hubbard 	unlink(Filename);
48184f33deaSJordan K. Hubbard  done:
48284f33deaSJordan K. Hubbard 	log_it(RealUser, Pid, "END EDIT", User);
48384f33deaSJordan K. Hubbard }
48484f33deaSJordan K. Hubbard 
48584f33deaSJordan K. Hubbard 
48684f33deaSJordan K. Hubbard /* returns	0	on success
48784f33deaSJordan K. Hubbard  *		-1	on syntax error
48884f33deaSJordan K. Hubbard  *		-2	on install error
48984f33deaSJordan K. Hubbard  */
49084f33deaSJordan K. Hubbard static int
491e93f27e3SJohn Baldwin replace_cmd(void)
492e93f27e3SJohn Baldwin {
49384f33deaSJordan K. Hubbard 	char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
49484f33deaSJordan K. Hubbard 	FILE *tmp;
49584f33deaSJordan K. Hubbard 	int ch, eof;
49684f33deaSJordan K. Hubbard 	entry *e;
49784f33deaSJordan K. Hubbard 	time_t now = time(NULL);
49884f33deaSJordan K. Hubbard 	char **envp = env_init();
49984f33deaSJordan K. Hubbard 
500bdddbd2fSPaul Traina 	if (envp == NULL) {
501401e6468SPhilippe Charnier 		warnx("cannot allocate memory");
502bdddbd2fSPaul Traina 		return (-2);
503bdddbd2fSPaul Traina 	}
504bdddbd2fSPaul Traina 
5052b9f079cSMatteo Riondato 	(void) snprintf(n, sizeof(n), "tmp.%d", Pid);
506*fe590ffeSEric van Gyzen 	if (snprintf(tn, sizeof(tn), CRON_TAB(n)) >= (int)sizeof(tn)) {
507*fe590ffeSEric van Gyzen 		warnx("path too long");
508*fe590ffeSEric van Gyzen 		return (-2);
509*fe590ffeSEric van Gyzen 	}
5108037791bSMatteo Riondato 
51184f33deaSJordan K. Hubbard 	if (!(tmp = fopen(tn, "w+"))) {
512401e6468SPhilippe Charnier 		warn("%s", tn);
51384f33deaSJordan K. Hubbard 		return (-2);
51484f33deaSJordan K. Hubbard 	}
51584f33deaSJordan K. Hubbard 
51684f33deaSJordan K. Hubbard 	/* write a signature at the top of the file.
51784f33deaSJordan K. Hubbard 	 *
51884f33deaSJordan K. Hubbard 	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
51984f33deaSJordan K. Hubbard 	 */
52084f33deaSJordan K. Hubbard 	fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
52184f33deaSJordan K. Hubbard 	fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
52284f33deaSJordan K. Hubbard 	fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
52384f33deaSJordan K. Hubbard 
52484f33deaSJordan K. Hubbard 	/* copy the crontab to the tmp
52584f33deaSJordan K. Hubbard 	 */
52625e9ca2bSDavid Malone 	rewind(NewCrontab);
52784f33deaSJordan K. Hubbard 	Set_LineNum(1)
52884f33deaSJordan K. Hubbard 	while (EOF != (ch = get_char(NewCrontab)))
52984f33deaSJordan K. Hubbard 		putc(ch, tmp);
530e78e0c43SPedro F. Giffuni 	ftruncate(fileno(tmp), ftello(tmp));
53184f33deaSJordan K. Hubbard 	fflush(tmp);  rewind(tmp);
53284f33deaSJordan K. Hubbard 
53384f33deaSJordan K. Hubbard 	if (ferror(tmp)) {
534401e6468SPhilippe Charnier 		warnx("error while writing new crontab to %s", tn);
53584f33deaSJordan K. Hubbard 		fclose(tmp);  unlink(tn);
53684f33deaSJordan K. Hubbard 		return (-2);
53784f33deaSJordan K. Hubbard 	}
53884f33deaSJordan K. Hubbard 
53984f33deaSJordan K. Hubbard 	/* check the syntax of the file being installed.
54084f33deaSJordan K. Hubbard 	 */
54184f33deaSJordan K. Hubbard 
54284f33deaSJordan K. Hubbard 	/* BUG: was reporting errors after the EOF if there were any errors
54384f33deaSJordan K. Hubbard 	 * in the file proper -- kludged it by stopping after first error.
54484f33deaSJordan K. Hubbard 	 *		vix 31mar87
54584f33deaSJordan K. Hubbard 	 */
54684f33deaSJordan K. Hubbard 	Set_LineNum(1 - NHEADER_LINES)
54784f33deaSJordan K. Hubbard 	CheckErrorCount = 0;  eof = FALSE;
54884f33deaSJordan K. Hubbard 	while (!CheckErrorCount && !eof) {
54984f33deaSJordan K. Hubbard 		switch (load_env(envstr, tmp)) {
55084f33deaSJordan K. Hubbard 		case ERR:
55184f33deaSJordan K. Hubbard 			eof = TRUE;
55284f33deaSJordan K. Hubbard 			break;
55384f33deaSJordan K. Hubbard 		case FALSE:
55484f33deaSJordan K. Hubbard 			e = load_entry(tmp, check_error, pw, envp);
55584f33deaSJordan K. Hubbard 			if (e)
5567044922bSPedro F. Giffuni 				free_entry(e);
55784f33deaSJordan K. Hubbard 			break;
55884f33deaSJordan K. Hubbard 		case TRUE:
55984f33deaSJordan K. Hubbard 			break;
56084f33deaSJordan K. Hubbard 		}
56184f33deaSJordan K. Hubbard 	}
56284f33deaSJordan K. Hubbard 
56384f33deaSJordan K. Hubbard 	if (CheckErrorCount != 0) {
564401e6468SPhilippe Charnier 		warnx("errors in crontab file, can't install");
56584f33deaSJordan K. Hubbard 		fclose(tmp);  unlink(tn);
56684f33deaSJordan K. Hubbard 		return (-1);
56784f33deaSJordan K. Hubbard 	}
56884f33deaSJordan K. Hubbard 
56984f33deaSJordan K. Hubbard #ifdef HAS_FCHOWN
57084f33deaSJordan K. Hubbard 	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
57184f33deaSJordan K. Hubbard #else
57284f33deaSJordan K. Hubbard 	if (chown(tn, ROOT_UID, -1) < OK)
57384f33deaSJordan K. Hubbard #endif
57484f33deaSJordan K. Hubbard 	{
575401e6468SPhilippe Charnier 		warn("chown");
57684f33deaSJordan K. Hubbard 		fclose(tmp);  unlink(tn);
57784f33deaSJordan K. Hubbard 		return (-2);
57884f33deaSJordan K. Hubbard 	}
57984f33deaSJordan K. Hubbard 
58084f33deaSJordan K. Hubbard #ifdef HAS_FCHMOD
58184f33deaSJordan K. Hubbard 	if (fchmod(fileno(tmp), 0600) < OK)
58284f33deaSJordan K. Hubbard #else
58384f33deaSJordan K. Hubbard 	if (chmod(tn, 0600) < OK)
58484f33deaSJordan K. Hubbard #endif
58584f33deaSJordan K. Hubbard 	{
586401e6468SPhilippe Charnier 		warn("chown");
58784f33deaSJordan K. Hubbard 		fclose(tmp);  unlink(tn);
58884f33deaSJordan K. Hubbard 		return (-2);
58984f33deaSJordan K. Hubbard 	}
59084f33deaSJordan K. Hubbard 
59184f33deaSJordan K. Hubbard 	if (fclose(tmp) == EOF) {
592401e6468SPhilippe Charnier 		warn("fclose");
59384f33deaSJordan K. Hubbard 		unlink(tn);
59484f33deaSJordan K. Hubbard 		return (-2);
59584f33deaSJordan K. Hubbard 	}
59684f33deaSJordan K. Hubbard 
597*fe590ffeSEric van Gyzen 	if (snprintf(n, sizeof(n), CRON_TAB(User)) >= (int)sizeof(n)) {
598*fe590ffeSEric van Gyzen 		warnx("path too long");
599*fe590ffeSEric van Gyzen 		unlink(tn);
600*fe590ffeSEric van Gyzen 		return (-2);
601*fe590ffeSEric van Gyzen 	}
602*fe590ffeSEric van Gyzen 
60384f33deaSJordan K. Hubbard 	if (rename(tn, n)) {
604401e6468SPhilippe Charnier 		warn("error renaming %s to %s", tn, n);
60584f33deaSJordan K. Hubbard 		unlink(tn);
60684f33deaSJordan K. Hubbard 		return (-2);
60784f33deaSJordan K. Hubbard 	}
6088037791bSMatteo Riondato 
60984f33deaSJordan K. Hubbard 	log_it(RealUser, Pid, "REPLACE", User);
61084f33deaSJordan K. Hubbard 
6115cdfc55cSJohn Baldwin 	/*
6125cdfc55cSJohn Baldwin 	 * Creating the 'tn' temp file has already updated the
6135cdfc55cSJohn Baldwin 	 * modification time of the spool directory.  Sleep for a
6145cdfc55cSJohn Baldwin 	 * second to ensure that poke_daemon() sets a later
6155cdfc55cSJohn Baldwin 	 * modification time.  Otherwise, this can race with the cron
6165cdfc55cSJohn Baldwin 	 * daemon scanning for updated crontabs.
6175cdfc55cSJohn Baldwin 	 */
6185cdfc55cSJohn Baldwin 	sleep(1);
6195cdfc55cSJohn Baldwin 
62084f33deaSJordan K. Hubbard 	poke_daemon();
62184f33deaSJordan K. Hubbard 
62284f33deaSJordan K. Hubbard 	return (0);
62384f33deaSJordan K. Hubbard }
62484f33deaSJordan K. Hubbard 
62584f33deaSJordan K. Hubbard static void
626e93f27e3SJohn Baldwin poke_daemon(void)
627e93f27e3SJohn Baldwin {
62884f33deaSJordan K. Hubbard 	if (utime(SPOOL_DIR, NULL) < OK) {
629401e6468SPhilippe Charnier 		warn("can't update mtime on spooldir %s", SPOOL_DIR);
63084f33deaSJordan K. Hubbard 		return;
63184f33deaSJordan K. Hubbard 	}
63284f33deaSJordan K. Hubbard }
633