Subject: Vixie cron (finally) arrives in 2.11BSD (#422 - 3 of 3)
Index:	usr.sbin/cron 2.11BSD

Description:
	The cron(8) program only allows for a single system wide
	crontab file.  Individual users can not maintain their own
	crontab files.

Repeat-By:
	Observation for many years ;)

Fix:
	This is a 3 part update.  This (#422) is part 3 of 3 and contains
	the first half of the new cron sources.  The instructions are in
	part 1.

	As always this and previous updates to 2.11BSD are available via
	anonymous FTP to either FTP.IIPO.GTEGSC.COM or MOE.2BSD.COM in the
	directory /pub/2.11BSD.
-----------------------------cut here-----------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	cron/cron.8
#	cron/cron.c
#	cron/cron.h
#	cron/crontab.1
#	cron/crontab.5
#	cron/crontab.c
#	cron/database.c
#	cron/do_command.c
#	cron/entry.c
#	cron/env.c
#	cron/externs.h
#	cron/job.c
#	cron/misc.c
#	cron/pathnames.h
#	cron/popen.c
#	cron/putman.sh
#	cron/user.c
# This archive created: Tue Jul 20 20:08:44 1999
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'cron/cron.8'
then
	echo shar: "will not over-write existing file 'cron/cron.8'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/cron.8'
Y.\"/* Copyright 1988,1990,1993 by Paul Vixie
Y.\" * All rights reserved
Y.\" *
Y.\" * Distribute freely, except: don't remove my name from the source or
Y.\" * documentation (don't take credit for my work), mark your changes (don't
Y.\" * get me blamed for your possible bugs), don't alter or remove this
Y.\" * notice.  May be sold if buildable source is provided to buyer.  No
Y.\" * warrantee of any kind, express or implied, is included with this
Y.\" * software; use at your own risk, responsibility for damages (if any) to
Y.\" * anyone resulting from the use of this software rests entirely with the
Y.\" * user.
Y.\" *
Y.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y.\" * I'll try to keep a version up to date.  I can be reached as follows:
Y.\" * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y.\" */
Y.\" 
Y.\" $Id: cron.8,v 2.2 1993/12/28 08:34:43 vixie Exp $
Y.\" 
Y.TH CRON 8 "20 December 1993"
Y.UC 4
Y.SH NAME
Ycron \- daemon to execute scheduled commands (Vixie Cron)
Y.SH SYNOPSIS
Ycron
Y.SH DESCRIPTION
Y.I Cron
Yshould be started from /etc/rc or /etc/rc.local.  It will return immediately,
Yso you don't need to start it with '&'.
Y.PP
Y.I Cron
Ysearches /var/cron/tabs for crontab files which are named after accounts in
Y/etc/passwd; crontabs found are loaded into memory.
Y.I Cron
Yalso searches for /etc/crontab which is in a different format (see
Y.IR crontab(5)).
Y.I Cron
Ythen wakes up every minute, examining all stored crontabs, checking each
Ycommand to see if it should be run in the current minute.  When executing
Ycommands, any output is mailed to the owner of the crontab (or to the user
Ynamed in the MAILTO environment variable in the crontab, if such exists).
Y.PP
YAdditionally,
Y.I cron
Ychecks each minute to see if its spool directory's modtime (or the modtime
Yon
Y.IR /etc/crontab)
Yhas changed, and if it has,
Y.I cron
Ywill then examine the modtime on all crontabs and reload those which have
Ychanged.  Thus
Y.I cron
Yneed not be restarted whenever a crontab file is modified.  Note that the
Y.IR Crontab (1)
Ycommand updates the modtime of the spool directory whenever it changes a
Ycrontab.
Y.SH "SEE ALSO"
Ycrontab(1), crontab(5)
Y.SH AUTHOR
Y.nf
YPaul Vixie <paul@vix.com>
SHAR_EOF
chmod 644 'cron/cron.8'
fi
if test -f 'cron/cron.c'
then
	echo shar: "will not over-write existing file 'cron/cron.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/cron.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: cron.c,v 2.11 1994/01/15 20:43:43 vixie Exp $";
Y#endif
Y
Y
Y#define	MAIN_PROGRAM
Y
Y
Y#include "cron.h"
Y#include <sys/signal.h>
Y#include <sys/time.h>
Y
Ystatic	void	usage __P((void)),
Y		run_reboot_jobs __P((cron_db *)),
Y		cron_tick __P((cron_db *)),
Y		cron_sync __P((void)),
Y		cron_sleep __P((void)),
Y		sigchld_handler __P((int)),
Y		sighup_handler __P((int)),
Y		parse_args __P((int c, char *v[]));
Y
Y
Ystatic void
Yusage() {
Y	fprintf(stderr, "usage:  %s [-x debugflag[,...]]\n", ProgramName);
Y	exit(ERROR_EXIT);
Y}
Y
Y
Yint
Ymain(argc, argv)
Y	int	argc;
Y	char	*argv[];
Y{
Y	cron_db	database;
Y
Y	ProgramName = argv[0];
Y
Y	setlinebuf(stdout);
Y	setlinebuf(stderr);
Y
Y	parse_args(argc, argv);
Y
Y	(void) signal(SIGCHLD, sigchld_handler);
Y	(void) signal(SIGHUP, sighup_handler);
Y
Y	acquire_daemonlock(0);
Y	set_cron_uid();
Y	set_cron_cwd();
Y
Y	setenv("PATH", _PATH_DEFPATH, 1);
Y
Y	/* if there are no debug flags turned on, fork as a daemon should.
Y	 */
Y# if DEBUGGING
Y	if (DebugFlags) {
Y# else
Y	if (0) {
Y# endif
Y		(void) fprintf(stderr, "[%d] cron started\n", getpid());
Y	} else {
Y		switch (fork()) {
Y		case -1:
Y			log_it("CRON",getpid(),"DEATH","can't fork");
Y			exit(0);
Y			break;
Y		case 0:
Y			/* child process */
Y			log_it("CRON",getpid(),"STARTUP","fork ok");
Y			(void) setsid();
Y			break;
Y		default:
Y			/* parent process should just die */
Y			_exit(0);
Y		}
Y	}
Y
Y	acquire_daemonlock(0);
Y	database.head = NULL;
Y	database.tail = NULL;
Y	database.mtime = (time_t) 0;
Y	load_database(&database);
Y	run_reboot_jobs(&database);
Y	cron_sync();
Y	while (TRUE) {
Y# if DEBUGGING
Y		if (!(DebugFlags & DTEST))
Y# endif /*DEBUGGING*/
Y			cron_sleep();
Y
Y		load_database(&database);
Y
Y		/* do this iteration
Y		 */
Y		cron_tick(&database);
Y
Y		/* sleep 1 minute
Y		 */
Y		TargetTime += 60;
Y	}
Y}
Y
Y
Ystatic void
Yrun_reboot_jobs(db)
Y	cron_db *db;
Y{
Y	register user		*u;
Y	register entry		*e;
Y
Y	for (u = db->head;  u != NULL;  u = u->next) {
Y		for (e = u->crontab;  e != NULL;  e = e->next) {
Y			if (e->flags & WHEN_REBOOT) {
Y				job_add(e, u);
Y			}
Y		}
Y	}
Y	(void) job_runqueue();
Y}
Y
Y
Ystatic void
Ycron_tick(db)
Y	cron_db	*db;
Y{
Y 	register struct tm	*tm = localtime(&TargetTime);
Y	register int		minute, hour, dom, month, dow;
Y	register user		*u;
Y	register entry		*e;
Y
Y	/* make 0-based values out of these so we can use them as indicies
Y	 */
Y	minute = tm->tm_min -FIRST_MINUTE;
Y	hour = tm->tm_hour -FIRST_HOUR;
Y	dom = tm->tm_mday -FIRST_DOM;
Y	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
Y	dow = tm->tm_wday -FIRST_DOW;
Y
Y	Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n",
Y		getpid(), minute, hour, dom, month, dow))
Y
Y	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
Y	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
Y	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
Y	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
Y	 * like many bizarre things, it's the standard.
Y	 */
Y	for (u = db->head;  u != NULL;  u = u->next) {
Y		for (e = u->crontab;  e != NULL;  e = e->next) {
Y			Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
Y					  env_get("LOGNAME", e->envp),
Y					  e->uid, e->gid, e->cmd))
Y			if (bit_test(e->minute, minute)
Y			 && bit_test(e->hour, hour)
Y			 && bit_test(e->month, month)
Y			 && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
Y			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
Y			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
Y			    )
Y			   ) {
Y				job_add(e, u);
Y			}
Y		}
Y	}
Y}
Y
Y
Y/* the task here is to figure out how long it's going to be until :00 of the
Y * following minute and initialize TargetTime to this value.  TargetTime
Y * will subsequently slide 60 seconds at a time, with correction applied
Y * implicitly in cron_sleep().  it would be nice to let cron execute in
Y * the "current minute" before going to sleep, but by restarting cron you
Y * could then get it to execute a given minute's jobs more than once.
Y * instead we have the chance of missing a minute's jobs completely, but
Y * that's something sysadmin's know to expect what with crashing computers..
Y */
Ystatic void
Ycron_sync() {
Y 	register struct tm	*tm;
Y
Y	TargetTime = time((time_t*)0);
Y	tm = localtime(&TargetTime);
Y	TargetTime += (60 - tm->tm_sec);
Y}
Y
Y
Ystatic void
Ycron_sleep() {
Y	register int	seconds_to_wait;
Y
Y	do {
Y		seconds_to_wait = (int) (TargetTime - time((time_t*)0));
Y		Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
Y			getpid(), TargetTime, seconds_to_wait))
Y
Y		/* if we intend to sleep, this means that it's finally
Y		 * time to empty the job queue (execute it).
Y		 *
Y		 * if we run any jobs, we'll probably screw up our timing,
Y		 * so go recompute.
Y		 *
Y		 * note that we depend here on the left-to-right nature
Y		 * of &&, and the short-circuiting.
Y		 */
Y	} while (seconds_to_wait > 0 && job_runqueue());
Y
Y	while (seconds_to_wait > 0) {
Y		Debug(DSCH, ("[%d] sleeping for %d seconds\n",
Y			getpid(), seconds_to_wait))
Y		seconds_to_wait = (int) sleep((unsigned int) seconds_to_wait);
Y	}
Y}
Y
Y
Ystatic void
Ysigchld_handler(x) {
Y	WAIT_T		waiter;
Y	PID_T		pid;
Y
Y	for (;;) {
Y		pid = waitpid(-1, &waiter, WNOHANG);
Y		switch (pid) {
Y		case -1:
Y			Debug(DPROC,
Y				("[%d] sigchld...no children\n", getpid()))
Y			return;
Y		case 0:
Y			Debug(DPROC,
Y				("[%d] sigchld...no dead kids\n", getpid()))
Y			return;
Y		default:
Y			Debug(DPROC,
Y				("[%d] sigchld...pid #%d died, stat=%d\n",
Y				getpid(), pid, WEXITSTATUS(waiter)))
Y		}
Y	}
Y}
Y
Ystatic void
Ysighup_handler(x) {
Y	log_close();
Y}
Y
Y
Ystatic void
Yparse_args(argc, argv)
Y	int	argc;
Y	char	*argv[];
Y{
Y	int	argch;
Y
Y	while (EOF != (argch = getopt(argc, argv, "x:"))) {
Y		switch (argch) {
Y		default:
Y			usage();
Y		case 'x':
Y			if (!set_debug_flags(optarg))
Y				usage();
Y			break;
Y		}
Y	}
Y}
SHAR_EOF
chmod 644 'cron/cron.c'
fi
if test -f 'cron/cron.h'
then
	echo shar: "will not over-write existing file 'cron/cron.h'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/cron.h'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y/* cron.h - header for vixie's cron
