crontab.c revision 29452
1148871Scperciva/* Copyright 1988,1990,1993,1994 by Paul Vixie
2148871Scperciva * All rights reserved
3148871Scperciva *
4148871Scperciva * Distribute freely, except: don't remove my name from the source or
5148871Scperciva * documentation (don't take credit for my work), mark your changes (don't
6148871Scperciva * get me blamed for your possible bugs), don't alter or remove this
7148871Scperciva * notice.  May be sold if buildable source is provided to buyer.  No
8148871Scperciva * warrantee of any kind, express or implied, is included with this
9148871Scperciva * software; use at your own risk, responsibility for damages (if any) to
10148871Scperciva * anyone resulting from the use of this software rests entirely with the
11148871Scperciva * user.
12148871Scperciva *
13148871Scperciva * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14148871Scperciva * I'll try to keep a version up to date.  I can be reached as follows:
15148871Scperciva * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16148871Scperciva * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp
17148871Scperciva */
18148871Scperciva
19148871Scperciva#if !defined(lint) && !defined(LINT)
20148871Scpercivastatic const char rcsid[] =
21148871Scperciva	"$Id: crontab.c,v 1.10 1997/03/31 05:09:58 imp Exp $";
22148871Scperciva#endif
23148871Scperciva
24148871Scperciva/* crontab - install and manage per-user crontab files
25148871Scperciva * vix 02may87 [RCS has the rest of the log]
26148871Scperciva * vix 26jan87 [original]
27148871Scperciva */
28148871Scperciva
29148871Scperciva#define	MAIN_PROGRAM
30148871Scperciva
31148871Scperciva#include "cron.h"
32148871Scperciva#include <errno.h>
33148871Scperciva#include <fcntl.h>
34148871Scperciva#include <sys/file.h>
35148871Scperciva#include <sys/stat.h>
36148871Scperciva#ifdef USE_UTIMES
37149027Scperciva# include <sys/time.h>
38148871Scperciva#else
39148871Scperciva# include <time.h>
40148871Scperciva# include <utime.h>
41148871Scperciva#endif
42148871Scperciva#if defined(POSIX)
43148871Scperciva# include <locale.h>
44148871Scperciva#endif
45148871Scperciva
46148871Scperciva
47148871Scperciva#define NHEADER_LINES 3
48148871Scperciva
49148871Scperciva
50148871Scpercivaenum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
51148871Scperciva
52148871Scperciva#if DEBUGGING
53148871Scpercivastatic char	*Options[] = { "???", "list", "delete", "edit", "replace" };
54148871Scperciva#endif
55148871Scperciva
56148871Scperciva
57148871Scpercivastatic	PID_T		Pid;
58148871Scpercivastatic	char		User[MAX_UNAME], RealUser[MAX_UNAME];
59148871Scpercivastatic	char		Filename[MAX_FNAME];
60148871Scpercivastatic	FILE		*NewCrontab;
61148871Scpercivastatic	int		CheckErrorCount;
62148871Scpercivastatic	enum opt_t	Option;
63148871Scpercivastatic	struct passwd	*pw;
64148871Scpercivastatic	void		list_cmd __P((void)),
65148871Scperciva			delete_cmd __P((void)),
66148871Scperciva			edit_cmd __P((void)),
67148871Scperciva			poke_daemon __P((void)),
68148871Scperciva			check_error __P((char *)),
69148871Scperciva			parse_args __P((int c, char *v[]));
70148871Scpercivastatic	int		replace_cmd __P((void));
71148871Scperciva
72148871Scperciva
73148871Scpercivastatic void
74148871Scpercivausage(msg)
75149027Scperciva	char *msg;
76148871Scperciva{
77148871Scperciva	fprintf(stderr, "crontab: usage error: %s\n", msg);
78148871Scperciva	fprintf(stderr, "%s\n%s\n",
79148879Scperciva		"usage: crontab [-u user] file",
80148871Scperciva		"       crontab [-u user] { -e | -l | -r }");
81148871Scperciva	exit(ERROR_EXIT);
82148871Scperciva}
83148871Scperciva
84149824Scperciva
85148871Scpercivaint
86148871Scpercivamain(argc, argv)
87148871Scperciva	int	argc;
88148871Scperciva	char	*argv[];
89148871Scperciva{
90148871Scperciva	int	exitstatus;
91148871Scperciva
92148871Scperciva	Pid = getpid();
93148871Scperciva	ProgramName = argv[0];
94148871Scperciva
95148871Scperciva#if defined(POSIX)
96148871Scperciva	setlocale(LC_ALL, "");
97148871Scperciva#endif
98148871Scperciva
99148871Scperciva#if defined(BSD)
100148871Scperciva	setlinebuf(stderr);
101148879Scperciva#endif
102148871Scperciva	parse_args(argc, argv);		/* sets many globals, opens a file */
103148871Scperciva	set_cron_uid();
104148871Scperciva	set_cron_cwd();
105148871Scperciva	if (!allowed(User)) {
106148871Scperciva		warnx("you (%s) are not allowed to use this program", User);
107148871Scperciva		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
108148871Scperciva		exit(ERROR_EXIT);
109148871Scperciva	}
110148871Scperciva	exitstatus = OK_EXIT;
111148871Scperciva	switch (Option) {
112148871Scperciva	case opt_list:		list_cmd();
113148871Scperciva				break;
114148871Scperciva	case opt_delete:	delete_cmd();
115148871Scperciva				break;
116148871Scperciva	case opt_edit:		edit_cmd();
117148871Scperciva				break;
118148871Scperciva	case opt_replace:	if (replace_cmd() < 0)
119148871Scperciva					exitstatus = ERROR_EXIT;
120148871Scperciva				break;
121148871Scperciva	case opt_unknown:
122148871Scperciva				break;
123148871Scperciva	}
124148871Scperciva	exit(0);
125148871Scperciva	/*NOTREACHED*/
126148871Scperciva}
127148871Scperciva
128148871Scperciva
129148871Scpercivastatic void
130148871Scpercivaparse_args(argc, argv)
131148871Scperciva	int	argc;
132148871Scperciva	char	*argv[];
133148871Scperciva{
134148871Scperciva	int		argch;
135148871Scperciva
136148871Scperciva	if (!(pw = getpwuid(getuid())))
137149027Scperciva		errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out");
138148871Scperciva	(void) strncpy(User, pw->pw_name, (sizeof User)-1);
139148871Scperciva	User[(sizeof User)-1] = '\0';
140148871Scperciva	strcpy(RealUser, User);
141149027Scperciva	Filename[0] = '\0';
142149027Scperciva	Option = opt_unknown;
143149027Scperciva	while ((argch = getopt(argc, argv, "u:lerx:")) != -1) {
144148871Scperciva		switch (argch) {
145148871Scperciva		case 'x':
146148871Scperciva			if (!set_debug_flags(optarg))
147148871Scperciva				usage("bad debug option");
148148871Scperciva			break;
149148871Scperciva		case 'u':
150149027Scperciva			if (getuid() != ROOT_UID)
151148871Scperciva				errx(ERROR_EXIT, "must be privileged to use -u");
152148871Scperciva			if (!(pw = getpwnam(optarg)))
153148871Scperciva				errx(ERROR_EXIT, "user `%s' unknown", optarg);
154148871Scperciva			(void) strncpy(User, pw->pw_name, (sizeof User)-1);
155148871Scperciva			User[(sizeof User)-1] = '\0';
156148871Scperciva			break;
157148871Scperciva		case 'l':
158148871Scperciva			if (Option != opt_unknown)
159148871Scperciva				usage("only one operation permitted");
160148871Scperciva			Option = opt_list;
161148871Scperciva			break;
162148871Scperciva		case 'r':
163148871Scperciva			if (Option != opt_unknown)
164148871Scperciva				usage("only one operation permitted");
165148871Scperciva			Option = opt_delete;
166148871Scperciva			break;
167148871Scperciva		case 'e':
168148871Scperciva			if (Option != opt_unknown)
169148871Scperciva				usage("only one operation permitted");
170148871Scperciva			Option = opt_edit;
171148871Scperciva			break;
172148871Scperciva		default:
173148871Scperciva			usage("unrecognized option");
174148871Scperciva		}
175148871Scperciva	}
176148871Scperciva
177149824Scperciva	endpwent();
178149824Scperciva
179148871Scperciva	if (Option != opt_unknown) {
180148871Scperciva		if (argv[optind] != NULL) {
181148871Scperciva			usage("no arguments permitted after this option");
182148871Scperciva		}
183148871Scperciva	} else {
184148871Scperciva		if (argv[optind] != NULL) {
185148871Scperciva			Option = opt_replace;
186148871Scperciva			(void) strncpy (Filename, argv[optind], (sizeof Filename)-1);
187148871Scperciva			Filename[(sizeof Filename)-1] = '\0';
188149824Scperciva
189149824Scperciva		} else {
190149824Scperciva			usage("file name must be specified for replace");
191149824Scperciva		}
192149824Scperciva	}
193149824Scperciva
194149824Scperciva	if (Option == opt_replace) {
195148871Scperciva		/* we have to open the file here because we're going to
196148871Scperciva		 * chdir(2) into /var/cron before we get around to
197148871Scperciva		 * reading the file.
198148871Scperciva		 */
199148871Scperciva		if (!strcmp(Filename, "-")) {
200148871Scperciva			NewCrontab = stdin;
201148871Scperciva		} else {
202148871Scperciva			/* relinquish the setuid status of the binary during
203148871Scperciva			 * the open, lest nonroot users read files they should
204148871Scperciva			 * not be able to read.  we can't use access() here
205148871Scperciva			 * since there's a race condition.  thanks go out to
206148871Scperciva			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
207148871Scperciva			 * the race.
208148871Scperciva			 */
209148871Scperciva
210148871Scperciva			if (swap_uids() < OK)
211148871Scperciva				err(ERROR_EXIT, "swapping uids");
212148871Scperciva			if (!(NewCrontab = fopen(Filename, "r")))
213148871Scperciva				err(ERROR_EXIT, "%s", Filename);
214148871Scperciva			if (swap_uids() < OK)
215148871Scperciva				err(ERROR_EXIT, "swapping uids back");
216148871Scperciva		}
217148871Scperciva	}
218148871Scperciva
219148871Scperciva	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
220148871Scperciva		      User, Filename, Options[(int)Option]))
221148871Scperciva}
222148871Scperciva
223148871Scperciva
224148871Scpercivastatic void
225148871Scpercivalist_cmd() {
226148871Scperciva	char	n[MAX_FNAME];
227148871Scperciva	FILE	*f;
228148871Scperciva	int	ch;
229148871Scperciva
230148871Scperciva	log_it(RealUser, Pid, "LIST", User);
231148871Scperciva	(void) sprintf(n, CRON_TAB(User));
232148871Scperciva	if (!(f = fopen(n, "r"))) {
233148871Scperciva		if (errno == ENOENT)
234148871Scperciva			errx(ERROR_EXIT, "no crontab for %s", User);
235148871Scperciva		else
236148871Scperciva			err(ERROR_EXIT, "%s", n);
237148871Scperciva	}
238148871Scperciva
239148871Scperciva	/* file is open. copy to stdout, close.
240148871Scperciva	 */
241148871Scperciva	Set_LineNum(1)
242148871Scperciva	while (EOF != (ch = get_char(f)))
243148871Scperciva		putchar(ch);
244148871Scperciva	fclose(f);
245148871Scperciva}
246148871Scperciva
247148871Scperciva
248148871Scpercivastatic void
249148871Scpercivadelete_cmd() {
250148871Scperciva	char	n[MAX_FNAME];
251148871Scperciva
252148871Scperciva	log_it(RealUser, Pid, "DELETE", User);
253148871Scperciva	(void) sprintf(n, CRON_TAB(User));
254148871Scperciva	if (unlink(n)) {
255148871Scperciva		if (errno == ENOENT)
256148871Scperciva			errx(ERROR_EXIT, "no crontab for %s", User);
257148871Scperciva		else
258154088Scperciva			err(ERROR_EXIT, "%s", n);
259154088Scperciva	}
260148871Scperciva	poke_daemon();
261148871Scperciva}
262154088Scperciva
263148871Scperciva
264148871Scpercivastatic void
265148871Scpercivacheck_error(msg)
266148871Scperciva	char	*msg;
267148871Scperciva{
268148871Scperciva	CheckErrorCount++;
269148871Scperciva	fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
270154088Scperciva}
271148871Scperciva
272148871Scperciva
273148871Scpercivastatic void
274148871Scpercivaedit_cmd() {
275148871Scperciva	char		n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
276148871Scperciva	FILE		*f;
277148871Scperciva	int		ch, t, x;
278148871Scperciva	struct stat	statbuf;
279148871Scperciva	time_t		mtime;
280148871Scperciva	WAIT_T		waiter;
281148871Scperciva	PID_T		pid, xpid;
282148871Scperciva	mode_t		um;
283148871Scperciva
284148871Scperciva	log_it(RealUser, Pid, "BEGIN EDIT", User);
285148871Scperciva	(void) sprintf(n, CRON_TAB(User));
286148871Scperciva	if (!(f = fopen(n, "r"))) {
287148871Scperciva		if (errno != ENOENT)
288148871Scperciva			err(ERROR_EXIT, "%s", n);
289148871Scperciva		warnx("no crontab for %s - using an empty one", User);
290148871Scperciva		if (!(f = fopen("/dev/null", "r")))
291148871Scperciva			err(ERROR_EXIT, "/dev/null");
292148871Scperciva	}
293148871Scperciva
294148871Scperciva	um = umask(077);
295148871Scperciva	(void) sprintf(Filename, "/tmp/crontab.XXXXXXXXXX");
296148871Scperciva	if ((t = mkstemp(Filename)) == -1) {
297148871Scperciva		warn("%s", Filename);
298148871Scperciva		(void) umask(um);
299148871Scperciva		goto fatal;
300148871Scperciva	}
301148871Scperciva	(void) umask(um);
302148871Scperciva#ifdef HAS_FCHOWN
303148871Scperciva	if (fchown(t, getuid(), getgid()) < 0) {
304148871Scperciva#else
305148871Scperciva	if (chown(Filename, getuid(), getgid()) < 0) {
306148871Scperciva#endif
307148871Scperciva		warn("fchown");
308148871Scperciva		goto fatal;
309148871Scperciva	}
310148871Scperciva	if (!(NewCrontab = fdopen(t, "w"))) {
311148871Scperciva		warn("fdopen");
312148871Scperciva		goto fatal;
313148871Scperciva	}
314148871Scperciva
315148871Scperciva	Set_LineNum(1)
316148871Scperciva
317148871Scperciva	/* ignore the top few comments since we probably put them there.
318148871Scperciva	 */
319148871Scperciva	for (x = 0;  x < NHEADER_LINES;  x++) {
320150159Scperciva		ch = get_char(f);
321156813Sru		if (EOF == ch)
322150159Scperciva			break;
323150159Scperciva		if ('#' != ch) {
324150159Scperciva			putc(ch, NewCrontab);
325150159Scperciva			break;
326148871Scperciva		}
327148871Scperciva		while (EOF != (ch = get_char(f)))
328148871Scperciva			if (ch == '\n')
329158245Scperciva				break;
330158245Scperciva		if (EOF == ch)
331158245Scperciva			break;
332158245Scperciva	}
333158245Scperciva
334158245Scperciva	/* copy the rest of the crontab (if any) to the temp file.
335158245Scperciva	 */
336148871Scperciva	if (EOF != ch)
337148871Scperciva		while (EOF != (ch = get_char(f)))
338148871Scperciva			putc(ch, NewCrontab);
339148871Scperciva	fclose(f);
340148871Scperciva	if (fclose(NewCrontab))
341148871Scperciva		err(ERROR_EXIT, "%s", Filename);
342148871Scperciva again:
343148871Scperciva	if (stat(Filename, &statbuf) < 0) {
344148871Scperciva		warn("stat");
345148871Scperciva fatal:		unlink(Filename);
346148871Scperciva		exit(ERROR_EXIT);
347148871Scperciva	}
348148871Scperciva	mtime = statbuf.st_mtime;
349148871Scperciva
350148871Scperciva	if ((!(editor = getenv("VISUAL")))
351148871Scperciva	 && (!(editor = getenv("EDITOR")))
352148871Scperciva	    ) {
353148871Scperciva		editor = EDITOR;
354148871Scperciva	}
355148871Scperciva
356148871Scperciva	/* we still have the file open.  editors will generally rewrite the
357148871Scperciva	 * original file rather than renaming/unlinking it and starting a
358148871Scperciva	 * new one; even backup files are supposed to be made by copying
359148871Scperciva	 * rather than by renaming.  if some editor does not support this,
360148871Scperciva	 * then don't use it.  the security problems are more severe if we
361148871Scperciva	 * close and reopen the file around the edit.
362148871Scperciva	 */
363148871Scperciva
364148871Scperciva	switch (pid = fork()) {
365148871Scperciva	case -1:
366148871Scperciva		warn("fork");
367148871Scperciva		goto fatal;
368148871Scperciva	case 0:
369148871Scperciva		/* child */
370148871Scperciva		if (setuid(getuid()) < 0)
371148871Scperciva			err(ERROR_EXIT, "setuid(getuid())");
372148871Scperciva		if (chdir("/tmp") < 0)
373148871Scperciva			err(ERROR_EXIT, "chdir(/tmp)");
374148871Scperciva		if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
375148871Scperciva			errx(ERROR_EXIT, "editor or filename too long");
376148871Scperciva		execlp(editor, editor, Filename, NULL);
377148871Scperciva		err(ERROR_EXIT, "%s", editor);
378148871Scperciva		/*NOTREACHED*/
379148871Scperciva	default:
380148871Scperciva		/* parent */
381148871Scperciva		break;
382148871Scperciva	}
383148871Scperciva
384148871Scperciva	/* parent */
385148871Scperciva	{
386148871Scperciva	void (*f[4])();
387148871Scperciva	f[0] = signal(SIGHUP, SIG_IGN);
388148871Scperciva	f[1] = signal(SIGINT, SIG_IGN);
389148871Scperciva	f[2] = signal(SIGTERM, SIG_IGN);
390148871Scperciva	xpid = wait(&waiter);
391148871Scperciva	signal(SIGHUP, f[0]);
392148871Scperciva	signal(SIGINT, f[1]);
393148871Scperciva	signal(SIGTERM, f[2]);
394148871Scperciva	}
395148871Scperciva	if (xpid != pid) {
396148871Scperciva		warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor);
397148871Scperciva		goto fatal;
398148871Scperciva	}
399148871Scperciva	if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
400148871Scperciva		warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter));
401148871Scperciva		goto fatal;
402148871Scperciva	}
403148871Scperciva	if (WIFSIGNALED(waiter)) {
404148871Scperciva		warnx("\"%s\" killed; signal %d (%score dumped)",
405148871Scperciva			editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no ");
406148871Scperciva		goto fatal;
407148871Scperciva	}
408148871Scperciva	if (stat(Filename, &statbuf) < 0) {
409148871Scperciva		warn("stat");
410148871Scperciva		goto fatal;
411148871Scperciva	}
412148871Scperciva	if (mtime == statbuf.st_mtime) {
413148871Scperciva		warnx("no changes made to crontab");
414148871Scperciva		goto remove;
415148871Scperciva	}
416148871Scperciva	warnx("installing new crontab");
417148871Scperciva	if (!(NewCrontab = fopen(Filename, "r"))) {
418148871Scperciva		warn("%s", Filename);
419148871Scperciva		goto fatal;
420148871Scperciva	}
421148871Scperciva	switch (replace_cmd()) {
422148871Scperciva	case 0:
423148871Scperciva		break;
424148871Scperciva	case -1:
425148871Scperciva		for (;;) {
426148871Scperciva			printf("Do you want to retry the same edit? ");
427148871Scperciva			fflush(stdout);
428148871Scperciva			q[0] = '\0';
429148871Scperciva			(void) fgets(q, sizeof q, stdin);
430148871Scperciva			switch (islower(q[0]) ? q[0] : tolower(q[0])) {
431148871Scperciva			case 'y':
432148871Scperciva				goto again;
433148871Scperciva			case 'n':
434148871Scperciva				goto abandon;
435148871Scperciva			default:
436148871Scperciva				fprintf(stderr, "Enter Y or N\n");
437148871Scperciva			}
438148871Scperciva		}
439148871Scperciva		/*NOTREACHED*/
440148871Scperciva	case -2:
441148871Scperciva	abandon:
442148871Scperciva		warnx("edits left in %s", Filename);
443149868Scperciva		goto done;
444148871Scperciva	default:
445148871Scperciva		warnx("panic: bad switch() in replace_cmd()");
446148871Scperciva		goto fatal;
447148871Scperciva	}
448148871Scperciva remove:
449148871Scperciva	unlink(Filename);
450148871Scperciva done:
451148871Scperciva	log_it(RealUser, Pid, "END EDIT", User);
452148871Scperciva}
453148871Scperciva
454148871Scperciva
455148871Scperciva/* returns	0	on success
456148871Scperciva *		-1	on syntax error
457148871Scperciva *		-2	on install error
458148871Scperciva */
459148871Scpercivastatic int
460148871Scpercivareplace_cmd() {
461148871Scperciva	char	n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
462148871Scperciva	FILE	*tmp;
463148871Scperciva	int	ch, eof;
464148879Scperciva	entry	*e;
465148871Scperciva	time_t	now = time(NULL);
466148871Scperciva	char	**envp = env_init();
467148871Scperciva
468148871Scperciva	if (envp == NULL) {
469148871Scperciva		warnx("cannot allocate memory");
470148871Scperciva		return (-2);
471148871Scperciva	}
472148871Scperciva
473148871Scperciva	(void) sprintf(n, "tmp.%d", Pid);
474148871Scperciva	(void) sprintf(tn, CRON_TAB(n));
475148871Scperciva	if (!(tmp = fopen(tn, "w+"))) {
476148871Scperciva		warn("%s", tn);
477148871Scperciva		return (-2);
478148871Scperciva	}
479148871Scperciva
480148871Scperciva	/* write a signature at the top of the file.
481148871Scperciva	 *
482148871Scperciva	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
483148871Scperciva	 */
484148871Scperciva	fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
485148871Scperciva	fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
486148871Scperciva	fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
487148871Scperciva
488148871Scperciva	/* copy the crontab to the tmp
489148871Scperciva	 */
490148871Scperciva	Set_LineNum(1)
491148871Scperciva	while (EOF != (ch = get_char(NewCrontab)))
492148871Scperciva		putc(ch, tmp);
493148871Scperciva	fclose(NewCrontab);
494148871Scperciva	ftruncate(fileno(tmp), ftell(tmp));
495148871Scperciva	fflush(tmp);  rewind(tmp);
496148871Scperciva
497148871Scperciva	if (ferror(tmp)) {
498148871Scperciva		warnx("error while writing new crontab to %s", tn);
499148871Scperciva		fclose(tmp);  unlink(tn);
500148871Scperciva		return (-2);
501148871Scperciva	}
502148871Scperciva
503148871Scperciva	/* check the syntax of the file being installed.
504148871Scperciva	 */
505148871Scperciva
506148871Scperciva	/* BUG: was reporting errors after the EOF if there were any errors
507148871Scperciva	 * in the file proper -- kludged it by stopping after first error.
508148871Scperciva	 *		vix 31mar87
509148871Scperciva	 */
510148871Scperciva	Set_LineNum(1 - NHEADER_LINES)
511148871Scperciva	CheckErrorCount = 0;  eof = FALSE;
512148871Scperciva	while (!CheckErrorCount && !eof) {
513148871Scperciva		switch (load_env(envstr, tmp)) {
514148871Scperciva		case ERR:
515148871Scperciva			eof = TRUE;
516148871Scperciva			break;
517148871Scperciva		case FALSE:
518148871Scperciva			e = load_entry(tmp, check_error, pw, envp);
519148871Scperciva			if (e)
520148871Scperciva				free(e);
521148871Scperciva			break;
522148871Scperciva		case TRUE:
523148871Scperciva			break;
524148871Scperciva		}
525148871Scperciva	}
526148871Scperciva
527148871Scperciva	if (CheckErrorCount != 0) {
528148871Scperciva		warnx("errors in crontab file, can't install");
529148871Scperciva		fclose(tmp);  unlink(tn);
530148871Scperciva		return (-1);
531148871Scperciva	}
532148871Scperciva
533148871Scperciva#ifdef HAS_FCHOWN
534148871Scperciva	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
535148871Scperciva#else
536148871Scperciva	if (chown(tn, ROOT_UID, -1) < OK)
537148871Scperciva#endif
538148871Scperciva	{
539148871Scperciva		warn("chown");
540148871Scperciva		fclose(tmp);  unlink(tn);
541148871Scperciva		return (-2);
542148871Scperciva	}
543148871Scperciva
544149023Scperciva#ifdef HAS_FCHMOD
545148871Scperciva	if (fchmod(fileno(tmp), 0600) < OK)
546148871Scperciva#else
547148871Scperciva	if (chmod(tn, 0600) < OK)
548148871Scperciva#endif
549148871Scperciva	{
550148871Scperciva		warn("chown");
551148871Scperciva		fclose(tmp);  unlink(tn);
552148871Scperciva		return (-2);
553148871Scperciva	}
554148871Scperciva
555148871Scperciva	if (fclose(tmp) == EOF) {
556148871Scperciva		warn("fclose");
557148871Scperciva		unlink(tn);
558148871Scperciva		return (-2);
559148871Scperciva	}
560148871Scperciva
561148871Scperciva	(void) sprintf(n, CRON_TAB(User));
562148871Scperciva	if (rename(tn, n)) {
563148871Scperciva		warn("error renaming %s to %s", tn, n);
564148871Scperciva		unlink(tn);
565148871Scperciva		return (-2);
566148871Scperciva	}
567148871Scperciva	log_it(RealUser, Pid, "REPLACE", User);
568148871Scperciva
569148871Scperciva	poke_daemon();
570148871Scperciva
571148871Scperciva	return (0);
572148871Scperciva}
573148871Scperciva
574148871Scperciva
575148871Scpercivastatic void
576148871Scpercivapoke_daemon() {
577148871Scperciva#ifdef USE_UTIMES
578148871Scperciva	struct timeval tvs[2];
579148871Scperciva	struct timezone tz;
580148871Scperciva
581148871Scperciva	(void) gettimeofday(&tvs[0], &tz);
582148871Scperciva	tvs[1] = tvs[0];
583148871Scperciva	if (utimes(SPOOL_DIR, tvs) < OK) {
584148871Scperciva		warn("can't update mtime on spooldir %s", SPOOL_DIR);
585154693Scperciva		return;
586148871Scperciva	}
587148871Scperciva#else
588148871Scperciva	if (utime(SPOOL_DIR, NULL) < OK) {
589148871Scperciva		warn("can't update mtime on spooldir %s", SPOOL_DIR);
590148871Scperciva		return;
591148871Scperciva	}
592148871Scperciva#endif /*USE_UTIMES*/
593148871Scperciva}
594148871Scperciva