[comp.sources.misc] PD Cron 02/03

paul@vixie.UUCP (Paul Vixie Esq) (06/12/87)

#! /bin/sh
##  This is a shell archive.  Remove anything before this line, then unpack
##  it by saving it into a file and typing "sh file".  To overwrite existing
##  files, type "sh file -c".  You can also feed this as standard input via
##  unshar, or by typing "sh <file".  If this archive is complete, you will
##  see the following message at the end:
#		"End of archive 2 (of 2)."
# Contents:  cron.h crontab.c do_command.c entry.c
# Wrapped by paul@vixie on Wed May  6 10:18:56 1987
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f cron.h -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"cron.h\"
else
echo shar: Extracting \"cron.h\" \(7420 characters\)
sed "s/^X//" >cron.h <<'END_OF_cron.h'
X/* cron.h - header for vixie's cron
X *
X * $Header: cron.h,v 1.4 87/05/02 17:33:08 paul Exp $
X * $Source: /usr/src/local/vix/cron/cron.h,v $
X * $Revision: 1.4 $
X * $Log:	cron.h,v $
X * Revision 1.4  87/05/02  17:33:08  paul
X * baseline for mod.sources release
X * 
X * Revision 1.3  87/03/31  12:07:13  paul
X * more and more and more suggestions from rs@mirror
X * 
X * Revision 1.2  87/02/13  00:29:14  paul
X * getting ready for beta test
X * 
X * Revision 1.1  87/02/13  00:26:01  paul
X * Initial revision
X *
X * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
X * vix 30dec86 [written]
X */
X
X/* Copyright 1987 by Vixie Enterprises
X * All rights reserved
X *
X * Distribute freely, except: don't sell it, don't remove my name from the
X * source or documentation (don't take credit for my work), mark your changes
X * (don't get me blamed for your possible bugs), don't alter or remove this
X * notice.  Commercial redistribution is negotiable; contact me for details.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114,
X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul.
X */
X
X#ifndef	_CRON_FLAG
X#define	_CRON_FLAG
X
X#include <stdio.h>
X#include <ctype.h>
X#include <bitstring.h>
X
X/*
X * these are site-dependent
X */
X			/*
X			 * choose one of these MAILCMD commands.  I use
X			 * /bin/mail for speed; it makes 'biff' go but doesn't
X			 * do aliasing.  /usr/lib/sendmail does aliasing but is
X			 * a hog for short messages.
X			 */
X
X/*# define MAILCMD "/usr/lib/sendmail -F\"Vcron Daemon\" -odi -oem  %s"  /*-*/
X			/* -Fx	= set full-name of sender
X			 * -odi	= Option Deliverymode Interactive (synchronous)
X			 * -oem	= Option Errors Mailedtosender
X			 */
X
X# define MAILCMD "/bin/mail -d  %s"		/*-*/
X			/* -d = undocumented but common flag: deliver locally?
X			 */
X
X			/* LIBDIR is unrelated to /usr/lib; it's actually
X			 * the base of the cron spool tree.  any control
X			 * files for cron will be there; also a directory
X			 * called 'crontabs' will be there to hold the
X			 * individual cron tabs.  this is less clean than
X			 * a /usr/spool/cron, /usr/lib/cron; you can change
X			 * it to be that way at your own taste.  remember
X			 * that SysV does it /usr/spool/cron, .../crontabs.
X			 */
X#define	LIBDIR		"/usr/spool/cron/%s"
X
X			/* SPOOLDIR is where the crontabs live
X			 */
X#define	SPOOLDIR	"/usr/spool/cron/crontabs/%s"
X
X			/* this file is created by 'crontab' when it changes
X			 * a cron tab; 'cron' sees it and reloads its tables.
X			 */
X#define	POKECRON	"/usr/spool/cron/POKECRON"
X
X			/* this is the name of the environment variable
X			 * that contains the user name.  it isn't read by
X			 * cron, but it is SET by crond in the environments
X			 * it creates for subprocesses.  on BSD, it will
X			 * always be USER; on SysV it could be LOGNAME or
X			 * something else.
X			 */
X#define	USERENV		"USER"
X
X/*
X * you shouldn't have to change anything after this
X */
X
X
X	/* these are really immutable, and are
X	 *   defined for symbolic convenience only
X	 * TRUE, FALSE, and ERR must be distinct
X	 */
X#define TRUE		1
X#define FALSE		0
X	/* system calls return this on success */
X#define OK		0
X	/*   or this on error */
X#define ERR		(-1)
X
X	/* turn this on to get '-x' code */
X#define DEBUGGING	FALSE
X
X#define READ_PIPE	0	/* which end of a pipe pair do you read? */
X#define WRITE_PIPE	1	/*   or write to? */
X#define STDIN		0	/* what is stdin's file descriptor? */
X#define STDOUT		1	/*   stdout's? */
X#define STDERR		2	/*   stderr's? */
X#define VFORK		vfork	/* sometimes vfork might be too virtual */
X#define ERROR_EXIT	1	/* exit() with this will scare the shell */
X#define	OK_EXIT		0	/* exit() with this is considered 'normal' */
X#define	MAX_FNAME	100	/* max length of internally generated fn */
X#define	MAX_COMMAND	1000	/* max length of internally generated cmd */
X#define	MAX_TEMPSTR	100	/* obvious */
X#define	MAX_UNAME	20	/* max length of username, should be overkill */
X#define	ROOT_UID	0	/* don't change this, it really must be root */
X#define	DEXT		0x0001	/* extend flag for other debug masks */
X#define	DSCH		0x0002	/* scheduling debug mask */
X#define	DPROC		0x0004	/* process control debug mask */
X#define	DPARS		0x0008	/* parsing debug mask */
X#define	DMISC		0x0010	/* misc debug flag */
X#define	DCOUNT		5	/* how many debug bits are in use? */
X
X#define	MAILSUBJ	"one of your cron commands generated this output"
X#define	REG		register
X#define	PPC_NULL	((char **)NULL)
X
X#define	Skip_Blanks(c, f) \
X			while (c == '\t' || c == ' ') \
X				c = get_char(f);
X
X#define	Skip_Nonblanks(c, f) \
X			while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
X				c = get_char(f);
X
X#define	Skip_Line(c, f) \
X			do {c = get_char(f);} while (c != '\n' && c != EOF);
X
X#if DEBUGGING
X# define Debug(mask, message) \
X			if ( (DEBUG_FLAGS & (mask) ) == (mask) ) \
X				printf message;
X#else /* !DEBUGGING */
X# define Debug(mask, message) \
X			;
X#endif /* DEBUGGING */
X
X#define	MkLower(ch)	(isupper(ch) ? tolower(ch) : ch)
X#define	MkUpper(ch)	(islower(ch) ? toupper(ch) : ch)
X#define	Set_LineNum(ln)	{Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
X			 LINE_NUMBER = ln; \
X			}
X
X#define	FIRST_MINUTE	0
X#define	LAST_MINUTE	59
X#define	MINUTE_COUNT	(LAST_MINUTE - FIRST_MINUTE + 1)
X
X#define	FIRST_HOUR	0
X#define	LAST_HOUR	23
X#define	HOUR_COUNT	(LAST_HOUR - FIRST_HOUR + 1)
X
X#define	FIRST_DOM	1
X#define	LAST_DOM	31
X#define	DOM_COUNT	(LAST_DOM - FIRST_DOM + 1)
X
X#define	FIRST_MONTH	1
X#define	LAST_MONTH	12
X#define	MONTH_COUNT	(LAST_MONTH - FIRST_MONTH + 1)
X
X#define	FIRST_DOW	0
X#define	LAST_DOW	7
X#define	DOW_COUNT	(LAST_DOW - FIRST_DOW + 1)
X
X			/* each users' crontab will be held as a list of
X			 * the following structure.
X			 */
X
Xtypedef	struct	_entry
X	{
X		struct _entry	*next;
X		char		*cmd;
X		bit_decl(	minute,	MINUTE_COUNT	)
X		bit_decl(	hour,	HOUR_COUNT	)
X		bit_decl(	dom,	DOM_COUNT	)
X		bit_decl(	month,	MONTH_COUNT	)
X		bit_decl(	dow,	DOW_COUNT	)
X		int		dom_star, dow_star;
X	}
X	entry;
X
X			/* the crontab database will be a list of the
X			 * following structure, one element per user.
X			 */
X
Xtypedef	struct	_user
X	{
X		struct _user	*next;		/* next item in list */
X		int		uid;		/* uid from passwd file */
X		int		gid;		/* gid from passwd file */
X		char		**envp;		/* environ for commands */
X		entry		*crontab;	/* this person's crontab */
X	}
X	user;
X
X				/* in the C tradition, we only create
X				 * variables for the main program, just
X				 * extern them elsewhere.
X				 */
X
X#ifdef MAIN_PROGRAM
X
X# if !defined(LINT) && !defined(lint)
X		static char *copyright[] = {
X			"@(#) Copyright (C) 1987 by Vixie Enterprises",
X			"@(#) All rights reserved"
X		};
X# endif
X
X		char *MONTH_NAMES[] = {
X			"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
X			NULL};
X		char *DOW_NAMES[] = {
X			"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
X			NULL};
X		char *PROGNAME;
X		int LINE_NUMBER;
X
X# if DEBUGGING
X		int DEBUG_FLAGS;
X		char *DEBUG_FLAG_NAMES[] = {	/* sync with #defines */
X			"ext", "sch", "proc", "pars", "misc",
X			NULL};
X# endif /* DEBUGGING */
X
X#else /* !MAIN_PROGRAM */
X
X		extern char	*MONTH_NAMES[];
X		extern char	*DOW_NAMES[];
X		extern char	*PROGNAME;
X		extern int	LINE_NUMBER;
X# if DEBUGGING
X		extern int	DEBUG_FLAGS;
X		extern char	*DEBUG_FLAG_NAMES[];
X# endif /* DEBUGGING */
X#endif /* MAIN_PROGRAM */
X
X
X#endif	/* _CRON_FLAG */
END_OF_cron.h
if test 7420 -ne `wc -c <cron.h`; then
    echo shar: \"cron.h\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f crontab.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"crontab.c\"