Y *
Y * $Id: cron.h,v 2.10 1994/01/15 20:43:43 vixie Exp $
Y *
Y * vix 14nov88 [rest of log is in RCS]
Y * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
Y * vix 30dec86 [written]
Y */
Y
Y/* reorder these #include's at your peril */
Y
Y#include <sys/types.h>
Y#include <sys/param.h>
Y#include "compat.h"
Y
Y#include <stdio.h>
Y#include <ctype.h>
Y#include <bitstring.h>
Y#include <pwd.h>
Y#include <sys/wait.h>
Y
Y#include "pathnames.h"
Y#include "config.h"
Y#include "externs.h"
Y
Y	/* these are really immutable, and are
Y	 *   defined for symbolic convenience only
Y	 * TRUE, FALSE, and ERR must be distinct
Y	 * ERR must be < OK.
Y	 */
Y#define TRUE		1
Y#define FALSE		0
Y	/* system calls return this on success */
Y#define OK		0
Y	/*   or this on error */
Y#define ERR		(-1)
Y
Y	/* turn this on to get '-x' code */
Y#ifndef DEBUGGING
Y#define DEBUGGING	FALSE
Y#endif
Y
Y#define READ_PIPE	0	/* which end of a pipe pair do you read? */
Y#define WRITE_PIPE	1	/*   or write to? */
Y#define STDIN		0	/* what is stdin's file descriptor? */
Y#define STDOUT		1	/*   stdout's? */
Y#define STDERR		2	/*   stderr's? */
Y#define ERROR_EXIT	1	/* exit() with this will scare the shell */
Y#define	OK_EXIT		0	/* exit() with this is considered 'normal' */
Y#define	MAX_FNAME	100	/* max length of internally generated fn */
Y#define	MAX_COMMAND	1000	/* max length of internally generated cmd */
Y#define	MAX_ENVSTR	1000	/* max length of envvar=value\0 strings */
Y#define	MAX_TEMPSTR	100	/* obvious */
Y#define	MAX_UNAME	20	/* max length of username, should be overkill */
Y#define	ROOT_UID	0	/* don't change this, it really must be root */
Y#define	ROOT_USER	"root"	/* ditto */
Y
Y				/* NOTE: these correspond to DebugFlagNames,
Y				 *	defined below.
Y				 */
Y#define	DEXT		0x0001	/* extend flag for other debug masks */
Y#define	DSCH		0x0002	/* scheduling debug mask */
Y#define	DPROC		0x0004	/* process control debug mask */
Y#define	DPARS		0x0008	/* parsing debug mask */
Y#define	DLOAD		0x0010	/* database loading debug mask */
Y#define	DMISC		0x0020	/* misc debug mask */
Y#define	DTEST		0x0040	/* test mode: don't execute any commands */
Y#define	DBIT		0x0080	/* bit twiddling shown (long) */
Y
Y#define	CRON_TAB(u)	"%s/%s", SPOOL_DIR, u
Y#define	REG		register
Y#define	PPC_NULL	((char **)NULL)
Y
Y#ifndef MAXHOSTNAMELEN
Y#define MAXHOSTNAMELEN 64
Y#endif
Y
Y#define	Skip_Blanks(c, f) \
Y			while (c == '\t' || c == ' ') \
Y				c = get_char(f);
Y
Y#define	Skip_Nonblanks(c, f) \
Y			while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
Y				c = get_char(f);
Y
Y#define	Skip_Line(c, f) \
Y			do {c = get_char(f);} while (c != '\n' && c != EOF);
Y
Y#if DEBUGGING
Y# define Debug(mask, message) \
Y			if ( (DebugFlags & (mask) ) == (mask) ) \
Y				printf message;
Y#else /* !DEBUGGING */
Y# define Debug(mask, message) \
Y			;
Y#endif /* DEBUGGING */
Y
Y#define	Set_LineNum(ln)	{Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
Y			 LineNumber = ln; \
Y			}
Y
Y#define	FIRST_MINUTE	0
Y#define	LAST_MINUTE	59
Y#define	MINUTE_COUNT	(LAST_MINUTE - FIRST_MINUTE + 1)
Y
Y#define	FIRST_HOUR	0
Y#define	LAST_HOUR	23
Y#define	HOUR_COUNT	(LAST_HOUR - FIRST_HOUR + 1)
Y
Y#define	FIRST_DOM	1
Y#define	LAST_DOM	31
Y#define	DOM_COUNT	(LAST_DOM - FIRST_DOM + 1)
Y
Y#define	FIRST_MONTH	1
Y#define	LAST_MONTH	12
Y#define	MONTH_COUNT	(LAST_MONTH - FIRST_MONTH + 1)
Y
Y/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
Y#define	FIRST_DOW	0
Y#define	LAST_DOW	7
Y#define	DOW_COUNT	(LAST_DOW - FIRST_DOW + 1)
Y
Y			/* each user's crontab will be held as a list of
Y			 * the following structure.
Y			 *
Y			 * These are the cron commands.
Y			 */
Y
Ytypedef	struct _entry {
Y	struct _entry	*next;
Y	uid_t		uid;	
Y	gid_t		gid;
Y	char		**envp;
Y	char		*cmd;
Y	bitstr_t	bit_decl(minute, MINUTE_COUNT);
Y	bitstr_t	bit_decl(hour,   HOUR_COUNT);
Y	bitstr_t	bit_decl(dom,    DOM_COUNT);
Y	bitstr_t	bit_decl(month,  MONTH_COUNT);
Y	bitstr_t	bit_decl(dow,    DOW_COUNT);
Y	int		flags;
Y#define	DOM_STAR	0x01
Y#define	DOW_STAR	0x02
Y#define	WHEN_REBOOT	0x04
Y} entry;
Y
Y			/* the crontab database will be a list of the
Y			 * following structure, one element per user
Y			 * plus one for the system.
Y			 *
Y			 * These are the crontabs.
Y			 */
Y
Ytypedef	struct _user {
Y	struct _user	*next, *prev;	/* links */
Y	char		*name;
Y	time_t		mtime;		/* last modtime of crontab */
Y	entry		*crontab;	/* this person's crontab */
Y} user;
Y
Ytypedef	struct _cron_db {
Y	user		*head, *tail;	/* links */
Y	time_t		mtime;		/* last modtime on spooldir */
Y} cron_db;
Y
Y
Yvoid		set_cron_uid __P((void)),
Y		set_cron_cwd __P((void)),
Y		load_database __P((cron_db *)),
Y		open_logfile __P((void)),
Y		sigpipe_func __P((void)),
Y		job_add __P((entry *, user *)),
Y		do_command __P((entry *, user *)),
Y		link_user __P((cron_db *, user *)),
Y		unlink_user __P((cron_db *, user *)),
Y		free_user __P((user *)),
Y		env_free __P((char **)),
Y		unget_char __P((int, FILE *)),
Y		free_entry __P((entry *)),
Y		acquire_daemonlock __P((int)),
Y		skip_comments __P((FILE *)),
Y		log_it __P((char *, int, char *, char *)),
Y		log_close __P((void));
Y
Yint		job_runqueue __P((void)),
Y		set_debug_flags __P((char *)),
Y		get_char __P((FILE *)),
Y		get_string __P((char *, int, FILE *, char *)),
Y		swap_uids __P((void)),
Y		load_env __P((char *, FILE *)),
Y		cron_pclose __P((FILE *)),
Y		strcmp_until __P((char *, char *, int)),
Y		allowed __P((char *)),
Y		strdtb __P((char *));
Y
Ychar		*env_get __P((char *, char **)),
Y		*arpadate __P((time_t *)),
Y		*mkprints __P((unsigned char *, unsigned int)),
Y		*first_word __P((char *, char *)),
Y		**env_init __P((void)),
Y		**env_copy __P((char **)),
Y		**env_set __P((char **, char *));
Y
Yuser		*load_user __P((int, struct passwd *, char *)),
Y		*find_user __P((cron_db *, char *));
Y
Yentry		*load_entry __P((FILE *, void (*)(),
Y				 struct passwd *, char **));
Y
YFILE		*cron_popen __P((char *, char *));
Y
Y
Y				/* in the C tradition, we only create
Y				 * variables for the main program, just
Y				 * extern them elsewhere.
Y				 */
Y
Y#ifdef MAIN_PROGRAM
Y# if !defined(LINT) && !defined(lint)
Ychar	*copyright[] = {
Y		"@(#) Copyright 1988,1989,1990,1993,1994 by Paul Vixie",
Y		"@(#) All rights reserved"
Y	};
Y# endif
Y
Ychar	*MonthNames[] = {
Y		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
Y		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
Y		NULL
Y	};
Y
Ychar	*DowNames[] = {
Y		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
Y		NULL
Y	};
Y
Ychar	*ProgramName;
Yint	LineNumber;
Ytime_t	TargetTime;
Y
Y# if DEBUGGING
Yint	DebugFlags;
Ychar	*DebugFlagNames[] = {	/* sync with #defines */
Y		"ext", "sch", "proc", "pars", "load", "misc", "test", "bit",
Y		NULL		/* NULL must be last element */
Y	};
Y# endif /* DEBUGGING */
Y#else /*MAIN_PROGRAM*/
Yextern	char	*copyright[],
Y		*MonthNames[],
Y		*DowNames[],
Y		*ProgramName;
Yextern	int	LineNumber;
Yextern	time_t	TargetTime;
Y# if DEBUGGING
Yextern	int	DebugFlags;
Yextern	char	*DebugFlagNames[];
Y# endif /* DEBUGGING */
Y#endif /*MAIN_PROGRAM*/
SHAR_EOF
chmod 644 'cron/cron.h'
fi
if test -f 'cron/crontab.1'
then
	echo shar: "will not over-write existing file 'cron/crontab.1'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/crontab.1'
Y.\"/* Copyright 1988,1990,1993 by Paul Vixie
Y.\" * All rights reserved
Y.\" *
Y.\" * Distribute freely, except: don't remove my name from the source or
Y.\" * documentation (don't take credit for my work), mark your changes (don't
Y.\" * get me blamed for your possible bugs), don't alter or remove this
Y.\" * notice.  May be sold if buildable source is provided to buyer.  No
Y.\" * warrantee of any kind, express or implied, is included with this
Y.\" * software; use at your own risk, responsibility for damages (if any) to
Y.\" * anyone resulting from the use of this software rests entirely with the
Y.\" * user.
Y.\" *
Y.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y.\" * I'll try to keep a version up to date.  I can be reached as follows:
Y.\" * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y.\" */
Y.\"
Y.\" $Id: crontab.1,v 2.4 1993/12/31 10:47:33 vixie Exp $
Y.\"
Y.TH CRONTAB 1 "29 December 1993"
Y.UC 4
Y.SH NAME
Ycrontab \- maintain crontab files for individual users (V3)
Y.SH SYNOPSIS
Ycrontab [ -u user ] file
Y.br
Ycrontab [ -u user ] { -l | -r | -e }
Y.SH DESCRIPTION
Y.I Crontab
Yis the program used to install, deinstall or list the tables
Yused to drive the
Y.IR cron (8)
Ydaemon in Vixie Cron.  Each user can have their own crontab, and though
Ythese are files in /var, they are not intended to be edited directly.
Y.PP
YIf the
Y.I allow
Yfile exists, then you must be listed therein in order to be allowed to use
Ythis command.  If the
Y.I allow
Yfile does not exist but the
Y.I deny
Yfile does exist, then you must \fBnot\fR be listed in the
Y.I deny
Yfile in order to use this command.  If neither of these files exists, then
Ydepending on site-dependent configuration parameters, only the super user
Ywill be allowed to use this command, or all users will be able to use this
Ycommand.
Y.PP
YIf the
Y.I -u
Yoption is given, it specifies the name of the user whose crontab is to be
Ytweaked.  If this option is not given,
Y.I crontab
Yexamines "your" crontab, i.e., the crontab of the person executing the
Ycommand.  Note that
Y.IR su (8)
Ycan confuse
Y.I crontab
Yand that if you are running inside of
Y.IR su (8)
Yyou should always use the
Y.I -u
Yoption for safety's sake.
Y.PP
YThe first form of this command is used to install a new crontab from some
Ynamed file or standard input if the pseudo-filename ``-'' is given.
Y.PP
YThe
Y.I -l
Yoption causes the current crontab to be displayed on standard output.
Y.PP
YThe
Y.I -r
Yoption causes the current crontab to be removed.
Y.PP
YThe
Y.I -e
Yoption is used to edit the current crontab using the editor specified by
Ythe \s-1VISUAL\s+1 or \s-1EDITOR\s+1 environment variables.  After you exit
Yfrom the editor, the modified crontab will be installed automatically.
Y.SH "SEE ALSO"
Ycrontab(5), cron(8)
Y.SH FILES
Y.nf
Y/var/cron/allow
Y/var/cron/deny
Y.fi
Y.SH STANDARDS
YThe
Y.I crontab
Ycommand conforms to IEEE Std1003.2-1992 (``POSIX'').  This new command syntax
Ydiffers from previous versions of Vixie Cron, as well as from the classic
YSVR3 syntax.
Y.SH DIAGNOSTICS
YA fairly informative usage message appears if you run it with a bad command
Yline.
Y.SH AUTHOR
Y.nf
YPaul Vixie <paul@vix.com>
SHAR_EOF
chmod 644 'cron/crontab.1'
fi
if test -f 'cron/crontab.5'
then
	echo shar: "will not over-write existing file 'cron/crontab.5'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/crontab.5'
