NetBSD Problem Report #17238

Received: (qmail 12791 invoked by uid 605); 12 Jun 2002 19:08:38 -0000
Message-Id: <20020612135648.DD20E46AB@stasis.kittenz.org>
Date: Wed, 12 Jun 2002 14:56:48 +0100 (BST)
From: xs@kittenz.org
Sender: gnats-bugs-owner@netbsd.org
Reply-To: xs@kittenz.org
To: gnats-bugs@gnats.netbsd.org
Subject: bumper patch for cron(8)
X-Send-Pr-Version: 3.95

>Number:         17238
>Category:       bin
>Synopsis:       bumper patch for cron(8)
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    bin-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Jun 12 19:09:02 +0000 2002
>Closed-Date:    
>Last-Modified:  
>Originator:     xs@kittenz.org
>Release:        NetBSD 1.6A
>Organization:
>Environment:
System: NetBSD stasis 1.6A NetBSD 1.6A (STASIS.systrace) #22: Wed Jun 12 10:07:07 BST 2002 xs@stasis:/usr/src/sys/arch/i386/compile/STASIS.systrace i386
Architecture: i386
Machine: i386
>Description:

Summary of patch:
Make crontab run setgid, greatly reducing the effect of a bug in 
crontab.

Make cron jobs honour resource limits, and also inherit an environment
derived from login_cap*.

Privileges when running a job are dropped earlier, reducing the effect of
a compromise in much of the code. This also means that sendmail is no longer
invoked as root by cron.

Fixes many bugs waiting to happen such as allocation failures that return
NULL being ignored, signal handler races, some bounds checking on popen(),
fd leakage, etc.

crontab should now clean up temp files upon being signalled in a fatal way.

Pushes various enforcement mechanisms up into cron. e.g. cron now also
enforces /var/cron/{allow,deny,maxtabsize}, this is to accommodate a 
gid cron compromise.

More notice is taken of string truncation.

A unix domain socket is used to notify cron of changes, allowing an
update to be read mid-sleep.

As a result of running crontab setgid, and to reduce the effect of
gid cron being compromised, it is necessary to perform more checks
on cron tabs before they are loaded. This needs an O_NOFOLLOW flag
to open(2). See PR#17053.

crontab no longer has a race for checking the crontab size. Actually it wasn't
a race, because when NewCrontab == stdin, st_size would always be 0.

Cosmetic: CRON_WITHIN now allows suffixes {y,d,h,m,s}. maxtabsize allows
suffixes {t,g,m,k}.

I rewrote the man page for cron. Didn't like the old one. I am tempted to
redo crontabs as well if it is deemed a good idea and I find more time.

Known problem:
scenerio:
	user subverts group cron
	user opens /var/cron/tabs/$user
	cron opens /var/cron/tabs/$user
	cron starts reading /var/cron/tabs/$user
	user starts appending to /var/cron/tabs/$user
	cron reads too much
	help!

This is no different to the race that previous existed in crontab, except
it is further up the chain of command, and harder to fix, and significantly
harder to exploit.
The solution is to cause i/o routines to fail when too much is read.

New tree:
/var/cron		mode=00710 uname=root gname=cron
/var/cron/tabs		mode=01730 uname=root gname=cron
/var/cron/tabs/.sock	mode=00660 uname=root gname=cron
/var/cron/tabs/${user}	mode=00600 uname=${user} gname=cron
/etc/crontab		mode=00600 uname=root
if not using syslog:
/var/cron/log		mode=00660 uname=root gname=cron

setgid(), signal handlers and the poking socket were inspired by OpenBSD.

I believe this patch addresses PR#15142, PR#16663 and PR#17211.
I apologise for patch size.

>How-To-Repeat:
>Fix:

Index: Makefile
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/Makefile,v
retrieving revision 1.9
diff -u -r1.9 Makefile
--- Makefile	1998/01/31 14:40:13	1.9
+++ Makefile	2002/06/12 13:22:38
@@ -6,4 +6,7 @@
 CPPFLAGS+=-I${.CURDIR}
 MAN=	cron.8

+DPADD+=	${LIBUTIL}
+LDADD+=	-lutil
+
 .include <bsd.prog.mk>
Index: compat.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/compat.c,v
retrieving revision 1.3
diff -u -r1.3 compat.c
--- compat.c	1998/01/31 14:40:15	1.3
+++ compat.c	2002/06/12 13:22:39
@@ -61,7 +61,8 @@
 	char	*temp;

 	temp = malloc(strlen(str) + 1);
-	(void) strcpy(temp, str);
+	if (temp != NULL)
+		(void) strcpy(temp, str);
 	return temp;
 }
 #endif
@@ -80,7 +81,7 @@
 		return sys_errlist[error];
 	}

-	sprintf(buf, "Unknown error: %d", error);
+	snprintf(buf, sizeof(buf), "Unknown error: %d", error);
 	return buf;
 }
 #endif
@@ -234,7 +235,7 @@
 		return -1;
 	}

-	sprintf("%s=%s", name, value);
+	sprintf(tmp, "%s=%s", name, value);
 	return putenv(tmp);	/* intentionally orphan 'tmp' storage */
 }
 #endif
Index: compat.h
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/compat.h,v
retrieving revision 1.2
diff -u -r1.2 compat.h
--- compat.h	1997/03/13 06:19:09	1.2
+++ compat.h	2002/06/12 13:22:39
@@ -122,10 +122,6 @@
 # define SYS_TIME_H 0
 #endif

-#if defined(BSD) && !defined(POSIX)
-# define USE_UTIMES
-#endif
-
 #if defined(AIX) || defined(HPUX) || defined(IRIX)
 # define NEED_SETENV
 #endif
Index: cron.8
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/cron.8,v
retrieving revision 1.4
diff -u -r1.4 cron.8
--- cron.8	2002/02/08 01:38:47	1.4
+++ cron.8	2002/06/12 13:22:39
@@ -1,4 +1,4 @@
-.\"	$NetBSD: cron.8,v 1.4 2002/02/08 01:38:47 ross Exp $
+.\"	$NetBSD$
 .\"
 .\"/* Copyright 1988,1990,1993 by Paul Vixie
 .\" * All rights reserved
@@ -17,47 +17,155 @@
 .\" * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
 .\" */
 .\"
-.\" Id: cron.8,v 2.2 1993/12/28 08:34:43 vixie Exp
+.\" rewritten 28may2002 by xs@kittenz.org
 .\"
-.TH CRON 8 "20 December 1993"
-.UC 4
-.SH NAME
-cron \- daemon to execute scheduled commands (Vixie Cron)
-.SH SYNOPSIS
-cron
-.SH DESCRIPTION
-.I Cron
-should be started from /etc/rc or /etc/rc.local.  It will return immediately,
-so you don't need to start it with '\*[Am]'.
-.PP
-.I Cron
-searches /var/cron/tabs for crontab files which are named after accounts in
-/etc/passwd; crontabs found are loaded into memory.
-.I Cron
-also searches for /etc/crontab which is in a different format (see
-.IR crontab(5)).
-.I Cron
-then wakes up every minute, examining all stored crontabs, checking each
-command to see if it should be run in the current minute.  When executing
-commands, any output is mailed to the owner of the crontab (or to the user
-named in the MAILTO environment variable in the crontab, if such exists).
-.PP
-Additionally,
-.I cron
-checks each minute to see if its spool directory's modtime (or the modtime
-on
-.IR /etc/crontab)
-has changed, and if it has,
-.I cron
-will then examine the modtime on all crontabs and reload those which have
-changed.  Thus
-.I cron
-need not be restarted whenever a crontab file is modified.  Note that the
-.IR crontab (1)
-command updates the modtime of the spool directory whenever it changes a
-crontab.
-.SH "SEE ALSO"
-crontab(1), crontab(5)
-.SH AUTHOR
-.nf
+.Dd May 28, 2002
+.Dt CRON 8
+.Os
+.Sh NAME
+.Nm cron
+.Nd daemon to executed scheduled commands (Vixie Cron)
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+runs user specified commands at regular, predefined intervals.
+To do this,
+.Nm
+searches
+.Pa /var/cron/tabs
+for crontab files that are named after accounts in the password database;
+files that are found, and meet certain other criteria, are loaded into memory.
+.Nm
+also will load command entries from
+.Pa /etc/crontab
+which is in a different format (see
+.Xr crontab 5 . )
+.Pp
+Every minute,
+.Nm
+wakes up. It performs a number of tasks:
+.Bl -bullet
+.It
+It examines all loaded crontabs, checking each command to see if it should
+be run in the current minute. When executing commands, any output is mailed
+to the owner of the crontab (or to the user named in the
+.Ev MAILTO
+environment variable in the crontab, if such exists.)
+.It
+It checks to see if the modification time of its spool directory
+.Pa ( /var/cron/tabs/ )
+or that of
+.Pa /etc/crontab
+has changed since it last read them. If either has,
+.Nm
+will then examine the modification times on all crontabs and reload
+those that have changed. Thus
+.Nm
+need not be restarted whenever a crontab file is modified. Note that the
+.Xr crontab 1
+command updates the modification time of the spool directory whenever it
+changes a crontab.
+.El
+.Pp
+.Nm
+also can receive
+.Dq hints
+that a crontab has been changed, via a
+.Xr unix 4
+domain socket. These hints cause
+.Nm
+to reload crontabs, and can be received whilst
+.Nm
+is sleeping.
+.Pp
+.Nm
+will refuse to load a crontab if it fails to meet any one of the following
+conditions.
+.Pp
+The crontab must be:
+.Bl -enum -compact
+.It
+a regular file;
+.It
+have a link count of one;
+.It
+at most mode 0600 (e.g., 0400 is ok);
+.It
+less than the size specified by
+.Pa /var/cron/maxtabsize
+(unless the crontab is
+.Pa /etc/crontab ) ;
+.It
+owned by the username under which it will executed (except in the case of
+.Pa /etc/crontab ,
+where it must be owned by root.)
+.El
+.Pp
+If
+.Pa /var/cron/allow
+exists, then the user must be listed in it. If
+.Pa /var/cron/allow
+does not exist, but
+.Pa /var/cron/deny
+does exist, then the user must
+.Em not
+be listed in it. If neither file exists, then, depending on a compile-time
+parameter, either only the super-user's crontab will be allowed, or
+everyone's will.
+.Sh FILES
+.Bl -tag -width "/var/cron/tabs/.tmp.XXXXXXX"
+.It Pa /etc/crontab
+System crontab.
+.It Pa /var/cron/tabs/
+Spool directory for
+.Nm
+.It Pa /var/cron/tabs/.tmp.XXXXXXX
+Temporary file created by
+.Xr crontab 1
+whenever it replaces or edits a crontab.
+.It Pa /var/cron/tabs/.sock
+A
+.Xr unix 4
+domain socket that allows
+.Xr crontab 1
+to inform
+.Nm
+of a crontab change.
+.It Pa /var/cron/allow
+An inclusive list of users. Takes priority over
+.Pa /var/cron/deny
+.It Pa /var/cron/deny
+An exclusive list of users.
+.It Pa /var/cron/maxtabsize
+A single-lined file containing a decimal number that is the maximum
+size (in bytes) of a crontab. If the file does not exist, or cannot be opened
+or read, the default limit of 256KB is used.
+.Pp
+The number may be suffixed by either
+.Dq t ,
+.Dq g ,
+.Dq m
+or
+.Dq k
+meaning the preceding number is in either terabytes, gigabytes, megabytes
+or kilobytes, respectively.
+.El
+.Sh SEE ALSO
+.\" Cross-references should be ordered by section (low to high), then in
+.\"     alphabetical order.
+.Xr at 1 ,
+.Xr crontab 1 ,
+.Xr crontab 5
+.Sh STANDARDS
+.Sh HISTORY
+.Sh AUTHORS
 Paul Vixie \*[Lt]paul@vix.com\*[Gt]