else
echo shar: Extracting \"crontab.c\" \(7017 characters\)
sed "s/^X//" >crontab.c <<'END_OF_crontab.c'
X#if !defined(lint) && !defined(LINT)
Xstatic char rcsid[] = "$Header: crontab.c,v 1.5 87/05/02 17:33:22 paul Exp $";
X#endif
X
X/* $Source: /usr/src/local/vix/cron/crontab.c,v $
X * $Revision: 1.5 $
X * $Log:	crontab.c,v $
X * Revision 1.5  87/05/02  17:33:22  paul
X * baseline for mod.sources release
X * 
X * Revision 1.4  87/03/31  13:11:48  paul
X * I won't say that rs@mirror gave me this idea
X *   but crontab uses getopt() now
X * 
X * Revision 1.3  87/03/30  23:43:48  paul
X * another suggestion from rs@mirror:
X *   use getpwuid(getuid)->pw_name instead of getenv("USER")
X *   this is a boost to security...
X * 
X * Revision 1.2  87/02/11  17:40:12  paul
X * changed command syntax to allow append and replace instead of append as
X * default and no replace at all.
X * 
X * Revision 1.1  87/01/26  23:49:06  paul
X * Initial revision
X */
X
X/* Copyright 1987 by Vixie Enterprises
X * All rights reserved
X *
X * Distribute freely, except: don't sell it, don't remove my name from the
X * source or documentation (don't take credit for my work), mark your changes
X * (don't get me blamed for your possible bugs), don't alter or remove this
X * notice.  Commercial redistribution is negotiable; contact me for details.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114,
X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul.
X */
X
X
X#define	MAIN_PROGRAM
X
X
X#include "cron.h"
X#include <pwd.h>
X#include <errno.h>
X#include <sys/file.h>
X
X
Xextern	char	*sprintf();
X
X
Xstatic char	USER[MAX_UNAME];
Xstatic char	FILENAME[MAX_FNAME];
Xstatic int	CHECK_ERROR_COUNT;
Xstatic enum	{opt_unknown, opt_list, opt_delete, opt_replace, opt_append}
X		OPTION;
X
X#if DEBUGGING
Xstatic char	*OPTIONS[] = {"???", "list", "delete", "replace", "append"};
X#endif
X
Xstatic void
Xusage()
X{
X	fprintf(stderr, "usage:  %s [-u user] ...\n", PROGNAME);
X	fprintf(stderr, " ... -l         (list user's crontab)\n");
X	fprintf(stderr, " ... -d         (delete user's crontab)\n");
X	fprintf(stderr, " ... -r file    (replace user's crontab)\n");
X	fprintf(stderr, " ... -a file    (append file to user's crontab)\n");
X	exit(ERROR_EXIT);
X}
X
X
Xvoid
Xmain(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	void	parse_args(), set_cron_uid(),
X		append_cmd(), list_cmd(), delete_cmd(), replace_cmd();
X
X	PROGNAME = argv[0];
X
X	setlinebuf(stderr);
X	parse_args(argc, argv);
X	set_cron_uid();
X	switch (OPTION)
X	{
X	case opt_list:		list_cmd();
X				break;
X	case opt_delete:	delete_cmd();
X				break;
X	case opt_replace:	replace_cmd();
X				append_cmd();
X				break;
X	case opt_append:	append_cmd();
X				break;
X	}
X}
X
X
Xstatic void
Xlist_cmd()
X{
X	extern	errno;
X	char	n[MAX_FNAME];
X	FILE	*f;
X	int	ch;
X
X	(void) sprintf(n, SPOOLDIR, USER);
X	if (!(f = fopen(n, "r")))
X	{
X		if (errno == ENOENT)
X			fprintf(stderr, "no crontab for %s\n", USER);
X		else
X			perror(n);
X		exit(ERROR_EXIT);
X	}
X
X	/* file it open. copy to stdout, close.
X	 */
X	Set_LineNum(1)
X	while (EOF != (ch = get_char(f)))
X		putchar(ch);
X	fclose(f);
X}
X
X
Xstatic void
Xdelete_cmd()
X{
X	extern	errno;
X	int	unlink();
X	void	poke_daemon();
X
X	char	n[MAX_FNAME];
X
X	(void) sprintf(n, SPOOLDIR, USER);
X
X	if (unlink(n))
X	{
X		if (errno == ENOENT)
X			fprintf(stderr, "no crontab for %s\n", USER);
X		else
X			perror(n);
X		exit(ERROR_EXIT);
X	}
X	poke_daemon();
X}
X
X
Xstatic void
Xreplace_cmd()
X{
X	extern	errno;
X	int	unlink();
X	void	poke_daemon();
X
X	char	n[MAX_FNAME];
X
X	(void) sprintf(n, SPOOLDIR, USER);
X
X	if (unlink(n))
X	{
X		if (errno != ENOENT)
X		{
X			perror(n);
X			exit(ERROR_EXIT);
X		}
X	}
X}
X
X
Xstatic void
Xcheck_error(msg)
X	char	*msg;
X{
X	CHECK_ERROR_COUNT += 1;
X	fprintf(stderr, "\"%s\", line %d: %s\n", FILENAME, LINE_NUMBER, msg);
X}
X
X
Xstatic void
Xappend_cmd()
X{
X	char	*sprintf();
X	entry	*load_entry();
X	int	load_env();
X	void	free_entry();
X	void	check_error();
X	void	poke_daemon();
X
X	char	n[MAX_FNAME], envstr[MAX_TEMPSTR];
X	FILE	*old, *new;
X	int	ch;
X	entry	*e;
X	int	status;
X
X	/* this routine is called for both 'replace' and 'append' commands
X	 */
X
X	/* open the new file being installed.
X	 */
X	if (!(new = fopen(FILENAME, "r")))
X	{
X		perror(FILENAME);
X		exit(ERROR_EXIT);
X	}
X
X	/* check the syntax of the file being installed.
X	 */
X
X	/* BUG: was reporting errors after the EOF if there were any errors
X	 * in the file proper -- kludged it by stopping after first error.
X	 *		vix 31mar87
X	 */
X	CHECK_ERROR_COUNT = 0;
X	while (!CHECK_ERROR_COUNT && ERR != (status = load_env(envstr, new)))
X	{
X		if (status == FALSE)
X		{
X			if (NULL != (e = load_entry(new, check_error)))
X				free((char *) e);
X		}
X	}
X	if (CHECK_ERROR_COUNT != 0)
X	{
X		fprintf(stderr, "errors in crontab file, can't install.\n");
X		fclose(new);
X		exit(ERROR_EXIT);
X	}
X	rewind(new);
X
X	/* open the old file for append-access.  this depends on the file
X	 * being created if it does not exist.
X	 */
X	(void) sprintf(n, SPOOLDIR, USER);
X	if (!(old = fopen(n, "a")))
X	{
X		perror(n);
X		fclose(new);
X		exit(ERROR_EXIT);
X	}
X
X	/* append the new to the old
X	 */
X	Set_LineNum(1)
X	while (EOF != (ch = get_char(new)))
X		putc(ch, old);
X
X	/* close them and we're done
X	 */
X	fclose(old);
X	fclose(new);
X
X	/* set the file to be owned by root
X	 */
X	if (ERR == chown(n, ROOT_UID, -1))
X	{
X		perror("chown");
X		exit(ERROR_EXIT);
X	}
X
X	poke_daemon();
X}
X
X
Xstatic void
Xpoke_daemon()
X{
X	int	fd;
X
X	if (ERR == (fd = open(POKECRON, O_WRONLY|O_CREAT, 0)))
X	{
X		perror(POKECRON);
X		return;
X	}
X	close(fd);
X}
X	
X
Xstatic void
Xparse_args(argc, argv)
X	int	argc;
X	char	*argv[];
X{
X	void		usage();
X	char		*getenv(), *strcpy();
X	int		getuid();
X	struct passwd	*getpwnam();
X	extern int	getopt(), optind;
X	extern char	*optarg;
X
X	struct passwd	*pw;
X	int		argch;
X
X	if (!(pw = getpwuid(getuid())))
X	{
X		fprintf(stderr, "Your UID isn't in the passwd file.\n");
X		perror("getpwuid");
X	}
X	strcpy(USER, pw->pw_name);
X	FILENAME[0] = '\0';
X	OPTION = opt_unknown;
X	while (EOF != (argch = getopt(argc, argv, "u:ldr:a:x:")))
X	{
X		switch (argch)
X		{
X		case 'x':
X			if (!set_debug_flags(optarg))
X				usage();
X			break;
X		case 'u':
X			if (getuid() != ROOT_UID)
X			{
X				fprintf(stderr,
X				"must be privileged to use -u\n"
X				);
X				exit(ERROR_EXIT);
X			}
X			if ((struct passwd *)NULL == getpwnam(optarg))
X			{
X				fprintf(stderr, "%s:  user '%s' unknown\n",
X					PROGNAME, optarg);
X				exit(ERROR_EXIT);
X			}
X			(void) strcpy(USER, optarg);
X			break;
X		case 'l':
X			if (OPTION != opt_unknown)
X				usage();
X			OPTION = opt_list;
X			break;
X		case 'd':
X			if (OPTION != opt_unknown)
X				usage();
X			OPTION = opt_delete;
X			break;
X		case 'a':
X			if (OPTION != opt_unknown)
X				usage();
X			OPTION = opt_append;
X			(void) strcpy(FILENAME, optarg);
X			break;
X		case 'r':
X			if (OPTION != opt_unknown)
X				usage();
X			OPTION = opt_replace;
X			(void) strcpy(FILENAME, optarg);
X			break;
X		default:
X			usage();
X		}
X	}
X
X	if (OPTION == opt_unknown || argv[optind] != NULL)
X		usage();
X
X	endpwent();
X
X	Debug(DMISC, ("user=%s, file=%s, option=%s\n",
X					USER, FILENAME, OPTIONS[(int)OPTION]))
X}
END_OF_crontab.c
if test 7017 -ne `wc -c <crontab.c`; then
    echo shar: \"crontab.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f do_command.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"do_command.c\"