Y.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y.\" * All rights reserved
Y.\" *
Y.\" * Distribute freely, except: don't remove my name from the source or
Y.\" * documentation (don't take credit for my work), mark your changes (don't
Y.\" * get me blamed for your possible bugs), don't alter or remove this
Y.\" * notice.  May be sold if buildable source is provided to buyer.  No
Y.\" * warrantee of any kind, express or implied, is included with this
Y.\" * software; use at your own risk, responsibility for damages (if any) to
Y.\" * anyone resulting from the use of this software rests entirely with the
Y.\" * user.
Y.\" *
Y.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y.\" * I'll try to keep a version up to date.  I can be reached as follows:
Y.\" * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y.\" */
Y.\"
Y.\" $Id: crontab.5,v 2.4 1994/01/15 20:43:43 vixie Exp $
Y.\" 
Y.TH CRONTAB 5 "24 January 1994"
Y.UC 4
Y.SH NAME
Ycrontab \- tables for driving cron
Y.SH DESCRIPTION
YA
Y.I crontab
Yfile contains instructions to the
Y.IR cron (8)
Ydaemon of the general form: ``run this command at this time on this date''.
YEach user has their own crontab, and commands in any given crontab will be
Yexecuted as the user who owns the crontab.  Uucp and News will usually have
Ytheir own crontabs, eliminating the need for explicitly running
Y.IR su (1)
Yas part of a cron command.
Y.PP
YBlank lines and leading spaces and tabs are ignored.  Lines whose first
Ynon-space character is a pound-sign (#) are comments, and are ignored.
YNote that comments are not allowed on the same line as cron commands, since
Ythey will be taken to be part of the command.  Similarly, comments are not
Yallowed on the same line as environment variable settings.
Y.PP
YAn active line in a crontab will be either an environment setting or a cron
Ycommand.  An environment setting is of the form,
Y.PP
Y    name = value
Y.PP
Ywhere the spaces around the equal-sign (=) are optional, and any subsequent
Ynon-leading spaces in
Y.I value
Ywill be part of the value assigned to
Y.IR name .
YThe
Y.I value
Ystring may be placed in quotes (single or double, but matching) to preserve
Yleading or trailing blanks.
Y.PP
YSeveral environment variables are set up
Yautomatically by the
Y.IR cron (8)
Ydaemon.
YSHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd 
Yline of the crontab's owner.
YHOME and SHELL may be overridden by settings in the crontab; LOGNAME may not.
Y.PP
Y(Another note: the LOGNAME variable is sometimes called USER on BSD systems...
Yon these systems, USER will be set also.)
Y.PP
YIn addition to LOGNAME, HOME, and SHELL,
Y.IR cron (8)
Ywill look at MAILTO if it has any reason to send mail as a result of running
Ycommands in ``this'' crontab.  If MAILTO is defined (and non-empty), mail is
Ysent to the user so named.  If MAILTO is defined but empty (MAILTO=""), no
Ymail will be sent.  Otherwise mail is sent to the owner of the crontab.  This
Yoption is useful if you decide on /bin/mail instead of /usr/lib/sendmail as
Yyour mailer when you install cron -- /bin/mail doesn't do aliasing, and UUCP
Yusually doesn't read its mail.
Y.PP
YThe format of a cron command is very much the V7 standard, with a number of
Yupward-compatible extensions.  Each line has five time and date fields,
Yfollowed by a user name if this is the system crontab file,
Yfollowed by a command.  Commands are executed by
Y.IR cron (8)
Ywhen the minute, hour, and month of year fields match the current time,
Y.I and
Ywhen at least one of the two day fields (day of month, or day of week)
Ymatch the current time (see ``Note'' below).
Y.IR cron (8)
Yexamines cron entries once every minute.
YThe time and date fields are:
Y.IP
Y.ta 1.5i
Yfield	allowed values
Y.br
Y-----	--------------
Y.br
Yminute	0-59
Y.br
Yhour	0-23
Y.br
Yday of month	0-31
Y.br
Ymonth	0-12 (or names, see below)
Y.br
Yday of week	0-7 (0 or 7 is Sun, or use names)
Y.br
Y.PP
YA field may be an asterisk (*), which always stands for ``first\-last''.
Y.PP
YRanges of numbers are allowed.  Ranges are two numbers separated
Ywith a hyphen.  The specified range is inclusive.  For example,
Y8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10
Yand 11.
Y.PP
YLists are allowed.  A list is a set of numbers (or ranges)
Yseparated by commas.  Examples: ``1,2,5,9'', ``0-4,8-12''.
Y.PP
YStep values can be used in conjunction with ranges.  Following
Ya range with ``/<number>'' specifies skips of the number's value
Ythrough the range.  For example, ``0-23/2'' can be used in the hours
Yfield to specify command execution every other hour (the alternative
Yin the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22'').  Steps are
Yalso permitted after an asterisk, so if you want to say ``every two
Yhours'', just use ``*/2''.
Y.PP
YNames can also be used for the ``month'' and ``day of week''
Yfields.  Use the first three letters of the particular
Yday or month (case doesn't matter).  Ranges or
Ylists of names are not allowed.
Y.PP
YThe ``sixth'' field (the rest of the line) specifies the command to be
Yrun.
YThe entire command portion of the line, up to a newline or %
Ycharacter, will be executed by /bin/sh or by the shell
Yspecified in the SHELL variable of the cronfile.
YPercent-signs (%) in the command, unless escaped with backslash
Y(\\), will be changed into newline characters, and all data
Yafter the first % will be sent to the command as standard
Yinput.
Y.PP
YNote: The day of a command's execution can be specified by two
Yfields \(em day of month, and day of week.  If both fields are
Yrestricted (ie, aren't *), the command will be run when
Y.I either
Yfield matches the current time.  For example,
Y.br
Y``30 4 1,15 * 5''
Ywould cause a command to be run at 4:30 am on the 1st and 15th of each
Ymonth, plus every Friday.
Y.SH EXAMPLE CRON FILE
Y.nf
Y
Y# use /bin/sh to run commands, no matter what /etc/passwd says
YSHELL=/bin/sh
Y# mail any output to `paul', no matter whose crontab this is
YMAILTO=paul
Y#
Y# run five minutes after midnight, every day
Y5 0 * * *       $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
Y# run at 2:15pm on the first of every month -- output mailed to paul
Y15 14 1 * *     $HOME/bin/monthly
Y# run at 10 pm on weekdays, annoy Joe
Y0 22 * * 1-5	mail -s "It's 10pm" joe%Joe,%%Where are your kids?%
Y23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday"
Y5 4 * * sun     echo "run at 5 after 4 every sunday"
Y.fi
Y.SH SEE ALSO
Ycron(8), crontab(1)
Y.SH EXTENSIONS
YWhen specifying day of week, both day 0 and day 7 will be considered Sunday.
YBSD and ATT seem to disagree about this.
Y.PP
YLists and ranges are allowed to co-exist in the same field.  "1-3,7-9" would
Ybe rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY.
Y.PP
YRanges can include "steps", so "1-9/2" is the same as "1,3,5,7,9".
Y.PP
YNames of months or days of the week can be specified by name.
Y.PP
YEnvironment variables can be set in the crontab.  In BSD or ATT, the
Yenvironment handed to child processes is basically the one from /etc/rc.
Y.PP
YCommand output is mailed to the crontab owner (BSD can't do this), can be
Ymailed to a person other than the crontab owner (SysV can't do this), or the
Yfeature can be turned off and no mail will be sent at all (SysV can't do this
Yeither).
Y.SH AUTHOR
Y.nf
YPaul Vixie <paul@vix.com>
SHAR_EOF
chmod 644 'cron/crontab.5'
fi
if test -f 'cron/crontab.c'
then
	echo shar: "will not over-write existing file 'cron/crontab.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/crontab.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $";
Y#endif
Y
Y/* crontab - install and manage per-user crontab files
Y * vix 02may87 [RCS has the rest of the log]
Y * vix 26jan87 [original]
Y */
Y
Y
Y#define	MAIN_PROGRAM
Y
Y
Y#include "cron.h"
Y#include <errno.h>
Y#include <fcntl.h>
Y#include <sys/file.h>
Y#include <sys/stat.h>
Y#include <sys/time.h>
Y
Y#define NHEADER_LINES 3
Y
Yenum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
Y
Y#if DEBUGGING
Ystatic char	*Options[] = { "???", "list", "delete", "edit", "replace" };
Y#endif
Y
Y
Ystatic	PID_T		Pid;
Ystatic	char		User[MAX_UNAME], RealUser[MAX_UNAME];
Ystatic	char		Filename[MAX_FNAME];
Ystatic	FILE		*NewCrontab;
Ystatic	int		CheckErrorCount;
Ystatic	enum opt_t	Option;
Ystatic	struct passwd	*pw;
Ystatic	void		list_cmd __P((void)),
Y			delete_cmd __P((void)),
Y			edit_cmd __P((void)),
Y			poke_daemon __P((void)),
Y			check_error __P((char *)),
Y			parse_args __P((int c, char *v[]));
Ystatic	int		replace_cmd __P((void));
Y
Y
Ystatic void
Yusage(msg)
Y	char *msg;
Y{
Y	fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg);
Y	fprintf(stderr, "usage:\t%s [-u user] file\n", ProgramName);
Y	fprintf(stderr, "\t%s [-u user] { -e | -l | -r }\n", ProgramName);
Y	fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n");
Y	fprintf(stderr, "\t-e\t(edit user's crontab)\n");
Y	fprintf(stderr, "\t-l\t(list user's crontab)\n");
Y	fprintf(stderr, "\t-r\t(delete user's crontab)\n");
Y	exit(ERROR_EXIT);
Y}
Y
Y
Yint
Ymain(argc, argv)
Y	int	argc;
Y	char	*argv[];
Y{
Y	int	exitstatus;
Y
Y	Pid = getpid();
Y	ProgramName = argv[0];
Y
Y	setlinebuf(stderr);
Y
Y	parse_args(argc, argv);		/* sets many globals, opens a file */
Y	set_cron_uid();
Y	set_cron_cwd();
Y	if (!allowed(User)) {
Y		fprintf(stderr,
Y			"You (%s) are not allowed to use this program (%s)\n",
Y			User, ProgramName);
Y		fprintf(stderr, "See crontab(1) for more information\n");
Y		log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
Y		exit(ERROR_EXIT);
Y	}
Y	exitstatus = OK_EXIT;
Y	switch (Option) {
Y	case opt_list:		list_cmd();
Y				break;
Y	case opt_delete:	delete_cmd();
Y				break;
Y	case opt_edit:		edit_cmd();
Y				break;
Y	case opt_replace:	if (replace_cmd() < 0)
Y					exitstatus = ERROR_EXIT;
Y				break;
Y	}
Y	exit(0);
Y	/*NOTREACHED*/
Y}
Y	
Y
Ystatic void
Yparse_args(argc, argv)
Y	int	argc;
Y	char	*argv[];
Y{
Y	int		argch;
Y
Y	if (!(pw = getpwuid(getuid()))) {
Y		fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
Y			ProgramName);
Y		fprintf(stderr, "bailing out.\n");
Y		exit(ERROR_EXIT);
Y	}
Y	strcpy(User, pw->pw_name);
Y	strcpy(RealUser, User);
Y	Filename[0] = '\0';
Y	Option = opt_unknown;
Y	while (EOF != (argch = getopt(argc, argv, "u:lerx:"))) {
Y		switch (argch) {
Y		case 'x':
Y			if (!set_debug_flags(optarg))
Y				usage("bad debug option");
Y			break;
Y		case 'u':
Y			if (getuid() != ROOT_UID)
Y			{
Y				fprintf(stderr,
Y					"must be privileged to use -u\n");
Y				exit(ERROR_EXIT);
Y			}
Y			if (!(pw = getpwnam(optarg)))
Y			{
Y				fprintf(stderr, "%s:  user `%s' unknown\n",
Y					ProgramName, optarg);
Y				exit(ERROR_EXIT);
Y			}
Y			(void) strcpy(User, optarg);
Y			break;
Y		case 'l':
Y			if (Option != opt_unknown)
Y				usage("only one operation permitted");
Y			Option = opt_list;
Y			break;
Y		case 'r':
Y			if (Option != opt_unknown)
Y				usage("only one operation permitted");
Y			Option = opt_delete;
Y			break;
Y		case 'e':
Y			if (Option != opt_unknown)
Y				usage("only one operation permitted");
Y			Option = opt_edit;
Y			break;
Y		default:
Y			usage("unrecognized option");
Y		}
Y	}
Y
Y	endpwent();
Y
Y	if (Option != opt_unknown) {
Y		if (argv[optind] != NULL) {
Y			usage("no arguments permitted after this option");
Y		}
Y	} else {
Y		if (argv[optind] != NULL) {
Y			Option = opt_replace;
Y			(void) strcpy (Filename, argv[optind]);
Y		} else {
Y			usage("file name must be specified for replace");
Y		}
Y	}
Y
Y	if (Option == opt_replace) {
Y		/* we have to open the file here because we're going to
Y		 * chdir(2) into /var/cron before we get around to
Y		 * reading the file.
Y		 */
Y		if (!strcmp(Filename, "-")) {
Y			NewCrontab = stdin;
Y		} else {
Y			/* relinquish the setuid status of the binary during
Y			 * the open, lest nonroot users read files they should
Y			 * not be able to read.  we can't use access() here
Y			 * since there's a race condition.  thanks go out to
Y			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
Y			 * the race.
Y			 */
Y
Y			if (swap_uids() < OK) {
Y				perror("swapping uids");
Y				exit(ERROR_EXIT);
Y			}
Y			if (!(NewCrontab = fopen(Filename, "r"))) {
Y				perror(Filename);
Y				exit(ERROR_EXIT);
Y			}
Y			if (swap_uids() < OK) {
Y				perror("swapping uids back");
Y				exit(ERROR_EXIT);
Y			}
Y		}
Y	}
Y
Y	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
Y		      User, Filename, Options[(int)Option]))
Y}
Y
Y
Ystatic void
Ylist_cmd() {
Y	char	n[MAX_FNAME];
Y	register FILE	*f;
Y	int	ch;
Y
Y	log_it(RealUser, Pid, "LIST", User);
Y	(void) sprintf(n, CRON_TAB(User));
Y	if (!(f = fopen(n, "r"))) {
Y		if (errno == ENOENT)
Y			fprintf(stderr, "no crontab for %s\n", User);
Y		else
Y			perror(n);
Y		exit(ERROR_EXIT);
Y	}
Y
Y	/* file is open. copy to stdout, close.
Y	 */
Y	Set_LineNum(1)
Y	while (EOF != (ch = get_char(f)))
Y		putchar(ch);
Y	fclose(f);
Y}
Y
Y
Ystatic void
Ydelete_cmd() {
Y	char	n[MAX_FNAME];
Y
Y	log_it(RealUser, Pid, "DELETE", User);
Y	(void) sprintf(n, CRON_TAB(User));
Y	if (unlink(n)) {
Y		if (errno == ENOENT)
Y			fprintf(stderr, "no crontab for %s\n", User);
Y		else
Y			perror(n);
Y		exit(ERROR_EXIT);
Y	}
Y	poke_daemon();
Y}
Y
Y
Ystatic void
Ycheck_error(msg)
Y	char	*msg;
Y{
Y	CheckErrorCount++;
Y	fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
Y}
Y
Y
Ystatic void
Yedit_cmd() {
Y	char		n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
Y	register FILE	*f;
Y	register int	ch;
Y	int		t, x;
Y	struct stat	statbuf;
Y	time_t		mtime;
Y	WAIT_T		waiter;
Y	PID_T		pid, xpid;
Y
Y	log_it(RealUser, Pid, "BEGIN EDIT", User);
Y	(void) sprintf(n, CRON_TAB(User));
Y	if (!(f = fopen(n, "r"))) {
Y		if (errno != ENOENT) {
Y			perror(n);
Y			exit(ERROR_EXIT);
Y		}
Y		fprintf(stderr, "no crontab for %s - using an empty one\n",
Y			User);
Y		if (!(f = fopen("/dev/null", "r"))) {
Y			perror("/dev/null");
Y			exit(ERROR_EXIT);
Y		}
Y	}
Y
Y	(void) sprintf(Filename, "/tmp/crontab.%d", Pid);
Y	if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0600))) {
Y		perror(Filename);
Y		goto fatal;
Y	}
Y	if (fchown(t, getuid(), getgid()) < 0) {
Y		perror("fchown");
Y		goto fatal;
Y	}
Y	if (!(NewCrontab = fdopen(t, "r+"))) {
Y		perror("fdopen");
Y		goto fatal;
Y	}
Y
Y	Set_LineNum(1)
Y
Y	/* ignore the top few comments since we probably put them there.
Y	 */
Y	for (x = 0;  x < NHEADER_LINES;  x++) {
Y		ch = get_char(f);
Y		if (EOF == ch)
Y			break;
Y		if ('#' != ch) {
Y			putc(ch, NewCrontab);
Y			break;
Y		}
Y		while (EOF != (ch = get_char(f)))
Y			if (ch == '\n')
Y				break;
Y		if (EOF == ch)
Y			break;
Y	}
Y
Y	/* copy the rest of the crontab (if any) to the temp file.
Y	 */
Y	if (EOF != ch)
Y		while (EOF != (ch = get_char(f)))
Y			putc(ch, NewCrontab);
Y	fclose(f);
Y	if (fflush(NewCrontab) < OK) {
Y		perror(Filename);
Y		exit(ERROR_EXIT);
Y	}
Y again:
Y	rewind(NewCrontab);
Y	if (ferror(NewCrontab)) {
Y		fprintf(stderr, "%s: error while writing new crontab to %s\n",
Y			ProgramName, Filename);
Y fatal:		unlink(Filename);
Y		exit(ERROR_EXIT);
Y	}
Y	if (fstat(t, &statbuf) < 0) {
Y		perror("fstat");
Y		goto fatal;
Y	}
Y	mtime = statbuf.st_mtime;
Y
Y	if ((!(editor = getenv("VISUAL")))
Y	 && (!(editor = getenv("EDITOR")))
Y	    ) {
Y		editor = EDITOR;
Y	}
Y
Y	/* we still have the file open.  editors will generally rewrite the
Y	 * original file rather than renaming/unlinking it and starting a
Y	 * new one; even backup files are supposed to be made by copying
Y	 * rather than by renaming.  if some editor does not support this,
Y	 * then don't use it.  the security problems are more severe if we
Y	 * close and reopen the file around the edit.
Y	 */
Y
Y	switch (pid = fork()) {
Y	case -1:
Y		perror("fork");
Y		goto fatal;
Y	case 0:
Y		/* child */
Y		if (setuid(getuid()) < 0) {
Y			perror("setuid(getuid())");
Y			exit(ERROR_EXIT);
Y		}
Y		if (chdir("/tmp") < 0) {
Y			perror("chdir(/tmp)");
Y			exit(ERROR_EXIT);
Y		}
Y		if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) {
Y			fprintf(stderr, "%s: editor or filename too long\n",
Y				ProgramName);
Y			exit(ERROR_EXIT);
Y		}
Y		sprintf(q, "%s %s", editor, Filename);
Y		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, NULL);
Y		perror(editor);
Y		exit(ERROR_EXIT);
Y		/*NOTREACHED*/
Y	default:
Y		/* parent */
Y		break;
Y	}
Y
Y	/* parent */
Y	xpid = wait(&waiter);
Y	if (xpid != pid) {
Y		fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n",
Y			ProgramName, xpid, pid, editor);
Y		goto fatal;
Y	}
Y	if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
Y		fprintf(stderr, "%s: \"%s\" exited with status %d\n",
Y			ProgramName, editor, WEXITSTATUS(waiter));
Y		goto fatal;
Y	}
Y	if (WIFSIGNALED(waiter)) {
Y		fprintf(stderr,
Y			"%s: \"%s\" killed; signal %d (%score dumped)\n",
Y			ProgramName, editor, WTERMSIG(waiter),
Y			WCOREDUMP(waiter) ?"" :"no ");
Y		goto fatal;
Y	}
Y	if (fstat(t, &statbuf) < 0) {
Y		perror("fstat");
Y		goto fatal;
Y	}
Y	if (mtime == statbuf.st_mtime) {
Y		fprintf(stderr, "%s: no changes made to crontab\n",
Y			ProgramName);
Y		goto remove;
Y	}
Y	fprintf(stderr, "%s: installing new crontab\n", ProgramName);
Y	switch (replace_cmd()) {
Y	case 0:
Y		break;
Y	case -1:
Y		for (;;) {
Y			printf("Do you want to retry the same edit? ");
Y			fflush(stdout);
Y			q[0] = '\0';
Y			(void) fgets(q, sizeof q, stdin);
Y			switch (islower(q[0]) ? q[0] : tolower(q[0])) {
Y			case 'y':
Y				goto again;
Y			case 'n':
Y				goto abandon;
Y			default:
Y				fprintf(stderr, "Enter Y or N\n");
Y			}
Y		}
Y		/*NOTREACHED*/
Y	case -2:
Y	abandon:
Y		fprintf(stderr, "%s: edits left in %s\n",
Y			ProgramName, Filename);
Y		goto done;
Y	default:
Y		fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n");
Y		goto fatal;
Y	}
Y remove:
Y	unlink(Filename);
Y done:
Y	log_it(RealUser, Pid, "END EDIT", User);
Y}
Y	
Y
Y/* returns	0	on success
Y *		-1	on syntax error
Y *		-2	on install error
Y */
Ystatic int
Yreplace_cmd() {
Y	char	n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
Y	register FILE	*tmp;
Y	register int	ch;
Y	int	eof;
Y	entry	*e;
Y	time_t	now = time(NULL);
Y	char	**envp = env_init();
Y
Y	(void) sprintf(n, "tmp.%d", Pid);
Y	(void) sprintf(tn, CRON_TAB(n));
Y	if (!(tmp = fopen(tn, "w+"))) {
Y		perror(tn);
Y		return (-2);
Y	}
Y
Y	/* write a signature at the top of the file.
Y	 *
Y	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
Y	 */
Y	fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
Y	fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
Y	fprintf(tmp, "# (Cron version -- %s)\n", rcsid);
Y
Y	/* copy the crontab to the tmp
Y	 */
Y	rewind(NewCrontab);
Y	Set_LineNum(1)
Y	while (EOF != (ch = get_char(NewCrontab)))
Y		putc(ch, tmp);
Y	ftruncate(fileno(tmp), ftell(tmp));
Y	fflush(tmp);  rewind(tmp);
Y
Y	if (ferror(tmp)) {
Y		fprintf(stderr, "%s: error while writing new crontab to %s\n",
Y			ProgramName, tn);
Y		fclose(tmp);  unlink(tn);
Y		return (-2);
Y	}
Y
Y	/* check the syntax of the file being installed.
Y	 */
Y
Y	/* BUG: was reporting errors after the EOF if there were any errors
Y	 * in the file proper -- kludged it by stopping after first error.
Y	 *		vix 31mar87
Y	 */
Y	Set_LineNum(1 - NHEADER_LINES)
Y	CheckErrorCount = 0;  eof = FALSE;
Y	while (!CheckErrorCount && !eof) {
Y		switch (load_env(envstr, tmp)) {
Y		case ERR:
Y			eof = TRUE;
Y			break;
Y		case FALSE:
Y			e = load_entry(tmp, check_error, pw, envp);
Y			if (e)
Y				free(e);
Y			break;
Y		case TRUE:
Y			break;
Y		}
Y	}
Y
Y	if (CheckErrorCount != 0) {
Y		fprintf(stderr, "errors in crontab file, can't install.\n");
Y		fclose(tmp);  unlink(tn);
Y		return (-1);
Y	}
Y
Y	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
Y		{
Y		perror("chown");
Y		fclose(tmp);  unlink(tn);
Y		return (-2);
Y		}
Y
Y	if (fchmod(fileno(tmp), 0600) < OK)
Y		{
Y		perror("chown");
Y		fclose(tmp);  unlink(tn);
Y		return (-2);
Y		}
Y
Y	if (fclose(tmp) == EOF) {
Y		perror("fclose");
Y		unlink(tn);
Y		return (-2);
Y	}
Y
Y	(void) sprintf(n, CRON_TAB(User));
Y	if (rename(tn, n)) {
Y		fprintf(stderr, "%s: error renaming %s to %s\n",
Y			ProgramName, tn, n);
Y		perror("rename");
Y		unlink(tn);
Y		return (-2);
Y	}
Y	log_it(RealUser, Pid, "REPLACE", User);
Y
Y	poke_daemon();
Y
Y	return (0);
Y}
Y
Y
Ystatic void
Ypoke_daemon() {
Y	struct timeval tvs[2];
Y
Y	(void) gettimeofday(&tvs[0], NULL);
Y	tvs[1] = tvs[0];
Y	if (utimes(SPOOL_DIR, tvs) < OK) {
Y		fprintf(stderr, "crontab: can't update mtime on spooldir\n");
Y		perror(SPOOL_DIR);
Y		return;
Y	}
Y}
SHAR_EOF
chmod 644 'cron/crontab.c'
fi
if test -f 'cron/database.c'
then
	echo shar: "will not over-write existing file 'cron/database.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/database.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: database.c,v 2.8 1994/01/15 20:43:43 vixie Exp $";