+.Sh BUGS
+.Xr unix 4
+domain sockets support passing credentials via the socket. This could
+be used to provide a more granular method of reloading crontabs.
+.Pp
+dotnames
+.Pp
+More precautions should be taken to avoid infinite loops when parsing.
Index: cron.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/cron.c,v
retrieving revision 1.10
diff -u -r1.10 cron.c
--- cron.c	2002/04/25 14:45:05	1.10
+++ cron.c	2002/06/12 13:22:39
@@ -31,6 +31,8 @@


 #include "cron.h"
+#include <sys/socket.h>
+#include <sys/un.h>
 #include <sys/signal.h>
 #if SYS_TIME_H
 # include <sys/time.h>
@@ -43,16 +45,24 @@
 		run_reboot_jobs __P((cron_db *)),
 		cron_tick __P((cron_db *)),
 		cron_sync __P((void)),
-		cron_sleep __P((void)),
+		cron_sleep __P((cron_db *, int)),
+		check_sigs __P((void)),
 #ifdef USE_SIGCHLD
 		sigchld_handler __P((int)),
+		sigchld_reaper __P((void)),
 #endif
 		sighup_handler __P((int)),
 		parse_args __P((int c, char *v[]));

-
 int main __P((int, char *[]));

+
+static	volatile sig_atomic_t		got_sighup;
+#ifdef USE_SIGCHLD
+static	volatile sig_atomic_t		got_sigchld;
+#endif
+
+
 static void
 usage() {
 	fprintf(stderr, "usage:  %s [-x debugflag[,...]]\n", ProgramName);
@@ -66,7 +76,9 @@
 	char	*argv[];
 {
 	cron_db	database;
+	int	cron_sock;

+	setprogname(argv[0]);
 	ProgramName = argv[0];

 #if defined(BSD)
@@ -76,7 +88,10 @@

 	parse_args(argc, argv);

+	got_sighup = 0;
 #ifdef USE_SIGCHLD
+	got_sigchld = 0;
+
 	(void) signal(SIGCHLD, sigchld_handler);
 #else
 	(void) signal(SIGCLD, SIG_IGN);
@@ -86,6 +101,8 @@
 	acquire_daemonlock(0);
 	set_cron_uid();
 	set_cron_cwd();
+	if ((cron_sock = open_socket(1)) == -1)
+		err(1, "unable to open poking socket");

 #if defined(POSIX)
 	setenv("PATH", _PATH_DEFPATH, 1);
@@ -114,11 +131,17 @@
 	run_reboot_jobs(&database);
 	cron_sync();
 	while (TRUE) {
+		check_sigs();
+
 # if DEBUGGING
 		if (!(DebugFlags & DTEST))
 # endif /*DEBUGGING*/
-			cron_sleep();
+			cron_sleep(&database, cron_sock);

+		/* XXX only needs to occur when: socket doesn't work
+		 * or /etc/crontab is used. perhaps also to keep 
+		 * the buffer cache full of the stuff we want (laptops)
+		 */
 		load_database(&database);

 		/* do this iteration
@@ -236,11 +259,19 @@


 static void
-cron_sleep() {
-	int	seconds_to_wait;
+cron_sleep(db, cron_sock)
+	cron_db *db;
+	int cron_sock;
+{
+	struct	sockaddr_un un;
+	struct	timeval tv;
+	fd_set	rfds;
+	int	seconds_to_wait, c;
+	int	nfds, fd;
+	socklen_t unlen;

 	do {
-		seconds_to_wait = (int) (TargetTime - time((time_t*)0));
+		seconds_to_wait = (int) (TargetTime - time(NULL));
 		Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
 			getpid(), (long)TargetTime, seconds_to_wait))

@@ -255,19 +286,62 @@
 		 */
 	} while (seconds_to_wait > 0 && job_runqueue());

-	while ((seconds_to_wait = (int) (TargetTime - time((time_t *)0))) > 0) {
+	FD_ZERO(&rfds);
+	unlen = sizeof(un);
+	while ((seconds_to_wait = (int) (TargetTime - time(NULL))) > 0) {
 		Debug(DSCH, ("[%d] sleeping for %d seconds\n",
 			getpid(), seconds_to_wait))
-		sleep((unsigned int) seconds_to_wait);
+
+		tv.tv_usec = 0;
+		tv.tv_sec = seconds_to_wait;
+		FD_SET(cron_sock, &rfds);
+
+		/* sleep and wait for poke or signal */
+		nfds = select(cron_sock + 1, &rfds, NULL, NULL, &tv);
+		if (nfds == 0) /* timeout */
+			break;
+		else if (nfds > 0) {
+			fd = accept(cron_sock, (struct sockaddr *)&un, &unlen);
+			if (fd > 0) {
+				/* XXX use sockcred to make sure the user
+				 * is allowed to reload?
+				 * could also be used for hints to load_db
+				 */
+				while (read(fd, &c, sizeof(c)) > 0)
+					; /* suck up anything in the socket */
+				close(fd);
+				Debug(DMISC, ("[%d] got rehash from crontab\n",
+							getpid()))
+				load_database(db);
+			}
+		}
+		check_sigs();
 	}
 }


+static void
+sighup_handler(x)
+	int x;
+{
+
+	got_sighup = 1;
+}
+
+
 #ifdef USE_SIGCHLD
 static void
 sigchld_handler(x)
 	int x;
 {
+
+	got_sigchld = 1;
+}
+
+
+static void
+sigchld_reaper()
+{
 	WAIT_T		waiter;
 	PID_T		pid;

@@ -297,10 +371,19 @@


 static void
-sighup_handler(x)
-	int x;
+check_sigs()
 {
-	log_close();
+
+	if (got_sighup) {
+		got_sighup = 0;
+		log_close();
+	}
+#ifdef USE_SIGCHLD
+	if (got_sigchld) {
+		got_sigchld = 0;
+		sigchld_reaper();
+	}
+#endif
 }


@@ -315,6 +398,7 @@
 		switch (argch) {
 		default:
 			usage();
+			/* NOTREACHED */
 		case 'x':
 			if (!set_debug_flags(optarg))
 				usage();
Index: cron.h
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/cron.h,v
retrieving revision 1.3
diff -u -r1.3 cron.h
--- cron.h	1998/01/31 14:40:21	1.3
+++ cron.h	2002/06/12 13:22:41
@@ -30,6 +30,8 @@

 #include <sys/types.h>
 #include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
 #include "compat.h"

 #include <stdio.h>
@@ -37,6 +39,7 @@
 #include <bitstring.h>
 #include <pwd.h>
 #include <sys/wait.h>
+#include <err.h>

 #include "pathnames.h"
 #include "config.h"
@@ -73,6 +76,8 @@
 #define	MAX_UNAME	20	/* max length of username, should be overkill */
 #define	ROOT_UID	0	/* don't change this, it really must be root */
 #define	ROOT_USER	"root"	/* ditto */
+#define	CRON_GROUP	"cron"	/* name of group to run as */
+#define	LOGIN_CAP		/* use setusercontext() & co */

 				/* NOTE: these correspond to DebugFlagNames,
 				 *	defined below.
@@ -86,7 +91,6 @@
 #define	DTEST		0x0040	/* test mode: don't execute any commands */
 #define	DBIT		0x0080	/* bit twiddling shown (long) */

-#define	CRON_TAB(u)	"%s/%s", SPOOL_DIR, u
 #define	REG		register
 #define	PPC_NULL	((char **)NULL)

@@ -183,6 +187,16 @@
 	time_t		mtime;		/* last modtime on spooldir */
 } cron_db;

+			/* a look up table of the last modification
+			 * times and sizes of files that were read in
+			 * eg, /var/cron/{allow,deny,maxtabsize}
+			 */
+struct updtab {
+	SLIST_ENTRY(updtab)	 up_link;
+	char			*up_name;
+	int			 up_canstat;
+	struct stat		 up_stat;
+};

 void		set_cron_uid __P((void)),
 		set_cron_cwd __P((void)),
@@ -199,27 +213,35 @@
 		free_entry __P((entry *)),
 		acquire_daemonlock __P((int)),
 		skip_comments __P((FILE *)),
-		log_it __P((char *, int, char *, char *)),
-		log_close __P((void));
+		log_it __P((const char *, int, const char *, const char *)),
+		log_close __P((void)),
+		done_update __P((const char *, struct updtab *));

 int		job_runqueue __P((void)),
 		set_debug_flags __P((char *)),
 		get_char __P((FILE *)),
 		get_string __P((char *, int, FILE *, char *)),
-		swap_uids __P((void)),
+		swap_gids __P((void)),
+		swap_gids_back __P((void)),
 		load_env __P((char *, FILE *)),
 		cron_pclose __P((FILE *)),
-		strcmp_until __P((char *, char *, int)),
+		strcmp_until __P((const char *, const char *, int)),
 		allowed __P((char *)),
-		strdtb __P((char *));
+		strdtb __P((char *)),
+		concat3 __P((char *, size_t, const char *, const char *, const char *)),
+		env_set __P((char ***, const char *)),
+		open_socket __P((int)),
+		drop_egid __P((void)),
+		need_update __P((const char *, struct updtab **));

 char		*env_get __P((char *, char **)),
 		*arpadate __P((time_t *)),
-		*mkprints __P((unsigned char *, unsigned int)),
+		*mkprints __P((unsigned char *, size_t)),
 		*first_word __P((char *, char *)),
 		**env_init __P((void)),
-		**env_copy __P((char **)),
-		**env_set __P((char **, char *));
+		**env_copy __P((char **));
+
+off_t		getmaxtabsize __P((void));

 user		*load_user __P((int, struct passwd *, char *)),
 		*find_user __P((cron_db *, char *));
Index: crontab.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/crontab.c,v
retrieving revision 1.15
diff -u -r1.15 crontab.c
--- crontab.c	1999/05/29 18:43:40	1.15
+++ crontab.c	2002/06/12 13:22:42
@@ -43,15 +43,14 @@
 #include <fcntl.h>
 #include <sys/file.h>
 #include <sys/stat.h>
-#ifdef USE_UTIMES
-# include <sys/time.h>
-#else
-# include <utime.h>
-#endif
+#include <sys/time.h>
+#include <utime.h>
 #if defined(POSIX)
 # include <locale.h>
 #endif
 #include <time.h>
+#include <signal.h>
+#include <err.h>


 #define NHEADER_LINES 3
@@ -66,9 +65,9 @@

 static	PID_T		Pid;
 static	char		User[MAX_UNAME], RealUser[MAX_UNAME];
-static	char		Filename[MAX_FNAME];
+static	char		Filename[MAX_FNAME], TempFile[2][MAX_FNAME];
 static	FILE		*NewCrontab;
-static	int		CheckErrorCount;
+static	int		CheckErrorCount, DeleteHooked;
 static	enum opt_t	Option;
 static	struct passwd	*pw;
 static	void		list_cmd __P((void)),
@@ -77,7 +76,10 @@
 			poke_daemon __P((void)),
 			check_error __P((const char *)),
 			parse_args __P((int c, char *v[])),
-			usage __P((char *));
+			usage __P((char *)),
+			hook_dtemp __P((void)),
+			sig_dtemp __P((int)),
+			exit_dtemp __P((void));
 static	int		replace_cmd __P((void));

 int main __P((int, char *[]));
@@ -105,8 +107,15 @@
 {
 	int	exitstatus;

+	setprogname(argv[0]);
+	if (getuid() != geteuid())
+		errx(1, "don't make me setuid. i run setgid.");
+
 	Pid = getpid();
 	ProgramName = argv[0];
+	TempFile[0][0] = '\0';
+	TempFile[1][0] = '\0';
+	DeleteHooked = 0;

 #if defined(POSIX)
 	setlocale(LC_ALL, "");
@@ -116,14 +125,13 @@
 	setlinebuf(stderr);
 #endif
 	parse_args(argc, argv);		/* sets many globals, opens a file */
-	set_cron_uid();
 	set_cron_cwd();
 	if (!allowed(User)) {
+		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
 		fprintf(stderr,
 			"You (%s) are not allowed to use this program (%s)\n",
 			User, ProgramName);
 		fprintf(stderr, "See crontab(1) for more information\n");
-		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
 		exit(ERROR_EXIT);
 	}
 	exitstatus = OK_EXIT;
@@ -141,10 +149,10 @@
 				usage("unrecognized option");
 				break;
 	}
-	exit(0);
+	exit(exitstatus);
 	/*NOTREACHED*/
 }
-	
+

 static void
 parse_args(argc, argv)
@@ -153,16 +161,14 @@
 {
 	int		argch;

-	if (!(pw = getpwuid(getuid()))) {
-		fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
-			ProgramName);
-		fprintf(stderr, "bailing out.\n");
-		exit(ERROR_EXIT);
-	}
-	strncpy(User, pw->pw_name, sizeof(User) - 1);
-	User[sizeof(User) - 1] = '\0';
-	strncpy(RealUser, User, sizeof(RealUser) - 1);
-	RealUser[sizeof(RealUser) - 1] = '\0';
+	if (!(pw = getpwuid(getuid())))
+		errx(1, "your UID is not in the passwd file.");
+
+	if (strlcpy(User, pw->pw_name, sizeof(User)) >= sizeof(User))
+		errx(1, "user name too long.");
+
+	(void) strlcpy(RealUser, User, sizeof(RealUser));
+
 	Filename[0] = '\0';
 	Option = opt_unknown;
 	while (-1 != (argch = getopt(argc, argv, "u:lerx:"))) {
@@ -184,8 +190,8 @@
 					ProgramName, optarg);
 				exit(ERROR_EXIT);
 			}
-			(void) strncpy(User, optarg, sizeof(User) - 1);
-			User[sizeof(User) - 1] = '\0';
+			if (strlcpy(User, optarg,sizeof(User)) >= sizeof(User))
+				errx(1, "user name too long.");
 			break;
 		case 'l':
 			if (Option != opt_unknown)
@@ -213,15 +219,13 @@
 		if (argv[optind] != NULL) {
 			usage("no arguments permitted after this option");
 		}
+	} else if (argv[optind] != NULL) {
+		Option = opt_replace;
+		if (strlcpy(Filename, argv[optind], sizeof(Filename))
+				>= sizeof(Filename))
+			errx(1, "file name too long.");
 	} else {
-		if (argv[optind] != NULL) {
-			Option = opt_replace;
-			(void) strncpy (Filename, argv[optind],
-			    sizeof(Filename) - 1);
-			Filename[sizeof(Filename) - 1] = '\0';
-		} else {
-			usage("file name must be specified for replace");
-		}
+		usage("file name must be specified for replace");
 	}

 	if (Option == opt_replace) {
@@ -232,7 +236,7 @@
 		if (!strcmp(Filename, "-")) {
 			NewCrontab = stdin;
 		} else {
-			/* relinquish the setuid status of the binary during
+			/* relinquish the setgid status of the binary during
 			 * the open, lest nonroot users read files they should
 			 * not be able to read.  we can't use access() here
 			 * since there's a race condition.  thanks go out to
@@ -240,18 +244,12 @@
 			 * the race.
 			 */

-			if (swap_uids() < OK) {
-				perror("swapping uids");
-				exit(ERROR_EXIT);
-			}
-			if (!(NewCrontab = fopen(Filename, "r"))) {
-				perror(Filename);
-				exit(ERROR_EXIT);
-			}
-			if (swap_uids() < OK) {
-				perror("swapping uids back");
-				exit(ERROR_EXIT);
-			}
+			if (swap_gids() < OK)
+				err(1, "swapping gids");
+			if (!(NewCrontab = fopen(Filename, "r")))
+				err(1, "%s", Filename);
+			if (swap_gids_back() < OK)
+				err(1, "swapping gids back");
 		}
 	}