else
echo shar: Extracting \"do_command.c\" \(9944 characters\)
sed "s/^X//" >do_command.c <<'END_OF_do_command.c'
X#if !defined(lint) && !defined(LINT)
Xstatic char rcsid[] = "$Header: do_command.c,v 1.4 87/05/02 17:33:35 paul Exp $";
X#endif
X
X/* $Source: /usr/src/local/vix/cron/do_command.c,v $
X * $Revision: 1.4 $
X * $Log:	do_command.c,v $
X * Revision 1.4  87/05/02  17:33:35  paul
X * baseline for mod.sources release
X * 
X * Revision 1.3  87/04/09  00:03:58  paul
X * improved data hiding, locality of declaration/references
X * fixed a rs@mirror bug by redesigning the mailto stuff completely
X * 
X * Revision 1.2  87/03/19  12:46:24  paul
X * implemented suggestions from rs@mirror (Rich $alz):
X *    MAILTO="" means no mail should be sent
X *    various fixes of bugs or lint complaints
X *    put a To: line in the mail message
X * 
X * Revision 1.1  87/01/26  23:47:00  paul
X * Initial revision
X */
X
X/* Copyright 1987 by Vixie Enterprises
X * All rights reserved
X *
X * Distribute freely, except: don't sell it, don't remove my name from the
X * source or documentation (don't take credit for my work), mark your changes
X * (don't get me blamed for your possible bugs), don't alter or remove this
X * notice.  Commercial redistribution is negotiable; contact me for details.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114,
X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul.
X */
X
X
X#include "cron.h"
X#include <signal.h>
X#include <pwd.h>
X#if defined(BSD)
X# include <syslog.h>
X# include <sys/wait.h>
X#endif /*BSD*/
X
Xvoid
Xdo_command(cmd, u)
X	char	*cmd;
X	user	*u;
X{
X	extern int	fork(), _exit();
X	extern char	*env_get();
X	extern void	child_process();
X
X	Debug(DPROC, ("do_command(%s, (%s,%d,%d))\n",
X		cmd, env_get(USERENV, u->envp), u->uid, u->gid))
X
X	/* fork to become asyncronous -- parent process is done immediately,
X	 * and continues to run the normal cron code, which means return to
X	 * tick().  the child and grandchild don't leave this function, alive.
X	 *
X	 * vfork() is unsuitable, since we have much to do, and the parent
X	 * needs to be able to run off and fork other processes.
X	 */
X	if (fork() == 0)
X	{
X		child_process(cmd, u);
X		Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
X		_exit(OK_EXIT);
X	}
X	Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
X}
X
X
Xvoid
Xchild_process(cmd, u)
X	char	*cmd;
X	user	*u;
X{
X	extern struct passwd	*getpwnam();
X	extern void	sigpipe_func(), be_different();
X	extern int	VFORK();
X	extern char	*index(), *env_get();
X
X	auto int	stdin_pipe[2], stdout_pipe[2];
X	register char	*input_data;
X
X
X	Debug(DPROC, ("[%d] child process running\n", getpid()))
X
X	/* mark ourselves as different to PS command watchers by upshifting
X	 * our program name.
X	 */
X	{
X		register char	*pch;
X
X		for (pch = PROGNAME;  *pch;  pch++)
X			*pch = MkUpper(*pch);
X	}
X
X#if defined(BSD)
X	/* our parent is watching for our death by catching SIGCHLD.  we
X	 * do not care to watch for our children's deaths this way -- we
X	 * use wait() explictly.  so we have to disable the signal (which
X	 * was inherited from the parent.
X	 *
X	 * this isn't needed for system V, since our parent is already
X	 * SIG_IGN on SIGCLD -- which, hopefully, will cause children to
X	 * simply vanish when then die.
X	 */
X	(void) signal(SIGCHLD, SIG_IGN);
X#endif /*BSD*/
X
X	/* create some pipes to talk to our future child
X	 */
X	pipe(stdin_pipe);	/* child's stdin */
X	pipe(stdout_pipe);	/* child's stdout */
X	
X	/* since we are a forked process, we can diddle the command string
X	 * we were passed -- nobody else is going to use it again, right?
X	 *
X	 * if a % is present in the command, previous characters are the
X	 * command, and subsequent characters are the additional input to
X	 * the command.  Subsequent %'s will be transformed into newlines,
X	 * but that happens later.
X	 */
X	if (NULL == (input_data = index(cmd, '%')))
X	{
X		/* no %.  point input_data at a null string.
X		 */
X		input_data = "";
X	}
X	else
X	{
X		/* % found.  replace with a null (remember, we're a forked
X		 * process and the string won't be reused), and increment
X		 * input_data to point at the following character.
X		 */
X		*input_data++ = '\0';
X	}
X
X	/* fork again, this time so we can exec the users' command.
X	 */
X	if (VFORK() == 0)
X	{
X		Debug(DPROC, ("[%d] grandchild process VFORK()'ed\n", getpid()))
X
X		/* get new pgrp, void tty, etc.
X		 */
X		be_different();
X
X		/* close the pipe ends that we won't use.  this doesn't affect
X		 * the parent, who has to read and write them; it keeps the
X		 * kernel from recording us as a potential client TWICE --
X		 * which would keep it from sending SIGPIPE in otherwise
X		 * appropriate circumstances.
X		 */
X		close(stdin_pipe[WRITE_PIPE]);
X		close(stdout_pipe[READ_PIPE]);
X
X		/* grandchild process.  make std{in,out} be the ends of
X		 * pipes opened by our daddy; make stderr go to stdout.
X		 */
X		close(STDIN);	dup2(stdin_pipe[READ_PIPE], STDIN);
X		close(STDOUT);	dup2(stdout_pipe[WRITE_PIPE], STDOUT);
X		close(STDERR);	dup2(STDOUT, STDERR);
X
X		/* close the pipes we just dup'ed.  The resources will remain,
X		 * since they've been dup'ed... :-)...
X		 */
X		close(stdin_pipe[READ_PIPE]);
X		close(stdout_pipe[WRITE_PIPE]);
X
X		/* set our directory, uid and gid.
X		 */
X		setgid(u->gid);		/* set group first! */
X		initgroups(env_get(USERENV, u->envp), u->gid);
X		setuid(u->uid);		/* you aren't root after this... */
X		chdir(env_get("HOME", u->envp));
X
X		/* exec the command.
X		 */
X		{
X			char	*shell = env_get("SHELL", u->envp);
X
X			execle(shell, shell, "-c", cmd, (char *)0, u->envp);
X			fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
X			perror("execl");
X			_exit(ERROR_EXIT);
X		}
X	}
X
X	/* middle process, child of original cron, parent of process running
X	 * the user's command.
X	 */
X
X	Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
X
X	/* close the ends of the pipe that will only be referenced in the
X	 * son process...
X	 */
X	close(stdin_pipe[READ_PIPE]);
X	close(stdout_pipe[WRITE_PIPE]);
X
X	/*
X	 * write, to the pipe connected to child's stdin, any input specified
X	 * after a % in the crontab entry; we will assume that it will all
X	 * fit, which means (on BSD anyway) that it should be 4096 bytes or
X	 * less.  this seems reasonable.  while we copy, convert any
X	 * additional %'s to newlines.  when done, if some characters were
X	 * written and the last one wasn't a newline, write a newline.
X	 */
X
X	Debug(DPROC, ("[%d] child sending data to grandchild\n", getpid()))
X
X	{
X		register FILE	*out = fdopen(stdin_pipe[WRITE_PIPE], "w");
X		register int	need_newline = FALSE;
X		register int	escaped = FALSE;
X
X		while (*input_data)
X		{
X			if (!escaped && *input_data == '%')
X			{
X				need_newline = FALSE;
X				putc('\n', out);
X			}
X			else
X			{
X				need_newline = TRUE;
X				putc(*input_data, out);
X				escaped = (*input_data == '\\');
X			}
X			input_data++;
X		}
X		if (need_newline)
X			putc('\n', out);
X
X		/* write 0-length message; this is EOF in a pipe (I hope)
X		 */
X		fflush(out);
X		write(stdin_pipe[WRITE_PIPE], "", 0);
X		fclose(out);
X		close(stdin_pipe[WRITE_PIPE]);
X
X		Debug(DPROC, ("[%d] child done sending to grandchild\n", getpid()))
X	}
X
X	/*
X	 * read output from the grandchild.  it's stderr has been redirected to
X	 * it's stdout, which has been redirected to our pipe.  if there is any
X	 * output, we'll be mailing it to the user whose crontab this is...
X	 * when the grandchild exits, we'll get EOF.
X	 */
X
X	Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
X
X	{
X		register FILE	*in = fdopen(stdout_pipe[READ_PIPE], "r");
X		register int	ch;
X
X		if (EOF != (ch = getc(in)))
X		{
X			FILE	*mail;
X			char	*usernm, *mailto;
X
X			Debug(DPROC, ("[%d] got data (%x:%c) from grandchild\n",
X				getpid(), ch, ch))
X
X			/* get name of recipient.  this is MAILTO if set to a
X			 * valid local username; USER otherwise.
X			 */
X			usernm = env_get(USERENV, u->envp);
X			mailto = env_get("MAILTO", u->envp);
X			if (mailto)
X			{
X				/* MAILTO was present in the environment
X				 */
X				if (!*mailto)
X				{
X					/* ... but it's empty. set to NULL
X					 */
X					mailto = NULL;
X				}
X				else
X				{
X					/* not empty -- verify it,
X					 * setting to USER if not valid.
X					 */
X					if (NULL == getpwnam(mailto))
X						mailto = usernm;
X					endpwent();
X				}
X			}
X			else
X			{
X				/* MAILTO not present, set to USER.
X				 */
X				mailto = usernm;
X			}
X		
X			/* if we are supposed to be mailing, MAILTO will
X			 * be non-NULL.  only in this case should we set
X			 * up the mail command and subjects and stuff...
X			 */
X
X			if (mailto)
X			{
X				extern FILE	*popen();
X				extern char	*sprintf();
X				register char	**env;
X				auto char	mailcmd[MAX_COMMAND];
X
X				(void) sprintf(mailcmd, MAILCMD, mailto);
X				if (!(mail = popen(mailcmd, "w")))
X				{
X					perror(MAILCMD);
X					(void) _exit(ERROR_EXIT);
X				}
X				fprintf(mail, "To: %s\n", mailto);
X				fprintf(mail, "Subject: %s\n", MAILSUBJ);
X				fprintf(mail, "X-cron-cmd: <%s>\n", cmd);
X				for (env = u->envp;  *env;  env++)
X					fprintf(mail, "X-cron-env: <%s>\n",
X						*env);
X				fprintf(mail, "\n");
X
X				/* this was the first char from the pipe
X				 */
X				putc(ch, mail);
X			}
X
X			/* we have to read the input pipe no matter whether
X			 * we mail or not, but obviously we only write to
X			 * mail pipe if we ARE mailing.
X			 */
X
X			while (EOF != (ch = getc(in)))
X			{
X				if (mailto)
X					putc(ch, mail);
X			}
X
X			/* only close pipe if we opened it -- i.e., we're
X			 * mailing...
X			 */
X
X			if (mailto)
X				pclose(mail);
X
X		} /*if data from grandchild*/
X
X		Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
X
X		fclose(in);
X		close(stdout_pipe[READ_PIPE]);
X	}
X
X#if defined(BSD)
X	/* wait for child to die.
X	 */
X	{
X		int		pid;
X		union wait	waiter;
X
X		Debug(DPROC, ("[%d] waiting for grandchild to finish\n", getpid()))
X		pid = wait(&waiter);
X		Debug(DPROC, ("[%d] grandchild #%d finished, status=%d\n",
X			getpid(), pid, waiter.w_status))
X	}
X#endif /*BSD*/
X}
END_OF_do_command.c
if test 9944 -ne `wc -c <do_command.c`; then
    echo shar: \"do_command.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f entry.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"entry.c\"