Y#endif
Y
Y/* vix 26jan87 [RCS has the log]
Y */
Y
Y
Y#include "cron.h"
Y#include <fcntl.h>
Y#include <sys/stat.h>
Y#include <sys/file.h>
Y
Y
Y#define TMAX(a,b) ((a)>(b)?(a):(b))
Y
Y
Ystatic	void		process_crontab __P((char *, char *, char *,
Y					     struct stat *,
Y					     cron_db *, cron_db *));
Y
Y
Yvoid
Yload_database(old_db)
Y	cron_db		*old_db;
Y{
Y	DIR		*dir;
Y	struct stat	statbuf;
Y	struct stat	syscron_stat;
Y	register DIR_T   *dp;
Y	cron_db		new_db;
Y	user		*u, *nu;
Y
Y	Debug(DLOAD, ("[%d] load_database()\n", getpid()))
Y
Y	/* before we start loading any data, do a stat on SPOOL_DIR
Y	 * so that if anything changes as of this moment (i.e., before we've
Y	 * cached any of the database), we'll see the changes next time.
Y	 */
Y	if (stat(SPOOL_DIR, &statbuf) < OK) {
Y		log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR);
Y		(void) exit(ERROR_EXIT);
Y	}
Y
Y	/* track system crontab file
Y	 */
Y	if (stat(SYSCRONTAB, &syscron_stat) < OK)
Y		syscron_stat.st_mtime = 0;
Y
Y	/* if spooldir's mtime has not changed, we don't need to fiddle with
Y	 * the database.
Y	 *
Y	 * Note that old_db->mtime is initialized to 0 in main(), and
Y	 * so is guaranteed to be different than the stat() mtime the first
Y	 * time this function is called.
Y	 */
Y	if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) {
Y		Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
Y			      getpid()))
Y		return;
Y	}
Y
Y	/* something's different.  make a new database, moving unchanged
Y	 * elements from the old database, reloading elements that have
Y	 * actually changed.  Whatever is left in the old database when
Y	 * we're done is chaff -- crontabs that disappeared.
Y	 */
Y	new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime);
Y	new_db.head = new_db.tail = NULL;
Y
Y	if (syscron_stat.st_mtime) {
Y		process_crontab("root", "*system*",
Y				SYSCRONTAB, &syscron_stat,
Y				&new_db, old_db);
Y	}
Y
Y	/* we used to keep this dir open all the time, for the sake of
Y	 * efficiency.  however, we need to close it in every fork, and
Y	 * we fork a lot more often than the mtime of the dir changes.
Y	 */
Y	if (!(dir = opendir(SPOOL_DIR))) {
Y		log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR);
Y		(void) exit(ERROR_EXIT);
Y	}
Y
Y	while (NULL != (dp = readdir(dir))) {
Y		char	fname[MAXNAMLEN+1],
Y			tabname[MAXNAMLEN+1];
Y
Y		/* avoid file names beginning with ".".  this is good
Y		 * because we would otherwise waste two guaranteed calls
Y		 * to getpwnam() for . and .., and also because user names
Y		 * starting with a period are just too nasty to consider.
Y		 */
Y		if (dp->d_name[0] == '.')
Y			continue;
Y
Y		(void) strcpy(fname, dp->d_name);
Y		sprintf(tabname, CRON_TAB(fname));
Y
Y		process_crontab(fname, fname, tabname,
Y				&statbuf, &new_db, old_db);
Y	}
Y	closedir(dir);
Y
Y	/* if we don't do this, then when our children eventually call
Y	 * getpwnam() in do_command.c's child_process to verify MAILTO=,
Y	 * they will screw us up (and v-v).
Y	 */
Y	endpwent();
Y
Y	/* whatever's left in the old database is now junk.
Y	 */
Y	Debug(DLOAD, ("unlinking old database:\n"))
Y	for (u = old_db->head;  u != NULL;  u = nu) {
Y		Debug(DLOAD, ("\t%s\n", u->name))
Y		nu = u->next;
Y		unlink_user(old_db, u);
Y		free_user(u);
Y	}
Y
Y	/* overwrite the database control block with the new one.
Y	 */
Y	*old_db = new_db;
Y	Debug(DLOAD, ("load_database is done\n"))
Y}
Y
Y
Yvoid
Ylink_user(db, u)
Y	register cron_db	*db;
Y	register user	*u;
Y{
Y	if (db->head == NULL)
Y		db->head = u;
Y	if (db->tail)
Y		db->tail->next = u;
Y	u->prev = db->tail;
Y	u->next = NULL;
Y	db->tail = u;
Y}
Y
Y
Yvoid
Yunlink_user(db, u)
Y	register cron_db	*db;
Y	register user	*u;
Y{
Y	if (u->prev == NULL)
Y		db->head = u->next;
Y	else
Y		u->prev->next = u->next;
Y
Y	if (u->next == NULL)
Y		db->tail = u->prev;
Y	else
Y		u->next->prev = u->prev;
Y}
Y
Y
Yuser *
Yfind_user(db, name)
Y	cron_db	*db;
Y	register char	*name;
Y{
Y	char	*env_get();
Y	register user	*u;
Y
Y	for (u = db->head;  u != NULL;  u = u->next)
Y		if (!strcmp(u->name, name))
Y			break;
Y	return u;
Y}
Y
Y
Ystatic void
Yprocess_crontab(uname, fname, tabname, statbuf, new_db, old_db)
Y	char		*uname;
Y	char		*fname;
Y	char		*tabname;
Y	struct stat	*statbuf;
Y	cron_db		*new_db;
Y	cron_db		*old_db;
Y{
Y	struct passwd	*pw = NULL;
Y	int		crontab_fd = OK - 1;
Y	register user		*u;
Y
Y	if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) {
Y		/* file doesn't have a user in passwd file.
Y		 */
Y		log_it(fname, getpid(), "ORPHAN", "no passwd entry");
Y		goto next_crontab;
Y	}
Y
Y	if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) {
Y		/* crontab not accessible?
Y		 */
Y		log_it(fname, getpid(), "CAN'T OPEN", tabname);
Y		goto next_crontab;
Y	}
Y
Y	if (fstat(crontab_fd, statbuf) < OK) {
Y		log_it(fname, getpid(), "FSTAT FAILED", tabname);
Y		goto next_crontab;
Y	}
Y
Y	Debug(DLOAD, ("\t%s:", fname))
Y	u = find_user(old_db, fname);
Y	if (u != NULL) {
Y		/* if crontab has not changed since we last read it
Y		 * in, then we can just use our existing entry.
Y		 */
Y		if (u->mtime == statbuf->st_mtime) {
Y			Debug(DLOAD, (" [no change, using old data]"))
Y			unlink_user(old_db, u);
Y			link_user(new_db, u);
Y			goto next_crontab;
Y		}
Y
Y		/* before we fall through to the code that will reload
Y		 * the user, let's deallocate and unlink the user in
Y		 * the old database.  This is more a point of memory
Y		 * efficiency than anything else, since all leftover
Y		 * users will be deleted from the old database when
Y		 * we finish with the crontab...
Y		 */
Y		Debug(DLOAD, (" [delete old data]"))
Y		unlink_user(old_db, u);
Y		free_user(u);
Y		log_it(fname, getpid(), "RELOAD", tabname);
Y	}
Y	u = load_user(crontab_fd, pw, fname);
Y	if (u != NULL) {
Y		u->mtime = statbuf->st_mtime;
Y		link_user(new_db, u);
Y	}
Y
Ynext_crontab:
Y	if (crontab_fd >= OK) {
Y		Debug(DLOAD, (" [done]\n"))
Y		close(crontab_fd);
Y	}
Y}
SHAR_EOF
chmod 644 'cron/database.c'
fi
if test -f 'cron/do_command.c'
then
	echo shar: "will not over-write existing file 'cron/do_command.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/do_command.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: do_command.c,v 2.12 1994/01/15 20:43:43 vixie Exp $";