@@ -267,7 +265,9 @@
 	int	ch;

 	log_it(RealUser, Pid, "LIST", User);
-	(void) snprintf(n, sizeof(n), CRON_TAB(User));
+	if (!concat3(n, sizeof(n), SPOOL_DIR, "/", User))
+		errx(1, "path too long.");
+
 	if (!(f = fopen(n, "r"))) {
 		if (errno == ENOENT)
 			fprintf(stderr, "no crontab for %s\n", User);
@@ -290,7 +290,9 @@
 	char	n[MAX_FNAME];

 	log_it(RealUser, Pid, "DELETE", User);
-	(void) snprintf(n, sizeof(n), CRON_TAB(User));
+	if (!concat3(n, sizeof(n), SPOOL_DIR, "/", User))
+		errx(1, "path too long.");
+
 	if (unlink(n)) {
 		if (errno == ENOENT)
 			fprintf(stderr, "no crontab for %s\n", User);
@@ -318,13 +320,15 @@
 	FILE		*f;
 	int		ch, t, x;
 	struct stat	statbuf;
-	time_t		mtime;
-	long		mtimensec;
+	struct timespec	mtimespec;
+	off_t		size;
 	WAIT_T		waiter;
 	PID_T		pid, xpid;

 	log_it(RealUser, Pid, "BEGIN EDIT", User);
-	(void) snprintf(n, sizeof(n), CRON_TAB(User));
+	if (!concat3(n, sizeof(n), SPOOL_DIR, "/", User))
+		errx(1, "path too long.");
+
 	if (!(f = fopen(n, "r"))) {
 		if (errno != ENOENT) {
 			perror(n);
@@ -337,25 +341,32 @@
 			exit(ERROR_EXIT);
 		}
 	}
+
+	if (!concat3(TempFile[0], sizeof(TempFile[0]), _PATH_TMP, "/",
+				"crontab.XXXXXXXX"))
+		errx(1, "path too long.");

-	(void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.%d", Pid);
-	if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0600))) {
-		perror(Filename);
-		goto fatal;
+	if (-1 == (t = mkstemp(TempFile[0]))) {
+		TempFile[0][0] = '\0';
+		err(1, "unable to open temp file");
 	}
+
+	hook_dtemp();
+
 #ifdef HAS_FCHOWN
-	if (fchown(t, getuid(), getgid()) < 0) {
+	if (fchown(t, getuid(), getgid()) < 0)
+		err(1, "fchown");
 #else
-	if (chown(Filename, getuid(), getgid()) < 0) {
+	if (chown(TempFile[0], getuid(), getgid()) < 0)
+		err(1, "chown");
 #endif
-		perror("fchown");
-		goto fatal;
-	}
-	if (!(NewCrontab = fdopen(t, "r+"))) {
-		perror("fdopen");
-		goto fatal;
-	}

+	if (fcntl(t, F_SETFD, 1) < 0)
+		err(1, "fcntl");
+
+	if (!(NewCrontab = fdopen(t, "r+")))
+		err(1, "fdopen");
+
 	Set_LineNum(1)

 	/* ignore the top few comments since we probably put them there.
@@ -382,24 +393,22 @@
 			putc(ch, NewCrontab);
 	fclose(f);
 	if (fflush(NewCrontab) < OK) {
-		perror(Filename);
+		perror(TempFile[0]);
 		exit(ERROR_EXIT);
 	}
  again:
 	rewind(NewCrontab);
 	if (ferror(NewCrontab)) {
 		fprintf(stderr, "%s: error while writing new crontab to %s\n",
-			ProgramName, Filename);
- fatal:		unlink(Filename);
+			ProgramName, TempFile[0]);
 		exit(ERROR_EXIT);
-	}
-	if (fstat(t, &statbuf) < 0) {
-		perror("fstat");
-		goto fatal;
 	}
-	mtime = statbuf.st_mtime;
-	mtimensec = statbuf.st_mtimensec;
+	if (fstat(t, &statbuf) < 0)
+		err(1, "fstat");

+	memcpy(&mtimespec, &statbuf.st_mtimespec, sizeof(mtimespec));
+	size = statbuf.st_size;
+
 	if ((!(editor = getenv("VISUAL")))
 	 && (!(editor = getenv("EDITOR")))
 	    ) {
@@ -416,27 +425,30 @@

 	switch (pid = fork()) {
 	case -1:
-		perror("fork");
-		goto fatal;
+		err(1, "fork");
+		/* NOTREACHED */
 	case 0:
 		/* child */
-		if (setuid(getuid()) < 0) {
-			perror("setuid(getuid())");
-			exit(ERROR_EXIT);
+		signal(SIGHUP, SIG_DFL);
+		signal(SIGINT, SIG_DFL);
+		signal(SIGQUIT, SIG_DFL);
+
+		if (drop_egid() == -1) {
+			perror("drop_egid()");
+			_exit(ERROR_EXIT);
 		}
-		if (chdir("/tmp") < 0) {
+		if (chdir(_PATH_TMP) < 0) {
 			perror("chdir(/tmp)");
-			exit(ERROR_EXIT);
+			_exit(ERROR_EXIT);
 		}
-		if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) {
+		if (!concat3(q, sizeof(q), editor, " ", TempFile[0])) {
 			fprintf(stderr, "%s: editor or filename too long\n",
 				ProgramName);
-			exit(ERROR_EXIT);
+			_exit(ERROR_EXIT);
 		}
-		snprintf(q, sizeof(q), "%s %s", editor, Filename);
 		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, NULL);
 		perror(editor);
-		exit(ERROR_EXIT);
+		_exit(ERROR_EXIT);
 		/*NOTREACHED*/
 	default:
 		/* parent */
@@ -448,29 +460,30 @@
 	if (xpid != pid) {
 		fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n",
 			ProgramName, xpid, pid, editor);
-		goto fatal;
+		exit(ERROR_EXIT);
 	}
 	if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
 		fprintf(stderr, "%s: \"%s\" exited with status %d\n",
 			ProgramName, editor, WEXITSTATUS(waiter));
-		goto fatal;
+		exit(ERROR_EXIT);
 	}
 	if (WIFSIGNALED(waiter)) {
 		fprintf(stderr,
 			"%s: \"%s\" killed; signal %d (%score dumped)\n",
 			ProgramName, editor, WTERMSIG(waiter),
 			WCOREDUMP(waiter) ?"" :"no ");
-		goto fatal;
-	}
-	if (fstat(t, &statbuf) < 0) {
-		perror("fstat");
-		goto fatal;
+		exit(ERROR_EXIT);
 	}
-	if (mtime == statbuf.st_mtime && mtimensec == statbuf.st_mtimensec) {
+	if (fstat(t, &statbuf) < 0)
+		err(1, "fstat");
+	if (timespeccmp(&mtimespec, &statbuf.st_mtimespec, -) == 0 &&
+			statbuf.st_size == size) {
 		fprintf(stderr, "%s: no changes made to crontab\n",
 			ProgramName);
 		goto remove;
 	}
+
+	strlcpy(Filename, TempFile[0], sizeof(Filename));
 	fprintf(stderr, "%s: installing new crontab\n", ProgramName);
 	switch (replace_cmd()) {
 	case 0:
@@ -499,14 +512,14 @@
 	default:
 		fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n",
 		    ProgramName);
-		goto fatal;
+		exit(ERROR_EXIT);
 	}
  remove:
-	unlink(Filename);
+	exit_dtemp();
  done:
 	log_it(RealUser, Pid, "END EDIT", User);
 }