else
echo shar: Extracting \"entry.c\" \(9326 characters\)
sed "s/^X//" >entry.c <<'END_OF_entry.c'
X#if !defined(lint) && !defined(LINT)
Xstatic char rcsid[] = "$Header: entry.c,v 1.2 87/05/02 17:33:51 paul Exp $";
X#endif
X
X/* $Source: /usr/src/local/vix/cron/entry.c,v $
X * $Revision: 1.2 $
X * $Log:	entry.c,v $
X * Revision 1.2  87/05/02  17:33:51  paul
X * baseline for mod.sources release
X * 
X * Revision 1.1  87/01/26  23:47:32  paul
X * Initial revision
X *
X * vix 01jan87 [added line-level error recovery]
X * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
X * vix 30dec86 [written]
X */
X
X
X/* Copyright 1987 by Vixie Enterprises
X * All rights reserved
X *
X * Distribute freely, except: don't sell it, don't remove my name from the
X * source or documentation (don't take credit for my work), mark your changes
X * (don't get me blamed for your possible bugs), don't alter or remove this
X * notice.  Commercial redistribution is negotiable; contact me for details.
X *
X * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
X * I'll try to keep a version up to date.  I can be reached as follows:
X * Paul Vixie, Vixie Enterprises, 329 Noe Street, San Francisco, CA, 94114,
X * (415) 864-7013, {ucbvax!dual,ames,ucsfmis,lll-crg,sun}!ptsfa!vixie!paul.
X */
X
X
X#include "cron.h"
X
Xtypedef	enum
X	{e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd}
X	ecode_e;
Xstatic char *ecodes[] =
X	{
X		"no error",
X		"bad minute",
X		"bad hour",
X		"bad day-of-month",
X		"bad month",
X		"bad day-of-week",
X		"bad command"
X	};
X
Xvoid
Xfree_entry(e)
X	entry	*e;
X{
X	int	free();
X
X	(void) free((char *) e);
X}
X
X
Xentry *
Xload_entry(file, error_func)
X	FILE	*file;
X	void	(*error_func)();
X{
X	/* this function reads one crontab entry -- the next -- from a file.
X	 * it skips any leading blank lines, ignores comments, and returns
X	 * EOF if for any reason the entry can't be read and parsed.
X	 *
X	 * the entry IS parsed here, btw.
X	 *
X	 * syntax:
X	 *	minutes hours doms months dows cmd\n
X	 */
X
X	int	free();
X	char	*malloc(), *savestr(), get_list();
X
X	ecode_e	ecode = e_none;
X	entry	*e;
X	int	ch;
X	void	skip_comments();
X	char	*pc;
X	char	cmd[MAX_COMMAND];
X
X	e = (entry *) malloc(sizeof(entry));
X
X	Debug(DPARS, ("load_entry()...about to eat comments\n"))
X
X	skip_comments(file);
X
X	ch = get_char(file);
X
X	/* ch is now the first useful character of a useful line, which means:
X	 * the first character of a list of minutes.
X	 */
X
X	Debug(DPARS, ("load_entry()...about to parse numerics\n"))
X
X	ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, PPC_NULL, ch, file);
X	if (ch == EOF)
X	{
X		ecode = e_minute;
X		goto eof;
X	}
X
X	/* hours
X	 */
X
X	ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, PPC_NULL, ch, file);
X	if (ch == EOF)
X	{
X		ecode = e_hour;
X		goto eof;
X	}
X
X	/* DOM (days of month)
X	 */
X
X	e->dom_star = (ch == '*');
X	Debug(DPARS, ("ch=%c, dom_star=%d\n", ch, e->dom_star))
X	ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
X	if (ch == EOF)
X	{
X		ecode = e_dom;
X		goto eof;
X	}
X
X	/* month
X	 */
X
X	ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, MONTH_NAMES, ch, file);
X	if (ch == EOF)
X	{
X		ecode = e_month;
X		goto eof;
X	}
X
X	/* DOW (days of week)
X	 */
X
X	e->dow_star = (ch == '*');
X	Debug(DPARS, ("ch=%c, dow_star=%d\n", ch, e->dow_star))
X	ch = get_list(e->dow, FIRST_DOW, LAST_DOW, DOW_NAMES, ch, file);
X	if (ch == EOF)
X	{
X		ecode = e_dow;
X		goto eof;
X	}
X
X	/* make sundays equivilent */
X	if (bit_test(e->dow, 0) || bit_test(e->dow, 7))
X	{
X		bit_set(e->dow, 0)
X		bit_set(e->dow, 7)
X	}
X
X	Debug(DPARS, ("load_entry()...about to parse command\n"))
X
X	/* ch is first character of a command.  everything up to the next
X	 * \n or EOF is part of the command... too bad we don't know in
X	 * advance how long it will be, since we need to malloc a string
X	 * for it... so, we limit it to MAX_COMMAND
X	 */ 
X	pc = cmd;
X	while (ch != '\n' && ch != EOF)
X	{
X		*pc++ = ch;
X		ch = get_char(file);
X	}
X	/* a file without a \n before the EOF is rude, so we'll complain...
X	 */
X	if (ch == EOF)
X	{
X		ecode = e_cmd;
X		goto eof;
X	}
X
X	/* got the command in the 'cmd' string; add a null and save it in *e.
X	 */
X	*pc = '\0';
X	e->cmd = savestr(cmd);
X
X	Debug(DPARS, ("load_entry()...returning successfully\n"))
X
X	/* success, fini, return pointer to the entry we just created...
X	 */
X	return e;
X
Xeof:	/* if we want to return EOF, we have to jump down here and
X	 * free the entry we've been building.
X	 *
X	 * now, in some cases, a parse routine will have returned EOF to
X	 * indicate an error, but the file is not actually done.  since, in
X	 * that case, we only want to skip the line with the error on it,
X	 * we'll do that here.
X	 *
X	 * many, including the author, see what's below as evil programming
X	 * practice: since I didn't want to change the structure of this
X	 * whole function to support this error recovery, I recurse.  Cursed!
X	 * I'm seriously considering using a GOTO...   argh!
X	 */
X
X	(void) free((char *) e);
X
X	if (feof(file))
X		return NULL;
X
X	if (error_func)
X		(*error_func)(ecodes[(int)ecode]);
X	do  {ch = get_char(file);}
X	while (ch != EOF && ch != '\n');
X	if (ch == EOF)
X		return NULL;
X	return load_entry(file, error_func);
X}
X
X
Xstatic char
Xget_list(bits, low, high, names, ch, file)
X	bit_ref(	bits	)	/* one bit per flag, default=FALSE */
X	int		low, high;	/* bounds, impl. offset for bitstr */
X	char		*names[];	/* NULL or *[] of names for these elements */
X	int		ch;		/* current character being processed */
X	FILE		*file;		/* file being read */
X{
X	char	get_range();
X	int	done;
X
X	/* we know that we point to a non-blank character here;
X	 * must do a Skip_Blanks before we exit, so that the
X	 * next call (or the code that picks up the cmd) can
X	 * assume the same thing.
X	 */
X
X	Debug(DPARS|DEXT, ("get_list()...entered\n"))
X
X	/* list = "*" | range {"," range}
X	 */
X	
X	if (ch == '*')
X	{
X		/* '*' means 'all elements'.
X		 */
X		bit_setall(bits, (high-low+1))
X		goto exit;
X	}
X
X	/* clear the bit string, since the default is 'off'.
X	 */
X	bit_clearall(bits, (high-low+1))
X
X	/* process all ranges
X	 */
X	done = FALSE;
X	while (!done)
X	{
X		ch = get_range(bits, low, high, names, ch, file);
X		if (ch == ',')
X			ch = get_char(file);
X		else
X			done = TRUE;
X	}
X
Xexit:	/* exiting.  skip to some blanks, then skip over the blanks.
X	 */
X	Skip_Nonblanks(ch, file)
X	Skip_Blanks(ch, file)
X
X	Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
X
X	return ch;
X}
X
X
Xstatic char
Xget_range(bits, low, high, names, ch, file)
X	bit_ref(	bits	)	/* one bit per flag, default=FALSE */
X	int		low, high;	/* bounds, impl. offset for bitstr */
X	char		*names[];	/* NULL or names of elements */
X	int		ch;		/* current character being processed */
X	FILE		*file;		/* file being read */
X{
X	/* range = number | number "-" number [ "/" number ]
X	 */
X
X	char	get_number();
X	int	i, num1, num2, num3;
X
X	Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
X
X	if (EOF == (ch = get_number(&num1, low, names, ch, file)))
X		return EOF;
X
X	if (ch != '-')
X	{
X		/* not a range, it's a single number.
X		 */
X		if (EOF == set_element(bits, low, high, num1))
X			return EOF;
X	}
X	else
X	{
X		/* eat the dash
X		 */
X		ch = get_char(file);
X		if (ch == EOF)
X			return EOF;
X
X		/* get the number following the dash
X		 */
X		ch = get_number(&num2, low, names, ch, file);
X		if (ch == EOF)
X			return EOF;
X
X		/* check for step size
X		 */
X		if (ch == '/')
X		{
X			/* eat the slash
X			 */
X			ch = get_char(file);
X			if (ch == EOF)
X				return EOF;
X
X			/* get the step size -- note: we don't pass the
X			 * names here, because the number is not an
X			 * element id, it's a step size.  'low' is
X			 * sent as a 0 since there is no offset either.
X			 */
X			ch = get_number(&num3, 0, PPC_NULL, ch, file);
X			if (ch == EOF)
X				return EOF;
X		}
X		else
X		{
X			/* no step.  default==1.
X			 */
X			num3 = 1;
X		}
X
X		/* range. set all elements from num1 to num2, stepping
X		 * by num3.  (the step is a downward-compatible extension
X		 * proposed conceptually by bob@acornrc, syntactically
X		 * designed then implmented by paul vixie).
X		 */
X		for (i = num1;  i <= num2;  i += num3)
X			if (EOF == set_element(bits, low, high, i))
X				return EOF;
X	}
X	return ch;
X}
X
X
Xstatic char
Xget_number(numptr, low, names, ch, file)
X	int	*numptr;
X	int	low;
X	char	*names[];
X	char	ch;
X	FILE	*file;
X{
X	char	temp[MAX_TEMPSTR], *pc;
X	int	len, i, all_digits;
X
X	/* collect alphanumerics into our fixed-size temp array
X	 */
X	pc = temp;
X	len = 0;
X	all_digits = TRUE;
X	while (isalnum(ch))
X	{
X		if (++len >= MAX_TEMPSTR)
X			return EOF;
X
X		*pc++ = ch;
X
X		if (!isdigit(ch))
X			all_digits = FALSE;
X
X		ch = get_char(file);
X	}
X	*pc = '\0';
X
X	/* try to find the name in the name list
X	 */
X	if (names)
X		for (i = 0;  names[i] != NULL;  i++)
X		{
X			Debug(DPARS|DEXT,
X				("get_num, compare(%s,%s)\n", names[i], temp))
X			if (!nocase_strcmp(names[i], temp))
X			{
X				*numptr = i+low;
X				return ch;
X			}
X		}
X
X	/* no name list specified, or there is one and our string isn't
X	 * in it.  either way: if it's all digits, use its magnitude.
X	 * otherwise, it's an error.
X	 */
X	if (all_digits)
X	{
X		*numptr = atoi(temp);
X		return ch;
X	}
X
X	return EOF;
X}
X
X
Xstatic int
Xset_element(bits, low, high, number)
X	bit_ref(	bits	)	/* one bit per flag, default=FALSE */
X	int		low;
X	int		high;
X	int		number;
X{
X	Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
X
X	if (number < low || number > high)
X		return EOF;
X
X	Debug(DPARS|DEXT, ("bit_set(%x,%d)\n",bits,(number-low)))
X	bit_set(bits, (number-low))
X	Debug(DPARS|DEXT, ("bit_set succeeded\n"))
X	return OK;
X}
END_OF_entry.c
if test 9326 -ne `wc -c <entry.c`; then
    echo shar: \"entry.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 2 \(of 2\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0