Y#endif
Y
Y#include "cron.h"
Y#include <sys/signal.h>
Y#if defined(SYSLOG)
Y# include <syslog.h>
Y#endif
Y
Ystatic void		child_process __P((entry *, user *));
Y
Yvoid
Ydo_command(e, u)
Y	entry	*e;
Y	user	*u;
Y{
Y	Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
Y		getpid(), e->cmd, u->name, e->uid, e->gid))
Y
Y	/* fork to become asynchronous -- parent process is done immediately,
Y	 * and continues to run the normal cron code, which means return to
Y	 * tick().  the child and grandchild don't leave this function, alive.
Y	 *
Y	 * vfork() is unsuitable, since we have much to do, and the parent
Y	 * needs to be able to run off and fork other processes.
Y	 */
Y	switch (fork()) {
Y	case -1:
Y		log_it("CRON",getpid(),"error","can't fork");
Y		break;
Y	case 0:
Y		/* child process */
Y		acquire_daemonlock(1);
Y		child_process(e, u);
Y		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
Y		_exit(OK_EXIT);
Y		break;
Y	default:
Y		/* parent process */
Y		break;
Y	}
Y	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
Y}
Y
Y
Ystatic void
Ychild_process(e, u)
Y	entry	*e;
Y	user	*u;
Y{
Y	int		stdin_pipe[2], stdout_pipe[2];
Y	register char	*input_data;
Y	char		*usernm, *mailto;
Y	register int ch;
Y	int		children = 0, escaped;
Y
Y	Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd))
Y
Y	/* mark ourselves as different to PS command watchers by upshifting
Y	 * our program name.  This has no effect on some kernels.
Y	 */
Y	for (input_data = ProgramName;  ch = *input_data;  input_data++)
Y		*input_data = (islower(ch) ? toupper(ch) : ch);
Y
Y	/* discover some useful and important environment settings
Y	 */
Y	usernm = env_get("LOGNAME", e->envp);
Y	mailto = env_get("MAILTO", e->envp);
Y
Y	/* our parent is watching for our death by catching SIGCHLD.  we
Y	 * do not care to watch for our children's deaths this way -- we
Y	 * use wait() explictly.  so we have to disable the signal (which
Y	 * was inherited from the parent).
Y	 */
Y	(void) signal(SIGCHLD, SIG_IGN);
Y
Y	/* create some pipes to talk to our future child
Y	 */
Y	pipe(stdin_pipe);	/* child's stdin */
Y	pipe(stdout_pipe);	/* child's stdout */
Y	
Y	/* since we are a forked process, we can diddle the command string
Y	 * we were passed -- nobody else is going to use it again, right?
Y	 *
Y	 * if a % is present in the command, previous characters are the
Y	 * command, and subsequent characters are the additional input to
Y	 * the command.  Subsequent %'s will be transformed into newlines,
Y	 * but that happens later.
Y	 */
Y	escaped = FALSE;
Y	for (input_data = e->cmd;  ch = *input_data;  input_data++) {
Y		if (escaped) {
Y			escaped = FALSE;
Y			continue;
Y		}
Y		if (ch == '\\') {
Y			escaped = TRUE;
Y			continue;
Y		}
Y		if (ch == '%') {
Y			*input_data++ = '\0';
Y			break;
Y		}
Y	}
Y
Y	/* fork again, this time so we can exec the user's command.
Y	 */
Y	switch (vfork()) {
Y	case -1:
Y		log_it("CRON",getpid(),"error","can't vfork");
Y		exit(ERROR_EXIT);
Y		/*NOTREACHED*/
Y	case 0:
Y		Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n",
Y			      getpid()))
Y
Y		/* write a log message.  we've waited this long to do it
Y		 * because it was not until now that we knew the PID that
Y		 * the actual user command shell was going to get and the
Y		 * PID is part of the log message.
Y		 */
Y		/*local*/{
Y			char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
Y
Y			log_it(usernm, getpid(), "CMD", x);
Y			free(x);
Y		}
Y
Y		/* that's the last thing we'll log.  close the log files.
Y		 */
Y#ifdef SYSLOG
Y		closelog();
Y#endif
Y
Y		/* get new pgrp, void tty, etc.
Y		 */
Y		(void) setsid();
Y
Y		/* close the pipe ends that we won't use.  this doesn't affect
Y		 * the parent, who has to read and write them; it keeps the
Y		 * kernel from recording us as a potential client TWICE --
Y		 * which would keep it from sending SIGPIPE in otherwise
Y		 * appropriate circumstances.
Y		 */
Y		close(stdin_pipe[WRITE_PIPE]);
Y		close(stdout_pipe[READ_PIPE]);
Y
Y		/* grandchild process.  make std{in,out} be the ends of
Y		 * pipes opened by our daddy; make stderr go to stdout.
Y		 */
Y		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
Y		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
Y		close(STDERR);	dup2(STDOUT, STDERR);
Y
Y		/* close the pipes we just dup'ed.  The resources will remain.
Y		 */
Y		close(stdin_pipe[READ_PIPE]);
Y		close(stdout_pipe[WRITE_PIPE]);
Y
Y		/* set our directory, uid and gid.  Set gid first, since once
Y		 * we set uid, we've lost root privledges.
Y		 */
Y		setgid(e->gid);
Y		initgroups(env_get("LOGNAME", e->envp), e->gid);
Y		setuid(e->uid);		/* we aren't root after this... */
Y		chdir(env_get("HOME", e->envp));
Y
Y		/* exec the command.
Y		 */
Y		{
Y			char	*shell = env_get("SHELL", e->envp);
Y
Y# if DEBUGGING
Y			if (DebugFlags & DTEST) {
Y				fprintf(stderr,
Y				"debug DTEST is on, not exec'ing command.\n");
Y				fprintf(stderr,
Y				"\tcmd='%s' shell='%s'\n", e->cmd, shell);
Y				_exit(OK_EXIT);
Y			}
Y# endif /*DEBUGGING*/
Y			execle(shell, shell, "-c", e->cmd, (char *)0, e->envp);
Y			fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
Y			perror("execl");
Y			_exit(ERROR_EXIT);
Y		}
Y		break;
Y	default:
Y		/* parent process */
Y		break;
Y	}
Y
Y	children++;
Y
Y	/* middle process, child of original cron, parent of process running
Y	 * the user's command.
Y	 */
Y
Y	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
Y
Y	/* close the ends of the pipe that will only be referenced in the
Y	 * grandchild process...
Y	 */
Y	close(stdin_pipe[READ_PIPE]);
Y	close(stdout_pipe[WRITE_PIPE]);
Y
Y	/*
Y	 * write, to the pipe connected to child's stdin, any input specified
Y	 * after a % in the crontab entry.  while we copy, convert any
Y	 * additional %'s to newlines.  when done, if some characters were
Y	 * written and the last one wasn't a newline, write a newline.
Y	 *
Y	 * Note that if the input data won't fit into one pipe buffer (2K
Y	 * or 4K on most BSD systems), and the child doesn't read its stdin,
Y	 * we would block here.  thus we must fork again.
Y	 */
Y
Y	if (*input_data && fork() == 0) {
Y		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
Y		register int	need_newline = FALSE;
Y		register int	escaped = FALSE;
Y		register int	ch;
Y
Y		Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
Y
Y		/* close the pipe we don't use, since we inherited it and
Y		 * are part of its reference count now.
Y		 */
Y		close(stdout_pipe[READ_PIPE]);
Y
Y		/* translation:
Y		 *	\% -> %
Y		 *	%  -> \n
Y		 *	\x -> \x	for all x != %
Y		 */
Y		while (ch = *input_data++) {
Y			if (escaped) {
Y				if (ch != '%')
Y					putc('\\', out);
Y			} else {
Y				if (ch == '%')
Y					ch = '\n';
Y			}
Y
Y			if (!(escaped = (ch == '\\'))) {
Y				putc(ch, out);
Y				need_newline = (ch != '\n');
Y			}
Y		}
Y		if (escaped)
Y			putc('\\', out);
Y		if (need_newline)
Y			putc('\n', out);
Y
Y		/* close the pipe, causing an EOF condition.  fclose causes
Y		 * stdin_pipe[WRITE_PIPE] to be closed, too.
Y		 */
Y		fclose(out);
Y
Y		Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
Y		exit(0);
Y	}
Y
Y	/* close the pipe to the grandkiddie's stdin, since its wicked uncle
Y	 * ernie back there has it open and will close it when he's done.
Y	 */
Y	close(stdin_pipe[WRITE_PIPE]);
Y
Y	children++;
Y
Y	/*
Y	 * read output from the grandchild.  it's stderr has been redirected to
Y	 * it's stdout, which has been redirected to our pipe.  if there is any
Y	 * output, we'll be mailing it to the user whose crontab this is...
Y	 * when the grandchild exits, we'll get EOF.
Y	 */
Y
Y	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
Y
Y	/*local*/{
Y		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
Y		register int	ch = getc(in);
Y
Y		if (ch != EOF) {
Y			register FILE	*mail;
Y			register int	bytes = 1;
Y			int		status = 0;
Y
Y			Debug(DPROC|DEXT,
Y				("[%d] got data (%x:%c) from grandchild\n",
Y					getpid(), ch, ch))
Y
Y			/* get name of recipient.  this is MAILTO if set to a
Y			 * valid local username; USER otherwise.
Y			 */
Y			if (mailto) {
Y				/* MAILTO was present in the environment
Y				 */
Y				if (!*mailto) {
Y					/* ... but it's empty. set to NULL
Y					 */
Y					mailto = NULL;
Y				}
Y			} else {
Y				/* MAILTO not present, set to USER.
Y				 */
Y				mailto = usernm;
Y			}
Y		
Y			/* if we are supposed to be mailing, MAILTO will
Y			 * be non-NULL.  only in this case should we set
Y			 * up the mail command and subjects and stuff...
Y			 */
Y
Y			if (mailto) {
Y				register char	**env;
Y				auto char	mailcmd[MAX_COMMAND];
Y				auto char	hostname[MAXHOSTNAMELEN];
Y
Y				(void) gethostname(hostname, MAXHOSTNAMELEN);
Y				(void) sprintf(mailcmd, MAILARGS,
Y					       MAILCMD, mailto);
Y				if (!(mail = cron_popen(mailcmd, "w"))) {
Y					perror(MAILCMD);
Y					(void) _exit(ERROR_EXIT);
Y				}
Y				fprintf(mail, "From: root (Cron Daemon)\n");
Y				fprintf(mail, "To: %s\n", mailto);
Y				fprintf(mail, "Subject: Cron <%s@%s> %s\n",
Y					usernm, first_word(hostname, "."),
Y					e->cmd);
Y# if defined(MAIL_DATE)
Y				fprintf(mail, "Date: %s\n",
Y					arpadate(&TargetTime));
Y# endif /* MAIL_DATE */
Y				for (env = e->envp;  *env;  env++)
Y					fprintf(mail, "X-Cron-Env: <%s>\n",
Y						*env);
Y				fprintf(mail, "\n");
Y
Y				/* this was the first char from the pipe
Y				 */
Y				putc(ch, mail);
Y			}
Y
Y			/* we have to read the input pipe no matter whether
Y			 * we mail or not, but obviously we only write to
Y			 * mail pipe if we ARE mailing.
Y			 */
Y
Y			while (EOF != (ch = getc(in))) {
Y				bytes++;
Y				if (mailto)
Y					putc(ch, mail);
Y			}
Y
Y			/* only close pipe if we opened it -- i.e., we're
Y			 * mailing...
Y			 */
Y
Y			if (mailto) {
Y				Debug(DPROC, ("[%d] closing pipe to mail\n",
Y					getpid()))
Y				/* Note: the pclose will probably see
Y				 * the termination of the grandchild
Y				 * in addition to the mail process, since
Y				 * it (the grandchild) is likely to exit
Y				 * after closing its stdout.
Y				 */
Y				status = cron_pclose(mail);
Y			}
Y
Y			/* if there was output and we could not mail it,
Y			 * log the facts so the poor user can figure out
Y			 * what's going on.
Y			 */
Y			if (mailto && status) {
Y				char buf[MAX_TEMPSTR];
Y
Y				sprintf(buf,
Y			"mailed %d byte%s of output but got status 0x%04x\n",
Y					bytes, (bytes==1)?"":"s",
Y					status);
Y				log_it(usernm, getpid(), "MAIL", buf);
Y			}
Y
Y		} /*if data from grandchild*/
Y
Y		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
Y
Y		fclose(in);	/* also closes stdout_pipe[READ_PIPE] */
Y	}
Y
Y	/* wait for children to die.
Y	 */
Y	for (;  children > 0;  children--)
Y	{
Y		WAIT_T		waiter;
Y		PID_T		pid;
Y
Y		Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
Y			getpid(), children))
Y		pid = wait(&waiter);
Y		if (pid < OK) {
Y			Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
Y				getpid()))
Y			break;
Y		}
Y		Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
Y			getpid(), pid, WEXITSTATUS(waiter)))
Y		if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
Y			Debug(DPROC, (", dumped core"))
Y		Debug(DPROC, ("\n"))
Y	}
Y}
SHAR_EOF
chmod 644 'cron/do_command.c'
fi
if test -f 'cron/entry.c'
then
	echo shar: "will not over-write existing file 'cron/entry.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/entry.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: entry.c,v 2.12 1994/01/17 03:20:37 vixie Exp $";