-	
+

 /* returns	0	on success
  *		-1	on syntax error
@@ -514,56 +527,46 @@
  */
 static int
 replace_cmd() {
-	char	n[MAX_FNAME], n2[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
-	FILE	*tmp, *fmaxtabsize;
-	int	ch, eof;
+	char	n[MAX_FNAME], envstr[MAX_ENVSTR];
+	FILE	*tmp;
+	int	ch, last, eof, fd, ret;
 	entry	*e;
-	time_t	now = time(NULL);
-	char	**envp = env_init();
-	size_t	maxtabsize;
-	struct	stat statbuf;
-
-	(void) snprintf(n, sizeof(n), "tmp.%d", Pid);
-	(void) snprintf(tn, sizeof(tn), CRON_TAB(n));
-	if (!(tmp = fopen(tn, "w+"))) {
-		perror(tn);
-		return (-2);
-	}
+	time_t	now;
+	char	**envp;
+	sigset_t fullset, oset;
+
+	tmp	= NULL;
+	ret	= 0;
+	now	= time(NULL);
+	envp	= env_init();
+	if (envp == NULL)
+		err(1, "unable to initialize the environment");

-	/* Make sure that the crontab is not an unreasonable size.
-	 *
-	 * XXX This is subject to a race condition--the user could
-	 * add stuff to the file after we've checked the size but
-	 * before we slurp it in and write it out. We can't just move
-	 * the test to test the temp file we later create, because by
-	 * that time we've already filled up the crontab disk. Probably
-	 * the right thing to do is to do a bytecount in the copy loop
-	 * rather than stating the file we're about to read.
+	sigfillset(&fullset);
+	sigprocmask(SIG_BLOCK, &fullset, &oset);
+
+	/* temp name begins with a dot so that cron will skip over this
+	 * file should it see us.
 	 */
-	(void) snprintf(n2, sizeof(n), "%s/%s", CRONDIR, MAXTABSIZE_FILE);
-	if ((fmaxtabsize = fopen(n2, "r")))  {
-	    if (fgets(n2, sizeof(n2), fmaxtabsize) == NULL)  {
-		maxtabsize = 0;
-	    } else {
-		maxtabsize = atoi(n2);
-	    }
-	    fclose(fmaxtabsize);
-	} else {
-	    maxtabsize = MAXTABSIZE_DEFAULT;
+	if (!concat3(TempFile[1], sizeof(TempFile[1]), SPOOL_DIR, "/",
+				".tmp.XXXXXXXX"))
+		errx(1, "path too long.");
+
+	if ((fd = mkstemp(TempFile[1])) == -1 || !(tmp = fdopen(fd, "w+"))) {
+		perror(TempFile[1]);
+		if (fd != -1) {
+			close(fd);
+			unlink(TempFile[1]);
+		}
+		TempFile[1][0] = '\0';
+		sigprocmask(SIG_SETMASK, &oset, NULL);
+		return (-2);
 	}

-	if (fstat(fileno(NewCrontab), &statbuf))  {
-	    fprintf(stderr, "%s: error stat'ing crontab input: %s\n",
-		ProgramName, strerror(errno));
-	    return(-2);
-	}
-	if (statbuf.st_size > maxtabsize)  {
-	    fprintf(stderr,
-		"%s: %ld bytes is larger than the maximum size of %ld bytes\n",
-		ProgramName, (long) statbuf.st_size, (long) maxtabsize);
-	    return(-1);
-	}
+	hook_dtemp();
+	sigprocmask(SIG_SETMASK, &oset, NULL);

+
 	/* write a signature at the top of the file.
 	 *
 	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
@@ -580,17 +583,27 @@
 	while (EOF != (ch = get_char(NewCrontab)))
 		putc(ch, tmp);
 	ftruncate(fileno(tmp), ftell(tmp));
-	fflush(tmp);  rewind(tmp);
+	fflush(tmp);

 	if (ferror(tmp)) {
 		fprintf(stderr, "%s: error while writing new crontab to %s\n",
-			ProgramName, tn);
-		fclose(tmp);  unlink(tn);
-		return (-2);
+			ProgramName, TempFile[1]);
+		ret = -2; goto out;
 	}

+	/* Make sure that the crontab is not an unreasonable size.
+	 */
+	if (ftello(tmp) > getmaxtabsize()) {
+		fprintf(stderr,
+			"%s: %qd bytes is larger than the maximum size of"
+			"%qd bytes\n", ProgramName, ftello(tmp),
+			getmaxtabsize());
+		ret = -2; goto out;
+	}
+
 	/* check the syntax of the file being installed.
 	 */
+	rewind(tmp);

 	/* BUG: was reporting errors after the EOF if there were any errors
 	 * in the file proper -- kludged it by stopping after first error.
@@ -615,72 +628,122 @@

 	if (CheckErrorCount != 0) {
 		fprintf(stderr, "errors in crontab file, can't install.\n");
-		fclose(tmp);  unlink(tn);
-		return (-1);
+		ret = -1; goto out;
 	}

-#ifdef HAS_FCHOWN
-	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
+#ifdef HAS_FCHMOD
+	if (fchmod(fileno(tmp), 0600) < OK)
 #else
-	if (chown(tn, ROOT_UID, -1) < OK)
+	if (chmod(TempFile[1], 0600) < OK)
 #endif
 	{
 		perror("chown");
-		fclose(tmp);  unlink(tn);
-		return (-2);
+		ret = -2; goto out;
 	}

-#ifdef HAS_FCHMOD
-	if (fchmod(fileno(tmp), 0600) < OK)
+#ifdef HAS_FCHOWN
+	if (fchown(fileno(tmp), pw->pw_uid, -1) < OK)
 #else
-	if (chmod(tn, 0600) < OK)
+	if (chown(TempFile[1], pw->pw_uid, -1) < OK)
 #endif
 	{
 		perror("chown");
-		fclose(tmp);  unlink(tn);
-		return (-2);
+		ret = -2; goto out;
 	}

 	if (fclose(tmp) == EOF) {
 		perror("fclose");
-		unlink(tn);
-		return (-2);
+		ret = -2; goto out;
 	}
+	tmp = NULL;

-	(void) snprintf(n, sizeof(n), CRON_TAB(User));
-	if (rename(tn, n)) {
-		fprintf(stderr, "%s: error renaming %s to %s\n",
-			ProgramName, tn, n);
-		perror("rename");
-		unlink(tn);
-		return (-2);
+	if (!concat3(n, sizeof(n), SPOOL_DIR, "/", User)) {
+		warnx("path too long.");
+		ret = -2; goto out;
+	}
+	if (rename(TempFile[1], n)) {
+		warn("unable to rename temporary file");
+		ret = -2; goto out;
 	}
+	TempFile[1][0] = '\0';
 	log_it(RealUser, Pid, "REPLACE", User);

 	poke_daemon();

-	return (0);
+out:
+	if (tmp != NULL)
+		fclose(tmp);
+	if (TempFile[1][0] != '\0') {
+		unlink(TempFile[1]);
+		TempFile[1][0] = '\0';
+	}
+	return (ret);
 }


 static void
 poke_daemon() {
-#ifdef USE_UTIMES
-	struct timeval tvs[2];
-	struct timezone tz;
-
-	(void) gettimeofday(&tvs[0], &tz);
-	tvs[1] = tvs[0];
-	if (utimes(SPOOL_DIR, tvs) < OK) {
-		fprintf(stderr, "crontab: can't update mtime on spooldir\n");
-		perror(SPOOL_DIR);
-		return;
-	}
-#else
+	int fd;
+
 	if (utime(SPOOL_DIR, NULL) < OK) {
 		fprintf(stderr, "crontab: can't update mtime on spooldir\n");
 		perror(SPOOL_DIR);
 		return;
+	}
+
+	(void) signal(SIGPIPE, SIG_IGN);
+	if ((fd = open_socket(0)) != -1) {
+		(void) write(fd, "!", 1);
+		close(fd);
 	}
-#endif /*USE_UTIMES*/
+	(void) signal(SIGPIPE, SIG_DFL);
 }
+
+
+static void
+hook_dtemp()
+{
+
+	if (!DeleteHooked) {
+		DeleteHooked = 1;
+		(void) signal(SIGINT, sig_dtemp);
+		(void) signal(SIGHUP, sig_dtemp);
+		(void) signal(SIGQUIT, sig_dtemp);
+		atexit(exit_dtemp);
+	}
+}
+
+
+static void
+exit_dtemp()
+{
+
+	if (TempFile[0][0] != '\0') {
+		unlink(TempFile[0]);
+		TempFile[0][0] = '\0';
+	}
+	if (TempFile[1][0] != '\0') {
+		unlink(TempFile[1]);
+		TempFile[1][0] = '\0';
+	}
+}
+
+
+static void
+sig_dtemp(signo)
+	int signo;
+{
+	int serr = errno;
+
+	/* XXX everything should be atomic. is it? */
+	if (TempFile[0][0] != '\0')
+		unlink(TempFile[0]);
+	if (TempFile[1][0] != '\0')
+		unlink(TempFile[1]);
+	if (signo != 0) {
+		(void) signal(signo, SIG_DFL);
+		raise(signo);
+	}
+	errno = serr;
+}
+
Index: database.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/database.c,v
retrieving revision 1.4
diff -u -r1.4 database.c
--- database.c	1998/01/31 14:40:26	1.4
+++ database.c	2002/06/12 13:22:42
@@ -31,6 +31,7 @@


 #include "cron.h"
+#include <string.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <sys/file.h>
@@ -38,6 +39,10 @@

 #define TMAX(a,b) ((a)>(b)?(a):(b))

+#ifndef	O_NOFOLLOW
+#warn "O_NOFOLLOW was not present, crontab file handling may be less secure"
+#define	O_NOFOLLOW	0
+#endif

 static	void		process_crontab __P((char *, char *, char *,
 					     struct stat *,
@@ -93,7 +98,7 @@
 	new_db.head = new_db.tail = NULL;

 	if (syscron_stat.st_mtime) {
-		process_crontab("root", "*system*",
+		process_crontab(ROOT_USER, "*system*",
 				SYSCRONTAB, &syscron_stat,
 				&new_db, old_db);
 	}
@@ -119,9 +124,8 @@
 		if (dp->d_name[0] == '.')
 			continue;

-		(void) strncpy(fname, dp->d_name, sizeof(fname) - 1);
-		fname[sizeof(fname) - 1] = '\0';
-		snprintf(tabname, sizeof(tabname), CRON_TAB(fname));
+		(void)strlcpy(fname, dp->d_name, sizeof(fname));
+		(void)concat3(tabname, sizeof(tabname), SPOOL_DIR, "/", fname);

 		process_crontab(fname, fname, tabname,
 				&statbuf, &new_db, old_db);
@@ -217,7 +221,13 @@
 		goto next_crontab;
 	}

-	if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) {
+	/* syscrontab unames are exempt from allowed(), but root isn't */
+	if (!allowed(uname)) {
+		log_it(fname, getpid(), "USER NOT ALLOWED", tabname);
+		goto next_crontab;
+	}
+
+	if ((crontab_fd = open(tabname, O_RDONLY|O_NOFOLLOW, 0)) < OK) {
 		/* crontab not accessible?
 		 */
 		log_it(fname, getpid(), "CAN'T OPEN", tabname);
@@ -226,6 +236,32 @@

 	if (fstat(crontab_fd, statbuf) < OK) {
 		log_it(fname, getpid(), "FSTAT FAILED", tabname);
+		goto next_crontab;
+	}
+
+	/* user's crontab should be at most mode 600 and owned
+	 * and owned by uname or root, link count must be one
+	 */
+	if (!S_ISREG(statbuf->st_mode)) {
+		log_it(fname, getpid(), "BAD FILE TYPE", tabname);
+		goto next_crontab;
+	}
+	if (statbuf->st_nlink != 1) {
+		log_it(fname, getpid(), "BAD LINK COUNT", tabname);
+		goto next_crontab;
+	}
+	if ((statbuf->st_mode & (07777 ^ 0600)) != 0) {
+		log_it(fname, getpid(), "BAD PERMISSIONS", tabname);
+		goto next_crontab;
+	}
+	if ((pw == NULL && ROOT_UID != statbuf->st_uid) ||
+			(pw != NULL && pw->pw_uid != statbuf->st_uid)) {
+		log_it(fname, getpid(), "BAD OWNER",  tabname);
+		goto next_crontab;
+	}
+
+	if (pw != NULL && statbuf->st_size > getmaxtabsize()) {
+		log_it(fname, getpid(), "BAD FILE SIZE (TOO BIG)", tabname);
 		goto next_crontab;
 	}

Index: do_command.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/do_command.c,v
retrieving revision 1.10
diff -u -r1.10 do_command.c
--- do_command.c	2002/03/23 09:38:02	1.10
+++ do_command.c	2002/06/12 13:22:43
@@ -35,10 +35,17 @@
 #if defined(SYSLOG)
 # include <syslog.h>
 #endif
+#include <fcntl.h>
+#ifdef LOGIN_CAP
+# include <pwd.h>
+# include <login_cap.h>
+#endif


-static void		child_process __P((entry *, user *)),
-			do_univ __P((user *));
+static	void		child_process __P((entry *));
+#ifndef LOGIN_CAP
+static	void		do_univ __P((user *));
+#endif


 void
@@ -46,6 +53,11 @@
 	entry	*e;
 	user	*u;
 {
+#ifdef LOGIN_CAP
+	struct passwd	*pwd;
+	login_cap_t	*lc;
+#endif
+
 	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
 		getpid(), e->cmd, u->name, e->uid, e->gid))

@@ -63,7 +75,44 @@
 	case 0:
 		/* child process */
 		acquire_daemonlock(1);
-		child_process(e, u);
+
+		signal(SIGHUP, SIG_IGN);
+
+		/* drop privileges */
+		chdir("/");
+#ifdef LOGIN_CAP
+		if ((pwd = getpwuid(e->uid)) == NULL) {
+			log_it("CRON",getpid(),"error","user not in passwd");
+			_exit(1);
+		}
+		lc = login_getclass(pwd->pw_class);
+		if (setusercontext(lc, pwd, e->uid, LOGIN_SETALL) < 0) {
+			log_it("CRON",getpid(),"error","setusercontext failed");
+			_exit(1);
+		}
+		if (lc != NULL)
+			login_close(lc);
+		endpwent();
+#else
+		/* set our login universe.  Do this in the grandchild
+		 * so that the child can invoke /usr/lib/sendmail
+		 * without surprises.
+		 */
+		do_univ(u);
+
+		/* set our directory, uid and gid.  Set gid first, since once
+		 * we set uid, we've lost root privledges.
+		 */
+		setgid(e->gid);
+# if defined(BSD)
+		initgroups(env_get("LOGNAME", e->envp), e->gid);
+# endif
+		setuid(e->uid);		/* we aren't root after this... */
+#endif /* LOGIN_CAP */
+
+		chdir(env_get("HOME", e->envp));
+
+		child_process(e);
 		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
 		_exit(OK_EXIT);
 		break;
@@ -76,14 +125,14 @@


 static void
-child_process(e, u)
+child_process(e)
 	entry	*e;
-	user	*u;
 {
 	int		stdin_pipe[2], stdout_pipe[2];
-	char	*input_data;
+	char		*input_data;
 	char		*usernm, *mailto;
 	int		children = 0;
+
 #ifdef __GNUC__
 	(void) &input_data;	/* Avoid vfork clobbering */
 	(void) &mailto;
@@ -125,6 +174,11 @@
 	 */
 	pipe(stdin_pipe);	/* child's stdin */
 	pipe(stdout_pipe);	/* child's stdout */
+
+	/* this descriptor might get leaked to child mail process
+	 * XXX closeall() in cron_popen()
+	 */
+	(void) fcntl(stdout_pipe[READ_PIPE], F_SETFD, 1);

 	/* since we are a forked process, we can diddle the command string
 	 * we were passed -- nobody else is going to use it again, right?
@@ -175,7 +229,7 @@
 	switch (vfork()) {
 	case -1:
 		log_it("CRON",getpid(),"error","can't vfork");
-		exit(ERROR_EXIT);
+		_exit(ERROR_EXIT);
 		/*NOTREACHED*/
 	case 0:
 		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
@@ -224,22 +278,6 @@
 		close(stdin_pipe[READ_PIPE]);
 		close(stdout_pipe[WRITE_PIPE]);

-		/* set our login universe.  Do this in the grandchild
-		 * so that the child can invoke /usr/lib/sendmail
-		 * without surprises.
-		 */
-		do_univ(u);
-
-		/* set our directory, uid and gid.  Set gid first, since once
-		 * we set uid, we've lost root privledges.
-		 */
-		setgid(e->gid);
-# if defined(BSD)
-		initgroups(env_get("LOGNAME", e->envp), e->gid);
-# endif
-		setuid(e->uid);		/* we aren't root after this... */
-		chdir(env_get("HOME", e->envp));
-
 #ifdef USE_SIGCHLD
 		/* our grandparent is watching for our death by catching
 		 * SIGCHLD.  the parent is ignoring SIGCHLD's; we want
@@ -342,7 +380,7 @@
 		fclose(out);

 		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
-		exit(0);
+		_exit(0);
 	}

 	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
@@ -368,7 +406,7 @@
 		if (ch != EOF) {
 			FILE	*mail;
 			int	bytes = 1;
-			int		status = 0;
+			int	status = 0;

 #ifdef __GNUC__
 			(void) &mail;	/* Avoid vfork clobbering */
@@ -503,6 +541,7 @@
 }


+#ifndef LOGIN_CAP
 static void
 do_univ(u)
 	user	*u;
@@ -539,3 +578,4 @@
 	(void) universe(U_ATT);
 #endif
 }
+#endif
Index: entry.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/entry.c,v
retrieving revision 1.5
diff -u -r1.5 entry.c
--- entry.c	2000/09/13 04:07:34	1.5
+++ entry.c	2002/06/12 13:22:43
@@ -112,7 +112,12 @@
 	 * of a list of minutes.
 	 */

-	e = (entry *) calloc(sizeof(entry), sizeof(char));
+	if ((e = (entry *) calloc(sizeof(entry), sizeof(char))) == NULL) {
+		ecode = e_none;
+		goto eof;
+	}
+	e->cmd = NULL;
+	e->envp = NULL;

 	if (ch == '@') {
 		/* all of these should be flagged and load-limited; i.e.,
@@ -254,25 +259,32 @@
 	/* copy and fix up environment.  some variables are just defaults and
 	 * others are overrides.
 	 */
-	e->envp = env_copy(envp);
-	if (!env_get("SHELL", e->envp)) {
-		snprintf(envstr, sizeof(envstr), "SHELL=%s", _PATH_BSHELL);
-		e->envp = env_set(e->envp, envstr);
-	}
-	if (!env_get("HOME", e->envp)) {
-		snprintf(envstr, sizeof(envstr), "HOME=%s", pw->pw_dir);
-		e->envp = env_set(e->envp, envstr);
-	}
-	if (!env_get("PATH", e->envp)) {
-		snprintf(envstr, sizeof(envstr), "PATH=%s", _PATH_DEFPATH);
-		e->envp = env_set(e->envp, envstr);
+	if ((e->envp = env_copy(envp)) == NULL) {
+		ecode = e_none;
+		goto eof;
 	}
-	snprintf(envstr, sizeof(envstr), "%s=%s", "LOGNAME", pw->pw_name);
-	e->envp = env_set(e->envp, envstr);
+
+#define ENV_SET(a, b)						\
+	do {							\
+	if (!concat3(envstr, sizeof(envstr), (a), "=", (b)) ||	\
+			!env_set(&e->envp, envstr)) {		\
+		ecode = e_none;					\
+		goto eof;					\
+	}							\
+	} while (0 /*CONSTCOND*/)
+
+	if (!env_get("SHELL", e->envp))
+		ENV_SET("SHELL", _PATH_BSHELL);
+	if (!env_get("HOME", e->envp))
+		ENV_SET("HOME", pw->pw_dir);
+	if (!env_get("PATH", e->envp))
+		ENV_SET("PATH", _PATH_DEFPATH);
+
+	ENV_SET("LOGNAME", pw->pw_name);
 #if defined(BSD)
-	snprintf(envstr, sizeof(envstr), "%s=%s", "USER", pw->pw_name);
-	e->envp = env_set(e->envp, envstr);
+	ENV_SET("USER", pw->pw_name);
 #endif
+#undef ENV_SET

 	Debug(DPARS, ("load_entry()...about to parse command\n"))

@@ -292,7 +304,10 @@

 	/* got the command in the 'cmd' string; save it in *e.
 	 */
-	e->cmd = strdup(cmd);
+	if ((e->cmd = strdup(cmd)) == NULL) {
+		ecode = e_none;
+		goto eof;
+	}

 	Debug(DPARS, ("load_entry()...returning successfully\n"))

@@ -301,7 +316,9 @@
 	return e;

  eof:
-	free(e);
+	if (e != NULL)
+		free_entry(e);
+
 	if (ecode != e_none && error_func)
 		(*error_func)(ecodes[(int)ecode]);
 	while (ch != EOF && ch != '\n')
Index: env.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/env.c,v
retrieving revision 1.11
diff -u -r1.11 env.c
--- env.c	1999/03/22 22:18:45	1.11
+++ env.c	2002/06/12 13:22:43
@@ -29,12 +29,16 @@

 #include "cron.h"
 #include <string.h>
+#include <errno.h>

+
 char **
 env_init()
 {
-	char	**p = (char **) malloc(sizeof(char **));
+	char	**p;

+	if ((p = malloc(sizeof(char **))) == NULL)
+		return NULL;
 	p[0] = NULL;
 	return (p);
 }
@@ -46,6 +50,12 @@
 {
 	char	**p;

+	/* code assumes env_free(NULL) will work ok. free(NULL) works
+	 * so we do too.
+	 */
+	if (envp == NULL)
+		return;
+
 	for (p = envp;  *p;  p++)
 		free(*p);
 	free(envp);
@@ -61,29 +71,46 @@

 	for (count = 0;  envp[count] != NULL;  count++)
 		;
-	p = (char **) malloc((count+1) * sizeof(char *));  /* 1 for the NULL */
+
+	p = malloc((count+1) * sizeof(char *));  /* 1 for the NULL */
+	if (p == NULL)
+		return NULL;
+
 	for (i = 0;  i < count;  i++)
-		p[i] = strdup(envp[i]);
+		if ((p[i] = strdup(envp[i])) == NULL) {
+			int save_errno = errno;
+
+			env_free(p);
+			errno = save_errno;
+			return (NULL);
+		}
+
 	p[count] = NULL;
 	return (p);
 }


-char **
+int
 env_set(envp, envstr)
-	char	**envp;
-	char	*envstr;
+	char		***envp;
+	const char	*envstr;
 {
 	int	count, found;
-	char	**p;
+	char	**p, *str;
+
+	/* preallocate the new string */
+	if ((str = strdup(envstr)) == NULL)
+		return 0;

 	/*
 	 * count the number of elements, including the null pointer;
 	 * also set 'found' to -1 or index of entry if already in here.
 	 */
+
 	found = -1;
-	for (count = 0;  envp[count] != NULL;  count++) {
-		if (!strcmp_until(envp[count], envstr, '='))
+	p = *envp;
+	for (count = 0;  p[count] != NULL;  count++) {
+		if (!strcmp_until(p[count], envstr, '='))
 			found = count;
 	}
 	count++;	/* for the NULL */
@@ -93,21 +120,26 @@
 		 * it exists already, so just free the existing setting,
 		 * save our new one there, and return the existing array.
 		 */
-		free(envp[found]);
-		envp[found] = strdup(envstr);
-		return (envp);
+		free(*envp[found]);
+		*envp[found] = str;
+		return 1;
 	}

 	/*
 	 * it doesn't exist yet, so resize the array, move null pointer over
-	 * one, save our string over the old null pointer, and return resized
-	 * array.
+	 * one, save our string over the old null pointer, and return the
+	 * new env in envp.
 	 */
-	p = (char **) realloc((void *) envp,
-			      (unsigned) ((count+1) * sizeof(char **)));
-	p[count] = p[count-1];
-	p[count-1] = strdup(envstr);
-	return (p);
+	p = realloc(*envp, ((count+1) * sizeof(char **)));
+	if (p == NULL) {
+		free(str);
+		return 0;
+	}
+
+	p[count] = NULL;
+	p[count-1] = str;
+	*envp = p;
+	return 1;
 }


@@ -206,7 +238,7 @@
 	char	*name;
 	char	**envp;
 {
-	int	len = strlen(name);
+	size_t	len = strlen(name);
 	char	*p, *q;

 	while ((p = *envp++) != NULL) {
Index: job.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/job.c,v
retrieving revision 1.4
diff -u -r1.4 job.c
--- job.c	2002/04/25 14:45:05	1.4
+++ job.c	2002/06/12 13:22:44
@@ -65,8 +65,9 @@
 		}

 	/* build a job queue element */
-	j = (job*)malloc(sizeof(job));
-	j->next = (job*) NULL;
+	if ((j = malloc(sizeof(job))) == NULL)
+		return;
+	j->next = NULL;
 	j->e = e;
 	j->u = u;
 	j->t = TargetTime;
@@ -109,17 +110,26 @@
 	job	*j;
 {
 	char *within, *t;
-	int delta;
+	long delta;

 	within = env_get("CRON_WITHIN", j->e->envp);
 	if (within == NULL)
 		return (1);

-	/* XXX handle 2m, 4h, etc? */
 	errno = 0;
 	delta = strtol(within, &t, 10);
-	if (errno == ERANGE || *t != '\0' || delta <= 0)
+	if (errno == ERANGE || delta <= 0)
 		return (1);
+
+	switch (tolower(*t)) {
+	case 'y':  delta *= 365;
+	case 'd':  delta *= 24;
+	case 'h':  delta *= 60;
+	case 'm':  delta *= 60;
+	case 's':
+	case '\0': break;
+	default:   return (1);
+	}

 	return ((j->t + delta) > time(NULL));
 }
Index: misc.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/misc.c,v
retrieving revision 1.8
diff -u -r1.8 misc.c
--- misc.c	1998/07/28 19:27:39	1.8
+++ misc.c	2002/06/12 13:22:44
@@ -32,6 +32,8 @@


 #include "cron.h"
+#include <sys/socket.h>
+#include <sys/un.h>
 #if SYS_TIME_H
 # include <sys/time.h>
 #else
@@ -45,23 +47,34 @@
 #if defined(SYSLOG)
 # include <syslog.h>
 #endif
+#include <stringlist.h>
+#include <grp.h>


 #if defined(LOG_DAEMON) && !defined(LOG_CRON)
 #define LOG_CRON LOG_DAEMON
 #endif

-static int in_file __P((char *, FILE *));
-static void mkprint __P((char *, unsigned char *, int));
+struct aclist {
+	const char	*ac_file;
+	int		 ac_exists;
+	StringList	*ac_users;
+}; 

-static int		LogFD = ERR;

+static	void	mkprint __P((char *, unsigned char *, size_t));
+static	int	check_list __P((struct aclist *));
+
+static	int		LogFD = ERR;
+static	gid_t		cron_gid = (gid_t) -1;
+static	SLIST_HEAD(up_head, updtab) uphead = SLIST_HEAD_INITIALIZER(uphead);

+
 int
 strcmp_until(left, right, until)
-	char	*left;
-	char	*right;
-	int	until;
+	const char	*left;
+	const char	*right;
+	int		until;
 {
 	int	diff;

@@ -113,6 +126,26 @@


 int
+concat3(dst, len, a, b, c)
+	char *dst;
+	size_t len;
+	const char *a;
+	const char *b;
+	const char *c;
+{
+	if (len > 0)
+		dst[0] = '\0';
+	if (strlcpy(dst, a, len) >= len)
+		return 0;
+	if (strlcat(dst, b, len) >= len)
+		return 0;
+	if (strlcat(dst, c, len) >= len)
+		return 0;
+	return 1;
+}
+
+
+int
 set_debug_flags(flags)
 	char	*flags;
 {
@@ -182,6 +215,7 @@
 void
 set_cron_uid()
 {
+	/* Only called from cron, not crontab */
 #if defined(BSD) || defined(POSIX)
 	if (seteuid(ROOT_UID) < OK) {
 		perror("seteuid");
@@ -193,6 +227,12 @@
 		exit(ERROR_EXIT);
 	}
 #endif
+
+	if (gid_from_group(CRON_GROUP, &cron_gid) == -1)
+		err(1, "gid_from_group(\""CRON_GROUP"\")");
+
+	if (setgroups(0, NULL) == -1 || setgid(cron_gid) == -1)
+		err(1, "failed to drop groups");
 }


@@ -203,33 +243,34 @@

 	/* first check for CRONDIR ("/var/cron" or some such)
 	 */
-	if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
+	if (stat(CRONDIR, &sb) < OK) {
 		perror(CRONDIR);
-		if (OK == mkdir(CRONDIR, 0700)) {
+		if (errno != ENOENT)
+			exit(ERROR_EXIT);
+
+		if (OK == mkdir(CRONDIR, 0710) &&
+				OK == chown(CRONDIR, ROOT_UID, cron_gid)) {
 			fprintf(stderr, "%s: created\n", CRONDIR);
 			stat(CRONDIR, &sb);
 		} else {
-			fprintf(stderr, "%s: ", CRONDIR);
-			perror("mkdir");
-			exit(ERROR_EXIT);
+			err(1, "%s: mkdir/chown", CRONDIR);
 		}
 	}
 	if (!S_ISDIR(sb.st_mode)) {
 		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
 			CRONDIR);
 		exit(ERROR_EXIT);
-	}
-	if (chdir(CRONDIR) < OK) {
-		fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR);
-		perror(CRONDIR);
-		exit(ERROR_EXIT);
 	}
+	if (chdir(CRONDIR) < OK)
+		err(1, "cannot chdir(%s), bailing out", CRONDIR);

 	/* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
 	 */
 	if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
 		perror(SPOOL_DIR);
-		if (OK == mkdir(SPOOL_DIR, 0700)) {
+		if (OK == mkdir(SPOOL_DIR, 01730) &&
+		    OK == chmod(SPOOL_DIR, 01730) &&
+		    OK == chown(SPOOL_DIR, ROOT_UID, cron_gid)) {
 			fprintf(stderr, "%s: created\n", SPOOL_DIR);
 			stat(SPOOL_DIR, &sb);
 		} else {
@@ -406,25 +447,59 @@
 }


-/* int in_file(char *string, FILE *file)
- *	return TRUE if one of the lines in file matches string exactly,
- *	FALSE otherwise.
+/* int check_list()
+ *	returns 1 if username is in filename
+ *	returns 0 if username is not in filename, or the file could not
+ *		be opened
+ *	returns -1 if an error occurs
  */
 static int
-in_file(string, file)
-	char *string;
-	FILE *file;
-{
-	char line[MAX_TEMPSTR];
-
-	rewind(file);
-	while (fgets(line, MAX_TEMPSTR, file)) {
-		if (line[0] != '\0')
-			line[strlen(line)-1] = '\0';
-		if (0 == strcmp(line, string))
-			return TRUE;
-	}
-	return FALSE;
+check_list(acl)
+	struct aclist	*acl;
+{
+	char		line[MAX_TEMPSTR];
+	StringList	*newl;
+	FILE		*fp;
+	struct updtab	*up;
+	char		*p;
+
+	if (need_update(acl->ac_file, &up)) {
+		/* reload */
+		if ((newl = sl_init()) == NULL)
+			return -1;
+
+		fp = fopen(acl->ac_file, "r");
+		acl->ac_exists = (fp != NULL);
+		if (acl->ac_exists) {
+			while(fgets(line, sizeof(line), fp) != NULL) {
+				if (line[0] == '\0' || line[0] == '#')
+					continue;
+
+				line[strlen(line) - 1] = '\0';	/* strip nl */
+				if ((p = strdup(line)) == NULL)
+					goto fail;
+				if (sl_add(newl, p) == -1) {
+					free(p); goto fail;
+				}
+			}
+			fclose(fp);
+		} else if (!acl->ac_exists &&
+				errno != ENOENT && errno != EPERM)
+			goto fail;
+
+		if (acl->ac_users != NULL)
+			sl_free(acl->ac_users, 1);
+		acl->ac_users = newl;
+		done_update(acl->ac_file, up);
+	}
+
+	return 1;
+fail:
+	if (fp != NULL)
+		fclose(fp);
+	if (newl != NULL)
+		sl_free(newl, 1);
+	return -1;
 }


@@ -437,26 +512,31 @@
 allowed(username)
 	char *username;
 {
-	static int	init = FALSE;
-	static FILE	*allow, *deny;
-
-	if (!init) {
-		init = TRUE;
 #if defined(ALLOW_FILE) && defined(DENY_FILE)
-		allow = fopen(ALLOW_FILE, "r");
-		deny = fopen(DENY_FILE, "r");
-		Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
-#else
-		allow = NULL;
-		deny = NULL;
-#endif
-	}
+	static struct aclist	allow	= { ALLOW_FILE, 0, NULL };
+	static struct aclist	deny	= { DENY_FILE, 0, NULL };

-	if (allow)
-		return (in_file(username, allow));
-	if (deny)
-		return (!in_file(username, deny));
+	Debug(DMISC, ("checking lists..\n"))
+	if (check_list(&allow) == -1 ||
+	    check_list(&deny) == -1) {
+		Debug(DMISC, ("error occured opening list\n"))
+		return 0;
+	}
+
+	if (allow.ac_exists) {
+		if (allow.ac_users != NULL)
+			if (sl_find(allow.ac_users, username))
+				return 1;
+
+		return 0;
+	} else if (deny.ac_exists) {
+		if (deny.ac_users != NULL)
+			if (sl_find(deny.ac_users, username))
+				return 0;

+		return 1;
+	}
+#endif /* ALLOW_FILE && DENY_FILE */
 #if defined(ALLOW_ONLY_ROOT)
 	return (strcmp(username, ROOT_USER) == 0);
 #else
@@ -467,17 +547,17 @@

 void
 log_it(username, xpid, event, detail)
-	char	*username;
-	int	xpid;
-	char	*event;
-	char	*detail;
+	const char	*username;
+	int		xpid;
+	const char	*event;
+	const char	*detail;
 {
 	PID_T			pid = xpid;
 #if defined(LOG_FILE)
 	char			*msg;
 	size_t			msglen;
 	TIME_T			now = time((TIME_T) 0);
-	struct tm	*t = localtime(&now);
+	struct tm		*t = localtime(&now);
 #endif /*LOG_FILE*/

 #if defined(SYSLOG)
@@ -489,14 +569,16 @@
 	 */
 	msglen = strlen(username) + strlen(event) + strlen(detail) +
 	    MAX_TEMPSTR;
-	msg = malloc(msglen);

+	/* XXX by adjusting ulimits user can evade being logged */
+	if ((msg = malloc(msglen)) == NULL)
+		goto next_meth;
+
 	if (LogFD < OK) {
 		LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
 		if (LogFD < OK) {
-			fprintf(stderr, "%s: can't open log file\n",
-				ProgramName);
-			perror(LOG_FILE);
+			err(1, "%s: can't open log file `%s'", ProgramName,
+					LOG_FILE);
 		} else {
 			(void) fcntl(LogFD, F_SETFD, 1);
 		}
@@ -506,29 +588,24 @@
 	 * everything out in one chunk and this has to be atomically appended
 	 * to the log file.
 	 */
-	snprintf(msg, msglen, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
+	msglen = snprintf(msg, msglen,
+		"%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
 		username,
 		t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid,
 		event, detail);

-	/* we have to run strlen() because sprintf() returns (char*) on old BSD
-	 */
-	if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
+	if (LogFD < OK || write(LogFD, msg, msglen) < OK) {
 		if (LogFD >= OK)
 			perror(LOG_FILE);
 		fprintf(stderr, "%s: can't write to log file\n", ProgramName);
-		write(STDERR, msg, strlen(msg));
+		write(STDERR, msg, msglen);
 	}

 	free(msg);
+next_meth:
 #endif /*LOG_FILE*/
-
 #if defined(SYSLOG)
 	if (!syslog_open) {
-		/* we don't use LOG_PID since the pid passed to us by
-		 * our client may not be our own.  therefore we want to
-		 * print the pid ourselves.
-		 */
 # ifdef LOG_DAEMON
 		openlog(ProgramName, LOG_PID, LOG_CRON);
 # else
@@ -600,7 +677,7 @@
 mkprint(dst, src, len)
 	char *dst;
 	unsigned char *src;
-	int len;
+	size_t len;
 {
 	while (len-- > 0)
 	{
@@ -628,7 +705,7 @@
 char *
 mkprints(src, len)
 	unsigned char *src;
-	unsigned int len;
+	size_t len;
 {
 	char *dst = malloc(len*4 + 1);

@@ -651,27 +728,178 @@
 	struct tm *tm = localtime(&t);
 	int hours = tm->tm_gmtoff / 3600;
 	int minutes = (tp->tm_gmtoff - (hours * 3600)) / 60;
+	char *p;

 	if (minutes < 0)
 		minutes = -minutes;

 	(void)strftime(ret, sizeof(ret), "%a, %e %b %Y %T ????? (%Z)", &tm);
-	(void)snprintf(strchr(ret, '?'), "% .2d%.2d", hours, minutes);
+	if ((p = strchr(ret, '?')) != NULL)
+	(void)snprintf(p, sizeof(ret) - (p - ret), "% .2d%.2d", hours, minutes);
 	ret[sizeof(ret) - 1] = '\0';
 	return ret;
 }
 #endif /*MAIL_DATE*/


-#ifdef HAVE_SAVED_UIDS
-static int save_euid;
-int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); }
-#if 0
-int swap_uids_back() { return seteuid(save_euid); }
-#endif
-#else /*HAVE_SAVED_UIDS*/
-int swap_uids() { return setreuid(geteuid(), getuid()); }
-#if 0
-int swap_uids_back() { return swap_uids(); }
-#endif
-#endif /*HAVE_SAVED_UIDS*/
+#ifdef HAVE_SAVED_GIDS
+static int save_egid;
+int swap_gids() { save_egid = getegid(); return setegid(getgid()); }
+int swap_gids_back() { return setegid(save_egid); }
+#else /*HAVE_SAVED_GIDS*/
+int swap_gids() { return setregid(getegid(), getgid()); }
+int swap_gids_back() { return swap_gids(); }
+#endif /*HAVE_SAVED_GIDS*/
+int drop_egid() { return setgid(getgid()); }
+
+
+/* int open_socket(int dolisten)
+ *	opens a local socket that crontab uses to poke cron
+ */
+int
+open_socket(int dolisten)
+{
+	struct sockaddr_un un;
+	int fd, tmp;
+	mode_t omask;
+
+	omask = umask(007);
+	fd = -1;
+
+	if (!concat3(un.sun_path, sizeof(un.sun_path),
+				SPOOL_DIR, "/", CRON_SOCK)) {
+		errno = ENOMEM;
+		goto die;
+	}
+
+	if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
+		goto die;
+
+	if ((tmp = fcntl(fd, F_GETFL, 0)) == -1 ||
+	    fcntl(fd, F_SETFL, tmp | O_NONBLOCK) == -1 ||
+	    fcntl(fd, F_SETFD, 1) == -1)
+		goto die;
+
+	un.sun_len = strlen(un.sun_path);
+	un.sun_family = PF_LOCAL;
+
+	if (dolisten) {
+		unlink(un.sun_path);
+		if (bind(fd, (struct sockaddr *)&un, sizeof(un)) == -1 ||
+		    listen(fd, SOMAXCONN) == -1 ||
+		    chmod(un.sun_path, 0660) == -1 ||
+		    chown(un.sun_path, ROOT_UID, cron_gid) == -1)
+			goto die;
+	} else if (connect(fd, (struct sockaddr *)&un, sizeof(un)) == -1)
+		goto die;
+
+	(void) umask(omask);
+	return fd;
+die:
+	{
+		int serr = errno;
+
+		if (fd != -1)
+			close(fd);
+		(void) umask(omask);
+		errno = serr;
+	}
+	return -1;
+}
+
+
+off_t
+getmaxtabsize()
+{
+	static off_t	tabsize;
+	char		fn[] = CRONDIR "/" MAXTABSIZE_FILE;
+	char		buf[64];
+	FILE		*fp;
+	struct updtab	*up;
+	char		*ep;
+
+	if (need_update(fn, &up)) {
+		tabsize = MAXTABSIZE_DEFAULT;
+		if ((fp = fopen(fn, "r")) != NULL) {
+			if (fgets(buf, sizeof(buf), fp) != NULL) {
+				tabsize = strtoll(buf, &ep, 10);
+				switch (tolower(*ep)) {
+				case 't': tabsize *= 1024;
+				case 'g': tabsize *= 1024;
+				case 'm': tabsize *= 1024;
+				case 'k': tabsize *= 1024;
+				case '\n':
+				case '\0':
+					  break;
+				default:
+					  tabsize = MAXTABSIZE_DEFAULT;
+					  break;
+				}
+			}
+			fclose(fp);
+		}
+		done_update(fn, up);
+	}
+	return tabsize;
+}
+
+
+
+int
+need_update(fn, newu)
+	const char *fn;
+	struct updtab **newu;
+{
+	struct updtab *u;
+	struct stat sb;
+	int canstat;
+
+	Debug(DMISC, ("update %s? ", fn));
+	*newu = NULL;
+	SLIST_FOREACH(u, &uphead, up_link) {
+		if (strcmp(u->up_name, fn) != 0)
+			continue;
+
+		*newu = u;
+		canstat = (stat(fn, &sb) == 0);
+		if (canstat != u->up_canstat) {
+			Debug(DMISC, ("yes, stat state changed"));
+			return 1;
+		} else if (canstat && (sb.st_size != u->up_stat.st_size ||
+		    timespeccmp(&sb.st_mtimespec, &u->up_stat.st_mtimespec, -)
+		    != 0)) {
+			Debug(DMISC, ("yes!\n"));
+			return 1;
+		}
+		Debug(DMISC, ("no\n"));
+		return 0;
+	}
+	Debug(DMISC, ("yes, and there's no entry\n"));
+	return 1;
+}
+
+
+void
+done_update(fn, u)
+	const char *fn;
+	struct updtab *u;
+{
+
+	Debug(DMISC, ("finished update on %s (cookie %p)\n", fn, u))
+	if (u == NULL) {
+		if ((u = malloc(sizeof(struct updtab))) == NULL)
+			return;
+		if ((u->up_name = strdup(fn)) == NULL)
+			return;
+		u->up_canstat = 1;
+		SLIST_INSERT_HEAD(&uphead, u, up_link);
+	}
+
+	u->up_canstat = (stat(fn, &u->up_stat) == 0);
+	if (!u->up_canstat) {
+		u->up_canstat = 0;
+		u->up_stat.st_size = 0;
+		timespecclear(&u->up_stat.st_mtimespec);
+	}
+}
+
Index: pathnames.h
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/pathnames.h,v
retrieving revision 1.5
diff -u -r1.5 pathnames.h
--- pathnames.h	1999/04/09 02:47:03	1.5
+++ pathnames.h	2002/06/12 13:22:45
@@ -40,8 +40,16 @@
 			 * crontab file and reload those whose modtimes are
 			 * newer than they were last time around (or which
 			 * didn't exist last time around...)
+			 * File names in this directory beginning with a dot
+			 * are ignored.
 			 */
 #define SPOOL_DIR	"tabs"
+
+			/* CRON_SOCK is the file under SPOOL_DIR of the
+			 * local socket that is used by crontab to tell
+			 * cron that it has made a modification.
+			 */
+#define	CRON_SOCK	".sock"

 			/* File containing maximum crontab size, in bytes. */
 #define MAXTABSIZE_FILE	"maxtabsize"
Index: popen.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/popen.c,v
retrieving revision 1.6
diff -u -r1.6 popen.c
--- popen.c	1998/01/31 15:07:14	1.6
+++ popen.c	2002/06/12 13:22:45
@@ -33,6 +33,7 @@

 #include "cron.h"
 #include <signal.h>
+#include <errno.h>

 /*
  * Special version of popen which avoids call to shell.  This insures noone
@@ -70,33 +71,40 @@
 	}
 	if (pipe(pdes) < 0)
 		return(NULL);
+	else if (pdes[0] >= fds || pdes[1] >= fds) {
+		errno = EBADF; goto die;
+	}

 	/* break up string into pieces */
 	for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL)
 		if (!(argv[argc++] = strtok(cp, " \t\n")))
 			break;

+	if (argc >= MAX_ARGS) {
+		errno = ENOMEM; goto die;
+	}
+
 	iop = NULL;
 	switch(pid = vfork()) {
 	case -1:			/* error */
-		(void)close(pdes[0]);
-		(void)close(pdes[1]);
-		goto pfree;
+		goto die;
 		/* NOTREACHED */
 	case 0:				/* child */
 		if (*type == 'r') {
 			if (pdes[1] != 1) {
 				dup2(pdes[1], 1);
-				dup2(pdes[1], 2);	/* stderr, too! */
 				(void)close(pdes[1]);
 			}
-			(void)close(pdes[0]);
+			dup2(1, 2);			/* stderr, too! */
+			if (pdes[0] > 2)
+				(void)close(pdes[0]);
 		} else {
 			if (pdes[0] != 0) {
 				dup2(pdes[0], 0);
 				(void)close(pdes[0]);
 			}
-			(void)close(pdes[1]);
+			if (pdes[1] > 2)
+				(void)close(pdes[1]);
 		}
 		execvp(argv[0], argv);
 		_exit(1);
@@ -109,11 +117,24 @@
 		iop = fdopen(pdes[1], type);
 		(void)close(pdes[0]);
 	}
+
 	pids[fileno(iop)] = pid;

-pfree:
 	return(iop);
+
+die:
+	{
+		int serr = errno;
+
+		if (iop != NULL)
+			fclose(iop);
+		close(pdes[0]);
+		close(pdes[1]);
+		errno = serr;
+	}
+	return(NULL);
 }
+

 int
 cron_pclose(iop)
Index: user.c
===================================================================
RCS file: /cvsroot/basesrc/usr.sbin/cron/user.c,v
retrieving revision 1.3
diff -u -r1.3 user.c
--- user.c	1998/01/31 14:40:45	1.3
+++ user.c	2002/06/12 13:22:45
@@ -68,16 +68,25 @@

 	Debug(DPARS, ("load_user()\n"))

+	envp = NULL;
+
 	/* file is open.  build user entry, then read the crontab file.
 	 */
-	u = (user *) malloc(sizeof(user));
-	u->name = strdup(name);
+	if ((u = malloc(sizeof(user))) == NULL)
+		goto done;
 	u->crontab = NULL;
+	if ((u->name = strdup(name)) == NULL) {
+		free_user(u); u = NULL;
+		goto done;
+	}

 	/* 
 	 * init environment.  this will be copied/augmented for each entry.
 	 */
-	envp = env_init();
+	if ((envp = env_init()) == NULL) {
+		free_user(u); u = NULL;
+		goto done;
+	}

 	/*
 	 * load the crontab
@@ -96,7 +105,11 @@
 			}
 			break;
 		case TRUE:
-			envp = env_set(envp, envstr);
+			if (!env_set(&envp, envstr)) {
+				free_user(u);
+				u = NULL;
+				goto done;
+			}
 			break;
 		}
 	}

>Release-Note:
>Audit-Trail:
>Unformatted:

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.39 2013/11/01 18:47:49 spz Exp $
$NetBSD: gnats_config.sh,v 1.8 2006/05/07 09:23:38 tsutsui Exp $
Copyright © 1994-2007 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.