crontab.c revision 184780
169408Sache/* Copyright 1988,1990,1993,1994 by Paul Vixie
259243Sobrien * All rights reserved
359243Sobrien *
459243Sobrien * Distribute freely, except: don't remove my name from the source or
559243Sobrien * documentation (don't take credit for my work), mark your changes (don't
659243Sobrien * get me blamed for your possible bugs), don't alter or remove this
759243Sobrien * notice.  May be sold if buildable source is provided to buyer.  No
859243Sobrien * warrantee of any kind, express or implied, is included with this
959243Sobrien * software; use at your own risk, responsibility for damages (if any) to
1059243Sobrien * anyone resulting from the use of this software rests entirely with the
1159243Sobrien * user.
1259243Sobrien *
1359243Sobrien * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
1459243Sobrien * I'll try to keep a version up to date.  I can be reached as follows:
1559243Sobrien * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
1659243Sobrien * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp
1759243Sobrien */
1859243Sobrien
1959243Sobrien#if !defined(lint) && !defined(LINT)
2059243Sobrienstatic const char rcsid[] =
2159243Sobrien  "$FreeBSD: head/usr.sbin/cron/crontab/crontab.c 184780 2008-11-09 07:34:11Z matteo $";
2259243Sobrien#endif
2359243Sobrien
2459243Sobrien/* crontab - install and manage per-user crontab files
2559243Sobrien * vix 02may87 [RCS has the rest of the log]
2659243Sobrien * vix 26jan87 [original]
2759243Sobrien */
2859243Sobrien
2959243Sobrien#define	MAIN_PROGRAM
3059243Sobrien
3159243Sobrien#include "cron.h"
3259243Sobrien#include <errno.h>
3359243Sobrien#include <fcntl.h>
3459243Sobrien#include <md5.h>
3559243Sobrien#include <paths.h>
3659243Sobrien#include <sys/file.h>
3759243Sobrien#include <sys/stat.h>
3859243Sobrien#ifdef USE_UTIMES
3959243Sobrien# include <sys/time.h>
4069408Sache#else
4159243Sobrien# include <time.h>
4269408Sache# include <utime.h>
4359243Sobrien#endif
4459243Sobrien#if defined(POSIX)
4559243Sobrien# include <locale.h>
4659243Sobrien#endif
4759243Sobrien
4859243Sobrien#define MD5_SIZE 33
4959243Sobrien#define NHEADER_LINES 3
5059243Sobrien
5159243Sobrien
5259243Sobrienenum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
5359243Sobrien
5469408Sache#if DEBUGGING
5569408Sachestatic char	*Options[] = { "???", "list", "delete", "edit", "replace" };
5669408Sache#endif
5769408Sache
5859243Sobrien
5959243Sobrienstatic	PID_T		Pid;
6059243Sobrienstatic	char		User[MAX_UNAME], RealUser[MAX_UNAME];
6159243Sobrienstatic	char		Filename[MAX_FNAME];
6259243Sobrienstatic	FILE		*NewCrontab;
6359243Sobrienstatic	int		CheckErrorCount;
6459243Sobrienstatic	enum opt_t	Option;
6559243Sobrienstatic	struct passwd	*pw;
6659243Sobrienstatic	char 		*tmp_path;
6759243Sobrienstatic	void		list_cmd(void),
6859243Sobrien			delete_cmd(void),
6959243Sobrien			edit_cmd(void),
7059243Sobrien			poke_daemon(void),
7159243Sobrien			check_error(char *),
7259243Sobrien			parse_args(int c, char *v[]);
7359243Sobrienstatic	int		replace_cmd(void);
7459243Sobrien
7559243Sobrien
7659243Sobrienstatic void
7759243Sobrienusage(msg)
7859243Sobrien	char *msg;
7959243Sobrien{
8059243Sobrien	fprintf(stderr, "crontab: usage error: %s\n", msg);
8159243Sobrien	fprintf(stderr, "%s\n%s\n",
8259243Sobrien		"usage: crontab [-u user] file",
8359243Sobrien		"       crontab [-u user] { -e | -l | -r }");
8459243Sobrien	exit(ERROR_EXIT);
8559243Sobrien}
8659243Sobrien
8759243Sobrien
8859243Sobrienint
8969408Sachemain(argc, argv)
9059243Sobrien	int	argc;
9169408Sache	char	*argv[];
9259243Sobrien{
9359243Sobrien	int	exitstatus;
9459243Sobrien
9559243Sobrien	Pid = getpid();
9659243Sobrien	ProgramName = argv[0];
9759243Sobrien
9859243Sobrien#if defined(POSIX)
9959243Sobrien	setlocale(LC_ALL, "");
10059243Sobrien#endif
10159243Sobrien
10259243Sobrien#if defined(BSD)
10359243Sobrien	setlinebuf(stderr);
10459243Sobrien#endif
10559243Sobrien	parse_args(argc, argv);		/* sets many globals, opens a file */
10659243Sobrien	set_cron_uid();
10759243Sobrien	set_cron_cwd();
10859243Sobrien	if (!allowed(User)) {
10959243Sobrien		warnx("you (%s) are not allowed to use this program", User);
11059243Sobrien		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
11159243Sobrien		exit(ERROR_EXIT);
11259243Sobrien	}
11359243Sobrien	exitstatus = OK_EXIT;
11459243Sobrien	switch (Option) {
11559243Sobrien	case opt_list:		list_cmd();
11659243Sobrien				break;
11759243Sobrien	case opt_delete:	delete_cmd();
11859243Sobrien				break;
11959243Sobrien	case opt_edit:		edit_cmd();
12059243Sobrien				break;
12159243Sobrien	case opt_replace:	if (replace_cmd() < 0)
12259243Sobrien					exitstatus = ERROR_EXIT;
12359243Sobrien				break;
12459243Sobrien	case opt_unknown:
12559243Sobrien				break;
12659243Sobrien	}
12759243Sobrien	exit(exitstatus);
12859243Sobrien	/*NOTREACHED*/
12959243Sobrien}
13059243Sobrien
13159243Sobrien
13259243Sobrienstatic void
13359243Sobrienparse_args(argc, argv)
13459243Sobrien	int	argc;
13559243Sobrien	char	*argv[];
13659243Sobrien{
13759243Sobrien	int		argch;
13859243Sobrien	char		resolved_path[PATH_MAX];
13959243Sobrien
14059243Sobrien	if (!(pw = getpwuid(getuid())))
14159243Sobrien		errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
14259243Sobrien	bzero(pw->pw_passwd, strlen(pw->pw_passwd));
14359243Sobrien	(void) strncpy(User, pw->pw_name, (sizeof User)-1);
14459243Sobrien	User[(sizeof User)-1] = '\0';
14559243Sobrien	strcpy(RealUser, User);
14659243Sobrien	Filename[0] = '\0';
14759243Sobrien	Option = opt_unknown;
14859243Sobrien	while ((argch = getopt(argc, argv, "u:lerx:")) != -1) {
14959243Sobrien		switch (argch) {
15059243Sobrien		case 'x':
15159243Sobrien			if (!set_debug_flags(optarg))
15259243Sobrien				usage("bad debug option");
15359243Sobrien			break;
15459243Sobrien		case 'u':
15559243Sobrien			if (getuid() != ROOT_UID)
15659243Sobrien				errx(ERROR_EXIT, "must be privileged to use -u");
15759243Sobrien			if (!(pw = getpwnam(optarg)))
15859243Sobrien				errx(ERROR_EXIT, "user `%s' unknown", optarg);
15959243Sobrien			bzero(pw->pw_passwd, strlen(pw->pw_passwd));
16059243Sobrien			(void) strncpy(User, pw->pw_name, (sizeof User)-1);
16159243Sobrien			User[(sizeof User)-1] = '\0';
16259243Sobrien			break;
16359243Sobrien		case 'l':
16459243Sobrien			if (Option != opt_unknown)
16559243Sobrien				usage("only one operation permitted");
16659243Sobrien			Option = opt_list;
16759243Sobrien			break;
16859243Sobrien		case 'r':
16959243Sobrien			if (Option != opt_unknown)
17059243Sobrien				usage("only one operation permitted");
17159243Sobrien			Option = opt_delete;
17259243Sobrien			break;
17359243Sobrien		case 'e':
17459243Sobrien			if (Option != opt_unknown)
17559243Sobrien				usage("only one operation permitted");
17659243Sobrien			Option = opt_edit;
17759243Sobrien			break;
17859243Sobrien		default:
17959243Sobrien			usage("unrecognized option");
18059243Sobrien		}
18159243Sobrien	}
18259243Sobrien
18359243Sobrien	endpwent();
18459243Sobrien
18559243Sobrien	if (Option != opt_unknown) {
18659243Sobrien		if (argv[optind] != NULL) {
18759243Sobrien			usage("no arguments permitted after this option");
18859243Sobrien		}
18959243Sobrien	} else {
19059243Sobrien		if (argv[optind] != NULL) {
19159243Sobrien			Option = opt_replace;
19259243Sobrien			(void) strncpy (Filename, argv[optind], (sizeof Filename)-1);
19359243Sobrien			Filename[(sizeof Filename)-1] = '\0';
19459243Sobrien
19559243Sobrien		} else {
19659243Sobrien			usage("file name must be specified for replace");
19759243Sobrien		}
19859243Sobrien	}
19959243Sobrien
20059243Sobrien	if (Option == opt_replace) {
20159243Sobrien		/* we have to open the file here because we're going to
20259243Sobrien		 * chdir(2) into /var/cron before we get around to
20359243Sobrien		 * reading the file.
20459243Sobrien		 */
20559243Sobrien		if (!strcmp(Filename, "-")) {
20659243Sobrien			NewCrontab = stdin;
20759243Sobrien		} else if (realpath(Filename, resolved_path) != NULL &&
20859243Sobrien		    !strcmp(resolved_path, SYSCRONTAB)) {
20959243Sobrien			err(ERROR_EXIT, SYSCRONTAB " must be edited manually");
21059243Sobrien		} else {
21159243Sobrien			/* relinquish the setuid status of the binary during
21259243Sobrien			 * the open, lest nonroot users read files they should
21359243Sobrien			 * not be able to read.  we can't use access() here
21459243Sobrien			 * since there's a race condition.  thanks go out to
21559243Sobrien			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
21659243Sobrien			 * the race.
21759243Sobrien			 */
21859243Sobrien
21959243Sobrien			if (swap_uids() < OK)
22059243Sobrien				err(ERROR_EXIT, "swapping uids");
22159243Sobrien			if (!(NewCrontab = fopen(Filename, "r")))
22259243Sobrien				err(ERROR_EXIT, "%s", Filename);
22359243Sobrien			if (swap_uids() < OK)
22459243Sobrien				err(ERROR_EXIT, "swapping uids back");
22559243Sobrien		}
22659243Sobrien	}
22759243Sobrien
22859243Sobrien	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
22959243Sobrien		      User, Filename, Options[(int)Option]))
23059243Sobrien}
23159243Sobrien
23259243Sobrienstatic void
23359243Sobriencopy_file(FILE *in, FILE *out) {
23459243Sobrien	int	x, ch;
23559243Sobrien
23659243Sobrien	Set_LineNum(1)
23759243Sobrien	/* ignore the top few comments since we probably put them there.
23859243Sobrien	 */
23959243Sobrien	for (x = 0;  x < NHEADER_LINES;  x++) {
24069408Sache		ch = get_char(in);
24159243Sobrien		if (EOF == ch)
24259243Sobrien			break;
24359243Sobrien		if ('#' != ch) {
24459243Sobrien			putc(ch, out);
24559243Sobrien			break;
24659243Sobrien		}
24759243Sobrien		while (EOF != (ch = get_char(in)))
24859243Sobrien			if (ch == '\n')
24959243Sobrien				break;
25069408Sache		if (EOF == ch)
25159243Sobrien			break;
25269408Sache	}
25359243Sobrien
25459243Sobrien	/* copy the rest of the crontab (if any) to the output file.
25559243Sobrien	 */
25659243Sobrien	if (EOF != ch)
25759243Sobrien		while (EOF != (ch = get_char(in)))
25859243Sobrien			putc(ch, out);
25959243Sobrien}
26059243Sobrien
26159243Sobrienstatic void
26259243Sobrienlist_cmd() {
26359243Sobrien	char	n[MAX_FNAME];
26459243Sobrien	FILE	*f;
26559243Sobrien
26659243Sobrien	log_it(RealUser, Pid, "LIST", User);
26759243Sobrien	(void) snprintf(n, sizeof(n), CRON_TAB(User));
26859243Sobrien	if (!(f = fopen(n, "r"))) {
26959243Sobrien		if (errno == ENOENT)
27059243Sobrien			errx(ERROR_EXIT, "no crontab for %s", User);
27159243Sobrien		else
27259243Sobrien			err(ERROR_EXIT, "%s", n);
27359243Sobrien	}
27459243Sobrien
27559243Sobrien	/* file is open. copy to stdout, close.
27659243Sobrien	 */
27759243Sobrien	copy_file(f, stdout);
27859243Sobrien	fclose(f);
27959243Sobrien}
28059243Sobrien
28159243Sobrien
28259243Sobrienstatic void
28359243Sobriendelete_cmd() {
28459243Sobrien	char	n[MAX_FNAME];
28559243Sobrien	int ch, first;
28659243Sobrien
28759243Sobrien	if (isatty(STDIN_FILENO)) {
28859243Sobrien		(void)fprintf(stderr, "remove crontab for %s? ", User);
28959243Sobrien		first = ch = getchar();
29059243Sobrien		while (ch != '\n' && ch != EOF)
29159243Sobrien			ch = getchar();
29259243Sobrien		if (first != 'y' && first != 'Y')
29359243Sobrien			return;
29459243Sobrien	}
29559243Sobrien
29659243Sobrien	log_it(RealUser, Pid, "DELETE", User);
29759243Sobrien	(void) snprintf(n, sizeof(n), CRON_TAB(User));
29859243Sobrien	if (unlink(n)) {
29959243Sobrien		if (errno == ENOENT)
30059243Sobrien			errx(ERROR_EXIT, "no crontab for %s", User);
30159243Sobrien		else
30259243Sobrien			err(ERROR_EXIT, "%s", n);
30359243Sobrien	}
30459243Sobrien	poke_daemon();
30559243Sobrien}
30659243Sobrien
30759243Sobrien
30859243Sobrienstatic void
30959243Sobriencheck_error(msg)
31059243Sobrien	char	*msg;
31159243Sobrien{
31259243Sobrien	CheckErrorCount++;
31359243Sobrien	fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
31459243Sobrien}
31559243Sobrien
31659243Sobrien
31759243Sobrienstatic void
31859243Sobrienedit_cmd() {
31959243Sobrien	char		n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
32059243Sobrien	FILE		*f;
32159243Sobrien	int		t;
32259243Sobrien	struct stat	statbuf, fsbuf;
32359243Sobrien	WAIT_T		waiter;
32459243Sobrien	PID_T		pid, xpid;
32559243Sobrien	mode_t		um;
32659243Sobrien	int		syntax_error = 0;
32759243Sobrien	char		orig_md5[MD5_SIZE];
32859243Sobrien	char		new_md5[MD5_SIZE];
32959243Sobrien
33059243Sobrien	log_it(RealUser, Pid, "BEGIN EDIT", User);
33159243Sobrien	(void) snprintf(n, sizeof(n), CRON_TAB(User));
33259243Sobrien	if (!(f = fopen(n, "r"))) {
33359243Sobrien		if (errno != ENOENT)
33459243Sobrien			err(ERROR_EXIT, "%s", n);
33559243Sobrien		warnx("no crontab for %s - using an empty one", User);
33659243Sobrien		if (!(f = fopen(_PATH_DEVNULL, "r")))
33759243Sobrien			err(ERROR_EXIT, _PATH_DEVNULL);
33859243Sobrien	}
33959243Sobrien
34059243Sobrien	um = umask(077);
34159243Sobrien	(void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX");
34259243Sobrien	if ((t = mkstemp(Filename)) == -1) {
34359243Sobrien		warn("%s", Filename);
34459243Sobrien		(void) umask(um);
34559243Sobrien		goto fatal;
34659243Sobrien	}
34759243Sobrien	(void) umask(um);
34859243Sobrien#ifdef HAS_FCHOWN
34959243Sobrien	if (fchown(t, getuid(), getgid()) < 0) {
35059243Sobrien#else
35159243Sobrien	if (chown(Filename, getuid(), getgid()) < 0) {
35259243Sobrien#endif
35359243Sobrien		warn("fchown");
35459243Sobrien		goto fatal;
35559243Sobrien	}
35659243Sobrien	if (!(NewCrontab = fdopen(t, "r+"))) {
35759243Sobrien		warn("fdopen");
35859243Sobrien		goto fatal;
35959243Sobrien	}
36059243Sobrien
36159243Sobrien	copy_file(f, NewCrontab);
36259243Sobrien	fclose(f);
36359243Sobrien	if (fflush(NewCrontab))
36459243Sobrien		err(ERROR_EXIT, "%s", Filename);
36559243Sobrien	if (fstat(t, &fsbuf) < 0) {
36659243Sobrien		warn("unable to fstat temp file");
36759243Sobrien		goto fatal;
36859243Sobrien	}
36959243Sobrien again:
37059243Sobrien	if (stat(Filename, &statbuf) < 0) {
37159243Sobrien		warn("stat");
37259243Sobrien fatal:		unlink(Filename);
37359243Sobrien		exit(ERROR_EXIT);
37459243Sobrien	}
37559243Sobrien	if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
37659243Sobrien		errx(ERROR_EXIT, "temp file must be edited in place");
37759243Sobrien	if (MD5File(Filename, orig_md5) == NULL) {
37859243Sobrien		warn("MD5");
37959243Sobrien		goto fatal;
38059243Sobrien	}
38159243Sobrien
38259243Sobrien	if ((!(editor = getenv("VISUAL")))
38359243Sobrien	 && (!(editor = getenv("EDITOR")))
38459243Sobrien	    ) {
38559243Sobrien		editor = EDITOR;
38659243Sobrien	}
38759243Sobrien
38859243Sobrien	/* we still have the file open.  editors will generally rewrite the
38959243Sobrien	 * original file rather than renaming/unlinking it and starting a
39059243Sobrien	 * new one; even backup files are supposed to be made by copying
39159243Sobrien	 * rather than by renaming.  if some editor does not support this,
39259243Sobrien	 * then don't use it.  the security problems are more severe if we
39359243Sobrien	 * close and reopen the file around the edit.
39459243Sobrien	 */
39559243Sobrien
39659243Sobrien	switch (pid = fork()) {
39759243Sobrien	case -1:
39859243Sobrien		warn("fork");
39959243Sobrien		goto fatal;
40059243Sobrien	case 0:
40159243Sobrien		/* child */
40259243Sobrien		if (setuid(getuid()) < 0)
40359243Sobrien			err(ERROR_EXIT, "setuid(getuid())");
40459243Sobrien		if (chdir("/tmp") < 0)
40559243Sobrien			err(ERROR_EXIT, "chdir(/tmp)");
40659243Sobrien		if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
40759243Sobrien			errx(ERROR_EXIT, "editor or filename too long");
40859243Sobrien		execlp(editor, editor, Filename, (char *)NULL);
40959243Sobrien		err(ERROR_EXIT, "%s", editor);
41059243Sobrien		/*NOTREACHED*/
41159243Sobrien	default:
41259243Sobrien		/* parent */
41359243Sobrien		break;
41459243Sobrien	}
41559243Sobrien
41659243Sobrien	/* parent */
41759243Sobrien	{
41859243Sobrien	void (*f[4])();
41959243Sobrien	f[0] = signal(SIGHUP, SIG_IGN);
42059243Sobrien	f[1] = signal(SIGINT, SIG_IGN);
42159243Sobrien	f[2] = signal(SIGTERM, SIG_IGN);
42259243Sobrien	xpid = wait(&waiter);
42359243Sobrien	signal(SIGHUP, f[0]);
42459243Sobrien	signal(SIGINT, f[1]);
42559243Sobrien	signal(SIGTERM, f[2]);
42659243Sobrien	}
42759243Sobrien	if (xpid != pid) {
42859243Sobrien		warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor);
42959243Sobrien		goto fatal;
43059243Sobrien	}
43159243Sobrien	if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
43259243Sobrien		warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter));
43359243Sobrien		goto fatal;
43459243Sobrien	}
43559243Sobrien	if (WIFSIGNALED(waiter)) {
43659243Sobrien		warnx("\"%s\" killed; signal %d (%score dumped)",
43759243Sobrien			editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no ");
43859243Sobrien		goto fatal;
43959243Sobrien	}
44059243Sobrien	if (stat(Filename, &statbuf) < 0) {
44159243Sobrien		warn("stat");
44259243Sobrien		goto fatal;
44359243Sobrien	}
44459243Sobrien	if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino)
44559243Sobrien		errx(ERROR_EXIT, "temp file must be edited in place");
44659243Sobrien	if (MD5File(Filename, new_md5) == NULL) {
44759243Sobrien		warn("MD5");
44859243Sobrien		goto fatal;
44959243Sobrien	}
45059243Sobrien	if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) {
45159243Sobrien		warnx("no changes made to crontab");
45259243Sobrien		goto remove;
45359243Sobrien	}
45459243Sobrien	warnx("installing new crontab");
45559243Sobrien	switch (replace_cmd()) {
45659243Sobrien	case 0:			/* Success */
45759243Sobrien		break;
45859243Sobrien	case -1:		/* Syntax error */
45959243Sobrien		for (;;) {
46059243Sobrien			printf("Do you want to retry the same edit? ");
46159243Sobrien			fflush(stdout);
46259243Sobrien			q[0] = '\0';
46359243Sobrien			(void) fgets(q, sizeof q, stdin);
46459243Sobrien			switch (islower(q[0]) ? q[0] : tolower(q[0])) {
46559243Sobrien			case 'y':
46659243Sobrien				syntax_error = 1;
46759243Sobrien				goto again;
46859243Sobrien			case 'n':
46959243Sobrien				goto abandon;
47059243Sobrien			default:
47159243Sobrien				fprintf(stderr, "Enter Y or N\n");
47259243Sobrien			}
47359243Sobrien		}
47459243Sobrien		/*NOTREACHED*/
47559243Sobrien	case -2:		/* Install error */
47659243Sobrien	abandon:
47759243Sobrien		warnx("edits left in %s", Filename);
47859243Sobrien		goto done;
47959243Sobrien	default:
48059243Sobrien		warnx("panic: bad switch() in replace_cmd()");
48159243Sobrien		goto fatal;
48259243Sobrien	}
48359243Sobrien remove:
48459243Sobrien	unlink(Filename);
48559243Sobrien done:
48659243Sobrien	log_it(RealUser, Pid, "END EDIT", User);
48759243Sobrien}
48859243Sobrien
48959243Sobrien
49059243Sobrienvoid
49159243Sobrienstatic remove_tmp(int sig)
49259243Sobrien{
49359243Sobrien	if (tmp_path) {
49459243Sobrien		unlink(tmp_path);
49559243Sobrien	}
49659243Sobrien	exit(ERROR_EXIT);
49759243Sobrien}
49859243Sobrien
49959243Sobrien
50059243Sobrien/* returns	0	on success
50159243Sobrien *		-1	on syntax error
50259243Sobrien *		-2	on install error
50359243Sobrien */
50459243Sobrienstatic int
50559243Sobrienreplace_cmd() {
50659243Sobrien	char	n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
50759243Sobrien	FILE	*tmp;
50859243Sobrien	int	ch, eof;
50969408Sache	entry	*e;
51059243Sobrien	time_t	now = time(NULL);
51159243Sobrien	char	**envp = env_init();
51259243Sobrien	void (*f[3])();
51359243Sobrien
51459243Sobrien	if (envp == NULL) {
51559243Sobrien		warnx("cannot allocate memory");
51659243Sobrien		return (-2);
51759243Sobrien	}
51859243Sobrien
51959243Sobrien	(void) snprintf(n, sizeof(n), "tmp.%d", Pid);
52059243Sobrien	(void) snprintf(tn, sizeof(n), CRON_TAB(n));
52159243Sobrien
52259243Sobrien	/* Set up to remove the temp file if interrupted by a signal. */
52359243Sobrien	f[0] = signal(SIGHUP, remove_tmp);
52459243Sobrien	f[1] = signal(SIGINT, remove_tmp);
52559243Sobrien	f[2] = signal(SIGTERM, remove_tmp);
52659243Sobrien	tmp_path = tn;
52759243Sobrien
52859243Sobrien	if (!(tmp = fopen(tn, "w+"))) {
52959243Sobrien		warn("%s", tn);
53059243Sobrien		return (-2);
53159243Sobrien	}
53259243Sobrien
53359243Sobrien	/* write a signature at the top of the file.
53469408Sache	 *
53569408Sache	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
53659243Sobrien	 */
53759243Sobrien	fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
53859243Sobrien	fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
53959243Sobrien	fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
54059243Sobrien
54159243Sobrien	/* copy the crontab to the tmp
54259243Sobrien	 */
54359243Sobrien	rewind(NewCrontab);
54459243Sobrien	Set_LineNum(1)
54559243Sobrien	while (EOF != (ch = get_char(NewCrontab)))
54659243Sobrien		putc(ch, tmp);
54759243Sobrien	ftruncate(fileno(tmp), ftell(tmp));
54859243Sobrien	fflush(tmp);  rewind(tmp);
54959243Sobrien
55059243Sobrien	if (ferror(tmp)) {
55159243Sobrien		warnx("error while writing new crontab to %s", tn);
55259243Sobrien		fclose(tmp);  unlink(tn);
55359243Sobrien		return (-2);
55459243Sobrien	}
55559243Sobrien
55659243Sobrien	/* check the syntax of the file being installed.
55759243Sobrien	 */
55859243Sobrien
55959243Sobrien	/* BUG: was reporting errors after the EOF if there were any errors
56059243Sobrien	 * in the file proper -- kludged it by stopping after first error.
56169408Sache	 *		vix 31mar87
56259243Sobrien	 */
56359243Sobrien	Set_LineNum(1 - NHEADER_LINES)
56459243Sobrien	CheckErrorCount = 0;  eof = FALSE;
56559243Sobrien	while (!CheckErrorCount && !eof) {
56659243Sobrien		switch (load_env(envstr, tmp)) {
56759243Sobrien		case ERR:
56859243Sobrien			eof = TRUE;
56959243Sobrien			break;
57059243Sobrien		case FALSE:
57159243Sobrien			e = load_entry(tmp, check_error, pw, envp);
57259243Sobrien			if (e)
57369408Sache				free(e);
57459243Sobrien			break;
57559243Sobrien		case TRUE:
57659243Sobrien			break;
57759243Sobrien		}
57859243Sobrien	}
57959243Sobrien
58059243Sobrien	if (CheckErrorCount != 0) {
58159243Sobrien		warnx("errors in crontab file, can't install");
58259243Sobrien		fclose(tmp);  unlink(tn);
58359243Sobrien		return (-1);
58459243Sobrien	}
58559243Sobrien
58659243Sobrien#ifdef HAS_FCHOWN
58759243Sobrien	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
58859243Sobrien#else
58959243Sobrien	if (chown(tn, ROOT_UID, -1) < OK)
59059243Sobrien#endif
59159243Sobrien	{
59259243Sobrien		warn("chown");
59359243Sobrien		fclose(tmp);  unlink(tn);
59459243Sobrien		return (-2);
59559243Sobrien	}
59659243Sobrien
59759243Sobrien#ifdef HAS_FCHMOD
59859243Sobrien	if (fchmod(fileno(tmp), 0600) < OK)
59959243Sobrien#else
60059243Sobrien	if (chmod(tn, 0600) < OK)
60159243Sobrien#endif
60259243Sobrien	{
60359243Sobrien		warn("chown");
60459243Sobrien		fclose(tmp);  unlink(tn);
60559243Sobrien		return (-2);
60659243Sobrien	}
60759243Sobrien
60859243Sobrien	if (fclose(tmp) == EOF) {
60959243Sobrien		warn("fclose");
61059243Sobrien		unlink(tn);
61159243Sobrien		return (-2);
61259243Sobrien	}
61359243Sobrien
61459243Sobrien	(void) snprintf(n, sizeof(n), CRON_TAB(User));
61559243Sobrien	if (rename(tn, n)) {
61659243Sobrien		warn("error renaming %s to %s", tn, n);
61759243Sobrien		unlink(tn);
61859243Sobrien		return (-2);
61959243Sobrien	}
62059243Sobrien
62159243Sobrien	/* Restore the default signal handlers. */
62259243Sobrien	tmp_path = NULL;
62359243Sobrien	signal(SIGHUP, f[0]);
62459243Sobrien	signal(SIGINT, f[1]);
62559243Sobrien	signal(SIGTERM, f[2]);
62659243Sobrien
62759243Sobrien	log_it(RealUser, Pid, "REPLACE", User);
62859243Sobrien
62959243Sobrien	poke_daemon();
63059243Sobrien
63159243Sobrien	return (0);
63259243Sobrien}
63359243Sobrien
63459243Sobrien
63559243Sobrienstatic void
63659243Sobrienpoke_daemon() {
63759243Sobrien#ifdef USE_UTIMES
63859243Sobrien	struct timeval tvs[2];
63959243Sobrien	struct timezone tz;
64059243Sobrien
64159243Sobrien	(void) gettimeofday(&tvs[0], &tz);
64259243Sobrien	tvs[1] = tvs[0];
64359243Sobrien	if (utimes(SPOOL_DIR, tvs) < OK) {
64459243Sobrien		warn("can't update mtime on spooldir %s", SPOOL_DIR);
64559243Sobrien		return;
64659243Sobrien	}
64759243Sobrien#else
64859243Sobrien	if (utime(SPOOL_DIR, NULL) < OK) {
64959243Sobrien		warn("can't update mtime on spooldir %s", SPOOL_DIR);
65059243Sobrien		return;
65159243Sobrien	}
65259243Sobrien#endif /*USE_UTIMES*/
65359243Sobrien}
65459243Sobrien