Y#endif
Y
Y/* vix 26jan87 [RCS'd; rest of log is in RCS file]
Y * vix 01jan87 [added line-level error recovery]
Y * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
Y * vix 30dec86 [written]
Y */
Y
Y
Y#include "cron.h"
Y
Y
Ytypedef	enum ecode {
Y	e_none, e_minute, e_hour, e_dom, e_month, e_dow,
Y	e_cmd, e_timespec, e_username
Y} ecode_e;
Y
Ystatic char	get_list __P((bitstr_t *, int, int, char *[], int, FILE *)),
Y		get_range __P((bitstr_t *, int, int, char *[], int, FILE *)),
Y		get_number __P((int *, int, char *[], int, FILE *));
Ystatic int	set_element __P((bitstr_t *, int, int, int));
Y
Ystatic char *ecodes[] =
Y	{
Y		"no error",
Y		"bad minute",
Y		"bad hour",
Y		"bad day-of-month",
Y		"bad month",
Y		"bad day-of-week",
Y		"bad command",
Y		"bad time specifier",
Y		"bad username",
Y	};
Y
Y
Yvoid
Yfree_entry(e)
Y	register entry	*e;
Y{
Y	free(e->cmd);
Y	env_free(e->envp);
Y	free(e);
Y}
Y
Y
Y/* return NULL if eof or syntax error occurs;
Y * otherwise return a pointer to a new entry.
Y */
Yentry *
Yload_entry(file, error_func, pw, envp)
Y	FILE		*file;
Y	void		(*error_func)();
Y	register struct passwd	*pw;
Y	char		**envp;
Y{
Y	/* this function reads one crontab entry -- the next -- from a file.
Y	 * it skips any leading blank lines, ignores comments, and returns
Y	 * EOF if for any reason the entry can't be read and parsed.
Y	 *
Y	 * the entry is also parsed here.
Y	 *
Y	 * syntax:
Y	 *   user crontab:
Y	 *	minutes hours doms months dows cmd\n
Y	 *   system crontab (/etc/crontab):
Y	 *	minutes hours doms months dows USERNAME cmd\n
Y	 */
Y
Y	ecode_e	ecode = e_none;
Y	register entry	*e;
Y	int	ch;
Y	char	cmd[MAX_COMMAND];
Y	char	envstr[MAX_ENVSTR];
Y
Y	Debug(DPARS, ("load_entry()...about to eat comments\n"))
Y
Y	skip_comments(file);
Y
Y	ch = get_char(file);
Y	if (ch == EOF)
Y		return NULL;
Y
Y	/* ch is now the first useful character of a useful line.
Y	 * it may be an @special or it may be the first character
Y	 * of a list of minutes.
Y	 */
Y
Y	e = (entry *) calloc(sizeof(entry), sizeof(char));
Y
Y	if (ch == '@') {
Y		/* all of these should be flagged and load-limited; i.e.,
Y		 * instead of @hourly meaning "0 * * * *" it should mean
Y		 * "close to the front of every hour but not 'til the
Y		 * system load is low".  Problems are: how do you know
Y		 * what "low" means? (save me from /etc/cron.conf!) and:
Y		 * how to guarantee low variance (how low is low?), which
Y		 * means how to we run roughly every hour -- seems like
Y		 * we need to keep a history or let the first hour set
Y		 * the schedule, which means we aren't load-limited
Y		 * anymore.  too much for my overloaded brain. (vix, jan90)
Y		 * HINT
Y		 */
Y		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
Y		if (!strcmp("reboot", cmd)) {
Y			e->flags |= WHEN_REBOOT;
Y		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
Y			bit_set(e->minute, 0);
Y			bit_set(e->hour, 0);
Y			bit_set(e->dom, 0);
Y			bit_set(e->month, 0);
Y			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
Y		} else if (!strcmp("monthly", cmd)) {
Y			bit_set(e->minute, 0);
Y			bit_set(e->hour, 0);
Y			bit_set(e->dom, 0);
Y			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
Y			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
Y		} else if (!strcmp("weekly", cmd)) {
Y			bit_set(e->minute, 0);
Y			bit_set(e->hour, 0);
Y			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
Y			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
Y			bit_set(e->dow, 0);
Y		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
Y			bit_set(e->minute, 0);
Y			bit_set(e->hour, 0);
Y			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
Y			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
Y			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
Y		} else if (!strcmp("hourly", cmd)) {
Y			bit_set(e->minute, 0);
Y			bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
Y			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
Y			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
Y			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
Y		} else {
Y			ecode = e_timespec;
Y			goto eof;
Y		}
Y	} else {
Y		Debug(DPARS, ("load_entry()...about to parse numerics\n"))
Y
Y		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
Y			      PPC_NULL, ch, file);
Y		if (ch == EOF) {
Y			ecode = e_minute;
Y			goto eof;
Y		}
Y
Y		/* hours
Y		 */
Y
Y		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
Y			      PPC_NULL, ch, file);
Y		if (ch == EOF) {
Y			ecode = e_hour;
Y			goto eof;
Y		}
Y
Y		/* DOM (days of month)
Y		 */
Y
Y		if (ch == '*')
Y			e->flags |= DOM_STAR;
Y		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
Y			      PPC_NULL, ch, file);
Y		if (ch == EOF) {
Y			ecode = e_dom;
Y			goto eof;
Y		}
Y
Y		/* month
Y		 */
Y
Y		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
Y			      MonthNames, ch, file);
Y		if (ch == EOF) {
Y			ecode = e_month;
Y			goto eof;
Y		}
Y
Y		/* DOW (days of week)
Y		 */
Y
Y		if (ch == '*')
Y			e->flags |= DOW_STAR;
Y		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
Y			      DowNames, ch, file);
Y		if (ch == EOF) {
Y			ecode = e_dow;
Y			goto eof;
Y		}
Y	}
Y
Y	/* make sundays equivilent */
Y	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
Y		bit_set(e->dow, 0);
Y		bit_set(e->dow, 7);
Y	}
Y
Y	/* ch is the first character of a command, or a username */
Y	unget_char(ch, file);
Y
Y	if (!pw) {
Y		char		*username = cmd;	/* temp buffer */
Y
Y		Debug(DPARS, ("load_entry()...about to parse username\n"))
Y		ch = get_string(username, MAX_COMMAND, file, " \t");
Y
Y		Debug(DPARS, ("load_entry()...got %s\n",username))
Y		if (ch == EOF) {
Y			ecode = e_cmd;
Y			goto eof;
Y		}
Y
Y		pw = getpwnam(username);
Y		if (pw == NULL) {
Y			ecode = e_username;
Y			goto eof;
Y		}
Y		Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid))
Y	}
Y
Y	e->uid = pw->pw_uid;
Y	e->gid = pw->pw_gid;
Y
Y	/* copy and fix up environment.  some variables are just defaults and
Y	 * others are overrides.
Y	 */
Y	e->envp = env_copy(envp);
Y	if (!env_get("SHELL", e->envp)) {
Y		sprintf(envstr, "SHELL=%s", _PATH_BSHELL);
Y		e->envp = env_set(e->envp, envstr);
Y	}
Y	if (!env_get("HOME", e->envp)) {
Y		sprintf(envstr, "HOME=%s", pw->pw_dir);
Y		e->envp = env_set(e->envp, envstr);
Y	}
Y	if (!env_get("PATH", e->envp)) {
Y		sprintf(envstr, "PATH=%s", _PATH_DEFPATH);
Y		e->envp = env_set(e->envp, envstr);
Y	}
Y	sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name);
Y	e->envp = env_set(e->envp, envstr);
Y	sprintf(envstr, "%s=%s", "USER", pw->pw_name);
Y	e->envp = env_set(e->envp, envstr);
Y
Y	Debug(DPARS, ("load_entry()...about to parse command\n"))
Y
Y	/* Everything up to the next \n or EOF is part of the command...
Y	 * too bad we don't know in advance how long it will be, since we
Y	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
Y	 * XXX - should use realloc().
Y	 */ 
Y	ch = get_string(cmd, MAX_COMMAND, file, "\n");
Y
Y	/* a file without a \n before the EOF is rude, so we'll complain...
Y	 */
Y	if (ch == EOF) {
Y		ecode = e_cmd;
Y		goto eof;
Y	}
Y
Y	/* got the command in the 'cmd' string; save it in *e.
Y	 */
Y	e->cmd = strdup(cmd);
Y
Y	Debug(DPARS, ("load_entry()...returning successfully\n"))
Y
Y	/* success, fini, return pointer to the entry we just created...
Y	 */
Y	return e;
Y
Y eof:
Y	free(e);
Y	if (ecode != e_none && error_func)
Y		(*error_func)(ecodes[(int)ecode]);
Y	while (ch != EOF && ch != '\n')
Y		ch = get_char(file);
Y	return NULL;
Y}
Y
Y
Ystatic char
Yget_list(bits, low, high, names, ch, file)
Y	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
Y	int		low, high;	/* bounds, impl. offset for bitstr */
Y	char		*names[];	/* NULL or *[] of names for these elements */
Y	int		ch;		/* current character being processed */
Y	register FILE		*file;		/* file being read */
Y{
Y	register int	done;
Y
Y	/* we know that we point to a non-blank character here;
Y	 * must do a Skip_Blanks before we exit, so that the
Y	 * next call (or the code that picks up the cmd) can
Y	 * assume the same thing.
Y	 */
Y
Y	Debug(DPARS|DEXT, ("get_list()...entered\n"))
Y
Y	/* list = range {"," range}
Y	 */
Y	
Y	/* clear the bit string, since the default is 'off'.
Y	 */
Y	bit_nclear(bits, 0, (high-low+1));
Y
Y	/* process all ranges
Y	 */
Y	done = FALSE;
Y	while (!done) {
Y		ch = get_range(bits, low, high, names, ch, file);
Y		if (ch == ',')
Y			ch = get_char(file);
Y		else
Y			done = TRUE;
Y	}
Y
Y	/* exiting.  skip to some blanks, then skip over the blanks.
Y	 */
Y	Skip_Nonblanks(ch, file)
Y	Skip_Blanks(ch, file)
Y
Y	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
Y
Y	return ch;
Y}
Y
Y
Ystatic char
Yget_range(bits, low, high, names, ch, file)
Y	bitstr_t	*bits;		/* one bit per flag, default=FALSE */
Y	int		low, high;	/* bounds, impl. offset for bitstr */
Y	char		*names[];	/* NULL or names of elements */
Y	register int	ch;		/* current character being processed */
Y	FILE		*file;		/* file being read */
Y{
Y	/* range = number | number "-" number [ "/" number ]
Y	 */
Y
Y	register int	i;
Y	auto int	num1, num2, num3;
Y
Y	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
Y
Y	if (ch == '*') {
Y		/* '*' means "first-last" but can still be modified by /step
Y		 */
Y		num1 = low;
Y		num2 = high;
Y		ch = get_char(file);
Y		if (ch == EOF)
Y			return EOF;
Y	} else {
Y		if (EOF == (ch = get_number(&num1, low, names, ch, file)))
Y			return EOF;
Y
Y		if (ch != '-') {
Y			/* not a range, it's a single number.
Y			 */
Y			if (EOF == set_element(bits, low, high, num1))
Y				return EOF;
Y			return ch;
Y		} else {
Y			/* eat the dash
Y			 */
Y			ch = get_char(file);
Y			if (ch == EOF)
Y				return EOF;
Y
Y			/* get the number following the dash
Y			 */
Y			ch = get_number(&num2, low, names, ch, file);
Y			if (ch == EOF)
Y				return EOF;
Y		}
Y	}
Y
Y	/* check for step size
Y	 */
Y	if (ch == '/') {
Y		/* eat the slash
Y		 */
Y		ch = get_char(file);
Y		if (ch == EOF)
Y			return EOF;
Y
Y		/* get the step size -- note: we don't pass the
Y		 * names here, because the number is not an
Y		 * element id, it's a step size.  'low' is
Y		 * sent as a 0 since there is no offset either.
Y		 */
Y		ch = get_number(&num3, 0, PPC_NULL, ch, file);
Y		if (ch == EOF)
Y			return EOF;
Y	} else {
Y		/* no step.  default==1.
Y		 */
Y		num3 = 1;
Y	}
Y
Y	/* range. set all elements from num1 to num2, stepping
Y	 * by num3.  (the step is a downward-compatible extension
Y	 * proposed conceptually by bob@acornrc, syntactically
Y	 * designed then implmented by paul vixie).
Y	 */
Y	for (i = num1;  i <= num2;  i += num3)
Y		if (EOF == set_element(bits, low, high, i))
Y			return EOF;
Y
Y	return ch;
Y}
Y
Y
Ystatic char
Yget_number(numptr, low, names, ch, file)
Y	int	*numptr;	/* where does the result go? */
Y	int	low;		/* offset applied to result if symbolic enum used */
Y	char	*names[];	/* symbolic names, if any, for enums */
Y	register int	ch;	/* current character */
Y	FILE	*file;		/* source */
Y{
Y	char	temp[MAX_TEMPSTR], *pc;
Y	int	len, i, all_digits;
Y
Y	/* collect alphanumerics into our fixed-size temp array
Y	 */
Y	pc = temp;
Y	len = 0;
Y	all_digits = TRUE;
Y	while (isalnum(ch)) {
Y		if (++len >= MAX_TEMPSTR)
Y			return EOF;
Y
Y		*pc++ = ch;
Y
Y		if (!isdigit(ch))
Y			all_digits = FALSE;
Y
Y		ch = get_char(file);
Y	}
Y	*pc = '\0';
Y
Y	/* try to find the name in the name list
Y	 */
Y	if (names) {
Y		for (i = 0;  names[i] != NULL;  i++) {
Y			Debug(DPARS|DEXT,
Y				("get_num, compare(%s,%s)\n", names[i], temp))
Y			if (!strcasecmp(names[i], temp)) {
Y				*numptr = i+low;
Y				return ch;
Y			}
Y		}
Y	}
Y
Y	/* no name list specified, or there is one and our string isn't
Y	 * in it.  either way: if it's all digits, use its magnitude.
Y	 * otherwise, it's an error.
Y	 */
Y	if (all_digits) {
Y		*numptr = atoi(temp);
Y		return ch;
Y	}
Y
Y	return EOF;
Y}
Y
Y
Ystatic int
Yset_element(bits, low, high, number)
Y	bitstr_t	*bits; 		/* one bit per flag, default=FALSE */
Y	int		low;
Y	int		high;
Y	int		number;
Y{
Y	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
Y
Y	if (number < low || number > high)
Y		return EOF;
Y
Y	bit_set(bits, (number-low));
Y	return OK;
Y}
SHAR_EOF
chmod 644 'cron/entry.c'
fi
if test -f 'cron/env.c'
then
	echo shar: "will not over-write existing file 'cron/env.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/env.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: env.c,v 2.7 1994/01/26 02:25:50 vixie Exp vixie $";
Y#endif
Y
Y
Y#include "cron.h"
Y
Y
Ychar **
Yenv_init()
Y{
Y	register char	**p = (char **) malloc(sizeof(char **));
Y
Y	p[0] = NULL;
Y	return (p);
Y}
Y
Y
Yvoid
Yenv_free(envp)
Y	char	**envp;
Y{
Y	register char	**p;
Y
Y	for (p = envp;  *p;  p++)
Y		free(*p);
Y	free(envp);
Y}
Y
Y
Ychar **
Yenv_copy(envp)
Y	char	**envp;
Y{
Y	register int	count, i;
Y	register char	**p;
Y
Y	for (count = 0;  envp[count] != NULL;  count++)
Y		;
Y	p = (char **) malloc((count+1) * sizeof(char *));  /* 1 for the NULL */
Y	for (i = 0;  i < count;  i++)
Y		p[i] = strdup(envp[i]);
Y	p[count] = NULL;
Y	return (p);
Y}
Y
Y
Ychar **
Yenv_set(envp, envstr)
Y	char	**envp;
Y	char	*envstr;
Y{
Y	register int	count, found;
Y	register char	**p;
Y
Y	/*
Y	 * count the number of elements, including the null pointer;
Y	 * also set 'found' to -1 or index of entry if already in here.
Y	 */
Y	found = -1;
Y	for (count = 0;  envp[count] != NULL;  count++) {
Y		if (!strcmp_until(envp[count], envstr, '='))
Y			found = count;
Y	}
Y	count++;	/* for the NULL */
Y
Y	if (found != -1) {
Y		/*
Y		 * it exists already, so just free the existing setting,
Y		 * save our new one there, and return the existing array.
Y		 */
Y		free(envp[found]);
Y		envp[found] = strdup(envstr);
Y		return (envp);
Y	}
Y
Y	/*
Y	 * it doesn't exist yet, so resize the array, move null pointer over
Y	 * one, save our string over the old null pointer, and return resized
Y	 * array.
Y	 */
Y	p = (char **) realloc((void *) envp,
Y			      (unsigned) ((count+1) * sizeof(char **)));
Y	p[count] = p[count-1];
Y	p[count-1] = strdup(envstr);
Y	return (p);
Y}
Y
Y
Y/* return	ERR = end of file
Y *		FALSE = not an env setting (file was repositioned)
Y *		TRUE = was an env setting
Y */
Yint
Yload_env(envstr, f)
Y	char	*envstr;
Y	FILE	*f;
Y{
Y	long	filepos;
Y	int	fileline;
Y	char	name[MAX_TEMPSTR], val[MAX_ENVSTR];
Y	int	fields;
Y
Y	filepos = ftell(f);
Y	fileline = LineNumber;
Y	skip_comments(f);
Y	if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n"))
Y		return (ERR);
Y
Y	Debug(DPARS, ("load_env, read <%s>\n", envstr))
Y
Y	name[0] = val[0] = '\0';
Y	fields = sscanf(envstr, "%[^ =] = %[^\n#]", name, val);
Y	if (fields != 2) {
Y		Debug(DPARS, ("load_env, not 2 fields (%d)\n", fields))
Y		fseek(f, filepos, 0);
Y		Set_LineNum(fileline);
Y		return (FALSE);
Y	}
Y
Y	/* 2 fields from scanf; looks like an env setting
Y	 */
Y
Y	/*
Y	 * process value string
Y	 */
Y	/*local*/{
Y		int	len = strdtb(val);
Y
Y		if (len >= 2) {
Y			if (val[0] == '\'' || val[0] == '"') {
Y				if (val[len-1] == val[0]) {
Y					val[len-1] = '\0';
Y					(void) strcpy(val, val+1);
Y				}
Y			}
Y		}
Y	}
Y
Y	(void) sprintf(envstr, "%s=%s", name, val);
Y	Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr))
Y	return (TRUE);
Y}
Y
Y
Ychar *
Yenv_get(name, envp)
Y	char	*name;
Y	char	**envp;
Y{
Y	register int	len = strlen(name);
Y	register char	*p, *q;
Y
Y	while (p = *envp++) {
Y		if (!(q = strchr(p, '=')))
Y			continue;
Y		if ((q - p) == len && !strncmp(p, name, len))
Y			return (q+1);
Y	}
Y	return (NULL);
Y}
SHAR_EOF
chmod 644 'cron/env.c'
fi
if test -f 'cron/externs.h'
then
	echo shar: "will not over-write existing file 'cron/externs.h'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/externs.h'
Y/* Copyright 1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#include <stdlib.h>
Y#include <unistd.h>
Y#include <string.h>
Y#include <sys/dir.h>
Y
Y#define DIR_T struct direct
Y#define	WEXITSTATUS(x)	((x).w_retcode)
Y#define	WTERMSIG(x)	((x).w_termsig)
Y#define	WCOREDUMP(x)	((x).w_coredump)
Y
Yextern	int		setsid __P((void));
SHAR_EOF
chmod 644 'cron/externs.h'
fi
if test -f 'cron/job.c'
then
	echo shar: "will not over-write existing file 'cron/job.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/job.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: job.c,v 1.6 1994/01/15 20:43:43 vixie Exp $";
Y#endif
Y
Y
Y#include "cron.h"
Y
Y
Ytypedef	struct _job {
Y	struct _job	*next;
Y	entry		*e;
Y	user		*u;
Y} job;
Y
Y
Ystatic job	*jhead = NULL, *jtail = NULL;
Y
Y
Yvoid
Yjob_add(e, u)
Y	register entry *e;
Y	register user *u;
Y{
Y	register job *j;
Y
Y	/* if already on queue, keep going */
Y	for (j=jhead; j; j=j->next)
Y		if (j->e == e && j->u == u) { return; }
Y
Y	/* build a job queue element */
Y	j = (job*)malloc(sizeof(job));
Y	j->next = (job*) NULL;
Y	j->e = e;
Y	j->u = u;
Y
Y	/* add it to the tail */
Y	if (!jhead) { jhead=j; }
Y	else { jtail->next=j; }
Y	jtail = j;
Y}
Y
Y
Yint
Yjob_runqueue()
Y{
Y	register job	*j, *jn;
Y	register int	run = 0;
Y
Y	for (j=jhead; j; j=jn) {
Y		do_command(j->e, j->u);
Y		jn = j->next;
Y		free(j);
Y		run++;
Y	}
Y	jhead = jtail = NULL;
Y	return run;
Y}
SHAR_EOF
chmod 644 'cron/job.c'
fi
if test -f 'cron/misc.c'
then
	echo shar: "will not over-write existing file 'cron/misc.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/misc.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: misc.c,v 2.9 1994/01/15 20:43:43 vixie Exp $";
