xref: /netbsd/external/bsd/cron/dist/crontab.c (revision 6550d01e)
1 /*	$NetBSD: crontab.c,v 1.3 2010/05/18 21:47:43 christos Exp $	*/
2 
3 /* Copyright 1988,1990,1993,1994 by Paul Vixie
4  * All rights reserved
5  */
6 
7 /*
8  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
9  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 #include <sys/cdefs.h>
24 #if !defined(lint) && !defined(LINT)
25 #if 0
26 static char rcsid[] = "Id: crontab.c,v 1.12 2004/01/23 18:56:42 vixie Exp";
27 #else
28 __RCSID("$NetBSD: crontab.c,v 1.3 2010/05/18 21:47:43 christos Exp $");
29 #endif
30 #endif
31 
32 /* crontab - install and manage per-user crontab files
33  * vix 02may87 [RCS has the rest of the log]
34  * vix 26jan87 [original]
35  */
36 
37 #define	MAIN_PROGRAM
38 
39 #include "cron.h"
40 
41 #define NHEADER_LINES 3
42 
43 enum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
44 
45 #if DEBUGGING
46 static const char	*Options[] = {
47     "???", "list", "delete", "edit", "replace" };
48 static const char	*getoptargs = "u:lerx:";
49 #else
50 static const char	*getoptargs = "u:ler";
51 #endif
52 
53 static	PID_T		Pid;
54 static	char		User[MAX_UNAME], RealUser[MAX_UNAME];
55 static	char		Filename[MAX_FNAME], TempFilename[MAX_FNAME];
56 static	FILE		*NewCrontab;
57 static	int		CheckErrorCount;
58 static	enum opt_t	Option;
59 static	struct passwd	*pw;
60 static	void		list_cmd(void),
61 			delete_cmd(void),
62 			edit_cmd(void),
63 			poke_daemon(void),
64 			check_error(const char *),
65 			parse_args(int c, char *v[]);
66 static	int		replace_cmd(void);
67 static  int		allowed(const char *, const char *, const char *);
68 static  int		in_file(const char *, FILE *, int);
69 static  int 		swap_uids(void);
70 
71 static void
72 usage(const char *msg) {
73 	(void)fprintf(stderr, "%s: usage error: %s\n", getprogname(), msg);
74 	(void)fprintf(stderr, "usage:\t%s [-u user] file\n", getprogname());
75 	(void)fprintf(stderr, "\t%s [-u user] [ -e | -l | -r ]\n", getprogname());
76 	(void)fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n");
77 	(void)fprintf(stderr, "\t-e\t(edit user's crontab)\n");
78 	(void)fprintf(stderr, "\t-l\t(list user's crontab)\n");
79 	(void)fprintf(stderr, "\t-r\t(delete user's crontab)\n");
80 	exit(ERROR_EXIT);
81 }
82 
83 int
84 main(int argc, char *argv[]) {
85 	int exitstatus;
86 
87 	setprogname(argv[0]);
88 	Pid = getpid();
89 	(void)setlocale(LC_ALL, "");
90 
91 	(void)setvbuf(stderr, NULL, _IOLBF, 0);
92 	parse_args(argc, argv);		/* sets many globals, opens a file */
93 	set_cron_cwd();
94 	if (!allowed(RealUser, CRON_ALLOW, CRON_DENY)) {
95 		(void)fprintf(stderr,
96 			"You `%s' are not allowed to use this program `%s'\n",
97 			User, getprogname());
98 		(void)fprintf(stderr, "See crontab(1) for more information\n");
99 		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
100 		exit(ERROR_EXIT);
101 	}
102 	exitstatus = OK_EXIT;
103 	switch (Option) {
104 	case opt_unknown:
105 		usage("unrecognized option");
106 		exitstatus = ERROR_EXIT;
107 		break;
108 	case opt_list:
109 		list_cmd();
110 		break;
111 	case opt_delete:
112 		delete_cmd();
113 		break;
114 	case opt_edit:
115 		edit_cmd();
116 		break;
117 	case opt_replace:
118 		if (replace_cmd() < 0)
119 			exitstatus = ERROR_EXIT;
120 		break;
121 	default:
122 		abort();
123 	}
124 	exit(exitstatus);
125 	/*NOTREACHED*/
126 }
127 
128 static void
129 parse_args(int argc, char *argv[]) {
130 	int argch;
131 
132 	if (!(pw = getpwuid(getuid()))) {
133 		errx(ERROR_EXIT,
134 		    "your UID isn't in the passwd file. bailingo out");
135 	}
136 	if (strlen(pw->pw_name) >= sizeof User) {
137 		errx(ERROR_EXIT, "username too long");
138 	}
139 	(void)strlcpy(User, pw->pw_name, sizeof(User));
140 	(void)strlcpy(RealUser, User, sizeof(RealUser));
141 	Filename[0] = '\0';
142 	Option = opt_unknown;
143 	while (-1 != (argch = getopt(argc, argv, getoptargs))) {
144 		switch (argch) {
145 #if DEBUGGING
146 		case 'x':
147 			if (!set_debug_flags(optarg))
148 				usage("bad debug option");
149 			break;
150 #endif
151 		case 'u':
152 			if (MY_UID(pw) != ROOT_UID) {
153 				errx(ERROR_EXIT,
154 				    "must be privileged to use -u");
155 			}
156 			if (!(pw = getpwnam(optarg))) {
157 				errx(ERROR_EXIT, "user `%s' unknown", optarg);
158 			}
159 			if (strlen(optarg) >= sizeof User)
160 				usage("username too long");
161 			(void) strlcpy(User, optarg, sizeof(User));
162 			break;
163 		case 'l':
164 			if (Option != opt_unknown)
165 				usage("only one operation permitted");
166 			Option = opt_list;
167 			break;
168 		case 'r':
169 			if (Option != opt_unknown)
170 				usage("only one operation permitted");
171 			Option = opt_delete;
172 			break;
173 		case 'e':
174 			if (Option != opt_unknown)
175 				usage("only one operation permitted");
176 			Option = opt_edit;
177 			break;
178 		default:
179 			usage("unrecognized option");
180 		}
181 	}
182 
183 	endpwent();
184 
185 	if (Option != opt_unknown) {
186 		if (argv[optind] != NULL)
187 			usage("no arguments permitted after this option");
188 	} else {
189 		if (argv[optind] != NULL) {
190 			Option = opt_replace;
191 			if (strlen(argv[optind]) >= sizeof Filename)
192 				usage("filename too long");
193 			(void)strlcpy(Filename, argv[optind], sizeof(Filename));
194 		} else
195 			usage("file name must be specified for replace");
196 	}
197 
198 	if (Option == opt_replace) {
199 		/* we have to open the file here because we're going to
200 		 * chdir(2) into /var/cron before we get around to
201 		 * reading the file.
202 		 */
203 		if (!strcmp(Filename, "-"))
204 			NewCrontab = stdin;
205 		else {
206 			/* relinquish the setuid status of the binary during
207 			 * the open, lest nonroot users read files they should
208 			 * not be able to read.  we can't use access() here
209 			 * since there's a race condition.  thanks go out to
210 			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
211 			 * the race.
212 			 */
213 
214 			if (swap_uids() < OK) {
215 				err(ERROR_EXIT, "swapping uids");
216 			}
217 			if (!(NewCrontab = fopen(Filename, "r"))) {
218 				err(ERROR_EXIT, "cannot open `%s'", Filename);
219 			}
220 			if (swap_uids() < OK) {
221 				err(ERROR_EXIT, "swapping uids back");
222 			}
223 		}
224 	}
225 
226 	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
227 		      User, Filename, Options[(int)Option]));
228 }
229 
230 static void
231 skip_header(int *pch, FILE *f)
232 {
233 	int ch;
234 	int x;
235 
236 	/* ignore the top few comments since we probably put them there.
237 	 */
238 	for (x = 0;  x < NHEADER_LINES;  x++) {
239 		ch = get_char(f);
240 		if (EOF == ch)
241 			break;
242 		if ('#' != ch)
243 			break;
244 		while (EOF != (ch = get_char(f)))
245 			if (ch == '\n')
246 				break;
247 		if (EOF == ch)
248 			break;
249 	}
250 	if (ch == '\n')
251 		ch = get_char(f);
252 
253 	*pch = ch;
254 }
255 
256 static void
257 list_cmd(void) {
258 	char n[MAX_FNAME];
259 	FILE *f;
260 	int ch;
261 
262 	log_it(RealUser, Pid, "LIST", User);
263 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
264 		errx(ERROR_EXIT, "path too long");
265 	}
266 	if (!(f = fopen(n, "r"))) {
267 		if (errno == ENOENT)
268 			errx(ERROR_EXIT, "no crontab for `%s'", User);
269 		else
270 			err(ERROR_EXIT, "Cannot open `%s'", n);
271 	}
272 
273 	/* file is open. copy to stdout, close.
274 	 */
275 	Set_LineNum(1);
276 	skip_header(&ch, f);
277 	for (; EOF != ch;  ch = get_char(f))
278 		(void)putchar(ch);
279 	(void)fclose(f);
280 }
281 
282 static void
283 delete_cmd(void) {
284 	char n[MAX_FNAME];
285 
286 	log_it(RealUser, Pid, "DELETE", User);
287 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
288 		errx(ERROR_EXIT, "path too long");
289 	}
290 	if (unlink(n) != 0) {
291 		if (errno == ENOENT)
292 			errx(ERROR_EXIT, "no crontab for `%s'", User);
293 		else
294 			err(ERROR_EXIT, "cannot unlink `%s'", n);
295 	}
296 	poke_daemon();
297 }
298 
299 static void
300 check_error(const char *msg) {
301 	CheckErrorCount++;
302 	(void)fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
303 }
304 
305 static void
306 edit_cmd(void) {
307 	char n[MAX_FNAME], q[MAX_TEMPSTR];
308 	const char *editor;
309 	FILE *f;
310 	int ch, t, x;
311 	sig_t oint, oabrt, oquit, ohup;
312 	struct stat statbuf;
313 	struct utimbuf utimebuf;
314 	long mtimensec;
315 	WAIT_T waiter;
316 	PID_T pid, xpid;
317 
318 	log_it(RealUser, Pid, "BEGIN EDIT", User);
319 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
320 		errx(ERROR_EXIT, "path too long");
321 	}
322 	if (!(f = fopen(n, "r"))) {
323 		if (errno != ENOENT) {
324 			err(ERROR_EXIT, "cannot open `%s'", n);
325 		}
326 		warnx("no crontab for `%s' - using an empty one", User);
327 		if (!(f = fopen(_PATH_DEVNULL, "r"))) {
328 			err(ERROR_EXIT, "cannot open `%s'", _PATH_DEVNULL);
329 		}
330 	}
331 
332 	if (fstat(fileno(f), &statbuf) < 0) {
333 		warn("cannot stat crontab file");
334 		goto fatal;
335 	}
336 	utimebuf.actime = statbuf.st_atime;
337 	utimebuf.modtime = statbuf.st_mtime;
338 	mtimensec = statbuf.st_mtimensec;
339 
340 	/* Turn off signals. */
341 	ohup = signal(SIGHUP, SIG_IGN);
342 	oint = signal(SIGINT, SIG_IGN);
343 	oquit = signal(SIGQUIT, SIG_IGN);
344 	oabrt = signal(SIGABRT, SIG_IGN);
345 
346 	if (!glue_strings(Filename, sizeof Filename, _PATH_TMP,
347 	    "crontab.XXXXXXXXXX", '/')) {
348 		warnx("path too long");
349 		goto fatal;
350 	}
351 	if (-1 == (t = mkstemp(Filename))) {
352 		warn("cannot create `%s'", Filename);
353 		goto fatal;
354 	}
355 #ifdef HAS_FCHOWN
356 	x = fchown(t, MY_UID(pw), MY_GID(pw));
357 #else
358 	x = chown(Filename, MY_UID(pw), MY_GID(pw));
359 #endif
360 	if (x < 0) {
361 		warn("cannot chown `%s'", Filename);
362 		goto fatal;
363 	}
364 	if (!(NewCrontab = fdopen(t, "r+"))) {
365 		warn("cannot open fd");
366 		goto fatal;
367 	}
368 
369 	Set_LineNum(1);
370 
371 	skip_header(&ch, f);
372 
373 	/* copy the rest of the crontab (if any) to the temp file.
374 	 */
375 	for (; EOF != ch; ch = get_char(f))
376 		(void)putc(ch, NewCrontab);
377 	(void)fclose(f);
378 	if (fflush(NewCrontab) < OK) {
379 		err(ERROR_EXIT, "cannot flush output for `%s'", Filename);
380 	}
381 	(void)utime(Filename, &utimebuf);
382  again:
383 	rewind(NewCrontab);
384 	if (ferror(NewCrontab)) {
385 		warn("error while writing new crontab to `%s'", Filename);
386  fatal:
387 		(void)unlink(Filename);
388 		exit(ERROR_EXIT);
389 	}
390 
391 	if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') &&
392 	    ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) {
393 		editor = EDITOR;
394 	}
395 
396 	/* we still have the file open.  editors will generally rewrite the
397 	 * original file rather than renaming/unlinking it and starting a
398 	 * new one; even backup files are supposed to be made by copying
399 	 * rather than by renaming.  if some editor does not support this,
400 	 * then don't use it.  the security problems are more severe if we
401 	 * close and reopen the file around the edit.
402 	 */
403 
404 	switch (pid = fork()) {
405 	case -1:
406 		warn("cannot fork");
407 		goto fatal;
408 	case 0:
409 		/* child */
410 		if (setgid(MY_GID(pw)) < 0) {
411 			err(ERROR_EXIT, "cannot setgid(getgid())");
412 		}
413 		if (setuid(MY_UID(pw)) < 0) {
414 			err(ERROR_EXIT, "cannot setuid(getuid())");
415 		}
416 		if (chdir(_PATH_TMP) < 0) {
417 			err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP);
418 		}
419 		if (!glue_strings(q, sizeof q, editor, Filename, ' ')) {
420 			errx(ERROR_EXIT, "editor command line too long");
421 		}
422 		(void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0);
423 		err(ERROR_EXIT, "cannot start `%s'", editor);
424 		/*NOTREACHED*/
425 	default:
426 		/* parent */
427 		break;
428 	}
429 
430 	/* parent */
431 	for (;;) {
432 		xpid = waitpid(pid, &waiter, WUNTRACED);
433 		if (xpid == -1) {
434 			if (errno != EINTR)
435 				warn("waitpid() failed waiting for PID %ld "
436 				    "from `%s'", (long)pid, editor);
437 		} else if (xpid != pid) {
438 			warnx("wrong PID (%ld != %ld) from `%s'",
439 			    (long)xpid, (long)pid, editor);
440 			goto fatal;
441 		} else if (WIFSTOPPED(waiter)) {
442 			(void)kill(getpid(), WSTOPSIG(waiter));
443 		} else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
444 			warnx("`%s' exited with status %d\n",
445 			    editor, WEXITSTATUS(waiter));
446 			goto fatal;
447 		} else if (WIFSIGNALED(waiter)) {
448 			warnx("`%s' killed; signal %d (%score dumped)",
449 			    editor, WTERMSIG(waiter),
450 			    WCOREDUMP(waiter) ? "" : "no ");
451 			goto fatal;
452 		} else
453 			break;
454 	}
455 	(void)signal(SIGHUP, ohup);
456 	(void)signal(SIGINT, oint);
457 	(void)signal(SIGQUIT, oquit);
458 	(void)signal(SIGABRT, oabrt);
459 
460 	if (fstat(t, &statbuf) < 0) {
461 		warn("cannot stat `%s'", Filename);
462 		goto fatal;
463 	}
464 	if (utimebuf.modtime == statbuf.st_mtime &&
465 	    mtimensec == statbuf.st_mtimensec) {
466 		warnx("no changes made to crontab");
467 		goto remove;
468 	}
469 	warnx("installing new crontab");
470 	switch (replace_cmd()) {
471 	case 0:
472 		break;
473 	case -1:
474 		for (;;) {
475 			(void)fpurge(stdin);
476 			(void)printf("Do you want to retry the same edit? ");
477 			(void)fflush(stdout);
478 			q[0] = '\0';
479 			(void) fgets(q, (int)sizeof(q), stdin);
480 			switch (q[0]) {
481 			case 'y':
482 			case 'Y':
483 				goto again;
484 			case 'n':
485 			case 'N':
486 				goto abandon;
487 			default:
488 				(void)printf("Enter Y or N\n");
489 			}
490 		}
491 		/*NOTREACHED*/
492 	case -2:
493 	abandon:
494 		warnx("edits left in `%s'", Filename);
495 		goto done;
496 	default:
497 		warnx("panic: bad switch() in replace_cmd()");
498 		goto fatal;
499 	}
500  remove:
501 	(void)unlink(Filename);
502  done:
503 	log_it(RealUser, Pid, "END EDIT", User);
504 }
505 
506 /* returns	0	on success
507  *		-1	on syntax error
508  *		-2	on install error
509  */
510 static int
511 replace_cmd(void) {
512 	char n[MAX_FNAME], n2[MAX_FNAME], envstr[MAX_ENVSTR], lastch;
513 	FILE *tmp, *fmaxtabsize;
514 	int ch, eof, fd;
515 	int error = 0;
516 	entry *e;
517 	sig_t oint, oabrt, oquit, ohup;
518 	uid_t file_owner;
519 	time_t now = time(NULL);
520 	char **envp = env_init();
521 	size_t	maxtabsize;
522 	struct	stat statbuf;
523 
524 	if (envp == NULL) {
525 		warn("Cannot allocate memory.");
526 		return (-2);
527 	}
528 
529 	if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR,
530 	    "tmp.XXXXXXXXXX", '/')) {
531 		TempFilename[0] = '\0';
532 		warnx("path too long");
533 		return (-2);
534 	}
535 	if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) {
536 		warn("cannot create `%s'", TempFilename);
537 		if (fd != -1) {
538 			(void)close(fd);
539 			(void)unlink(TempFilename);
540 		}
541 		TempFilename[0] = '\0';
542 		return (-2);
543 	}
544 
545 	ohup = signal(SIGHUP, SIG_IGN);
546 	oint = signal(SIGINT, SIG_IGN);
547 	oquit = signal(SIGQUIT, SIG_IGN);
548 	oabrt = signal(SIGABRT, SIG_IGN);
549 
550 	/* Make sure that the crontab is not an unreasonable size.
551 	 *
552 	 * XXX This is subject to a race condition--the user could
553 	 * add stuff to the file after we've checked the size but
554 	 * before we slurp it in and write it out. We can't just move
555 	 * the test to test the temp file we later create, because by
556 	 * that time we've already filled up the crontab disk. Probably
557 	 * the right thing to do is to do a bytecount in the copy loop
558 	 * rather than stating the file we're about to read.
559 	 */
560 	(void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE);
561 	if ((fmaxtabsize = fopen(n2, "r")) != NULL)  {
562 	    if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL)  {
563 		maxtabsize = 0;
564 	    } else {
565 		maxtabsize = atoi(n2);
566 	    }
567 	    (void)fclose(fmaxtabsize);
568 	} else {
569 	    maxtabsize = MAXTABSIZE_DEFAULT;
570 	}
571 
572 	if (fstat(fileno(NewCrontab), &statbuf))  {
573 	    warn("error stat'ing crontab input");
574 	    error = -2;
575 	    goto done;
576 	}
577 	if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize)  {
578 	    warnx("%ld bytes is larger than the maximum size of %ld bytes",
579 		(long) statbuf.st_size, (long) maxtabsize);
580 	    error = -2;
581 	    goto done;
582 	}
583 
584 	/* write a signature at the top of the file.
585 	 *
586 	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
587 	 */
588 	(void)fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
589 	(void)fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
590 	(void)fprintf(tmp, "# (Cron version %s -- %s)\n", CRON_VERSION, "$NetBSD: crontab.c,v 1.3 2010/05/18 21:47:43 christos Exp $");
591 
592 	/* copy the crontab to the tmp
593 	 */
594 	(void)rewind(NewCrontab);
595 	Set_LineNum(1);
596 	lastch = EOF;
597 	while (EOF != (ch = get_char(NewCrontab)))
598 		(void)putc(lastch = ch, tmp);
599 
600 	if (lastch != (char)EOF && lastch != '\n') {
601 		warnx("missing trailing newline in `%s'", Filename);
602 		error = -1;
603 		goto done;
604 	}
605 
606 	if (ferror(NewCrontab)) {
607 		warn("error while reading `%s'", Filename);
608 		error = -2;
609 		goto done;
610 	}
611 
612 	(void)ftruncate(fileno(tmp), ftell(tmp));
613 	/* XXX this should be a NOOP - is */
614 	(void)fflush(tmp);
615 
616 	if (ferror(tmp)) {
617 		(void)fclose(tmp);
618 		warn("error while writing new crontab to `%s'", TempFilename);
619 		error = -2;
620 		goto done;
621 	}
622 
623 	/* check the syntax of the file being installed.
624 	 */
625 
626 	/* BUG: was reporting errors after the EOF if there were any errors
627 	 * in the file proper -- kludged it by stopping after first error.
628 	 *		vix 31mar87
629 	 */
630 	Set_LineNum(1 - NHEADER_LINES);
631 	CheckErrorCount = 0;  eof = FALSE;
632 	while (!CheckErrorCount && !eof) {
633 		switch (load_env(envstr, tmp)) {
634 		case ERR:
635 			/* check for data before the EOF */
636 			if (envstr[0] != '\0') {
637 				Set_LineNum(LineNumber + 1);
638 				check_error("premature EOF");
639 			}
640 			eof = TRUE;
641 			break;
642 		case FALSE:
643 			e = load_entry(tmp, check_error, pw, envp);
644 			if (e)
645 				free(e);
646 			break;
647 		case TRUE:
648 			break;
649 		}
650 	}
651 
652 	if (CheckErrorCount != 0) {
653 		warnx("errors in crontab file, can't install.");
654 		(void)fclose(tmp);
655 		error = -1;
656 		goto done;
657 	}
658 
659 	file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid;
660 
661 #ifdef HAVE_FCHOWN
662 	error = fchown(fileno(tmp), file_owner, (uid_t)-1);
663 #else
664 	error = chown(TempFilename, file_owner, (gid_t)-1);
665 #endif
666 	if (error < OK) {
667 		warn("cannot chown `%s'", TempFilename);
668 		(void)fclose(tmp);
669 		error = -2;
670 		goto done;
671 	}
672 
673 	if (fclose(tmp) == EOF) {
674 		warn("error closing file");
675 		error = -2;
676 		goto done;
677 	}
678 
679 	if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
680 		warnx("path too long");
681 		error = -2;
682 		goto done;
683 	}
684 	if (rename(TempFilename, n)) {
685 		warn("error renaming `%s' to `%s'", TempFilename, n);
686 		error = -2;
687 		goto done;
688 	}
689 	TempFilename[0] = '\0';
690 	log_it(RealUser, Pid, "REPLACE", User);
691 
692 	poke_daemon();
693 
694 done:
695 	(void)signal(SIGHUP, ohup);
696 	(void)signal(SIGINT, oint);
697 	(void)signal(SIGQUIT, oquit);
698 	(void)signal(SIGABRT, oabrt);
699 	if (TempFilename[0]) {
700 		(void) unlink(TempFilename);
701 		TempFilename[0] = '\0';
702 	}
703 	return (error);
704 }
705 
706 static void
707 poke_daemon(void) {
708 #ifdef HAVE_UTIMES
709 	struct timeval tvs[2];
710 	struct timezone tz;
711 
712 	(void) gettimeofday(&tvs[0], &tz);
713 	tvs[1] = tvs[0];
714 	if (utimes(SPOOL_DIR, tvs) < OK)
715 #else
716 	if (utime(SPOOL_DIR, NULL) < OK)
717 #endif /* HAVE_UTIMES */
718 		warn("can't update mtime on spooldir %s", SPOOL_DIR);
719 }
720 /* int allowed(const char *username, const char *allow_file, const char *deny_file)
721  *	returns TRUE if (allow_file exists and user is listed)
722  *	or (deny_file exists and user is NOT listed).
723  *	root is always allowed.
724  */
725 static int
726 allowed(const char *username, const char *allow_file, const char *deny_file) {
727 	FILE	*fp;
728 	int	isallowed;
729 
730 	if (strcmp(username, ROOT_USER) == 0)
731 		return (TRUE);
732 #ifdef ALLOW_ONLY_ROOT
733 	isallowed = FALSE;
734 #else
735 	isallowed = TRUE;
736 #endif
737 	if ((fp = fopen(allow_file, "r")) != NULL) {
738 		isallowed = in_file(username, fp, FALSE);
739 		(void)fclose(fp);
740 	} else if ((fp = fopen(deny_file, "r")) != NULL) {
741 		isallowed = !in_file(username, fp, FALSE);
742 		(void)fclose(fp);
743 	}
744 	return (isallowed);
745 }
746 /* int in_file(const char *string, FILE *file, int error)
747  *	return TRUE if one of the lines in file matches string exactly,
748  *	FALSE if no lines match, and error on error.
749  */
750 static int
751 in_file(const char *string, FILE *file, int error)
752 {
753 	char line[MAX_TEMPSTR];
754 	char *endp;
755 
756 	if (fseek(file, 0L, SEEK_SET))
757 		return (error);
758 	while (fgets(line, MAX_TEMPSTR, file)) {
759 		if (line[0] != '\0') {
760 			endp = &line[strlen(line) - 1];
761 			if (*endp != '\n')
762 				return (error);
763 			*endp = '\0';
764 			if (0 == strcmp(line, string))
765 				return (TRUE);
766 		}
767 	}
768 	if (ferror(file))
769 		return (error);
770 	return (FALSE);
771 }
772 
773 #ifdef HAVE_SAVED_UIDS
774 
775 static int swap_uids(void) {
776 	return ((setegid(getgid()) || seteuid(getuid())) ? -1 : 0);
777 }
778 #if 0
779 static int swap_uids_back(void) {
780 	return ((setegid(getgid()) || seteuid(getuid())) ? -1 : 0);
781 }
782 #endif
783 
784 #else /*HAVE_SAVED_UIDS*/
785 
786 static int swap_uids(void) {
787 	return ((setregid(getegid(), getgid()) || setreuid(geteuid(), getuid()))
788 	    ? -1 : 0);
789 }
790 
791 #if 0
792 static int swap_uids_back(void) {
793 	return (swap_uids());
794 }
795 #endif
796 #endif /*HAVE_SAVED_UIDS*/
797