diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
commit | d6583bb2a13f329cf0332ef2570eb8bb8fc0e39c (patch) | |
tree | ece253b876159b39c620e62b6c9b1174642e070e /usr.sbin/cron |
initial import of NetBSD tree
Diffstat (limited to 'usr.sbin/cron')
-rw-r--r-- | usr.sbin/cron/Makefile | 9 | ||||
-rw-r--r-- | usr.sbin/cron/compat.c | 233 | ||||
-rw-r--r-- | usr.sbin/cron/compat.h | 137 | ||||
-rw-r--r-- | usr.sbin/cron/config.h | 87 | ||||
-rw-r--r-- | usr.sbin/cron/cron.8 | 61 | ||||
-rw-r--r-- | usr.sbin/cron/cron.c | 301 | ||||
-rw-r--r-- | usr.sbin/cron/cron.h | 277 | ||||
-rw-r--r-- | usr.sbin/cron/crontab.1 | 100 | ||||
-rw-r--r-- | usr.sbin/cron/crontab.5 | 188 | ||||
-rw-r--r-- | usr.sbin/cron/crontab.c | 624 | ||||
-rw-r--r-- | usr.sbin/cron/database.c | 261 | ||||
-rw-r--r-- | usr.sbin/cron/do_command.c | 501 | ||||
-rw-r--r-- | usr.sbin/cron/entry.c | 507 | ||||
-rw-r--r-- | usr.sbin/cron/env.c | 178 | ||||
-rw-r--r-- | usr.sbin/cron/externs.h | 145 | ||||
-rw-r--r-- | usr.sbin/cron/job.c | 74 | ||||
-rw-r--r-- | usr.sbin/cron/misc.c | 664 | ||||
-rw-r--r-- | usr.sbin/cron/pathnames.h | 81 | ||||
-rw-r--r-- | usr.sbin/cron/popen.c | 167 | ||||
-rw-r--r-- | usr.sbin/cron/user.c | 102 |
20 files changed, 4697 insertions, 0 deletions
diff --git a/usr.sbin/cron/Makefile b/usr.sbin/cron/Makefile new file mode 100644 index 00000000000..333fd7d6c7d --- /dev/null +++ b/usr.sbin/cron/Makefile @@ -0,0 +1,9 @@ +# $Id: Makefile,v 1.1 1995/10/18 08:47:30 deraadt Exp $ + +PROG= cron +SRCS= cron.c database.c user.c entry.c job.c do_command.c \ + misc.c env.c popen.c +CFLAGS+=-I${.CURDIR} +MAN= cron.8 + +.include <bsd.prog.mk> diff --git a/usr.sbin/cron/compat.c b/usr.sbin/cron/compat.c new file mode 100644 index 00000000000..492a16e024d --- /dev/null +++ b/usr.sbin/cron/compat.c @@ -0,0 +1,233 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: compat.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + +/* vix 30dec93 [broke this out of misc.c - see RCS log for history] + * vix 15jan87 [added TIOCNOTTY, thanks csg@pyramid] + */ + + +#include "cron.h" +#ifdef NEED_GETDTABLESIZE +# include <limits.h> +#endif +#if defined(NEED_SETSID) && defined(BSD) +# include <sys/ioctl.h> +#endif +#include <errno.h> + + +/* the code does not depend on any of vfork's + * side-effects; it just uses it as a quick + * fork-and-exec. + */ +#ifdef NEED_VFORK +PID_T +vfork() { + return (fork()); +} +#endif + + +#ifdef NEED_STRDUP +char * +strdup(str) + char *str; +{ + char *temp; + + temp = malloc(strlen(str) + 1); + (void) strcpy(temp, str); + return temp; +} +#endif + + +#ifdef NEED_STRERROR +char * +strerror(error) + int error; +{ + extern char *sys_errlist[]; + extern int sys_nerr; + static char buf[32]; + + if ((error <= sys_nerr) && (error > 0)) { + return sys_errlist[error]; + } + + sprintf(buf, "Unknown error: %d", error); + return buf; +} +#endif + + +#ifdef NEED_STRCASECMP +int +strcasecmp(left, right) + char *left; + char *right; +{ + while (*left && (MkLower(*left) == MkLower(*right))) { + left++; + right++; + } + return MkLower(*left) - MkLower(*right); +} +#endif + + +#ifdef NEED_SETSID +int +setsid() +{ + int newpgrp; +# if defined(BSD) + int fd; +# if defined(POSIX) + newpgrp = setpgid((pid_t)0, getpid()); +# else + newpgrp = setpgrp(0, getpid()); +# endif + if ((fd = open("/dev/tty", 2)) >= 0) + { + (void) ioctl(fd, TIOCNOTTY, (char*)0); + (void) close(fd); + } +# else /*BSD*/ + newpgrp = setpgrp(); + + (void) close(STDIN); (void) open("/dev/null", 0); + (void) close(STDOUT); (void) open("/dev/null", 1); + (void) close(STDERR); (void) open("/dev/null", 2); +# endif /*BSD*/ + return newpgrp; +} +#endif /*NEED_SETSID*/ + + +#ifdef NEED_GETDTABLESIZE +int +getdtablesize() { +#ifdef _SC_OPEN_MAX + return sysconf(_SC_OPEN_MAX); +#else + return _POSIX_OPEN_MAX; +#endif +} +#endif + + +#ifdef NEED_FLOCK +/* The following flock() emulation snarfed intact *) from the HP-UX + * "BSD to HP-UX porting tricks" maintained by + * system@alchemy.chem.utoronto.ca (System Admin (Mike Peterson)) + * from the version "last updated: 11-Jan-1993" + * Snarfage done by Jarkko Hietaniemi <Jarkko.Hietaniemi@hut.fi> + * *) well, almost, had to K&R the function entry, HPUX "cc" + * does not grok ANSI function prototypes */ + +/* + * flock (fd, operation) + * + * This routine performs some file locking like the BSD 'flock' + * on the object described by the int file descriptor 'fd', + * which must already be open. + * + * The operations that are available are: + * + * LOCK_SH - get a shared lock. + * LOCK_EX - get an exclusive lock. + * LOCK_NB - don't block (must be ORed with LOCK_SH or LOCK_EX). + * LOCK_UN - release a lock. + * + * Return value: 0 if lock successful, -1 if failed. + * + * Note that whether the locks are enforced or advisory is + * controlled by the presence or absence of the SETGID bit on + * the executable. + * + * Note that there is no difference between shared and exclusive + * locks, since the 'lockf' system call in SYSV doesn't make any + * distinction. + * + * The file "<sys/file.h>" should be modified to contain the definitions + * of the available operations, which must be added manually (see below + * for the values). + */ + +/* this code has been reformatted by vixie */ + +int +flock(fd, operation) + int fd; + int operation; +{ + int i; + + switch (operation) { + case LOCK_SH: /* get a shared lock */ + case LOCK_EX: /* get an exclusive lock */ + i = lockf (fd, F_LOCK, 0); + break; + + case LOCK_SH|LOCK_NB: /* get a non-blocking shared lock */ + case LOCK_EX|LOCK_NB: /* get a non-blocking exclusive lock */ + i = lockf (fd, F_TLOCK, 0); + if (i == -1) + if ((errno == EAGAIN) || (errno == EACCES)) + errno = EWOULDBLOCK; + break; + + case LOCK_UN: /* unlock */ + i = lockf (fd, F_ULOCK, 0); + break; + + default: /* can't decipher operation */ + i = -1; + errno = EINVAL; + break; + } + + return (i); +} +#endif /*NEED_FLOCK*/ + + +#ifdef NEED_SETENV +int +setenv(name, value, overwrite) + char *name, *value; + int overwrite; +{ + char *tmp; + + if (overwrite && getenv(name)) + return -1; + + if (!(tmp = malloc(strlen(name) + strlen(value) + 2))) { + errno = ENOMEM; + return -1; + } + + sprintf("%s=%s", name, value); + return putenv(tmp); /* intentionally orphan 'tmp' storage */ +} +#endif diff --git a/usr.sbin/cron/compat.h b/usr.sbin/cron/compat.h new file mode 100644 index 00000000000..552ee0ffb4b --- /dev/null +++ b/usr.sbin/cron/compat.h @@ -0,0 +1,137 @@ +/* Copyright 1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +/* + * $Id: compat.h,v 1.1 1995/10/18 08:47:30 deraadt Exp $ + */ + +#ifndef __P +# ifdef __STDC__ +# define __P(x) x +# else +# define __P(x) () +# define const +# endif +#endif + +#if defined(UNIXPC) || defined(unixpc) +# define UNIXPC 1 +# define ATT 1 +#endif + +#if defined(hpux) || defined(_hpux) || defined(__hpux) +# define HPUX 1 +# define seteuid(e) setresuid(-1,e,-1) +# define setreuid(r,e) setresuid(r,e,-1) +#endif + +#if defined(_IBMR2) +# define AIX 1 +#endif + +#if defined(__convex__) +# define CONVEX 1 +#endif + +#if defined(sgi) || defined(_sgi) || defined(__sgi) +# define IRIX 1 +/* IRIX 4 hdrs are broken: one cannot #include both <stdio.h> + * and <stdlib.h> because they disagree on system(), perror(). + * Therefore we must zap the "const" keyword BEFORE including + * either of them. + */ +# define const +#endif + +#if defined(_UNICOS) +# define UNICOS 1 +#endif + +#ifndef POSIX +# if (BSD >= 199103) || defined(__linux) || defined(ultrix) || defined(AIX) ||\ + defined(HPUX) || defined(CONVEX) || defined(IRIX) +# define POSIX +# endif +#endif + +#ifndef BSD +# if defined(ultrix) +# define BSD 198902 +# endif +#endif + +/*****************************************************************/ + +#if !defined(BSD) && !defined(HPUX) && !defined(CONVEX) && !defined(__linux) +# define NEED_VFORK +#endif + +#if (!defined(BSD) || (BSD < 198902)) && !defined(__linux) && \ + !defined(IRIX) && !defined(NeXT) && !defined(HPUX) +# define NEED_STRCASECMP +#endif + +#if (!defined(BSD) || (BSD < 198911)) && !defined(__linux) &&\ + !defined(IRIX) && !defined(UNICOS) && !defined(HPUX) +# define NEED_STRDUP +#endif + +#if (!defined(BSD) || (BSD < 198911)) && !defined(POSIX) && !defined(NeXT) +# define NEED_STRERROR +#endif + +#if defined(HPUX) || defined(AIX) || defined(UNIXPC) +# define NEED_FLOCK +#endif + +#ifndef POSIX +# define NEED_SETSID +#endif + +#if (defined(POSIX) && !defined(BSD)) && !defined(__linux) +# define NEED_GETDTABLESIZE +#endif + +#if (BSD >= 199103) +# define HAVE_SAVED_UIDS +#endif + +#if !defined(ATT) && !defined(__linux) && !defined(IRIX) && !defined(UNICOS) +# define USE_SIGCHLD +#endif + +#if !defined(AIX) && !defined(UNICOS) +# define SYS_TIME_H 1 +#else +# define SYS_TIME_H 0 +#endif + +#if defined(BSD) && !defined(POSIX) +# define USE_UTIMES +#endif + +#if defined(AIX) || defined(HPUX) || defined(IRIX) +# define NEED_SETENV +#endif + +#if !defined(UNICOS) && !defined(UNIXPC) +# define HAS_FCHOWN +#endif + +#if !defined(UNICOS) && !defined(UNIXPC) +# define HAS_FCHMOD +#endif diff --git a/usr.sbin/cron/config.h b/usr.sbin/cron/config.h new file mode 100644 index 00000000000..4c36d0d8e3a --- /dev/null +++ b/usr.sbin/cron/config.h @@ -0,0 +1,87 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +/* config.h - configurables for Vixie Cron + * + * $Id: config.h,v 1.1 1995/10/18 08:47:30 deraadt Exp $ + */ + +#if !defined(_PATH_SENDMAIL) +# define _PATH_SENDMAIL "/usr/lib/sendmail" +#endif /*SENDMAIL*/ + +/* + * these are site-dependent + */ + +#ifndef DEBUGGING +#define DEBUGGING 1 /* 1 or 0 -- do you want debugging code built in? */ +#endif + + /* + * choose one of these MAILCMD commands. I use + * /bin/mail for speed; it makes biff bark but doesn't + * do aliasing. /usr/lib/sendmail does aliasing but is + * a hog for short messages. aliasing is not needed + * if you make use of the MAILTO= feature in crontabs. + * (hint: MAILTO= was added for this reason). + */ + +#define MAILCMD _PATH_SENDMAIL /*-*/ +#define MAILARGS "%s -FCronDaemon -odi -oem -oi -or0s -t" /*-*/ + /* -Fx = set full-name of sender + * -odi = Option Deliverymode Interactive + * -oem = Option Errors Mailedtosender + * -or0s = Option Readtimeout -- don't time out + * -t = read recipient from header of message + */ + +/* #define MAILCMD "/bin/mail" /*-*/ +/* #define MAILARGS "%s -d %s" /*-*/ + /* -d = undocumented but common flag: deliver locally? + */ + +/* #define MAILCMD "/usr/mmdf/bin/submit" /*-*/ +/* #define MAILARGS "%s -mlrxto %s" /*-*/ + +/* #define MAIL_DATE /*-*/ + /* should we include an ersatz Date: header in + * generated mail? if you are using sendmail + * for MAILCMD, it is better to let sendmail + * generate the Date: header. + */ + + /* if ALLOW_FILE and DENY_FILE are not defined or are + * defined but neither exists, should crontab(1) be + * usable only by root? + */ +/*#define ALLOW_ONLY_ROOT /*-*/ + + /* if you want to use syslog(3) instead of appending + * to CRONDIR/LOG_FILE (/var/cron/log, e.g.), define + * SYSLOG here. Note that quite a bit of logging + * info is written, and that you probably don't want + * to use this on 4.2bsd since everything goes in + * /usr/spool/mqueue/syslog. On 4.[34]bsd you can + * tell /etc/syslog.conf to send cron's logging to + * a separate file. + * + * Note that if this and LOG_FILE in "pathnames.h" + * are both defined, then logging will go to both + * places. + */ +#define SYSLOG /*-*/ diff --git a/usr.sbin/cron/cron.8 b/usr.sbin/cron/cron.8 new file mode 100644 index 00000000000..8270b457d9d --- /dev/null +++ b/usr.sbin/cron/cron.8 @@ -0,0 +1,61 @@ +.\"/* Copyright 1988,1990,1993 by Paul Vixie +.\" * All rights reserved +.\" * +.\" * Distribute freely, except: don't remove my name from the source or +.\" * documentation (don't take credit for my work), mark your changes (don't +.\" * get me blamed for your possible bugs), don't alter or remove this +.\" * notice. May be sold if buildable source is provided to buyer. No +.\" * warrantee of any kind, express or implied, is included with this +.\" * software; use at your own risk, responsibility for damages (if any) to +.\" * anyone resulting from the use of this software rests entirely with the +.\" * user. +.\" * +.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and +.\" * I'll try to keep a version up to date. I can be reached as follows: +.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul +.\" */ +.\" +.\" $Id: cron.8,v 1.1 1995/10/18 08:47:30 deraadt Exp $ +.\" +.TH CRON 8 "20 December 1993" +.UC 4 +.SH NAME +cron \- daemon to execute scheduled commands (Vixie Cron) +.SH SYNOPSIS +cron +.SH DESCRIPTION +.I Cron +should be started from /etc/rc or /etc/rc.local. It will return immediately, +so you don't need to start it with '&'. +.PP +.I Cron +searches /var/cron/tabs for crontab files which are named after accounts in +/etc/passwd; crontabs found are loaded into memory. +.I Cron +also searches for /etc/crontab which is in a different format (see +.IR crontab(5)). +.I Cron +then wakes up every minute, examining all stored crontabs, checking each +command to see if it should be run in the current minute. When executing +commands, any output is mailed to the owner of the crontab (or to the user +named in the MAILTO environment variable in the crontab, if such exists). +.PP +Additionally, +.I cron +checks each minute to see if its spool directory's modtime (or the modtime +on +.IR /etc/crontab) +has changed, and if it has, +.I cron +will then examine the modtime on all crontabs and reload those which have +changed. Thus +.I cron +need not be restarted whenever a crontab file is modified. Note that the +.IR Crontab (1) +command updates the modtime of the spool directory whenever it changes a +crontab. +.SH "SEE ALSO" +crontab(1), crontab(5) +.SH AUTHOR +.nf +Paul Vixie <paul@vix.com> diff --git a/usr.sbin/cron/cron.c b/usr.sbin/cron/cron.c new file mode 100644 index 00000000000..39ef36ac2c3 --- /dev/null +++ b/usr.sbin/cron/cron.c @@ -0,0 +1,301 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: cron.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + + +#define MAIN_PROGRAM + + +#include "cron.h" +#include <sys/signal.h> +#if SYS_TIME_H +# include <sys/time.h> +#else +# include <time.h> +#endif + + +static void usage __P((void)), + run_reboot_jobs __P((cron_db *)), + cron_tick __P((cron_db *)), + cron_sync __P((void)), + cron_sleep __P((void)), +#ifdef USE_SIGCHLD + sigchld_handler __P((int)), +#endif + sighup_handler __P((int)), + parse_args __P((int c, char *v[])); + + +static void +usage() { + fprintf(stderr, "usage: %s [-x debugflag[,...]]\n", ProgramName); + exit(ERROR_EXIT); +} + + +int +main(argc, argv) + int argc; + char *argv[]; +{ + cron_db database; + + ProgramName = argv[0]; + +#if defined(BSD) + setlinebuf(stdout); + setlinebuf(stderr); +#endif + + parse_args(argc, argv); + +#ifdef USE_SIGCHLD + (void) signal(SIGCHLD, sigchld_handler); +#else + (void) signal(SIGCLD, SIG_IGN); +#endif + (void) signal(SIGHUP, sighup_handler); + + acquire_daemonlock(0); + set_cron_uid(); + set_cron_cwd(); + +#if defined(POSIX) + setenv("PATH", _PATH_DEFPATH, 1); +#endif + + /* if there are no debug flags turned on, fork as a daemon should. + */ +# if DEBUGGING + if (DebugFlags) { +# else + if (0) { +# endif + (void) fprintf(stderr, "[%d] cron started\n", getpid()); + } else { + switch (fork()) { + case -1: + log_it("CRON",getpid(),"DEATH","can't fork"); + exit(0); + break; + case 0: + /* child process */ + log_it("CRON",getpid(),"STARTUP","fork ok"); + (void) setsid(); + break; + default: + /* parent process should just die */ + _exit(0); + } + } + + acquire_daemonlock(0); + database.head = NULL; + database.tail = NULL; + database.mtime = (time_t) 0; + load_database(&database); + run_reboot_jobs(&database); + cron_sync(); + while (TRUE) { +# if DEBUGGING + if (!(DebugFlags & DTEST)) +# endif /*DEBUGGING*/ + cron_sleep(); + + load_database(&database); + + /* do this iteration + */ + cron_tick(&database); + + /* sleep 1 minute + */ + TargetTime += 60; + } +} + + +static void +run_reboot_jobs(db) + cron_db *db; +{ + register user *u; + register entry *e; + + for (u = db->head; u != NULL; u = u->next) { + for (e = u->crontab; e != NULL; e = e->next) { + if (e->flags & WHEN_REBOOT) { + job_add(e, u); + } + } + } + (void) job_runqueue(); +} + + +static void +cron_tick(db) + cron_db *db; +{ + register struct tm *tm = localtime(&TargetTime); + register int minute, hour, dom, month, dow; + register user *u; + register entry *e; + + /* make 0-based values out of these so we can use them as indicies + */ + minute = tm->tm_min -FIRST_MINUTE; + hour = tm->tm_hour -FIRST_HOUR; + dom = tm->tm_mday -FIRST_DOM; + month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH; + dow = tm->tm_wday -FIRST_DOW; + + Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d)\n", + getpid(), minute, hour, dom, month, dow)) + + /* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the + * first and fifteenth AND every Sunday; '* * * * Sun' will run *only* + * on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this + * is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre. + * like many bizarre things, it's the standard. + */ + for (u = db->head; u != NULL; u = u->next) { + for (e = u->crontab; e != NULL; e = e->next) { + Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n", + env_get("LOGNAME", e->envp), + e->uid, e->gid, e->cmd)) + if (bit_test(e->minute, minute) + && bit_test(e->hour, hour) + && bit_test(e->month, month) + && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) + ? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) + : (bit_test(e->dow,dow) || bit_test(e->dom,dom)) + ) + ) { + job_add(e, u); + } + } + } +} + + +/* the task here is to figure out how long it's going to be until :00 of the + * following minute and initialize TargetTime to this value. TargetTime + * will subsequently slide 60 seconds at a time, with correction applied + * implicitly in cron_sleep(). it would be nice to let cron execute in + * the "current minute" before going to sleep, but by restarting cron you + * could then get it to execute a given minute's jobs more than once. + * instead we have the chance of missing a minute's jobs completely, but + * that's something sysadmin's know to expect what with crashing computers.. + */ +static void +cron_sync() { + register struct tm *tm; + + TargetTime = time((time_t*)0); + tm = localtime(&TargetTime); + TargetTime += (60 - tm->tm_sec); +} + + +static void +cron_sleep() { + register int seconds_to_wait; + + do { + seconds_to_wait = (int) (TargetTime - time((time_t*)0)); + Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n", + getpid(), TargetTime, seconds_to_wait)) + + /* if we intend to sleep, this means that it's finally + * time to empty the job queue (execute it). + * + * if we run any jobs, we'll probably screw up our timing, + * so go recompute. + * + * note that we depend here on the left-to-right nature + * of &&, and the short-circuiting. + */ + } while (seconds_to_wait > 0 && job_runqueue()); + + while (seconds_to_wait > 0) { + Debug(DSCH, ("[%d] sleeping for %d seconds\n", + getpid(), seconds_to_wait)) + seconds_to_wait = (int) sleep((unsigned int) seconds_to_wait); + } +} + + +#ifdef USE_SIGCHLD +static void +sigchld_handler(x) { + WAIT_T waiter; + PID_T pid; + + for (;;) { +#ifdef POSIX + pid = waitpid(-1, &waiter, WNOHANG); +#else + pid = wait3(&waiter, WNOHANG, (struct rusage *)0); +#endif + switch (pid) { + case -1: + Debug(DPROC, + ("[%d] sigchld...no children\n", getpid())) + return; + case 0: + Debug(DPROC, + ("[%d] sigchld...no dead kids\n", getpid())) + return; + default: + Debug(DPROC, + ("[%d] sigchld...pid #%d died, stat=%d\n", + getpid(), pid, WEXITSTATUS(waiter))) + } + } +} +#endif /*USE_SIGCHLD*/ + + +static void +sighup_handler(x) { + log_close(); +} + + +static void +parse_args(argc, argv) + int argc; + char *argv[]; +{ + int argch; + + while (EOF != (argch = getopt(argc, argv, "x:"))) { + switch (argch) { + default: + usage(); + case 'x': + if (!set_debug_flags(optarg)) + usage(); + break; + } + } +} diff --git a/usr.sbin/cron/cron.h b/usr.sbin/cron/cron.h new file mode 100644 index 00000000000..60cfada5899 --- /dev/null +++ b/usr.sbin/cron/cron.h @@ -0,0 +1,277 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +/* cron.h - header for vixie's cron + * + * $Id: cron.h,v 1.1 1995/10/18 08:47:30 deraadt Exp $ + * + * vix 14nov88 [rest of log is in RCS] + * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley] + * vix 30dec86 [written] + */ + +/* reorder these #include's at your peril */ + +#include <sys/types.h> +#include <sys/param.h> +#include "compat.h" + +#include <stdio.h> +#include <ctype.h> +#include <bitstring.h> +#include <pwd.h> +#include <sys/wait.h> + +#include "pathnames.h" +#include "config.h" +#include "externs.h" + + /* these are really immutable, and are + * defined for symbolic convenience only + * TRUE, FALSE, and ERR must be distinct + * ERR must be < OK. + */ +#define TRUE 1 +#define FALSE 0 + /* system calls return this on success */ +#define OK 0 + /* or this on error */ +#define ERR (-1) + + /* turn this on to get '-x' code */ +#ifndef DEBUGGING +#define DEBUGGING FALSE +#endif + +#define READ_PIPE 0 /* which end of a pipe pair do you read? */ +#define WRITE_PIPE 1 /* or write to? */ +#define STDIN 0 /* what is stdin's file descriptor? */ +#define STDOUT 1 /* stdout's? */ +#define STDERR 2 /* stderr's? */ +#define ERROR_EXIT 1 /* exit() with this will scare the shell */ +#define OK_EXIT 0 /* exit() with this is considered 'normal' */ +#define MAX_FNAME 100 /* max length of internally generated fn */ +#define MAX_COMMAND 1000 /* max length of internally generated cmd */ +#define MAX_ENVSTR 1000 /* max length of envvar=value\0 strings */ +#define MAX_TEMPSTR 100 /* obvious */ +#define MAX_UNAME 20 /* max length of username, should be overkill */ +#define ROOT_UID 0 /* don't change this, it really must be root */ +#define ROOT_USER "root" /* ditto */ + + /* NOTE: these correspond to DebugFlagNames, + * defined below. + */ +#define DEXT 0x0001 /* extend flag for other debug masks */ +#define DSCH 0x0002 /* scheduling debug mask */ +#define DPROC 0x0004 /* process control debug mask */ +#define DPARS 0x0008 /* parsing debug mask */ +#define DLOAD 0x0010 /* database loading debug mask */ +#define DMISC 0x0020 /* misc debug mask */ +#define DTEST 0x0040 /* test mode: don't execute any commands */ +#define DBIT 0x0080 /* bit twiddling shown (long) */ + +#define CRON_TAB(u) "%s/%s", SPOOL_DIR, u +#define REG register +#define PPC_NULL ((char **)NULL) + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define Skip_Blanks(c, f) \ + while (c == '\t' || c == ' ') \ + c = get_char(f); + +#define Skip_Nonblanks(c, f) \ + while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \ + c = get_char(f); + +#define Skip_Line(c, f) \ + do {c = get_char(f);} while (c != '\n' && c != EOF); + +#if DEBUGGING +# define Debug(mask, message) \ + if ( (DebugFlags & (mask) ) == (mask) ) \ + printf message; +#else /* !DEBUGGING */ +# define Debug(mask, message) \ + ; +#endif /* DEBUGGING */ + +#define MkLower(ch) (isupper(ch) ? tolower(ch) : ch) +#define MkUpper(ch) (islower(ch) ? toupper(ch) : ch) +#define Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \ + LineNumber = ln; \ + } + +#define FIRST_MINUTE 0 +#define LAST_MINUTE 59 +#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1) + +#define FIRST_HOUR 0 +#define LAST_HOUR 23 +#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1) + +#define FIRST_DOM 1 +#define LAST_DOM 31 +#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1) + +#define FIRST_MONTH 1 +#define LAST_MONTH 12 +#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1) + +/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */ +#define FIRST_DOW 0 +#define LAST_DOW 7 +#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1) + + /* each user's crontab will be held as a list of + * the following structure. + * + * These are the cron commands. + */ + +typedef struct _entry { + struct _entry *next; + uid_t uid; + gid_t gid; + char **envp; + char *cmd; + bitstr_t bit_decl(minute, MINUTE_COUNT); + bitstr_t bit_decl(hour, HOUR_COUNT); + bitstr_t bit_decl(dom, DOM_COUNT); + bitstr_t bit_decl(month, MONTH_COUNT); + bitstr_t bit_decl(dow, DOW_COUNT); + int flags; +#define DOM_STAR 0x01 +#define DOW_STAR 0x02 +#define WHEN_REBOOT 0x04 +} entry; + + /* the crontab database will be a list of the + * following structure, one element per user + * plus one for the system. + * + * These are the crontabs. + */ + +typedef struct _user { + struct _user *next, *prev; /* links */ + char *name; + time_t mtime; /* last modtime of crontab */ + entry *crontab; /* this person's crontab */ +} user; + +typedef struct _cron_db { + user *head, *tail; /* links */ + time_t mtime; /* last modtime on spooldir */ +} cron_db; + + +void set_cron_uid __P((void)), + set_cron_cwd __P((void)), + load_database __P((cron_db *)), + open_logfile __P((void)), + sigpipe_func __P((void)), + job_add __P((entry *, user *)), + do_command __P((entry *, user *)), + link_user __P((cron_db *, user *)), + unlink_user __P((cron_db *, user *)), + free_user __P((user *)), + env_free __P((char **)), + unget_char __P((int, FILE *)), + free_entry __P((entry *)), + acquire_daemonlock __P((int)), + skip_comments __P((FILE *)), + log_it __P((char *, int, char *, char *)), + log_close __P((void)); + +int job_runqueue __P((void)), + set_debug_flags __P((char *)), + get_char __P((FILE *)), + get_string __P((char *, int, FILE *, char *)), + swap_uids __P((void)), + load_env __P((char *, FILE *)), + cron_pclose __P((FILE *)), + strcmp_until __P((char *, char *, int)), + allowed __P((char *)), + strdtb __P((char *)); + +char *env_get __P((char *, char **)), + *arpadate __P((time_t *)), + *mkprints __P((unsigned char *, unsigned int)), + *first_word __P((char *, char *)), + **env_init __P((void)), + **env_copy __P((char **)), + **env_set __P((char **, char *)); + +user *load_user __P((int, struct passwd *, char *)), + *find_user __P((cron_db *, char *)); + +entry *load_entry __P((FILE *, void (*)(), + struct passwd *, char **)); + +FILE *cron_popen __P((char *, char *)); + + + /* in the C tradition, we only create + * variables for the main program, just + * extern them elsewhere. + */ + +#ifdef MAIN_PROGRAM +# if !defined(LINT) && !defined(lint) +char *copyright[] = { + "@(#) Copyright 1988,1989,1990,1993,1994 by Paul Vixie", + "@(#) All rights reserved" + }; +# endif + +char *MonthNames[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + NULL + }; + +char *DowNames[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", + NULL + }; + +char *ProgramName; +int LineNumber; +time_t TargetTime; + +# if DEBUGGING +int DebugFlags; +char *DebugFlagNames[] = { /* sync with #defines */ + "ext", "sch", "proc", "pars", "load", "misc", "test", "bit", + NULL /* NULL must be last element */ + }; +# endif /* DEBUGGING */ +#else /*MAIN_PROGRAM*/ +extern char *copyright[], + *MonthNames[], + *DowNames[], + *ProgramName; +extern int LineNumber; +extern time_t TargetTime; +# if DEBUGGING +extern int DebugFlags; +extern char *DebugFlagNames[]; +# endif /* DEBUGGING */ +#endif /*MAIN_PROGRAM*/ diff --git a/usr.sbin/cron/crontab.1 b/usr.sbin/cron/crontab.1 new file mode 100644 index 00000000000..3801abf3b30 --- /dev/null +++ b/usr.sbin/cron/crontab.1 @@ -0,0 +1,100 @@ +.\"/* Copyright 1988,1990,1993 by Paul Vixie +.\" * All rights reserved +.\" * +.\" * Distribute freely, except: don't remove my name from the source or +.\" * documentation (don't take credit for my work), mark your changes (don't +.\" * get me blamed for your possible bugs), don't alter or remove this +.\" * notice. May be sold if buildable source is provided to buyer. No +.\" * warrantee of any kind, express or implied, is included with this +.\" * software; use at your own risk, responsibility for damages (if any) to +.\" * anyone resulting from the use of this software rests entirely with the +.\" * user. +.\" * +.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and +.\" * I'll try to keep a version up to date. I can be reached as follows: +.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul +.\" */ +.\" +.\" $Id: crontab.1,v 1.1 1995/10/18 08:47:30 deraadt Exp $ +.\" +.TH CRONTAB 1 "29 December 1993" +.UC 4 +.SH NAME +crontab \- maintain crontab files for individual users (V3) +.SH SYNOPSIS +crontab [ -u user ] file +.br +crontab [ -u user ] [ -l | -r | -e ] +.SH DESCRIPTION +.I Crontab +is the program used to install, deinstall or list the tables +used to drive the +.IR cron (8) +daemon in Vixie Cron. Each user can have their own crontab, and though +these are files in /var, they are not intended to be edited directly. +.PP +If the +.I allow +file exists, then you must be listed therein in order to be allowed to use +this command. If the +.I allow +file does not exist but the +.I deny +file does exist, then you must \fBnot\fR be listed in the +.I deny +file in order to use this command. If neither of these files exists, then +depending on site-dependent configuration parameters, only the super user +will be allowed to use this command, or all users will be able to use this +command. +.PP +If the +.I -u +option is given, it specifies the name of the user whose crontab is to be +tweaked. If this option is not given, +.I crontab +examines "your" crontab, i.e., the crontab of the person executing the +command. Note that +.IR su (8) +can confuse +.I crontab +and that if you are running inside of +.IR su (8) +you should always use the +.I -u +option for safety's sake. +.PP +The first form of this command is used to install a new crontab from some +named file or standard input if the pseudo-filename ``-'' is given. +.PP +The +.I -l +option causes the current crontab to be displayed on standard output. +.PP +The +.I -r +option causes the current crontab to be removed. +.PP +The +.I -e +option is used to edit the current crontab using the editor specified by +the \s-1VISUAL\s+1 or \s-1EDITOR\s+1 environment variables. After you exit +from the editor, the modified crontab will be installed automatically. +.SH "SEE ALSO" +crontab(5), cron(8) +.SH FILES +.nf +/var/cron/allow +/var/cron/deny +.fi +.SH STANDARDS +The +.I crontab +command conforms to IEEE Std1003.2-1992 (``POSIX''). This new command syntax +differs from previous versions of Vixie Cron, as well as from the classic +SVR3 syntax. +.SH DIAGNOSTICS +A fairly informative usage message appears if you run it with a bad command +line. +.SH AUTHOR +.nf +Paul Vixie <paul@vix.com> diff --git a/usr.sbin/cron/crontab.5 b/usr.sbin/cron/crontab.5 new file mode 100644 index 00000000000..50c5852ed91 --- /dev/null +++ b/usr.sbin/cron/crontab.5 @@ -0,0 +1,188 @@ +.\"/* Copyright 1988,1990,1993,1994 by Paul Vixie +.\" * All rights reserved +.\" * +.\" * Distribute freely, except: don't remove my name from the source or +.\" * documentation (don't take credit for my work), mark your changes (don't +.\" * get me blamed for your possible bugs), don't alter or remove this +.\" * notice. May be sold if buildable source is provided to buyer. No +.\" * warrantee of any kind, express or implied, is included with this +.\" * software; use at your own risk, responsibility for damages (if any) to +.\" * anyone resulting from the use of this software rests entirely with the +.\" * user. +.\" * +.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and +.\" * I'll try to keep a version up to date. I can be reached as follows: +.\" * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul +.\" */ +.\" +.\" $Id: crontab.5,v 1.1 1995/10/18 08:47:30 deraadt Exp $ +.\" +.TH CRONTAB 5 "24 January 1994" +.UC 4 +.SH NAME +crontab \- tables for driving cron +.SH DESCRIPTION +A +.I crontab +file contains instructions to the +.IR cron (8) +daemon of the general form: ``run this command at this time on this date''. +Each user has their own crontab, and commands in any given crontab will be +executed as the user who owns the crontab. Uucp and News will usually have +their own crontabs, eliminating the need for explicitly running +.IR su (1) +as part of a cron command. +.PP +Blank lines and leading spaces and tabs are ignored. Lines whose first +non-space character is a pound-sign (#) are comments, and are ignored. +Note that comments are not allowed on the same line as cron commands, since +they will be taken to be part of the command. Similarly, comments are not +allowed on the same line as environment variable settings. +.PP +An active line in a crontab will be either an environment setting or a cron +command. An environment setting is of the form, +.PP + name = value +.PP +where the spaces around the equal-sign (=) are optional, and any subsequent +non-leading spaces in +.I value +will be part of the value assigned to +.IR name . +The +.I value +string may be placed in quotes (single or double, but matching) to preserve +leading or trailing blanks. +.PP +Several environment variables are set up +automatically by the +.IR cron (8) +daemon. +SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd +line of the crontab's owner. +HOME and SHELL may be overridden by settings in the crontab; LOGNAME may not. +.PP +(Another note: the LOGNAME variable is sometimes called USER on BSD systems... +on these systems, USER will be set also.) +.PP +In addition to LOGNAME, HOME, and SHELL, +.IR cron (8) +will look at MAILTO if it has any reason to send mail as a result of running +commands in ``this'' crontab. If MAILTO is defined (and non-empty), mail is +sent to the user so named. If MAILTO is defined but empty (MAILTO=""), no +mail will be sent. Otherwise mail is sent to the owner of the crontab. This +option is useful if you decide on /bin/mail instead of /usr/lib/sendmail as +your mailer when you install cron -- /bin/mail doesn't do aliasing, and UUCP +usually doesn't read its mail. +.PP +The format of a cron command is very much the V7 standard, with a number of +upward-compatible extensions. Each line has five time and date fields, +followed by a user name if this is the system crontab file, +followed by a command. Commands are executed by +.IR cron (8) +when the minute, hour, and month of year fields match the current time, +.I and +when at least one of the two day fields (day of month, or day of week) +match the current time (see ``Note'' below). +.IR cron (8) +examines cron entries once every minute. +The time and date fields are: +.IP +.ta 1.5i +field allowed values +.br +----- -------------- +.br +minute 0-59 +.br +hour 0-23 +.br +day of month 0-31 +.br +month 0-12 (or names, see below) +.br +day of week 0-7 (0 or 7 is Sun, or use names) +.br +.PP +A field may be an asterisk (*), which always stands for ``first\-last''. +.PP +Ranges of numbers are allowed. Ranges are two numbers separated +with a hyphen. The specified range is inclusive. For example, +8-11 for an ``hours'' entry specifies execution at hours 8, 9, 10 +and 11. +.PP +Lists are allowed. A list is a set of numbers (or ranges) +separated by commas. Examples: ``1,2,5,9'', ``0-4,8-12''. +.PP +Step values can be used in conjunction with ranges. Following +a range with ``/<number>'' specifies skips of the number's value +through the range. For example, ``0-23/2'' can be used in the hours +field to specify command execution every other hour (the alternative +in the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22''). Steps are +also permitted after an asterisk, so if you want to say ``every two +hours'', just use ``*/2''. +.PP +Names can also be used for the ``month'' and ``day of week'' +fields. Use the first three letters of the particular +day or month (case doesn't matter). Ranges or +lists of names are not allowed. +.PP +The ``sixth'' field (the rest of the line) specifies the command to be +run. +The entire command portion of the line, up to a newline or % +character, will be executed by /bin/sh or by the shell +specified in the SHELL variable of the cronfile. +Percent-signs (%) in the command, unless escaped with backslash +(\\), will be changed into newline characters, and all data +after the first % will be sent to the command as standard +input. +.PP +Note: The day of a command's execution can be specified by two +fields \(em day of month, and day of week. If both fields are +restricted (ie, aren't *), the command will be run when +.I either +field matches the current time. For example, +.br +``30 4 1,15 * 5'' +would cause a command to be run at 4:30 am on the 1st and 15th of each +month, plus every Friday. +.SH EXAMPLE CRON FILE +.nf + +# use /bin/sh to run commands, no matter what /etc/passwd says +SHELL=/bin/sh +# mail any output to `paul', no matter whose crontab this is +MAILTO=paul +# +# run five minutes after midnight, every day +5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 +# run at 2:15pm on the first of every month -- output mailed to paul +15 14 1 * * $HOME/bin/monthly +# run at 10 pm on weekdays, annoy Joe +0 22 * * 1-5 mail -s "It's 10pm" joe%Joe,%%Where are your kids?% +23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" +5 4 * * sun echo "run at 5 after 4 every sunday" +.fi +.SH SEE ALSO +cron(8), crontab(1) +.SH EXTENSIONS +When specifying day of week, both day 0 and day 7 will be considered Sunday. +BSD and ATT seem to disagree about this. +.PP +Lists and ranges are allowed to co-exist in the same field. "1-3,7-9" would +be rejected by ATT or BSD cron -- they want to see "1-3" or "7,8,9" ONLY. +.PP +Ranges can include "steps", so "1-9/2" is the same as "1,3,5,7,9". +.PP +Names of months or days of the week can be specified by name. +.PP +Environment variables can be set in the crontab. In BSD or ATT, the +environment handed to child processes is basically the one from /etc/rc. +.PP +Command output is mailed to the crontab owner (BSD can't do this), can be +mailed to a person other than the crontab owner (SysV can't do this), or the +feature can be turned off and no mail will be sent at all (SysV can't do this +either). +.SH AUTHOR +.nf +Paul Vixie <paul@vix.com> diff --git a/usr.sbin/cron/crontab.c b/usr.sbin/cron/crontab.c new file mode 100644 index 00000000000..0d0decb0d4b --- /dev/null +++ b/usr.sbin/cron/crontab.c @@ -0,0 +1,624 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: crontab.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + +/* crontab - install and manage per-user crontab files + * vix 02may87 [RCS has the rest of the log] + * vix 26jan87 [original] + */ + + +#define MAIN_PROGRAM + + +#include "cron.h" +#include <errno.h> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/stat.h> +#ifdef USE_UTIMES +# include <sys/time.h> +#else +# include <time.h> +# include <utime.h> +#endif +#if defined(POSIX) +# include <locale.h> +#endif + + +#define NHEADER_LINES 3 + + +enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; + +#if DEBUGGING +static char *Options[] = { "???", "list", "delete", "edit", "replace" }; +#endif + + +static PID_T Pid; +static char User[MAX_UNAME], RealUser[MAX_UNAME]; +static char Filename[MAX_FNAME]; +static FILE *NewCrontab; +static int CheckErrorCount; +static enum opt_t Option; +static struct passwd *pw; +static void list_cmd __P((void)), + delete_cmd __P((void)), + edit_cmd __P((void)), + poke_daemon __P((void)), + check_error __P((char *)), + parse_args __P((int c, char *v[])); +static int replace_cmd __P((void)); + + +static void +usage(msg) + char *msg; +{ + fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg); + fprintf(stderr, "usage:\t%s [-u user] file\n", ProgramName); + fprintf(stderr, "\t%s [-u user] [ -e | -l | -r ]\n", ProgramName); + fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n"); + fprintf(stderr, "\t-e\t(edit user's crontab)\n"); + fprintf(stderr, "\t-l\t(list user's crontab)\n"); + fprintf(stderr, "\t-r\t(delete user's crontab)\n"); + exit(ERROR_EXIT); +} + + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int exitstatus; + + Pid = getpid(); + ProgramName = argv[0]; + +#if defined(POSIX) + setlocale(LC_ALL, ""); +#endif + +#if defined(BSD) + setlinebuf(stderr); +#endif + parse_args(argc, argv); /* sets many globals, opens a file */ + set_cron_uid(); + set_cron_cwd(); + if (!allowed(User)) { + fprintf(stderr, + "You (%s) are not allowed to use this program (%s)\n", + User, ProgramName); + fprintf(stderr, "See crontab(1) for more information\n"); + log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); + exit(ERROR_EXIT); + } + exitstatus = OK_EXIT; + switch (Option) { + case opt_list: list_cmd(); + break; + case opt_delete: delete_cmd(); + break; + case opt_edit: edit_cmd(); + break; + case opt_replace: if (replace_cmd() < 0) + exitstatus = ERROR_EXIT; + break; + } + exit(0); + /*NOTREACHED*/ +} + + +static void +parse_args(argc, argv) + int argc; + char *argv[]; +{ + int argch; + + if (!(pw = getpwuid(getuid()))) { + fprintf(stderr, "%s: your UID isn't in the passwd file.\n", + ProgramName); + fprintf(stderr, "bailing out.\n"); + exit(ERROR_EXIT); + } + strcpy(User, pw->pw_name); + strcpy(RealUser, User); + Filename[0] = '\0'; + Option = opt_unknown; + while (EOF != (argch = getopt(argc, argv, "u:lerx:"))) { + switch (argch) { + case 'x': + if (!set_debug_flags(optarg)) + usage("bad debug option"); + break; + case 'u': + if (getuid() != ROOT_UID) + { + fprintf(stderr, + "must be privileged to use -u\n"); + exit(ERROR_EXIT); + } + if (!(pw = getpwnam(optarg))) + { + fprintf(stderr, "%s: user `%s' unknown\n", + ProgramName, optarg); + exit(ERROR_EXIT); + } + (void) strcpy(User, optarg); + break; + case 'l': + if (Option != opt_unknown) + usage("only one operation permitted"); + Option = opt_list; + break; + case 'r': + if (Option != opt_unknown) + usage("only one operation permitted"); + Option = opt_delete; + break; + case 'e': + if (Option != opt_unknown) + usage("only one operation permitted"); + Option = opt_edit; + break; + default: + usage("unrecognized option"); + } + } + + endpwent(); + + if (Option != opt_unknown) { + if (argv[optind] != NULL) { + usage("no arguments permitted after this option"); + } + } else { + if (argv[optind] != NULL) { + Option = opt_replace; + (void) strcpy (Filename, argv[optind]); + } else { + usage("file name must be specified for replace"); + } + } + + if (Option == opt_replace) { + /* we have to open the file here because we're going to + * chdir(2) into /var/cron before we get around to + * reading the file. + */ + if (!strcmp(Filename, "-")) { + NewCrontab = stdin; + } else { + /* relinquish the setuid status of the binary during + * the open, lest nonroot users read files they should + * not be able to read. we can't use access() here + * since there's a race condition. thanks go out to + * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting + * the race. + */ + + if (swap_uids() < OK) { + perror("swapping uids"); + exit(ERROR_EXIT); + } + if (!(NewCrontab = fopen(Filename, "r"))) { + perror(Filename); + exit(ERROR_EXIT); + } + if (swap_uids() < OK) { + perror("swapping uids back"); + exit(ERROR_EXIT); + } + } + } + + Debug(DMISC, ("user=%s, file=%s, option=%s\n", + User, Filename, Options[(int)Option])) +} + + +static void +list_cmd() { + char n[MAX_FNAME]; + FILE *f; + int ch; + + log_it(RealUser, Pid, "LIST", User); + (void) sprintf(n, CRON_TAB(User)); + if (!(f = fopen(n, "r"))) { + if (errno == ENOENT) + fprintf(stderr, "no crontab for %s\n", User); + else + perror(n); + exit(ERROR_EXIT); + } + + /* file is open. copy to stdout, close. + */ + Set_LineNum(1) + while (EOF != (ch = get_char(f))) + putchar(ch); + fclose(f); +} + + +static void +delete_cmd() { + char n[MAX_FNAME]; + + log_it(RealUser, Pid, "DELETE", User); + (void) sprintf(n, CRON_TAB(User)); + if (unlink(n)) { + if (errno == ENOENT) + fprintf(stderr, "no crontab for %s\n", User); + else + perror(n); + exit(ERROR_EXIT); + } + poke_daemon(); +} + + +static void +check_error(msg) + char *msg; +{ + CheckErrorCount++; + fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); +} + + +static void +edit_cmd() { + char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; + FILE *f; + int ch, t, x; + struct stat statbuf; + time_t mtime; + WAIT_T waiter; + PID_T pid, xpid; + + log_it(RealUser, Pid, "BEGIN EDIT", User); + (void) sprintf(n, CRON_TAB(User)); + if (!(f = fopen(n, "r"))) { + if (errno != ENOENT) { + perror(n); + exit(ERROR_EXIT); + } + fprintf(stderr, "no crontab for %s - using an empty one\n", + User); + if (!(f = fopen("/dev/null", "r"))) { + perror("/dev/null"); + exit(ERROR_EXIT); + } + } + + (void) sprintf(Filename, "/tmp/crontab.%d", Pid); + if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0600))) { + perror(Filename); + goto fatal; + } +#ifdef HAS_FCHOWN + if (fchown(t, getuid(), getgid()) < 0) { +#else + if (chown(Filename, getuid(), getgid()) < 0) { +#endif + perror("fchown"); + goto fatal; + } + if (!(NewCrontab = fdopen(t, "r+"))) { + perror("fdopen"); + goto fatal; + } + + Set_LineNum(1) + + /* ignore the top few comments since we probably put them there. + */ + for (x = 0; x < NHEADER_LINES; x++) { + ch = get_char(f); + if (EOF == ch) + break; + if ('#' != ch) { + putc(ch, NewCrontab); + break; + } + while (EOF != (ch = get_char(f))) + if (ch == '\n') + break; + if (EOF == ch) + break; + } + + /* copy the rest of the crontab (if any) to the temp file. + */ + if (EOF != ch) + while (EOF != (ch = get_char(f))) + putc(ch, NewCrontab); + fclose(f); + if (fflush(NewCrontab) < OK) { + perror(Filename); + exit(ERROR_EXIT); + } + again: + rewind(NewCrontab); + if (ferror(NewCrontab)) { + fprintf(stderr, "%s: error while writing new crontab to %s\n", + ProgramName, Filename); + fatal: unlink(Filename); + exit(ERROR_EXIT); + } + if (fstat(t, &statbuf) < 0) { + perror("fstat"); + goto fatal; + } + mtime = statbuf.st_mtime; + + if ((!(editor = getenv("VISUAL"))) + && (!(editor = getenv("EDITOR"))) + ) { + editor = EDITOR; + } + + /* we still have the file open. editors will generally rewrite the + * original file rather than renaming/unlinking it and starting a + * new one; even backup files are supposed to be made by copying + * rather than by renaming. if some editor does not support this, + * then don't use it. the security problems are more severe if we + * close and reopen the file around the edit. + */ + + switch (pid = fork()) { + case -1: + perror("fork"); + goto fatal; + case 0: + /* child */ + if (setuid(getuid()) < 0) { + perror("setuid(getuid())"); + exit(ERROR_EXIT); + } + if (chdir("/tmp") < 0) { + perror("chdir(/tmp)"); + exit(ERROR_EXIT); + } + if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) { + fprintf(stderr, "%s: editor or filename too long\n", + ProgramName); + exit(ERROR_EXIT); + } + sprintf(q, "%s %s", editor, Filename); + execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, NULL); + perror(editor); + exit(ERROR_EXIT); + /*NOTREACHED*/ + default: + /* parent */ + break; + } + + /* parent */ + xpid = wait(&waiter); + if (xpid != pid) { + fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n", + ProgramName, xpid, pid, editor); + goto fatal; + } + if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { + fprintf(stderr, "%s: \"%s\" exited with status %d\n", + ProgramName, editor, WEXITSTATUS(waiter)); + goto fatal; + } + if (WIFSIGNALED(waiter)) { + fprintf(stderr, + "%s: \"%s\" killed; signal %d (%score dumped)\n", + ProgramName, editor, WTERMSIG(waiter), + WCOREDUMP(waiter) ?"" :"no "); + goto fatal; + } + if (fstat(t, &statbuf) < 0) { + perror("fstat"); + goto fatal; + } + if (mtime == statbuf.st_mtime) { + fprintf(stderr, "%s: no changes made to crontab\n", + ProgramName); + goto remove; + } + fprintf(stderr, "%s: installing new crontab\n", ProgramName); + switch (replace_cmd()) { + case 0: + break; + case -1: + for (;;) { + printf("Do you want to retry the same edit? "); + fflush(stdout); + q[0] = '\0'; + (void) fgets(q, sizeof q, stdin); + switch (islower(q[0]) ? q[0] : tolower(q[0])) { + case 'y': + goto again; + case 'n': + goto abandon; + default: + fprintf(stderr, "Enter Y or N\n"); + } + } + /*NOTREACHED*/ + case -2: + abandon: + fprintf(stderr, "%s: edits left in %s\n", + ProgramName, Filename); + goto done; + default: + fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n"); + goto fatal; + } + remove: + unlink(Filename); + done: + log_it(RealUser, Pid, "END EDIT", User); +} + + +/* returns 0 on success + * -1 on syntax error + * -2 on install error + */ +static int +replace_cmd() { + char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; + FILE *tmp; + int ch, eof; + entry *e; + time_t now = time(NULL); + char **envp = env_init(); + + (void) sprintf(n, "tmp.%d", Pid); + (void) sprintf(tn, CRON_TAB(n)); + if (!(tmp = fopen(tn, "w+"))) { + perror(tn); + return (-2); + } + + /* write a signature at the top of the file. + * + * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. + */ + fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); + fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); + fprintf(tmp, "# (Cron version -- %s)\n", rcsid); + + /* copy the crontab to the tmp + */ + rewind(NewCrontab); + Set_LineNum(1) + while (EOF != (ch = get_char(NewCrontab))) + putc(ch, tmp); + ftruncate(fileno(tmp), ftell(tmp)); + fflush(tmp); rewind(tmp); + + if (ferror(tmp)) { + fprintf(stderr, "%s: error while writing new crontab to %s\n", + ProgramName, tn); + fclose(tmp); unlink(tn); + return (-2); + } + + /* check the syntax of the file being installed. + */ + + /* BUG: was reporting errors after the EOF if there were any errors + * in the file proper -- kludged it by stopping after first error. + * vix 31mar87 + */ + Set_LineNum(1 - NHEADER_LINES) + CheckErrorCount = 0; eof = FALSE; + while (!CheckErrorCount && !eof) { + switch (load_env(envstr, tmp)) { + case ERR: + eof = TRUE; + break; + case FALSE: + e = load_entry(tmp, check_error, pw, envp); + if (e) + free(e); + break; + case TRUE: + break; + } + } + + if (CheckErrorCount != 0) { + fprintf(stderr, "errors in crontab file, can't install.\n"); + fclose(tmp); unlink(tn); + return (-1); + } + +#ifdef HAS_FCHOWN + if (fchown(fileno(tmp), ROOT_UID, -1) < OK) +#else + if (chown(tn, ROOT_UID, -1) < OK) +#endif + { + perror("chown"); + fclose(tmp); unlink(tn); + return (-2); + } + +#ifdef HAS_FCHMOD + if (fchmod(fileno(tmp), 0600) < OK) +#else + if (chmod(tn, 0600) < OK) +#endif + { + perror("chown"); + fclose(tmp); unlink(tn); + return (-2); + } + + if (fclose(tmp) == EOF) { + perror("fclose"); + unlink(tn); + return (-2); + } + + (void) sprintf(n, CRON_TAB(User)); + if (rename(tn, n)) { + fprintf(stderr, "%s: error renaming %s to %s\n", + ProgramName, tn, n); + perror("rename"); + unlink(tn); + return (-2); + } + log_it(RealUser, Pid, "REPLACE", User); + + poke_daemon(); + + return (0); +} + + +static void +poke_daemon() { +#ifdef USE_UTIMES + struct timeval tvs[2]; + struct timezone tz; + + (void) gettimeofday(&tvs[0], &tz); + tvs[1] = tvs[0]; + if (utimes(SPOOL_DIR, tvs) < OK) { + fprintf(stderr, "crontab: can't update mtime on spooldir\n"); + perror(SPOOL_DIR); + return; + } +#else + if (utime(SPOOL_DIR, NULL) < OK) { + fprintf(stderr, "crontab: can't update mtime on spooldir\n"); + perror(SPOOL_DIR); + return; + } +#endif /*USE_UTIMES*/ +} diff --git a/usr.sbin/cron/database.c b/usr.sbin/cron/database.c new file mode 100644 index 00000000000..99c657e9e78 --- /dev/null +++ b/usr.sbin/cron/database.c @@ -0,0 +1,261 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: database.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + +/* vix 26jan87 [RCS has the log] + */ + + +#include "cron.h" +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/file.h> + + +#define TMAX(a,b) ((a)>(b)?(a):(b)) + + +static void process_crontab __P((char *, char *, char *, + struct stat *, + cron_db *, cron_db *)); + + +void +load_database(old_db) + cron_db *old_db; +{ + DIR *dir; + struct stat statbuf; + struct stat syscron_stat; + DIR_T *dp; + cron_db new_db; + user *u, *nu; + + Debug(DLOAD, ("[%d] load_database()\n", getpid())) + + /* before we start loading any data, do a stat on SPOOL_DIR + * so that if anything changes as of this moment (i.e., before we've + * cached any of the database), we'll see the changes next time. + */ + if (stat(SPOOL_DIR, &statbuf) < OK) { + log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR); + (void) exit(ERROR_EXIT); + } + + /* track system crontab file + */ + if (stat(SYSCRONTAB, &syscron_stat) < OK) + syscron_stat.st_mtime = 0; + + /* if spooldir's mtime has not changed, we don't need to fiddle with + * the database. + * + * Note that old_db->mtime is initialized to 0 in main(), and + * so is guaranteed to be different than the stat() mtime the first + * time this function is called. + */ + if (old_db->mtime == TMAX(statbuf.st_mtime, syscron_stat.st_mtime)) { + Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n", + getpid())) + return; + } + + /* something's different. make a new database, moving unchanged + * elements from the old database, reloading elements that have + * actually changed. Whatever is left in the old database when + * we're done is chaff -- crontabs that disappeared. + */ + new_db.mtime = TMAX(statbuf.st_mtime, syscron_stat.st_mtime); + new_db.head = new_db.tail = NULL; + + if (syscron_stat.st_mtime) { + process_crontab("root", "*system*", + SYSCRONTAB, &syscron_stat, + &new_db, old_db); + } + + /* we used to keep this dir open all the time, for the sake of + * efficiency. however, we need to close it in every fork, and + * we fork a lot more often than the mtime of the dir changes. + */ + if (!(dir = opendir(SPOOL_DIR))) { + log_it("CRON", getpid(), "OPENDIR FAILED", SPOOL_DIR); + (void) exit(ERROR_EXIT); + } + + while (NULL != (dp = readdir(dir))) { + char fname[MAXNAMLEN+1], + tabname[MAXNAMLEN+1]; + + /* avoid file names beginning with ".". this is good + * because we would otherwise waste two guaranteed calls + * to getpwnam() for . and .., and also because user names + * starting with a period are just too nasty to consider. + */ + if (dp->d_name[0] == '.') + continue; + + (void) strcpy(fname, dp->d_name); + sprintf(tabname, CRON_TAB(fname)); + + process_crontab(fname, fname, tabname, + &statbuf, &new_db, old_db); + } + closedir(dir); + + /* if we don't do this, then when our children eventually call + * getpwnam() in do_command.c's child_process to verify MAILTO=, + * they will screw us up (and v-v). + */ + endpwent(); + + /* whatever's left in the old database is now junk. + */ + Debug(DLOAD, ("unlinking old database:\n")) + for (u = old_db->head; u != NULL; u = nu) { + Debug(DLOAD, ("\t%s\n", u->name)) + nu = u->next; + unlink_user(old_db, u); + free_user(u); + } + + /* overwrite the database control block with the new one. + */ + *old_db = new_db; + Debug(DLOAD, ("load_database is done\n")) +} + + +void +link_user(db, u) + cron_db *db; + user *u; +{ + if (db->head == NULL) + db->head = u; + if (db->tail) + db->tail->next = u; + u->prev = db->tail; + u->next = NULL; + db->tail = u; +} + + +void +unlink_user(db, u) + cron_db *db; + user *u; +{ + if (u->prev == NULL) + db->head = u->next; + else + u->prev->next = u->next; + + if (u->next == NULL) + db->tail = u->prev; + else + u->next->prev = u->prev; +} + + +user * +find_user(db, name) + cron_db *db; + char *name; +{ + char *env_get(); + user *u; + + for (u = db->head; u != NULL; u = u->next) + if (!strcmp(u->name, name)) + break; + return u; +} + + +static void +process_crontab(uname, fname, tabname, statbuf, new_db, old_db) + char *uname; + char *fname; + char *tabname; + struct stat *statbuf; + cron_db *new_db; + cron_db *old_db; +{ + struct passwd *pw = NULL; + int crontab_fd = OK - 1; + user *u; + + if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) { + /* file doesn't have a user in passwd file. + */ + log_it(fname, getpid(), "ORPHAN", "no passwd entry"); + goto next_crontab; + } + + if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { + /* crontab not accessible? + */ + log_it(fname, getpid(), "CAN'T OPEN", tabname); + goto next_crontab; + } + + if (fstat(crontab_fd, statbuf) < OK) { + log_it(fname, getpid(), "FSTAT FAILED", tabname); + goto next_crontab; + } + + Debug(DLOAD, ("\t%s:", fname)) + u = find_user(old_db, fname); + if (u != NULL) { + /* if crontab has not changed since we last read it + * in, then we can just use our existing entry. + */ + if (u->mtime == statbuf->st_mtime) { + Debug(DLOAD, (" [no change, using old data]")) + unlink_user(old_db, u); + link_user(new_db, u); + goto next_crontab; + } + + /* before we fall through to the code that will reload + * the user, let's deallocate and unlink the user in + * the old database. This is more a point of memory + * efficiency than anything else, since all leftover + * users will be deleted from the old database when + * we finish with the crontab... + */ + Debug(DLOAD, (" [delete old data]")) + unlink_user(old_db, u); + free_user(u); + log_it(fname, getpid(), "RELOAD", tabname); + } + u = load_user(crontab_fd, pw, fname); + if (u != NULL) { + u->mtime = statbuf->st_mtime; + link_user(new_db, u); + } + +next_crontab: + if (crontab_fd >= OK) { + Debug(DLOAD, (" [done]\n")) + close(crontab_fd); + } +} diff --git a/usr.sbin/cron/do_command.c b/usr.sbin/cron/do_command.c new file mode 100644 index 00000000000..d28a399912e --- /dev/null +++ b/usr.sbin/cron/do_command.c @@ -0,0 +1,501 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: do_command.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + + +#include "cron.h" +#include <sys/signal.h> +#if defined(sequent) +# include <sys/universe.h> +#endif +#if defined(SYSLOG) +# include <syslog.h> +#endif + + +static void child_process __P((entry *, user *)), + do_univ __P((user *)); + + +void +do_command(e, u) + entry *e; + user *u; +{ + Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n", + getpid(), e->cmd, u->name, e->uid, e->gid)) + + /* fork to become asynchronous -- parent process is done immediately, + * and continues to run the normal cron code, which means return to + * tick(). the child and grandchild don't leave this function, alive. + * + * vfork() is unsuitable, since we have much to do, and the parent + * needs to be able to run off and fork other processes. + */ + switch (fork()) { + case -1: + log_it("CRON",getpid(),"error","can't fork"); + break; + case 0: + /* child process */ + acquire_daemonlock(1); + child_process(e, u); + Debug(DPROC, ("[%d] child process done, exiting\n", getpid())) + _exit(OK_EXIT); + break; + default: + /* parent process */ + break; + } + Debug(DPROC, ("[%d] main process returning to work\n", getpid())) +} + + +static void +child_process(e, u) + entry *e; + user *u; +{ + int stdin_pipe[2], stdout_pipe[2]; + register char *input_data; + char *usernm, *mailto; + int children = 0; + + Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), e->cmd)) + + /* mark ourselves as different to PS command watchers by upshifting + * our program name. This has no effect on some kernels. + */ + /*local*/{ + register char *pch; + + for (pch = ProgramName; *pch; pch++) + *pch = MkUpper(*pch); + } + + /* discover some useful and important environment settings + */ + usernm = env_get("LOGNAME", e->envp); + mailto = env_get("MAILTO", e->envp); + +#ifdef USE_SIGCHLD + /* our parent is watching for our death by catching SIGCHLD. we + * do not care to watch for our children's deaths this way -- we + * use wait() explictly. so we have to disable the signal (which + * was inherited from the parent). + */ + (void) signal(SIGCHLD, SIG_IGN); +#else + /* on system-V systems, we are ignoring SIGCLD. we have to stop + * ignoring it now or the wait() in cron_pclose() won't work. + * because of this, we have to wait() for our children here, as well. + */ + (void) signal(SIGCLD, SIG_DFL); +#endif /*BSD*/ + + /* create some pipes to talk to our future child + */ + pipe(stdin_pipe); /* child's stdin */ + pipe(stdout_pipe); /* child's stdout */ + + /* since we are a forked process, we can diddle the command string + * we were passed -- nobody else is going to use it again, right? + * + * if a % is present in the command, previous characters are the + * command, and subsequent characters are the additional input to + * the command. Subsequent %'s will be transformed into newlines, + * but that happens later. + */ + /*local*/{ + register int escaped = FALSE; + register int ch; + + for (input_data = e->cmd; ch = *input_data; input_data++) { + if (escaped) { + escaped = FALSE; + continue; + } + if (ch == '\\') { + escaped = TRUE; + continue; + } + if (ch == '%') { + *input_data++ = '\0'; + break; + } + } + } + + /* fork again, this time so we can exec the user's command. + */ + switch (vfork()) { + case -1: + log_it("CRON",getpid(),"error","can't vfork"); + exit(ERROR_EXIT); + /*NOTREACHED*/ + case 0: + Debug(DPROC, ("[%d] grandchild process Vfork()'ed\n", + getpid())) + + /* write a log message. we've waited this long to do it + * because it was not until now that we knew the PID that + * the actual user command shell was going to get and the + * PID is part of the log message. + */ + /*local*/{ + char *x = mkprints((u_char *)e->cmd, strlen(e->cmd)); + + log_it(usernm, getpid(), "CMD", x); + free(x); + } + + /* that's the last thing we'll log. close the log files. + */ +#ifdef SYSLOG + closelog(); +#endif + + /* get new pgrp, void tty, etc. + */ + (void) setsid(); + + /* close the pipe ends that we won't use. this doesn't affect + * the parent, who has to read and write them; it keeps the + * kernel from recording us as a potential client TWICE -- + * which would keep it from sending SIGPIPE in otherwise + * appropriate circumstances. + */ + close(stdin_pipe[WRITE_PIPE]); + close(stdout_pipe[READ_PIPE]); + + /* grandchild process. make std{in,out} be the ends of + * pipes opened by our daddy; make stderr go to stdout. + */ + close(STDIN); dup2(stdin_pipe[READ_PIPE], STDIN); + close(STDOUT); dup2(stdout_pipe[WRITE_PIPE], STDOUT); + close(STDERR); dup2(STDOUT, STDERR); + + /* close the pipes we just dup'ed. The resources will remain. + */ + close(stdin_pipe[READ_PIPE]); + close(stdout_pipe[WRITE_PIPE]); + + /* set our login universe. Do this in the grandchild + * so that the child can invoke /usr/lib/sendmail + * without surprises. + */ + do_univ(u); + + /* set our directory, uid and gid. Set gid first, since once + * we set uid, we've lost root privledges. + */ + setgid(e->gid); +# if defined(BSD) + initgroups(env_get("LOGNAME", e->envp), e->gid); +# endif + setuid(e->uid); /* we aren't root after this... */ + chdir(env_get("HOME", e->envp)); + + /* exec the command. + */ + { + char *shell = env_get("SHELL", e->envp); + +# if DEBUGGING + if (DebugFlags & DTEST) { + fprintf(stderr, + "debug DTEST is on, not exec'ing command.\n"); + fprintf(stderr, + "\tcmd='%s' shell='%s'\n", e->cmd, shell); + _exit(OK_EXIT); + } +# endif /*DEBUGGING*/ + execle(shell, shell, "-c", e->cmd, (char *)0, e->envp); + fprintf(stderr, "execl: couldn't exec `%s'\n", shell); + perror("execl"); + _exit(ERROR_EXIT); + } + break; + default: + /* parent process */ + break; + } + + children++; + + /* middle process, child of original cron, parent of process running + * the user's command. + */ + + Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid())) + + /* close the ends of the pipe that will only be referenced in the + * grandchild process... + */ + close(stdin_pipe[READ_PIPE]); + close(stdout_pipe[WRITE_PIPE]); + + /* + * write, to the pipe connected to child's stdin, any input specified + * after a % in the crontab entry. while we copy, convert any + * additional %'s to newlines. when done, if some characters were + * written and the last one wasn't a newline, write a newline. + * + * Note that if the input data won't fit into one pipe buffer (2K + * or 4K on most BSD systems), and the child doesn't read its stdin, + * we would block here. thus we must fork again. + */ + + if (*input_data && fork() == 0) { + register FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w"); + register int need_newline = FALSE; + register int escaped = FALSE; + register int ch; + + Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid())) + + /* close the pipe we don't use, since we inherited it and + * are part of its reference count now. + */ + close(stdout_pipe[READ_PIPE]); + + /* translation: + * \% -> % + * % -> \n + * \x -> \x for all x != % + */ + while (ch = *input_data++) { + if (escaped) { + if (ch != '%') + putc('\\', out); + } else { + if (ch == '%') + ch = '\n'; + } + + if (!(escaped = (ch == '\\'))) { + putc(ch, out); + need_newline = (ch != '\n'); + } + } + if (escaped) + putc('\\', out); + if (need_newline) + putc('\n', out); + + /* close the pipe, causing an EOF condition. fclose causes + * stdin_pipe[WRITE_PIPE] to be closed, too. + */ + fclose(out); + + Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid())) + exit(0); + } + + /* close the pipe to the grandkiddie's stdin, since its wicked uncle + * ernie back there has it open and will close it when he's done. + */ + close(stdin_pipe[WRITE_PIPE]); + + children++; + + /* + * read output from the grandchild. it's stderr has been redirected to + * it's stdout, which has been redirected to our pipe. if there is any + * output, we'll be mailing it to the user whose crontab this is... + * when the grandchild exits, we'll get EOF. + */ + + Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid())) + + /*local*/{ + register FILE *in = fdopen(stdout_pipe[READ_PIPE], "r"); + register int ch = getc(in); + + if (ch != EOF) { + register FILE *mail; + register int bytes = 1; + int status = 0; + + Debug(DPROC|DEXT, + ("[%d] got data (%x:%c) from grandchild\n", + getpid(), ch, ch)) + + /* get name of recipient. this is MAILTO if set to a + * valid local username; USER otherwise. + */ + if (mailto) { + /* MAILTO was present in the environment + */ + if (!*mailto) { + /* ... but it's empty. set to NULL + */ + mailto = NULL; + } + } else { + /* MAILTO not present, set to USER. + */ + mailto = usernm; + } + + /* if we are supposed to be mailing, MAILTO will + * be non-NULL. only in this case should we set + * up the mail command and subjects and stuff... + */ + + if (mailto) { + register char **env; + auto char mailcmd[MAX_COMMAND]; + auto char hostname[MAXHOSTNAMELEN]; + + (void) gethostname(hostname, MAXHOSTNAMELEN); + (void) snprintf(mailcmd, sizeof(mailcmd), + MAILARGS, MAILCMD); + if (!(mail = cron_popen(mailcmd, "w"))) { + perror(MAILCMD); + (void) _exit(ERROR_EXIT); + } + fprintf(mail, "From: root (Cron Daemon)\n"); + fprintf(mail, "To: %s\n", mailto); + fprintf(mail, "Subject: Cron <%s@%s> %s\n", + usernm, first_word(hostname, "."), + e->cmd); +# if defined(MAIL_DATE) + fprintf(mail, "Date: %s\n", + arpadate(&TargetTime)); +# endif /* MAIL_DATE */ + for (env = e->envp; *env; env++) + fprintf(mail, "X-Cron-Env: <%s>\n", + *env); + fprintf(mail, "\n"); + + /* this was the first char from the pipe + */ + putc(ch, mail); + } + + /* we have to read the input pipe no matter whether + * we mail or not, but obviously we only write to + * mail pipe if we ARE mailing. + */ + + while (EOF != (ch = getc(in))) { + bytes++; + if (mailto) + putc(ch, mail); + } + + /* only close pipe if we opened it -- i.e., we're + * mailing... + */ + + if (mailto) { + Debug(DPROC, ("[%d] closing pipe to mail\n", + getpid())) + /* Note: the pclose will probably see + * the termination of the grandchild + * in addition to the mail process, since + * it (the grandchild) is likely to exit + * after closing its stdout. + */ + status = cron_pclose(mail); + } + + /* if there was output and we could not mail it, + * log the facts so the poor user can figure out + * what's going on. + */ + if (mailto && status) { + char buf[MAX_TEMPSTR]; + + sprintf(buf, + "mailed %d byte%s of output but got status 0x%04x\n", + bytes, (bytes==1)?"":"s", + status); + log_it(usernm, getpid(), "MAIL", buf); + } + + } /*if data from grandchild*/ + + Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid())) + + fclose(in); /* also closes stdout_pipe[READ_PIPE] */ + } + + /* wait for children to die. + */ + for (; children > 0; children--) + { + WAIT_T waiter; + PID_T pid; + + Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n", + getpid(), children)) + pid = wait(&waiter); + if (pid < OK) { + Debug(DPROC, ("[%d] no more grandchildren--mail written?\n", + getpid())) + break; + } + Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x", + getpid(), pid, WEXITSTATUS(waiter))) + if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) + Debug(DPROC, (", dumped core")) + Debug(DPROC, ("\n")) + } +} + + +static void +do_univ(u) + user *u; +{ +#if defined(sequent) +/* Dynix (Sequent) hack to put the user associated with + * the passed user structure into the ATT universe if + * necessary. We have to dig the gecos info out of + * the user's password entry to see if the magic + * "universe(att)" string is present. + */ + + struct passwd *p; + char *s; + int i; + + p = getpwuid(u->uid); + (void) endpwent(); + + if (p == NULL) + return; + + s = p->pw_gecos; + + for (i = 0; i < 4; i++) + { + if ((s = strchr(s, ',')) == NULL) + return; + s++; + } + if (strcmp(s, "universe(att)")) + return; + + (void) universe(U_ATT); +#endif +} diff --git a/usr.sbin/cron/entry.c b/usr.sbin/cron/entry.c new file mode 100644 index 00000000000..448924ada2f --- /dev/null +++ b/usr.sbin/cron/entry.c @@ -0,0 +1,507 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: entry.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + +/* vix 26jan87 [RCS'd; rest of log is in RCS file] + * vix 01jan87 [added line-level error recovery] + * vix 31dec86 [added /step to the from-to range, per bob@acornrc] + * vix 30dec86 [written] + */ + + +#include "cron.h" + + +typedef enum ecode { + e_none, e_minute, e_hour, e_dom, e_month, e_dow, + e_cmd, e_timespec, e_username +} ecode_e; + +static char get_list __P((bitstr_t *, int, int, char *[], int, FILE *)), + get_range __P((bitstr_t *, int, int, char *[], int, FILE *)), + get_number __P((int *, int, char *[], int, FILE *)); +static int set_element __P((bitstr_t *, int, int, int)); + +static char *ecodes[] = + { + "no error", + "bad minute", + "bad hour", + "bad day-of-month", + "bad month", + "bad day-of-week", + "bad command", + "bad time specifier", + "bad username", + }; + + +void +free_entry(e) + entry *e; +{ + free(e->cmd); + env_free(e->envp); + free(e); +} + + +/* return NULL if eof or syntax error occurs; + * otherwise return a pointer to a new entry. + */ +entry * +load_entry(file, error_func, pw, envp) + FILE *file; + void (*error_func)(); + struct passwd *pw; + char **envp; +{ + /* this function reads one crontab entry -- the next -- from a file. + * it skips any leading blank lines, ignores comments, and returns + * EOF if for any reason the entry can't be read and parsed. + * + * the entry is also parsed here. + * + * syntax: + * user crontab: + * minutes hours doms months dows cmd\n + * system crontab (/etc/crontab): + * minutes hours doms months dows USERNAME cmd\n + */ + + ecode_e ecode = e_none; + entry *e; + int ch; + char cmd[MAX_COMMAND]; + char envstr[MAX_ENVSTR]; + + Debug(DPARS, ("load_entry()...about to eat comments\n")) + + skip_comments(file); + + ch = get_char(file); + if (ch == EOF) + return NULL; + + /* ch is now the first useful character of a useful line. + * it may be an @special or it may be the first character + * of a list of minutes. + */ + + e = (entry *) calloc(sizeof(entry), sizeof(char)); + + if (ch == '@') { + /* all of these should be flagged and load-limited; i.e., + * instead of @hourly meaning "0 * * * *" it should mean + * "close to the front of every hour but not 'til the + * system load is low". Problems are: how do you know + * what "low" means? (save me from /etc/cron.conf!) and: + * how to guarantee low variance (how low is low?), which + * means how to we run roughly every hour -- seems like + * we need to keep a history or let the first hour set + * the schedule, which means we aren't load-limited + * anymore. too much for my overloaded brain. (vix, jan90) + * HINT + */ + ch = get_string(cmd, MAX_COMMAND, file, " \t\n"); + if (!strcmp("reboot", cmd)) { + e->flags |= WHEN_REBOOT; + } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){ + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_set(e->dom, 0); + bit_set(e->month, 0); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + } else if (!strcmp("monthly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_set(e->dom, 0); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + } else if (!strcmp("weekly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_set(e->dow, 0); + } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, 0); + bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + } else if (!strcmp("hourly", cmd)) { + bit_set(e->minute, 0); + bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1)); + bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1)); + bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1)); + bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1)); + } else { + ecode = e_timespec; + goto eof; + } + } else { + Debug(DPARS, ("load_entry()...about to parse numerics\n")) + + ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE, + PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_minute; + goto eof; + } + + /* hours + */ + + ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR, + PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_hour; + goto eof; + } + + /* DOM (days of month) + */ + + if (ch == '*') + e->flags |= DOM_STAR; + ch = get_list(e->dom, FIRST_DOM, LAST_DOM, + PPC_NULL, ch, file); + if (ch == EOF) { + ecode = e_dom; + goto eof; + } + + /* month + */ + + ch = get_list(e->month, FIRST_MONTH, LAST_MONTH, + MonthNames, ch, file); + if (ch == EOF) { + ecode = e_month; + goto eof; + } + + /* DOW (days of week) + */ + + if (ch == '*') + e->flags |= DOW_STAR; + ch = get_list(e->dow, FIRST_DOW, LAST_DOW, + DowNames, ch, file); + if (ch == EOF) { + ecode = e_dow; + goto eof; + } + } + + /* make sundays equivilent */ + if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) { + bit_set(e->dow, 0); + bit_set(e->dow, 7); + } + + /* ch is the first character of a command, or a username */ + unget_char(ch, file); + + if (!pw) { + char *username = cmd; /* temp buffer */ + + Debug(DPARS, ("load_entry()...about to parse username\n")) + ch = get_string(username, MAX_COMMAND, file, " \t"); + + Debug(DPARS, ("load_entry()...got %s\n",username)) + if (ch == EOF) { + ecode = e_cmd; + goto eof; + } + + pw = getpwnam(username); + if (pw == NULL) { + ecode = e_username; + goto eof; + } + Debug(DPARS, ("load_entry()...uid %d, gid %d\n",e->uid,e->gid)) + } + + e->uid = pw->pw_uid; + e->gid = pw->pw_gid; + + /* copy and fix up environment. some variables are just defaults and + * others are overrides. + */ + e->envp = env_copy(envp); + if (!env_get("SHELL", e->envp)) { + sprintf(envstr, "SHELL=%s", _PATH_BSHELL); + e->envp = env_set(e->envp, envstr); + } + if (!env_get("HOME", e->envp)) { + sprintf(envstr, "HOME=%s", pw->pw_dir); + e->envp = env_set(e->envp, envstr); + } + if (!env_get("PATH", e->envp)) { + sprintf(envstr, "PATH=%s", _PATH_DEFPATH); + e->envp = env_set(e->envp, envstr); + } + sprintf(envstr, "%s=%s", "LOGNAME", pw->pw_name); + e->envp = env_set(e->envp, envstr); +#if defined(BSD) + sprintf(envstr, "%s=%s", "USER", pw->pw_name); + e->envp = env_set(e->envp, envstr); +#endif + + Debug(DPARS, ("load_entry()...about to parse command\n")) + + /* Everything up to the next \n or EOF is part of the command... + * too bad we don't know in advance how long it will be, since we + * need to malloc a string for it... so, we limit it to MAX_COMMAND. + * XXX - should use realloc(). + */ + ch = get_string(cmd, MAX_COMMAND, file, "\n"); + + /* a file without a \n before the EOF is rude, so we'll complain... + */ + if (ch == EOF) { + ecode = e_cmd; + goto eof; + } + + /* got the command in the 'cmd' string; save it in *e. + */ + e->cmd = strdup(cmd); + + Debug(DPARS, ("load_entry()...returning successfully\n")) + + /* success, fini, return pointer to the entry we just created... + */ + return e; + + eof: + free(e); + if (ecode != e_none && error_func) + (*error_func)(ecodes[(int)ecode]); + while (ch != EOF && ch != '\n') + ch = get_char(file); + return NULL; +} + + +static char +get_list(bits, low, high, names, ch, file) + bitstr_t *bits; /* one bit per flag, default=FALSE */ + int low, high; /* bounds, impl. offset for bitstr */ + char *names[]; /* NULL or *[] of names for these elements */ + int ch; /* current character being processed */ + FILE *file; /* file being read */ +{ + register int done; + + /* we know that we point to a non-blank character here; + * must do a Skip_Blanks before we exit, so that the + * next call (or the code that picks up the cmd) can + * assume the same thing. + */ + + Debug(DPARS|DEXT, ("get_list()...entered\n")) + + /* list = range {"," range} + */ + + /* clear the bit string, since the default is 'off'. + */ + bit_nclear(bits, 0, (high-low+1)); + + /* process all ranges + */ + done = FALSE; + while (!done) { + ch = get_range(bits, low, high, names, ch, file); + if (ch == ',') + ch = get_char(file); + else + done = TRUE; + } + + /* exiting. skip to some blanks, then skip over the blanks. + */ + Skip_Nonblanks(ch, file) + Skip_Blanks(ch, file) + + Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch)) + + return ch; +} + + +static char +get_range(bits, low, high, names, ch, file) + bitstr_t *bits; /* one bit per flag, default=FALSE */ + int low, high; /* bounds, impl. offset for bitstr */ + char *names[]; /* NULL or names of elements */ + int ch; /* current character being processed */ + FILE *file; /* file being read */ +{ + /* range = number | number "-" number [ "/" number ] + */ + + register int i; + auto int num1, num2, num3; + + Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n")) + + if (ch == '*') { + /* '*' means "first-last" but can still be modified by /step + */ + num1 = low; + num2 = high; + ch = get_char(file); + if (ch == EOF) + return EOF; + } else { + if (EOF == (ch = get_number(&num1, low, names, ch, file))) + return EOF; + + if (ch != '-') { + /* not a range, it's a single number. + */ + if (EOF == set_element(bits, low, high, num1)) + return EOF; + return ch; + } else { + /* eat the dash + */ + ch = get_char(file); + if (ch == EOF) + return EOF; + + /* get the number following the dash + */ + ch = get_number(&num2, low, names, ch, file); + if (ch == EOF) + return EOF; + } + } + + /* check for step size + */ + if (ch == '/') { + /* eat the slash + */ + ch = get_char(file); + if (ch == EOF) + return EOF; + + /* get the step size -- note: we don't pass the + * names here, because the number is not an + * element id, it's a step size. 'low' is + * sent as a 0 since there is no offset either. + */ + ch = get_number(&num3, 0, PPC_NULL, ch, file); + if (ch == EOF) + return EOF; + } else { + /* no step. default==1. + */ + num3 = 1; + } + + /* range. set all elements from num1 to num2, stepping + * by num3. (the step is a downward-compatible extension + * proposed conceptually by bob@acornrc, syntactically + * designed then implmented by paul vixie). + */ + for (i = num1; i <= num2; i += num3) + if (EOF == set_element(bits, low, high, i)) + return EOF; + + return ch; +} + + +static char +get_number(numptr, low, names, ch, file) + int *numptr; /* where does the result go? */ + int low; /* offset applied to result if symbolic enum used */ + char *names[]; /* symbolic names, if any, for enums */ + int ch; /* current character */ + FILE *file; /* source */ +{ + char temp[MAX_TEMPSTR], *pc; + int len, i, all_digits; + + /* collect alphanumerics into our fixed-size temp array + */ + pc = temp; + len = 0; + all_digits = TRUE; + while (isalnum(ch)) { + if (++len >= MAX_TEMPSTR) + return EOF; + + *pc++ = ch; + + if (!isdigit(ch)) + all_digits = FALSE; + + ch = get_char(file); + } + *pc = '\0'; + + /* try to find the name in the name list + */ + if (names) { + for (i = 0; names[i] != NULL; i++) { + Debug(DPARS|DEXT, + ("get_num, compare(%s,%s)\n", names[i], temp)) + if (!strcasecmp(names[i], temp)) { + *numptr = i+low; + return ch; + } + } + } + + /* no name list specified, or there is one and our string isn't + * in it. either way: if it's all digits, use its magnitude. + * otherwise, it's an error. + */ + if (all_digits) { + *numptr = atoi(temp); + return ch; + } + + return EOF; +} + + +static int +set_element(bits, low, high, number) + bitstr_t *bits; /* one bit per flag, default=FALSE */ + int low; + int high; + int number; +{ + Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number)) + + if (number < low || number > high) + return EOF; + + bit_set(bits, (number-low)); + return OK; +} diff --git a/usr.sbin/cron/env.c b/usr.sbin/cron/env.c new file mode 100644 index 00000000000..ae9e70470fc --- /dev/null +++ b/usr.sbin/cron/env.c @@ -0,0 +1,178 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: env.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + + +#include "cron.h" + + +char ** +env_init() +{ + register char **p = (char **) malloc(sizeof(char **)); + + p[0] = NULL; + return (p); +} + + +void +env_free(envp) + char **envp; +{ + char **p; + + for (p = envp; *p; p++) + free(*p); + free(envp); +} + + +char ** +env_copy(envp) + register char **envp; +{ + register int count, i; + register char **p; + + for (count = 0; envp[count] != NULL; count++) + ; + p = (char **) malloc((count+1) * sizeof(char *)); /* 1 for the NULL */ + for (i = 0; i < count; i++) + p[i] = strdup(envp[i]); + p[count] = NULL; + return (p); +} + + +char ** +env_set(envp, envstr) + char **envp; + char *envstr; +{ + register int count, found; + register char **p; + + /* + * count the number of elements, including the null pointer; + * also set 'found' to -1 or index of entry if already in here. + */ + found = -1; + for (count = 0; envp[count] != NULL; count++) { + if (!strcmp_until(envp[count], envstr, '=')) + found = count; + } + count++; /* for the NULL */ + + if (found != -1) { + /* + * it exists already, so just free the existing setting, + * save our new one there, and return the existing array. + */ + free(envp[found]); + envp[found] = strdup(envstr); + return (envp); + } + + /* + * it doesn't exist yet, so resize the array, move null pointer over + * one, save our string over the old null pointer, and return resized + * array. + */ + p = (char **) realloc((void *) envp, + (unsigned) ((count+1) * sizeof(char **))); + p[count] = p[count-1]; + p[count-1] = strdup(envstr); + return (p); +} + + +/* return ERR = end of file + * FALSE = not an env setting (file was repositioned) + * TRUE = was an env setting + */ +int +load_env(envstr, f) + char *envstr; + FILE *f; +{ + long filepos; + int fileline; + char name[MAX_TEMPSTR], val[MAX_ENVSTR]; + int fields; + + filepos = ftell(f); + fileline = LineNumber; + skip_comments(f); + if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n")) + return (ERR); + + Debug(DPARS, ("load_env, read <%s>\n", envstr)) + + name[0] = val[0] = '\0'; + fields = sscanf(envstr, "%[^ =] = %[^\n#]", name, val); + if (fields != 2) { + Debug(DPARS, ("load_env, not 2 fields (%d)\n", fields)) + fseek(f, filepos, 0); + Set_LineNum(fileline); + return (FALSE); + } + + /* 2 fields from scanf; looks like an env setting + */ + + /* + * process value string + */ + /*local*/{ + int len = strdtb(val); + + if (len >= 2) { + if (val[0] == '\'' || val[0] == '"') { + if (val[len-1] == val[0]) { + val[len-1] = '\0'; + (void) strcpy(val, val+1); + } + } + } + } + + (void) sprintf(envstr, "%s=%s", name, val); + Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr)) + return (TRUE); +} + + +char * +env_get(name, envp) + register char *name; + register char **envp; +{ + register int len = strlen(name); + register char *p, *q; + + while (p = *envp++) { + if (!(q = strchr(p, '='))) + continue; + if ((q - p) == len && !strncmp(p, name, len)) + return (q+1); + } + return (NULL); +} diff --git a/usr.sbin/cron/externs.h b/usr.sbin/cron/externs.h new file mode 100644 index 00000000000..3efe605897e --- /dev/null +++ b/usr.sbin/cron/externs.h @@ -0,0 +1,145 @@ +/* Copyright 1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if defined(POSIX) || defined(ATT) +# include <stdlib.h> +# include <unistd.h> +# include <string.h> +# include <dirent.h> +# define DIR_T struct dirent +# define WAIT_T int +# define WAIT_IS_INT 1 +extern char *tzname[2]; +# define TZONE(tm) tzname[(tm).tm_isdst] +#endif + +#if defined(UNIXPC) +# undef WAIT_T +# undef WAIT_IS_INT +# define WAIT_T union wait +#endif + +#if defined(POSIX) +# define SIG_T sig_t +# define TIME_T time_t +# define PID_T pid_t +#endif + +#if defined(ATT) +# define SIG_T void +# define TIME_T long +# define PID_T int +#endif + +#if !defined(POSIX) && !defined(ATT) +/* classic BSD */ +extern time_t time(); +extern unsigned sleep(); +extern struct tm *localtime(); +extern struct passwd *getpwnam(); +extern int errno; +extern void perror(), exit(), free(); +extern char *getenv(), *strcpy(), *strchr(), *strtok(); +extern void *malloc(), *realloc(); +# define SIG_T void +# define TIME_T long +# define PID_T int +# define WAIT_T union wait +# define DIR_T struct direct +# include <sys/dir.h> +# define TZONE(tm) (tm).tm_zone +#endif + +/* getopt() isn't part of POSIX. some systems define it in <stdlib.h> anyway. + * of those that do, some complain that our definition is different and some + * do not. to add to the misery and confusion, some systems define getopt() + * in ways that we cannot predict or comprehend, yet do not define the adjunct + * external variables needed for the interface. + */ +#if (!defined(BSD) || (BSD < 198911)) && !defined(ATT) && !defined(UNICOS) +int getopt __P((int, char * const *, const char *)); +#endif + +#if (!defined(BSD) || (BSD < 199103)) +extern char *optarg; +extern int optind, opterr, optopt; +#endif + +#if WAIT_IS_INT +# ifndef WEXITSTATUS +# define WEXITSTATUS(x) (((x) >> 8) & 0xff) +# endif +# ifndef WTERMSIG +# define WTERMSIG(x) ((x) & 0x7f) +# endif +# ifndef WCOREDUMP +# define WCOREDUMP(x) ((x) & 0x80) +# endif +#else /*WAIT_IS_INT*/ +# ifndef WEXITSTATUS +# define WEXITSTATUS(x) ((x).w_retcode) +# endif +# ifndef WTERMSIG +# define WTERMSIG(x) ((x).w_termsig) +# endif +# ifndef WCOREDUMP +# define WCOREDUMP(x) ((x).w_coredump) +# endif +#endif /*WAIT_IS_INT*/ + +#ifndef WIFSIGNALED +#define WIFSIGNALED(x) (WTERMSIG(x) != 0) +#endif +#ifndef WIFEXITED +#define WIFEXITED(x) (WTERMSIG(x) == 0) +#endif + +#ifdef NEED_STRCASECMP +extern int strcasecmp __P((char *, char *)); +#endif + +#ifdef NEED_STRDUP +extern char *strdup __P((char *)); +#endif + +#ifdef NEED_STRERROR +extern char *strerror __P((int)); +#endif + +#ifdef NEED_FLOCK +extern int flock __P((int, int)); +# define LOCK_SH 1 +# define LOCK_EX 2 +# define LOCK_NB 4 +# define LOCK_UN 8 +#endif + +#ifdef NEED_SETSID +extern int setsid __P((void)); +#endif + +#ifdef NEED_GETDTABLESIZE +extern int getdtablesize __P((void)); +#endif + +#ifdef NEED_SETENV +extern int setenv __P((char *, char *, int)); +#endif + +#ifdef NEED_VFORK +extern PID_T vfork __P((void)); +#endif diff --git a/usr.sbin/cron/job.c b/usr.sbin/cron/job.c new file mode 100644 index 00000000000..e1097cad102 --- /dev/null +++ b/usr.sbin/cron/job.c @@ -0,0 +1,74 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: job.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + + +#include "cron.h" + + +typedef struct _job { + struct _job *next; + entry *e; + user *u; +} job; + + +static job *jhead = NULL, *jtail = NULL; + + +void +job_add(e, u) + register entry *e; + register user *u; +{ + register job *j; + + /* if already on queue, keep going */ + for (j=jhead; j; j=j->next) + if (j->e == e && j->u == u) { return; } + + /* build a job queue element */ + j = (job*)malloc(sizeof(job)); + j->next = (job*) NULL; + j->e = e; + j->u = u; + + /* add it to the tail */ + if (!jhead) { jhead=j; } + else { jtail->next=j; } + jtail = j; +} + + +int +job_runqueue() +{ + register job *j, *jn; + register int run = 0; + + for (j=jhead; j; j=jn) { + do_command(j->e, j->u); + jn = j->next; + free(j); + run++; + } + jhead = jtail = NULL; + return run; +} diff --git a/usr.sbin/cron/misc.c b/usr.sbin/cron/misc.c new file mode 100644 index 00000000000..160a0cd1896 --- /dev/null +++ b/usr.sbin/cron/misc.c @@ -0,0 +1,664 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: misc.c,v 1.1 1995/10/18 08:47:30 deraadt Exp $"; +#endif + +/* vix 26jan87 [RCS has the rest of the log] + * vix 30dec86 [written] + */ + + +#include "cron.h" +#if SYS_TIME_H +# include <sys/time.h> +#else +# include <time.h> +#endif +#include <sys/file.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#if defined(SYSLOG) +# include <syslog.h> +#endif + + +#if defined(LOG_DAEMON) && !defined(LOG_CRON) +#define LOG_CRON LOG_DAEMON +#endif + + +static int LogFD = ERR; + + +int +strcmp_until(left, right, until) + char *left; + char *right; + int until; +{ + register int diff; + + while (*left && *left != until && *left == *right) { + left++; + right++; + } + + if ((*left=='\0' || *left == until) && + (*right=='\0' || *right == until)) { + diff = 0; + } else { + diff = *left - *right; + } + + return diff; +} + + +/* strdtb(s) - delete trailing blanks in string 's' and return new length + */ +int +strdtb(s) + char *s; +{ + char *x = s; + + /* scan forward to the null + */ + while (*x) + x++; + + /* scan backward to either the first character before the string, + * or the last non-blank in the string, whichever comes first. + */ + do {x--;} + while (x >= s && isspace(*x)); + + /* one character beyond where we stopped above is where the null + * goes. + */ + *++x = '\0'; + + /* the difference between the position of the null character and + * the position of the first character of the string is the length. + */ + return x - s; +} + + +int +set_debug_flags(flags) + char *flags; +{ + /* debug flags are of the form flag[,flag ...] + * + * if an error occurs, print a message to stdout and return FALSE. + * otherwise return TRUE after setting ERROR_FLAGS. + */ + +#if !DEBUGGING + + printf("this program was compiled without debugging enabled\n"); + return FALSE; + +#else /* DEBUGGING */ + + char *pc = flags; + + DebugFlags = 0; + + while (*pc) { + char **test; + int mask; + + /* try to find debug flag name in our list. + */ + for ( test = DebugFlagNames, mask = 1; + *test && strcmp_until(*test, pc, ','); + test++, mask <<= 1 + ) + ; + + if (!*test) { + fprintf(stderr, + "unrecognized debug flag <%s> <%s>\n", + flags, pc); + return FALSE; + } + + DebugFlags |= mask; + + /* skip to the next flag + */ + while (*pc && *pc != ',') + pc++; + if (*pc == ',') + pc++; + } + + if (DebugFlags) { + int flag; + + fprintf(stderr, "debug flags enabled:"); + + for (flag = 0; DebugFlagNames[flag]; flag++) + if (DebugFlags & (1 << flag)) + fprintf(stderr, " %s", DebugFlagNames[flag]); + fprintf(stderr, "\n"); + } + + return TRUE; + +#endif /* DEBUGGING */ +} + + +void +set_cron_uid() +{ +#if defined(BSD) || defined(POSIX) + if (seteuid(ROOT_UID) < OK) { + perror("seteuid"); + exit(ERROR_EXIT); + } +#else + if (setuid(ROOT_UID) < OK) { + perror("setuid"); + exit(ERROR_EXIT); + } +#endif +} + + +void +set_cron_cwd() +{ + struct stat sb; + + /* first check for CRONDIR ("/var/cron" or some such) + */ + if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { + perror(CRONDIR); + if (OK == mkdir(CRONDIR, 0700)) { + fprintf(stderr, "%s: created\n", CRONDIR); + stat(CRONDIR, &sb); + } else { + fprintf(stderr, "%s: ", CRONDIR); + perror("mkdir"); + exit(ERROR_EXIT); + } + } + if (!(sb.st_mode & S_IFDIR)) { + fprintf(stderr, "'%s' is not a directory, bailing out.\n", + CRONDIR); + exit(ERROR_EXIT); + } + if (chdir(CRONDIR) < OK) { + fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR); + perror(CRONDIR); + exit(ERROR_EXIT); + } + + /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) + */ + if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { + perror(SPOOL_DIR); + if (OK == mkdir(SPOOL_DIR, 0700)) { + fprintf(stderr, "%s: created\n", SPOOL_DIR); + stat(SPOOL_DIR, &sb); + } else { + fprintf(stderr, "%s: ", SPOOL_DIR); + perror("mkdir"); + exit(ERROR_EXIT); + } + } + if (!(sb.st_mode & S_IFDIR)) { + fprintf(stderr, "'%s' is not a directory, bailing out.\n", + SPOOL_DIR); + exit(ERROR_EXIT); + } +} + + +/* acquire_daemonlock() - write our PID into /etc/cron.pid, unless + * another daemon is already running, which we detect here. + * + * note: main() calls us twice; once before forking, once after. + * we maintain static storage of the file pointer so that we + * can rewrite our PID into the PIDFILE after the fork. + * + * it would be great if fflush() disassociated the file buffer. + */ +void +acquire_daemonlock(closeflag) + int closeflag; +{ + static FILE *fp = NULL; + + if (closeflag && fp) { + fclose(fp); + fp = NULL; + return; + } + + if (!fp) { + char pidfile[MAX_FNAME]; + char buf[MAX_TEMPSTR]; + int fd, otherpid; + + (void) sprintf(pidfile, PIDFILE, PIDDIR); + if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644))) + || (NULL == (fp = fdopen(fd, "r+"))) + ) { + sprintf(buf, "can't open or create %s: %s", + pidfile, strerror(errno)); + fprintf(stderr, "%s: %s\n", ProgramName, buf); + log_it("CRON", getpid(), "DEATH", buf); + exit(ERROR_EXIT); + } + + if (flock(fd, LOCK_EX|LOCK_NB) < OK) { + int save_errno = errno; + + fscanf(fp, "%d", &otherpid); + sprintf(buf, "can't lock %s, otherpid may be %d: %s", + pidfile, otherpid, strerror(save_errno)); + fprintf(stderr, "%s: %s\n", ProgramName, buf); + log_it("CRON", getpid(), "DEATH", buf); + exit(ERROR_EXIT); + } + + (void) fcntl(fd, F_SETFD, 1); + } + + rewind(fp); + fprintf(fp, "%d\n", getpid()); + fflush(fp); + (void) ftruncate(fileno(fp), ftell(fp)); + + /* abandon fd and fp even though the file is open. we need to + * keep it open and locked, but we don't need the handles elsewhere. + */ +} + +/* get_char(file) : like getc() but increment LineNumber on newlines + */ +int +get_char(file) + FILE *file; +{ + int ch; + + ch = getc(file); + if (ch == '\n') + Set_LineNum(LineNumber + 1) + return ch; +} + + +/* unget_char(ch, file) : like ungetc but do LineNumber processing + */ +void +unget_char(ch, file) + int ch; + FILE *file; +{ + ungetc(ch, file); + if (ch == '\n') + Set_LineNum(LineNumber - 1) +} + + +/* get_string(str, max, file, termstr) : like fgets() but + * (1) has terminator string which should include \n + * (2) will always leave room for the null + * (3) uses get_char() so LineNumber will be accurate + * (4) returns EOF or terminating character, whichever + */ +int +get_string(string, size, file, terms) + char *string; + int size; + FILE *file; + char *terms; +{ + int ch; + + while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) { + if (size > 1) { + *string++ = (char) ch; + size--; + } + } + + if (size > 0) + *string = '\0'; + + return ch; +} + + +/* skip_comments(file) : read past comment (if any) + */ +void +skip_comments(file) + FILE *file; +{ + int ch; + + while (EOF != (ch = get_char(file))) { + /* ch is now the first character of a line. + */ + + while (ch == ' ' || ch == '\t') + ch = get_char(file); + + if (ch == EOF) + break; + + /* ch is now the first non-blank character of a line. + */ + + if (ch != '\n' && ch != '#') + break; + + /* ch must be a newline or comment as first non-blank + * character on a line. + */ + + while (ch != '\n' && ch != EOF) + ch = get_char(file); + + /* ch is now the newline of a line which we're going to + * ignore. + */ + } + if (ch != EOF) + unget_char(ch, file); +} + + +/* int in_file(char *string, FILE *file) + * return TRUE if one of the lines in file matches string exactly, + * FALSE otherwise. + */ +static int +in_file(string, file) + char *string; + FILE *file; +{ + char line[MAX_TEMPSTR]; + + rewind(file); + while (fgets(line, MAX_TEMPSTR, file)) { + if (line[0] != '\0') + line[strlen(line)-1] = '\0'; + if (0 == strcmp(line, string)) + return TRUE; + } + return FALSE; +} + + +/* int allowed(char *username) + * returns TRUE if (ALLOW_FILE exists and user is listed) + * or (DENY_FILE exists and user is NOT listed) + * or (neither file exists but user=="root" so it's okay) + */ +int +allowed(username) + char *username; +{ + static int init = FALSE; + static FILE *allow, *deny; + + if (!init) { + init = TRUE; +#if defined(ALLOW_FILE) && defined(DENY_FILE) + allow = fopen(ALLOW_FILE, "r"); + deny = fopen(DENY_FILE, "r"); + Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) +#else + allow = NULL; + deny = NULL; +#endif + } + + if (allow) + return (in_file(username, allow)); + if (deny) + return (!in_file(username, deny)); + +#if defined(ALLOW_ONLY_ROOT) + return (strcmp(username, ROOT_USER) == 0); +#else + return TRUE; +#endif +} + + +void +log_it(username, xpid, event, detail) + char *username; + int xpid; + char *event; + char *detail; +{ + PID_T pid = xpid; +#if defined(LOG_FILE) + char *msg; + TIME_T now = time((TIME_T) 0); + register struct tm *t = localtime(&now); +#endif /*LOG_FILE*/ + +#if defined(SYSLOG) + static int syslog_open = 0; +#endif + +#if defined(LOG_FILE) + /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. + */ + msg = malloc(strlen(username) + + strlen(event) + + strlen(detail) + + MAX_TEMPSTR); + + if (LogFD < OK) { + LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); + if (LogFD < OK) { + fprintf(stderr, "%s: can't open log file\n", + ProgramName); + perror(LOG_FILE); + } else { + (void) fcntl(LogFD, F_SETFD, 1); + } + } + + /* we have to sprintf() it because fprintf() doesn't always write + * everything out in one chunk and this has to be atomically appended + * to the log file. + */ + sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", + username, + t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, + event, detail); + + /* we have to run strlen() because sprintf() returns (char*) on old BSD + */ + if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) { + if (LogFD >= OK) + perror(LOG_FILE); + fprintf(stderr, "%s: can't write to log file\n", ProgramName); + write(STDERR, msg, strlen(msg)); + } + + free(msg); +#endif /*LOG_FILE*/ + +#if defined(SYSLOG) + if (!syslog_open) { + /* we don't use LOG_PID since the pid passed to us by + * our client may not be our own. therefore we want to + * print the pid ourselves. + */ +# ifdef LOG_DAEMON + openlog(ProgramName, LOG_PID, LOG_CRON); +# else + openlog(ProgramName, LOG_PID); +# endif + syslog_open = TRUE; /* assume openlog success */ + } + + syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail); + +#endif /*SYSLOG*/ + +#if DEBUGGING + if (DebugFlags) { + fprintf(stderr, "log_it: (%s %d) %s (%s)\n", + username, pid, event, detail); + } +#endif +} + + +void +log_close() { + if (LogFD != ERR) { + close(LogFD); + LogFD = ERR; + } +} + + +/* two warnings: + * (1) this routine is fairly slow + * (2) it returns a pointer to static storage + */ +char * +first_word(s, t) + register char *s; /* string we want the first word of */ + register char *t; /* terminators, implicitly including \0 */ +{ + static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */ + static int retsel = 0; + register char *rb, *rp; + + /* select a return buffer */ + retsel = 1-retsel; + rb = &retbuf[retsel][0]; + rp = rb; + + /* skip any leading terminators */ + while (*s && (NULL != strchr(t, *s))) { + s++; + } + + /* copy until next terminator or full buffer */ + while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { + *rp++ = *s++; + } + + /* finish the return-string and return it */ + *rp = '\0'; + return rb; +} + + +/* warning: + * heavily ascii-dependent. + */ +void +mkprint(dst, src, len) + register char *dst; + register unsigned char *src; + register int len; +{ + while (len-- > 0) + { + register unsigned char ch = *src++; + + if (ch < ' ') { /* control character */ + *dst++ = '^'; + *dst++ = ch + '@'; + } else if (ch < 0177) { /* printable */ + *dst++ = ch; + } else if (ch == 0177) { /* delete/rubout */ + *dst++ = '^'; + *dst++ = '?'; + } else { /* parity character */ + sprintf(dst, "\\%03o", ch); + dst += 4; + } + } + *dst = '\0'; +} + + +/* warning: + * returns a pointer to malloc'd storage, you must call free yourself. + */ +char * +mkprints(src, len) + register unsigned char *src; + register unsigned int len; +{ + register char *dst = malloc(len*4 + 1); + + mkprint(dst, src, len); + + return dst; +} + + +#ifdef MAIL_DATE +/* Sat, 27 Feb 93 11:44:51 CST + * 123456789012345678901234567 + */ +char * +arpadate(clock) + time_t *clock; +{ + time_t t = clock ?*clock :time(0L); + struct tm *tm = localtime(&t); + static char ret[30]; /* zone name might be >3 chars */ + + (void) sprintf(ret, "%s, %2d %s %2d %02d:%02d:%02d %s", + DowNames[tm->tm_wday], + tm->tm_mday, + MonthNames[tm->tm_mon], + tm->tm_year, + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + TZONE(*tm)); + return ret; +} +#endif /*MAIL_DATE*/ + + +#ifdef HAVE_SAVED_UIDS +static int save_euid; +int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); } +int swap_uids_back() { return seteuid(save_euid); } +#else /*HAVE_SAVED_UIDS*/ +int swap_uids() { return setreuid(geteuid(), getuid()); } +int swap_uids_back() { return swap_uids(); } +#endif /*HAVE_SAVED_UIDS*/ diff --git a/usr.sbin/cron/pathnames.h b/usr.sbin/cron/pathnames.h new file mode 100644 index 00000000000..08a3933e152 --- /dev/null +++ b/usr.sbin/cron/pathnames.h @@ -0,0 +1,81 @@ +/* Copyright 1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +/* + * $Id: pathnames.h,v 1.1 1995/10/18 08:47:30 deraadt Exp $ + */ + +#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX) +# include <paths.h> +#endif /*BSD*/ + +#ifndef CRONDIR + /* CRONDIR is where crond(8) and crontab(1) both chdir + * to; SPOOL_DIR, ALLOW_FILE, DENY_FILE, and LOG_FILE + * are all relative to this directory. + */ +#define CRONDIR "/var/cron" +#endif + + /* SPOOLDIR is where the crontabs live. + * This directory will have its modtime updated + * whenever crontab(1) changes a crontab; this is + * the signal for crond(8) to look at each individual + * crontab file and reload those whose modtimes are + * newer than they were last time around (or which + * didn't exist last time around...) + */ +#define SPOOL_DIR "tabs" + + /* undefining these turns off their features. note + * that ALLOW_FILE and DENY_FILE must both be defined + * in order to enable the allow/deny code. If neither + * LOG_FILE or SYSLOG is defined, we don't log. If + * both are defined, we log both ways. + */ +#define ALLOW_FILE "allow" /*-*/ +#define DENY_FILE "deny" /*-*/ +/* #define LOG_FILE "log" /*-*/ + + /* where should the daemon stick its PID? + */ +#ifdef _PATH_VARRUN +# define PIDDIR _PATH_VARRUN +#else +# define PIDDIR "/etc/" +#endif +#define PIDFILE "%scron.pid" + + /* 4.3BSD-style crontab */ +#define SYSCRONTAB "/etc/crontab" + + /* what editor to use if no EDITOR or VISUAL + * environment variable specified. + */ +#if defined(_PATH_VI) +# define EDITOR _PATH_VI +#else +# define EDITOR "/usr/ucb/vi" +#endif + +#ifndef _PATH_BSHELL +# define _PATH_BSHELL "/bin/sh" +#endif + +#ifndef _PATH_DEFPATH +# define _PATH_DEFPATH "/usr/bin:/bin" +#endif diff --git a/usr.sbin/cron/popen.c b/usr.sbin/cron/popen.c new file mode 100644 index 00000000000..2c2f5dc6286 --- /dev/null +++ b/usr.sbin/cron/popen.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 1988 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* this came out of the ftpd sources; it's been modified to avoid the + * globbing stuff since we don't need it. also execvp instead of execv. + */ + +#ifndef lint +static char rcsid[] = "$Id: popen.c,v 1.1 1995/10/18 08:47:31 deraadt Exp $"; +static char sccsid[] = "@(#)popen.c 5.7 (Berkeley) 2/14/89"; +#endif /* not lint */ + +#include "cron.h" +#include <sys/signal.h> + + +#define MAX_ARGS 100 +#define WANT_GLOBBING 0 + +/* + * Special version of popen which avoids call to shell. This insures noone + * may create a pipe to a hidden program as a side effect of a list or dir + * command. + */ +static PID_T *pids; +static int fds; + +FILE * +cron_popen(program, type) + char *program, *type; +{ + register char *cp; + FILE *iop; + int argc, pdes[2]; + PID_T pid; + char *argv[MAX_ARGS + 1]; +#if WANT_GLOBBING + char **pop, *vv[2]; + int gargc; + char *gargv[1000]; + extern char **glob(), **copyblk(); +#endif + + if (*type != 'r' && *type != 'w' || type[1]) + return(NULL); + + if (!pids) { + if ((fds = getdtablesize()) <= 0) + return(NULL); + if (!(pids = (PID_T *)malloc((u_int)(fds * sizeof(PID_T))))) + return(NULL); + bzero((char *)pids, fds * sizeof(PID_T)); + } + if (pipe(pdes) < 0) + return(NULL); + + /* break up string into pieces */ + for (argc = 0, cp = program; argc < MAX_ARGS; cp = NULL) + if (!(argv[argc++] = strtok(cp, " \t\n"))) + break; + +#if WANT_GLOBBING + /* glob each piece */ + gargv[0] = argv[0]; + for (gargc = argc = 1; argv[argc]; argc++) { + if (!(pop = glob(argv[argc]))) { /* globbing failed */ + vv[0] = argv[argc]; + vv[1] = NULL; + pop = copyblk(vv); + } + argv[argc] = (char *)pop; /* save to free later */ + while (*pop && gargc < 1000) + gargv[gargc++] = *pop++; + } + gargv[gargc] = NULL; +#endif + + iop = NULL; + switch(pid = vfork()) { + case -1: /* error */ + (void)close(pdes[0]); + (void)close(pdes[1]); + goto pfree; + /* NOTREACHED */ + case 0: /* child */ + if (*type == 'r') { + if (pdes[1] != 1) { + dup2(pdes[1], 1); + dup2(pdes[1], 2); /* stderr, too! */ + (void)close(pdes[1]); + } + (void)close(pdes[0]); + } else { + if (pdes[0] != 0) { + dup2(pdes[0], 0); + (void)close(pdes[0]); + } + (void)close(pdes[1]); + } +#if WANT_GLOBBING + execvp(gargv[0], gargv); +#else + execvp(argv[0], argv); +#endif + _exit(1); + } + /* parent; assume fdopen can't fail... */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + pids[fileno(iop)] = pid; + +pfree: +#if WANT_GLOBBING + for (argc = 1; argv[argc] != NULL; argc++) { +/* blkfree((char **)argv[argc]); */ + free((char *)argv[argc]); + } +#endif + return(iop); +} + +int +cron_pclose(iop) + FILE *iop; +{ + register int fdes; + int omask; + WAIT_T stat_loc; + PID_T pid; + + /* + * pclose returns -1 if stream is not associated with a + * `popened' command, or, if already `pclosed'. + */ + if (pids == 0 || pids[fdes = fileno(iop)] == 0) + return(-1); + (void)fclose(iop); + omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); + while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1) + ; + (void)sigsetmask(omask); + pids[fdes] = 0; + return (pid == -1 ? -1 : WEXITSTATUS(stat_loc)); +} diff --git a/usr.sbin/cron/user.c b/usr.sbin/cron/user.c new file mode 100644 index 00000000000..e03d07861f6 --- /dev/null +++ b/usr.sbin/cron/user.c @@ -0,0 +1,102 @@ +/* Copyright 1988,1990,1993,1994 by Paul Vixie + * All rights reserved + * + * Distribute freely, except: don't remove my name from the source or + * documentation (don't take credit for my work), mark your changes (don't + * get me blamed for your possible bugs), don't alter or remove this + * notice. May be sold if buildable source is provided to buyer. No + * warrantee of any kind, express or implied, is included with this + * software; use at your own risk, responsibility for damages (if any) to + * anyone resulting from the use of this software rests entirely with the + * user. + * + * Send bug reports, bug fixes, enhancements, requests, flames, etc., and + * I'll try to keep a version up to date. I can be reached as follows: + * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul + */ + +#if !defined(lint) && !defined(LINT) +static char rcsid[] = "$Id: user.c,v 1.1 1995/10/18 08:47:31 deraadt Exp $"; +#endif + +/* vix 26jan87 [log is in RCS file] + */ + + +#include "cron.h" + + +void +free_user(u) + user *u; +{ + entry *e, *ne; + + free(u->name); + for (e = u->crontab; e != NULL; e = ne) { + ne = e->next; + free_entry(e); + } + free(u); +} + + +user * +load_user(crontab_fd, pw, name) + int crontab_fd; + struct passwd *pw; /* NULL implies syscrontab */ + char *name; +{ + char envstr[MAX_ENVSTR]; + FILE *file; + user *u; + entry *e; + int status; + char **envp; + + if (!(file = fdopen(crontab_fd, "r"))) { + perror("fdopen on crontab_fd in load_user"); + return NULL; + } + + Debug(DPARS, ("load_user()\n")) + + /* file is open. build user entry, then read the crontab file. + */ + u = (user *) malloc(sizeof(user)); + u->name = strdup(name); + u->crontab = NULL; + + /* + * init environment. this will be copied/augmented for each entry. + */ + envp = env_init(); + + /* + * load the crontab + */ + while ((status = load_env(envstr, file)) >= OK) { + switch (status) { + case ERR: + free_user(u); + u = NULL; + goto done; + case FALSE: + e = load_entry(file, NULL, pw, envp); + if (e) { + e->next = u->crontab; + u->crontab = e; + } + break; + case TRUE: + envp = env_set(envp, envstr); + break; + } + } + + done: + env_free(envp); + fclose(file); + Debug(DPARS, ("...load_user() done\n")) + return u; +} |