Y#endif
Y
Y/* vix 26jan87 [RCS has the rest of the log]
Y * vix 30dec86 [written]
Y */
Y
Y
Y#include "cron.h"
Y#include <sys/time.h>
Y#include <sys/file.h>
Y#include <sys/stat.h>
Y#include <errno.h>
Y#include <string.h>
Y#include <fcntl.h>
Y#if defined(SYSLOG)
Y# include <syslog.h>
Y#endif
Y
Y
Y#if defined(LOG_DAEMON) && !defined(LOG_CRON)
Y#define LOG_CRON LOG_DAEMON
Y#endif
Y
Ystatic int		LogFD = ERR;
Y
Yint
Ystrcmp_until(left, right, until)
Y	register char	*left;
Y	register char	*right;
Y	int	until;
Y{
Y	register int	diff;
Y
Y	while (*left && *left != until && *left == *right) {
Y		left++;
Y		right++;
Y	}
Y
Y	if ((*left=='\0' || *left == until) &&
Y	    (*right=='\0' || *right == until)) {
Y		diff = 0;
Y	} else {
Y		diff = *left - *right;
Y	}
Y
Y	return diff;
Y}
Y
Y
Y/* strdtb(s) - delete trailing blanks in string 's' and return new length
Y */
Yint
Ystrdtb(s)
Y	register char	*s;
Y{
Y	register char	*x = s;
Y
Y	/* scan forward to the null
Y	 */
Y	while (*x)
Y		x++;
Y
Y	/* scan backward to either the first character before the string,
Y	 * or the last non-blank in the string, whichever comes first.
Y	 */
Y	do	{x--;}
Y	while (x >= s && isspace(*x));
Y
Y	/* one character beyond where we stopped above is where the null
Y	 * goes.
Y	 */
Y	*++x = '\0';
Y
Y	/* the difference between the position of the null character and
Y	 * the position of the first character of the string is the length.
Y	 */
Y	return x - s;
Y}
Y
Y
Yint
Yset_debug_flags(flags)
Y	char	*flags;
Y{
Y	/* debug flags are of the form    flag[,flag ...]
Y	 *
Y	 * if an error occurs, print a message to stdout and return FALSE.
Y	 * otherwise return TRUE after setting ERROR_FLAGS.
Y	 */
Y
Y#if !DEBUGGING
Y
Y	printf("this program was compiled without debugging enabled\n");
Y	return FALSE;
Y
Y#else /* DEBUGGING */
Y
Y	register char	*pc = flags;
Y
Y	DebugFlags = 0;
Y
Y	while (*pc) {
Y		char	**test;
Y		int	mask;
Y
Y		/* try to find debug flag name in our list.
Y		 */
Y		for (	test = DebugFlagNames, mask = 1;
Y			*test && strcmp_until(*test, pc, ',');
Y			test++, mask <<= 1
Y		    )
Y			;
Y
Y		if (!*test) {
Y			fprintf(stderr,
Y				"unrecognized debug flag <%s> <%s>\n",
Y				flags, pc);
Y			return FALSE;
Y		}
Y
Y		DebugFlags |= mask;
Y
Y		/* skip to the next flag
Y		 */
Y		while (*pc && *pc != ',')
Y			pc++;
Y		if (*pc == ',')
Y			pc++;
Y	}
Y
Y	if (DebugFlags) {
Y		int	flag;
Y
Y		fprintf(stderr, "debug flags enabled:");
Y
Y		for (flag = 0;  DebugFlagNames[flag];  flag++)
Y			if (DebugFlags & (1 << flag))
Y				fprintf(stderr, " %s", DebugFlagNames[flag]);
Y		fprintf(stderr, "\n");
Y	}
Y
Y	return TRUE;
Y
Y#endif /* DEBUGGING */
Y}
Y
Y
Yvoid
Yset_cron_uid()
Y{
Y	if (seteuid(ROOT_UID) < OK) {
Y		perror("seteuid");
Y		exit(ERROR_EXIT);
Y	}
Y}
Y
Y
Yvoid
Yset_cron_cwd()
Y{
Y	struct stat	sb;
Y
Y	/* first check for CRONDIR ("/var/cron" or some such)
Y	 */
Y	if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
Y		perror(CRONDIR);
Y		if (OK == mkdir(CRONDIR, 0700)) {
Y			fprintf(stderr, "%s: created\n", CRONDIR);
Y			stat(CRONDIR, &sb);
Y		} else {
Y			fprintf(stderr, "%s: ", CRONDIR);
Y			perror("mkdir");
Y			exit(ERROR_EXIT);
Y		}
Y	}
Y	if (!(sb.st_mode & S_IFDIR)) {
Y		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
Y			CRONDIR);
Y		exit(ERROR_EXIT);
Y	}
Y	if (chdir(CRONDIR) < OK) {
Y		fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR);
Y		perror(CRONDIR);
Y		exit(ERROR_EXIT);
Y	}
Y
Y	/* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
Y	 */
Y	if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
Y		perror(SPOOL_DIR);
Y		if (OK == mkdir(SPOOL_DIR, 0700)) {
Y			fprintf(stderr, "%s: created\n", SPOOL_DIR);
Y			stat(SPOOL_DIR, &sb);
Y		} else {
Y			fprintf(stderr, "%s: ", SPOOL_DIR);
Y			perror("mkdir");
Y			exit(ERROR_EXIT);
Y		}
Y	}
Y	if (!(sb.st_mode & S_IFDIR)) {
Y		fprintf(stderr, "'%s' is not a directory, bailing out.\n",
Y			SPOOL_DIR);
Y		exit(ERROR_EXIT);
Y	}
Y}
Y
Y
Y/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless
Y *	another daemon is already running, which we detect here.
Y *
Y * note: main() calls us twice; once before forking, once after.
Y *	we maintain static storage of the file pointer so that we
Y *	can rewrite our PID into the PIDFILE after the fork.
Y *
Y * it would be great if fflush() disassociated the file buffer.
Y */
Yvoid
Yacquire_daemonlock(closeflag)
Y	int closeflag;
Y{
Y	static	FILE	*fp = NULL;
Y
Y	if (closeflag && fp) {
Y		fclose(fp);
Y		fp = NULL;
Y		return;
Y	}
Y
Y	if (!fp) {
Y		char	pidfile[MAX_FNAME];
Y		char	buf[MAX_TEMPSTR];
Y		int	fd, otherpid;
Y
Y		(void) sprintf(pidfile, PIDFILE, PIDDIR);
Y		if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644)))
Y		    || (NULL == (fp = fdopen(fd, "r+")))
Y		    ) {
Y			sprintf(buf, "can't open or create %s: %s",
Y				pidfile, strerror(errno));
Y			fprintf(stderr, "%s: %s\n", ProgramName, buf);
Y			log_it("CRON", getpid(), "DEATH", buf);
Y			exit(ERROR_EXIT);
Y		}
Y
Y		if (flock(fd, LOCK_EX|LOCK_NB) < OK) {
Y			int save_errno = errno;
Y
Y			fscanf(fp, "%d", &otherpid);
Y			sprintf(buf, "can't lock %s, otherpid may be %d: %s",
Y				pidfile, otherpid, strerror(save_errno));
Y			fprintf(stderr, "%s: %s\n", ProgramName, buf);
Y			log_it("CRON", getpid(), "DEATH", buf);
Y			exit(ERROR_EXIT);
Y		}
Y
Y		(void) fcntl(fd, F_SETFD, 1);
Y	}
Y
Y	rewind(fp);
Y	fprintf(fp, "%d\n", getpid());
Y	fflush(fp);
Y	(void) ftruncate(fileno(fp), ftell(fp));
Y
Y	/* abandon fd and fp even though the file is open. we need to
Y	 * keep it open and locked, but we don't need the handles elsewhere.
Y	 */
Y}
Y
Y/* get_char(file) : like getc() but increment LineNumber on newlines
Y */
Yint
Yget_char(file)
Y	register FILE	*file;
Y{
Y	register int	ch;
Y
Y	ch = getc(file);
Y	if (ch == '\n')
Y		Set_LineNum(LineNumber + 1)
Y	return ch;
Y}
Y
Y
Y/* unget_char(ch, file) : like ungetc but do LineNumber processing
Y */
Yvoid
Yunget_char(ch, file)
Y	register int	ch;
Y	register FILE	*file;
Y{
Y	ungetc(ch, file);
Y	if (ch == '\n')
Y		Set_LineNum(LineNumber - 1)
Y}
Y
Y
Y/* get_string(str, max, file, termstr) : like fgets() but
Y *		(1) has terminator string which should include \n
Y *		(2) will always leave room for the null
Y *		(3) uses get_char() so LineNumber will be accurate
Y *		(4) returns EOF or terminating character, whichever
Y */
Yint
Yget_string(string, size, file, terms)
Y	register char	*string;
Y	int	size;
Y	FILE	*file;
Y	char	*terms;
Y{
Y	register int	ch;
Y
Y	while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
Y		if (size > 1) {
Y			*string++ = (char) ch;
Y			size--;
Y		}
Y	}
Y
Y	if (size > 0)
Y		*string = '\0';
Y
Y	return ch;
Y}
Y
Y
Y/* skip_comments(file) : read past comment (if any)
Y */
Yvoid
Yskip_comments(file)
Y	register FILE	*file;
Y{
Y	register int	ch;
Y
Y	while (EOF != (ch = get_char(file))) {
Y		/* ch is now the first character of a line.
Y		 */
Y
Y		while (ch == ' ' || ch == '\t')
Y			ch = get_char(file);
Y
Y		if (ch == EOF)
Y			break;
Y
Y		/* ch is now the first non-blank character of a line.
Y		 */
Y
Y		if (ch != '\n' && ch != '#')
Y			break;
Y
Y		/* ch must be a newline or comment as first non-blank
Y		 * character on a line.
Y		 */
Y
Y		while (ch != '\n' && ch != EOF)
Y			ch = get_char(file);
Y
Y		/* ch is now the newline of a line which we're going to
Y		 * ignore.
Y		 */
Y	}
Y	if (ch != EOF)
Y		unget_char(ch, file);
Y}
Y
Y
Y/* int in_file(char *string, FILE *file)
Y *	return TRUE if one of the lines in file matches string exactly,
Y *	FALSE otherwise.
Y */
Ystatic int
Yin_file(string, file)
Y	char *string;
Y	FILE *file;
Y{
Y	char line[MAX_TEMPSTR];
Y
Y	rewind(file);
Y	while (fgets(line, MAX_TEMPSTR, file)) {
Y		if (line[0] != '\0')
Y			line[strlen(line)-1] = '\0';
Y		if (0 == strcmp(line, string))
Y			return TRUE;
Y	}
Y	return FALSE;
Y}
Y
Y
Y/* int allowed(char *username)
Y *	returns TRUE if (ALLOW_FILE exists and user is listed)
Y *	or (DENY_FILE exists and user is NOT listed)
Y *	or (neither file exists but user=="root" so it's okay)
Y */
Yint
Yallowed(username)
Y	char *username;
Y{
Y	static int	init = FALSE;
Y	static FILE	*allow, *deny;
Y
Y	if (!init) {
Y		init = TRUE;
Y#if defined(ALLOW_FILE) && defined(DENY_FILE)
Y		allow = fopen(ALLOW_FILE, "r");
Y		deny = fopen(DENY_FILE, "r");
Y		Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
Y#else
Y		allow = NULL;
Y		deny = NULL;
Y#endif
Y	}
Y
Y	if (allow)
Y		return (in_file(username, allow));
Y	if (deny)
Y		return (!in_file(username, deny));
Y
Y#if defined(ALLOW_ONLY_ROOT)
Y	return (strcmp(username, ROOT_USER) == 0);
Y#else
Y	return TRUE;
Y#endif
Y}
Y
Y
Yvoid
Ylog_it(username, xpid, event, detail)
Y	char	*username;
Y	int	xpid;
Y	char	*event;
Y	char	*detail;
Y{
Y	PID_T			pid = xpid;
Y#if defined(LOG_FILE)
Y	char			*msg;
Y	TIME_T			now = time((TIME_T) 0);
Y	register struct tm	*t = localtime(&now);
Y#endif /*LOG_FILE*/
Y
Y#if defined(SYSLOG)
Y	static int		syslog_open = 0;
Y#endif
Y
Y#if defined(LOG_FILE)
Y	/* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
Y	 */
Y	msg = (char *)malloc(strlen(username)
Y		     + strlen(event)
Y		     + strlen(detail)
Y		     + MAX_TEMPSTR);
Y
Y	if (LogFD < OK) {
Y		LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
Y		if (LogFD < OK) {
Y			fprintf(stderr, "%s: can't open log file\n",
Y				ProgramName);
Y			perror(LOG_FILE);
Y		} else {
Y			(void) fcntl(LogFD, F_SETFD, 1);
Y		}
Y	}
Y
Y	/* we have to sprintf() it because fprintf() doesn't always write
Y	 * everything out in one chunk and this has to be atomically appended
Y	 * to the log file.
Y	 */
Y	sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
Y		username,
Y		t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid,
Y		event, detail);
Y
Y	/* we have to run strlen() because sprintf() returns (char*) on old BSD
Y	 */
Y	if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
Y		if (LogFD >= OK)
Y			perror(LOG_FILE);
Y		fprintf(stderr, "%s: can't write to log file\n", ProgramName);
Y		write(STDERR, msg, strlen(msg));
Y	}
Y
Y	free(msg);
Y#endif /*LOG_FILE*/
Y
Y#if defined(SYSLOG)
Y	if (!syslog_open) {
Y		/* we don't use LOG_PID since the pid passed to us by
Y		 * our client may not be our own.  therefore we want to
Y		 * print the pid ourselves.
Y		 */
Y		openlog(ProgramName, LOG_PID, LOG_CRON);
Y		syslog_open = TRUE;		/* assume openlog success */
Y	}
Y
Y	syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
Y
Y#endif /*SYSLOG*/
Y
Y#if DEBUGGING
Y	if (DebugFlags) {
Y		fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
Y			username, pid, event, detail);
Y	}
Y#endif
Y}
Y
Y
Yvoid
Ylog_close() {
Y	if (LogFD != ERR) {
Y		close(LogFD);
Y		LogFD = ERR;
Y	}
Y}
Y
Y
Y/* two warnings:
Y *	(1) this routine is fairly slow
Y *	(2) it returns a pointer to static storage
Y */
Ychar *
Yfirst_word(s, t)
Y	register char *s;	/* string we want the first word of */
Y	char *t;		/* terminators, implicitly including \0 */
Y{
Y	static char retbuf[2][MAX_TEMPSTR + 1];	/* sure wish C had GC */
Y	static int retsel = 0;
Y	register char *rb, *rp;
Y
Y	/* select a return buffer */
Y	retsel = 1-retsel;
Y	rb = &retbuf[retsel][0];
Y	rp = rb;
Y
Y	/* skip any leading terminators */
Y	while (*s && (NULL != strchr(t, *s))) {
Y		s++;
Y	}
Y
Y	/* copy until next terminator or full buffer */
Y	while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
Y		*rp++ = *s++;
Y	}
Y
Y	/* finish the return-string and return it */
Y	*rp = '\0';
Y	return rb;
Y}
Y
Y
Y/* warning:
Y *	heavily ascii-dependent.
Y */
Yvoid
Ymkprint(dst, src, len)
Y	register char *dst;
Y	register unsigned char *src;
Y	register int len;
Y{
Y	while (len-- > 0)
Y	{
Y		register unsigned char ch = *src++;
Y
Y		if (ch < ' ') {			/* control character */
Y			*dst++ = '^';
Y			*dst++ = ch + '@';
Y		} else if (ch < 0177) {		/* printable */
Y			*dst++ = ch;
Y		} else if (ch == 0177) {	/* delete/rubout */
Y			*dst++ = '^';
Y			*dst++ = '?';
Y		} else {			/* parity character */
Y			sprintf(dst, "\\%03o", ch);
Y			dst += 4;
Y		}
Y	}
Y	*dst = '\0';
Y}
Y
Y
Y/* warning:
Y *	returns a pointer to malloc'd storage, you must call free yourself.
Y */
Ychar *
Ymkprints(src, len)
Y	unsigned char *src;
Y	unsigned int len;
Y{
Y	register char *dst = (char *)malloc(len*4 + 1);
Y
Y	mkprint(dst, src, len);
Y
Y	return dst;
Y}
Y
Y
Y#ifdef MAIL_DATE
Y/* Sat, 27 Feb 93 11:44:51 CST
Y * 123456789012345678901234567
Y */
Ychar *
Yarpadate(clock)
Y	time_t *clock;
Y{
Y	time_t t = clock ?*clock :time(0L);
Y	struct tm *tm = localtime(&t);
Y	static char ret[30];	/* zone name might be >3 chars */
Y	
Y	(void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %s",
Y		       DowNames[tm->tm_wday],
Y		       tm->tm_mday,
Y		       MonthNames[tm->tm_mon],
Y		       tm->tm_year,
Y		       tm->tm_hour,
Y		       tm->tm_min,
Y		       tm->tm_sec,
Y		       TZONE(*tm));
Y	return ret;
Y}
Y#endif /*MAIL_DATE*/
Y
Ystatic int save_euid;
Yint swap_uids()
Y	{
Y	save_euid = geteuid();
Y	return(seteuid(getuid()));
Y	}
Y
Yint swap_uids_back()
Y	{
Y	return(seteuid(save_euid));
Y	}
SHAR_EOF
chmod 644 'cron/misc.c'
fi
if test -f 'cron/pathnames.h'
then
	echo shar: "will not over-write existing file 'cron/pathnames.h'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/pathnames.h'
Y/* Copyright 1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y/*
Y * $Id: pathnames.h,v 1.3 1994/01/15 20:43:43 vixie Exp $
Y */
Y
Y#include <paths.h>
Y
Y#ifndef CRONDIR
Y			/* CRONDIR is where crond(8) and crontab(1) both chdir
Y			 * to; SPOOL_DIR, ALLOW_FILE, DENY_FILE, and LOG_FILE
Y			 * are all relative to this directory.
Y			 */
Y#define CRONDIR		"/var/cron"
Y#endif
Y
Y			/* SPOOLDIR is where the crontabs live.
Y			 * This directory will have its modtime updated
Y			 * whenever crontab(1) changes a crontab; this is
Y			 * the signal for crond(8) to look at each individual
Y			 * crontab file and reload those whose modtimes are
Y			 * newer than they were last time around (or which
Y			 * didn't exist last time around...)
Y			 */
Y#define SPOOL_DIR	"tabs"
Y
Y			/* undefining these turns off their features.  note
Y			 * that ALLOW_FILE and DENY_FILE must both be defined
Y			 * in order to enable the allow/deny code.  If neither
Y			 * LOG_FILE or SYSLOG is defined, we don't log.  If
Y			 * both are defined, we log both ways.
Y			 */
Y#define	ALLOW_FILE	"allow"		/*-*/
Y#define DENY_FILE	"deny"		/*-*/
Y#undef	LOG_FILE			/* "log" */
Y
Y			/* where should the daemon stick its PID?
Y			 */
Y#ifdef _PATH_VARRUN
Y# define PIDDIR	_PATH_VARRUN
Y#else
Y# define PIDDIR "/etc/"
Y#endif
Y#define PIDFILE		"%scron.pid"
Y
Y			/* 4.3BSD-style crontab */
Y#define SYSCRONTAB	"/etc/crontab"
Y
Y			/* what editor to use if no EDITOR or VISUAL
Y			 * environment variable specified.
Y			 */
Y#if defined(_PATH_VI)
Y# define EDITOR _PATH_VI
Y#else
Y# define EDITOR "/usr/ucb/vi"
Y#endif
Y
Y#ifndef _PATH_BSHELL
Y# define _PATH_BSHELL "/bin/sh"
Y#endif
Y
Y#ifndef _PATH_DEFPATH
Y# define _PATH_DEFPATH "/usr/bin:/bin"
Y#endif
SHAR_EOF
chmod 644 'cron/pathnames.h'
fi
if test -f 'cron/popen.c'
then
	echo shar: "will not over-write existing file 'cron/popen.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/popen.c'
Y/*
Y * Copyright (c) 1988 The Regents of the University of California.
Y * All rights reserved.
Y *
Y * This code is derived from software written by Ken Arnold and
Y * published in UNIX Review, Vol. 6, No. 8.
Y *
Y * Redistribution and use in source and binary forms are permitted
Y * provided that the above copyright notice and this paragraph are
Y * duplicated in all such forms and that any documentation,
Y * advertising materials, and other materials related to such
Y * distribution and use acknowledge that the software was developed
Y * by the University of California, Berkeley.  The name of the
Y * University may not be used to endorse or promote products derived
Y * from this software without specific prior written permission.
Y * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
Y * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
Y * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
Y *
Y */
Y
Y/* this came out of the ftpd sources; it's been modified to avoid the
Y * globbing stuff since we don't need it.  also execvp instead of execv.
Y */
Y
Y#ifndef lint
Ystatic char rcsid[] = "$Id: popen.c,v 1.5 1994/01/15 20:43:43 vixie Exp $";
Ystatic char sccsid[] = "@(#)popen.c	5.7 (Berkeley) 2/14/89";
Y#endif /* not lint */
Y
Y#include "cron.h"
Y#include <sys/signal.h>
Y
Y
Y#define WANT_GLOBBING 0
Y
Y/*
Y * Special version of popen which avoids call to shell.  This insures noone
Y * may create a pipe to a hidden program as a side effect of a list or dir
Y * command.
Y */
Ystatic PID_T *pids;
Ystatic int fds;
Y
YFILE *
Ycron_popen(program, type)
Y	char *program, *type;
Y{
Y	register char *cp;
Y	FILE *iop;
Y	int argc, pdes[2];
Y	PID_T pid;
Y	char *argv[100];
Y#if WANT_GLOBBING
Y	char **pop, *vv[2];
Y	int gargc;
Y	char *gargv[1000];
Y	extern char **glob(), **copyblk();
Y#endif
Y
Y	if (*type != 'r' && *type != 'w' || type[1])
Y		return(NULL);
Y
Y	if (!pids) {
Y		if ((fds = getdtablesize()) <= 0)
Y			return(NULL);
Y		if (!(pids = (PID_T *)malloc((u_int)(fds * sizeof(PID_T)))))
Y			return(NULL);
Y		bzero((char *)pids, fds * sizeof(PID_T));
Y	}
Y	if (pipe(pdes) < 0)
Y		return(NULL);
Y
Y	/* break up string into pieces */
Y	for (argc = 0, cp = program;; cp = NULL)
Y		if (!(argv[argc++] = strtok(cp, " \t\n")))
Y			break;
Y
Y#if WANT_GLOBBING
Y	/* glob each piece */
Y	gargv[0] = argv[0];
Y	for (gargc = argc = 1; argv[argc]; argc++) {
Y		if (!(pop = glob(argv[argc]))) {	/* globbing failed */
Y			vv[0] = argv[argc];
Y			vv[1] = NULL;
Y			pop = copyblk(vv);
Y		}
Y		argv[argc] = (char *)pop;		/* save to free later */
Y		while (*pop && gargc < 1000)
Y			gargv[gargc++] = *pop++;
Y	}
Y	gargv[gargc] = NULL;
Y#endif
Y
Y	iop = NULL;
Y	switch(pid = vfork()) {
Y	case -1:			/* error */
Y		(void)close(pdes[0]);
Y		(void)close(pdes[1]);
Y		goto pfree;
Y		/* NOTREACHED */
Y	case 0:				/* child */
Y		if (*type == 'r') {
Y			if (pdes[1] != 1) {
Y				dup2(pdes[1], 1);
Y				dup2(pdes[1], 2);	/* stderr, too! */
Y				(void)close(pdes[1]);
Y			}
Y			(void)close(pdes[0]);
Y		} else {
Y			if (pdes[0] != 0) {
Y				dup2(pdes[0], 0);
Y				(void)close(pdes[0]);
Y			}
Y			(void)close(pdes[1]);
Y		}
Y#if WANT_GLOBBING
Y		execvp(gargv[0], gargv);
Y#else
Y		execvp(argv[0], argv);
Y#endif
Y		_exit(1);
Y	}
Y	/* parent; assume fdopen can't fail...  */
Y	if (*type == 'r') {
Y		iop = fdopen(pdes[0], type);
Y		(void)close(pdes[1]);
Y	} else {
Y		iop = fdopen(pdes[1], type);
Y		(void)close(pdes[0]);
Y	}
Y	pids[fileno(iop)] = pid;
Y
Ypfree:
Y#if WANT_GLOBBING
Y	for (argc = 1; argv[argc] != NULL; argc++) {
Y/*		blkfree((char **)argv[argc]);	*/
Y		free((char *)argv[argc]);
Y	}
Y#endif
Y	return(iop);
Y}
Y
Yint
Ycron_pclose(iop)
Y	FILE *iop;
Y{
Y	register int fdes;
Y	int omask;
Y	WAIT_T stat_loc;
Y	PID_T pid;
Y
Y	/*
Y	 * pclose returns -1 if stream is not associated with a
Y	 * `popened' command, or, if already `pclosed'.
Y	 */
Y	if (pids == 0 || pids[fdes = fileno(iop)] == 0)
Y		return(-1);
Y	(void)fclose(iop);
Y	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
Y	while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1)
Y		;
Y	(void)sigsetmask(omask);
Y	pids[fdes] = 0;
Y	return (pid == -1 ? -1 : WEXITSTATUS(stat_loc));
Y}
SHAR_EOF
chmod 644 'cron/popen.c'
fi
if test -f 'cron/putman.sh'
then
	echo shar: "will not over-write existing file 'cron/putman.sh'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/putman.sh'
Y#!/bin/sh
Y
Y# putman.sh - install a man page according to local custom
Y# vixie 27dec93 [original]
Y#
Y# $Id:$
Y
YPAGE=$1
YDIR=$2
Y
YSECT=`expr $PAGE : '[a-z]*.\([0-9]\)'`
YMDIR="$DIR/cat$SECT"
YDEST="$MDIR/`basename $PAGE .$SECT`.0"
Y
Yset -x
Yif [ ! -d $MDIR ]; then
Y	rm -f $MDIR
Y	mkdir -p $MDIR
Y	chmod 755 $MDIR
Yfi
Y
Ynroff -man $PAGE >$DEST
Ychmod 444 $DEST
Yset +x
Y
Yexit 0
SHAR_EOF
chmod 644 'cron/putman.sh'
fi
if test -f 'cron/user.c'
then
	echo shar: "will not over-write existing file 'cron/user.c'"
else
sed 's/^Y//' << \SHAR_EOF > 'cron/user.c'
Y/* Copyright 1988,1990,1993,1994 by Paul Vixie
Y * All rights reserved
Y *
Y * Distribute freely, except: don't remove my name from the source or
Y * documentation (don't take credit for my work), mark your changes (don't
Y * get me blamed for your possible bugs), don't alter or remove this
Y * notice.  May be sold if buildable source is provided to buyer.  No
Y * warrantee of any kind, express or implied, is included with this
Y * software; use at your own risk, responsibility for damages (if any) to
Y * anyone resulting from the use of this software rests entirely with the
Y * user.
Y *
Y * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
Y * I'll try to keep a version up to date.  I can be reached as follows:
Y * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
Y */
Y
Y#if !defined(lint) && !defined(LINT)
Ystatic char rcsid[] = "$Id: user.c,v 2.8 1994/01/15 20:43:43 vixie Exp $";
Y#endif
Y
Y/* vix 26jan87 [log is in RCS file]
Y */
Y
Y
Y#include "cron.h"
Y
Y
Yvoid
Yfree_user(u)
Y	user	*u;
Y{
Y	register entry	*e, *ne;
Y
Y	free(u->name);
Y	for (e = u->crontab;  e != NULL;  e = ne) {
Y		ne = e->next;
Y		free_entry(e);
Y	}
Y	free(u);
Y}
Y
Y
Yuser *
Yload_user(crontab_fd, pw, name)
Y	int		crontab_fd;
Y	struct passwd	*pw;		/* NULL implies syscrontab */
Y	char		*name;
Y{
Y	char	envstr[MAX_ENVSTR];
Y	FILE	*file;
Y	register user	*u;
Y	register entry	*e;
Y	int	status;
Y	char	**envp;
Y
Y	if (!(file = fdopen(crontab_fd, "r"))) {
Y		perror("fdopen on crontab_fd in load_user");
Y		return NULL;
Y	}
Y
Y	Debug(DPARS, ("load_user()\n"))
Y
Y	/* file is open.  build user entry, then read the crontab file.
Y	 */
Y	u = (user *) malloc(sizeof(user));
Y	u->name = strdup(name);
Y	u->crontab = NULL;
Y
Y	/* 
Y	 * init environment.  this will be copied/augmented for each entry.
Y	 */
Y	envp = env_init();
Y
Y	/*
Y	 * load the crontab
Y	 */
Y	while ((status = load_env(envstr, file)) >= OK) {
Y		switch (status) {
Y		case ERR:
Y			free_user(u);
Y			u = NULL;
Y			goto done;
Y		case FALSE:
Y			e = load_entry(file, NULL, pw, envp);
Y			if (e) {
Y				e->next = u->crontab;
Y				u->crontab = e;
Y			}
Y			break;
Y		case TRUE:
Y			envp = env_set(envp, envstr);
Y			break;
Y		}
Y	}
Y
Y done:
Y	env_free(envp);
Y	fclose(file);
Y	Debug(DPARS, ("...load_user() done\n"))
Y	return u;
Y}
SHAR_EOF
chmod 644 'cron/user.c'
fi
exit 0
#	End of shell archive
